vercel/next.js

33 workflows · maturity 67% · 13 patterns · GitHub ↗

Security 10.46/100

Security dimensions

permissions
3.8
security scan
0
supply chain
6.7
secret handling
0
harden runner
0

Workflows (33)

build_and_deploy matrix .github/workflows/build_and_deploy.yml
Triggers
push, pull_request, workflow_dispatch
Runs on
ubuntu-latest, ubuntu-latest, ubuntu-latest, ${{ fromJSON(matrix.host) }}, self-hosted, linux, x64, metal, ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest
Jobs
deploy-target, build, generate-native-matrix, build-native, build-wasm, deploy-tarball, publishRelease, publish-turbopack-npm-packages, deployExamples, buildPassed, upload_turbopack_bytesize
Matrix
include, target→ ${{ fromJSON(needs.generate-native-matrix.outputs.matrix) }}, nodejs, web
Actions
mmastrac/mmm-matrix, ijjk/rust-cache
Commands
  • echo "${{ github.event.after }}"
  • npm i -g corepack@0.31 corepack enable
  • if [[ $(node ./scripts/check-is-release.js 2> /dev/null || :) == v* ]]; then echo "value=production" >> $GITHUB_OUTPUT elif [ '${{ github.ref }}' == 'refs/heads/canary' ] then echo "value=staging" >> $GITHUB_OUTPUT elif [ '${{ github.event_name }}' == 'workflow_dispatch' ] then echo "value=force-preview" >> $GITHUB_OUTPUT elif [[ $(node scripts/run-for-change.mjs --not --type docs --exec echo 'false') != 'false' ]]; then echo "value=skipped" >> $GITHUB_OUTPUT else echo "value=automated-preview" >> $GITHUB_OUTPUT fi
  • echo "Deploy target is '${{ steps.deploy-target.outputs.value }}'"
  • npm i -g corepack@0.31 corepack enable
  • echo STORE_PATH=$(pnpm store path) >> $GITHUB_OUTPUT
  • pnpm install
  • node scripts/set-preview-version.js "${{ github.sha }}" pnpm install --no-frozen-lockfile
View raw YAML
# Update all mentions of this name in vercel-packages when changing.
name: build-and-deploy

on:
  push:
    # Don't run when tags or graphite base branches are pushed
    branches-ignore:
      - 'graphite-base/**'
  # we need the preview tarball for deploy tests
  pull_request:
    types: [opened, synchronize]
  workflow_dispatch:

concurrency:
  # Limit concurrent runs to 1 per PR,
  # but allow concurrent runs on push if they potentially use different source code
  group: ${{ github.event_name == 'pull_request' && format('{0}-pr-{1}', github.workflow, github.ref_name) || format('{0}-sha-{1}', github.workflow, github.sha) }}
  cancel-in-progress: true

env:
  NAPI_CLI_VERSION: 2.18.4
  TURBO_VERSION: 2.8.11
  # --env-mode loose is a breaking change required with turbo 2.x since Strict mode is now the default
  # TODO: we should add the relevant envs later to to switch to strict mode
  TURBO_ARGS: '-v --env-mode loose --remote-cache-timeout 90 --summarize --log-order stream'
  NODE_LTS_VERSION: 20
  TURBO_TEAM: 'vercel'
  TURBO_CACHE: 'remote:rw'
  # Without this environment variable, rust-lld will fail because some dependencies defaults to newer version of macOS by default.
  #
  # See https://doc.rust-lang.org/rustc/platform-support/apple-darwin.html#os-version for more details
  MACOSX_DEPLOYMENT_TARGET: 11.0
  # Run GitHub Actions JS with Node.js 24 to avoid deprecation warnings
  # (needed until actions/checkout, actions/setup-node, etc. all default to node24)
  FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true

