vercel/turborepo
13 workflows · maturity 67% · 11 patterns · GitHub ↗
Practices
✓ Matrix✓ Permissions○ Security scan○ AI review✓ Cache✓ Concurrency✓ Reusable workflows
Detected patterns
Security dimensions
Workflows (13)
docs .github/workflows/docs.yml
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
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
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
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
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
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
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
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
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
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
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
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
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 }}