vercel/turborepo

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

Security 13.46/100

Security dimensions

permissions
13.5
security scan
0
supply chain
0
secret handling
0
harden runner
0

Workflows (13)

docs .github/workflows/docs.yml
Triggers
pull_request
Runs on
ubuntu-latest
Jobs
docs-quality
Commands
  • turbo run check-types check-links check-openapi --filter="./docs/*"
View raw YAML
name: Docs checks

on:
  pull_request:
    paths:
      - "docs/**"

concurrency:
  group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
  cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
  docs-quality:
    runs-on: ubuntu-latest
    timeout-minutes: 30
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - uses: ./.github/actions/setup-node

      - name: Install Global Turbo
        uses: ./.github/actions/install-global-turbo

      - name: Run docs checks
        run: turbo run check-types check-links check-openapi --filter="./docs/*"
docs-alias-failure-notification .github/workflows/docs-alias-failure-notification.yml
Triggers
workflow_run
Runs on
ubuntu-latest
Jobs
notify-failure
Actions
slackapi/slack-github-action
Commands
  • if [ -f version.txt ]; then VERSION=$(head -n 1 version.txt) echo "version=${VERSION}" >> $GITHUB_OUTPUT else echo "version=unknown" >> $GITHUB_OUTPUT fi
View raw YAML
# Docs Alias Failure Notification
#
# Sends a Slack notification when the versioned docs alias assignment fails.

name: Docs Alias Failure Notification

on:
  workflow_run:
    workflows: ["Release"]
    types:
      - completed

jobs:
  notify-failure:
    name: "Notify Slack on Docs Alias Failure"
    runs-on: ubuntu-latest
    if: ${{ github.event.workflow_run.conclusion == 'failure' }}
    steps:
      - name: Checkout staging branch
        uses: actions/checkout@v4
        with:
          ref: ${{ github.event.workflow_run.head_branch }}

      - name: Get version
        id: version
        run: |
          if [ -f version.txt ]; then
            VERSION=$(head -n 1 version.txt)
            echo "version=${VERSION}" >> $GITHUB_OUTPUT
          else
            echo "version=unknown" >> $GITHUB_OUTPUT
          fi

      - name: Send failure notification to Slack
        uses: slackapi/slack-github-action@v2.0.0
        with:
          webhook: ${{ secrets.DOCS_ALIAS_FAILURE_SLACK_WEBHOOK_URL }}
          webhook-type: incoming-webhook
          payload: |
            {
              "version": "${{ steps.version.outputs.version }}"
            }
lint perms .github/workflows/lint.yml
Triggers
pull_request
Runs on
ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest
Jobs
determine_changes, rust_fmt, rust_clippy, rust_licenses, format_lint, rust_audit, js_audit, cleanup
Actions
dorny/paths-filter, mozilla-actions/sccache-action, EmbarkStudios/cargo-deny-action
Commands
  • cargo fmt --check
  • if [ -z "${RUSTC_WRAPPER}" ]; then unset RUSTC_WRAPPER fi cargo lint
  • TURBO_API= turbo run quality --env-mode=strict
  • cargo install cargo-audit --locked --quiet cargo audit --deny unsound --deny yanked
  • pnpm audit --prod --audit-level low --ignore GHSA-6cpc-mj5c-m9rq
View raw YAML
name: Lint
on:
  pull_request:

concurrency:
  group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
  cancel-in-progress: ${{ github.event_name == 'pull_request' }}

env:
  RUSTFLAGS: "-D warnings"

permissions:
  actions: write
  contents: read
  pull-requests: read

jobs:
  determine_changes:
    name: Determine changes
    runs-on: ubuntu-latest
    timeout-minutes: 5
    permissions:
      contents: read
    outputs:
      rust: ${{ steps.filter.outputs.rust }}
      formatting: ${{ steps.filter.outputs.formatting }}
      dependencies: ${{ steps.filter.outputs.dependencies }}
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Check for changes
        uses: dorny/paths-filter@v3
        id: filter
        with:
          filters: |
            rust:
              - ".github/actions/**"
              - ".github/workflows/lint.yml"
              - "Cargo.**"
              - "crates/**"
              - ".cargo/**"
              - "rust-toolchain.toml"
            formatting:
              - "**/*.{yml,yaml,md,mdx,js,jsx,ts,tsx,json,toml,css}"
              - "pnpm-lock.yaml"
              - "package.json"
            dependencies:
              - "Cargo.lock"
              - "Cargo.toml"
              - "crates/**/Cargo.toml"
              - "pnpm-lock.yaml"
              - "package.json"
              - "packages/**/package.json"

  rust_fmt:
    name: Rust format
    needs: determine_changes
    if: ${{ needs.determine_changes.outputs.rust == 'true' }}
    runs-on: ubuntu-latest
    timeout-minutes: 30
    steps:
      - name: Checkout
        uses: actions/checkout@v4

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

      - name: Run cargo fmt check
        run: cargo fmt --check

  rust_clippy:
    name: Rust clippy
    needs: determine_changes
    if: ${{ needs.determine_changes.outputs.rust == 'true' }}
    runs-on: ubuntu-latest
    timeout-minutes: 30
    env:
      SCCACHE_BUCKET: turborepo-sccache
      SCCACHE_REGION: us-east-2
      RUSTC_WRAPPER: ${{ !github.event.pull_request.head.repo.fork && 'sccache' || '' }}
      CARGO_INCREMENTAL: 0
      AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
      AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
      SCCACHE_IDLE_TIMEOUT: 0
      SCCACHE_REQUEST_TIMEOUT: 30
      SCCACHE_ERROR_LOG: error
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Environment
        uses: ./.github/actions/setup-environment
        with:
          github-token: "${{ secrets.GITHUB_TOKEN }}"
          node: "false"

      - name: Run sccache-cache
        uses: mozilla-actions/sccache-action@v0.0.6

      - name: Run cargo clippy
        run: |
          if [ -z "${RUSTC_WRAPPER}" ]; then
            unset RUSTC_WRAPPER
          fi
          cargo lint

  rust_licenses:
    name: Rust licenses
    needs: determine_changes
    if: ${{ needs.determine_changes.outputs.rust == 'true' }}
    runs-on: ubuntu-latest
    timeout-minutes: 30
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Check licenses
        uses: EmbarkStudios/cargo-deny-action@v2
        with:
          command: check licenses

  format_lint:
    name: Formatting
    needs: determine_changes
    if: ${{ needs.determine_changes.outputs.formatting == 'true' }}
    runs-on: ubuntu-latest
    timeout-minutes: 30
    env:
      TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
      TURBO_TEAM: ${{ vars.TURBO_TEAM }}
      TURBO_CACHE: remote:rw
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: "Setup Node"
        uses: ./.github/actions/setup-node

      - name: Install Global Turbo
        uses: ./.github/actions/install-global-turbo

      - name: Lint
        # Manually set TURBO_API to an empty string to override Hetzner env
        run: |
          TURBO_API= turbo run quality --env-mode=strict

  rust_audit:
    name: Rust security audit
    needs: determine_changes
    if: ${{ needs.determine_changes.outputs.dependencies == 'true' }}
    runs-on: ubuntu-latest
    timeout-minutes: 10
    continue-on-error: true
    steps:
      - name: Checkout
        uses: actions/checkout@v4

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

      - name: Run cargo audit
        run: |
          cargo install cargo-audit --locked --quiet
          cargo audit --deny unsound --deny yanked

  js_audit:
    name: JS security audit
    needs: determine_changes
    if: ${{ needs.determine_changes.outputs.dependencies == 'true' }}
    runs-on: ubuntu-latest
    timeout-minutes: 10
    continue-on-error: true
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Node
        uses: ./.github/actions/setup-node

      - name: Run pnpm audit
        # --ignore: false positive from workspace directory "cli/" matching npm package "cli"
        run: pnpm audit --prod --audit-level low --ignore GHSA-6cpc-mj5c-m9rq

  cleanup:
    name: Cleanup
    needs:
      - determine_changes
      - rust_fmt
      - rust_clippy
      - rust_licenses
      - rust_audit
      - js_audit
      - format_lint
    if: always()
    uses: ./.github/workflows/pr-clean-caches.yml
    secrets: inherit
lint-pr-title perms .github/workflows/lint-pr-title.yml
Triggers
pull_request_target
Runs on
ubuntu-latest
Jobs
main
Actions
amannn/action-semantic-pull-request
View raw YAML
name: Lint pull request title

on:
  pull_request_target:
    types:
      - opened
      - edited
      - synchronize
      - reopened

permissions:
  pull-requests: read

jobs:
  main:
    name: Validate PR title
    runs-on: ubuntu-latest
    timeout-minutes: 30
    steps:
      - uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          # Configure which types are allowed (newline-delimited).
          types: |
            fix
            feat
            chore
            ci
            docs
            refactor
            perf
            test
            style
            examples
          # Scopes are not allowed.
          scopes: ""
          # Configure additional validation for the subject based on a regex.
          # Ensures that the subject starts with an uppercase character.
          subjectPattern: ^[A-Z].*$
          # If `subjectPattern` is configured, you can use this property to override
          # the default error message that is shown when the pattern doesn't match.
          # The variables `subject` and `title` can be used within the message.
          subjectPatternError: |
            The subject "{subject}" found in the pull request title "{title}" doesn't match the configured pattern.
            Please ensure that the subject starts with an uppercase character.
lsp matrix .github/workflows/lsp.yml
Triggers
workflow_dispatch
Runs on
${{ matrix.settings.host }}
Jobs
build-rust
Matrix
settings, settings.container-options, settings.container-setup, settings.host, settings.rust-build-env, settings.setup, settings.target→ --platform=linux/amd64 --rm, --rm, CC_aarch64_unknown_linux_musl=clang AR_aarch64_unknown_linux_musl=llvm-ar RUSTFLAGS="-Clink-self-contained=yes -Clinker=rust-lld", aarch64-apple-darwin, aarch64-pc-windows-msvc, aarch64-unknown-linux-musl, macos-latest, rustup set default-host aarch64-pc-windows-msvc, rustup set default-host x86_64-pc-windows-msvc, sudo apt-get install -y build-essential, sudo apt-get update && sudo apt-get install -y build-essential musl-tools clang llvm gcc-aarch64-linux-gnu binutils-aarch64-linux-gnu, sudo apt-get update && sudo apt-get install -y curl musl-tools, ubuntu-latest, windows-latest, x86_64-apple-darwin, x86_64-pc-windows-msvc, x86_64-unknown-linux-musl
Commands
  • ${{ matrix.settings.container-setup }}
  • ${{ matrix.settings.setup }}
  • ${{ matrix.settings.rust-build-env }} cargo build --profile release-turborepo-lsp -p turborepo-lsp --target ${{ matrix.settings.target }}
View raw YAML
# LSP Pipeline
#
# Currently this just dumps the LSP binaries into the artifacts, but in the future
# we will want to do the entire packaging process here.

name: LSP

on:
  workflow_dispatch:

jobs:
  build-rust:
    name: "Build Rust"
    strategy:
      fail-fast: false
      matrix:
        settings:
          - host: macos-latest
            target: "x86_64-apple-darwin"
            container-options: "--rm"
          - host: macos-latest
            target: "aarch64-apple-darwin"
            container-options: "--rm"
          - host: ubuntu-latest
            container-options: "--platform=linux/amd64 --rm"
            container-setup: "sudo apt-get update && sudo apt-get install -y curl musl-tools"
            target: "x86_64-unknown-linux-musl"
            setup: "sudo apt-get install -y build-essential"
          - host: ubuntu-latest
            container-options: "--rm"
            target: "aarch64-unknown-linux-musl"
            rust-build-env: 'CC_aarch64_unknown_linux_musl=clang AR_aarch64_unknown_linux_musl=llvm-ar RUSTFLAGS="-Clink-self-contained=yes -Clinker=rust-lld"'
            setup: "sudo apt-get update && sudo apt-get install -y build-essential musl-tools clang llvm gcc-aarch64-linux-gnu binutils-aarch64-linux-gnu"
          - host: windows-latest
            target: x86_64-pc-windows-msvc
            setup: "rustup set default-host x86_64-pc-windows-msvc"
            container-options: "--rm"
          - host: windows-latest
            target: aarch64-pc-windows-msvc
            setup: "rustup set default-host aarch64-pc-windows-msvc"
            container-options: "--rm"
    runs-on: ${{ matrix.settings.host }}
    timeout-minutes: 30
    container:
      image: ${{ matrix.settings.container }}
      options: ${{ matrix.settings.container-options }}
    steps:
      - name: Checkout repo
        uses: actions/checkout@v4
      - name: Setup Container
        if: ${{ matrix.settings.container-setup }}
        run: ${{ matrix.settings.container-setup }}

      - name: Setup capnproto
        uses: ./.github/actions/setup-capnproto

      - name: Rust Setup
        uses: ./.github/actions/setup-rust
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          targets: ${{ matrix.settings.target }}

      - name: Build Setup
        shell: bash
        if: ${{ matrix.settings.setup }}
        run: ${{ matrix.settings.setup }}

      - name: Build
        run: ${{ matrix.settings.rust-build-env }} cargo build --profile release-turborepo-lsp -p turborepo-lsp --target ${{ matrix.settings.target }}

      - name: Upload Artifacts
        uses: actions/upload-artifact@v4
        with:
          name: turborepo-lsp-${{ matrix.settings.target }}
          path: target/${{ matrix.settings.target }}/release-turborepo-lsp/turborepo-lsp*