jobs:
  deploy-target:
    runs-on: ubuntu-latest
    # Don't trigger this job on `pull_request` events from upstream branches.
    # Those would already run this job on the `push` event
    if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork }}
    outputs:
      value: ${{ steps.deploy-target.outputs.value }}
    steps:
      - uses: actions/checkout@v6
        with:
          fetch-depth: 1
      - run: echo "${{ github.event.after }}"
      - name: Setup node
        uses: actions/setup-node@v6
        with:
          node-version: ${{ env.NODE_LTS_VERSION }}
          check-latest: true
      - name: Setup corepack
        run: |
          npm i -g corepack@0.31
          corepack enable
      - name: Determine deploy target
        # 'force-preview' performs a full preview build but only if acknowledged i.e. workflow_dispatch
        # 'automated-preview' for pushes on branches other than 'canary' for integration testing.
        # 'staging' for canary branch since that will eventually be published i.e. become the production build.
        id: deploy-target
        run: |
          if [[ $(node ./scripts/check-is-release.js 2> /dev/null || :) == v* ]];
          then
            echo "value=production" >> $GITHUB_OUTPUT
          elif [ '${{ github.ref }}' == 'refs/heads/canary' ]
          then
            echo "value=staging" >> $GITHUB_OUTPUT
          elif [ '${{ github.event_name }}' == 'workflow_dispatch' ]
          then
            echo "value=force-preview" >> $GITHUB_OUTPUT
          elif [[ $(node scripts/run-for-change.mjs --not --type docs --exec echo 'false') != 'false' ]];
          then
            echo "value=skipped" >> $GITHUB_OUTPUT
          else
            echo "value=automated-preview" >> $GITHUB_OUTPUT
          fi
      - name: Print deploy target
        run: echo "Deploy target is '${{ steps.deploy-target.outputs.value }}'"

  build:
    if: ${{ needs.deploy-target.outputs.value != 'skipped' }}
    needs:
      - deploy-target
    runs-on: ubuntu-latest
    env:
      NEXT_TELEMETRY_DISABLED: 1
      # we build a dev binary for use in CI so skip downloading
      # canary next-swc binaries in the monorepo
      NEXT_SKIP_NATIVE_POSTINSTALL: 1
    steps:
      - name: Setup node
        uses: actions/setup-node@v6
        with:
          node-version: ${{ env.NODE_LTS_VERSION }}
          check-latest: true
      - name: Setup corepack
        run: |
          npm i -g corepack@0.31
          corepack enable

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

      - id: get-store-path
        run: echo STORE_PATH=$(pnpm store path) >> $GITHUB_OUTPUT

      - uses: actions/cache@v5
        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.

      - run: pnpm install

      - name: Set preview version
        if: ${{ contains(fromJSON('["automated-preview","force-preview"]'), needs.deploy-target.outputs.value) }}
        run: |
          node scripts/set-preview-version.js "${{ github.sha }}"
          pnpm install --no-frozen-lockfile

      - run: pnpm run build

      - uses: actions/cache@v5
        timeout-minutes: 5
        id: cache-build
        with:
          path: ./*
          key: ${{ github.sha }}-${{ github.run_number }}-${{ github.run_attempt}}

  # Generate the build matrix for native binaries.
  # For automated-preview, only build linux/x86_64/gnu (the target we run automated tests against).
  generate-native-matrix:
    if: ${{ needs.deploy-target.outputs.value != 'skipped' }}
    needs:
      - deploy-target
    runs-on: ubuntu-latest
    outputs:
      matrix: ${{ steps.matrix.outputs.matrix }}
    steps:
      - uses: mmastrac/mmm-matrix@3edd85c30addba11887c770740309c979a446aa9 # v1
        id: matrix
        with:
          config: |
            deployTarget: ${{ needs.deploy-target.outputs.value }}
          input: |
            $if: "config.deployTarget != 'automated-preview' || (this.target == 'x86_64-unknown-linux-gnu')"
            target:
              $dynamic: "`${this.arch}-${this.vendor}-${this.sys}${this.abi ? '-' + this.abi : ''}`"
            build_task: build-native-release
            os:
              mac:
                host: "['self-hosted', 'macos', 'arm64']"
                arch: [x86_64, aarch64]
                vendor: apple
                sys: darwin
              windows:
                host: "['self-hosted', 'windows', 'x64']"
                vendor: pc
                sys: windows
                abi: msvc
                arch:
                  x86_64: {}
                  aarch64:
                    build_task: build-native-no-plugin-release
              linux:
                host: "['self-hosted', 'linux', 'x64', 'metal']"
                docker: next-swc-builder:latest
                vendor: unknown
                sys: linux
                abi:
                  gnu:
                    arch: [x86_64, aarch64]
                  musl:
                    arch: [x86_64, aarch64]

  # Build binaries for publishing
  build-native:
    if: ${{ needs.deploy-target.outputs.value != 'skipped' }}
    needs:
      - deploy-target
      - generate-native-matrix
    defaults:
      run:
        shell: bash -leo pipefail {0}

    strategy:
      fail-fast: false
      matrix:
        include: ${{ fromJSON(needs.generate-native-matrix.outputs.matrix) }}

    name: stable - ${{ matrix.target }} - node@20
    runs-on: ${{ fromJSON(matrix.host) }}
    timeout-minutes: 45
    steps:
      # Enable long paths on Windows to avoid MAX_PATH (260 char) errors
      # with deeply nested node_modules/.pnpm paths
      - name: Enable git long paths
        if: ${{ matrix.os == 'windows' }}
        run: git config --system core.longpaths true

      # we use checkout here instead of the build cache since
      # it can fail to restore in different OS'
      - uses: actions/checkout@v6
        with:
          # crates/next-napi-bindings/build.rs uses git-describe to find the most recent git tag. It's okay if
          # this fails, but fetch with enough depth that we're likely to find a recent tag.
          fetch-depth: 100

      - name: Setup node
        uses: actions/setup-node@v6
        if: ${{ !matrix.docker }}
        with:
          node-version: ${{ env.NODE_LTS_VERSION }}
          check-latest: true

      - name: Setup corepack
        run: |
          npm i -g corepack@0.31
          corepack enable

      # we always want to run this to set environment variables
      # (skip for docker builds - rust is already in the image)
      - name: Install Rust
        if: ${{ !matrix.docker }}
        uses: ./.github/actions/setup-rust
        with:
          targets: ${{ matrix.target }}

      - name: normalize versions
        run: node scripts/normalize-version-bump.js

      - name: Cache on ${{ github.ref_name }}
        if: ${{ !matrix.docker }}
        uses: ijjk/rust-cache@turbo-cache-v1.0.9
        with:
          save-if: 'true'
          cache-provider: 'turbo'
          shared-key: build-${{ matrix.target }}-${{ hashFiles('.cargo/config.toml') }}

      - name: Clear native build
        run: rm -rf packages/next-swc/native

      # Build or restore the Docker image via turbo-cache.js.
      - name: Build/restore Docker image
        if: ${{ matrix.docker }}
        run: node scripts/docker-image-cache.js

      # Try to restore previously-built native binary from turbo cache
      - name: pull build cache
        if: ${{ matrix.docker }}
        run: TURBO_VERSION=${TURBO_VERSION} node ./scripts/pull-turbo-cache.js ${{ matrix.target }}

      - name: check build exists
        if: ${{ matrix.docker }}
        run: if [ -f packages/next-swc/native/next-swc.*.node ]; then echo "BUILD_EXISTS=yes" >> $GITHUB_OUTPUT; else echo "BUILD_EXISTS=no" >> $GITHUB_OUTPUT; fi
        id: build-exists

      - name: Build in docker
        if: ${{ matrix.docker && steps.build-exists.outputs.BUILD_EXISTS == 'no' }}
        run: |
          docker run --rm \
            -e CI -e RUST_BACKTRACE -e CARGO_TERM_COLOR \
            -e CARGO_INCREMENTAL=0 -e CARGO_REGISTRIES_CRATES_IO_PROTOCOL -e TURBO_API \
            -e TURBO_TEAM -e TURBO_TOKEN -e TURBO_VERSION -e TURBO_CACHE="remote:rw" \
            -e TARGET="${{ matrix.target }}" \
            -e ABI="${{ matrix.abi }}" \
            -e ARCH="${{ matrix.arch }}" \
            -e BUILD_TASK="${{ matrix.build_task }}" \
            -v ${{ env.HOME }}/.cargo/git:/root/.cargo/git \
            -v ${{ env.HOME }}/.cargo/registry:/root/.cargo/registry \
            -v ${{ github.workspace }}:/build \
            -w /build \
            --entrypoint bash \
            ${{ matrix.docker }} \
            -xeo pipefail scripts/docker-native-build.sh

      - name: cache build
        if: ${{ matrix.docker && steps.build-exists.outputs.BUILD_EXISTS == 'no' }}
        run: pnpm dlx turbo@${TURBO_VERSION} run cache-build-native --force -- ${{ matrix.target }}

      - name: 'Build'
        if: ${{ !matrix.docker }}
        env:
          # We don't want incremental builds on CI
          CARGO_INCREMENTAL: 0
        run: |
          echo "Host arch: $(uname -m)"
          echo "Node arch: $(node -e 'console.log(process.arch)')"
          echo "Node binary: $(file $(which node))"
          echo "Rustc binary: $(file $(which rustc) || echo 'not found')"
          rustc --version --verbose || true
          node -v
          npm i -g "@napi-rs/cli@${NAPI_CLI_VERSION}"
          pnpm dlx turbo@${TURBO_VERSION} run ${{ matrix.build_task }} ${TURBO_ARGS} -- --target ${{ matrix.target }}
          if [ "${{ matrix.os }}" != "windows" ]; then
            strip -x packages/next-swc/native/next-swc.*.node
          fi

      - name: 'check build cache status'
        id: check-did-build
        run: if [[ ! -z $(ls packages/next-swc/native) ]]; then echo "DID_BUILD=true" >> $GITHUB_OUTPUT; fi

      - name: 'Report binary size'
        if: ${{ steps.check-did-build.outputs.DID_BUILD == 'true' }}
        run: |
          shopt -s nullglob
          for f in packages/next-swc/native/next-swc.*.node; do
            FILE="$f" node -e "const s=require('fs').statSync(process.env.FILE).size; console.log('::notice title=${{ matrix.target }} binary size::' + (s/1024/1024).toFixed(1) + ' MB (' + s + ' bytes)')"
          done

      # Try to upload metrics for Turbopack to datadog's CI pipeline execution
      - name: 'Collect turbopack build metrics'
        id: check-turbopack-bytesize
        if: ${{ steps.check-did-build.outputs.DID_BUILD == 'true' }}
        continue-on-error: true
        run: |
          mkdir -p ./turbopack-bin-size
          shopt -s nullglob
          for filename in packages/next-swc/native/next-swc.*.node; do
            # Strip out filename to extract target triple
            export FILENAME=$(basename ${filename})
            export FILENAME=${FILENAME#*.}
            export FILENAME=${FILENAME%.node}
            export BYTESIZE=$(wc -c < $filename | xargs)
            echo "Reporting $FILENAME:$BYTESIZE for Turbopack bytesize"
            echo "turbopack.bytesize.$FILENAME:$BYTESIZE" > ./turbopack-bin-size/${{ matrix.target }}
          done

      - name: Upload turbopack bytesize artifact
        if: ${{ steps.check-did-build.outputs.DID_BUILD == 'true' }}
        uses: actions/upload-artifact@v6
        with:
          name: turbopack-bytesize-${{ matrix.target }}
          path: turbopack-bin-size/*

      - name: Upload swc artifact
        uses: actions/upload-artifact@v6
        with:
          name: next-swc-binaries-${{ matrix.target }}
          path: packages/next-swc/native/next-swc.*.node

      - name: Upload turbo summary artifact
        if: ${{ !matrix.docker }}
        uses: actions/upload-artifact@v6
        with:
          name: turbo-run-summary-${{ matrix.target }}
          path: .turbo/runs

  build-wasm:
    if: ${{ needs.deploy-target.outputs.value != 'skipped' }}
    needs:
      - deploy-target
    strategy:
      matrix:
        target: [web, nodejs]

    runs-on:
      - 'self-hosted'
      - 'linux'
      - 'x64'
      - 'metal'

    steps:
      - uses: actions/checkout@v6

      - name: Setup node
        uses: actions/setup-node@v6
        with:
          node-version: ${{ env.NODE_LTS_VERSION }}
          check-latest: true
      - run: corepack enable

      - name: Install Rust
        uses: ./.github/actions/setup-rust
        with:
          targets: wasm32-unknown-unknown

      - name: Install wasm-pack
        run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh

      - name: normalize versions
        run: node scripts/normalize-version-bump.js

      - name: Build
        run: pnpm dlx turbo@${TURBO_VERSION} run build-wasm ${TURBO_ARGS} -- --target ${{ matrix.target }}

      - name: Add target to folder name
        run: '[[ -d "crates/wasm/pkg" ]] && mv crates/wasm/pkg crates/wasm/pkg-${{ matrix.target }} || ls crates/wasm'

      - name: Upload turbo summary artifact
        uses: actions/upload-artifact@v6
        with:
          name: turbo-run-summary-wasm-${{matrix.target}}
          path: .turbo/runs

      - name: Upload swc artifact
        uses: actions/upload-artifact@v6
        with:
          name: wasm-binaries-${{matrix.target}}
          path: crates/wasm/pkg-*

  deploy-tarball:
    if: ${{ needs.deploy-target.outputs.value != 'production' }}
    name: Deploy preview tarball
    runs-on: ubuntu-latest
    needs:
      - deploy-target
      - build
      - build-wasm
      - build-native
    steps:
      - name: Setup node
        uses: actions/setup-node@v6
        with:
          node-version: ${{ env.NODE_LTS_VERSION }}
          check-latest: true
      - name: Setup corepack
        run: |
          npm i -g corepack@0.31
          corepack enable

      # https://github.com/actions/virtual-environments/issues/1187
      - name: tune linux network
        run: sudo ethtool -K eth0 tx off rx off

      - uses: actions/cache@v5
        timeout-minutes: 5
        id: restore-build
        with:
          path: ./*
          # Cache includes repo checkout which is required for later scripts
          fail-on-cache-miss: true
          key: ${{ github.sha }}-${{ github.run_number }}-${{ github.run_attempt }}
          restore-keys: |
            ${{ github.sha }}-${{ github.run_number }}
            ${{ github.sha }}-${{ github.run_number }}-${{ github.run_attempt}}

      - uses: actions/download-artifact@v8
        with:
          pattern: next-swc-binaries-*
          merge-multiple: true
          path: packages/next-swc/native

      - uses: actions/download-artifact@v8
        with:
          pattern: wasm-binaries-*
          merge-multiple: true
          path: crates/wasm

      - name: Create tarballs
        # github.event.after is available on push and pull_request#synchronize events.
        # For workflow_dispatch events, github.sha is the head commit.
        run: node scripts/create-preview-tarballs.js "${{ github.event.after || github.sha }}" "${{ runner.temp }}/preview-tarballs"

      - name: Upload tarballs
        uses: actions/upload-artifact@v6
        with:
          # Update all mentions of this name in vercel-packages when changing.
          name: preview-tarballs
          path: ${{ runner.temp }}/preview-tarballs/*

  publishRelease:
    if: ${{ needs.deploy-target.outputs.value == 'production' }}
    name: Potentially publish release
    runs-on: ubuntu-latest
    needs:
      - deploy-target
      - build
      - build-wasm
      - build-native
    permissions:
      contents: write
      id-token: write
    env:
      NPM_TOKEN: ${{ secrets.NPM_TOKEN_ELEVATED }}
    steps:
      - name: Setup node
        uses: actions/setup-node@v6
        with:
          node-version: ${{ env.NODE_LTS_VERSION }}
          check-latest: true
      - name: Setup corepack
        run: |
          npm i -g corepack@0.31
          corepack enable

      # https://github.com/actions/virtual-environments/issues/1187
      - name: tune linux network
        run: sudo ethtool -K eth0 tx off rx off

      - uses: actions/cache@v5
        timeout-minutes: 5
        id: restore-build
        with:
          path: ./*
          # Cache includes repo checkout which is required for later scripts
          fail-on-cache-miss: true
          key: ${{ github.sha }}-${{ github.run_number }}-${{ github.run_attempt }}
          restore-keys: |
            ${{ github.sha }}-${{ github.run_number }}
            ${{ github.sha }}-${{ github.run_number }}-${{ github.run_attempt}}

      - uses: actions/download-artifact@v8
        with:
          pattern: next-swc-binaries-*
          merge-multiple: true
          path: packages/next-swc/native

      - uses: actions/download-artifact@v8
        with:
          pattern: wasm-binaries-*
          merge-multiple: true
          path: crates/wasm

      - run: npm i -g npm@10.4.0 # need latest version for provenance (pinning to avoid bugs)
      - run: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc
      - run: ./scripts/publish-native.js
      - run: ./scripts/publish-release.js
        env:
          RELEASE_BOT_GITHUB_TOKEN: ${{ secrets.RELEASE_BOT_GITHUB_TOKEN }}

  publish-turbopack-npm-packages:
    # Matches the commit message written by turbopack/xtask/src/publish.rs:377
    if: "${{(github.ref == 'refs/heads/canary') && startsWith(github.event.head_commit.message, 'chore: release turbopack npm packages')}}"
    runs-on: ubuntu-latest
    permissions:
      contents: write
      id-token: write
    steps:
      - uses: actions/checkout@v6

      - uses: actions/setup-node@v6
        with:
          node-version: ${{ env.NODE_LTS_VERSION }}
          check-latest: true
      - name: Setup corepack
        run: |
          npm i -g corepack@0.31
          corepack enable

      - uses: ./.github/actions/setup-rust
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}

      - run: pnpm install --frozen-lockfile

      - name: Build packages
        run: pnpx turbo@canary run build --only --filter='./turbopack/packages/*'

      - name: Write NPM_TOKEN
        run: echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN_ELEVATED }}" > ~/.npmrc

      - name: Publish
        run: cargo xtask workspace --publish

  deployExamples:
    if: ${{ needs.deploy-target.outputs.value != 'automated-preview' }}
    name: Deploy examples
    runs-on: ubuntu-latest
    needs: [build, deploy-target]
    steps:
      - run: echo '${{ needs.deploy-target.outputs.value }}'
      - uses: actions/checkout@v6
        with:
          fetch-depth: 25
      - name: Install Vercel CLI
        run: npm i -g vercel@latest
      - name: Deploy preview examples
        if: ${{ needs.deploy-target.outputs.value != 'production' }}
        run: ./scripts/deploy-examples.sh
        env:
          VERCEL_API_TOKEN: ${{ secrets.VERCEL_API_TOKEN }}
          DEPLOY_ENVIRONMENT: preview
      - name: Deploy production examples
        if: ${{ needs.deploy-target.outputs.value == 'production' }}
        run: ./scripts/deploy-examples.sh
        env:
          VERCEL_API_TOKEN: ${{ secrets.VERCEL_API_TOKEN }}
          DEPLOY_ENVIRONMENT: production

  buildPassed:
    needs: ['deploy-target', 'build', 'build-wasm', 'build-native']
    if: ${{ always() && needs.deploy-target.outputs.value != '' }}
    # Coupled with retry logic in retry_test.yml
    name: thank you, build
    runs-on: ubuntu-latest
    steps:
      - run: exit 1
        if: ${{ always() && (contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')) }}

  upload_turbopack_bytesize:
    if: ${{ needs.deploy-target.outputs.value != 'automated-preview'}}
    name: Upload Turbopack Bytesize metrics to Datadog
    runs-on: ubuntu-latest
    needs: [build-native, deploy-target]
    env:
      DATADOG_API_KEY: ${{ secrets.DATA_DOG_API_KEY }}
    steps:
      - name: Collect bytesize metrics
        uses: actions/download-artifact@v8
        with:
          pattern: turbopack-bytesize-*
          merge-multiple: true
          path: turbopack-bin-size

      - name: Upload to Datadog
        run: |
          ls -al turbopack-bin-size

          for filename in turbopack-bin-size/*; do
            export BYTESIZE+=" --metrics $(cat $filename)"
          done

          echo "Reporting $BYTESIZE"

          npx @datadog/datadog-ci@2.23.1 metric --no-fail --level pipeline $BYTESIZE
build_and_test matrix .github/workflows/build_and_test.yml
Triggers
push, pull_request
Runs on
ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest
Jobs
optimize-ci, changes, build-native, build-native-windows, build-next, fetch-test-timings, lint, validate-docs-links, check-types-precompiled, test-cargo-unit, test-bench, rust-check, rustdoc-check, ast-grep, devlow-bench, test-devlow, test-turbopack-dev, test-turbopack-integration, test-turbopack-production, test-rspack-dev, test-rspack-integration, test-rspack-production, test-rspack-production-integration, test-next-swc-wasm, test-next-napi-bindings-wasi, test-unit, test-next-config-ts-native-ts-dev, test-next-config-ts-native-ts-prod, test-unit-windows, test-new-tests-dev, test-new-tests-start, test-new-tests-deploy, test-new-tests-deploy-cache-components, test-dev, test-dev-windows, test-integration-windows, test-prod-windows, test-prod, test-integration, test-firefox-safari, test-cache-components-integration, test-cache-components-dev, test-cache-components-prod, tests-pass
Matrix
exclude, exclude.react, group, mode, node, react, selector→ , ${{ github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'run-react-18-tests') && '18.3.1' }}, --scenario=heavy-npm-deps-build --page=homepage, --scenario=heavy-npm-deps-build-turbo-cache-enabled --page=homepage, --scenario=heavy-npm-deps-dev --page=homepage, --turbopack=false, --turbopack=true, 1/10, 1/13, 1/5, 1/6, 1/7, 10/10, 10/13, 11/13, 12/13, 13/13, 18.3.1, 2/10, 2/13, 2/5, 2/6, 2/7, 20, 22, 24, 3/10, 3/13, 3/5, 3/6, 3/7, 4/10, 4/13, 4/5, 4/6, 4/7, 5/10, 5/13, 5/5, 5/6, 5/7, 6/10, 6/13, 6/6, 6/7, 7/10, 7/13, 7/7, 8/10, 8/13, 9/10, 9/13
Actions
ast-grep/action
Commands
  • echo "DOCS_ONLY<<EOF" >> $GITHUB_OUTPUT; echo "$(node scripts/run-for-change.mjs --not --type docs --exec echo 'false')" >> $GITHUB_OUTPUT; echo 'EOF' >> $GITHUB_OUTPUT
  • if [[ $(node ./scripts/check-is-release.js 2> /dev/null || :) == v* ]]; then echo "IS_RELEASE=true" >> $GITHUB_OUTPUT else echo "IS_RELEASE=false" >> $GITHUB_OUTPUT fi
  • npm i -g corepack@0.31 corepack enable
  • pnpm install
  • node run-tests.js --timings --write-timings -g 1/1
  • if [ ! -f test-timings.json ]; then echo "No timings fetched, creating empty timings file" echo '{}' > test-timings.json fi
  • npm i -g corepack@0.31 corepack enable
  • node ./.github/actions/validate-docs-links/dist/index.js
View raw YAML
name: build-and-test

on:
  push:
    branches: ['canary']
  pull_request:
    types: [opened, synchronize]

concurrency:
  # Limit concurrent runs to 1 per PR,
  # but allow concurrent runs on push if they potentially use different source code
  group: ${{ github.event_name == 'pull_request' && format('{0}-pr-{1}', github.workflow, github.ref_name) || format('{0}-sha-{1}', github.workflow, github.sha) }}
  cancel-in-progress: true

# NOTE: anything in `afterBuild` inherits environment variables defined in
# `build_reusable.yml` (not these!) because that job executes within the context
# of that workflow. Environment variables are not automatically passed to
# reusable workflows.
env:
  NODE_MAINTENANCE_VERSION: 20
  NODE_LTS_VERSION: 22

jobs:
  optimize-ci:
    uses: ./.github/workflows/graphite_ci_optimizer.yml
    secrets: inherit

  changes:
    name: Determine changes
    runs-on: ubuntu-latest
    permissions:
      contents: read
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 25

      - name: check for docs only change
        id: docs-change
        run: |
          echo "DOCS_ONLY<<EOF" >> $GITHUB_OUTPUT;
          echo "$(node scripts/run-for-change.mjs --not --type docs --exec echo 'false')" >> $GITHUB_OUTPUT;
          echo 'EOF' >> $GITHUB_OUTPUT

      - name: check for release
        id: is-release
        run: |
          if [[ $(node ./scripts/check-is-release.js 2> /dev/null || :) == v* ]];
            then
              echo "IS_RELEASE=true" >> $GITHUB_OUTPUT
            else
              echo "IS_RELEASE=false" >> $GITHUB_OUTPUT
          fi

    outputs:
      docs-only: ${{ steps.docs-change.outputs.DOCS_ONLY != 'false' }}
      is-release: ${{ steps.is-release.outputs.IS_RELEASE == 'true' }}
      rspack: >-
        ${{
          steps.is-release.outputs.IS_RELEASE == 'true' || (
            github.event_name == 'pull_request' &&
            contains(github.event.pull_request.labels.*.name, 'Rspack')
          )
        }}

  build-native:
    name: build-native
    uses: ./.github/workflows/build_reusable.yml
    needs: ['changes']
    if: ${{ needs.changes.outputs.docs-only == 'false' }}
    with:
      skipInstallBuild: 'yes'
      stepName: 'build-native'
    secrets: inherit

  build-native-windows:
    name: build-native-windows
    uses: ./.github/workflows/build_reusable.yml
    needs: ['changes']
    if: ${{ needs.changes.outputs.docs-only == 'false' }}
    with:
      skipInstallBuild: 'yes'
      stepName: 'build-native-windows'
      runs_on_labels: '["windows","self-hosted","x64"]'
      buildNativeTarget: 'x86_64-pc-windows-msvc'

    secrets: inherit

  build-next:
    name: build-next
    uses: ./.github/workflows/build_reusable.yml
    with:
      skipNativeBuild: 'yes'
      stepName: 'build-next'
    secrets: inherit

  fetch-test-timings:
    name: fetch test timings
    runs-on: ubuntu-latest
    needs: ['changes']
    if: ${{ needs.changes.outputs.docs-only == 'false' }}
    steps:
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_LTS_VERSION }}
          check-latest: true

      - name: Setup pnpm
        run: |
          npm i -g corepack@0.31
          corepack enable

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

      - name: Install dependencies
        run: pnpm install

      - name: Fetch test timings
        run: node run-tests.js --timings --write-timings -g 1/1
        continue-on-error: true
        env:
          KV_REST_API_URL: ${{ secrets.KV_REST_API_URL }}
          KV_REST_API_TOKEN: ${{ secrets.KV_REST_API_TOKEN }}

      - name: Ensure test timings file exists
        run: |
          if [ ! -f test-timings.json ]; then
            echo "No timings fetched, creating empty timings file"
            echo '{}' > test-timings.json
          fi

      - name: Upload test timings
        uses: actions/upload-artifact@v4
        with:
          name: test-timings
          path: test-timings.json
          # Allows "rerun failed jobs" for N days
          retention-days: 5
          if-no-files-found: error

  lint:
    name: lint
    needs: ['build-next']
    uses: ./.github/workflows/build_reusable.yml
    with:
      skipNativeBuild: 'yes'
      skipNativeInstall: 'yes'
      afterBuild: |
        pnpm lint-no-typescript
        pnpm check-examples
        pnpm validate-externals-doc
      stepName: 'lint'
    secrets: inherit

  validate-docs-links:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - name: Setup corepack
        run: |
          npm i -g corepack@0.31
          corepack enable
      - name: 'Run link checker'
        run: node ./.github/actions/validate-docs-links/dist/index.js
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

  check-types-precompiled:
    name: types and precompiled
    needs: ['changes', 'build-native', 'build-next']

    uses: ./.github/workflows/build_reusable.yml
    with:
      afterBuild: pnpm types-and-precompiled
      stepName: 'types-and-precompiled'
    secrets: inherit

  test-cargo-unit:
    name: test cargo unit
    needs: ['changes', 'build-next']
    if: ${{ needs.changes.outputs.docs-only == 'false' }}

    uses: ./.github/workflows/build_reusable.yml
    with:
      needsRust: 'yes'
      needsNextest: 'yes'
      skipNativeBuild: 'yes'
      afterBuild: pnpm dlx turbo@${TURBO_VERSION} run test-cargo-unit --remote-cache-timeout 60 --log-order stream
      stepName: 'test-cargo-unit'
    secrets: inherit

  test-bench:
    name: test cargo benches
    needs: ['optimize-ci', 'changes', 'build-next']
    if: ${{ needs.optimize-ci.outputs.skip == 'false' && needs.changes.outputs.docs-only == 'false' }}

    uses: ./.github/workflows/test-turbopack-rust-bench-test.yml
    secrets: inherit

  rust-check:
    name: rust check
    needs: ['changes', 'build-next']
    if: ${{ needs.changes.outputs.docs-only == 'false' }}

    uses: ./.github/workflows/build_reusable.yml
    with:
      needsRust: 'yes'
      skipInstallBuild: 'yes'
      skipNativeBuild: 'yes'
      afterBuild: pnpm dlx turbo@${TURBO_VERSION} run rust-check
      stepName: 'rust-check'
    secrets: inherit

  rustdoc-check:
    name: rustdoc check
    needs: ['changes', 'build-next']
    if: ${{ needs.changes.outputs.docs-only == 'false' }}

    uses: ./.github/workflows/build_reusable.yml
    with:
      needsRust: 'yes'
      skipInstallBuild: 'yes'
      skipNativeBuild: 'yes'
      afterBuild: ./scripts/deploy-turbopack-docs.sh
      stepName: 'rustdoc-check'
    secrets: inherit

  ast-grep:
    needs: ['changes', 'build-next']
    runs-on: ubuntu-latest
    name: ast-grep lint
    steps:
      - uses: actions/checkout@v4
      - name: ast-grep lint step
        uses: ast-grep/action@v1.5.0
        with:
          # Keep in sync with the next.js repo's root package.json
          version: 0.31.0

  devlow-bench:
    name: Run devlow benchmarks
    needs: ['optimize-ci', 'changes', 'build-next', 'build-native']
    if: ${{ needs.optimize-ci.outputs.skip == 'false' && needs.changes.outputs.docs-only == 'false' && github.event_name != 'pull_request' }}

    strategy:
      fail-fast: false
      matrix:
        mode:
          - '--turbopack=false'
          - '--turbopack=true'
        selector:
          - '--scenario=heavy-npm-deps-dev --page=homepage'
          - '--scenario=heavy-npm-deps-build --page=homepage'
          - '--scenario=heavy-npm-deps-build-turbo-cache-enabled --page=homepage'
    uses: ./.github/workflows/build_reusable.yml
    with:
      afterBuild: |
        ./node_modules/.bin/devlow-bench ./scripts/devlow-bench.mjs \
          --datadog=ubuntu-latest-16-core \
          ${{ matrix.mode }} \
          ${{ matrix.selector }}
      stepName: 'devlow-bench-${{ matrix.mode }}-${{ matrix.selector }}'
    secrets: inherit

  test-devlow:
    name: test devlow package
    needs: ['optimize-ci', 'changes']
    if: ${{ needs.optimize-ci.outputs.skip == 'false' && needs.changes.outputs.docs-only == 'false' }}
    uses: ./.github/workflows/build_reusable.yml
    with:
      skipNativeBuild: 'yes'
      stepName: 'test-devlow'
      afterBuild: |
        pnpm run --filter=devlow-bench test
    secrets: inherit

  test-turbopack-dev:
    name: test turbopack dev
    needs:
      [
        'optimize-ci',
        'changes',
        'build-next',
        'build-native',
        'fetch-test-timings',
      ]
    if: ${{ needs.optimize-ci.outputs.skip == 'false' && needs.changes.outputs.docs-only == 'false' }}

    strategy:
      fail-fast: false
      matrix:
        exclude:
          # Excluding React 18 tests unless on `canary` branch until budget is approved.
          - react: ${{ github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'run-react-18-tests') && '18.3.1' }}
        group: [1/7, 2/7, 3/7, 4/7, 5/7, 6/7, 7/7]
        # Empty value uses default
        react: ['', '18.3.1']
    uses: ./.github/workflows/build_reusable.yml
    with:
      afterBuild: |
        export IS_TURBOPACK_TEST=1
        export TURBOPACK_DEV=1
        export NEXT_TEST_MODE=dev
        export NEXT_TEST_REACT_VERSION="${{ matrix.react }}"
        export __NEXT_EXPERIMENTAL_STRICT_ROUTE_TYPES=true
        export RUST_BACKTRACE=1

        node run-tests.js \
          --test-pattern '^(test\/(development|e2e))/.*\.test\.(js|jsx|ts|tsx)$' \
          --timings \
          --require-timings \
          -g ${{ matrix.group }}
      testTimingsArtifact: 'test-timings'
      stepName: 'test-turbopack-dev-react-${{ matrix.react }}-${{ matrix.group }}'
    secrets: inherit

  test-turbopack-integration:
    name: test turbopack integration
    needs:
      [
        'optimize-ci',
        'changes',
        'build-native',
        'build-next',
        'fetch-test-timings',
      ]
    if: ${{ needs.optimize-ci.outputs.skip == 'false' && needs.changes.outputs.docs-only == 'false' }}

    strategy:
      fail-fast: false
      matrix:
        group:
          - 1/13
          - 2/13
          - 3/13
          - 4/13
          - 5/13
          - 6/13
          - 7/13
          - 8/13
          - 9/13
          - 10/13
          - 11/13
          - 12/13
          - 13/13
        # Empty value uses default
        # TODO: Run with React 18.
        # Integration tests use the installed React version in next/package.json.
        # We can't easily switch like we do for e2e tests.
        # Skipping this dimension until we can figure out a way to test multiple React versions.
        react: ['']
    uses: ./.github/workflows/build_reusable.yml
    with:
      nodeVersion: 20.9.0
      afterBuild: |
        export IS_TURBOPACK_TEST=1
        export NEXT_TEST_REACT_VERSION="${{ matrix.react }}"
        export __NEXT_EXPERIMENTAL_STRICT_ROUTE_TYPES=true
        export RUST_BACKTRACE=1

        node run-tests.js \
          --timings \
          --require-timings \
          -g ${{ matrix.group }} \
          --type integration
      testTimingsArtifact: 'test-timings'
      stepName: 'test-turbopack-integration-react-${{ matrix.react }}-${{ matrix.group }}'
    secrets: inherit

  test-turbopack-production:
    name: test turbopack production
    needs:
      [
        'optimize-ci',
        'changes',
        'build-next',
        'build-native',
        'fetch-test-timings',
      ]
    if: ${{ needs.optimize-ci.outputs.skip == 'false' && needs.changes.outputs.docs-only == 'false' }}

    strategy:
      fail-fast: false
      matrix:
        exclude:
          # Excluding React 18 tests unless on `canary` branch until budget is approved.
          - react: ${{ github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'run-react-18-tests') && '18.3.1' }}
        group: [1/7, 2/7, 3/7, 4/7, 5/7, 6/7, 7/7]
        # Empty value uses default
        react: ['', '18.3.1']
    uses: ./.github/workflows/build_reusable.yml
    with:
      nodeVersion: 20.9.0
      afterBuild: |
        export IS_TURBOPACK_TEST=1
        export TURBOPACK_BUILD=1
        export NEXT_TEST_MODE=start
        export NEXT_TEST_REACT_VERSION="${{ matrix.react }}"
        export __NEXT_EXPERIMENTAL_STRICT_ROUTE_TYPES=true
        export RUST_BACKTRACE=1

        node run-tests.js --timings --require-timings -g ${{ matrix.group }} --type production
      testTimingsArtifact: 'test-timings'
      stepName: 'test-turbopack-production-react-${{ matrix.react }}-${{ matrix.group }}'
    secrets: inherit

  test-rspack-dev:
    name: test rspack dev
    needs:
      [
        'optimize-ci',
        'changes',
        'build-next',
        'build-native',
        'fetch-test-timings',
      ]
    if: ${{ needs.optimize-ci.outputs.skip == 'false' && needs.changes.outputs.docs-only == 'false' && needs.changes.outputs.rspack == 'true' }}
    strategy:
      fail-fast: false
      matrix:
        exclude:
          # Excluding React 18 tests unless on `canary` branch until budget is approved.
          - react: ${{ github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'run-react-18-tests') && '18.3.1' }}
        group: [1/5, 2/5, 3/5, 4/5, 5/5]
        # Empty value uses default
        react: ['', '18.3.1']
    uses: ./.github/workflows/build_reusable.yml
    with:
      afterBuild: |
        export NEXT_EXTERNAL_TESTS_FILTERS="$(pwd)/test/rspack-dev-tests-manifest.json"
        export NEXT_TEST_MODE=dev
        export NEXT_TEST_REACT_VERSION="${{ matrix.react }}"
        export __NEXT_EXPERIMENTAL_STRICT_ROUTE_TYPES=true

        # rspack flags
        export NEXT_RSPACK=1
        export NEXT_TEST_USE_RSPACK=1

        # HACK: Despite the name, this environment variable is only used to gate
        # tests, so it's applicable to rspack
        export TURBOPACK_DEV=1

        node run-tests.js \
          --test-pattern '^(test\/(development|e2e))/.*\.test\.(js|jsx|ts|tsx)$' \
          --timings \
          --require-timings \
          -g ${{ matrix.group }}
      testTimingsArtifact: 'test-timings'
      stepName: 'test-rspack-dev-react-${{ matrix.react }}-${{ matrix.group }}'
    secrets: inherit

  test-rspack-integration:
    name: test rspack development integration
    needs:
      [
        'optimize-ci',
        'changes',
        'build-next',
        'build-native',
        'fetch-test-timings',
      ]
    if: ${{ needs.optimize-ci.outputs.skip == 'false' && needs.changes.outputs.docs-only == 'false' && needs.changes.outputs.rspack == 'true' }}
    strategy:
      fail-fast: false
      matrix:
        group: [1/6, 2/6, 3/6, 4/6, 5/6, 6/6]
        # Empty value uses default
        # TODO: Run with React 18.
        # Integration tests use the installed React version in next/package.json.
        # We can't easily switch like we do for e2e tests.
        # Skipping this dimension until we can figure out a way to test multiple React versions.
        react: ['']
    uses: ./.github/workflows/build_reusable.yml
    with:
      nodeVersion: 20.9.0
      afterBuild: |
        export NEXT_EXTERNAL_TESTS_FILTERS="$(pwd)/test/rspack-dev-tests-manifest.json"
        export NEXT_TEST_REACT_VERSION="${{ matrix.react }}"
        export __NEXT_EXPERIMENTAL_STRICT_ROUTE_TYPES=true

        # rspack flags
        export NEXT_RSPACK=1
        export NEXT_TEST_USE_RSPACK=1

        # HACK: Despite the name, this environment variable is only used to gate
        # tests, so it's applicable to rspack
        export TURBOPACK_DEV=1

        node run-tests.js \
          --timings \
          --require-timings \
          -g ${{ matrix.group }} \
          --type integration
      testTimingsArtifact: 'test-timings'
      stepName: 'test-rspack-integration-react-${{ matrix.react }}-${{ matrix.group }}'
    secrets: inherit

  test-rspack-production:
    name: test rspack production
    needs:
      [
        'optimize-ci',
        'changes',
        'build-next',
        'build-native',
        'fetch-test-timings',
      ]
    if: ${{ needs.optimize-ci.outputs.skip == 'false' && needs.changes.outputs.docs-only == 'false' && needs.changes.outputs.rspack == 'true' }}
    strategy:
      fail-fast: false
      matrix:
        exclude:
          # Excluding React 18 tests unless on `canary` branch until budget is approved.
          - react: ${{ github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'run-react-18-tests') && '18.3.1' }}
        group: [1/7, 2/7, 3/7, 4/7, 5/7, 6/7, 7/7]
        # Empty value uses default
        react: ['', '18.3.1']
    uses: ./.github/workflows/build_reusable.yml
    with:
      nodeVersion: 20.9.0
      afterBuild: |
        export NEXT_EXTERNAL_TESTS_FILTERS="$(pwd)/test/rspack-build-tests-manifest.json"
        export NEXT_TEST_MODE=start
        export NEXT_TEST_REACT_VERSION="${{ matrix.react }}"
        export __NEXT_EXPERIMENTAL_STRICT_ROUTE_TYPES=true

        # rspack flags
        export NEXT_RSPACK=1
        export NEXT_TEST_USE_RSPACK=1

        # HACK: Despite the name, this environment variable is only used to gate
        # tests, so it's applicable to rspack
        export TURBOPACK_BUILD=1

        node run-tests.js --timings --require-timings -g ${{ matrix.group }} --type production
      testTimingsArtifact: 'test-timings'
      stepName: 'test-rspack-production-react-${{ matrix.react }}-${{ matrix.group }}'
    secrets: inherit

  test-rspack-production-integration:
    name: test rspack production integration
    needs:
      [
        'optimize-ci',
        'changes',
        'build-next',
        'build-native',
        'fetch-test-timings',
      ]
    if: ${{ needs.optimize-ci.outputs.skip == 'false' && needs.changes.outputs.docs-only == 'false' && needs.changes.outputs.rspack == 'true' }}
    strategy:
      fail-fast: false
      matrix:
        group: [1/7, 2/7, 3/7, 4/7, 5/7, 6/7, 7/7]
        # Empty value uses default
        # TODO: Run with React 18.
        # Integration tests use the installed React version in next/package.json.
        # We can't easily switch like we do for e2e tests.
        # Skipping this dimension until we can figure out a way to test multiple React versions.
        react: ['']
    uses: ./.github/workflows/build_reusable.yml
    with:
      nodeVersion: 20.9.0
      afterBuild: |
        export NEXT_EXTERNAL_TESTS_FILTERS="$(pwd)/test/rspack-build-tests-manifest.json"
        export NEXT_TEST_REACT_VERSION="${{ matrix.react }}"
        export __NEXT_EXPERIMENTAL_STRICT_ROUTE_TYPES=true

        # rspack flags
        export NEXT_RSPACK=1
        export NEXT_TEST_USE_RSPACK=1

        # HACK: Despite the name, this environment variable is only used to gate
        # tests, so it's applicable to rspack
        export TURBOPACK_BUILD=1

        node run-tests.js \
          --timings \
          --require-timings \
          -g ${{ matrix.group }} \
          --type integration
      testTimingsArtifact: 'test-timings'
      stepName: 'test-rspack-production-integration-react-${{ matrix.react }}-${{ matrix.group }}'
    secrets: inherit

  test-next-swc-wasm:
    name: test next-swc wasm
    needs: ['optimize-ci', 'changes', 'build-next']
    if: ${{ needs.optimize-ci.outputs.skip == 'false' && needs.changes.outputs.docs-only == 'false' }}

    uses: ./.github/workflows/build_reusable.yml
    with:
      skipNativeBuild: 'yes'
      skipNativeInstall: 'yes'
      afterBuild: |
        rustup target add wasm32-unknown-unknown
        node ./scripts/normalize-version-bump.js
        pnpm dlx turbo@${TURBO_VERSION} run build-wasm -- --target nodejs
        git checkout .

        export NEXT_TEST_MODE=start
        export NEXT_TEST_WASM=true
        export IS_WEBPACK_TEST=1
        node run-tests.js \
          test/production/pages-dir/production/test/index.test.ts \
          test/e2e/streaming-ssr/index.test.ts
      stepName: 'test-next-swc-wasm'
    secrets: inherit

  #[NOTE] currently this only checks building wasi target
  test-next-napi-bindings-wasi:
    name: test next-swc wasi
    needs: ['optimize-ci', 'changes', 'build-next']
    # TODO: Re-enable this when https://github.com/napi-rs/napi-rs/issues/2009 is addressed.
    # Specifically, the `platform` value is now `threads` in
    # https://github.com/napi-rs/napi-rs/blob/e4ad4767efaf093fdff3dc768856f6100a6e3f72/cli/src/api/build.ts#L530
    if: false
    # if: ${{ needs.changes.outputs.docs-only == 'false' }}

    uses: ./.github/workflows/build_reusable.yml
    with:
      skipNativeBuild: 'yes'
      skipNativeInstall: 'yes'
      afterBuild: |
        rustup target add wasm32-wasip1-threads
        pnpm dlx turbo@${TURBO_VERSION} run build-native-wasi
      stepName: 'test-next-napi-bindings-wasi'
    secrets: inherit

  test-unit:
    name: test unit
    needs: ['changes', 'build-next', 'build-native']
    if: ${{ needs.changes.outputs.docs-only == 'false' }}

    strategy:
      fail-fast: false
      matrix:
        node: [20, 22] # TODO: use env var like [env.NODE_MAINTENANCE_VERSION, env.NODE_LTS_VERSION]

    uses: ./.github/workflows/build_reusable.yml
    with:
      nodeVersion: ${{ matrix.node }}
      afterBuild: node run-tests.js --type unit
      stepName: 'test-unit-${{ matrix.node }}'

    secrets: inherit

  # TODO: Remove this once we bump minimum Node.js version to v22
  test-next-config-ts-native-ts-dev:
    name: test next-config-ts-native-ts dev
    needs: ['changes', 'build-next', 'build-native']
    if: ${{ needs.changes.outputs.docs-only == 'false' }}

    strategy:
      fail-fast: false
      matrix:
        node: [22, 24]

    uses: ./.github/workflows/build_reusable.yml
    with:
      nodeVersion: ${{ matrix.node }}
      afterBuild: |
        export __NEXT_NODE_NATIVE_TS_LOADER_ENABLED=true
        export __NEXT_EXPERIMENTAL_STRICT_ROUTE_TYPES=true
        NEXT_TEST_MODE=dev NODE_OPTIONS=--experimental-transform-types node run-tests.js test/e2e/app-dir/next-config-ts-native-ts/**/*.test.ts test/e2e/app-dir/next-config-ts-native-mts/**/*.test.ts
      stepName: 'test-next-config-ts-native-ts-dev-${{ matrix.node }}'

    secrets: inherit

  # TODO: Remove this once we bump minimum Node.js version to v22
  test-next-config-ts-native-ts-prod:
    name: test next-config-ts-native-ts prod
    needs: ['changes', 'build-next', 'build-native']
    if: ${{ needs.changes.outputs.docs-only == 'false' }}

    strategy:
      fail-fast: false
      matrix:
        node: [22, 24]

    uses: ./.github/workflows/build_reusable.yml
    with:
      nodeVersion: ${{ matrix.node }}
      afterBuild: |
        export __NEXT_NODE_NATIVE_TS_LOADER_ENABLED=true
        export __NEXT_EXPERIMENTAL_STRICT_ROUTE_TYPES=true
        NEXT_TEST_MODE=start NODE_OPTIONS=--experimental-transform-types node run-tests.js test/e2e/app-dir/next-config-ts-native-ts/**/*.test.ts test/e2e/app-dir/next-config-ts-native-mts/**/*.test.ts
      stepName: 'test-next-config-ts-native-ts-prod-${{ matrix.node }}'

    secrets: inherit

  test-unit-windows:
    name: test unit windows
    needs: ['changes', 'build-native', 'build-native-windows']
    if: ${{ needs.changes.outputs.docs-only == 'false' }}

    strategy:
      fail-fast: false
      matrix:
        node: [20, 22] # TODO: use env var like [env.NODE_MAINTENANCE_VERSION, env.NODE_LTS_VERSION]

    uses: ./.github/workflows/build_reusable.yml
    with:
      nodeVersion: ${{ matrix.node }}
      afterBuild: node run-tests.js --type unit
      stepName: 'test-unit-windows-${{ matrix.node }}'
      runs_on_labels: '["windows","self-hosted","x64"]'
      buildNativeTarget: 'x86_64-pc-windows-msvc'

    secrets: inherit

  test-new-tests-dev:
    name: Test new and changed tests for flakes (dev)
    needs: ['optimize-ci', 'changes', 'build-native', 'build-next']
    # test-new-tests-if
    if: ${{ needs.optimize-ci.outputs.skip == 'false' && needs.changes.outputs.docs-only == 'false' }}
    # test-new-tests-end-if

    strategy:
      fail-fast: false
      matrix:
        group: [1/5, 2/5, 3/5, 4/5, 5/5]

    uses: ./.github/workflows/build_reusable.yml
    with:
      afterBuild: |
        export __NEXT_EXPERIMENTAL_STRICT_ROUTE_TYPES=true
        node scripts/test-new-tests.mjs \
          --flake-detection \
          --mode dev \
          --group ${{ matrix.group }}
      stepName: 'test-new-tests-dev-${{matrix.group}}'
      timeout_minutes: 60 # Increase the default timeout as tests are intentionally run multiple times to detect flakes

    secrets: inherit

  test-new-tests-start:
    name: Test new and changed tests for flakes (prod)
    needs: ['optimize-ci', 'changes', 'build-native', 'build-next']
    # test-new-tests-if
    if: ${{ needs.optimize-ci.outputs.skip == 'false' && needs.changes.outputs.docs-only == 'false' }}
    # test-new-tests-end-if

    strategy:
      fail-fast: false
      matrix:
        group: [1/5, 2/5, 3/5, 4/5, 5/5]

    uses: ./.github/workflows/build_reusable.yml
    with:
      afterBuild: |
        export __NEXT_EXPERIMENTAL_STRICT_ROUTE_TYPES=true
        node scripts/test-new-tests.mjs \
          --flake-detection \
          --mode start \
          --group ${{ matrix.group }}
      stepName: 'test-new-tests-start-${{matrix.group}}'
      timeout_minutes: 60 # Increase the default timeout as tests are intentionally run multiple times to detect flakes
    secrets: inherit

  test-new-tests-deploy:
    name: Test new and changed tests when deployed
    needs:
      ['optimize-ci', 'test-prod', 'test-new-tests-dev', 'test-new-tests-start']
    # test-new-tests-if
    if: ${{ needs.optimize-ci.outputs.skip == 'false' }}
    # test-new-tests-end-if

    strategy:
      fail-fast: false
      matrix:
        group: [1/5, 2/5, 3/5, 4/5, 5/5]

    uses: ./.github/workflows/build_reusable.yml
    with:
      # Keep Next.js related env variables in sync with additionalEnv in next-deploy.ts
      afterBuild: |
        export NEXT_ENABLE_ADAPTER=1
        export NEXT_E2E_TEST_TIMEOUT=240000
        export __NEXT_EXPERIMENTAL_STRICT_ROUTE_TYPES=true
        node scripts/test-new-tests.mjs \
          --mode deploy \
          --group ${{ matrix.group }}
      stepName: 'test-new-tests-deploy-${{matrix.group}}'

    secrets: inherit

  test-new-tests-deploy-cache-components:
    name: Test new and changed tests when deployed (cache components)
    needs:
      [
        'optimize-ci',
        'test-cache-components-prod',
        'test-new-tests-dev',
        'test-new-tests-start',
      ]
    # test-new-tests-if
    if: ${{ needs.optimize-ci.outputs.skip == 'false' }}
    # test-new-tests-end-if

    strategy:
      fail-fast: false
      matrix:
        group: [1/5, 2/5, 3/5, 4/5, 5/5]

    uses: ./.github/workflows/build_reusable.yml
    with:
      # Keep Next.js related env variables in sync with additionalEnv in next-deploy.ts
      afterBuild: |
        export __NEXT_CACHE_COMPONENTS=true
        export __NEXT_EXPERIMENTAL_CACHED_NAVIGATIONS=true
        export __NEXT_EXPERIMENTAL_APP_NEW_SCROLL_HANDLER=true
        export NEXT_ENABLE_ADAPTER=1
        export NEXT_EXTERNAL_TESTS_FILTERS="test/deploy-tests-manifest.json,test/cache-components-tests-manifest.json"
        export NEXT_E2E_TEST_TIMEOUT=240000
        node scripts/test-new-tests.mjs \
          --mode deploy \
          --group ${{ matrix.group }}
      stepName: 'test-new-tests-deploy-cache-components-${{matrix.group}}'

    secrets: inherit

  test-dev: # TODO: rename to include webpack
    name: test dev
    needs:
      [
        'optimize-ci',
        'changes',
        'build-native',
        'build-next',
        'fetch-test-timings',
      ]
    if: ${{ needs.optimize-ci.outputs.skip == 'false' && needs.changes.outputs.docs-only == 'false' }}

    strategy:
      fail-fast: false
      matrix:
        exclude:
          # Excluding React 18 tests unless on `canary` branch until budget is approved.
          - react: ${{ github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'run-react-18-tests') && '18.3.1' }}
        group: [1/10, 2/10, 3/10, 4/10, 5/10, 6/10, 7/10, 8/10, 9/10, 10/10]
        # Empty value uses default
        react: ['', '18.3.1']
    uses: ./.github/workflows/build_reusable.yml
    with:
      afterBuild: |
        export IS_WEBPACK_TEST=1
        export NEXT_TEST_MODE=dev
        export NEXT_TEST_REACT_VERSION="${{ matrix.react }}"
        export __NEXT_EXPERIMENTAL_STRICT_ROUTE_TYPES=true

        node run-tests.js \
          --timings \
          --require-timings \
          -g ${{ matrix.group }} \
          --type development
      testTimingsArtifact: 'test-timings'
      stepName: 'test-dev-react-${{ matrix.react }}-${{ matrix.group }}'
    secrets: inherit

  test-dev-windows:
    name: test dev windows
    needs:
      [
        'optimize-ci',
        'changes',
        'build-native-windows',
        'build-native',
        'build-next',
      ]
    if: ${{ needs.optimize-ci.outputs.skip == 'false' && needs.changes.outputs.docs-only == 'false' }}

    uses: ./.github/workflows/build_reusable.yml
    with:
      # Should this be using turbopack? a variation?
      afterBuild: |
        export NEXT_TEST_MODE=dev
        export IS_WEBPACK_TEST=1
        export __NEXT_EXPERIMENTAL_STRICT_ROUTE_TYPES=true

        node run-tests.js \
          test/e2e/app-dir/app/index.test.ts \
          test/e2e/app-dir/app-edge/app-edge.test.ts \
          test/e2e/app-dir/proxy-runtime-nodejs/proxy-runtime-nodejs.test.ts \
          test/development/app-dir/segment-explorer/segment-explorer.test.ts
      stepName: 'test-dev-windows'
      runs_on_labels: '["windows","self-hosted","x64"]'
      buildNativeTarget: 'x86_64-pc-windows-msvc'
    secrets: inherit

  test-integration-windows:
    name: test integration windows
    needs:
      [
        'optimize-ci',
        'changes',
        'build-native-windows',
        'build-native',
        'build-next',
      ]
    if: ${{ needs.optimize-ci.outputs.skip == 'false' && needs.changes.outputs.docs-only == 'false' }}

    uses: ./.github/workflows/build_reusable.yml
    with:
      nodeVersion: 20.9.0
      afterBuild: |
        export IS_WEBPACK_TEST=1
        export __NEXT_EXPERIMENTAL_STRICT_ROUTE_TYPES=true
        node run-tests.js \
          --concurrency 4 \
          test/production/pages-dir/production/test/index.test.ts \
          test/integration/css-client-nav/test/index.test.ts \
          test/integration/rewrites-has-condition/test/index.test.ts \
          test/integration/create-next-app/index.test.ts \
          test/integration/create-next-app/package-manager/pnpm.test.ts
      stepName: 'test-integration-windows'
      runs_on_labels: '["windows","self-hosted","x64"]'
      buildNativeTarget: 'x86_64-pc-windows-msvc'
    secrets: inherit

  test-prod-windows:
    name: test prod windows
    needs:
      [
        'optimize-ci',
        'changes',
        'build-native-windows',
        'build-native',
        'build-next',
      ]
    if: ${{ needs.optimize-ci.outputs.skip == 'false' && needs.changes.outputs.docs-only == 'false' }}

    uses: ./.github/workflows/build_reusable.yml
    with:
      afterBuild: |
        export NEXT_TEST_MODE=start
        export IS_WEBPACK_TEST=1
        export __NEXT_EXPERIMENTAL_STRICT_ROUTE_TYPES=true

        node run-tests.js --type production \
          test/e2e/app-dir/app/index.test.ts \
          test/e2e/app-dir/app-edge/app-edge.test.ts \
          test/e2e/app-dir/metadata-edge/index.test.ts \
          test/e2e/app-dir/non-root-project-monorepo/non-root-project-monorepo.test.ts \
          test/e2e/app-dir/proxy-runtime-nodejs/proxy-runtime-nodejs.test.ts
      stepName: 'test-prod-windows'
      runs_on_labels: '["windows","self-hosted","x64"]'
      buildNativeTarget: 'x86_64-pc-windows-msvc'
    secrets: inherit

  test-prod:
    name: test prod
    needs:
      [
        'optimize-ci',
        'changes',
        'build-native',
        'build-next',
        'fetch-test-timings',
      ]
    if: ${{ needs.optimize-ci.outputs.skip == 'false' && needs.changes.outputs.docs-only == 'false' }}

    strategy:
      fail-fast: false
      matrix:
        exclude:
          # Excluding React 18 tests unless on `canary` branch until budget is approved.
          - react: ${{ github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'run-react-18-tests') && '18.3.1' }}
        group: [1/10, 2/10, 3/10, 4/10, 5/10, 6/10, 7/10, 8/10, 9/10, 10/10]
        # Empty value uses default
        react: ['', '18.3.1']
    uses: ./.github/workflows/build_reusable.yml
    with:
      afterBuild: |
        export IS_WEBPACK_TEST=1
        export NEXT_TEST_MODE=start
        export NEXT_TEST_REACT_VERSION="${{ matrix.react }}"
        export __NEXT_EXPERIMENTAL_STRICT_ROUTE_TYPES=true

        node run-tests.js --timings --require-timings -g ${{ matrix.group }} --type production
      testTimingsArtifact: 'test-timings'
      stepName: 'test-prod-react-${{ matrix.react }}-${{ matrix.group }}'
    secrets: inherit

  test-integration:
    name: test integration
    needs:
      [
        'optimize-ci',
        'changes',
        'build-native',
        'build-next',
        'fetch-test-timings',
      ]
    if: ${{ needs.optimize-ci.outputs.skip == 'false' && needs.changes.outputs.docs-only == 'false' }}

    strategy:
      fail-fast: false
      matrix:
        group:
          - 1/13
          - 2/13
          - 3/13
          - 4/13
          - 5/13
          - 6/13
          - 7/13
          - 8/13
          - 9/13
          - 10/13
          - 11/13
          - 12/13
          - 13/13
        # Empty value uses default
        # TODO: Run with React 18.
        # Integration tests use the installed React version in next/package.json.
        # We can't easily switch like we do for e2e tests.
        # Skipping this dimension until we can figure out a way to test multiple React versions.
        react: ['']
    uses: ./.github/workflows/build_reusable.yml
    with:
      nodeVersion: 20.9.0
      afterBuild: |
        export IS_WEBPACK_TEST=1
        export NEXT_TEST_REACT_VERSION="${{ matrix.react }}"
        export __NEXT_EXPERIMENTAL_STRICT_ROUTE_TYPES=true

        node run-tests.js \
          --timings \
          --require-timings \
          -g ${{ matrix.group }} \
          --type integration
      testTimingsArtifact: 'test-timings'
      stepName: 'test-integration-${{ matrix.group }}-react-${{ matrix.react }}'
    secrets: inherit

  test-firefox-safari:
    name: test firefox and safari
    needs: ['optimize-ci', 'changes', 'build-native', 'build-next']
    if: ${{ needs.optimize-ci.outputs.skip == 'false' && needs.changes.outputs.docs-only == 'false' }}

    uses: ./.github/workflows/build_reusable.yml
    with:
      browser: 'firefox webkit'
      afterBuild: |
        pnpm playwright install

        # these all run without concurrency because they're heavier
        export TEST_CONCURRENCY=1
        export IS_WEBPACK_TEST=1

        BROWSER_NAME=firefox node run-tests.js \
          test/production/pages-dir/production/test/index.test.ts \
          test/production/chunk-load-failure/chunk-load-failure.test.ts

        NEXT_TEST_MODE=start BROWSER_NAME=safari node run-tests.js \
          test/production/pages-dir/production/test/index.test.ts \
          test/production/chunk-load-failure/chunk-load-failure.test.ts \
          test/e2e/basepath/basepath.test.ts \
          test/e2e/basepath/error-pages.test.ts

        BROWSER_NAME=safari DEVICE_NAME='iPhone XR' node run-tests.js \
          test/production/prerender-prefetch/index.test.ts
      stepName: 'test-firefox-safari'
    secrets: inherit

  # Manifest generated via: https://gist.github.com/wyattjoh/2ceaebd82a5bcff4819600fd60126431
  test-cache-components-integration:
    name: test cache components integration
    needs: ['optimize-ci', 'changes', 'build-native', 'build-next']
    if: ${{ needs.optimize-ci.outputs.skip == 'false' && needs.changes.outputs.docs-only == 'false' }}

    uses: ./.github/workflows/build_reusable.yml
    with:
      nodeVersion: 20.9.0
      afterBuild: |
        export __NEXT_CACHE_COMPONENTS=true
        export __NEXT_EXPERIMENTAL_CACHED_NAVIGATIONS=true
        export __NEXT_EXPERIMENTAL_APP_NEW_SCROLL_HANDLER=true
        export NEXT_EXTERNAL_TESTS_FILTERS="test/cache-components-tests-manifest.json"
        export IS_WEBPACK_TEST=1

        node run-tests.js \
          --timings \
          --type integration
      stepName: 'test-cache-components-integration'
    secrets: inherit

  test-cache-components-dev:
    name: test cache components dev
    needs:
      [
        'optimize-ci',
        'changes',
        'build-native',
        'build-next',
        'fetch-test-timings',
      ]
    if: ${{ needs.optimize-ci.outputs.skip == 'false' && needs.changes.outputs.docs-only == 'false' }}

    strategy:
      fail-fast: false
      matrix:
        group: [1/6, 2/6, 3/6, 4/6, 5/6, 6/6]
    uses: ./.github/workflows/build_reusable.yml
    with:
      afterBuild: |
        export __NEXT_CACHE_COMPONENTS=true
        export __NEXT_EXPERIMENTAL_CACHED_NAVIGATIONS=true
        export __NEXT_EXPERIMENTAL_APP_NEW_SCROLL_HANDLER=true
        export NEXT_EXTERNAL_TESTS_FILTERS="test/cache-components-tests-manifest.json"
        export NEXT_TEST_MODE=dev
        export IS_WEBPACK_TEST=1

        node run-tests.js \
          --timings \
          --require-timings \
          -g ${{ matrix.group }} \
          --type development
      testTimingsArtifact: 'test-timings'
      stepName: 'test-cache-components-dev-${{ matrix.group }}'
    secrets: inherit

  test-cache-components-prod:
    name: test cache components prod
    needs:
      [
        'optimize-ci',
        'changes',
        'build-native',
        'build-next',
        'fetch-test-timings',
      ]
    if: ${{ needs.optimize-ci.outputs.skip == 'false' && needs.changes.outputs.docs-only == 'false' }}

    strategy:
      fail-fast: false
      matrix:
        group: [1/7, 2/7, 3/7, 4/7, 5/7, 6/7, 7/7]
    uses: ./.github/workflows/build_reusable.yml
    with:
      afterBuild: |
        export __NEXT_CACHE_COMPONENTS=true
        export __NEXT_EXPERIMENTAL_CACHED_NAVIGATIONS=true
        export __NEXT_EXPERIMENTAL_APP_NEW_SCROLL_HANDLER=true
        export NEXT_EXTERNAL_TESTS_FILTERS="test/cache-components-tests-manifest.json"
        export NEXT_TEST_MODE=start
        export IS_WEBPACK_TEST=1

        node run-tests.js \
          --timings \
          --require-timings \
          -g ${{ matrix.group }} \
          --type production
      testTimingsArtifact: 'test-timings'
      stepName: 'test-cache-components-prod-${{ matrix.group }}'
    secrets: inherit

  tests-pass:
    needs:
      [
        'build-native',
        'build-next',
        'lint',
        'validate-docs-links',
        'check-types-precompiled',
        'test-unit',
        'test-next-config-ts-native-ts-dev',
        'test-next-config-ts-native-ts-prod',
        'test-dev',
        'test-prod',
        'test-integration',
        'test-firefox-safari',
        'test-cache-components-dev',
        'test-cache-components-prod',
        'test-cache-components-integration',
        'test-cargo-unit',
        'rust-check',
        'rustdoc-check',
        'test-next-swc-wasm',
        'test-turbopack-dev',
        'test-turbopack-integration',
        'test-new-tests-dev',
        'test-new-tests-start',
        'test-new-tests-deploy',
        'test-new-tests-deploy-cache-components',
        'test-turbopack-production',
        'test-unit-windows',
        'test-dev-windows',
        'test-integration-windows',
        'test-prod-windows',
      ]

    if: always()
    runs-on: ubuntu-latest
    # Coupled with retry logic in retry_test.yml
    name: thank you, next
    steps:
      - run: exit 1
        if: ${{ always() && (contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')) }}
build_reusable .github/workflows/build_reusable.yml
Triggers
workflow_call
Runs on
${{ fromJson(inputs.runs_on_labels) }}
Jobs
build
Actions
taiki-e/install-action, ijjk/rust-cache
Commands
  • git config --global core.autocrlf false git config --global core.eol lf
  • 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
  • curl -fsSL https://fnm.vercel.app/install | bash export PATH="/home/runner/.local/share/fnm:$PATH" echo "/home/runner/.local/share/fnm" >> $GITHUB_PATH fnm env --json | jq -r 'to_entries|map("\(.key)=\(.value|tostring)")|.[]' | xargs -I {} echo "{}" >> $GITHUB_ENV
  • 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"
  • which node node --version
  • npm i -g corepack@0.31
  • corepack enable
  • pwd
View raw YAML
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: '["self-hosted", "linux", "x64", "metal"]'
      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: ''

      browser:
        description: 'Browser to use for tests'
        required: false
        type: string
        default: 'chromium'
env:
  NAPI_CLI_VERSION: 2.18.4
  TURBO_VERSION: 2.8.11
  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-e2e-tests'
  TURBO_CACHE: 'remote:rw'
  TURBO_TOKEN: ${{ secrets.TURBO_REMOTE_CACHE_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: ${{ contains(fromJson(inputs.runs_on_labels), '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: |
          curl -fsSL https://fnm.vercel.app/install | bash
          export PATH="/home/runner/.local/share/fnm:$PATH"
          echo "/home/runner/.local/share/fnm" >> $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: ${{ contains(fromJson(inputs.runs_on_labels), 'ubuntu-latest') }}
        run: |
          npm i -g corepack@0.31
      - run: corepack enable
      - run: pwd

      - 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.skipNativeBuild != 'yes' || inputs.needsNextest == 'yes' || inputs.needsRust == 'yes' }}

      - name: Install nextest
        if: ${{ inputs.needsNextest == 'yes' }}
        uses: taiki-e/install-action@nextest

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

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

      - name: Cache on ${{ github.ref_name }}
        uses: ijjk/rust-cache@turbo-cache-v1.0.9
        if: ${{ inputs.rustCacheKey }}
        with:
          cache-provider: 'turbo'
          save-if: ${{ github.ref_name == 'canary' }}
          shared-key: ${{ inputs.rustCacheKey }}-${{ inputs.buildNativeTarget }}-build-${{ inputs.rustBuildProfile }}-${{ hashFiles('.cargo/config.toml') }}

      # 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"

      - run: cargo clean
        if: ${{ inputs.skipNativeBuild != 'yes' || inputs.needsNextest == 'yes' || inputs.needsRust == 'yes' }}

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

      - run: pnpm dlx turbo@${TURBO_VERSION} run build-native-${{ inputs.rustBuildProfile }} -v --env-mode loose --remote-cache-timeout 90 --summarize -- --target ${{ inputs.buildNativeTarget }}
        if: ${{ inputs.skipNativeBuild != 'yes' }}

      - name: Upload next-swc artifact
        if: ${{ inputs.uploadSwcArtifact == 'yes' }}
        uses: actions/upload-artifact@v4
        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' }}

      - 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' }}

      - run: pnpm playwright install --with-deps ${{ inputs.browser }}
        if: ${{ inputs.skipInstallBuild != 'yes' }}

      - name: Download pre-built test timings
        if: ${{ inputs.testTimingsArtifact != '' }}
        uses: actions/download-artifact@v4
        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@v4
        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@v4
        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@v4
        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: Upload test report to datadog
        if: ${{ inputs.afterBuild && always() && !github.event.pull_request.head.repo.fork }}
        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
            pnpm dlx @datadog/datadog-ci@2.45.1 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
            pnpm dlx @datadog/datadog-ci@2.45.1 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@v4
        if: ${{ inputs.afterBuild && always() }}
        with:
          name: test-playwright-snapshots-${{ steps.var.outputs.input_step_key }}
          path: |
            test/traces
          if-no-files-found: ignore
code_freeze .github/workflows/code_freeze.yml
Triggers
workflow_dispatch
Runs on
ubuntu-latest
Jobs
start
Commands
  • npm i -g corepack@0.31 corepack enable
  • git clone https://github.com/vercel/next.js.git --depth=1 .
  • sudo ethtool -K eth0 tx off rx off
  • node ./scripts/code-freeze.js --type ${{ github.event.inputs.type }}
View raw YAML
on:
  workflow_dispatch:
    inputs:
      type:
        description: Enable/disable code freeze
        required: true
        type: choice
        options:
          - enable
          - disable

    secrets:
      CODE_FREEZE_TOKEN:
        required: true

name: Code Freeze

env:
  NAPI_CLI_VERSION: 2.18.4
  TURBO_VERSION: 2.8.11
  NODE_LTS_VERSION: 20

jobs:
  start:
    runs-on: ubuntu-latest

    environment: release-${{ github.event.inputs.releaseType }}
    steps:
      - name: Setup node
        uses: actions/setup-node@v4
        with:
          node-version: 20
          check-latest: true
      - name: Setup corepack
        run: |
          npm i -g corepack@0.31
          corepack enable

      - run: git clone https://github.com/vercel/next.js.git --depth=1 .

      # https://github.com/actions/virtual-environments/issues/1187
      - name: tune linux network
        run: sudo ethtool -K eth0 tx off rx off

      - run: node ./scripts/code-freeze.js --type ${{ github.event.inputs.type }}
        env:
          CODE_FREEZE_TOKEN: ${{ secrets.CODE_FREEZE_TOKEN }}
create_release_branch .github/workflows/create_release_branch.yml
Triggers
workflow_dispatch
Runs on
ubuntu-latest
Jobs
start
Commands
  • git clone https://github.com/vercel/next.js.git --depth=25 --single-branch --branch ${GITHUB_REF_NAME:-canary} .
  • gh auth status
  • sudo ethtool -K eth0 tx off rx off
  • npm i -g corepack@0.31 corepack enable pnpm --version
  • echo STORE_PATH=$(pnpm store path) >> $GITHUB_OUTPUT
  • pnpm install
  • node ./scripts/create-release-branch.js --branch-name ${{ github.event.inputs.branchName }} --tag-name ${{ github.event.inputs.tagName }}
View raw YAML
on:
  workflow_dispatch:
    inputs:
      branchName:
        description: name of branch to create (next-15-4)
        required: true
        type: string

      tagName:
        description: Tag to start the branch from (v15.4.1)
        type: string
        required: true

    secrets:
      RELEASE_BOT_GITHUB_TOKEN:
        required: true

name: Create Release Branch

env:
  NAPI_CLI_VERSION: 2.18.4
  TURBO_VERSION: 2.8.11
  NODE_LTS_VERSION: 20

jobs:
  start:
    if: github.repository_owner == 'vercel'
    runs-on: ubuntu-latest
    env:
      NEXT_TELEMETRY_DISABLED: 1
      # we build a dev binary for use in CI so skip downloading
      # canary next-swc binaries in the monorepo
      NEXT_SKIP_NATIVE_POSTINSTALL: 1

    steps:
      - name: Setup node
        uses: actions/setup-node@v4
        with:
          node-version: 20
          check-latest: true

      - name: Clone Next.js repository
        run: git clone https://github.com/vercel/next.js.git --depth=25 --single-branch --branch ${GITHUB_REF_NAME:-canary} .

      - name: Check token
        run: gh auth status
        env:
          GITHUB_TOKEN: ${{ secrets.RELEASE_BOT_GITHUB_TOKEN }}

      # https://github.com/actions/virtual-environments/issues/1187
      - name: tune linux network
        run: sudo ethtool -K eth0 tx off rx off

      - name: Setup corepack
        run: |
          npm i -g corepack@0.31
          corepack enable
          pnpm --version

      - id: get-store-path
        run: echo STORE_PATH=$(pnpm store path) >> $GITHUB_OUTPUT

      - 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.

      - run: pnpm install

      - run: node ./scripts/create-release-branch.js --branch-name ${{ github.event.inputs.branchName }} --tag-name ${{ github.event.inputs.tagName }}
        env:
          RELEASE_BOT_GITHUB_TOKEN: ${{ secrets.RELEASE_BOT_GITHUB_TOKEN }}
deploy_docs perms .github/workflows/deploy_docs.yml
Triggers
pull_request
Runs on
ubuntu-latest
Jobs
deploy
Commands
  • npm i -g vercel@latest
  • URL=$(bash scripts/deploy-docs.sh) echo "url=$URL" >> $GITHUB_OUTPUT echo "Deployed to $URL"
View raw YAML
name: docs-deploy

on:
  pull_request:
    paths:
      - 'apps/docs/**'
    types: [opened, synchronize]

permissions:
  contents: read
  pull-requests: write

env:
  NODE_LTS_VERSION: 20

jobs:
  deploy:
    name: Deploy docs to Vercel
    runs-on: ubuntu-latest
    env:
      VERCEL_API_TOKEN: ${{ secrets.VERCEL_API_TOKEN }}
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          ref: ${{ github.sha }}
          fetch-depth: 25

      - name: Install Vercel CLI
        run: npm i -g vercel@latest

      - name: Deploy docs
        id: deploy
        run: |
          URL=$(bash scripts/deploy-docs.sh)
          echo "url=$URL" >> $GITHUB_OUTPUT
          echo "Deployed to $URL"
        env:
          DEPLOY_ENVIRONMENT: preview

      # - name: Comment on PR with deployment URL
      #   if: ${{ github.event_name == 'pull_request' }}
      #   uses: actions/github-script@v7
      #   with:
      #     script: |
      #       const pr = github.context.payload.pull_request?.number
      #       const url = '${{ steps.deploy.outputs.url }}'
      #       if (!pr || !url) return
      #       await github.rest.issues.createComment({
      #         owner: context.repo.owner,
      #         repo: context.repo.repo,
      #         issue_number: pr,
      #         body: `Docs deployed: ${url}`
      #       })
graphite_ci_optimizer .github/workflows/graphite_ci_optimizer.yml
Triggers
workflow_call
Runs on
ubuntu-latest
Jobs
optimize-ci
Actions
withgraphite/graphite-ci-action
Commands
  • echo 'github.event_name: ${{ github.event_name }}' echo "secrets.GRAPHITE_TOKEN != '': ${{ secrets.GRAPHITE_TOKEN != '' }}" echo 'env.HAS_BYPASS_LABEL: ${{ env.HAS_BYPASS_LABEL }}' echo 'steps.check-skip.outputs.skip: ${{ steps.check-skip.outputs.skip }}'
View raw YAML
# Avoid running the full CI on mid-stack PRs: https://graphite.dev/docs/stacking-and-ci
#
# We still run some high-signal low-cost jobs (e.g. lint, unit tests) on these mid-stack PRs, just
# not anything slow (e.g. integration tests).
#
# Because we don't use Graphite's CI batching, full CI will still run on every PR individually
# before it merges. The goal is just to avoid wasting CI capacity when frequently rebasing large
# stacks.
#
# This can by bypassed by labeling a PR with 'CI Bypass Graphite Optimization', and manually
# re-running CI.

name: Graphite CI Optimizer
on:
  workflow_call:
    outputs:
      skip:
        description: "'true' if expensive CI checks should be skipped, 'false' otherwise."
        value: ${{ jobs.optimize-ci.outputs.skip }}
    secrets:
      GRAPHITE_TOKEN:
        description: 'The Graphite CI optimization secret'
        # secrets are not available in forks, check-skip will just fail-open with a warning
        required: false
env:
  # FYI, if you add this label, you must *push* to the repository again to trigger a new event. Just
  # re-running in the GitHub actions UI won't work, as it will re-use the old event with the old
  # labels.
  HAS_BYPASS_LABEL: |-
    ${{
      github.event_name == 'pull_request' &&
      contains(github.event.pull_request.labels.*.name, 'CI Bypass Graphite Optimization')
    }}
jobs:
  optimize-ci:
    name: Graphite CI Optimizer
    runs-on: ubuntu-latest
    outputs:
      skip: ${{ env.HAS_BYPASS_LABEL == 'false' && steps.check-skip.outputs.skip == 'true' }}
    steps:
      - name: Optimize CI
        id: check-skip
        uses: withgraphite/graphite-ci-action@main
        with:
          graphite_token: ${{ secrets.GRAPHITE_TOKEN }}
      - name: Debug Output
        run: |
          echo 'github.event_name: ${{ github.event_name }}'
          echo "secrets.GRAPHITE_TOKEN != '': ${{ secrets.GRAPHITE_TOKEN != '' }}"
          echo 'env.HAS_BYPASS_LABEL: ${{ env.HAS_BYPASS_LABEL }}'
          echo 'steps.check-skip.outputs.skip: ${{ steps.check-skip.outputs.skip }}'
integration_tests_reusable matrix .github/workflows/integration_tests_reusable.yml
Triggers
workflow_call
Runs on
self-hosted, linux, x64, metal, self-hosted, linux, x64, metal
Jobs
build-next, build-native, generate-matrices, test-e2e, test-integration, collect_nextjs_development_integration_stat
Matrix
group→ ${{ fromJSON(needs.generate-matrices.outputs.e2e) }}, ${{ fromJSON(needs.generate-matrices.outputs.integration) }}
Commands
  • printf 'e2e=[%s]\n' \ "$(seq -s, 1 ${{ inputs.e2e_groups }})" | \ tee -a "$GITHUB_OUTPUT" printf 'integration=[%s]\n' \ "$(seq -s, 1 ${{ inputs.integration_groups }})" | \ tee -a "$GITHUB_OUTPUT"
View raw YAML
name: Integration Tests Reusable

on:
  workflow_call:
    inputs:
      name:
        description: A unique identifer used for uploaded assets
        type: string
      test_type:
        description: '"development" or "production"'
        required: true
        type: string
      run_before_test:
        description: >
          Bash code to run before executing the test (e.g. setting environment
          variables). Runs in the same step as the test.
        type: string
        default: ''
      e2e_groups:
        description: >
          Size of the matrix used for running e2e tests (controls parallelism)
        type: number
        default: 6
      integration_groups:
        description: >
          Size of the matrix used for running legacy integration tests (controls
          parallelism)
        type: number
        default: 6
      e2e_timeout_minutes:
        type: number
        default: 30
      integration_timeout_minutes:
        type: number
        default: 30
      num_retries:
        type: number
        default: 2

jobs:
  # First, build Next.js to execute across tests.
  build-next:
    name: build-next
    uses: ./.github/workflows/build_reusable.yml
    with:
      skipNativeBuild: yes
      stepName: build-next
    secrets: inherit

  build-native:
    name: build-native
    uses: ./.github/workflows/build_reusable.yml
    with:
      skipInstallBuild: yes
      stepName: build-native
    secrets: inherit

  generate-matrices:
    runs-on: [self-hosted, linux, x64, metal]
    steps:
      - id: out
        run: |
          printf 'e2e=[%s]\n' \
            "$(seq -s, 1 ${{ inputs.e2e_groups }})" | \
            tee -a "$GITHUB_OUTPUT"
          printf 'integration=[%s]\n' \
            "$(seq -s, 1 ${{ inputs.integration_groups }})" | \
            tee -a "$GITHUB_OUTPUT"
    outputs:
      e2e: ${{ steps.out.outputs.e2e }}
      integration: ${{ steps.out.outputs.integration }}

  # Actual test scheduling. These jobs mimic the normal test jobs.
  # Refer build_and_test.yml for more details.
  #
  # We run tests in two parts. Legacy integration tests are run separately:
  # https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#test-types-in-nextjs
  test-e2e:
    # Name must match `integrationTestJobs` in
    # `./.github/actions/next-integration-stat`
    name: >-
      Next.js integration test (E2E and ${{ inputs.test_type }})
      (${{ matrix.group }}/${{ inputs.e2e_groups }})
    needs: [build-next, build-native, generate-matrices]
    strategy:
      fail-fast: false
      matrix:
        group: ${{ fromJSON(needs.generate-matrices.outputs.e2e) }}
    uses: ./.github/workflows/build_reusable.yml
    with:
      afterBuild: |
        # e2e and ${{ inputs.test_type }} tests with `node run-tests.js`

        export NEXT_TEST_MODE=${{
          inputs.test_type == 'development' && 'dev' || 'start'
        }}
        export NEXT_TEST_EMIT_ALL_OUTPUT=1

        ${{ inputs.run_before_test }}

        node run-tests.js \
          --group ${{ matrix.group }}/${{ inputs.e2e_groups }} \
          --retries ${{ inputs.num_retries }} \
          --type ${{ inputs.test_type }}
      stepName: test-${{ inputs.name }}-${{ matrix.group }}
      timeout_minutes: ${{ inputs.e2e_timeout_minutes }}
    secrets: inherit

  test-integration:
    # Name must match `integrationTestJobs` in
    # `./.github/actions/next-integration-stat`
    name: >-
      Next.js integration test (Integration)
      (${{ matrix.group }}/${{ inputs.e2e_groups }})
    needs: [build-next, build-native, generate-matrices]
    strategy:
      fail-fast: false
      matrix:
        group: ${{ fromJSON(needs.generate-matrices.outputs.integration) }}
    uses: ./.github/workflows/build_reusable.yml
    with:
      nodeVersion: 20.9.0
      afterBuild: |
        # legacy integration tests with `node run-tests.js`

        # HACK: Despite the name, these environment variables are just used to
        # gate tests, so they're applicable to both turbopack and rspack tests
        export ${{
          inputs.test_type == 'development' &&
            'TURBOPACK_DEV=1' ||
            'TURBOPACK_BUILD=1'
        }}
        export NEXT_TEST_EMIT_ALL_OUTPUT=1

        ${{ inputs.run_before_test }}

        node run-tests.js \
          --group ${{ matrix.group }}/${{ inputs.integration_groups }} \
          --retries ${{ inputs.num_retries }} \
          --type integration
      stepName: test-${{ inputs.name }}-integration-${{ matrix.group }}
      timeout_minutes: ${{ inputs.integration_timeout_minutes }}
    secrets: inherit

  # Collect integration test results from execute_tests,
  # Store it as github artifact for next step to consume.
  collect_nextjs_development_integration_stat:
    needs: [test-e2e, test-integration]
    name: Next.js integration test development status report
    runs-on: [self-hosted, linux, x64, metal]
    if: always()
    permissions:
      pull-requests: write
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Collect integration test stat
        uses: ./.github/actions/next-integration-stat
        with:
          token: ${{ secrets.GITHUB_TOKEN }}

      - name: Store artifacts
        uses: actions/upload-artifact@v4
        with:
          name: test-results-${{ inputs.name }}
          path: |
            nextjs-test-results.json
            failed-test-path-list.json
            passed-test-path-list.json
issue_lock perms .github/workflows/issue_lock.yml
Triggers
schedule, workflow_dispatch
Runs on
ubuntu-latest
Jobs
action
Actions
dessant/lock-threads
View raw YAML
name: 'Lock Threads'

on:
  schedule:
    # This runs twice a day: https://crontab.guru/#0_0,12_*_*_*
    - cron: '0 0,12 * * *'
  workflow_dispatch:

permissions:
  issues: write
  pull-requests: write

concurrency:
  group: lock

jobs:
  action:
    runs-on: ubuntu-latest
    if: github.repository_owner == 'vercel'
    steps:
      - uses: dessant/lock-threads@v5
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          add-issue-labels: 'locked'
          add-pr-labels: 'locked'
          issue-inactive-days: 14
          issue-comment: 'This closed issue has been automatically locked because it had no new activity for 2 weeks. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.'
          pr-inactive-days: 14
          log-output: true
issue_stale .github/workflows/issue_stale.yml
Triggers
workflow_dispatch, schedule
Runs on
ubuntu-latest
Jobs
stale
Actions
actions/stale, actions/stale, actions/stale, actions/stale
View raw YAML
name: 'Stale issue handler'
on:
  workflow_dispatch:
  schedule:
    # This runs every day 20 minutes before midnight: https://crontab.guru/#40_23_*_*_*
    - cron: '40 23 * * *'

jobs:
  stale:
    runs-on: ubuntu-latest
    if: github.repository_owner == 'vercel'
    steps:
      - uses: actions/stale@v9
        id: issue-stale
        name: 'Mark stale issues, close stale issues'
        with:
          repo-token: ${{ secrets.STALE_TOKEN }}
          ascending: true
          days-before-issue-close: 7
          days-before-issue-stale: 545 # issues with no activity in over ~1.5 years
          days-before-pr-close: -1
          days-before-pr-stale: -1
          remove-issue-stale-when-updated: true
          stale-issue-label: 'stale'
          labels-to-add-when-unstale: 'not stale'
          stale-issue-message: 'This issue has been automatically marked as stale due to inactivity. It will be closed in 7 days unless there’s further input. If you believe this issue is still relevant, please leave a comment or provide updated details. Thank you.'
          close-issue-message: 'This issue has been automatically closed due to inactivity. If you’re still experiencing a similar problem or have additional details to share, please open a new issue following our current issue template. Your updated report helps us investigate and address concerns more efficiently. Thank you for your understanding!'
          operations-per-run: 300 # 1 operation per 100 issues, the rest is to label/comment/close
      - uses: actions/stale@v9
        id: stale-no-repro
        name: 'Close stale issues with no reproduction'
        with:
          repo-token: ${{ secrets.STALE_TOKEN }}
          any-of-issue-labels: 'please add a complete reproduction'
          close-issue-message: 'This issue has been automatically closed due to 2 days of inactivity and the absence of a complete reproduction. If you believe this was done in error, please leave a comment. If you are experiencing a similar issue, consider opening a new issue with a complete reproduction. Thank you.'
          days-before-issue-close: 2
          days-before-issue-stale: 1
          days-before-pr-close: -1
          days-before-pr-stale: -1
          remove-issue-stale-when-updated: true
          stale-issue-label: 'stale'
          labels-to-add-when-unstale: 'not stale'
          operations-per-run: 300 # 1 operation per 100 issues, the rest is to label/comment/close
      - uses: actions/stale@v9
        id: stale-simple-repro
        name: 'Close issues with no simple repro'
        with:
          repo-token: ${{ secrets.STALE_TOKEN }}
          any-of-issue-labels: 'please simplify reproduction'
          close-issue-message: 'This issue has been automatically closed due to 14 days of inactivity and the absence of a simple reproduction for investigation. If you believe this was done in error, please leave a comment. If you are experiencing a similar issue, consider opening a new issue with a simple reproduction. Thank you.'
          days-before-issue-close: 14
          days-before-issue-stale: 1
          days-before-pr-close: -1
          days-before-pr-stale: -1
          remove-issue-stale-when-updated: true
          stale-issue-label: 'stale'
          labels-to-add-when-unstale: 'not stale'
          operations-per-run: 300 # 1 operation per 100 issues, the rest is to label/comment/close
      - uses: actions/stale@v9
        id: stale-no-canary
        name: 'Close issues not verified on canary'
        with:
          repo-token: ${{ secrets.STALE_TOKEN }}
          any-of-issue-labels: 'please verify canary'
          close-issue-message: 'This issue has been automatically closed due to 14 days of inactivity and the absence of testing against next@canary. If you believe this was done in error, please leave a comment. If you are experiencing a similar issue, consider opening a new issue with a reproduction. Thank you.'
          days-before-issue-close: 14
          days-before-issue-stale: 1
          days-before-pr-close: -1
          days-before-pr-stale: -1
          remove-issue-stale-when-updated: true
          stale-issue-label: 'stale'
          labels-to-add-when-unstale: 'not stale'
          operations-per-run: 300 # 1 operation per 100 issues, the rest is to label/comment/close
issue_wrong_template .github/workflows/issue_wrong_template.yml
Triggers
issues
Runs on
ubuntu-latest
Jobs
close
Commands
  • npm i -g corepack@0.31 corepack enable
  • node ./.github/actions/next-repo-actions/dist/wrong-issue-template/index.js
View raw YAML
name: 'Close issues using the wrong issue template'

on:
  issues:
    types: [labeled]

jobs:
  close:
    if: github.event.label.name == 'please use the correct issue template'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
      - name: Setup corepack
        run: |
          npm i -g corepack@0.31
          corepack enable
      - name: 'Close issues using the wrong issue template'
        run: node ./.github/actions/next-repo-actions/dist/wrong-issue-template/index.js
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
popular .github/workflows/popular.yml
Triggers
schedule, workflow_dispatch
Runs on
ubuntu-latest
Jobs
run
Commands
  • npm i -g corepack@0.31 corepack enable
  • node ./.github/actions/next-repo-actions/dist/issues/index.mjs
  • node ./.github/actions/next-repo-actions/dist/prs/index.js
  • node ./.github/actions/next-repo-actions/dist/feature-requests/index.mjs
View raw YAML
name: Notify about the top 15 issues/PRs/feature requests (most reacted) in the last 90 days

on:
  schedule:
    - cron: '0 10 * * 1' # Every Monday at 10AM UTC (6AM EST)
  workflow_dispatch:

jobs:
  run:
    if: github.repository_owner == 'vercel'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
      - name: Setup corepack
        run: |
          npm i -g corepack@0.31
          corepack enable
      - name: 'Issues: Send notification to Slack'
        run: node ./.github/actions/next-repo-actions/dist/issues/index.mjs
        continue-on-error: true
      - name: 'PRs: Send notification to Slack'
        run: node ./.github/actions/next-repo-actions/dist/prs/index.js
        continue-on-error: true
      - name: 'Feature requests: Send notification to Slack'
        run: node ./.github/actions/next-repo-actions/dist/feature-requests/index.mjs
        continue-on-error: true
    env:
      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }}
pull_request_stats matrix .github/workflows/pull_request_stats.yml
Triggers
pull_request, push
Runs on
self-hosted, linux, x64, metal, ubuntu-latest
Jobs
build, stats, stats-aggregate
Matrix
bundler→ turbopack, webpack
Commands
  • echo "DOCS_CHANGE<<EOF" >> $GITHUB_OUTPUT; echo "$(node scripts/run-for-change.mjs --not --type docs --exec echo 'nope')" >> $GITHUB_OUTPUT; echo 'EOF' >> $GITHUB_OUTPUT
  • cp -r packages/next-swc/native .github/actions/next-stats-action/native
  • echo "DOCS_CHANGE<<EOF" >> $GITHUB_OUTPUT; echo "$(node scripts/run-for-change.mjs --not --type docs --exec echo 'nope')" >> $GITHUB_OUTPUT; echo 'EOF' >> $GITHUB_OUTPUT
  • npm install
  • node src/aggregate-results.js ${{ github.workspace }}/stats-results
View raw YAML
on:
  pull_request:
    types: [opened, synchronize]
  push:
    branches:
      - canary

name: Generate Stats

concurrency:
  # Keep cancel-on-update behavior for PRs, but allow canary pushes to run independently.
  group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.ref || github.run_id }}
  cancel-in-progress: ${{ github.event_name == 'pull_request' }}

env:
  NAPI_CLI_VERSION: 2.18.4
  TURBO_VERSION: 2.8.11
  NODE_LTS_VERSION: 20
  TEST_CONCURRENCY: 6

  TURBO_TEAM: 'vercel'
  TURBO_CACHE: 'remote:rw'
  NEXT_TELEMETRY_DISABLED: 1
  # we build a dev binary for use in CI so skip downloading
  # canary next-swc binaries in the monorepo
  NEXT_SKIP_NATIVE_POSTINSTALL: 1
  # 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
  NEXT_DISABLE_SWC_WASM: 1

jobs:
  build:
    uses: ./.github/workflows/build_reusable.yml
    secrets: inherit
    with:
      stepName: 'generate-pull-request-stats'
      uploadSwcArtifact: 'yes'
      uploadAnalyzerArtifacts: 'yes'

  stats:
    name: Stats (${{ matrix.bundler }})
    needs: build
    timeout-minutes: 25
    strategy:
      fail-fast: false
      matrix:
        bundler: [webpack, turbopack]
    runs-on:
      - 'self-hosted'
      - 'linux'
      - 'x64'
      - 'metal'
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 25

      - name: Check non-docs only change
        run: echo "DOCS_CHANGE<<EOF" >> $GITHUB_OUTPUT; echo "$(node scripts/run-for-change.mjs --not --type docs --exec echo 'nope')" >> $GITHUB_OUTPUT; echo 'EOF' >> $GITHUB_OUTPUT
        id: docs-change

      - uses: actions/download-artifact@v4
        if: ${{ steps.docs-change.outputs.DOCS_CHANGE == 'nope' }}
        with:
          name: next-swc-binary
          path: packages/next-swc/native

      - run: cp -r packages/next-swc/native .github/actions/next-stats-action/native
        if: ${{ steps.docs-change.outputs.DOCS_CHANGE == 'nope' }}

      - uses: ./.github/actions/next-stats-action
        if: ${{ steps.docs-change.outputs.DOCS_CHANGE == 'nope' }}
        with:
          bundler: ${{ matrix.bundler }}
        env:
          PR_STATS_COMMENT_TOKEN: ${{ secrets.PR_STATS_COMMENT_TOKEN }}
          TURBO_TEAM: ${{ env.TURBO_TEAM }}
          TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
          TURBO_CACHE: ${{ env.TURBO_CACHE }}

      - name: Upload stats results
        if: ${{ steps.docs-change.outputs.DOCS_CHANGE == 'nope' }}
        uses: actions/upload-artifact@v4
        with:
          name: pr-stats-${{ matrix.bundler }}
          path: pr-stats-${{ matrix.bundler }}.json
          retention-days: 1

  stats-aggregate:
    name: Aggregate Stats
    needs: stats
    if: always() && needs.stats.result != 'cancelled'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 25

      - name: Check non-docs only change
        run: echo "DOCS_CHANGE<<EOF" >> $GITHUB_OUTPUT; echo "$(node scripts/run-for-change.mjs --not --type docs --exec echo 'nope')" >> $GITHUB_OUTPUT; echo 'EOF' >> $GITHUB_OUTPUT
        id: docs-change

      - name: Download all stats artifacts
        if: ${{ steps.docs-change.outputs.DOCS_CHANGE == 'nope' }}
        uses: actions/download-artifact@v4
        with:
          pattern: pr-stats-*
          path: stats-results
          merge-multiple: true

      - name: Setup Node.js
        if: ${{ steps.docs-change.outputs.DOCS_CHANGE == 'nope' }}
        uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_LTS_VERSION }}

      - name: Install dependencies
        if: ${{ steps.docs-change.outputs.DOCS_CHANGE == 'nope' }}
        working-directory: .github/actions/next-stats-action
        run: npm install

      - name: Aggregate and post results
        if: ${{ steps.docs-change.outputs.DOCS_CHANGE == 'nope' }}
        working-directory: .github/actions/next-stats-action
        run: node src/aggregate-results.js ${{ github.workspace }}/stats-results
        env:
          PR_STATS_COMMENT_TOKEN: ${{ secrets.PR_STATS_COMMENT_TOKEN }}
          KV_REST_API_URL: ${{ secrets.KV_REST_API_URL }}
          KV_REST_API_TOKEN: ${{ secrets.KV_REST_API_TOKEN }}
