name: Upgrade Vite & Rolldown 📦
on:
schedule:
# Run daily at 07:00 UTC (staggered from React upgrade)
- cron: "0 7 * * *"
workflow_dispatch:
env:
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: "1"
permissions:
contents: write
pull-requests: write
concurrency:
group: upgrade-vite-rolldown
cancel-in-progress: true
jobs:
upgrade-vite-rolldown:
name: Upgrade Vite & Rolldown to latest
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Generate GitHub App token
id: app-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
- name: Check for existing open PR
id: pr
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
run: |
OPEN=$(gh pr list --head chore/upgrade-vite-rolldown --state open --json number --jq 'length')
if [ "$OPEN" -gt 0 ]; then
echo "exists=true" >> "$GITHUB_OUTPUT"
echo "An open upgrade PR already exists, skipping."
else
echo "exists=false" >> "$GITHUB_OUTPUT"
fi
- name: Get current versions
if: steps.pr.outputs.exists == 'false'
id: current
run: |
VITE=$(jq -r '.dependencies.vite' packages/react-server/package.json)
ROLLDOWN=$(jq -r '.dependencies.rolldown' packages/react-server/package.json)
# Strip ^ prefix from vitest version
VITEST=$(jq -r '.devDependencies.vitest' test/package.json | sed 's/^\^//')
echo "vite=$VITE" >> "$GITHUB_OUTPUT"
echo "rolldown=$ROLLDOWN" >> "$GITHUB_OUTPUT"
echo "vitest=$VITEST" >> "$GITHUB_OUTPUT"
echo "Current Vite: $VITE"
echo "Current Rolldown: $ROLLDOWN"
echo "Current Vitest: $VITEST"
- name: Get latest versions from npm
if: steps.pr.outputs.exists == 'false'
id: latest
run: |
# For Vite, check both latest and beta tags, pick the highest
VITE_LATEST=$(npm view vite@latest version 2>/dev/null || echo "")
VITE_BETA=$(npm view vite@beta version 2>/dev/null || echo "")
# Pick the higher semver (prefer pre-release if it has a higher major/minor/patch)
VITE=$(node -e "
const latest = '$VITE_LATEST';
const beta = '$VITE_BETA';
if (!latest && !beta) { process.exit(1); }
if (!beta) { console.log(latest); process.exit(0); }
if (!latest) { console.log(beta); process.exit(0); }
const lParts = latest.replace(/-.*$/, '').split('.').map(Number);
const bParts = beta.replace(/-.*$/, '').split('.').map(Number);
for (let i = 0; i < 3; i++) {
if (bParts[i] > lParts[i]) { console.log(beta); process.exit(0); }
if (bParts[i] < lParts[i]) { console.log(latest); process.exit(0); }
}
// Same base version — prefer stable
console.log(latest);
")
if [ -z "$VITE" ]; then
VITE="${VITE_BETA:-$VITE_LATEST}"
fi
# For Rolldown, check latest and rc tags
ROLLDOWN_LATEST=$(npm view rolldown@latest version 2>/dev/null || echo "")
ROLLDOWN_RC=$(npm view rolldown@rc version 2>/dev/null || echo "")
ROLLDOWN=$(node -e "
const latest = '$ROLLDOWN_LATEST';
const rc = '$ROLLDOWN_RC';
if (!latest && !rc) { process.exit(1); }
if (!rc) { console.log(latest); process.exit(0); }
if (!latest) { console.log(rc); process.exit(0); }
const lParts = latest.replace(/-.*$/, '').split('.').map(Number);
const rParts = rc.replace(/-.*$/, '').split('.').map(Number);
for (let i = 0; i < 3; i++) {
if (rParts[i] > lParts[i]) { console.log(rc); process.exit(0); }
if (rParts[i] < lParts[i]) { console.log(latest); process.exit(0); }
}
console.log(latest);
")
if [ -z "$ROLLDOWN" ]; then
ROLLDOWN="${ROLLDOWN_RC:-$ROLLDOWN_LATEST}"
fi
# For Vitest, check both latest and beta tags (same as Vite)
VITEST_LATEST=$(npm view vitest@latest version 2>/dev/null || echo "")
VITEST_BETA=$(npm view vitest@beta version 2>/dev/null || echo "")
VITEST=$(node -e "
const latest = '$VITEST_LATEST';
const beta = '$VITEST_BETA';
if (!latest && !beta) { process.exit(1); }
if (!beta) { console.log(latest); process.exit(0); }
if (!latest) { console.log(beta); process.exit(0); }
const lParts = latest.replace(/-.*$/, '').split('.').map(Number);
const bParts = beta.replace(/-.*$/, '').split('.').map(Number);
for (let i = 0; i < 3; i++) {
if (bParts[i] > lParts[i]) { console.log(beta); process.exit(0); }
if (bParts[i] < lParts[i]) { console.log(latest); process.exit(0); }
}
// Same base version — prefer stable
console.log(latest);
")
if [ -z "$VITEST" ]; then
VITEST="${VITEST_BETA:-$VITEST_LATEST}"
fi
echo "vite=$VITE" >> "$GITHUB_OUTPUT"
echo "rolldown=$ROLLDOWN" >> "$GITHUB_OUTPUT"
echo "vitest=$VITEST" >> "$GITHUB_OUTPUT"
echo "Latest Vite: $VITE"
echo "Latest Rolldown: $ROLLDOWN"
echo "Latest Vitest: $VITEST"
- name: Check if upgrade is needed
if: steps.pr.outputs.exists == 'false'
id: check
run: |
VITE_CHANGED="false"
ROLLDOWN_CHANGED="false"
VITEST_CHANGED="false"
if [ "${{ steps.current.outputs.vite }}" != "${{ steps.latest.outputs.vite }}" ]; then
VITE_CHANGED="true"
fi
if [ "${{ steps.current.outputs.rolldown }}" != "${{ steps.latest.outputs.rolldown }}" ]; then
ROLLDOWN_CHANGED="true"
fi
if [ "${{ steps.current.outputs.vitest }}" != "${{ steps.latest.outputs.vitest }}" ]; then
VITEST_CHANGED="true"
fi
if [ "$VITE_CHANGED" = "false" ] && [ "$ROLLDOWN_CHANGED" = "false" ] && [ "$VITEST_CHANGED" = "false" ]; then
echo "needed=false" >> "$GITHUB_OUTPUT"
echo "Already on latest versions, skipping."
else
echo "needed=true" >> "$GITHUB_OUTPUT"
echo "vite_changed=$VITE_CHANGED" >> "$GITHUB_OUTPUT"
echo "rolldown_changed=$ROLLDOWN_CHANGED" >> "$GITHUB_OUTPUT"
echo "vitest_changed=$VITEST_CHANGED" >> "$GITHUB_OUTPUT"
echo "Upgrade needed — Vite changed: $VITE_CHANGED, Rolldown changed: $ROLLDOWN_CHANGED, Vitest changed: $VITEST_CHANGED"
fi
- name: Install pnpm
if: steps.pr.outputs.exists == 'false' && steps.check.outputs.needed == 'true'
uses: pnpm/action-setup@v4.0.0
- name: Setup Node
if: steps.pr.outputs.exists == 'false' && steps.check.outputs.needed == 'true'
uses: actions/setup-node@v4
with:
node-version: "24"
cache: "pnpm"
- name: Replace Vite version in all package.json files
if: steps.pr.outputs.exists == 'false' && steps.check.outputs.needed == 'true' && steps.check.outputs.vite_changed == 'true'
run: bash .github/scripts/bump-package-dep.sh vite "${{ steps.latest.outputs.vite }}"
- name: Replace Rolldown version in all package.json files
if: steps.pr.outputs.exists == 'false' && steps.check.outputs.needed == 'true' && steps.check.outputs.rolldown_changed == 'true'
run: bash .github/scripts/bump-package-dep.sh rolldown "${{ steps.latest.outputs.rolldown }}"
- name: Replace Vitest version in all package.json files
if: steps.pr.outputs.exists == 'false' && steps.check.outputs.needed == 'true' && steps.check.outputs.vitest_changed == 'true'
run: bash .github/scripts/bump-package-dep.sh vitest "${{ steps.latest.outputs.vitest }}"
- name: Install deps
if: steps.pr.outputs.exists == 'false' && steps.check.outputs.needed == 'true'
run: pnpm install --no-frozen-lockfile