pr-clean-caches perms .github/workflows/pr-clean-caches.yml
Triggers
pull_request, push, workflow_dispatch, workflow_call
Runs on
ubuntu-latest
Jobs
cleanup
Commands
  • gh extension install actions/gh-actions-cache REPO=${{ github.repository }} BRANCH=${{ github.ref }} echo "Fetching list of cache key" cacheKeysForPR=$(gh actions-cache list -R "$REPO" -B "$BRANCH" --limit 100 | cut -f 1) ## Setting this to not fail the workflow while deleting cache keys. set +e echo "Deleting caches..." for cacheKey in $cacheKeysForPR do gh actions-cache delete "$cacheKey" -R "$REPO" -B "$BRANCH" --confirm done echo "Done"
View raw YAML
name: Cleanup branch caches
on:
  pull_request:
    types: [opened, closed, reopened, synchronize]
  push:
  workflow_dispatch:
  workflow_call:

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

permissions:
  actions: write

jobs:
  cleanup:
    runs-on: ubuntu-latest
    timeout-minutes: 30
    if: ${{ github.ref != 'refs/heads/main' }}
    steps:
      - name: Cleanup
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          gh extension install actions/gh-actions-cache

          REPO=${{ github.repository }}
          BRANCH=${{ github.ref }}

          echo "Fetching list of cache key"
          cacheKeysForPR=$(gh actions-cache list -R "$REPO" -B "$BRANCH" --limit 100 | cut -f 1)

          ## Setting this to not fail the workflow while deleting cache keys.
          set +e
          echo "Deleting caches..."
          for cacheKey in $cacheKeysForPR
          do
              gh actions-cache delete "$cacheKey" -R "$REPO" -B "$BRANCH" --confirm
          done
          echo "Done"