release-next-rspack .github/workflows/release-next-rspack.yml
Triggers
workflow_dispatch
Runs on
ubuntu-latest
Jobs
build, release
Actions
rspack-contrib/rspack-toolchain/get-napi-info, rspack-contrib/rspack-toolchain/download-rspack-binding
Commands
  • echo "🚀 Release Configuration:" echo " - Dry-run mode: ${{ inputs.dry-run }}" echo " - NPM name: ${{ inputs.npm-name || '@next/rspack-core' }}" echo " - NPM tag: ${{ inputs.npm-tag || 'latest' }}" if [ "${{ inputs.dry-run }}" == "true" ]; then echo " - ⚠️ This is a DRY RUN - no packages will be published" else echo " - 📦 This will PUBLISH packages to npm" fi
  • corepack enable
  • corepack prepare
  • pnpm run change-npm-name "${{ inputs.npm-name }}"
  • pnpm install
  • ls -R artifacts
  • pnpm napi create-npm-dirs
  • pnpm napi artifacts
View raw YAML
name: Release next-rspack bindings

on:
  workflow_dispatch:
    inputs:
      dry-run:
        description: 'Run in dry-run mode (no actual publishing)'
        required: false
        default: false
        type: boolean
      npm-name:
        description: 'NPM package name to publish'
        required: false
        default: '@next/rspack-core'
        type: string
      npm-tag:
        description: 'NPM tag for publishing'
        required: false
        default: 'latest'
        type: choice
        options:
          - latest
          - alpha
          - beta
          - canary

env:
  DEBUG: napi:*

jobs:
  build:
    name: Build
    uses: rspack-contrib/rspack-toolchain/.github/workflows/build.yml@f69dc04fcae6b38d97b87acef448ed7a285b01cc
    with:
      package-json-path: rspack/crates/binding/package.json
      napi-build-command: pnpm build --release
      working-directory: rspack

  release:
    runs-on: ubuntu-latest
    environment: npm
    name: Release
    permissions:
      contents: write
      id-token: write
    needs: [build]

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

      - name: Display release mode
        run: |
          echo "🚀 Release Configuration:"
          echo "  - Dry-run mode: ${{ inputs.dry-run }}"
          echo "  - NPM name: ${{ inputs.npm-name || '@next/rspack-core' }}"
          echo "  - NPM tag: ${{ inputs.npm-tag || 'latest' }}"
          if [ "${{ inputs.dry-run }}" == "true" ]; then
            echo "  - ⚠️  This is a DRY RUN - no packages will be published"
          else
            echo "  - 📦 This will PUBLISH packages to npm"
          fi
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '22'

      - name: Enable corepack
        run: corepack enable

      - name: Setup pnpm
        run: corepack prepare

      - name: Change npm name
        run: pnpm run change-npm-name "${{ inputs.npm-name }}"
        working-directory: ./rspack

      - name: Cache pnpm dependencies
        uses: actions/cache@v3
        with:
          path: ~/.pnpm-store
          key: ${{ runner.os }}-${{ runner.arch }}-pnpm-v2-${{ hashFiles('**/pnpm-lock.yaml') }}
          # Do not use restore-keys since it leads to indefinite growth of the cache.

      - name: Install dependencies
        run: pnpm install
        working-directory: ./rspack

      - name: Get NAPI info
        id: napi-info
        uses: rspack-contrib/rspack-toolchain/get-napi-info@f69dc04fcae6b38d97b87acef448ed7a285b01cc
        with:
          package-json-path: rspack/crates/binding/package.json

      - name: Download rspack binding
        uses: rspack-contrib/rspack-toolchain/download-rspack-binding@f69dc04fcae6b38d97b87acef448ed7a285b01cc
        with:
          path: ${{ steps.napi-info.outputs.binding-directory }}/artifacts

      - name: List artifacts
        run: ls -R artifacts
        working-directory: ${{ steps.napi-info.outputs.binding-directory }}

      - name: Create npm dirs
        run: pnpm napi create-npm-dirs
        working-directory: ${{ steps.napi-info.outputs.binding-directory }}

      - name: Move artifacts
        run: pnpm napi artifacts
        working-directory: ${{ steps.napi-info.outputs.binding-directory }}

      - name: List npm dirs
        run: ls -R npm
        working-directory: ${{ steps.napi-info.outputs.binding-directory }}

      - name: Create npm token
        run: |
          echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc
        env:
          NPM_TOKEN: ${{ secrets.NPM_TOKEN_ELEVATED }}

      - name: Release npm binding packages
        run: |
          npm config set access public
          npm config set provenance true
          pnpm napi pre-publish --no-gh-release -t npm ${{ inputs.dry-run && '--dry-run' || '' }}
        working-directory: ${{ steps.napi-info.outputs.binding-directory }}

      - name: Release npm packages
        run: |
          pnpm publish -r --tag ${{ inputs.npm-tag }} --no-git-checks --provenance --access public ${{ inputs.dry-run && '--dry-run' || '' }}
        working-directory: ./rspack