test-js-packages matrix perms .github/workflows/test-js-packages.yml
Triggers
pull_request
Runs on
ubuntu-latest, ${{ matrix.os.runner }}, ubuntu-latest
Jobs
find-changes, js_packages, summary
Matrix
node-version, os, os.name, os.runner→ 18, 20, 22, 24, macos, macos-latest, ubuntu, ubuntu-latest
Actions
oven-sh/setup-bun
Commands
  • if [ "${{ github.event_name }}" == "pull_request" ]; then git fetch origin ${{ github.base_ref }} BASE_COMMIT="origin/${{ github.base_ref }}" HEAD_COMMIT="HEAD" else BASE_COMMIT="${{ github.event.before }}" HEAD_COMMIT="${{ github.event.after }}" fi JS_PATTERNS="^(package\.json|pnpm-workspace\.yaml|pnpm-lock\.yaml|version\.txt|packages/|\.github/actions/|\.github/workflows/test-js-packages\.yml)" CHANGED_FILES=$(git diff --name-only $BASE_COMMIT $HEAD_COMMIT) JS_CHANGES=$(echo "$CHANGED_FILES" | grep -E "$JS_PATTERNS" || true) if [ -n "$JS_CHANGES" ]; then echo "js-packages=true" >> $GITHUB_OUTPUT echo "JS package changes detected" else echo "js-packages=false" >> $GITHUB_OUTPUT echo "No JS package changes" fi
  • echo "depth=$(( ${{ github.event.pull_request.commits || 1 }} + 1 ))" >> $GITHUB_OUTPUT
  • TURBO_API= turbo run check-types test build package-checks --filter="!turborepo-repository" --filter={./packages/*}...[${{ github.event.pull_request.base.sha || 'HEAD^1' }}] --color --env-mode=strict
  • echo "Release PR detected - skipping tests (code already tested on main)"
  • echo "No JS package changes detected - skipping tests"
  • cancelled=false failure=false subjob () { local result=$1 if [ "$result" = "cancelled" ]; then cancelled=true elif [ "$result" != "success" ] && [ "$result" != "skipped" ]; then failure=true fi } subjob ${{needs.js_packages.result}} if [ "$cancelled" = "true" ]; then echo "Job was cancelled." exit 0 elif [ "$failure" = "true" ]; then echo "Job failed." exit 1 else echo "Job succeeded." exit 0 fi
View raw YAML
name: JS Package Tests
on:
  pull_request:

concurrency:
  group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
  cancel-in-progress: ${{ github.event_name == 'pull_request' }}

permissions:
  actions: write
  contents: read
  pull-requests: read

jobs:
  find-changes:
    name: Find path changes
    runs-on: ubuntu-latest
    outputs:
      is-release-pr: ${{ steps.check.outputs.is-release-pr }}
      js-packages: ${{ steps.filter.outputs.js-packages }}
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Check if automated release PR
        id: check
        uses: ./.github/actions/check-release-pr

      - name: Check path changes
        if: steps.check.outputs.is-release-pr != 'true'
        id: filter
        run: |
          if [ "${{ github.event_name }}" == "pull_request" ]; then
            git fetch origin ${{ github.base_ref }}
            BASE_COMMIT="origin/${{ github.base_ref }}"
            HEAD_COMMIT="HEAD"
          else
            BASE_COMMIT="${{ github.event.before }}"
            HEAD_COMMIT="${{ github.event.after }}"
          fi

          JS_PATTERNS="^(package\.json|pnpm-workspace\.yaml|pnpm-lock\.yaml|version\.txt|packages/|\.github/actions/|\.github/workflows/test-js-packages\.yml)"
          CHANGED_FILES=$(git diff --name-only $BASE_COMMIT $HEAD_COMMIT)
          JS_CHANGES=$(echo "$CHANGED_FILES" | grep -E "$JS_PATTERNS" || true)

          if [ -n "$JS_CHANGES" ]; then
            echo "js-packages=true" >> $GITHUB_OUTPUT
            echo "JS package changes detected"
          else
            echo "js-packages=false" >> $GITHUB_OUTPUT
            echo "No JS package changes"
          fi

  js_packages:
    name: "(${{matrix.os.name}}, Node ${{matrix.node-version}})"
    needs: find-changes
    if: needs.find-changes.outputs.is-release-pr != 'true' && needs.find-changes.outputs.js-packages == 'true'
    timeout-minutes: 30
    runs-on: ${{ matrix.os.runner }}
    strategy:
      fail-fast: false
      matrix:
        os:
          - name: ubuntu
            runner: ubuntu-latest
          - name: macos
            runner: macos-latest
        node-version:
          - 18
          - 20
          - 22
          - 24
    env:
      TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
      TURBO_TEAM: ${{ vars.TURBO_TEAM }}
      TURBO_CACHE: remote:rw

    steps:
      # on main -> current + prev commit
      # pr -> pr commits + base commit
      - name: Determine fetch depth
        id: fetch-depth
        run: |
          echo "depth=$(( ${{ github.event.pull_request.commits || 1 }} + 1 ))" >> $GITHUB_OUTPUT

      - name: Checkout
        uses: actions/checkout@v4
        with:
          ref: ${{ github.ref }}
          fetch-depth: ${{ steps.fetch-depth.outputs.depth  }}

      - name: "Setup Node"
        uses: ./.github/actions/setup-node
        with:
          node-version: ${{ matrix.node-version }}
        env:
          PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1

      - name: Install Bun
        uses: oven-sh/setup-bun@v2

      - name: Install Global Turbo
        uses: ./.github/actions/install-global-turbo

      - name: Run tests
        # We manually set TURBO_API to an empty string to override Hetzner env
        # We filter out turborepo-repository because it's a native package and needs
        # to run when turbo core changes. This job (`js_packages`) does not run on turborpeo core
        # changes, and we don't want to enable that beahvior for _all_ our JS packages.
        run: |
          TURBO_API= turbo run check-types test build package-checks --filter="!turborepo-repository" --filter={./packages/*}...[${{ github.event.pull_request.base.sha || 'HEAD^1' }}] --color --env-mode=strict
        env:
          NODE_VERSION: ${{ matrix.node-version }}

  summary:
    name: JS Test Summary
    runs-on: ubuntu-latest
    timeout-minutes: 30
    if: always()
    needs:
      - find-changes
      - js_packages
    steps:
      - name: Skip for release PR
        if: needs.find-changes.outputs.is-release-pr == 'true'
        run: echo "Release PR detected - skipping tests (code already tested on main)"

      - name: Skip - no JS changes
        if: needs.find-changes.outputs.is-release-pr != 'true' && needs.find-changes.outputs.js-packages != 'true'
        run: echo "No JS package changes detected - skipping tests"

      - name: Compute info
        if: needs.find-changes.outputs.is-release-pr != 'true' && needs.find-changes.outputs.js-packages == 'true'
        run: |
          cancelled=false
          failure=false

          subjob () {
            local result=$1
            if [ "$result" = "cancelled" ]; then
              cancelled=true
            elif [ "$result" != "success" ] && [ "$result" != "skipped" ]; then
              failure=true
            fi
          }

          subjob ${{needs.js_packages.result}}

          if [ "$cancelled" = "true" ]; then
            echo "Job was cancelled."
            exit 0
          elif [ "$failure" = "true" ]; then
            echo "Job failed."
            exit 1
          else
            echo "Job succeeded."
            exit 0
          fi
turborepo-compare-cache-item matrix .github/workflows/turborepo-compare-cache-item.yml
Triggers
workflow_dispatch
Runs on
${{ matrix.os }}, ${{ matrix.os }}
Jobs
generate_cache_artifact, use_cache_artifact
Matrix
cache_os, os→ macos-latest, ubuntu-latest, windows-latest
Commands
  • npm install -g pnpm turbo@${{ inputs.version }} pnpm dlx create-turbo@${{ inputs.version }} my-turborepo pnpm
  • cd my-turborepo turbo run build --filter=docs --filter=web --summarize --skip-infer -vvv
  • npm install -g pnpm turbo@${{ inputs.version }} pnpm dlx create-turbo@${{ inputs.version }} my-turborepo pnpm
  • cd my-turborepo rm .turbo/runs/*.json turbo run build --filter=docs --filter=web --summarize --skip-infer -vvv cat .turbo/runs/*.json | jq -e '.execution.cached == 2'
  • curl https://raw.githubusercontent.com/vercel/turbo/main/scripts/server.js -O node server.js my-turborepo/apps/docs
View raw YAML
name: Compare Cache Item

on:
  workflow_dispatch:
    inputs:
      version:
        description: Turborepo release to test.
        type: string
        default: "canary"

jobs:
  generate_cache_artifact:
    strategy:
      matrix:
        os: [macos-latest, ubuntu-latest, windows-latest]
    runs-on: ${{ matrix.os }}
    timeout-minutes: 30

    steps:
      - name: Setup Node
        uses: ./.github/actions/setup-node

      - name: create-turbo
        run: |
          npm install -g pnpm turbo@${{ inputs.version }}
          pnpm dlx create-turbo@${{ inputs.version }} my-turborepo pnpm

      - name: Run build
        run: |
          cd my-turborepo
          turbo run build --filter=docs --filter=web --summarize --skip-infer -vvv

      - name: Grab artifacts
        uses: actions/upload-artifact@v4
        with:
          name: cache-item-${{ matrix.os }}-${{ inputs.version }}
          path: |
            my-turborepo/node_modules/.cache/turbo
            my-turborepo/.turbo/runs
          retention-days: 1

  use_cache_artifact:
    needs: generate_cache_artifact
    strategy:
      fail-fast: false
      matrix:
        os: [macos-latest, ubuntu-latest, windows-latest]
        cache_os: [macos-latest, ubuntu-latest, windows-latest]
    runs-on: ${{ matrix.os }}
    timeout-minutes: 30

    steps:
      - name: Setup Node
        uses: ./.github/actions/setup-node

      - name: create-turbo
        run: |
          npm install -g pnpm turbo@${{ inputs.version }}
          pnpm dlx create-turbo@${{ inputs.version }} my-turborepo pnpm

      - name: Download cache artifacts
        uses: actions/download-artifact@v4
        with:
          name: cache-item-${{ matrix.cache_os }}-${{ inputs.version }}
          path: my-turborepo

      - name: Check for cache hit
        run: |
          cd my-turborepo
          rm .turbo/runs/*.json
          turbo run build --filter=docs --filter=web --summarize --skip-infer -vvv
          cat .turbo/runs/*.json | jq -e '.execution.cached == 2'

      - name: Check for functional server
        run: |
          curl https://raw.githubusercontent.com/vercel/turbo/main/scripts/server.js -O
          node server.js my-turborepo/apps/docs
turborepo-library-release matrix perms .github/workflows/turborepo-library-release.yml
Triggers
workflow_dispatch
Runs on
${{ matrix.settings.host }}, ubuntu-latest, ubuntu-latest, ubuntu-latest
Jobs
build, package, create-release-pr, cleanup-on-failure
Matrix
settings, settings.container, settings.host, settings.install, settings.rust_env, settings.setup, settings.target→ CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER=/aarch64-linux-musl-cross/bin/aarch64-linux-musl-gcc RUSTFLAGS="-Ctarget-feature=-crt-static", aarch64-apple-darwin, aarch64-pc-windows-msvc, aarch64-unknown-linux-gnu, aarch64-unknown-linux-musl, amazon/aws-lambda-nodejs:20, apk update && apk upgrade apk add libc6-compat curl echo /root/.cargo/bin >> ${GITHUB_PATH} echo /usr/local/cargo/bin/rustup >> ${GITHUB_PATH} , apk update && apk upgrade apk add libc6-compat curl echo /root/.cargo/bin >> ${GITHUB_PATH} echo /usr/local/cargo/bin/rustup >> ${GITHUB_PATH} echo /aarch64-linux-musl-cross/bin >> ${GITHUB_PATH} , export PATH=/aarch64-linux-musl-cross/bin:/usr/local/cargo/bin/rustup:/root/.cargo/bin:${PATH} rustup show active-toolchain rustup target add aarch64-unknown-linux-musl dirname $(rustup which cargo) >> ${GITHUB_PATH} pnpm install , export PATH=/usr/local/cargo/bin/rustup:/root/.cargo/bin:${PATH} rustup show active-toolchain dirname $(rustup which cargo) >> ${GITHUB_PATH} pnpm install , ghcr.io/napi-rs/napi-rs/nodejs-rust:stable-2023-09-17-alpine, macos-latest, microdnf install -y gcc gcc-c++ git tar xz make curl https://sh.rustup.rs -sSf | bash -s -- -y npm i -g pnpm@10.28.0 mkdir ../zig curl --show-error --location https://ziglang.org/download/0.14.0/zig-linux-x86_64-0.14.0.tar.xz | tar -J -xf - -C ../zig --strip-components 1 export PATH=$PATH:$(pwd)/../zig echo "$(pwd)/../zig" >> $GITHUB_PATH , pnpm install , sudo apt update sudo apt install -y g++-aarch64-linux-gnu libc6-dev-arm64-cross xz-utils mkdir zig curl --show-error --location https://ziglang.org/download/0.14.0/zig-linux-x86_64-0.14.0.tar.xz | tar -J -xf - -C zig --strip-components 1 export PATH=$PATH:$(pwd)/zig echo "$(pwd)/zig" >> $GITHUB_PATH , ubuntu-latest, windows-latest, x86_64-apple-darwin, x86_64-pc-windows-msvc, x86_64-unknown-linux-gnu, x86_64-unknown-linux-musl
Commands
  • ${{ matrix.settings.install }}
  • ${{ matrix.settings.setup }}
  • export PATH=/usr/local/cargo/bin/rustup:/root/.cargo/bin:${PATH} cd packages/turbo-repository ${{ matrix.settings.rust_env }} pnpm build:release --target=${{ matrix.settings.target }}
  • npm install -g npm@11.5.1 npm config set registry https://registry.npmjs.org
  • git config --global user.name 'Turbobot' git config --global user.email 'turbobot@vercel.com'
  • CURRENT_VERSION=$(jq -r .version packages/turbo-repository/js/package.json) NEXT_VERSION=$(cd packages/turbo-repository/js && npm version prerelease --preid canary --no-git-tag-version) NEXT_VERSION="${NEXT_VERSION#v}" echo "Bumping all packages from ${CURRENT_VERSION} to ${NEXT_VERSION}" cd packages/turbo-repository && bash scripts/bump-version.sh "${NEXT_VERSION}" cd "$GITHUB_WORKSPACE" BRANCH="library-release/${NEXT_VERSION}" git checkout -b "${BRANCH}" git add -A git commit -m "library release: ${NEXT_VERSION}" git push origin "${BRANCH}" echo "version=${NEXT_VERSION}" >> $GITHUB_OUTPUT echo "branch=${BRANCH}" >> $GITHUB_OUTPUT
  • mv native-packages/turbo-library-aarch64-apple-darwin/@turbo/repository.darwin-arm64.node packages/turbo-repository/npm/darwin-arm64/ mv native-packages/turbo-library-x86_64-apple-darwin/@turbo/repository.darwin-x64.node packages/turbo-repository/npm/darwin-x64/ mv native-packages/turbo-library-aarch64-unknown-linux-gnu/@turbo/repository.linux-arm64-gnu.node packages/turbo-repository/npm/linux-arm64-gnu/ mv native-packages/turbo-library-aarch64-unknown-linux-musl/@turbo/repository.linux-arm64-musl.node packages/turbo-repository/npm/linux-arm64-musl/ mv native-packages/turbo-library-x86_64-unknown-linux-gnu/@turbo/repository.linux-x64-gnu.node packages/turbo-repository/npm/linux-x64-gnu/ mv native-packages/turbo-library-x86_64-unknown-linux-musl/@turbo/repository.linux-x64-musl.node packages/turbo-repository/npm/linux-x64-musl/ mv native-packages/turbo-library-aarch64-pc-windows-msvc/@turbo/repository.win32-arm64-msvc.node packages/turbo-repository/npm/win32-arm64-msvc/ mv native-packages/turbo-library-x86_64-pc-windows-msvc/@turbo/repository.win32-x64-msvc.node packages/turbo-repository/npm/win32-x64-msvc/
  • cd packages/turbo-repository/js npm run build
View raw YAML
name: Library Release

on:
  workflow_dispatch:
    inputs:
      dry_run:
        description: Do a dry run, skipping the final publish step.
        type: boolean

permissions:
  id-token: write # Required for npm Trusted Publishing using OIDC
  contents: write
  pull-requests: write

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

    strategy:
      fail-fast: false
      matrix:
        settings:
          - host: macos-latest
            target: aarch64-apple-darwin

          - host: macos-latest
            target: x86_64-apple-darwin

          - host: ubuntu-latest
            target: aarch64-unknown-linux-gnu
            setup: |
              sudo apt update
              sudo apt install -y g++-aarch64-linux-gnu libc6-dev-arm64-cross xz-utils
              mkdir zig
              curl --show-error --location https://ziglang.org/download/0.14.0/zig-linux-x86_64-0.14.0.tar.xz | tar -J -xf - -C zig --strip-components 1
              export PATH=$PATH:$(pwd)/zig
              echo "$(pwd)/zig" >> $GITHUB_PATH

          - host: ubuntu-latest
            target: x86_64-unknown-linux-gnu
            container: amazon/aws-lambda-nodejs:20
            install: |
              microdnf install -y gcc gcc-c++ git tar xz make
              curl https://sh.rustup.rs -sSf | bash -s -- -y
              npm i -g pnpm@10.28.0
              mkdir ../zig
              curl --show-error --location https://ziglang.org/download/0.14.0/zig-linux-x86_64-0.14.0.tar.xz | tar -J -xf - -C ../zig --strip-components 1
              export PATH=$PATH:$(pwd)/../zig
              echo "$(pwd)/../zig" >> $GITHUB_PATH
            setup: |
              pnpm install

          - host: ubuntu-latest
            target: x86_64-unknown-linux-musl
            container: ghcr.io/napi-rs/napi-rs/nodejs-rust:stable-2023-09-17-alpine
            install: |
              apk update && apk upgrade
              apk add libc6-compat curl
              echo /root/.cargo/bin >> ${GITHUB_PATH}
              echo /usr/local/cargo/bin/rustup >> ${GITHUB_PATH}
            setup: |
              export PATH=/usr/local/cargo/bin/rustup:/root/.cargo/bin:${PATH}
              rustup show active-toolchain
              dirname $(rustup which cargo) >> ${GITHUB_PATH}
              pnpm install

          - host: ubuntu-latest
            target: aarch64-unknown-linux-musl
            container: ghcr.io/napi-rs/napi-rs/nodejs-rust:stable-2023-09-17-alpine
            install: |
              apk update && apk upgrade
              apk add libc6-compat curl
              echo /root/.cargo/bin >> ${GITHUB_PATH}
              echo /usr/local/cargo/bin/rustup >> ${GITHUB_PATH}
              echo /aarch64-linux-musl-cross/bin >> ${GITHUB_PATH}
            setup: |
              export PATH=/aarch64-linux-musl-cross/bin:/usr/local/cargo/bin/rustup:/root/.cargo/bin:${PATH}
              rustup show active-toolchain
              rustup target add aarch64-unknown-linux-musl
              dirname $(rustup which cargo) >> ${GITHUB_PATH}
              pnpm install
            rust_env: CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER=/aarch64-linux-musl-cross/bin/aarch64-linux-musl-gcc RUSTFLAGS="-Ctarget-feature=-crt-static"

          - host: windows-latest
            target: aarch64-pc-windows-msvc

          - host: windows-latest
            target: x86_64-pc-windows-msvc

    runs-on: ${{ matrix.settings.host }}
    timeout-minutes: 30
    container:
      image: ${{ matrix.settings.container }}
    steps:
      - name: Install Packages
        run: ${{ matrix.settings.install }}
        if: ${{ matrix.settings.install }}

      - name: Checkout
        uses: actions/checkout@v3
        with:
          fetch-depth: 0

      - name: Setup Rust
        uses: ./.github/actions/setup-rust
        with:
          targets: ${{ matrix.settings.target }}
          github-token: ${{ github.token }}
        if: ${{ !matrix.settings.install }}

      - name: Setup Node
        uses: ./.github/actions/setup-node
        with:
          enable-corepack: false
        if: ${{ !matrix.settings.install }}

      - name: Setup capnproto
        uses: ./.github/actions/setup-capnproto
        with:
          use-cache: ${{ !matrix.settings.install }}

      - name: Setup toolchain
        run: ${{ matrix.settings.setup }}
        if: ${{ matrix.settings.setup }}

      - name: Build native library
        run: |
          export PATH=/usr/local/cargo/bin/rustup:/root/.cargo/bin:${PATH}
          cd packages/turbo-repository
          ${{ matrix.settings.rust_env }} pnpm build:release --target=${{ matrix.settings.target }}

      - name: Upload Artifacts
        uses: actions/upload-artifact@v4
        with:
          name: turbo-library-${{ matrix.settings.target }}
          path: packages/turbo-repository/native

  package:
    name: Publish to NPM
    runs-on: ubuntu-latest
    timeout-minutes: 30
    needs: [build]
    outputs:
      version: ${{ steps.version.outputs.version }}
      branch: ${{ steps.version.outputs.branch }}
    steps:
      - name: Checkout
        uses: actions/checkout@v3
        with:
          fetch-depth: 0

      - name: Setup Node
        uses: ./.github/actions/setup-node
        with:
          enable-corepack: false
          package-install: false

      - name: Configure npm
        run: |
          npm install -g npm@11.5.1
          npm config set registry https://registry.npmjs.org

      - name: Configure git
        run: |
          git config --global user.name 'Turbobot'
          git config --global user.email 'turbobot@vercel.com'

      - name: Bump version
        id: version
        run: |
          CURRENT_VERSION=$(jq -r .version packages/turbo-repository/js/package.json)
          NEXT_VERSION=$(cd packages/turbo-repository/js && npm version prerelease --preid canary --no-git-tag-version)
          NEXT_VERSION="${NEXT_VERSION#v}"
          echo "Bumping all packages from ${CURRENT_VERSION} to ${NEXT_VERSION}"
          cd packages/turbo-repository && bash scripts/bump-version.sh "${NEXT_VERSION}"
          cd "$GITHUB_WORKSPACE"
          BRANCH="library-release/${NEXT_VERSION}"
          git checkout -b "${BRANCH}"
          git add -A
          git commit -m "library release: ${NEXT_VERSION}"
          git push origin "${BRANCH}"
          echo "version=${NEXT_VERSION}" >> $GITHUB_OUTPUT
          echo "branch=${BRANCH}" >> $GITHUB_OUTPUT

      - name: Download Artifacts
        uses: actions/download-artifact@v4
        with:
          path: native-packages

      - name: Move artifacts into place
        run: |
          mv native-packages/turbo-library-aarch64-apple-darwin/@turbo/repository.darwin-arm64.node packages/turbo-repository/npm/darwin-arm64/
          mv native-packages/turbo-library-x86_64-apple-darwin/@turbo/repository.darwin-x64.node packages/turbo-repository/npm/darwin-x64/
          mv native-packages/turbo-library-aarch64-unknown-linux-gnu/@turbo/repository.linux-arm64-gnu.node packages/turbo-repository/npm/linux-arm64-gnu/
          mv native-packages/turbo-library-aarch64-unknown-linux-musl/@turbo/repository.linux-arm64-musl.node packages/turbo-repository/npm/linux-arm64-musl/
          mv native-packages/turbo-library-x86_64-unknown-linux-gnu/@turbo/repository.linux-x64-gnu.node packages/turbo-repository/npm/linux-x64-gnu/
          mv native-packages/turbo-library-x86_64-unknown-linux-musl/@turbo/repository.linux-x64-musl.node packages/turbo-repository/npm/linux-x64-musl/
          mv native-packages/turbo-library-aarch64-pc-windows-msvc/@turbo/repository.win32-arm64-msvc.node packages/turbo-repository/npm/win32-arm64-msvc/
          mv native-packages/turbo-library-x86_64-pc-windows-msvc/@turbo/repository.win32-x64-msvc.node packages/turbo-repository/npm/win32-x64-msvc/

      - name: Build Meta Package
        run: |
          cd packages/turbo-repository/js
          npm run build

      - name: Package Artifacts
        run: |
          mkdir tarballs
          npm pack packages/turbo-repository/npm/darwin-arm64
          npm pack packages/turbo-repository/npm/darwin-x64
          npm pack packages/turbo-repository/npm/linux-arm64-gnu
          npm pack packages/turbo-repository/npm/linux-arm64-musl
          npm pack packages/turbo-repository/npm/linux-x64-gnu
          npm pack packages/turbo-repository/npm/linux-x64-musl
          npm pack packages/turbo-repository/npm/win32-arm64-msvc
          npm pack packages/turbo-repository/npm/win32-x64-msvc
          npm pack packages/turbo-repository/js
          mv *.tgz tarballs/

      - name: Upload Artifacts
        uses: actions/upload-artifact@v4
        with:
          name: Upload Tarballs
          path: tarballs

      - name: Publish Artifacts
        if: ${{ !inputs.dry_run }}
        run: |
          VERSION=$(jq -r .version packages/turbo-repository/js/package.json)
          cd tarballs
          ls
          TAG="canary"
          npm publish -ddd --tag ${TAG} --access public turbo-repository-darwin-arm64-${VERSION}.tgz
          npm publish -ddd --tag ${TAG} --access public turbo-repository-darwin-x64-${VERSION}.tgz
          npm publish -ddd --tag ${TAG} --access public turbo-repository-linux-arm64-gnu-${VERSION}.tgz
          npm publish -ddd --tag ${TAG} --access public turbo-repository-linux-arm64-musl-${VERSION}.tgz
          npm publish -ddd --tag ${TAG} --access public turbo-repository-linux-x64-gnu-${VERSION}.tgz
          npm publish -ddd --tag ${TAG} --access public turbo-repository-linux-x64-musl-${VERSION}.tgz
          npm publish -ddd --tag ${TAG} --access public turbo-repository-win32-arm64-msvc-${VERSION}.tgz
          npm publish -ddd --tag ${TAG} --access public turbo-repository-win32-x64-msvc-${VERSION}.tgz
          npm publish -ddd --tag ${TAG} --access public turbo-repository-${VERSION}.tgz

  create-release-pr:
    name: Create Release PR
    runs-on: ubuntu-latest
    timeout-minutes: 10
    needs: [package]
    if: ${{ !inputs.dry_run }}
    steps:
      - name: Create PR
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          VERSION="${{ needs.package.outputs.version }}"
          BRANCH="${{ needs.package.outputs.branch }}"
          gh pr create \
            --repo "${{ github.repository }}" \
            --title "release(library): ${VERSION}" \
            --body "Bumps \`@turbo/repository\` packages to \`${VERSION}\`." \
            --head "${BRANCH}" \
            --base main
          PR_NUM=$(gh pr view "${BRANCH}" --repo "${{ github.repository }}" --json number --jq '.number')
          gh pr merge "$PR_NUM" --repo "${{ github.repository }}" --auto --squash

  cleanup-on-failure:
    name: Cleanup Failed Release
    runs-on: ubuntu-latest
    timeout-minutes: 10
    needs: [package]
    if: ${{ always() && needs.package.result == 'failure' }}
    steps:
      - name: Delete staging branch
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          BRANCH="${{ needs.package.outputs.branch }}"
          if [ -n "${BRANCH}" ]; then
            echo "Cleaning up branch ${BRANCH}..."
            gh api -X DELETE "repos/${{ github.repository }}/git/refs/heads/${BRANCH}" || echo "Branch may already be deleted"
          fi
turborepo-release matrix perms .github/workflows/turborepo-release.yml
Triggers
schedule, workflow_dispatch
Runs on
ubuntu-latest, ubuntu-latest, ubuntu-latest, ${{ matrix.settings.host }}, ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest
Jobs
check-skip, stage, js-smoke-test, build-rust, security-scan, npm-publish, create-release-tag, alias-versioned-docs, update-examples, create-release-pr, cleanup-on-failure
Matrix
settings, settings.host, settings.rust-build-env, settings.setup, settings.target→ CC_aarch64_unknown_linux_musl=clang AR_aarch64_unknown_linux_musl=llvm-ar RUSTFLAGS="-Clink-self-contained=yes -Clinker=rust-lld", aarch64-apple-darwin, aarch64-unknown-linux-musl, macos-latest, sudo apt-get update && sudo apt-get install -y build-essential clang lldb llvm libclang-dev curl musl-tools sudo unzip, sudo apt-get update && sudo apt-get install -y build-essential musl-tools clang llvm gcc-aarch64-linux-gnu binutils-aarch64-linux-gnu, ubuntu-latest, windows-latest, x86_64-apple-darwin, x86_64-pc-windows-msvc, x86_64-unknown-linux-musl
Actions
oven-sh/setup-bun, actions-rust-lang/setup-rust-toolchain, slackapi/slack-github-action
Commands
  • # Find the commit that last updated version.txt (the release PR merge). # If no relevant files changed since then, there's nothing new to release. # Uses the GitHub API instead of a full clone to avoid fetching entire repo history. LAST_VERSION_COMMIT=$(gh api "repos/${{ github.repository }}/commits?path=version.txt&per_page=1" --jq '.[0].sha') CHANGES=$(gh api "repos/${{ github.repository }}/compare/${LAST_VERSION_COMMIT}...${{ github.sha }}" \ --jq '[.files[].filename | select(startswith("crates/") or startswith("packages/") or startswith("cli/"))] | length') if [ "$CHANGES" = "0" ]; then echo "Skipping: No relevant changes since last release (${LAST_VERSION_COMMIT:0:12})" echo "should_skip=true" >> $GITHUB_OUTPUT else echo "should_skip=false" >> $GITHUB_OUTPUT fi
  • git config --global user.name 'Turbobot' git config --global user.email 'turbobot@vercel.com'
  • echo "sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
  • # For stable releases, compare against the previous stable tag so # release notes include everything since the last stable release. # For canary releases, compare against the latest canary tag. if [[ "$INCREMENT" == "patch" || "$INCREMENT" == "minor" || "$INCREMENT" == "major" ]]; then PREV_TAG=$(git ls-remote --tags origin 'refs/tags/v*' \ | awk '{print $2}' | sed 's|refs/tags/||' | grep -v '\^{}$' \ | grep -v canary | sort -V | tail -n 1) else PREV_TAG=$(git ls-remote --tags origin 'refs/tags/v*-canary.*' \ | awk '{print $2}' | sed 's|refs/tags/||' | grep -v '\^{}$' \ | sort -V | tail -n 1) if [ -z "$PREV_TAG" ]; then PREV_TAG=$(git ls-remote --tags origin 'refs/tags/v*' \ | awk '{print $2}' | sed 's|refs/tags/||' | grep -v '\^{}$' \ | grep -v canary | sort -V | tail -n 1) fi fi echo "tag=$PREV_TAG" >> $GITHUB_OUTPUT echo "Previous tag: $PREV_TAG"
  • echo "::warning::clear-staging-branch was enabled. This should only be used to recover from a failed release." echo "" # Calculate what version we're about to release so we know which staging branch to delete ./scripts/version.js "$INCREMENT" "$TAG_OVERRIDE" VERSION=$(head -n 1 version.txt) echo "Checking for stale staging branch: staging-${VERSION}" if git ls-remote --exit-code --heads origin "staging-${VERSION}" >/dev/null 2>&1; then echo "::warning::Deleting staging branch staging-${VERSION}..." git push origin --delete "staging-${VERSION}" echo "Deleted staging branch staging-${VERSION}" else echo "No staging branch found for staging-${VERSION}" fi # Reset version.txt so the Version step can run cleanly git checkout version.txt
  • if [[ -n "$TAG_OVERRIDE" && ! "$TAG_OVERRIDE" =~ ^[a-zA-Z0-9-]+$ ]]; then echo "::error::Invalid tag-override format. Must be alphanumeric with hyphens only." exit 1 fi ./scripts/version.js "$INCREMENT" "$TAG_OVERRIDE" VERSION=$(head -n 1 version.txt) if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$ ]]; then echo "::error::Invalid version format produced: $VERSION" exit 1 fi # If this is a canary, check whether its stable version was already # published to npm. This catches the window between a stable publish # and the release PR merging back into main (which bumps version.txt). if [[ "$VERSION" == *"-canary."* ]]; then STABLE_VERSION="${VERSION%%-canary.*}" if npm view "turbo@${STABLE_VERSION}" version >/dev/null 2>&1; then echo "::error::turbo@${STABLE_VERSION} already exists on npm. Skipping stale canary ${VERSION}." echo "::error::The release PR that bumps version.txt likely hasn't merged yet." exit 1 fi fi echo "version=$VERSION" >> $GITHUB_OUTPUT echo "New version: $VERSION" cat version.txt
  • cd cli && make stage-release echo "stage-branch=$(git branch --show-current)" >> $GITHUB_OUTPUT
  • turbo run check-types test --filter="./packages/*" --filter="!turborepo-repository" --color
View raw YAML
# Release Pipeline
#
# This release consists of a few steps
#
# 1. Create a staging branch (acts as a lock to prevent concurrent releases)
# 2. Run some smoke tests on that branch
# 3. Build the Rust binary
# 4. Run security audits (cargo audit + pnpm audit)
# 5. Publish JS packages to npm (including turbo itself)
# 6. Create the git tag (only after npm publish succeeds)
# 7. Alias versioned docs (e.g., v2-5-4.turborepo.dev)
# 8. Create a release branch and open a PR
# 9. On failure, cleanup the staging branch and release tag automatically
#
# Canary releases run on an hourly schedule.
# Manual releases are triggered via workflow_dispatch.
#
# RECOVERY: If a release fails and cleanup doesn't work, use the
# 'clear-staging-branch' input to manually clear the stale staging branch.

name: Release

env:
  CARGO_PROFILE_RELEASE_LTO: true
  HUSKY: "0"
  NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
  RELEASE_TURBO_CLI: true # TODO: do we need this?

permissions:
  id-token: write # Required for npm Trusted Publishing using OIDC
  contents: write # Allow workflow to checkout code from the repository
  pull-requests: write # Allows the PR for post-release to be created
  checks: write # Allows posting check statuses for release PRs

on:
  schedule:
    - cron: "0 * * * *"
  workflow_dispatch:
    inputs:
      increment:
        description: "SemVer Increment (prerelease = bump canary)"
        required: true
        default: "prerelease"
        type: choice
        options:
          # Bump the canary version of the existing semver release
          - prerelease
          # Bump to the next patch version, creating its first canary release
          - prepatch
          # Bump to the next minor version, creating its first canary release
          - preminor
          # Bump to the next major version, creating its first canary release
          - premajor
          # Bump to the next patch version
          - patch
          # Bump to the next minor version
          - minor
          # Bump to the next major version
          - major
      dry_run:
        description: "Do a dry run, skipping the final publish step."
        type: boolean
      tag-override:
        description: "Override default npm dist-tag for the release. Should only be used for backporting"
        required: false
        type: string
      ci-tag-override:
        description: "Override default npm dist-tag to use for running tests. Should only be used when the most recent release was faulty"
        required: false
        type: string
        default: ""
      sha:
        description: "Override the SHA to use for the release. Should rarely be used, usually only for debugging."
        required: false
        type: string
        default: ""
      clear-staging-branch:
        # ┌─────────────────────────────────────────────────────────────────────────────┐
        # │ ⚠️  DANGER ZONE - READ CAREFULLY BEFORE USING                               │
        # ├─────────────────────────────────────────────────────────────────────────────┤
        # │                                                                             │
        # │ This option deletes the staging branch for the version being released,     │
        # │ allowing the release to proceed when a previous release attempt failed.    │
        # │                                                                             │
        # │ ❌ DO NOT USE IF:                                                           │
        # │   • A release workflow is currently running (check the Actions tab!)       │
        # │   • You're unsure why the staging branch exists                            │
        # │   • The npm package for this version was already published                 │
        # │                                                                             │
        # │ ✅ USE ONLY IF:                                                             │
        # │   • A previous release workflow failed or was cancelled                    │
        # │   • No release workflow is currently running for this version              │
        # │   • You've verified the npm package was NOT published (check npm)          │
        # │                                                                             │
        # │ HOW TO VERIFY IT'S SAFE:                                                   │
        # │   1. Check Actions tab - no running release workflows                      │
        # │   2. Run: npm view turbo@<version> - should return "not found"             │
        # │   3. Check git tags: git ls-remote --tags origin | grep <version>          │
        # │      - If tag exists, version was released successfully                    │
        # │                                                                             │
        # └─────────────────────────────────────────────────────────────────────────────┘
        description: "⚠️ DANGER: Delete stale staging branch from a failed release. Only use if previous release failed AND no release is in progress. See workflow file for details."
        type: boolean
        default: false

concurrency:
  group: turborepo-release
  cancel-in-progress: false

jobs:
  check-skip:
    name: "Check Skip Conditions"
    runs-on: ubuntu-latest
    if: ${{ github.event_name == 'schedule' }}
    outputs:
      should_skip: ${{ steps.check.outputs.should_skip }}
    steps:
      - name: Check if should skip
        id: check
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          # Find the commit that last updated version.txt (the release PR merge).
          # If no relevant files changed since then, there's nothing new to release.
          # Uses the GitHub API instead of a full clone to avoid fetching entire repo history.
          LAST_VERSION_COMMIT=$(gh api "repos/${{ github.repository }}/commits?path=version.txt&per_page=1" --jq '.[0].sha')
          CHANGES=$(gh api "repos/${{ github.repository }}/compare/${LAST_VERSION_COMMIT}...${{ github.sha }}" \
            --jq '[.files[].filename | select(startswith("crates/") or startswith("packages/") or startswith("cli/"))] | length')

          if [ "$CHANGES" = "0" ]; then
            echo "Skipping: No relevant changes since last release (${LAST_VERSION_COMMIT:0:12})"
            echo "should_skip=true" >> $GITHUB_OUTPUT
          else
            echo "should_skip=false" >> $GITHUB_OUTPUT
          fi

  stage:
    needs: [check-skip]
    if: ${{ always() && (github.event_name == 'workflow_dispatch' || needs.check-skip.outputs.should_skip != 'true') }}
    runs-on: ubuntu-latest
    timeout-minutes: 30
    outputs:
      stage-branch: ${{ steps.stage.outputs.stage-branch }}
      base-sha: ${{ steps.base-sha.outputs.sha }}
      version: ${{ steps.version.outputs.version }}
      previous-tag: ${{ steps.previous-tag.outputs.tag }}
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ inputs.sha || github.sha }}
          filter: blob:none

      - uses: ./.github/actions/setup-node
        with:
          enable-corepack: false

      - name: Configure git
        run: |
          git config --global user.name 'Turbobot'
          git config --global user.email 'turbobot@vercel.com'

      - name: Get base SHA
        id: base-sha
        run: echo "sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT

      - name: Get previous tag
        id: previous-tag
        env:
          INCREMENT: ${{ github.event_name == 'schedule' && 'prerelease' || inputs.increment }}
        run: |
          # For stable releases, compare against the previous stable tag so
          # release notes include everything since the last stable release.
          # For canary releases, compare against the latest canary tag.
          if [[ "$INCREMENT" == "patch" || "$INCREMENT" == "minor" || "$INCREMENT" == "major" ]]; then
            PREV_TAG=$(git ls-remote --tags origin 'refs/tags/v*' \
              | awk '{print $2}' | sed 's|refs/tags/||' | grep -v '\^{}$' \
              | grep -v canary | sort -V | tail -n 1)
          else
            PREV_TAG=$(git ls-remote --tags origin 'refs/tags/v*-canary.*' \
              | awk '{print $2}' | sed 's|refs/tags/||' | grep -v '\^{}$' \
              | sort -V | tail -n 1)
            if [ -z "$PREV_TAG" ]; then
              PREV_TAG=$(git ls-remote --tags origin 'refs/tags/v*' \
                | awk '{print $2}' | sed 's|refs/tags/||' | grep -v '\^{}$' \
                | grep -v canary | sort -V | tail -n 1)
            fi
          fi
          echo "tag=$PREV_TAG" >> $GITHUB_OUTPUT
          echo "Previous tag: $PREV_TAG"

      - name: Clear stale staging branch (if requested)
        if: ${{ inputs.clear-staging-branch }}
        env:
          INCREMENT: ${{ github.event_name == 'schedule' && 'prerelease' || inputs.increment }}
          TAG_OVERRIDE: ${{ inputs.tag-override }}
        run: |
          echo "::warning::clear-staging-branch was enabled. This should only be used to recover from a failed release."
          echo ""

          # Calculate what version we're about to release so we know which staging branch to delete
          ./scripts/version.js "$INCREMENT" "$TAG_OVERRIDE"
          VERSION=$(head -n 1 version.txt)

          echo "Checking for stale staging branch: staging-${VERSION}"

          if git ls-remote --exit-code --heads origin "staging-${VERSION}" >/dev/null 2>&1; then
            echo "::warning::Deleting staging branch staging-${VERSION}..."
            git push origin --delete "staging-${VERSION}"
            echo "Deleted staging branch staging-${VERSION}"
          else
            echo "No staging branch found for staging-${VERSION}"
          fi

          # Reset version.txt so the Version step can run cleanly
          git checkout version.txt

      - name: Version
        id: version
        env:
          # For scheduled runs (canary), always use prerelease. For workflow_dispatch, use the input.
          INCREMENT: ${{ github.event_name == 'schedule' && 'prerelease' || inputs.increment }}
          TAG_OVERRIDE: ${{ inputs.tag-override }}
        run: |
          if [[ -n "$TAG_OVERRIDE" && ! "$TAG_OVERRIDE" =~ ^[a-zA-Z0-9-]+$ ]]; then
            echo "::error::Invalid tag-override format. Must be alphanumeric with hyphens only."
            exit 1
          fi

          ./scripts/version.js "$INCREMENT" "$TAG_OVERRIDE"
          VERSION=$(head -n 1 version.txt)

          if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$ ]]; then
            echo "::error::Invalid version format produced: $VERSION"
            exit 1
          fi

          # If this is a canary, check whether its stable version was already
          # published to npm. This catches the window between a stable publish
          # and the release PR merging back into main (which bumps version.txt).
          if [[ "$VERSION" == *"-canary."* ]]; then
            STABLE_VERSION="${VERSION%%-canary.*}"
            if npm view "turbo@${STABLE_VERSION}" version >/dev/null 2>&1; then
              echo "::error::turbo@${STABLE_VERSION} already exists on npm. Skipping stale canary ${VERSION}."
              echo "::error::The release PR that bumps version.txt likely hasn't merged yet."
              exit 1
            fi
          fi

          echo "version=$VERSION" >> $GITHUB_OUTPUT
          echo "New version: $VERSION"
          cat version.txt

      - name: Stage Commit
        id: stage
        run: |
          cd cli && make stage-release
          echo "stage-branch=$(git branch --show-current)" >> $GITHUB_OUTPUT

  # TODO: Re-enable Rust unit tests once flakiness is resolved.
  # rust-smoke-test:
  #   name: Rust Unit Tests
  #   runs-on: ubuntu-latest
  #   timeout-minutes: 30
  #   needs: [stage]
  #   if: ${{ always() && needs.stage.result == 'success' }}
  #   steps:
  #     - uses: actions/checkout@v4
  #       with:
  #         ref: ${{ needs.stage.outputs.stage-branch }}
  #
  #     - name: Setup Environment
  #       uses: ./.github/actions/setup-environment
  #       with:
  #         github-token: ${{ secrets.GITHUB_TOKEN }}
  #         node: "false"
  #
  #     - name: Install cargo-nextest
  #       uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532
  #       with:
  #         tool: nextest
  #
  #     - name: Run tests
  #       timeout-minutes: 30
  #       run: cargo nextest run --workspace

  js-smoke-test:
    name: JS Package Tests
    runs-on: ubuntu-latest
    timeout-minutes: 30
    needs: [stage]
    if: ${{ always() && needs.stage.result == 'success' }}
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ needs.stage.outputs.stage-branch }}
          filter: blob:none

      - name: Setup Environment
        uses: ./.github/actions/setup-environment
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          rust: "false"
          capnproto: "false"
          node-extra-flags: "--no-frozen-lockfile"

      - name: Install Bun
        uses: oven-sh/setup-bun@v2

      - name: Install Global Turbo
        uses: ./.github/actions/install-global-turbo
        with:
          turbo-version: ${{ inputs.ci-tag-override || '' }}

      - name: Run JS Package Tests
        run: turbo run check-types test --filter="./packages/*" --filter="!turborepo-repository" --color

  build-rust:
    name: "Build Rust"
    needs: [stage]
    if: ${{ always() && needs.stage.result == 'success' }}
    strategy:
      fail-fast: false
      matrix:
        settings:
          - host: macos-latest
            target: "x86_64-apple-darwin"
          - host: macos-latest
            target: "aarch64-apple-darwin"
          - host: ubuntu-latest
            target: "x86_64-unknown-linux-musl"
            setup: "sudo apt-get update && sudo apt-get install -y build-essential clang lldb llvm libclang-dev curl musl-tools sudo unzip"
          - host: ubuntu-latest
            target: "aarch64-unknown-linux-musl"
            rust-build-env: 'CC_aarch64_unknown_linux_musl=clang AR_aarch64_unknown_linux_musl=llvm-ar RUSTFLAGS="-Clink-self-contained=yes -Clinker=rust-lld"'
            setup: "sudo apt-get update && sudo apt-get install -y build-essential musl-tools clang llvm gcc-aarch64-linux-gnu binutils-aarch64-linux-gnu"
          - host: windows-latest
            target: x86_64-pc-windows-msvc
    runs-on: ${{ matrix.settings.host }}
    timeout-minutes: 30
    steps:
      - name: Checkout repo
        uses: actions/checkout@v4
        with:
          ref: ${{ needs.stage.outputs.stage-branch }}
          filter: blob:none

      - name: Setup Protoc
        uses: ./.github/actions/setup-protoc
        with:
          version: "26.x"
          github-token: ${{ secrets.GITHUB_TOKEN }}

      - name: Setup capnproto
        uses: ./.github/actions/setup-capnproto

      - name: Rust Setup
        uses: actions-rust-lang/setup-rust-toolchain@v1
        with:
          target: ${{ matrix.settings.target }}
          # needed to not make it override the defaults
          rustflags: ""
          # we want more specific settings
          cache: false

      - name: Build Setup
        if: ${{ matrix.settings.setup }}
        run: ${{ matrix.settings.setup }}

      - name: Build
        run: ${{ matrix.settings.rust-build-env }} cargo build --profile release-turborepo -p turbo --target ${{ matrix.settings.target }}

      - name: Upload Artifacts
        uses: actions/upload-artifact@v4
        with:
          name: turbo-${{ matrix.settings.target }}
          path: target/${{ matrix.settings.target }}/release-turborepo/turbo*

  security-scan:
    name: "Security Audit (Non-blocking)"
    runs-on: ubuntu-latest
    timeout-minutes: 15
    continue-on-error: true
    needs: [stage]
    if: ${{ always() && needs.stage.result == 'success' }}
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ needs.stage.outputs.stage-branch }}
          filter: blob:none

      - name: Setup Environment
        uses: ./.github/actions/setup-environment
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          node-extra-flags: "--no-frozen-lockfile"

      - name: Rust dependency audit
        run: |
          cargo install cargo-audit --locked --quiet
          cargo audit --deny unsound --deny yanked

      - name: JS dependency audit
        # --ignore: false positive from workspace directory "cli/" matching npm package "cli"
        run: pnpm audit --prod --audit-level low --ignore GHSA-6cpc-mj5c-m9rq

  npm-publish:
    name: "Publish To NPM"
    runs-on: ubuntu-latest
    timeout-minutes: 30
    # TODO: Add rust-smoke-test back to needs and if-condition when re-enabled.
    needs: [stage, build-rust, js-smoke-test]
    if: ${{ always() && needs.stage.result == 'success' && needs.build-rust.result == 'success' && needs.js-smoke-test.result == 'success' }}
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ needs.stage.outputs.stage-branch }}
          filter: blob:none

      - uses: ./.github/actions/setup-node
        with:
          enable-corepack: false
          extra-flags: "--no-frozen-lockfile"

      - name: Install Global Turbo
        uses: ./.github/actions/install-global-turbo
        with:
          turbo-version: ${{ inputs.ci-tag-override || '' }}

      - name: Configure git
        run: |
          git config --global user.name 'Turbobot'
          git config --global user.email 'turbobot@vercel.com'

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

      - name: Move Rust artifacts into place
        run: |
          mv rust-artifacts/turbo-aarch64-apple-darwin cli/dist-darwin-arm64
          mv rust-artifacts/turbo-aarch64-unknown-linux-musl cli/dist-linux-arm64
          cp -r rust-artifacts/turbo-x86_64-pc-windows-msvc cli/dist-windows-arm64
          mv rust-artifacts/turbo-x86_64-unknown-linux-musl cli/dist-linux-x64
          mv rust-artifacts/turbo-x86_64-apple-darwin cli/dist-darwin-x64
          mv rust-artifacts/turbo-x86_64-pc-windows-msvc cli/dist-windows-x64

      - name: Ensure npm version
        run: npm install -g npm@11.5.1

      - name: Perform Release
        run: |
          SKIP_FLAG=""
          if [ "${{ inputs.dry_run }}" = "true" ]; then
            SKIP_FLAG="--skip-publish"
          fi
          cd cli && make publish-turbo SKIP_PUBLISH=$SKIP_FLAG

      # Upload published artifacts in case they are needed for debugging later
      - name: Upload Artifacts
        uses: actions/upload-artifact@v4
        with:
          name: turbo-combined
          path: cli/dist

  create-release-tag:
    name: "Create Release Tag"
    runs-on: ubuntu-latest
    timeout-minutes: 10
    needs: [stage, npm-publish]
    if: ${{ always() && !inputs.dry_run && needs.npm-publish.result == 'success' }}
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ needs.stage.outputs.stage-branch }}
          filter: blob:none

      - name: Configure git
        run: |
          git config --global user.name 'Turbobot'
          git config --global user.email 'turbobot@vercel.com'

      - name: Create and push release tag
        run: cd cli && make create-release-tag

      - name: Create GitHub Release
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          VERSION: ${{ needs.stage.outputs.version }}
          PREVIOUS_TAG: ${{ needs.stage.outputs.previous-tag }}
        run: |
          PRERELEASE_FLAG=""
          if [[ "$VERSION" == *"-canary."* ]]; then
            PRERELEASE_FLAG="--prerelease"
          fi

          NOTES_START_TAG_FLAG=""
          if [ -n "$PREVIOUS_TAG" ]; then
            NOTES_START_TAG_FLAG="--notes-start-tag ${PREVIOUS_TAG}"
          fi

          gh release create "v${VERSION}" \
            --title "Turborepo v${VERSION}" \
            --generate-notes \
            $NOTES_START_TAG_FLAG \
            $PRERELEASE_FLAG

  alias-versioned-docs:
    name: "Alias Versioned Docs"
    runs-on: ubuntu-latest
    timeout-minutes: 10
    needs: [stage, npm-publish]
    if: ${{ always() && !inputs.dry_run && needs.npm-publish.result == 'success' }}
    outputs:
      success: ${{ steps.alias.outcome == 'success' }}
      subdomain: ${{ steps.version.outputs.subdomain }}
      version: ${{ steps.version.outputs.version }}
      docs_url: ${{ steps.alias.outputs.docs_url }}
    steps:
      - name: Checkout staging branch
        uses: actions/checkout@v4
        with:
          ref: ${{ needs.stage.outputs.stage-branch }}
          filter: blob:none

      - name: Get version and compute subdomain
        id: version
        run: |
          VERSION=$(head -n 1 version.txt)
          # Transform version to valid subdomain (replace dots with dashes, prepend v)
          SUBDOMAIN=$(echo "v${VERSION}" | tr '.' '-')
          echo "version=${VERSION}" >> $GITHUB_OUTPUT
          echo "subdomain=${SUBDOMAIN}" >> $GITHUB_OUTPUT

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

      - name: Find Vercel deployment for SHA
        id: find-deployment
        env:
          VERCEL_TOKEN: ${{ secrets.TURBO_TOKEN }}
        run: |
          SHA="${{ needs.stage.outputs.base-sha }}"
          DEPLOYMENT_URL=$(vercel list turbo-site --scope=vercel -m githubCommitSha="${SHA}" --status=READY --token="${VERCEL_TOKEN}" 2>&1 | tee /dev/stderr | grep -E '^\S+\.vercel\.(app|sh)' | head -n 1 | awk '{print $1}')

          if [ -z "$DEPLOYMENT_URL" ]; then
            echo "::error::No deployment found for SHA ${SHA}."
            exit 1
          fi

          echo "deployment_url=${DEPLOYMENT_URL}" >> $GITHUB_OUTPUT

      - name: Assign subdomain alias
        id: alias
        env:
          VERCEL_TOKEN: ${{ secrets.TURBO_TOKEN }}
        run: |
          ALIAS="${{ steps.version.outputs.subdomain }}.turborepo.dev"
          DEPLOYMENT_URL="${{ steps.find-deployment.outputs.deployment_url }}"
          vercel alias set "${DEPLOYMENT_URL}" "${ALIAS}" --token="${VERCEL_TOKEN}" --scope=vercel
          echo "docs_url=https://${ALIAS}" >> $GITHUB_OUTPUT

      - name: Notify Slack on failure
        if: failure()
        uses: slackapi/slack-github-action@v1.23.0
        with:
          payload: |
            {
              "text": "Versioned docs aliasing failed for v${{ steps.version.outputs.version }}",
              "blocks": [
                {
                  "type": "header",
                  "text": { "type": "plain_text", "text": "Versioned Docs Aliasing Failed" }
                },
                {
                  "type": "section",
                  "fields": [
                    { "type": "mrkdwn", "text": "*Version:*\nv${{ steps.version.outputs.version }}" },
                    { "type": "mrkdwn", "text": "*Subdomain:*\n${{ steps.version.outputs.subdomain }}.turborepo.dev" }
                  ]
                },
                {
                  "type": "section",
                  "text": { "type": "mrkdwn", "text": "*Workflow:*\n<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View Logs>" }
                }
              ]
            }
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.DOCS_ALIAS_FAILURE_SLACK_WEBHOOK_URL }}

  update-examples:
    name: "Update Examples"
    needs: [stage, create-release-tag]
    if: ${{ always() && needs.create-release-tag.result == 'success' && !contains(needs.stage.outputs.version, '-canary.') }}
    uses: ./.github/workflows/update-examples-on-release.yml
    secrets: inherit

  create-release-pr:
    name: "Create Release PR"
    needs: [stage, npm-publish, create-release-tag, alias-versioned-docs]
    if: ${{ always() && needs.npm-publish.result == 'success' && needs.create-release-tag.result == 'success' && !inputs.dry_run }}
    runs-on: ubuntu-latest
    timeout-minutes: 30
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ needs.stage.outputs.stage-branch }}
          filter: blob:none

      - uses: ./.github/actions/setup-node
        with:
          enable-corepack: false
          extra-flags: "--no-frozen-lockfile"

      - name: Configure git
        run: |
          git config --global user.name 'Turbobot'
          git config --global user.email 'turbobot@vercel.com'

      - name: Commit lockfile if updated by install
        run: |
          if git diff --quiet pnpm-lock.yaml; then
            echo "Lockfile already up to date."
          else
            git add pnpm-lock.yaml
            git commit -m "Update lockfile for release"
            git push origin "${{ needs.stage.outputs.stage-branch }}"
          fi

      - name: Bump to next canary for stable releases
        run: |
          TAG=$(sed -n '2p' version.txt)

          if [ "$TAG" = "latest" ]; then
            VERSION="${{ needs.stage.outputs.version }}"
            echo "Stable release detected (${VERSION}). Bumping to next prepatch canary..."
            ./scripts/version.js prepatch
            cat version.txt
            git commit -anm "bump to next canary after ${VERSION}"
            git push origin "${{ needs.stage.outputs.stage-branch }}"
          else
            echo "Pre-release ($TAG), skipping canary bump."
          fi

      - name: Build PR body
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          VERSION="${{ needs.stage.outputs.version }}"
          PREVIOUS_TAG="${{ needs.stage.outputs.previous-tag }}"
          DOCS_URL="${{ needs.alias-versioned-docs.outputs.docs_url }}"

          echo "## Release v${VERSION}" > pr-body.md
          echo "" >> pr-body.md

          # Docs link (or warning if failed)
          if [ "${{ needs.alias-versioned-docs.result }}" != "success" ]; then
            echo "> [!CAUTION]" >> pr-body.md
            echo "> Versioned docs aliasing FAILED. [View logs](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})" >> pr-body.md
          elif [ -n "$DOCS_URL" ]; then
            echo "Versioned docs: ${DOCS_URL}" >> pr-body.md
          fi
          echo "" >> pr-body.md

          # Changelog (via GitHub API to avoid needing full git history)
          echo "### Changes" >> pr-body.md
          echo "" >> pr-body.md
          if [ -n "$PREVIOUS_TAG" ]; then
            gh api "repos/${{ github.repository }}/compare/${PREVIOUS_TAG}...main" \
              --jq '.commits[] | "- \(.commit.message | split("\n") | .[0]) (`\(.sha[:7])`)"' >> pr-body.md
          else
            echo "First release - no previous tag." >> pr-body.md
          fi

      - name: Create PR with auto-merge
        id: create-pr
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          VERSION="${{ needs.stage.outputs.version }}"
          STAGE_BRANCH="${{ needs.stage.outputs.stage-branch }}"

          PR_URL=$(gh pr create \
            --title "release(turborepo): ${VERSION}" \
            --body-file pr-body.md \
            --head "${STAGE_BRANCH}" \
            --base main)

          echo "url=$PR_URL" >> $GITHUB_OUTPUT
          PR_NUM=$(echo "$PR_URL" | grep -oE '[0-9]+$')
          echo "number=$PR_NUM" >> $GITHUB_OUTPUT

          gh pr merge "$PR_NUM" --auto --squash

      - name: Post required check statuses
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          PR_NUM="${{ steps.create-pr.outputs.number }}"
          SHA=$(gh pr view "$PR_NUM" --json headRefOid --jq '.headRefOid')

          for check in "Test Summary" "JS Test Summary"; do
            gh api "repos/${{ github.repository }}/check-runs" \
              --method POST \
              -f name="$check" \
              -f head_sha="$SHA" \
              -f status="completed" \
              -f conclusion="success" \
              -f "output[title]=Skipped for release PR" \
              -f "output[summary]=Release PRs skip CI - code was already tested on main before release."
          done

  cleanup-on-failure:
    name: "Cleanup Failed Release"
    runs-on: ubuntu-latest
    timeout-minutes: 10
    # TODO: Add rust-smoke-test back to needs and if-condition when re-enabled.
    needs:
      [
        stage,
        build-rust,
        js-smoke-test,
        npm-publish,
        create-release-tag,
        create-release-pr
      ]
    if: >-
      ${{
        always()
        && needs.stage.result == 'success'
        && (
          needs.build-rust.result == 'failure'
          || needs.js-smoke-test.result == 'failure'
          || needs.npm-publish.result == 'failure'
          || needs.create-release-tag.result == 'failure'
          || needs.create-release-pr.result == 'failure'
        )
      }}
    steps:
      - name: Delete staging branch
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          STAGE_BRANCH="${{ needs.stage.outputs.stage-branch }}"
          echo "::warning::Release failed. Cleaning up staging branch ${STAGE_BRANCH}..."
          gh api -X DELETE "repos/${{ github.repository }}/git/refs/heads/${STAGE_BRANCH}" || echo "Branch may already be deleted or not exist"

      - name: Delete release tag if it exists
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          VERSION="${{ needs.stage.outputs.version }}"
          echo "::warning::Cleaning up release tag v${VERSION} if it exists..."
          HTTP_STATUS=$(gh api -X DELETE "repos/${{ github.repository }}/git/refs/tags/v${VERSION}" 2>&1) && echo "Tag deleted." || {
            if echo "$HTTP_STATUS" | grep -q "Reference does not exist"; then
              echo "Tag does not exist, nothing to clean up."
            else
              echo "::error::Failed to delete tag v${VERSION}: ${HTTP_STATUS}"
              exit 1
            fi
          }
          echo "Cleanup complete. You can retry the release without using clear-staging-branch."
turborepo-test matrix perms .github/workflows/turborepo-test.yml
Triggers
pull_request, push
Runs on
ubuntu-latest, ubuntu-latest, windows-latest-8-core-oss, macos-latest-xlarge, macos-latest-xlarge, windows-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest, ${{ matrix.os.runner }}, ubuntu-latest
Jobs
find-changes, turbo_types_check, build_rust_test_windows, build_rust_test_macos, rust_test_macos, rust_test_windows, build_rust_test_ubuntu, rust_test_ubuntu, check-examples, check-lockfiles, js_native_packages, summary, cleanup
Matrix
node-version, os, os.name, os.runner, partition→ 1, 10, 18, 2, 20, 22, 24, 3, 4, 5, 6, 7, 8, 9, macos, macos-latest, ubuntu, ubuntu-latest
Actions
mozilla-actions/sccache-action, mozilla-actions/sccache-action, taiki-e/install-action, oven-sh/setup-bun, taiki-e/install-action, oven-sh/setup-bun, mozilla-actions/sccache-action, taiki-e/install-action, oven-sh/setup-bun, taiki-e/install-action, pnpm/action-setup, oven-sh/setup-bun, mozilla-actions/sccache-action
Commands
  • # Determine the base and head commits to compare if [ "${{ github.event_name }}" == "pull_request" ]; then # For pull requests, compare the base branch to the current HEAD git fetch origin ${{ github.base_ref }} BASE_COMMIT="origin/${{ github.base_ref }}" HEAD_COMMIT="HEAD" else # For pushes, use the before and after SHAs BASE_COMMIT="${{ github.event.before }}" HEAD_COMMIT="${{ github.event.after }}" fi echo "Comparing changes between $BASE_COMMIT and $HEAD_COMMIT" # Function to check if files in given paths have changed check_path_changes() { local name=$1 shift local paths=("$@") # Create a command that checks all paths local cmd="git diff --name-only $BASE_COMMIT $HEAD_COMMIT -- " for path in "${paths[@]}"; do cmd+="\"$path\" " done # Run the command and check if there are any results if eval "$cmd" | grep -q .; then echo "$name=true" >> $GITHUB_OUTPUT echo "Changes detected in $name paths" else echo "$name=false" >> $GITHUB_OUTPUT echo "No changes in $name paths" fi } # Function to make path checking more readable check_paths() { local name=$1 local path_string=$2 # Convert the comma-separated string to an array IFS=',' read -ra path_array <<< "$path_string" # Call the check_path_changes function with the name and paths check_path_changes "$name" "${path_array[@]}" } # Check each path pattern with a more readable syntax echo "Checking path patterns..." check_paths "docs" "docs/" check_paths "native-lib" "packages/turbo-repository/,crates/" # Handle the "rest" pattern - files that are NOT in examples/ or docs/ CHANGED_FILES=$(git diff --name-only $BASE_COMMIT $HEAD_COMMIT) # Filter to only include files that do NOT start with examples/ or docs/ FILES_NOT_IN_EXAMPLES_OR_DOCS=$(echo "$CHANGED_FILES" | grep -v -E "^(examples/|docs/)" || true) if [ -n "$FILES_NOT_IN_EXAMPLES_OR_DOCS" ]; then echo "rest=true" >> $GITHUB_OUTPUT echo "Changes detected outside examples/ and docs/ directories" else echo "rest=false" >> $GITHUB_OUTPUT echo "No changes outside examples/ and docs/ directories" fi # Detect if Rust/core code changed (requires Rust tests + integration tests) # This excludes JS-only changes like packages/*, lockfile, etc. RUST_PATTERNS="^(crates/|cli/|Cargo\.|rust-toolchain|\.cargo/|\.config/|\.github/|turborepo-tests/)" RUST_CHANGES=$(echo "$CHANGED_FILES" | grep -E "$RUST_PATTERNS" || true) if [ -n "$RUST_CHANGES" ]; then echo "rust=true" >> $GITHUB_OUTPUT echo "Rust/core changes detected:" echo "$RUST_CHANGES" else echo "rust=false" >> $GITHUB_OUTPUT echo "No Rust/core changes detected (JS-only change)" fi
  • cargo run -p turborepo-schema-gen -- schema -o packages/turbo-types/schemas/schema.json cargo run -p turborepo-schema-gen -- typescript -o packages/turbo-types/src/types/config-v2.ts pnpm exec oxfmt packages/turbo-types/schemas/schema.json packages/turbo-types/src/types/config-v2.ts
  • if ! git diff --exit-code; then echo "::error::Generated schema files are out of sync with Rust types" echo "::error::Please run 'pnpm generate-schema' in packages/turbo-types and commit the changes" git diff exit 1 fi
  • curl -L -o "%TEMP%\mingit.zip" "https://github.com/git-for-windows/git/releases/download/v2.47.1.windows.2/MinGit-2.47.1.2-64-bit.zip" mkdir "%ProgramFiles%\Git" tar -xf "%TEMP%\mingit.zip" -C "%ProgramFiles%\Git" del "%ProgramFiles%\Git\etc\gitconfig" echo %ProgramFiles%\Git\cmd>> "%GITHUB_PATH%"
  • git config --global core.autocrlf false git config --global core.eol lf
  • curl -L -o "%TEMP%\nextest.zip" "https://get.nexte.st/latest/windows" tar -xf "%TEMP%\nextest.zip" -C "%USERPROFILE%\.cargo\bin"
  • if [ -z "${RUSTC_WRAPPER}" ]; then unset RUSTC_WRAPPER fi turbo run build:test-archive --filter=@turbo/cli
  • if [ -z "${RUSTC_WRAPPER}" ]; then unset RUSTC_WRAPPER fi turbo run build:test-archive --filter=@turbo/cli
View raw YAML
name: Test
on:
  pull_request:
  push:
    branches: [main]

concurrency:
  group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
  cancel-in-progress: ${{ github.event_name == 'pull_request' }}

permissions:
  actions: write
  contents: read
  pull-requests: write

jobs:
  find-changes:
    name: Find path changes
    runs-on: ubuntu-latest
    timeout-minutes: 30
    outputs:
      is-release-pr: ${{ steps.check-release.outputs.is-release-pr }}
      docs: ${{ steps.filter.outputs.docs }}
      rest: ${{ steps.filter.outputs.rest }}
      rust: ${{ steps.filter.outputs.rust }}
      native-lib: ${{ steps.filter.outputs.native-lib }}
    steps:
      # Detect automated release PRs which only contain version bumps.
      # These PRs are created by the release workflow after code has already
      # been tested on main. Skipping tests on version-only changes saves CI time.
      - name: Checkout
        uses: actions/checkout@v4

      - name: Check if automated release PR
        id: check-release
        uses: ./.github/actions/check-release-pr

      - name: Check path changes
        if: steps.check-release.outputs.is-release-pr != 'true'
        id: filter
        run: |
          # Determine the base and head commits to compare
          if [ "${{ github.event_name }}" == "pull_request" ]; then
            # For pull requests, compare the base branch to the current HEAD
            git fetch origin ${{ github.base_ref }}
            BASE_COMMIT="origin/${{ github.base_ref }}"
            HEAD_COMMIT="HEAD"
          else
            # For pushes, use the before and after SHAs
            BASE_COMMIT="${{ github.event.before }}"
            HEAD_COMMIT="${{ github.event.after }}"
          fi

          echo "Comparing changes between $BASE_COMMIT and $HEAD_COMMIT"

          # Function to check if files in given paths have changed
          check_path_changes() {
            local name=$1
            shift
            local paths=("$@")

            # Create a command that checks all paths
            local cmd="git diff --name-only $BASE_COMMIT $HEAD_COMMIT -- "
            for path in "${paths[@]}"; do
              cmd+="\"$path\" "
            done

            # Run the command and check if there are any results
            if eval "$cmd" | grep -q .; then
              echo "$name=true" >> $GITHUB_OUTPUT
              echo "Changes detected in $name paths"
            else
              echo "$name=false" >> $GITHUB_OUTPUT
              echo "No changes in $name paths"
            fi
          }

          # Function to make path checking more readable
          check_paths() {
            local name=$1
            local path_string=$2

            # Convert the comma-separated string to an array
            IFS=',' read -ra path_array <<< "$path_string"

            # Call the check_path_changes function with the name and paths
            check_path_changes "$name" "${path_array[@]}"
          }

          # Check each path pattern with a more readable syntax
          echo "Checking path patterns..."

          check_paths "docs" "docs/"
          check_paths "native-lib" "packages/turbo-repository/,crates/"

          # Handle the "rest" pattern - files that are NOT in examples/ or docs/
          CHANGED_FILES=$(git diff --name-only $BASE_COMMIT $HEAD_COMMIT)

          # Filter to only include files that do NOT start with examples/ or docs/
          FILES_NOT_IN_EXAMPLES_OR_DOCS=$(echo "$CHANGED_FILES" | grep -v -E "^(examples/|docs/)" || true)

          if [ -n "$FILES_NOT_IN_EXAMPLES_OR_DOCS" ]; then
            echo "rest=true" >> $GITHUB_OUTPUT
            echo "Changes detected outside examples/ and docs/ directories"
          else
            echo "rest=false" >> $GITHUB_OUTPUT
            echo "No changes outside examples/ and docs/ directories"
          fi

          # Detect if Rust/core code changed (requires Rust tests + integration tests)
          # This excludes JS-only changes like packages/*, lockfile, etc.
          RUST_PATTERNS="^(crates/|cli/|Cargo\.|rust-toolchain|\.cargo/|\.config/|\.github/|turborepo-tests/)"
          RUST_CHANGES=$(echo "$CHANGED_FILES" | grep -E "$RUST_PATTERNS" || true)

          if [ -n "$RUST_CHANGES" ]; then
            echo "rust=true" >> $GITHUB_OUTPUT
            echo "Rust/core changes detected:"
            echo "$RUST_CHANGES"
          else
            echo "rust=false" >> $GITHUB_OUTPUT
            echo "No Rust/core changes detected (JS-only change)"
          fi

  turbo_types_check:
    name: "@turbo/types codegen check"
    needs:
      - find-changes
    if: ${{ needs.find-changes.outputs.is-release-pr != 'true' && needs.find-changes.outputs.rust == 'true' }}
    runs-on: ubuntu-latest
    timeout-minutes: 30
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Environment
        uses: ./.github/actions/setup-environment
        with:
          github-token: "${{ secrets.GITHUB_TOKEN }}"

      - name: Install Global Turbo
        uses: ./.github/actions/install-global-turbo

      - name: Generate schemas from Rust
        run: |
          cargo run -p turborepo-schema-gen -- schema -o packages/turbo-types/schemas/schema.json
          cargo run -p turborepo-schema-gen -- typescript -o packages/turbo-types/src/types/config-v2.ts
          pnpm exec oxfmt packages/turbo-types/schemas/schema.json packages/turbo-types/src/types/config-v2.ts

      - name: Check for uncommitted changes
        run: |
          if ! git diff --exit-code; then
            echo "::error::Generated schema files are out of sync with Rust types"
            echo "::error::Please run 'pnpm generate-schema' in packages/turbo-types and commit the changes"
            git diff
            exit 1
          fi

  build_rust_test_windows:
    runs-on: windows-latest-8-core-oss
    timeout-minutes: 45
    needs:
      - find-changes
    if: ${{ needs.find-changes.outputs.is-release-pr != 'true' && needs.find-changes.outputs.rust == 'true' }}
    name: Build test artifacts (windows)
    env:
      TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
      TURBO_TEAM: ${{ vars.TURBO_TEAM }}
      TURBO_CACHE: remote:rw
      SCCACHE_BUCKET: turborepo-sccache
      SCCACHE_REGION: us-east-2
      RUSTC_WRAPPER: ${{ !github.event.pull_request.head.repo.fork && 'sccache' || '' }}
      CARGO_INCREMENTAL: 0
      AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
      AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
      SCCACHE_IDLE_TIMEOUT: 0
      SCCACHE_REQUEST_TIMEOUT: 30
      SCCACHE_ERROR_LOG: error
    steps:
      - name: Install git (Windows larger runners)
        shell: cmd
        run: |
          curl -L -o "%TEMP%\mingit.zip" "https://github.com/git-for-windows/git/releases/download/v2.47.1.windows.2/MinGit-2.47.1.2-64-bit.zip"
          mkdir "%ProgramFiles%\Git"
          tar -xf "%TEMP%\mingit.zip" -C "%ProgramFiles%\Git"
          del "%ProgramFiles%\Git\etc\gitconfig"
          echo %ProgramFiles%\Git\cmd>> "%GITHUB_PATH%"

      - name: Set git to use LF line endings
        run: |
          git config --global core.autocrlf false
          git config --global core.eol lf

      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Environment
        uses: ./.github/actions/setup-environment
        with:
          github-token: "${{ secrets.GITHUB_TOKEN }}"
          node-version: "18.20.2"
          package-install: "false"

      - name: Run sccache-cache
        uses: mozilla-actions/sccache-action@v0.0.6

      - name: Install cargo-nextest
        shell: cmd
        run: |
          curl -L -o "%TEMP%\nextest.zip" "https://get.nexte.st/latest/windows"
          tar -xf "%TEMP%\nextest.zip" -C "%USERPROFILE%\.cargo\bin"

      - name: Install Global Turbo
        uses: ./.github/actions/install-global-turbo

      - name: Build and archive test binaries
        run: |
          if [ -z "${RUSTC_WRAPPER}" ]; then
            unset RUSTC_WRAPPER
          fi
          turbo run build:test-archive --filter=@turbo/cli
        shell: bash

  build_rust_test_macos:
    runs-on: macos-latest-xlarge
    timeout-minutes: 45
    needs:
      - find-changes
    if: ${{ needs.find-changes.outputs.is-release-pr != 'true' && needs.find-changes.outputs.rust == 'true' }}
    name: Build test artifacts (macos)
    env:
      TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
      TURBO_TEAM: ${{ vars.TURBO_TEAM }}
      TURBO_CACHE: remote:rw
      SCCACHE_BUCKET: turborepo-sccache
      SCCACHE_REGION: us-east-2
      RUSTC_WRAPPER: ${{ !github.event.pull_request.head.repo.fork && 'sccache' || '' }}
      CARGO_INCREMENTAL: 0
      AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
      AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
      SCCACHE_IDLE_TIMEOUT: 0
      SCCACHE_REQUEST_TIMEOUT: 30
      SCCACHE_ERROR_LOG: error
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Environment
        uses: ./.github/actions/setup-environment
        with:
          github-token: "${{ secrets.GITHUB_TOKEN }}"
          node-version: "18.20.2"
          package-install: "false"

      - name: Run sccache-cache
        uses: mozilla-actions/sccache-action@v0.0.6

      - name: Install cargo-nextest
        uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532
        with:
          tool: nextest

      - name: Install Global Turbo
        uses: ./.github/actions/install-global-turbo

      - name: Build and archive test binaries
        run: |
          if [ -z "${RUSTC_WRAPPER}" ]; then
            unset RUSTC_WRAPPER
          fi
          turbo run build:test-archive --filter=@turbo/cli
        shell: bash

  rust_test_macos:
    runs-on: macos-latest-xlarge
    timeout-minutes: 45
    needs:
      - find-changes
      - build_rust_test_macos
    if: ${{ needs.find-changes.outputs.is-release-pr != 'true' && needs.find-changes.outputs.rust == 'true' }}
    strategy:
      fail-fast: false
      matrix:
        partition: [1, 2]
    name: Rust testing on macos (partition ${{ matrix.partition }}/2)
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Environment
        uses: ./.github/actions/setup-environment
        with:
          github-token: "${{ secrets.GITHUB_TOKEN }}"
          node-version: "18.20.2"
          package-install: "false"

      - name: Install Bun
        uses: oven-sh/setup-bun@4bc047ad259df6fc24a6c9b0f9a0cb08cf17fbe5 # v2.0.1

      - name: Install cargo-nextest
        uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532
        with:
          tool: nextest

      - name: Install Global Turbo
        uses: ./.github/actions/install-global-turbo

      - name: Restore test archive from cache
        run: turbo run build:test-archive --filter=@turbo/cli
        shell: bash
        env:
          TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
          TURBO_TEAM: ${{ vars.TURBO_TEAM }}
          TURBO_CACHE: remote:rw

      - name: Run tests
        timeout-minutes: 45
        run: |
          cargo nextest run --archive-file cli/nextest-archive.tar.zst --no-fail-fast --partition hash:${{ matrix.partition }}/2
        shell: bash

  rust_test_windows:
    strategy:
      fail-fast: false
      matrix:
        partition: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    runs-on: windows-latest
    timeout-minutes: 45
    needs:
      - find-changes
      - build_rust_test_windows
    if: ${{ needs.find-changes.outputs.is-release-pr != 'true' && needs.find-changes.outputs.rust == 'true' }}
    name: Rust testing on windows (partition ${{ matrix.partition }}/10)
    steps:
      - name: Set git to use LF line endings
        run: |
          git config --global core.autocrlf false
          git config --global core.eol lf

      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Environment
        uses: ./.github/actions/setup-environment
        with:
          github-token: "${{ secrets.GITHUB_TOKEN }}"
          node-version: "18.20.2"
          capnproto: ${{ github.event.pull_request.head.repo.fork && 'true' || 'false' }}
          rust: ${{ github.event.pull_request.head.repo.fork && 'true' || 'false' }}
          package-install: "false"
          rust-cache: "false"

      - name: Install Bun
        uses: oven-sh/setup-bun@4bc047ad259df6fc24a6c9b0f9a0cb08cf17fbe5 # v2.0.1

      - name: Install cargo-nextest
        shell: cmd
        run: |
          curl -L -o "%TEMP%\nextest.zip" "https://get.nexte.st/latest/windows"
          tar -xf "%TEMP%\nextest.zip" -C "%USERPROFILE%\.cargo\bin"

      - name: Install Global Turbo
        uses: ./.github/actions/install-global-turbo

      - name: Restore test archive from cache
        run: turbo run build:test-archive --filter=@turbo/cli
        shell: bash
        env:
          TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
          TURBO_TEAM: ${{ vars.TURBO_TEAM }}
          TURBO_CACHE: remote:rw

      - name: Run tests
        timeout-minutes: 45
        run: |
          cargo nextest run --archive-file cli/nextest-archive.tar.zst --no-fail-fast --partition hash:${{ matrix.partition }}/10 --workspace-remap "$GITHUB_WORKSPACE"
        shell: bash
        env:
          INSTA_WORKSPACE_ROOT: ${{ github.workspace }}

  build_rust_test_ubuntu:
    runs-on: ubuntu-latest
    timeout-minutes: 45
    needs:
      - find-changes
    if: ${{ needs.find-changes.outputs.is-release-pr != 'true' && needs.find-changes.outputs.rust == 'true' }}
    name: Build test artifacts (ubuntu)
    env:
      TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
      TURBO_TEAM: ${{ vars.TURBO_TEAM }}
      TURBO_CACHE: remote:rw
      SCCACHE_BUCKET: turborepo-sccache
      SCCACHE_REGION: us-east-2
      RUSTC_WRAPPER: ${{ !github.event.pull_request.head.repo.fork && 'sccache' || '' }}
      CARGO_INCREMENTAL: 0
      AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
      AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
      SCCACHE_IDLE_TIMEOUT: 0
      SCCACHE_REQUEST_TIMEOUT: 30
      SCCACHE_ERROR_LOG: error
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Environment
        uses: ./.github/actions/setup-environment
        with:
          github-token: "${{ secrets.GITHUB_TOKEN }}"
          node-version: "18.20.2"
          package-install: "false"

      - name: Run sccache-cache
        uses: mozilla-actions/sccache-action@v0.0.6

      - name: Install cargo-nextest
        uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532
        with:
          tool: nextest

      - name: Install Global Turbo
        uses: ./.github/actions/install-global-turbo

      - name: Build and archive test binaries
        run: |
          if [ -z "${RUSTC_WRAPPER}" ]; then
            unset RUSTC_WRAPPER
          fi
          turbo run build:test-archive --filter=@turbo/cli
        shell: bash

  rust_test_ubuntu:
    runs-on: ubuntu-latest
    timeout-minutes: 45
    needs:
      - find-changes
      - build_rust_test_ubuntu
    if: ${{ needs.find-changes.outputs.is-release-pr != 'true' && needs.find-changes.outputs.rust == 'true' }}
    strategy:
      fail-fast: false
      matrix:
        partition: [1, 2]
    name: Rust testing on ubuntu (partition ${{ matrix.partition }}/2)
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Environment
        uses: ./.github/actions/setup-environment
        with:
          github-token: "${{ secrets.GITHUB_TOKEN }}"
          node-version: "18.20.2"
          package-install: "false"

      - name: Install Bun
        uses: oven-sh/setup-bun@4bc047ad259df6fc24a6c9b0f9a0cb08cf17fbe5 # v2.0.1

      - name: Install cargo-nextest
        uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532
        with:
          tool: nextest

      - name: Install Global Turbo
        uses: ./.github/actions/install-global-turbo

      - name: Restore test archive from cache
        run: turbo run build:test-archive --filter=@turbo/cli
        shell: bash
        env:
          TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
          TURBO_TEAM: ${{ vars.TURBO_TEAM }}
          TURBO_CACHE: remote:rw

      - name: Run tests
        timeout-minutes: 45
        run: |
          cargo nextest run --archive-file cli/nextest-archive.tar.zst --no-fail-fast --partition hash:${{ matrix.partition }}/2
        shell: bash

  check-examples:
    name: Check examples
    timeout-minutes: 40
    needs:
      - find-changes
    if: ${{ needs.find-changes.outputs.is-release-pr != 'true' && !github.event.pull_request.head.repo.fork }}
    runs-on: ubuntu-latest
    env:
      TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
      TURBO_TEAM: ${{ vars.TURBO_TEAM }}
      TURBO_CACHE: remote:rw
      VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: "20"

      - name: Setup pnpm
        uses: pnpm/action-setup@v4

      - name: Install dependencies
        run: pnpm install

      - name: Install Global Turbo
        uses: ./.github/actions/install-global-turbo

      - name: Setup Vercel credentials
        run: |
          cd examples
          npx vercel link --yes --token=${{ secrets.VERCEL_TOKEN }} --project turborepo-examples-app
          npx vercel env pull --yes --token=${{ secrets.VERCEL_TOKEN }}

      - name: Run examples check
        run: turbo run check-examples --filter=turborepo-examples --env-mode=strict

  check-lockfiles:
    name: Integration - Lockfile parsers
    timeout-minutes: 15
    needs:
      - find-changes
    if: ${{ needs.find-changes.outputs.is-release-pr != 'true' }}
    runs-on: ubuntu-latest
    env:
      TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
      TURBO_TEAM: ${{ vars.TURBO_TEAM }}
      TURBO_CACHE: remote:rw
      SCCACHE_BUCKET: turborepo-sccache
      SCCACHE_REGION: us-east-2
      RUSTC_WRAPPER: ${{ !github.event.pull_request.head.repo.fork && 'sccache' || '' }}
      CARGO_INCREMENTAL: 0
      AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
      AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Environment
        uses: ./.github/actions/setup-environment
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}

      - name: Install Bun
        uses: oven-sh/setup-bun@4bc047ad259df6fc24a6c9b0f9a0cb08cf17fbe5 # v2.0.1

      - name: Run sccache-cache
        uses: mozilla-actions/sccache-action@v0.0.6

      - name: Build turbo binary
        run: cargo build

      - name: Run lockfile tests
        run: pnpm check-lockfiles
        working-directory: lockfile-tests

  js_native_packages:
    name: "@turbo/repository (${{matrix.os.name}}, Node ${{matrix.node-version}})"
    needs:
      - find-changes
    if: ${{ needs.find-changes.outputs.is-release-pr != 'true' && needs.find-changes.outputs.native-lib == 'true' }}
    timeout-minutes: 30
    runs-on: ${{ matrix.os.runner }}
    strategy:
      fail-fast: false
      matrix:
        os:
          - name: ubuntu
            runner: ubuntu-latest
          - name: macos
            runner: macos-latest
        node-version:
          - 18
          - 20
          - 22
          - 24
    env:
      TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
      TURBO_TEAM: ${{ vars.TURBO_TEAM }}
      TURBO_CACHE: remote:rw
    steps:
      - name: Determine fetch depth
        id: fetch-depth
        run: |
          echo "depth=$(( ${{ github.event.pull_request.commits || 1 }} + 1 ))" >> $GITHUB_OUTPUT

      - name: Checkout
        uses: actions/checkout@v4
        with:
          ref: ${{ github.ref }}
          fetch-depth: ${{ steps.fetch-depth.outputs.depth  }}

      - name: Setup Environment
        uses: ./.github/actions/setup-environment
        with:
          github-token: "${{ secrets.GITHUB_TOKEN }}"
          node-version: ${{ matrix.node-version }}

      - name: Install Global Turbo
        uses: ./.github/actions/install-global-turbo

      - name: Run tests
        run: |
          TURBO_API= turbo run test --filter "turborepo-repository" --color --env-mode=strict
        env:
          NODE_VERSION: ${{ matrix.node-version }}

  summary:
    name: Test Summary
    runs-on: ubuntu-latest
    timeout-minutes: 30
    if: always()
    needs:
      - find-changes
      - turbo_types_check
      - build_rust_test_macos
      - build_rust_test_windows
      - build_rust_test_ubuntu
      - rust_test_macos
      - rust_test_windows
      - rust_test_ubuntu
      - check-examples
      - check-lockfiles
      - js_native_packages
    steps:
      - name: Skip for release PR
        if: needs.find-changes.outputs.is-release-pr == 'true'
        run: |
          echo "Release PR detected - skipping tests (code already tested on main)"

      - name: Compute info
        id: info
        if: needs.find-changes.outputs.is-release-pr != 'true'
        run: |
          cancelled=false
          failure=false

          subjob () {
            local result=$1
            if [ "$result" = "cancelled" ]; then
              cancelled=true
            elif [ "$result" != "success" ] && [ "$result" != "skipped" ]; then
              failure=true
            fi
          }

          subjob ${{needs.turbo_types_check.result}}
          subjob ${{needs.build_rust_test_macos.result}}
          subjob ${{needs.build_rust_test_windows.result}}
          subjob ${{needs.build_rust_test_ubuntu.result}}
          subjob ${{needs.rust_test_macos.result}}
          subjob ${{needs.rust_test_windows.result}}
          subjob ${{needs.rust_test_ubuntu.result}}
          subjob ${{needs.check-examples.result}}
          subjob ${{needs.check-lockfiles.result}}
          subjob ${{needs.js_native_packages.result}}

          if [ "$cancelled" = "true" ]; then
            echo "cancelled=true" >> $GITHUB_OUTPUT
          elif [ "$failure" = "true" ]; then
            echo "failure=true" >> $GITHUB_OUTPUT
          else
            echo "success=true" >> $GITHUB_OUTPUT
          fi

      - name: Failed
        if: needs.find-changes.outputs.is-release-pr != 'true' && steps.info.outputs.failure == 'true'
        run: exit 1

      - name: Succeeded
        if: needs.find-changes.outputs.is-release-pr != 'true' && steps.info.outputs.success == 'true'
        run: echo Ok

  cleanup:
    name: Cleanup
    needs: summary
    if: always()
    uses: ./.github/workflows/pr-clean-caches.yml
    secrets: inherit
turborepo-top-issues .github/workflows/turborepo-top-issues.yml
Triggers
schedule, workflow_dispatch
Runs on
ubuntu-latest
Jobs
run
Actions
slackapi/slack-github-action
Commands
  • corepack enable
  • pnpm install
  • node ./packages/top-issues/src/index.mjs packages/top-issues
  • cat packages/top-issues/slack-payload.json
View raw YAML
name: Top Issues

on:
  schedule:
    - cron: "0 13 * * 1" # Every Monday at 1PM UTC (9AM EST)
  workflow_dispatch:

jobs:
  run:
    # if: github.repository_owner == 'vercel'
    runs-on: ubuntu-latest
    timeout-minutes: 30
    steps:
      - uses: actions/checkout@v4
      - uses: ./.github/actions/setup-node
      - run: corepack enable
      - run: pnpm install
      - name: "Get Top Issues"
        run: node ./packages/top-issues/src/index.mjs packages/top-issues
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      - name: "Show slack payload"
        run: cat packages/top-issues/slack-payload.json
      - name: Send payload to slack
        uses: slackapi/slack-github-action@v1.23.0
        with:
          payload-file-path: "packages/top-issues/slack-payload.json"
        env:
          SLACK_WEBHOOK_URL: "${{ secrets.TURBOREPO_REPO_STATS_SLACK_WEBHOOK_URL }}"
update-examples-on-release .github/workflows/update-examples-on-release.yml
Triggers
workflow_dispatch, workflow_call
Runs on
ubuntu-latest
Jobs
update-examples-pr
Actions
thomaseizinger/create-pull-request
Commands
  • npm install --force --global corepack@latest npm config get prefix >> $GITHUB_PATH
  • git config --global user.name 'Turbobot' git config --global user.email 'turbobot@vercel.com'
  • git checkout -b post-release-bump-examples echo "STAGE_BRANCH=$(git branch --show-current)" >> $GITHUB_OUTPUT
  • bash scripts/update-examples-dep.sh
  • git commit -am "release(turborepo): update examples to latest" git push origin ${{ steps.branch.outputs.STAGE_BRANCH }}
  • echo ${{ steps.pr.outputs.html_url }}
View raw YAML
name: Update examples to latest

on:
  workflow_dispatch:
  workflow_call:

jobs:
  update-examples-pr:
    name: "Update examples PR"
    runs-on: ubuntu-latest
    timeout-minutes: 30
    steps:
      - uses: actions/checkout@v4
        with:
          filter: blob:none

      - uses: ./.github/actions/setup-node

      - name: Upgrade corepack
        shell: bash
        run: |
          npm install --force --global corepack@latest
          npm config get prefix >> $GITHUB_PATH

      - name: Configure git
        run: |
          git config --global user.name 'Turbobot'
          git config --global user.email 'turbobot@vercel.com'

      - name: Make branch
        id: branch
        run: |
          git checkout -b post-release-bump-examples
          echo "STAGE_BRANCH=$(git branch --show-current)" >> $GITHUB_OUTPUT

      - name: Run upgrade script
        run: bash scripts/update-examples-dep.sh

      - name: Commit and push
        env:
          HUSKY: "0"
        run: |
          git commit -am "release(turborepo): update examples to latest"
          git push origin ${{ steps.branch.outputs.STAGE_BRANCH }}

      - name: Create pull request
        id: pr
        uses: thomaseizinger/create-pull-request@master
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          head: ${{ steps.branch.outputs.STAGE_BRANCH }}
          base: main
          title: "release(turborepo): update examples to latest"
      - name: PR link
        run: echo ${{ steps.pr.outputs.html_url }}