retry_deploy_test perms .github/workflows/retry_deploy_test.yml
Triggers
workflow_run
Runs on
ubuntu-latest, ubuntu-latest
Jobs
retry-on-failure, report-failure
Actions
slackapi/slack-github-action
Commands
  • gh api \ --method POST \ -H "Accept: application/vnd.github+json" \ -H "X-GitHub-Api-Version: 2022-11-28" \ /repos/${{ github.repository }}/actions/runs/${{ github.event.workflow_run.id }}/rerun-failed-jobs
View raw YAML
name: retry-deploy-tests

on:
  workflow_run:
    workflows: ['test-e2e-deploy-release']
    types:
      - completed

env:
  SLACK_WEBHOOK_URL: ${{ secrets.BROKEN_DEPLOY_SLACK_WEBHOOK_URL }}

permissions:
  actions: write

jobs:
  retry-on-failure:
    name: retry failed jobs
    # Retry the test-e2e-deploy-release workflow once
    if: >-
      ${{
        (
          github.event.workflow_run.event == 'release' ||
          github.event.workflow_run.display_title == 'test-e2e-deploy canary'
        ) &&
        github.event.workflow_run.conclusion == 'failure' &&
        github.repository == 'vercel/next.js' &&
        github.event.workflow_run.run_attempt < 2
      }}
    runs-on: ubuntu-latest
    steps:
      - name: send retry request to GitHub API
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          gh api \
            --method POST \
            -H "Accept: application/vnd.github+json" \
            -H "X-GitHub-Api-Version: 2022-11-28" \
            /repos/${{ github.repository }}/actions/runs/${{ github.event.workflow_run.id }}/rerun-failed-jobs

  report-failure:
    name: report failure to slack
    # Report the failure to Slack if the test-e2e-deploy-release workflow has failed 2 times
    if: >-
      ${{
        (
          github.event.workflow_run.event == 'release' ||
          github.event.workflow_run.display_title == 'test-e2e-deploy canary'
        ) &&
        github.event.workflow_run.conclusion == 'failure' &&
        github.event.workflow_run.run_attempt >= 2 &&
        !github.event.workflow_run.head_repository.fork
      }}
    runs-on: ubuntu-latest
    steps:
      - name: send webhook
        uses: slackapi/slack-github-action@v1.25.0
        with:
          payload: |
            {
              "commit_title": ${{ toJSON(github.event.workflow_run.display_title) }},
              "commit_url": "github.com/${{ github.repository }}/commit/${{ github.event.workflow_run.head_sha }}",
              "workflow_run_url": "github.com/${{ github.repository }}/actions/runs/${{ github.event.workflow_run.id }}/attempts/${{ github.event.workflow_run.run_attempt }}",
              "workflow_branch": ${{ toJSON(github.event.workflow_run.head_branch) }}
            }
        env:
          SLACK_WEBHOOK_URL: ${{ env.SLACK_WEBHOOK_URL }}
retry_test perms .github/workflows/retry_test.yml
Triggers
workflow_run
Runs on
ubuntu-latest, ubuntu-latest
Jobs
retry-on-failure, report-failure
Actions
slackapi/slack-github-action
Commands
  • gh api \ --method POST \ -H "Accept: application/vnd.github+json" \ -H "X-GitHub-Api-Version: 2022-11-28" \ /repos/${{ github.repository }}/actions/runs/${{ github.event.workflow_run.id }}/rerun-failed-jobs
View raw YAML
name: retry-tests

on:
  workflow_run:
    # Make sure that required_job_conclusion knows what the name of the required
    # job is in each workflow.
    workflows: ['build-and-test', 'build-and-deploy']
    branches: [canary]
    types:
      - completed

env:
  SLACK_WEBHOOK_URL: ${{ secrets.BROKEN_CANARY_SLACK_WEBHOOK_URL }}

permissions:
  actions: write

jobs:
  retry-on-failure:
    name: retry failed jobs
    # Retry the build-and-test workflow up to 2 times
    if: >-
      ${{ 
        github.event.workflow_run.conclusion == 'failure' &&
        github.repository == 'vercel/next.js' &&
        github.event.workflow_run.run_attempt < 3
      }}
    runs-on: ubuntu-latest
    steps:
      - name: Check conclusion of required job
        id: required_job_conclusion
        uses: actions/github-script@v7
        continue-on-error: true
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          script: |
            // See build-and-test.yml and build-and-deploy.yml for the required job names
            const requiredJobName = {
              'build-and-test': 'thank you, next',
              'build-and-deploy': 'thank you, build',
            }[context.payload.workflow_run.name]

            async function rerunIfRequiredNotSuccessful() {
              const result = await github.paginate.iterator(
                'GET /repos/{owner}/{repo}/actions/runs/{run_id}/attempts/{attempt_number}/jobs',
                {
                  attempt_number: context.payload.workflow_run.run_attempt,
                  owner: context.repo.owner,
                  repo: context.repo.repo,
                  run_id: context.payload.workflow_run.id,
                }
              )

              for await (const { data: jobs } of result) {
                for (const job of jobs) {
                  if (job.name === requiredJobName) {
                    console.log(
                      "Using conclusion '%s' from %s",
                      job.conclusion,
                      job.html_url
                    )
                    return job.conclusion
                  }
                }
              }

              console.log("Couldn't find job with name '%s'", requiredJobName)
              return null
            }

            return await rerunIfRequiredNotSuccessful()
      - name: send retry request to GitHub API
        if: ${{ steps.required_job_conclusion.outputs.result != '"success"' }}
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          gh api \
            --method POST \
            -H "Accept: application/vnd.github+json" \
            -H "X-GitHub-Api-Version: 2022-11-28" \
            /repos/${{ github.repository }}/actions/runs/${{ github.event.workflow_run.id }}/rerun-failed-jobs

  report-failure:
    name: report failure to slack
    # Report the failure to Slack if the build-and-test workflow has failed 3 times
    # build-and-deploy is not retried so we always report it
    if: >-
      ${{ 
        github.event.workflow_run.conclusion == 'failure' &&
        github.event.workflow_run.run_attempt >= 3 &&
        !github.event.workflow_run.head_repository.fork
      }}
    runs-on: ubuntu-latest
    steps:
      - name: Check conclusion of required job
        id: required_job_conclusion
        uses: actions/github-script@v7
        continue-on-error: true
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          script: |
            // See build-and-test.yml and build-and-deploy.yml for the required job names
            const requiredJobName = {
              'build-and-test': 'thank you, next',
              'build-and-deploy': 'thank you, build',
            }[context.payload.workflow_run.name]

            async function rerunIfRequiredNotSuccessful() {
              const result = await github.paginate.iterator(
                'GET /repos/{owner}/{repo}/actions/runs/{run_id}/attempts/{attempt_number}/jobs',
                {
                  attempt_number: context.payload.workflow_run.run_attempt,
                  owner: context.repo.owner,
                  repo: context.repo.repo,
                  run_id: context.payload.workflow_run.id,
                }
              )

              for await (const { data: jobs } of result) {
                for (const job of jobs) {
                  if (job.name === requiredJobName) {
                    console.log(
                      "Using conclusion '%s' from %s",
                      job.conclusion,
                      job.html_url
                    )
                    return job.conclusion
                  }
                }
              }

              console.log("Couldn't find job with name '%s'", requiredJobName)
              return null
            }

            return await rerunIfRequiredNotSuccessful()
      - name: send webhook
        if: ${{ steps.required_job_conclusion.outputs.result != '"success"' }}
        uses: slackapi/slack-github-action@v1.25.0
        with:
          # These urls are intentionally missing the protocol,
          # allowing them to be transformed into actual links in the Slack workflow
          # (through slightly hacky means).
          payload: |
            {
              "commit_title": ${{ toJSON(github.event.workflow_run.display_title) }},
              "commit_url": "github.com/${{ github.repository }}/commit/${{ github.event.workflow_run.head_sha }}",
              "workflow_run_url": "github.com/${{ github.repository }}/actions/runs/${{ github.event.workflow_run.id }}/attempts/${{ github.event.workflow_run.run_attempt }}"
            }
        env:
          SLACK_WEBHOOK_URL: ${{ env.SLACK_WEBHOOK_URL }}
rspack-nextjs-build-integration-tests .github/workflows/rspack-nextjs-build-integration-tests.yml
Triggers
schedule, workflow_dispatch
Runs on
Jobs
test-dev
View raw YAML
name: Rspack Next.js production integration tests

on:
  schedule:
    # Run an hour earlier than the turbopack tests, so we don't overwhelm the CI
    - cron: '0 5 * * *'
  workflow_dispatch: {}

jobs:
  test-dev:
    name: Rspack integration tests
    uses: ./.github/workflows/integration_tests_reusable.yml
    with:
      name: rspack-production
      test_type: production
      run_before_test: |
        export NEXT_RSPACK=1 NEXT_TEST_USE_RSPACK=1 \
      # Failing tests take longer (due to timeouts and retries). Since we have
      # many failing tests, we need smaller groups and longer timeouts, in case
      # a group gets stuck with a cluster of failing tests.
      e2e_groups: 12
      integration_groups: 12
      e2e_timeout_minutes: 90
      integration_timeout_minutes: 90
    secrets: inherit
rspack-nextjs-dev-integration-tests .github/workflows/rspack-nextjs-dev-integration-tests.yml
Triggers
schedule, workflow_dispatch
Runs on
Jobs
test-dev
View raw YAML
name: Rspack Next.js development integration tests

on:
  schedule:
    # Run an hour earlier than the turbopack tests, so we don't overwhelm the CI
    - cron: '0 5 * * *'
  workflow_dispatch: {}

jobs:
  test-dev:
    name: Rspack integration tests
    uses: ./.github/workflows/integration_tests_reusable.yml
    with:
      name: rspack-development
      test_type: development
      run_before_test: |
        export NEXT_RSPACK=1 NEXT_TEST_USE_RSPACK=1
      # Failing tests take longer (due to timeouts and retries). Since we have
      # many failing tests, we need smaller groups and longer timeouts, in case
      # a group gets stuck with a cluster of failing tests.
      e2e_groups: 16
      integration_groups: 16
      e2e_timeout_minutes: 90
      integration_timeout_minutes: 90
    secrets: inherit
rspack-update-tests-manifest .github/workflows/rspack-update-tests-manifest.yml
Triggers
schedule, workflow_dispatch
Runs on
ubuntu-latest, ubuntu-latest
Jobs
update_dev_manifest, update_build_manifest
Commands
  • npm i -g corepack@0.31 corepack enable
  • pnpm i
  • node scripts/automated-update-workflow.js
  • npm i -g corepack@0.31 corepack enable
  • pnpm i
  • node scripts/automated-update-workflow.js
View raw YAML
# A recurring workflow which updates the passing/failing/skipped integration tests for Turbopack.
name: Update Rspack test manifest

on:
  schedule:
    # Every day at 7AM https://crontab.guru/#0_7_*_*_*
    - cron: '0 7 * * *'
  workflow_dispatch:

jobs:
  update_dev_manifest:
    name: Update and upload Rspack development test manifest
    if: github.repository_owner == 'vercel'
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          # Commits made with the default `GITHUB_TOKEN` won't trigger workflows.
          # See: https://docs.github.com/en/actions/security-guides/automatic-token-authentication#using-the-github_token-in-a-workflow
          token: ${{ secrets.RELEASE_BOT_GITHUB_TOKEN }}

      - name: Setup node
        uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_LTS_VERSION }}
          check-latest: true

      - name: Setup corepack
        run: |
          npm i -g corepack@0.31
          corepack enable

      - name: Install dependencies
        shell: bash
        run: pnpm i

      - name: Create Pull Request
        shell: bash
        run: node scripts/automated-update-workflow.js
        env:
          GITHUB_TOKEN: ${{ secrets.GH_TOKEN_PULL_REQUESTS }}
          BRANCH_NAME: rspack-manifest
          # We need to use `--override` for rspack (but not for turbopack).
          # We don't currently have any CI running on every PR, so it's quite
          # possible for us to regress on tests. We need to skip the
          # only-promote-to-passing merge logic.
          SCRIPT: test/update-bundler-manifest.js --bundler rspack --test-suite dev --override
          PR_TITLE: Update Rspack development test manifest
          PR_BODY: This auto-generated PR updates the development integration test manifest used when testing Rspack.
  update_build_manifest:
    name: Update and upload Rspack production test manifest
    if: github.repository_owner == 'vercel'
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          # Commits made with the default `GITHUB_TOKEN` won't trigger workflows.
          # See: https://docs.github.com/en/actions/security-guides/automatic-token-authentication#using-the-github_token-in-a-workflow
          token: ${{ secrets.RELEASE_BOT_GITHUB_TOKEN }}

      - name: Setup node
        uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_LTS_VERSION }}
          check-latest: true

      - name: Setup corepack
        run: |
          npm i -g corepack@0.31
          corepack enable

      - name: Install dependencies
        shell: bash
        run: pnpm i

      - name: Create Pull Request
        shell: bash
        run: node scripts/automated-update-workflow.js
        env:
          GITHUB_TOKEN: ${{ secrets.GH_TOKEN_PULL_REQUESTS }}
          BRANCH_NAME: rspack-manifest
          SCRIPT: test/update-bundler-manifest.js --bundler rspack --test-suite build --override
          PR_TITLE: Update Rspack production test manifest
          PR_BODY: This auto-generated PR updates the production integration test manifest used when testing Rspack.
setup-nextjs-build .github/workflows/setup-nextjs-build.yml
Triggers
workflow_call
Runs on
self-hosted, linux, x64, metal
Jobs
build_nextjs
Actions
SimenB/github-actions-cpu-cores, dtolnay/rust-toolchain
Commands
  • echo runner cpu count ${{ steps.cpu-cores.outputs.count }}
  • # Grab the latest release version from next.js repo, including prelease. `/releases/latest` will only return latest stable release. echo NEXJS_LATEST_VERSION=$(gh release --repo vercel/next.js --limit 1 list | sed -n 1p | awk '{print $1}') >> $GITHUB_ENV
  • echo "NEXTJS_VERSION=${{ inputs.version != '' && inputs.version || env.NEXJS_LATEST_VERSION }}" >> $GITHUB_ENV
  • echo "Checking out Next.js ${{ env.NEXTJS_VERSION }}"
  • wget https://github.com/sharkdp/hyperfine/releases/download/v1.16.1/hyperfine_1.16.1_amd64.deb sudo dpkg -i hyperfine_1.16.1_amd64.deb npm i -g corepack@0.31 corepack enable pnpm install --loglevel error
  • hyperfine --min-runs 1 --show-output 'pnpm run --filter=@next/swc build-native --features plugin --release' echo "Successfully built next-swc with published turbopack"
  • pnpm run build strip packages/next-swc/native/next-swc.*.node ls -al packages/next-swc/native # Reduce the size of the cache bit cd packages/next-swc && cargo clean && cd ../../ echo NEXT_SWC_FILESIZE: $(stat -c %s packages/next-swc/native/next-swc.linux-x64-gnu.node) node -e "console.log('Host', require('os').arch(), require('os').platform())"
  • # This is being used in github action to collect test results. Do not change it, or should update ./.github/actions/next-integration-test to match. docker run --rm -v $(pwd):/work mcr.microsoft.com/playwright:v1.28.1-jammy /bin/bash -c 'curl https://install-node.vercel.app/v16 | FORCE=1 bash && cd /work && echo RUNNING NEXTJS VERSION: $(packages/next/dist/bin/next --version) && ls -al packages/next-swc/native && node -e "console.log(\"Container\", require(\"os\").arch(), require(\"os\").platform())"'
View raw YAML
# Reusable workflow to setup next.js integration test environment.
name: Setup Next.js

on:
  workflow_call:
    inputs:
      # Allow to specify Next.js version to run integration test against.
      # If not specified, will use latest release version including canary.
      version:
        type: string
      nodeVersion:
        required: false
        description: 'version of Node.js to use'
        type: string

jobs:
  build_nextjs:
    name: Build Next.js for the turbopack integration test
    runs-on:
      - 'self-hosted'
      - 'linux'
      - 'x64'
      - 'metal'
    outputs:
      output1: ${{ steps.build-next-swc-turbopack-patch.outputs.success }}
    steps:
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ inputs.nodeVersion || env.NODE_LTS_VERSION }}
          check-latest: true
      - name: Get number of CPU cores
        uses: SimenB/github-actions-cpu-cores@v2
        id: cpu-cores

      - name: 'Setup Rust toolchain'
        uses: dtolnay/rust-toolchain@stable

      - name: Display runner information
        run: echo runner cpu count ${{ steps.cpu-cores.outputs.count }}

      - name: Find Next.js latest release version
        env:
          GH_TOKEN: ${{ github.token }}
        run: |
          # Grab the latest release version from next.js repo, including prelease. `/releases/latest` will only return latest stable release.
          echo NEXJS_LATEST_VERSION=$(gh release --repo vercel/next.js --limit 1 list | sed -n 1p | awk '{print $1}') >> $GITHUB_ENV

      - name: Set Next.js release version
        run: |
          echo "NEXTJS_VERSION=${{ inputs.version != '' && inputs.version || env.NEXJS_LATEST_VERSION }}" >> $GITHUB_ENV

      - name: Print Next.js release version to checkout
        run: echo "Checking out Next.js ${{ env.NEXTJS_VERSION }}"

      - name: Checkout Next.js
        uses: actions/checkout@v4
        with:
          repository: vercel/next.js
          ref: ${{ env.NEXTJS_VERSION }}

      - name: Checkout failed test lists
        uses: actions/checkout@v4
        with:
          repository: vercel/turbo
          ref: nextjs-integration-test-data
          path: integration-test-data

      - name: Download binary
        uses: actions/download-artifact@v4
        with:
          path: artifacts

      - uses: actions/cache/restore@v3
        id: restore-build
        with:
          path: |
            ./*
          key: ${{ inputs.version }}-${{ github.sha }}

      - name: Install dependencies
        run: |
          wget https://github.com/sharkdp/hyperfine/releases/download/v1.16.1/hyperfine_1.16.1_amd64.deb
          sudo dpkg -i hyperfine_1.16.1_amd64.deb
          npm i -g corepack@0.31
          corepack enable
          pnpm install --loglevel error

      - name: Build next-swc
        run: |
          hyperfine --min-runs 1 --show-output 'pnpm run --filter=@next/swc build-native --features plugin --release'
          echo "Successfully built next-swc with published turbopack"

      - name: Build next.js
        run: |
          pnpm run build
          strip packages/next-swc/native/next-swc.*.node
          ls -al packages/next-swc/native
          # Reduce the size of the cache bit
          cd packages/next-swc && cargo clean && cd ../../
          echo NEXT_SWC_FILESIZE: $(stat -c %s packages/next-swc/native/next-swc.linux-x64-gnu.node)
          node -e "console.log('Host', require('os').arch(), require('os').platform())"

      # If input version is published release, detect version by running next.js build.
      - name: Detects Next.js build version
        run: |
          # This is being used in github action to collect test results. Do not change it, or should update ./.github/actions/next-integration-test to match.
          docker run --rm -v $(pwd):/work mcr.microsoft.com/playwright:v1.28.1-jammy /bin/bash -c 'curl https://install-node.vercel.app/v16 | FORCE=1 bash && cd /work && echo RUNNING NEXTJS VERSION: $(packages/next/dist/bin/next --version) && ls -al packages/next-swc/native && node -e "console.log(\"Container\", require(\"os\").arch(), require(\"os\").platform())"'

      - name: Temporary test skip
        run: |
          rm -rf test/integration/jsconfig-paths/test/index.test.js

      # Once build completes, creates a cache of the build output
      # so subsequent job to actually execute tests can reuse it.
      # Note that we do not use upload / download artifacts for this -
      # it is too heavyweight for the purpose since we do not need to persist
      # the cache across multiple runs.
      - name: Store next.js build cache with next-swc
        uses: actions/cache/save@v3
        id: cache-build
        with:
          path: |
            ./*
          key: ${{ inputs.version }}-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt}}-${{ github.run_number }}
test-turbopack-rust-bench-test .github/workflows/test-turbopack-rust-bench-test.yml
Triggers
workflow_call
Runs on
${{ fromJSON(inputs.runner) }}
Jobs
test
Commands
  • git config --global core.autocrlf false git config --global core.eol lf
  • corepack enable
  • pnpm install
  • cargo test --benches --workspace --release --no-fail-fast --exclude turbopack-bench --exclude next-napi-bindings --no-run
  • cargo test --benches --workspace --release --no-fail-fast --exclude turbopack-bench --exclude next-napi-bindings
  • cargo test --benches --release -p turbopack-bench --no-run
  • cargo test --benches --release -p turbopack-bench
View raw YAML
name: Turbopack Rust testing benchmarks
on:
  workflow_call:
    inputs:
      runner:
        type: string
        default: '["self-hosted", "linux", "x64", "metal"]'
      os:
        type: string
        default: 'linux'
      all:
        type: boolean
        default: false

env:
  TURBOPACK_BENCH_COUNTS: '100'
  TURBOPACK_BENCH_PROGRESS: '1'

  NODE_LTS_VERSION: 20

jobs:
  test:
    name: Test
    runs-on: ${{ fromJSON(inputs.runner) }}
    steps:
      - name: Set git to use LF
        run: |
          git config --global core.autocrlf false
          git config --global core.eol lf
        if: inputs.os == 'windows'

      - name: Checkout
        uses: actions/checkout@v3

      - name: Setup Rust
        uses: ./.github/actions/setup-rust

      - name: Setup node
        uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_LTS_VERSION }}
          check-latest: true
      - run: corepack enable

      # We need to install the dependencies for the benchmark apps
      - run: pnpm install
        working-directory: turbopack/benchmark-apps

      - name: Build benchmarks for tests
        timeout-minutes: 120
        run: |
          cargo test --benches --workspace --release --no-fail-fast --exclude turbopack-bench --exclude next-napi-bindings --no-run

      - name: Run cargo test on benchmarks
        timeout-minutes: 120
        run: |
          cargo test --benches --workspace --release --no-fail-fast --exclude turbopack-bench --exclude next-napi-bindings

      - name: Build benchmarks for tests for other bundlers
        if: inputs.all
        timeout-minutes: 120
        run: |
          cargo test --benches --release -p turbopack-bench --no-run

      - name: Run cargo test on benchmarks for other bundlers
        if: inputs.all
        timeout-minutes: 120
        run: |
          cargo test --benches --release -p turbopack-bench
test_e2e_deploy_release matrix .github/workflows/test_e2e_deploy_release.yml
Triggers
release, workflow_dispatch
Runs on
ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest
Jobs
setup, test-deploy-webpack, test-deploy-turbopack, test-deploy-adapter, report-test-results-to-datadog, upload-adapter-test-results, create-draft-prs, update-prs
Matrix
group, include, include.repo, include.workflow_id, include.workflow_url→ 1/12, 1/8, 10/12, 11/12, 12/12, 2/12, 2/8, 3/12, 3/8, 4/12, 4/8, 5/12, 5/8, 6/12, 6/8, 7/12, 7/8, 8/12, 8/8, 9/12, cron-update-next.yml, front, https://github.com/vercel/front/actions/workflows/cron-update-next.yml?query=event%3Aworkflow_dispatch, https://github.com/vercel/v0/actions/workflows/update-next.yaml?query=event%3Aworkflow_dispatch, update-next.yaml, v0
Commands
  • npm i -g corepack@0.31 corepack enable
  • cd packages/next { echo 'value<<EOF' cat package.json echo EOF } >> "$GITHUB_OUTPUT"
  • echo 'value=${{ fromJson(steps.nextPackageInfo.outputs.value).version }}' >> "$GITHUB_OUTPUT"
  • pnpm install
  • node run-tests.js --timings --write-timings -g 1/1
  • if [ ! -f test-timings.json ]; then echo "No timings fetched, creating empty timings file" echo '{}' > test-timings.json fi
  • if [ -d ./test/test-junit-report ]; then DD_ENV=ci npx @datadog/datadog-ci@2.23.1 junit upload --tags test.type:deploy --service nextjs ./test/test-junit-report fi
  • if [ -z "${ADAPTER_TEST_RESULTS_SECRET:-}" ]; then echo "ADAPTER_TEST_RESULTS_SECRET is not configured, skipping upload" exit 0 fi if [ ! -d adapter-test-results ]; then echo "No adapter test artifact directory found, skipping upload" exit 0 fi if ! find adapter-test-results -type f -name '*.results.json' | grep -q .; then echo "No adapter .results.json files found, skipping upload" exit 0 fi node scripts/upload-adapter-test-results.mjs \ --results-root adapter-test-results \ --provider vercel \ --commit-sha "${{ github.sha }}"
View raw YAML
name: test-e2e-deploy-release

on:
  # run on every release/prerelease
  release:
    types: [published]
  # allow triggering manually as well
  workflow_dispatch:
    inputs:
      nextVersion:
        description: canary or custom tarball URL
        default: canary
        type: string
      vercelCliVersion:
        description: Version of Vercel CLI to use
        default: 'vercel@latest'
        type: string
      overrideProxyAddress:
        description: Override the proxy address to use for the test
        default: ''
        type: string
      deployScriptPath:
        description: Custom deploy script path (NEXT_TEST_DEPLOY_SCRIPT_PATH)
        default: ''
        type: string
      deployLogsScriptPath:
        description: Custom deploy logs script path (NEXT_TEST_DEPLOY_LOGS_SCRIPT_PATH)
        default: ''
        type: string
      cleanupScriptPath:
        description: Custom cleanup script path (NEXT_TEST_CLEANUP_SCRIPT_PATH)
        default: ''
        type: string

env:
  DD_ENV: 'ci'
  DATADOG_API_KEY: ${{ secrets.DATA_DOG_API_KEY }}
  VERCEL_TEST_TEAM: vtest314-next-e2e-tests
  VERCEL_TEST_TOKEN: ${{ secrets.VERCEL_TEST_TOKEN }}
  VERCEL_ADAPTER_TEST_TEAM: vtest314-next-adapter-e2e-tests
  VERCEL_ADAPTER_TEST_TOKEN: ${{ secrets.VERCEL_ADAPTER_TEST_TOKEN }}
  VERCEL_TURBOPACK_TEST_TEAM: vtest314-next-turbo-e2e-tests
  VERCEL_TURBOPACK_TEST_TOKEN: ${{ secrets.VERCEL_TURBOPACK_TEST_TOKEN }}

run-name: test-e2e-deploy ${{ inputs.nextVersion || (github.event_name == 'release' && github.event.release.tag_name) || 'canary' }}

jobs:
  setup:
    runs-on: ubuntu-latest
    if: github.repository_owner == 'vercel'
    outputs:
      next-version: ${{ steps.version.outputs.value }}
    steps:
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_LTS_VERSION }}
          check-latest: true

      - name: Setup pnpm
        run: |
          npm i -g corepack@0.31
          corepack enable

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

      - id: nextPackageInfo
        name: Get `next` package info
        run: |
          cd packages/next 
          {
            echo 'value<<EOF'
            cat package.json
            echo EOF
          } >> "$GITHUB_OUTPUT"
      - id: version
        name: Extract `next` version
        run: echo 'value=${{ fromJson(steps.nextPackageInfo.outputs.value).version }}' >> "$GITHUB_OUTPUT"

      - name: Install dependencies
        run: pnpm install

      - name: Fetch test timings
        run: node run-tests.js --timings --write-timings -g 1/1
        continue-on-error: true
        env:
          KV_REST_API_URL: ${{ secrets.KV_REST_API_URL }}
          KV_REST_API_TOKEN: ${{ secrets.KV_REST_API_TOKEN }}

      - name: Ensure test timings file exists
        run: |
          if [ ! -f test-timings.json ]; then
            echo "No timings fetched, creating empty timings file"
            echo '{}' > test-timings.json
          fi

      - name: Upload test timings
        uses: actions/upload-artifact@v4
        with:
          name: test-timings
          path: test-timings.json
          retention-days: 1
          if-no-files-found: error

  test-deploy-webpack:
    name: Run Deploy Tests (Webpack)
    needs: setup
    if: ${{ github.event.inputs.deployScriptPath == '' }}
    uses: ./.github/workflows/build_reusable.yml
    secrets: inherit
    strategy:
      fail-fast: false
      matrix:
        group: [1/8, 2/8, 3/8, 4/8, 5/8, 6/8, 7/8, 8/8]
    with:
      afterBuild: |
        npm i -g vercel@latest && \
        NEXT_E2E_TEST_TIMEOUT=240000 \
        NEXT_TEST_MODE=deploy \
        IS_WEBPACK_TEST=1 \
        NEXT_EXTERNAL_TESTS_FILTERS="test/deploy-tests-manifest.json" \
        NEXT_TEST_VERSION="${{ github.event.inputs.nextVersion || needs.setup.outputs.next-version || 'canary' }}" \
        VERCEL_CLI_VERSION="${{ github.event.inputs.vercelCliVersion || 'vercel@latest' }}" \
        NEXT_TEST_DEPLOY_SCRIPT_PATH="${{ github.event.inputs.deployScriptPath || '' }}" \
        NEXT_TEST_DEPLOY_LOGS_SCRIPT_PATH="${{ github.event.inputs.deployLogsScriptPath || '' }}" \
        NEXT_TEST_CLEANUP_SCRIPT_PATH="${{ github.event.inputs.cleanupScriptPath || '' }}" \
        node run-tests.js --timings --require-timings -g ${{ matrix.group }} -c 2 --type e2e
      testTimingsArtifact: 'test-timings'
      skipNativeBuild: 'yes'
      skipNativeInstall: 'no'
      stepName: 'test-deploy-webpack-${{ matrix.group }}'
      timeout_minutes: 180
      runs_on_labels: '["ubuntu-latest"]'
      overrideProxyAddress: ${{ inputs.overrideProxyAddress || '' }}

  test-deploy-turbopack:
    name: Run Deploy Tests (Turbopack)
    needs: [setup]
    uses: ./.github/workflows/build_reusable.yml
    secrets: inherit
    strategy:
      fail-fast: false
      matrix:
        group:
          [
            1/12,
            2/12,
            3/12,
            4/12,
            5/12,
            6/12,
            7/12,
            8/12,
            9/12,
            10/12,
            11/12,
            12/12,
          ]
    with:
      afterBuild: |
        npm i -g vercel@latest && \
        NEXT_E2E_TEST_TIMEOUT=240000 \
        NEXT_TEST_MODE=deploy \
        IS_TURBOPACK_TEST=1 \
        NEXT_TEST_CONTINUE_ON_ERROR="${{ github.event.inputs.continueOnError || false }}" \
        NEXT_EXTERNAL_TESTS_FILTERS="test/deploy-tests-manifest.json" \
        NEXT_TEST_VERSION="${{ github.event.inputs.nextVersion || needs.setup.outputs.next-version || 'canary' }}" \
        VERCEL_CLI_VERSION="${{ github.event.inputs.vercelCliVersion || 'vercel@latest' }}" \
        NEXT_TEST_DEPLOY_SCRIPT_PATH="${{ github.event.inputs.deployScriptPath || '' }}" \
        NEXT_TEST_DEPLOY_LOGS_SCRIPT_PATH="${{ github.event.inputs.deployLogsScriptPath || '' }}" \
        NEXT_TEST_CLEANUP_SCRIPT_PATH="${{ github.event.inputs.cleanupScriptPath || '' }}" \
        node run-tests.js --timings --require-timings -g ${{ matrix.group }} -c 2 --type e2e
      testTimingsArtifact: 'test-timings'
      skipNativeBuild: 'yes'
      skipNativeInstall: 'no'
      stepName: 'test-deploy-turbopack-${{ matrix.group }}'
      timeout_minutes: 180
      runs_on_labels: '["ubuntu-latest"]'
      overrideProxyAddress: ${{ inputs.overrideProxyAddress || '' }}

  test-deploy-adapter:
    name: Run Deploy Adapter Tests (Turbopack)
    needs: setup
    if: ${{ github.event.inputs.deployScriptPath == '' }}
    uses: ./.github/workflows/build_reusable.yml
    secrets: inherit
    strategy:
      fail-fast: false
      matrix:
        group:
          [
            1/12,
            2/12,
            3/12,
            4/12,
            5/12,
            6/12,
            7/12,
            8/12,
            9/12,
            10/12,
            11/12,
            12/12,
          ]
    with:
      afterBuild: |
        npm i -g vercel@latest && \
        NEXT_E2E_TEST_TIMEOUT=240000 \
        NEXT_TEST_MODE=deploy \
        IS_TURBOPACK_TEST=1 \
        NEXT_ENABLE_ADAPTER=1 \
        NEXT_EXTERNAL_TESTS_FILTERS="test/deploy-tests-manifest.json" \
        NEXT_TEST_VERSION="${{ github.event.inputs.nextVersion || needs.setup.outputs.next-version || 'canary' }}" \
        VERCEL_CLI_VERSION="${{ github.event.inputs.vercelCliVersion || 'vercel@latest' }}" \
        node run-tests.js --timings --require-timings -g ${{ matrix.group }} -c 2 --type e2e
      testTimingsArtifact: 'test-timings'
      skipNativeBuild: 'yes'
      skipNativeInstall: 'no'
      stepName: 'test-deploy-deploy-${{ matrix.group }}'
      testReportsArtifactPrefix: 'adapter-test-reports'
      timeout_minutes: 180
      runs_on_labels: '["ubuntu-latest"]'
      overrideProxyAddress: ${{ inputs.overrideProxyAddress || '' }}

  report-test-results-to-datadog:
    needs: [test-deploy-turbopack, test-deploy-webpack]
    if: ${{ always() && github.event.inputs.deployScriptPath == '' }}

    runs-on: ubuntu-latest
    name: Report test results to datadog
    steps:
      - name: Download test report artifacts
        id: download-test-reports
        uses: actions/download-artifact@v4
        with:
          pattern: test-reports-*
          path: test
          merge-multiple: true

      - name: Upload test report to datadog
        run: |
          if [ -d ./test/test-junit-report ]; then
            DD_ENV=ci npx @datadog/datadog-ci@2.23.1 junit upload --tags test.type:deploy --service nextjs ./test/test-junit-report
          fi

  upload-adapter-test-results:
    name: Upload adapter test results
    needs: [test-deploy-adapter]
    if: >-
      ${{
        always() &&
        github.event.inputs.deployScriptPath == '' &&
        github.repository_owner == 'vercel' &&
        (
          (github.event_name == 'workflow_dispatch' && github.ref_name == 'canary') ||
          (github.event_name == 'release' && github.event.release.target_commitish == 'canary')
        )
      }}
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20.9.0
          check-latest: true

      - name: Download adapter test result artifacts
        continue-on-error: true
        uses: actions/download-artifact@v4
        with:
          pattern: adapter-test-reports-*
          path: adapter-test-results
          merge-multiple: true

      - name: Upload adapter test results
        run: |
          if [ -z "${ADAPTER_TEST_RESULTS_SECRET:-}" ]; then
            echo "ADAPTER_TEST_RESULTS_SECRET is not configured, skipping upload"
            exit 0
          fi

          if [ ! -d adapter-test-results ]; then
            echo "No adapter test artifact directory found, skipping upload"
            exit 0
          fi

          if ! find adapter-test-results -type f -name '*.results.json' | grep -q .; then
            echo "No adapter .results.json files found, skipping upload"
            exit 0
          fi

          node scripts/upload-adapter-test-results.mjs \
            --results-root adapter-test-results \
            --provider vercel \
            --commit-sha "${{ github.sha }}"
        env:
          ADAPTER_TEST_RESULTS_SECRET: ${{ secrets.ADAPTER_TEST_RESULTS_SECRET }}

  create-draft-prs:
    name: Immediately open draft prs
    needs: setup
    if: ${{ github.repository_owner == 'vercel' && github.event_name == 'release' }}
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        include:
          - repo: front
            workflow_id: cron-update-next.yml
          - repo: v0
            workflow_id: update-next.yaml
    steps:
      - name: Check token
        run: gh auth status
        # This sometimes fails for unknown reasons.
        # Ignoring failures for now to check if a failure actually blocks subsequent steps
        continue-on-error: true
        env:
          GITHUB_TOKEN: ${{ secrets.GH_UPDATE_NEXT_WORKFLOW_TRIGGER }}
      - uses: actions/github-script@v7
        name: Check if target workflow is enabled
        id: check-workflow-enabled
        with:
          retries: 3
          retry-exempt-status-codes: 400,401,403,404,422
          github-token: ${{ secrets.GH_UPDATE_NEXT_WORKFLOW_TRIGGER }}
          result-encoding: string
          script: |
            const response = await github.request(
              "GET /repos/{owner}/{repo}/actions/workflows/{workflow_id}",
              {
                owner: "vercel",
                repo: "${{ matrix.repo }}",
                workflow_id: "${{ matrix.workflow_id }}",
              }
            );

            const isEnabled = response.data.state === 'active';
            console.info(`Target workflow state: ${response.data.state}`);
            console.info(`Target workflow enabled: ${isEnabled}`);

            return isEnabled ? 'true' : 'false';
      - uses: actions/github-script@v7
        name: Create draft PR for vercel/${{ matrix.repo }}
        id: create-draft-pr
        if: steps.check-workflow-enabled.outputs.result == 'true'
        with:
          retries: 3
          retry-exempt-status-codes: 400,401,404
          github-token: ${{ secrets.GH_UPDATE_NEXT_WORKFLOW_TRIGGER }}
          result-encoding: string
          script: |
            const inputs = {
              version: "${{ needs.setup.outputs.next-version }}"
            };

            await github.request(
              "POST /repos/{owner}/{repo}/actions/workflows/{workflow_id}/dispatches",
              {
                owner: "vercel",
                repo: "${{ matrix.repo }}",
                workflow_id: "${{ matrix.workflow_id }}",
                ref: "main",
                inputs: inputs,
              }
            );

            console.info(`Draft PR creation triggered for ${{ matrix.repo }}`);
            console.info(`Workflow will create draft PR by default`);

  update-prs:
    name: Update prs as ready for review
    needs: [test-deploy-turbopack, test-deploy-webpack, create-draft-prs]
    if: ${{ (needs.test-deploy-webpack.result == 'success' || needs.test-deploy-webpack.result == 'skipped') && needs.test-deploy-turbopack.result == 'success' && github.repository_owner == 'vercel' && github.event_name == 'release' }}
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        include:
          - repo: front
            workflow_id: cron-update-next.yml
            workflow_url: https://github.com/vercel/front/actions/workflows/cron-update-next.yml?query=event%3Aworkflow_dispatch
          - repo: v0
            workflow_id: update-next.yaml
            workflow_url: https://github.com/vercel/v0/actions/workflows/update-next.yaml?query=event%3Aworkflow_dispatch
    steps:
      - name: Check token
        run: gh auth status
        # This sometimes fails for unknown reasons.
        # Ignoring failures for now to check if a failure actually blocks subsequent steps
        continue-on-error: true
        env:
          GITHUB_TOKEN: ${{ secrets.GH_UPDATE_NEXT_WORKFLOW_TRIGGER }}
      - uses: actions/github-script@v7
        name: Check if target workflow is enabled
        id: check-workflow-enabled
        with:
          retries: 3
          retry-exempt-status-codes: 400,401,404
          github-token: ${{ secrets.GH_UPDATE_NEXT_WORKFLOW_TRIGGER }}
          result-encoding: string
          script: |
            try {
              const response = await github.request(
                "GET /repos/{owner}/{repo}/actions/workflows/{workflow_id}",
                {
                  owner: "vercel",
                  repo: "${{ matrix.repo }}",
                  workflow_id: "${{ matrix.workflow_id }}",
                }
              );
              
              const isEnabled = response.data.state === 'active';
              console.info(`Target workflow state: ${response.data.state}`);
              console.info(`Target workflow enabled: ${isEnabled}`);
              
              return isEnabled ? 'true' : 'false';
            } catch (error) {
              console.error('Error checking workflow status:', error);
              return 'false';
            }
      - uses: actions/github-script@v7
        name: Mark PR ready for review in vercel/${{ matrix.repo }}
        id: mark-pr-ready
        if: steps.check-workflow-enabled.outputs.result == 'true'
        with:
          retries: 3
          retry-exempt-status-codes: 400,401,404
          # Default github token cannot dispatch events to the remote repo, it should be
          # a PAT with Actions write access (https://docs.github.com/en/rest/actions/workflows?apiVersion=2022-11-28#create-a-workflow-dispatch-event)
          github-token: ${{ secrets.GH_UPDATE_NEXT_WORKFLOW_TRIGGER }}
          # Note `workflow_id` and `inputs` are contract between vercel/${{ matrix.repo }},
          # if these need to be changed both side should be updated accordingly.
          script: |
            const inputs = {
              version: "${{ needs.setup.outputs.next-version }}",
              'make-ready-for-review': 'true',
              force: 'true'
            };

            await github.request(
              "POST /repos/{owner}/{repo}/actions/workflows/{workflow_id}/dispatches",
              {
                owner: "vercel",
                repo: "${{ matrix.repo }}",
                workflow_id: "${{ matrix.workflow_id }}",
                ref: "main",
                inputs: inputs,
              }
            );

            console.info("Tests passed - PR will be marked ready for review");
            console.info(
              "PR ready-for-review triggered in ${{ matrix.workflow_url }}"
            );
test_e2e_project_reset_cron .github/workflows/test_e2e_project_reset_cron.yml
Triggers
schedule, workflow_dispatch
Runs on
ubuntu-latest
Jobs
reset-test-project
Commands
  • npm i -g corepack@0.31 corepack enable
  • pnpm install
  • node scripts/run-e2e-test-project-reset.mjs
View raw YAML
name: test-e2e-project-reset-cron

on:
  # Run every Sunday at 5AM UTC
  schedule:
    - cron: '0 5 * * 0'
  # Allow manual triggering for emergency resets
  workflow_dispatch:

env:
  VERCEL_TEST_TEAM: vtest314-next-e2e-tests
  VERCEL_TEST_TOKEN: ${{ secrets.VERCEL_TEST_TOKEN }}
  VERCEL_ADAPTER_TEST_TEAM: vtest314-next-adapter-e2e-tests
  VERCEL_ADAPTER_TEST_TOKEN: ${{ secrets.VERCEL_ADAPTER_TEST_TOKEN }}
  NODE_LTS_VERSION: 20
  TURBO_TEAM: 'vercel'
  TURBO_CACHE: 'remote:rw'

run-name: test-e2e-project-reset (scheduled)

jobs:
  reset-test-project:
    runs-on: ubuntu-latest
    if: github.repository_owner == 'vercel'
    steps:
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_LTS_VERSION }}
          check-latest: true

      - name: Setup pnpm
        run: |
          npm i -g corepack@0.31
          corepack enable

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

      - name: Install dependencies
        run: pnpm install

      - name: Reset test project
        run: node scripts/run-e2e-test-project-reset.mjs
test_examples matrix .github/workflows/test_examples.yml
Triggers
workflow_dispatch, schedule
Runs on
ubuntu-latest
Jobs
testExamples
Matrix
node→ 20, 22
Commands
  • sudo ethtool -K eth0 tx off rx off
  • npm i -g corepack@0.31 corepack enable
  • pnpm install
  • pnpm build
  • docker run --rm -v $(pwd):/work mcr.microsoft.com/playwright:v1.35.1-focal /bin/bash -c "cd /work && curl -s https://install-node.vercel.app/v${{ matrix.node }} | FORCE=1 bash && node -v && corepack enable > /dev/null && NEXT_TEST_JOB=1 NEXT_TEST_MODE=start xvfb-run node run-tests.js --type examples >> /proc/1/fd/1"
View raw YAML
# This file duplicates bunch of things from build_test_deploy

on:
  workflow_dispatch:
    inputs:
      is_dispatched:
        description: 'Leave this option enabled'
        required: true
        default: true
        type: boolean
  schedule:
    - cron: '0 */4 * * *'

name: Test examples

jobs:
  testExamples:
    # Don't execute using cron on forks
    if: (github.repository == 'vercel/next.js') || (inputs.is_dispatched == true)
    name: Test Examples
    runs-on: ubuntu-latest
    timeout-minutes: 120
    env:
      NEXT_TELEMETRY_DISABLED: 1
    strategy:
      fail-fast: false
      matrix:
        node: [20, 22]
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 25
      # https://github.com/actions/virtual-environments/issues/1187
      - name: tune linux network
        run: sudo ethtool -K eth0 tx off rx off

      - name: Setup node
        uses: actions/setup-node@v4
        with:
          node-version: 20
          check-latest: true
      - name: Setup corepack
        run: |
          npm i -g corepack@0.31
          corepack enable

      - run: pnpm install
      - run: pnpm build

      - run: docker run --rm -v $(pwd):/work mcr.microsoft.com/playwright:v1.35.1-focal /bin/bash -c "cd /work && curl -s https://install-node.vercel.app/v${{ matrix.node }} | FORCE=1 bash && node -v && corepack enable > /dev/null && NEXT_TEST_JOB=1 NEXT_TEST_MODE=start xvfb-run node run-tests.js --type examples >> /proc/1/fd/1"
        name: Run test/examples
triage perms .github/workflows/triage.yml
Triggers
issues, issue_comment
Runs on
ubuntu-latest
Jobs
triage
Actions
balazsorban44/nissuer
View raw YAML
name: Triage issues

on:
  issues:
    types: [opened, labeled]
  issue_comment:
    types: [created]

env:
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

permissions:
  issues: write

jobs:
  triage:
    name: Nissuer
    runs-on: ubuntu-latest
    if: >-
      ${{
        (github.event_name != 'issue_comment' ||
        (github.event_name == 'issue_comment' && !contains(github.event.issue.labels.*.name, 'stale'))) &&
        github.event.issue.type.name != 'Documentation'
      }}
    steps:
      - uses: balazsorban44/nissuer@1.10.0
        with:
          label-area-prefix: ''
          label-area-match: 'name'
          label-area-section: 'Which area\(s\) are affected\? \(Select all that apply\)(.*)### Additional context'
          label-comments: |
            {
              "good first issue": ".github/comments/good-first-issue.md",
              "please add a complete reproduction": ".github/comments/invalid-reproduction.md",
              "please simplify reproduction": ".github/comments/simplify-reproduction.md",
              "please verify canary": ".github/comments/verify-canary.md",
              "resolved": ".github/comments/resolved.md"
            }
          reproduction-comment: '.github/comments/invalid-link.md'
          reproduction-hosts: 'github.com,bitbucket.org,gitlab.com,codesandbox.io,stackblitz.com'
          reproduction-blocklist: 'github.com/vercel/next.js.*,github.com/\\w*/?$,github.com$'
          reproduction-link-section: '### Link to the code that reproduces this issue(.*)### To Reproduce'
          reproduction-invalid-label: 'invalid link'
          reproduction-issue-labels: 'bug,'
          comment-unhelpful-weight: 0.5
          webhook-url: ${{ secrets.NISSUER_WEBHOOK_URL }}
          webhook-secret: ${{ secrets.NISSUER_WEBHOOK_SECRET }}
trigger_release .github/workflows/trigger_release.yml
Triggers
schedule, workflow_dispatch
Runs on
ubuntu-latest
Jobs
start
Commands
  • git clone https://github.com/vercel/next.js.git --depth=25 --single-branch --branch ${GITHUB_REF_NAME:-canary} .
  • gh auth status
  • echo "LATEST_TAG_COMMIT=$(git rev-list -n 1 $(git describe --tags --abbrev=0))" >> $GITHUB_ENV
  • echo "LATEST_COMMIT=$(git rev-parse HEAD)" >> $GITHUB_ENV
  • if [ "$LATEST_TAG_COMMIT" = "$LATEST_COMMIT" ]; then echo "No new commits. Exiting..." exit 1 fi
  • sudo ethtool -K eth0 tx off rx off
  • npm i -g corepack@0.31 corepack enable pnpm --version
  • echo STORE_PATH=$(pnpm store path) >> $GITHUB_OUTPUT
View raw YAML
on:
  schedule:
    # run every day at 23:15
    - cron: '15 23 * * *'

  workflow_dispatch:
    inputs:
      releaseType:
        description: stable, canary, beta, or release candidate?
        required: true
        type: choice
        options:
          - canary
          - stable
          - release-candidate
          - beta

      semverType:
        description: semver type?
        type: choice
        options:
          - patch
          - minor
          - major

      force:
        description: create a new release even if there are no new commits
        default: false
        type: boolean

name: Trigger Release

env:
  NAPI_CLI_VERSION: 2.18.4
  TURBO_VERSION: 2.8.11
  NODE_LTS_VERSION: 20

jobs:
  start:
    if: github.repository_owner == 'vercel'
    runs-on: ubuntu-latest
    env:
      NEXT_TELEMETRY_DISABLED: 1
      # we build a dev binary for use in CI so skip downloading
      # canary next-swc binaries in the monorepo
      NEXT_SKIP_NATIVE_POSTINSTALL: 1

    environment: release-${{ github.event.inputs.releaseType || 'canary' }}
    steps:
      - name: Setup node
        uses: actions/setup-node@v4
        with:
          node-version: 20
          check-latest: true

      - name: Clone Next.js repository
        run: git clone https://github.com/vercel/next.js.git --depth=25 --single-branch --branch ${GITHUB_REF_NAME:-canary} .

      - name: Check token
        run: gh auth status
        # This sometimes fails for unknown reasons.
        # Ignoring failures for now to check if a failure truly implies a failed publish.
        continue-on-error: true
        env:
          GITHUB_TOKEN: ${{ secrets.RELEASE_BOT_GITHUB_TOKEN }}

      - name: Get commit of the latest tag
        run: echo "LATEST_TAG_COMMIT=$(git rev-list -n 1 $(git describe --tags --abbrev=0))" >> $GITHUB_ENV

      - name: Get latest commit
        run: echo "LATEST_COMMIT=$(git rev-parse HEAD)" >> $GITHUB_ENV

      - name: Check if new commits since last tag
        if: ${{ github.event.inputs.releaseType != 'stable' && github.event.inputs.force != true }}
        run: |
          if [ "$LATEST_TAG_COMMIT" = "$LATEST_COMMIT" ]; then
            echo "No new commits. Exiting..."
            exit 1
          fi

      # https://github.com/actions/virtual-environments/issues/1187
      - name: tune linux network
        run: sudo ethtool -K eth0 tx off rx off

      - name: Setup corepack
        run: |
          npm i -g corepack@0.31
          corepack enable
          pnpm --version

      - id: get-store-path
        run: echo STORE_PATH=$(pnpm store path) >> $GITHUB_OUTPUT

      - 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.

      - run: pnpm install

      - run: node ./scripts/start-release.js --release-type ${{ github.event.inputs.releaseType || 'canary' }} --semver-type ${{ github.event.inputs.semverType }}
        env:
          RELEASE_BOT_GITHUB_TOKEN: ${{ secrets.RELEASE_BOT_GITHUB_TOKEN }}
turbopack-benchmark .github/workflows/turbopack-benchmark.yml
Triggers
workflow_dispatch, push, pull_request
Runs on
self-hosted, linux, x64, metal, self-hosted, linux, x64, metal, self-hosted, linux, x64, metal
Jobs
benchmark-small-apps, benchmark-analyzer, benchmark-large
Actions
taiki-e/install-action, ijjk/rust-cache, CodSpeedHQ/action, taiki-e/install-action, ijjk/rust-cache, CodSpeedHQ/action, taiki-e/install-action, CodSpeedHQ/action
Commands
  • npm i -g corepack@0.31 corepack enable pnpm install --loglevel error
  • cargo codspeed build -p turbopack-cli small_apps
  • npm i -g corepack@0.31 corepack enable pnpm install --loglevel error
  • cargo codspeed build -p turbopack-ecmascript references
  • cargo codspeed build -p turbopack -p turbopack-bench
View raw YAML
name: Turbopack Benchmark

on:
  workflow_dispatch:
  push:
    branches:
      - canary
  pull_request:
    types: ['opened', 'reopened', 'synchronize', 'labeled']
    paths:
      - '**/crates/**'
      - '**/Cargo.toml'
      - '**/Cargo.lock'

concurrency:
  # Limit concurrent runs to 1 per PR, but allow concurrent runs on canary branch
  group: ${{ github.event_name == 'pull_request' && format('{0}-{1}', github.workflow, github.event.pull_request.number) || format('{0}-{1}-{2}', github.workflow, github.ref_name, github.run_id) }}
  cancel-in-progress: ${{ github.event_name == 'pull_request' }}

env:
  CI: 1
  CARGO_INCREMENTAL: 0
  # For faster CI
  RUST_LOG: 'off'
  TURBO_TEAM: 'vercel'
  TURBO_CACHE: 'remote:rw'

jobs:
  benchmark-small-apps:
    name: Benchmark Rust Crates (small apps)
    runs-on: ['self-hosted', 'linux', 'x64', 'metal']
    steps:
      - uses: actions/checkout@v4

      - name: Setup Rust toolchain
        uses: ./.github/actions/setup-rust

      - name: Install cargo-codspeed
        uses: taiki-e/install-action@v2
        with:
          tool: cargo-codspeed@3.0.5

      - name: Cache on ${{ github.ref_name }}
        uses: ijjk/rust-cache@turbo-cache-v1.0.9
        with:
          save-if: 'true'
          cache-provider: 'turbo'
          shared-key: build-turbopack-benchmark-small-apps-${{ hashFiles('.cargo/config.toml') }}

      - name: Install pnpm dependencies
        working-directory: turbopack/benchmark-apps
        run: |
          npm i -g corepack@0.31
          corepack enable
          pnpm install --loglevel error

      - name: Build app build benchmarks
        env:
          CODSPEED_RUNNER_MODE: instrumentation
        run: cargo codspeed build -p turbopack-cli small_apps

      - name: Run the benchmarks
        uses: CodSpeedHQ/action@v4
        with:
          mode: instrumentation
          run: cargo codspeed run -p turbopack-cli small_apps
          token: ${{ secrets.CODSPEED_TOKEN }}

  benchmark-analyzer:
    name: Benchmark Rust Crates (analyzer)
    runs-on: ['self-hosted', 'linux', 'x64', 'metal']
    steps:
      - uses: actions/checkout@v4

      - name: Setup Rust toolchain
        uses: ./.github/actions/setup-rust

      - name: Install cargo-codspeed
        uses: taiki-e/install-action@v2
        with:
          tool: cargo-codspeed@3.0.5

      - name: Cache on ${{ github.ref_name }}
        uses: ijjk/rust-cache@turbo-cache-v1.0.9
        with:
          save-if: 'true'
          cache-provider: 'turbo'
          shared-key: build-turbopack-benchmark-analyzer-${{ hashFiles('.cargo/config.toml') }}

      - name: Install pnpm dependencies
        working-directory: turbopack/benchmark-apps
        run: |
          npm i -g corepack@0.31
          corepack enable
          pnpm install --loglevel error

      - name: Build app build benchmarks
        env:
          CODSPEED_RUNNER_MODE: instrumentation
        run: cargo codspeed build -p turbopack-ecmascript references

      - name: Run the benchmarks
        uses: CodSpeedHQ/action@v4
        with:
          mode: instrumentation
          run: cargo codspeed run -p turbopack-ecmascript references
          token: ${{ secrets.CODSPEED_TOKEN }}

  benchmark-large:
    name: Benchmark Rust Crates (large)
    if: ${{ github.event.label.name == 'benchmark' || github.event_name == 'workflow_dispatch' }}
    runs-on: ['self-hosted', 'linux', 'x64', 'metal']
    steps:
      - uses: actions/checkout@v4

      - name: Setup Rust toolchain
        uses: ./.github/actions/setup-rust

      - name: Install cargo-codspeed
        uses: taiki-e/install-action@v2
        with:
          tool: cargo-codspeed@3.0.5

      - name: Build the benchmark target(s)
        env:
          CODSPEED_RUNNER_MODE: instrumentation
        run: cargo codspeed build -p turbopack -p turbopack-bench

      - name: Run the benchmarks
        uses: CodSpeedHQ/action@v4
        with:
          mode: instrumentation
          run: cargo codspeed run
          token: ${{ secrets.CODSPEED_TOKEN }}
turbopack-nextjs-build-integration-tests .github/workflows/turbopack-nextjs-build-integration-tests.yml
Triggers
schedule, workflow_dispatch
Runs on
Jobs
test-dev
View raw YAML
name: Turbopack Next.js production integration tests

on:
  schedule:
    - cron: '0 6 * * *'
  workflow_dispatch: {}

jobs:
  test-dev:
    name: Next.js integration tests
    uses: ./.github/workflows/integration_tests_reusable.yml
    with:
      name: turbopack-production
      test_type: production
      run_before_test: |
        export IS_TURBOPACK_TEST=1 TURBOPACK_BUILD=1 RUST_BACKTRACE=1
      # Failing tests take longer (due to timeouts and retries). Since we have
      # many failing tests, we need smaller groups and longer timeouts, in case
      # a group gets stuck with a cluster of failing tests.
      e2e_groups: 12
      integration_groups: 12
      e2e_timeout_minutes: 60
      integration_timeout_minutes: 60
    secrets: inherit
turbopack-nextjs-dev-integration-tests .github/workflows/turbopack-nextjs-dev-integration-tests.yml
Triggers
schedule, workflow_dispatch
Runs on
Jobs
test-dev
View raw YAML
name: Turbopack Next.js development integration tests

on:
  schedule:
    - cron: '0 6 * * *'
  workflow_dispatch: {}

jobs:
  test-dev:
    name: Next.js integration tests
    uses: ./.github/workflows/integration_tests_reusable.yml
    with:
      name: turbopack-development
      test_type: development
      run_before_test: |
        export IS_TURBOPACK_TEST=1 TURBOPACK_DEV=1 RUST_BACKTRACE=1
    secrets: inherit
turbopack-update-tests-manifest .github/workflows/turbopack-update-tests-manifest.yml
Triggers
workflow_dispatch
Runs on
ubuntu-latest, ubuntu-latest
Jobs
update_dev_manifest, update_build_manifest
Commands
  • npm i -g corepack@0.31 corepack enable
  • pnpm i
  • node scripts/automated-update-workflow.js
  • npm i -g corepack@0.31 corepack enable
  • pnpm i
  • node scripts/automated-update-workflow.js
View raw YAML
# A recurring workflow which updates the passing/failing/skipped integration tests for Turbopack.
name: Update Turbopack test manifest

on:
  # schedule:
  # Every day at 7AM UTC https://crontab.guru/#0_7_*_*_*
  # - cron: '0 7 * * *'
  workflow_dispatch:

jobs:
  update_dev_manifest:
    name: Update and upload Turbopack development test manifest
    if: github.repository_owner == 'vercel'
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          # Commits made with the default `GITHUB_TOKEN` won't trigger workflows.
          # See: https://docs.github.com/en/actions/security-guides/automatic-token-authentication#using-the-github_token-in-a-workflow
          token: ${{ secrets.RELEASE_BOT_GITHUB_TOKEN }}

      - name: Setup node
        uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_LTS_VERSION }}
          check-latest: true

      - name: Setup corepack
        run: |
          npm i -g corepack@0.31
          corepack enable

      - name: Install dependencies
        shell: bash
        run: pnpm i

      - name: Create Pull Request
        shell: bash
        run: node scripts/automated-update-workflow.js
        env:
          GITHUB_TOKEN: ${{ secrets.GH_TOKEN_PULL_REQUESTS }}
          BRANCH_NAME: turbopack-manifest
          SCRIPT: test/update-bundler-manifest.js --bundler turbopack --test-suite dev
          PR_TITLE: Update Turbopack development test manifest
          PR_BODY: This auto-generated PR updates the development integration test manifest used when testing Turbopack.
  update_build_manifest:
    name: Update and upload Turbopack production test manifest
    if: github.repository_owner == 'vercel'
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          # Commits made with the default `GITHUB_TOKEN` won't trigger workflows.
          # See: https://docs.github.com/en/actions/security-guides/automatic-token-authentication#using-the-github_token-in-a-workflow
          token: ${{ secrets.RELEASE_BOT_GITHUB_TOKEN }}

      - name: Setup node
        uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_LTS_VERSION }}
          check-latest: true

      - name: Setup corepack
        run: |
          npm i -g corepack@0.31
          corepack enable

      - name: Install dependencies
        shell: bash
        run: pnpm i

      - name: Create Pull Request
        shell: bash
        run: node scripts/automated-update-workflow.js
        env:
          GITHUB_TOKEN: ${{ secrets.GH_TOKEN_PULL_REQUESTS }}
          BRANCH_NAME: turbopack-manifest
          SCRIPT: test/update-bundler-manifest.js --bundler turbopack --test-suite build
          PR_TITLE: Update Turbopack production test manifest
          PR_BODY: This auto-generated PR updates the production integration test manifest used when testing Turbopack.
update_fonts_data .github/workflows/update_fonts_data.yml
Triggers
schedule, workflow_dispatch
Runs on
ubuntu-latest
Jobs
create-pull-request
Commands
  • npm i -g corepack@0.31 corepack enable
  • pnpm i
  • node scripts/automated-update-workflow.js
View raw YAML
name: Update Font Data

on:
  # Run every every day at midnight https://crontab.guru/#0_0_*_*_*/1
  schedule:
    - cron: '0 0 * * */1'
  # Allow manual runs
  workflow_dispatch:

env:
  NODE_LTS_VERSION: 20

jobs:
  create-pull-request:
    runs-on: ubuntu-latest
    if: github.repository_owner == 'vercel'
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          # Commits made with the default `GITHUB_TOKEN` won't trigger workflows.
          # See: https://docs.github.com/en/actions/security-guides/automatic-token-authentication#using-the-github_token-in-a-workflow
          token: ${{ secrets.RELEASE_BOT_GITHUB_TOKEN }}

      - name: Setup node
        uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_LTS_VERSION }}
          check-latest: true

      - name: Setup corepack
        run: |
          npm i -g corepack@0.31
          corepack enable

      - name: Install dependencies
        shell: bash
        run: pnpm i

      - name: Create Pull Request
        shell: bash
        run: node scripts/automated-update-workflow.js
        env:
          GITHUB_TOKEN: ${{ secrets.GH_TOKEN_PULL_REQUESTS }}
          BRANCH_NAME: fonts-data
          SCRIPT: scripts/update-google-fonts.js
          PR_TITLE: Update font data
          PR_BODY: This auto-generated PR updates font data with latest available
update_react .github/workflows/update_react.yml
Triggers
schedule, workflow_dispatch
Runs on
ubuntu-latest
Jobs
create-pull-request
Commands
  • git config user.name "nextjs-bot" git config user.email "it+nextjs-bot@vercel.com"
  • npm i -g corepack@0.31 corepack enable
  • pnpm install --filter .
  • pnpm sync-react --actor "${{ github.actor }}" --commit --create-pull --version "${{ inputs.version }}"
View raw YAML
name: Update React

on:
  schedule:
    # At 40 minutes past 16:00 on Mon, Tue, Wed, Thu, and Fri
    # i.e. 30min past React nightlies: https://github.com/facebook/react/blob/941e1b4a0a81ca3d5f2ac6ef35682e2f8e96dae1/.github/workflows/runtime_prereleases_nightly.yml#L6
    # TODO: automatically trigger on React release
    - cron: 40 16 * * 1,2,3,4,5
  # Allow manual runs
  workflow_dispatch:
    inputs:
      version:
        description: 'The version to update to. Uses latest Canary if omitted.'
        required: false

env:
  NODE_LTS_VERSION: 20
  PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1

jobs:
  create-pull-request:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          # Commits made with the default `GITHUB_TOKEN` won't trigger workflows.
          # See: https://docs.github.com/en/actions/security-guides/automatic-token-authentication#using-the-github_token-in-a-workflow
          token: ${{ secrets.RELEASE_BOT_GITHUB_TOKEN }}

      - name: Set Git author
        run: |
          git config user.name "nextjs-bot"
          git config user.email "it+nextjs-bot@vercel.com"

      - name: Setup node
        uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_LTS_VERSION }}
          check-latest: true

      - name: Setup corepack
        run: |
          npm i -g corepack@0.31
          corepack enable

      - name: Install dependencies
        shell: bash
        # Just need scripts/ but those dependencies are listed in the workspace root.
        run: pnpm install --filter .

      - name: Create Pull Request
        shell: bash
        run: pnpm sync-react --actor "${{ github.actor }}" --commit --create-pull --version "${{ inputs.version }}"
        env:
          GITHUB_TOKEN: ${{ secrets.RELEASE_BOT_GITHUB_TOKEN }}
upload-tests-manifest .github/workflows/upload-tests-manifest.yml
Triggers
schedule, workflow_dispatch, push
Runs on
ubuntu-latest
Jobs
upload_test_results
Commands
  • npm i -g corepack@0.31 corepack enable
  • pnpm i
View raw YAML
# Workflow to upload next.js integration test results to KV for https://areweturboyet.com/
# This workflow assumes the `next-integration-test` workflow has been executed
# and test reports have been uploaded to the `test-results` artifact.
name: Upload bundler test manifests to areweturboyet.com

on:
  schedule:
    - cron: '0 8 * * *'
  workflow_dispatch: {}
  push:
    branches:
      - canary
    paths:
      - 'test/*-manifest.json'

jobs:
  upload_test_results:
    name: Upload test results
    runs-on: ubuntu-latest
    steps:
      - name: Setup node
        uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_LTS_VERSION }}
          check-latest: true

      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup corepack
        run: |
          npm i -g corepack@0.31
          corepack enable

      - name: Install dependencies
        shell: bash
        run: pnpm i

      - name: 'Upload results to "Are We Turbo Yet" KV'
        env:
          TURBOYET_KV_REST_API_URL: ${{ secrets.TURBOYET_KV_REST_API_URL }}
          TURBOYET_KV_REST_API_TOKEN: ${{ secrets.TURBOYET_KV_REST_API_TOKEN }}
          TURBOYET_TOKEN: ${{ secrets.TURBOYET_TOKEN }}
        uses: ./.github/actions/upload-turboyet-data