Repository: vitejs/vite-ecosystem-ci
Branch: main
Commit: a0c409db48ea
Files: 57
Total size: 78.7 KB
Directory structure:
gitextract_ju04i1kh/
├── .editorconfig
├── .github/
│ ├── renovate.json5
│ └── workflows/
│ ├── ci.yml
│ ├── ecosystem-ci-from-pr.yml
│ ├── ecosystem-ci-selected.yml
│ └── ecosystem-ci.yml
├── .gitignore
├── .prettierrc.json
├── LICENSE
├── README.md
├── builds/
│ ├── vite-plugin-react.ts
│ ├── vite-plugin-svelte.ts
│ └── vite-plugin-vue.ts
├── discord-webhook.ts
├── docs/
│ └── pr-comment-setup.md
├── ecosystem-ci.ts
├── eslint.config.js
├── package.json
├── pnpm-workspace.yaml
├── tests/
│ ├── _selftest.ts
│ ├── analogjs.ts
│ ├── astro.ts
│ ├── histoire.ts
│ ├── hydrogen.ts
│ ├── iles.ts
│ ├── ladle.ts
│ ├── laravel.ts
│ ├── marko.ts
│ ├── nuxt.ts
│ ├── nx.ts
│ ├── one.ts
│ ├── quasar.ts
│ ├── qwik.ts
│ ├── rakkas.ts
│ ├── react-router.ts
│ ├── redwoodjs.ts
│ ├── storybook.ts
│ ├── sveltekit.ts
│ ├── tanstack-start.ts
│ ├── unocss.ts
│ ├── vike.ts
│ ├── vite-environment-examples.ts
│ ├── vite-plugin-cloudflare.ts
│ ├── vite-plugin-laravel.ts
│ ├── vite-plugin-pwa.ts
│ ├── vite-plugin-react.ts
│ ├── vite-plugin-rsc.ts
│ ├── vite-plugin-svelte.ts
│ ├── vite-plugin-vue.ts
│ ├── vite-setup-catalogue.ts
│ ├── vitepress.ts
│ ├── vitest.ts
│ ├── vuepress.ts
│ └── waku.ts
├── tsconfig.json
├── types.d.ts
└── utils.ts
================================================
FILE CONTENTS
================================================
================================================
FILE: .editorconfig
================================================
root = true
[*]
end_of_line = lf
insert_final_newline = true
indent_style = tab
indent_size = 2
charset = utf-8
trim_trailing_whitespace = true
[package.json]
indent_style = space
================================================
FILE: .github/renovate.json5
================================================
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:base", "schedule:weekly", "group:allNonMajor"],
"labels": ["dependencies"],
"ignorePaths": [],
"rangeStrategy": "bump",
"packageRules": [
{
"depTypeList": ["peerDependencies", "engines"],
"enabled": false,
},
],
"ignoreDeps": [],
}
================================================
FILE: .github/workflows/ci.yml
================================================
name: CI
env:
# 7 GiB by default on GitHub, setting to 6 GiB
# https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources
NODE_OPTIONS: --max-old-space-size=6144
# configure corepack to be strict but not download newer versions or change anything
COREPACK_DEFAULT_TO_LATEST: 0
COREPACK_ENABLE_AUTO_PIN: 0
COREPACK_ENABLE_STRICT: 1
# see https://turbo.build/repo/docs/telemetry#how-do-i-opt-out
TURBO_TELEMETRY_DISABLED: 1
DO_NOT_TRACK: 1
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
ci:
timeout-minutes: 10
runs-on: ubuntu-latest
permissions:
contents: read # to clone the repo
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: ^24.14.1
- run: corepack enable
- run: pnpm --version
- uses: actions/setup-node@v6
with:
node-version: ^24.14.1
cache: "pnpm"
- name: install
run: pnpm install --frozen-lockfile --prefer-offline
- name: format
run: pnpm format
- name: lint
run: pnpm run lint
- name: typecheck
run: pnpm run typecheck
- name: audit
if: (${{ success() }} || ${{ failure() }})
run: pnpm audit --prod --audit-level moderate
- name: test
if: (${{ success() }} || ${{ failure() }})
run: pnpm test:self
================================================
FILE: .github/workflows/ecosystem-ci-from-pr.yml
================================================
# integration tests for vite ecosystem - run from pr comments
name: vite-ecosystem-ci-from-pr
env:
# 7 GiB by default on GitHub, setting to 6 GiB
# https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources
NODE_OPTIONS: --max-old-space-size=6144
# configure corepack to be strict but not download newer versions or change anything
COREPACK_DEFAULT_TO_LATEST: 0
COREPACK_ENABLE_AUTO_PIN: 0
COREPACK_ENABLE_STRICT: 1
# see https://turbo.build/repo/docs/telemetry#how-do-i-opt-out
TURBO_TELEMETRY_DISABLED: 1
DO_NOT_TRACK: 1
on:
workflow_dispatch:
inputs:
prNumber:
description: "PR number (e.g. 9887)"
required: true
type: string
branchName:
description: "vite branch to use"
required: true
type: string
default: "main"
repo:
description: "vite repository to use"
required: true
type: string
default: "vitejs/vite"
commit:
description: "vite commit sha to use"
type: string
suite:
description: "testsuite to run. runs all testsuits when `-`."
required: false
type: choice
options:
- "-"
- analogjs
- astro
# - histoire # disabled temporarily
- hydrogen
- iles
# - ladle # disabled until its CI is fixed
- laravel
- marko
- nuxt
- nx
- one
- quasar
- qwik
# - rakkas # disabled temporarily
- react-router
# - redwoodjs # disabled temporarily
- storybook
- sveltekit
- tanstack-start
- unocss
- vike
- vite-environment-examples
- vite-plugin-pwa
- vite-plugin-react
- vite-plugin-svelte
- vite-plugin-vue
- vite-plugin-cloudflare
- vite-plugin-rsc
- vite-setup-catalogue
- vitepress
- vitest
- vuepress
- waku
jobs:
init:
runs-on: ubuntu-latest
outputs:
comment-id: ${{ steps.create-comment.outputs.result }}
permissions: {}
steps:
- id: generate-token
uses: actions/create-github-app-token@v3
with:
app-id: ${{ secrets.PR_GITHUB_APP_ID }}
private-key: ${{ secrets.PR_GITHUB_APP_PRIVATE_KEY }}
repositories: vite
- id: create-comment
uses: actions/github-script@v8
with:
github-token: ${{ steps.generate-token.outputs.token }}
result-encoding: string
script: |
const url = `${context.serverUrl}//${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`
const urlLink = `[Open](${url})`
const { data: comment } = await github.rest.issues.createComment({
issue_number: context.payload.inputs.prNumber,
owner: context.repo.owner,
repo: 'vite',
body: `⏳ Triggered ecosystem CI: ${urlLink}`
})
return comment.id
execute-selected-suite:
timeout-minutes: 30
runs-on: ubuntu-latest
needs: init
if: "inputs.suite != '-'"
outputs:
ref: ${{ steps.get-ref.outputs.ref }}
permissions:
contents: read # to clone the repo
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: ^24.14.1
- run: corepack enable
- run: pnpm --version
- run: pnpm i --frozen-lockfile
- run: >-
node ecosystem-ci.ts
--branch "$BRANCH_NAME"
--repo "$REPO"
$(if [ -n "$COMMIT" ] ; then echo "--commit $COMMIT"; fi)
"$SUITE"
env:
BRANCH_NAME: ${{ inputs.branchName }}
REPO: ${{ inputs.repo }}
COMMIT: ${{ inputs.commit }}
SUITE: ${{ inputs.suite }}
- id: get-ref
if: always()
run: |
REF="${REF:-"$(git log -1 --pretty=format:%H)"}"
echo "ref=$REF" >> $GITHUB_OUTPUT
env:
COMMIT: ${{ inputs.commit }}
working-directory: ${{ inputs.commit && '.' || 'workspace/vite' }}
execute-all:
timeout-minutes: 30
runs-on: ubuntu-latest
needs: init
if: "inputs.suite == '-'"
outputs:
ref: ${{ steps.get-ref.outputs.ref }}
permissions:
contents: read # to clone the repo
strategy:
matrix:
suite:
- analogjs
- astro
# - histoire # disabled temporarily
# - hydrogen # disabled until they complete they migration back to Vite
# - iles # disabled until its CI is fixed
# - ladle # disabled until its CI is fixed
- laravel
- marko
- nuxt
# - nx # disabled temporarily
# - one # disabled until we figured out how to support bun
- quasar
- qwik
# - rakkas # disabled temporarily
- react-router
# - redwoodjs # disabled temporarily
- storybook
- sveltekit
- tanstack-start
- unocss
- vike
- vite-environment-examples
- vite-plugin-pwa
- vite-plugin-react
- vite-plugin-svelte
- vite-plugin-vue
- vite-plugin-cloudflare
- vite-plugin-rsc
- vite-setup-catalogue
- vitepress
- vitest
- vuepress
- waku
fail-fast: false
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: ^24.14.1
- run: corepack enable
- run: pnpm --version
- run: pnpm i --frozen-lockfile
- run: >-
node ecosystem-ci.ts
--branch "$BRANCH_NAME"
--repo "$REPO"
$(if [ -n "$COMMIT" ] ; then echo "--commit $COMMIT"; fi)
"$SUITE"
env:
BRANCH_NAME: ${{ inputs.branchName }}
REPO: ${{ inputs.repo }}
COMMIT: ${{ inputs.commit }}
SUITE: ${{ matrix.suite }}
- id: get-ref
if: always()
run: |
REF="${REF:-"$(git log -1 --pretty=format:%H)"}"
echo "ref=$REF" >> $GITHUB_OUTPUT
env:
COMMIT: ${{ inputs.commit }}
working-directory: ${{ inputs.commit && '.' || 'workspace/vite' }}
update-comment:
runs-on: ubuntu-latest
needs: [init, execute-selected-suite, execute-all]
if: always()
permissions: {}
steps:
- id: generate-token
uses: actions/create-github-app-token@v3
with:
app-id: ${{ secrets.PR_GITHUB_APP_ID }}
private-key: ${{ secrets.PR_GITHUB_APP_PRIVATE_KEY }}
repositories: |
vite
vite-ecosystem-ci
- uses: actions/github-script@v8
with:
github-token: ${{ steps.generate-token.outputs.token }}
script: |
const mainRepoName = 'vite'
const ref = process.env.REF
const refLink = `[\`${ref.slice(0, 7)}\`](${context.serverUrl}/${context.repo.owner}/${mainRepoName}/pull/${context.payload.inputs.prNumber}/commits/${ref})`
const { data: { jobs } } = await github.rest.actions.listJobsForWorkflowRun({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: context.runId,
per_page: 100
});
const selectedSuite = context.payload.inputs.suite
let results
if (selectedSuite !== "-") {
const { conclusion, html_url } = jobs.find(job => job.name === "execute-selected-suite")
results = [{ suite: selectedSuite, conclusion, link: html_url }]
} else {
results = jobs
.filter(job => job.name.startsWith('execute-all '))
.map(job => {
const suite = job.name.replace(/^execute-all \(([^)]+)\)$/, "$1")
return { suite, conclusion: job.conclusion, link: job.html_url }
})
}
const url = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`
const urlLink = `[Open](${url})`
const conclusionEmoji = {
success: ":white_check_mark:",
failure: ":x:",
cancelled: ":stop_button:"
}
// check for previous ecosystem-ci runs against the main branch
// first, list workflow runs for ecosystem-ci.yml
const { data: { workflow_runs } } = await github.rest.actions.listWorkflowRuns({
owner: context.repo.owner,
repo: context.repo.repo,
workflow_id: 'ecosystem-ci.yml'
});
// for simplity, we only take the latest completed scheduled run
// otherwise we would have to check the inputs for every maunally triggerred runs, which is an overkill
const latestScheduledRun = workflow_runs.find(run => run.event === "schedule" && run.status === "completed")
// get the jobs for the latest scheduled run
const { data: { jobs: scheduledJobs } } = await github.rest.actions.listJobsForWorkflowRun({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: latestScheduledRun.id
});
const scheduledResults = scheduledJobs
.filter(job => job.name.startsWith('test-ecosystem '))
.map(job => {
const suite = job.name.replace(/^test-ecosystem \(([^)]+)\)$/, "$1")
return { suite, conclusion: job.conclusion, link: job.html_url }
})
const rows = []
const successfulSuitesWithoutChanges = []
results.forEach(current => {
const latest = scheduledResults.find(s => s.suite === current.suite) || {} // in case a new suite is added after latest scheduled
if (current.conclusion === "success" && latest.conclusion === "success") {
successfulSuitesWithoutChanges.push(`[${current.suite}](${current.link})`)
}
else {
const firstColumn = current.suite
const secondColumn = `${conclusionEmoji[current.conclusion]} [${current.conclusion}](${current.link})`
const thirdColumn = `${conclusionEmoji[latest.conclusion]} [${latest.conclusion}](${latest.link})`
rows.push(`| ${firstColumn} | ${secondColumn} | ${thirdColumn} |`)
}
})
let body = `
📝 Ran ecosystem CI on ${refLink}: ${urlLink}
`
if (rows.length > 0) {
body += `| suite | result | [latest scheduled](${latestScheduledRun.html_url}) |
|-------|--------|----------------|
${rows.join("\n")}
${conclusionEmoji.success} ${successfulSuitesWithoutChanges.join(", ")}
`
} else {
body += `${conclusionEmoji.success} ${successfulSuitesWithoutChanges.join(", ")}
`
}
await github.rest.issues.createComment({
issue_number: context.payload.inputs.prNumber,
owner: context.repo.owner,
repo: mainRepoName,
body
})
await github.rest.issues.deleteComment({
owner: context.repo.owner,
repo: mainRepoName,
comment_id: +process.env.COMMENT_ID
})
env:
REF: ${{ needs.execute-all.outputs.ref || needs.execute-selected-suite.outputs.ref }}
COMMENT_ID: ${{ needs.init.outputs.comment-id }}
================================================
FILE: .github/workflows/ecosystem-ci-selected.yml
================================================
# integration tests for vite ecosystem - single run of selected testsuite
name: vite-ecosystem-ci-selected
env:
# 7 GiB by default on GitHub, setting to 6 GiB
# https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources
NODE_OPTIONS: --max-old-space-size=6144
# configure corepack to be strict but not download newer versions or change anything
COREPACK_DEFAULT_TO_LATEST: 0
COREPACK_ENABLE_AUTO_PIN: 0
COREPACK_ENABLE_STRICT: 1
# see https://turbo.build/repo/docs/telemetry#how-do-i-opt-out
TURBO_TELEMETRY_DISABLED: 1
DO_NOT_TRACK: 1
on:
workflow_dispatch:
inputs:
refType:
description: "type of vite ref to use"
required: true
type: choice
options:
- branch
- tag
- commit
- release
default: "branch"
ref:
description: "vite ref to use"
required: true
type: string
default: "main"
repo:
description: "vite repository to use"
required: true
type: string
default: "vitejs/vite"
vite_plugin_react_ref:
description: "vite-plugin-react ref to use"
type: string
vite_plugin_react_repo:
description: "vite-plugin-react repository to use"
type: string
rolldownRef:
description: "rolldown commit sha to use from pkg.pr.new"
type: string
suite:
description: "testsuite to run"
required: true
type: choice
options:
- analogjs
- astro
- histoire
- hydrogen
- iles
- ladle
- laravel
- marko
- nuxt
- nx
# - one # disabled until we figured out how to support bun
- quasar
- qwik
- rakkas
- react-router
- redwoodjs
- storybook
- sveltekit
- tanstack-start
- unocss
- vike
- vite-environment-examples
- vite-plugin-pwa
- vite-plugin-react
- vite-plugin-svelte
- vite-plugin-vue
- vite-plugin-cloudflare
- vite-plugin-rsc
- vite-setup-catalogue
- vitepress
- vitest
- vuepress
- waku
sendDiscordReport:
description: "send results to discord"
type: boolean
jobs:
execute-selected-suite:
timeout-minutes: 30
runs-on: ubuntu-latest
permissions:
contents: read # to clone the repo
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: ^24.14.1
id: setup-node
- run: corepack enable
- run: pnpm --version
- run: pnpm i --frozen-lockfile
- run: >-
node ecosystem-ci.ts
"--$REF_TYPE" "$REF"
--repo "$REPO"
${ROLLDOWN_REF:+--rolldown-ref "$ROLLDOWN_REF"}
"$SUITE"
id: ecosystem-ci-run
env:
REF_TYPE: ${{ inputs.refType }}
REF: ${{ inputs.ref }}
REPO: ${{ inputs.repo }}
ROLLDOWN_REF: ${{ inputs.rolldownRef }}
SUITE: ${{ inputs.suite }}
VITE_PLUGIN_REACT_REF: ${{ inputs.vite_plugin_react_ref }}
VITE_PLUGIN_REACT_REPO: ${{ inputs.vite_plugin_react_repo }}
- if: always() && (inputs.sendDiscordReport || github.event_name != 'workflow_dispatch')
run: node discord-webhook.ts
env:
WORKFLOW_NAME: ci-selected
REF_TYPE: ${{ inputs.refType }}
REF: ${{ inputs.ref }}
REPO: ${{ inputs.repo }}
SUITE: ${{ inputs.suite }}
STATUS: ${{ job.status }}
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
================================================
FILE: .github/workflows/ecosystem-ci.yml
================================================
# integration tests for vite ecosystem projects - scheduled or manual run for all suites
name: vite-ecosystem-ci
env:
# 7 GiB by default on GitHub, setting to 6 GiB
# https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources
NODE_OPTIONS: --max-old-space-size=6144
# configure corepack to be strict but not download newer versions or change anything
COREPACK_DEFAULT_TO_LATEST: 0
COREPACK_ENABLE_AUTO_PIN: 0
COREPACK_ENABLE_STRICT: 1
# see https://turbo.build/repo/docs/telemetry#how-do-i-opt-out
TURBO_TELEMETRY_DISABLED: 1
DO_NOT_TRACK: 1
on:
schedule:
- cron: "0 5 * * 1-5" # Monday-Friday 5AM
workflow_dispatch:
inputs:
refType:
description: "type of ref"
required: true
type: choice
options:
- branch
- tag
- commit
- release
default: "branch"
ref:
description: "vite ref to use"
required: true
type: string
default: "main"
repo:
description: "vite repository to use"
required: true
type: string
default: "vitejs/vite"
rolldownRef:
description: "rolldown commit sha to use from pkg.pr.new"
type: string
sendDiscordReport:
description: "send results to discord"
type: boolean
repository_dispatch:
types: [ecosystem-ci]
jobs:
test-ecosystem:
timeout-minutes: 30
runs-on: ubuntu-latest
strategy:
matrix:
suite:
- analogjs
- astro
# - histoire # disabled temporarily
# - hydrogen # disabled until they complete they migration back to Vite
# - iles # disabled until its CI is fixed
# - ladle # disabled until its CI is fixed
- laravel
- marko
- nuxt
# - one # disabled until we figured out how to support bun
# - nx # disabled temporarily
- quasar
- qwik
# - rakkas # disabled temporarily
- react-router
# - redwoodjs # disabled temporarily
- storybook
- sveltekit
- tanstack-start
- unocss
- vike
- vite-environment-examples
- vite-plugin-pwa
- vite-plugin-react
- vite-plugin-svelte
- vite-plugin-vue
- vite-plugin-cloudflare
- vite-plugin-rsc
- vite-setup-catalogue
- vitepress
- vitest
- vuepress
- waku
fail-fast: false
permissions:
contents: read # to clone the repo
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: ^24.14.1
id: setup-node
- run: corepack enable
- run: pnpm --version
- run: pnpm i --frozen-lockfile
- run: >-
node ecosystem-ci.ts
"--$REF_TYPE" "$REF"
--repo "$REPO"
${ROLLDOWN_REF:+--rolldown-ref "$ROLLDOWN_REF"}
"$SUITE"
id: ecosystem-ci-run
env:
REF_TYPE: ${{ inputs.refType || github.event.client_payload.refType || 'branch' }}
REF: ${{ inputs.ref || github.event.client_payload.ref || 'main' }}
REPO: ${{ inputs.repo || github.event.client_payload.repo || 'vitejs/vite' }}
ROLLDOWN_REF: ${{ inputs.rolldownRef || github.event.client_payload.rolldownRef }}
SUITE: ${{ matrix.suite }}
- if: always() && (inputs.sendDiscordReport || github.event_name != 'workflow_dispatch')
run: node discord-webhook.ts
env:
WORKFLOW_NAME: ci
REF_TYPE: ${{ inputs.refType || github.event.client_payload.refType || 'branch' }}
REF: ${{ inputs.ref || github.event.client_payload.ref || 'main' }}
REPO: ${{ inputs.repo || github.event.client_payload.repo || 'vitejs/vite' }}
SUITE: ${{ matrix.suite }}
STATUS: ${{ job.status }}
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
IS_ROLLDOWN_VITE: "1"
================================================
FILE: .gitignore
================================================
.DS_Store
.DS_Store?
node_modules
vite
workspace
.pnpm-debug.log
.idea
.eslintcache
================================================
FILE: .prettierrc.json
================================================
{
"useTabs": true,
"semi": false,
"tabWidth": 2,
"singleQuote": true,
"printWidth": 80,
"trailingComma": "all",
"overrides": [
{
"files": ["*.json5"],
"options": {
"singleQuote": false,
"quoteProps": "preserve"
}
},
{
"files": ["*.yml"],
"options": {
"singleQuote": false
}
},
{
"files": "**/pnpm-lock.yaml",
"options": {
"requirePragma": true
}
},
{
"files": "**/package.json",
"options": {
"useTabs": false,
"tabWidth": 2
}
}
]
}
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2021-present, Vite contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# vite-ecosystem-ci
This repository is used to run integration tests for vite ecosystem projects
## via github workflow
### scheduled
Workflows are scheduled to run automatically every Monday, Wednesday and Friday
### manually
- open [workflow](../../actions/workflows/ecosystem-ci-selected.yml)
- click 'Run workflow' button on top right of the list
- select suite to run in dropdown
- start workflow
## via shell script
- clone this repo
- run `pnpm i`
- run `pnpm test` to run all suites
- or `pnpm test <suitename>` to select a suite
You can pass `--tag v2.8.0-beta.1`, `--branch somebranch` or `--commit abcd1234` option to select a specific vite version to build.
If you pass `--release 2.7.13`, vite build will be skipped and vite is fetched from the registry instead
The repositories are checked out into `workspace` subdirectory as shallow clones
## via comment on PR
- comment `/ecosystem-ci run` on a PR
- or `/ecosystem-ci run <suitename>` to select a suite
Users with triage permission to vitejs/vite repository can only use this.
See [docs/pr-comment-setup.md](./docs/pr-comment-setup.md) for how to setup this feature.
# how to add a new integration test
- check out the existing [tests](./tests) and add one yourself. Thanks to some utilities it is really easy
- once you are confident the suite works, add it to the lists of suites in the [workflows](../../actions/)
# reporting results
## Discord
Results are posted automatically to `#ecosystem-ci` on [vite discord](https://chat.vitejs.dev/)
### on your own server
- Go to `Server settings > Integrations > Webhooks` and click `New Webhook`
- Give it a name, icon and a channel to post to
- copy the webhook url
- get in touch with admins of this repo so they can add the webhook
#### how to add a discord webhook here
- Go to `<github repo>/settings/secrets/actions` and click on `New repository secret`
- set `Name` as `DISCORD_WEBHOOK_URL`
- paste the discord webhook url you copied from above into `Value`
- Click `Add secret`
================================================
FILE: builds/vite-plugin-react.ts
================================================
import { runInRepo } from '../utils.ts'
import type { RunOptions } from '../types.d.ts'
export async function build(options: RunOptions) {
return runInRepo({
...options,
repo: process.env.VITE_PLUGIN_REACT_REPO || 'vitejs/vite-plugin-react',
branch: process.env.VITE_PLUGIN_REACT_REF || 'main',
build: 'build',
})
}
export const packages = {
'@vitejs/plugin-react': 'packages/plugin-react',
'@vitejs/plugin-react-swc': 'packages/plugin-react-swc/dist',
'@vitejs/plugin-rsc': 'packages/plugin-rsc',
}
================================================
FILE: builds/vite-plugin-svelte.ts
================================================
import { runInRepo } from '../utils.ts'
import type { RunOptions } from '../types.d.ts'
export async function build(options: RunOptions) {
return runInRepo({
...options,
repo: 'sveltejs/vite-plugin-svelte',
branch: options.viteMajor < 8 ? 'v6' : 'main',
overrides: {
svelte: 'latest',
},
})
}
export const packages = {
'@sveltejs/vite-plugin-svelte': 'packages/vite-plugin-svelte',
'@sveltejs/vite-plugin-svelte-inspector':
'packages/vite-plugin-svelte-inspector',
}
================================================
FILE: builds/vite-plugin-vue.ts
================================================
import { runInRepo } from '../utils.ts'
import type { RunOptions } from '../types.d.ts'
export async function build(options: RunOptions) {
return runInRepo({
...options,
repo: 'vitejs/vite-plugin-vue',
build: 'build',
})
}
export const packages = {
'@vitejs/plugin-vue': 'packages/plugin-vue',
'@vitejs/plugin-vue-jsx': 'packages/plugin-vue-jsx',
}
================================================
FILE: discord-webhook.ts
================================================
import { getPermanentRef, setupEnvironment } from './utils.ts'
type RefType = 'branch' | 'tag' | 'commit' | 'release'
type Status = 'success' | 'failure' | 'cancelled'
type Env = {
WORKFLOW_NAME?: string
REF_TYPE?: RefType
REF?: string
REPO?: string
SUITE?: string
STATUS?: Status
DISCORD_WEBHOOK_URL?: string
IS_ROLLDOWN_VITE?: '1'
}
const statusConfig = {
success: {
color: parseInt('57ab5a', 16),
emoji: ':white_check_mark:',
},
expectedFailure: {
color: parseInt('c69026', 16),
emoji: ':construction:',
},
failure: {
color: parseInt('e5534b', 16),
emoji: ':x:',
},
cancelled: {
color: parseInt('768390', 16),
emoji: ':stop_button:',
},
}
async function run() {
if (!process.env.GITHUB_ACTIONS) {
throw new Error('This script can only run on GitHub Actions.')
}
if (!process.env.DISCORD_WEBHOOK_URL) {
console.warn(
"Skipped beacuse process.env.DISCORD_WEBHOOK_URL was empty or didn't exist",
)
return
}
if (!process.env.GITHUB_TOKEN) {
console.warn(
"Not using a token because process.env.GITHUB_TOKEN was empty or didn't exist",
)
}
const env = process.env as Env
assertEnv('WORKFLOW_NAME', env.WORKFLOW_NAME)
assertEnv('REF_TYPE', env.REF_TYPE)
assertEnv('REF', env.REF)
assertEnv('REPO', env.REPO)
assertEnv('SUITE', env.SUITE)
assertEnv('STATUS', env.STATUS)
assertEnv('DISCORD_WEBHOOK_URL', env.DISCORD_WEBHOOK_URL)
const isRolldownVite = !!env.IS_ROLLDOWN_VITE
const expectedFailureReason = isRolldownVite
? await loadExpectedFailureReason(env.SUITE)
: undefined
const status =
env.STATUS === 'failure' && expectedFailureReason
? 'expectedFailure'
: env.STATUS
await setupEnvironment()
const refType = env.REF_TYPE
// vite repo is not cloned when release
const permRef = refType === 'release' ? undefined : await getPermanentRef()
const targetText = createTargetText(refType, env.REF, permRef, env.REPO)
const webhookContent = {
username: `vite-ecosystem-ci (${env.WORKFLOW_NAME})`,
avatar_url: 'https://github.com/vitejs.png',
embeds: [
{
title: `${statusConfig[status].emoji} ${env.SUITE}`,
description: await createDescription(
env.SUITE,
targetText,
expectedFailureReason,
),
color: statusConfig[status].color,
},
],
}
const res = await fetch(env.DISCORD_WEBHOOK_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(webhookContent),
})
if (res.ok) {
console.log('Sent Webhook')
} else {
console.error(`Webhook failed ${res.status}:`, await res.text())
}
}
function assertEnv<T>(
name: string,
value: T,
): asserts value is Exclude<T, undefined> {
if (!value) {
throw new Error(`process.env.${name} is empty or does not exist.`)
}
}
async function loadExpectedFailureReason(suite: string) {
const module = await import(`./tests/${suite}.ts`)
const reason: string | undefined = module.rolldownViteExpectedFailureReason
return reason?.trim()
}
async function createRunUrl(suite: string) {
const result = await fetchJobs()
if (!result) {
return undefined
}
if (result.total_count <= 0) {
console.warn('total_count was 0')
return undefined
}
const job = result.jobs.find((job) => job.name === process.env.GITHUB_JOB)
if (job) {
return job.html_url
}
// when matrix
const jobM = result.jobs.find(
(job) => job.name === `${process.env.GITHUB_JOB} (${suite})`,
)
return jobM?.html_url
}
interface GitHubActionsJob {
name: string
html_url: string
}
async function fetchJobs() {
const url = `${process.env.GITHUB_API_URL}/repos/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}/jobs`
const res = await fetch(url, {
headers: {
Accept: 'application/vnd.github.v3+json',
...(process.env.GITHUB_TOKEN
? {
Authorization: `token ${process.env.GITHUB_TOKEN}`,
}
: undefined),
},
})
if (!res.ok) {
console.warn(
`Failed to fetch jobs (${res.status} ${res.statusText}): ${res.text()}`,
)
return null
}
const result = await res.json()
return result as {
total_count: number
jobs: GitHubActionsJob[]
}
}
async function createDescription(
suite: string,
targetText: string,
expectedFailureReason: string | undefined,
) {
const runUrl = await createRunUrl(suite)
const open = runUrl === undefined ? 'Null' : `[Open](${runUrl})`
let message = `
:scroll:\u00a0\u00a0${open}\u3000\u3000:zap:\u00a0\u00a0${targetText}
`.trim()
if (expectedFailureReason) {
message +=
'\n' +
`
:bulb:\u00a0\u00a0${expectedFailureReason}
`.trim()
}
return message
}
function createTargetText(
refType: RefType,
ref: string,
permRef: string | undefined,
repo: string,
) {
const repoText = repo !== 'vitejs/vite' ? `${repo}:` : ''
if (refType === 'branch') {
const shortRef = permRef?.slice(0, 7)
const link = `https://github.com/${repo}/commits/${permRef || ref}`
return `[${repoText}${ref} (${shortRef || 'unknown'})](${link})`
}
const refTypeText = refType === 'release' ? ' (release)' : ''
const link = `https://github.com/${repo}/commits/${ref}`
return `[${repoText}${ref}${refTypeText}](${link})`
}
run().catch((e) => {
console.error('Error sending webhook:', e)
})
================================================
FILE: docs/pr-comment-setup.md
================================================
# Setting up "PR comment trigger" feature
## (1) Create a GitHub App
1. [Create a GitHub App](https://docs.github.com/en/apps/creating-github-apps/registering-a-github-app/registering-a-github-app). Webhook is not needed. The following permissions are required:
- Metadata: Read only
- Actions: Read and Write
- Issues: Read and Write
- Pull requests: Read and Write
1. Install that App to the organization/user. Give that App access to vitejs/vite and vitejs/vite-ecosystem-ci.
1. Check the App ID. It's written on `https://github.com/settings/apps/<github-app-name-slug>`. This is used later.

1. Generate a private key. It can be generated on the same page with the App ID. The key will be downloaded when you generate it.

## (2) Adding secrets to vitejs/vite and vitejs/vite-ecosystem-ci
- vitejs/vite
- `ECOSYSTEM_CI_GITHUB_APP_ID`: ID of the created GitHub App
- `ECOSYSTEM_CI_GITHUB_APP_PRIVATE_KEY`: the content of the private key of the created GitHub App
- vitejs/vite-ecosystem-ci
- `PR_GITHUB_APP_ID`: ID of the created GitHub App
- `PR_GITHUB_APP_PRIVATE_KEY`: the content of the private key of the created GitHub App
## (3) Adding workflows to vitejs/vite
Add [this workflow](https://github.com/vitejs/vite/blob/main/.github/workflows/ecosystem-ci-trigger.yml).
================================================
FILE: ecosystem-ci.ts
================================================
import fs from 'fs'
import path from 'path'
import process from 'process'
import { cac } from 'cac'
import {
setupEnvironment,
setupViteRepo,
buildVite,
bisectVite,
parseViteMajor,
parseMajorVersion,
} from './utils.ts'
import type { CommandOptions, RunOptions } from './types.d.ts'
const cli = cac()
cli
.command('[...suites]', 'build vite and run selected suites')
.option('--verify', 'verify checkouts by running tests', { default: false })
.option('--repo <repo>', 'vite repository to use', { default: 'vitejs/vite' })
.option('--branch <branch>', 'vite branch to use', { default: 'main' })
.option('--tag <tag>', 'vite tag to use')
.option('--commit <commit>', 'vite commit sha to use')
.option('--release <version>', 'vite release to use from npm registry')
.option(
'--rolldown-ref <commit>',
'rolldown commit sha to use from pkg.pr.new',
)
.action(async (suites, options: CommandOptions) => {
if (options.commit) {
const url = `https://pkg.pr.new/vite@${options.commit}`
const { status } = await fetch(url)
if (status === 200) {
options.release = url
delete options.commit
console.log(`continuous release available on ${url}`)
}
}
let rolldownRelease: string | undefined
if (options.rolldownRef) {
const url = `https://pkg.pr.new/rolldown@${options.rolldownRef}`
const { status } = await fetch(url)
if (status === 200) {
rolldownRelease = url
console.log(`rolldown continuous release available on ${url}`)
} else {
throw new Error(
`rolldown continuous release not found for ref ${options.rolldownRef} (HTTP ${status}): ${url}`,
)
}
}
const { root, vitePath, workspace } = await setupEnvironment()
const suitesToRun = getSuitesToRun(suites, root)
let viteMajor
if (!options.release) {
await setupViteRepo(options)
await buildVite({ verify: options.verify, rolldownRelease })
viteMajor = parseViteMajor(vitePath)
} else {
viteMajor = parseMajorVersion(options.release)
}
const runOptions: RunOptions = {
root,
vitePath,
viteMajor,
workspace,
release: options.release,
rolldownRelease,
verify: options.verify,
skipGit: false,
}
for (const suite of suitesToRun) {
await run(suite, runOptions)
}
})
cli
.command('build-vite', 'build vite only')
.option('--verify', 'verify vite checkout by running tests', {
default: false,
})
.option('--repo <repo>', 'vite repository to use', { default: 'vitejs/vite' })
.option('--branch <branch>', 'vite branch to use', { default: 'main' })
.option('--tag <tag>', 'vite tag to use')
.option('--commit <commit>', 'vite commit sha to use')
.action(async (options: CommandOptions) => {
await setupEnvironment()
await setupViteRepo(options)
await buildVite({ verify: options.verify })
})
cli
.command('run-suites [...suites]', 'run single suite with pre-built vite')
.option(
'--verify',
'verify checkout by running tests before using local vite',
{ default: false },
)
.option('--repo <repo>', 'vite repository to use', { default: 'vitejs/vite' })
.option('--release <version>', 'vite release to use from npm registry')
.action(async (suites, options: CommandOptions) => {
const { root, vitePath, workspace } = await setupEnvironment()
const suitesToRun = getSuitesToRun(suites, root)
const runOptions: RunOptions = {
...options,
root,
vitePath,
viteMajor: parseViteMajor(vitePath),
workspace,
}
for (const suite of suitesToRun) {
await run(suite, runOptions)
}
})
cli
.command(
'bisect [...suites]',
'use git bisect to find a commit in vite that broke suites',
)
.option('--good <ref>', 'last known good ref, e.g. a previous tag. REQUIRED!')
.option('--verify', 'verify checkouts by running tests', { default: false })
.option('--repo <repo>', 'vite repository to use', { default: 'vitejs/vite' })
.option('--branch <branch>', 'vite branch to use', { default: 'main' })
.option('--tag <tag>', 'vite tag to use')
.option('--commit <commit>', 'vite commit sha to use')
.action(async (suites, options: CommandOptions & { good: string }) => {
if (!options.good) {
console.log(
'you have to specify a known good version with `--good <commit|tag>`',
)
process.exit(1)
}
const { root, vitePath, workspace } = await setupEnvironment()
const suitesToRun = getSuitesToRun(suites, root)
let isFirstRun = true
const { verify } = options
const runSuite = async () => {
try {
await buildVite({ verify: isFirstRun && verify })
for (const suite of suitesToRun) {
await run(suite, {
verify: !!(isFirstRun && verify),
skipGit: !isFirstRun,
root,
vitePath,
viteMajor: parseViteMajor(vitePath),
workspace,
})
}
isFirstRun = false
return null
} catch (e) {
return e
}
}
await setupViteRepo({ ...options, shallow: false })
const initialError = await runSuite()
if (initialError) {
await bisectVite(options.good, runSuite)
} else {
console.log(`no errors for starting commit, cannot bisect`)
}
})
cli.help()
cli.parse()
async function run(suite: string, options: RunOptions) {
const { test } = await import(`./tests/${suite}.ts`)
await test({
...options,
workspace: path.resolve(options.workspace, suite),
})
}
function getSuitesToRun(suites: string[], root: string) {
let suitesToRun: string[] = suites
const availableSuites: string[] = fs
.readdirSync(path.join(root, 'tests'))
.filter((f: string) => !f.startsWith('_') && f.endsWith('.ts'))
.map((f: string) => f.slice(0, -3))
availableSuites.sort()
if (suitesToRun.length === 0) {
suitesToRun = availableSuites
} else {
const invalidSuites = suitesToRun.filter(
(x) => !x.startsWith('_') && !availableSuites.includes(x),
)
if (invalidSuites.length) {
console.log(`invalid suite(s): ${invalidSuites.join(', ')}`)
console.log(`available suites: ${availableSuites.join(', ')}`)
process.exit(1)
}
}
return suitesToRun
}
================================================
FILE: eslint.config.js
================================================
// @ts-check
import eslint from '@eslint/js'
import n from 'eslint-plugin-n'
import tseslint from 'typescript-eslint'
import prettierConfig from 'eslint-config-prettier/flat'
export default tseslint.config([
{
name: 'local/ignores',
ignores: ['workspace/**'],
},
eslint.configs.recommended,
tseslint.configs.recommended,
n.configs['flat/recommended-module'],
prettierConfig,
{
name: 'local/rules',
files: ['**/*.{js,ts}'],
rules: {
eqeqeq: ['warn', 'always', { null: 'never' }],
'no-debugger': ['error'],
'no-empty': ['warn', { allowEmptyCatch: true }],
'no-process-exit': 'off',
'no-useless-escape': 'off',
'prefer-const': [
'warn',
{
destructuring: 'all',
},
],
'n/no-process-exit': 'off',
'@typescript-eslint/no-explicit-any': 'off', // we use any in some places
},
},
])
================================================
FILE: package.json
================================================
{
"name": "vite-ecosystem-ci",
"private": true,
"version": "0.0.1",
"description": "Vite Ecosystem CI",
"scripts": {
"prepare": "pnpm exec simple-git-hooks",
"lint": "eslint --cache '**/*.{js,ts}'",
"lint:fix": "pnpm lint --fix",
"typecheck": "tsc",
"format": "prettier --ignore-path .gitignore --check .",
"format:fix": "pnpm format --write",
"test:self": "node ecosystem-ci.ts _selftest",
"test": "node ecosystem-ci.ts",
"bisect": "node ecosystem-ci.ts bisect"
},
"simple-git-hooks": {
"pre-commit": "pnpm exec lint-staged --concurrent false"
},
"lint-staged": {
"*": [
"prettier --write --ignore-unknown"
],
"*.{js,ts}": [
"eslint --fix"
]
},
"packageManager": "pnpm@10.32.1",
"type": "module",
"engines": {
"node": ">=24",
"pnpm": "^10.0.0"
},
"repository": {
"type": "git",
"url": "git+https://github.com/vitejs/vite-ecosystem-ci.git"
},
"license": "MIT",
"bugs": {
"url": "https://github.com/vitejs/vite-ecosystem-ci/issues"
},
"homepage": "https://github.com/vitejs/vite-ecosystem-ci#readme",
"dependencies": {
"@actions/core": "^3.0.0",
"cac": "^7.0.0",
"execa": "^9.6.1"
},
"devDependencies": {
"@antfu/ni": "^30.0.0",
"@eslint/js": "^10.0.1",
"@types/node": "^25.5.0",
"@types/pacote": "^11.1.8",
"@types/semver": "^7.7.1",
"eslint": "^10.1.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-n": "^17.24.0",
"lint-staged": "^16.4.0",
"pacote": "^21.5.0",
"prettier": "^3.8.1",
"semver": "^7.7.4",
"simple-git-hooks": "^2.13.1",
"typescript": "^5.9.3",
"typescript-eslint": "^8.57.2"
}
}
================================================
FILE: pnpm-workspace.yaml
================================================
engineStrict: true
strictPeerDependencies: false
packageManagerStrict: false
onlyBuiltDependencies:
- esbuild
- simple-git-hooks
overrides:
'cross-spawn@>=7.0.0 <7.0.5': '^7.0.6'
================================================
FILE: tests/_selftest.ts
================================================
import path from 'path'
import fs from 'fs'
import { runInRepo } from '../utils.ts'
import type { RunOptions } from '../types.d.ts'
export async function test(options: RunOptions) {
await runInRepo({
...options,
repo: 'vitejs/vite-ecosystem-ci',
build: async () => {
const dir = path.resolve(options.workspace, 'vite-ecosystem-ci')
const pkgFile = path.join(dir, 'package.json')
const pkg = JSON.parse(await fs.promises.readFile(pkgFile, 'utf-8'))
if (pkg.name !== 'vite-ecosystem-ci') {
throw new Error(
`invalid checkout, expected package.json with "name":"vite-ecosystem-ci" in ${dir}`,
)
}
pkg.scripts.selftestscript =
"[ -d ../../vite/packages/vite/dist ] || (echo 'vite build failed' && exit 1)"
await fs.promises.writeFile(
pkgFile,
JSON.stringify(pkg, null, 2),
'utf-8',
)
},
test: 'pnpm run selftestscript',
verify: false,
})
}
================================================
FILE: tests/analogjs.ts
================================================
import { runInRepo } from '../utils.ts'
import type { RunOptions } from '../types.d.ts'
export async function test(options: RunOptions) {
await runInRepo({
...options,
repo: 'analogjs/analog',
branch: 'beta',
build: 'build:vite-ci',
beforeTest: 'pnpm playwright install chromium',
test: 'test:vite-ci',
})
}
================================================
FILE: tests/astro.ts
================================================
import { runInRepo } from '../utils.ts'
import type { RunOptions } from '../types.d.ts'
export async function test(options: RunOptions) {
await runInRepo({
...options,
repo: 'withastro/astro',
branch: 'main',
build: 'build:ci',
test: 'test:vite-ci',
})
}
================================================
FILE: tests/histoire.ts
================================================
import { runInRepo } from '../utils.ts'
import type { RunOptions } from '../types.d.ts'
export async function test(options: RunOptions) {
await runInRepo({
...options,
repo: 'histoire-dev/histoire',
branch: 'main',
build: 'build',
test: ['test', 'test:examples'],
})
}
export const rolldownViteExpectedFailureReason = `
needs to be updated on histoire side (manualChunks)
`
================================================
FILE: tests/hydrogen.ts
================================================
import { runInRepo } from '../utils.ts'
import type { RunOptions } from '../types.d.ts'
export async function test(options: RunOptions) {
await runInRepo({
...options,
repo: 'Shopify/hydrogen',
build: 'build',
test: 'test:vite-ci',
})
}
================================================
FILE: tests/iles.ts
================================================
import { runInRepo, $ } from '../utils.ts'
import type { RunOptions } from '../types.d.ts'
export async function test(options: RunOptions) {
await runInRepo({
...options,
repo: 'ElMassimo/iles',
overrides: {
'@vitejs/plugin-vue': true,
},
beforeInstall: async () => $`git lfs install && git lfs pull`,
build: 'build:all',
test: 'test',
})
}
================================================
FILE: tests/ladle.ts
================================================
import { runInRepo } from '../utils.ts'
import type { RunOptions } from '../types.d.ts'
export async function test(options: RunOptions) {
await runInRepo({
...options,
repo: 'tajo/ladle',
branch: 'main',
build: 'build',
beforeTest: 'pnpm playwright install chromium',
test: 'test',
})
}
================================================
FILE: tests/laravel.ts
================================================
import { runInRepo } from '../utils.ts'
import type { RunOptions } from '../types.d.ts'
import path from 'node:path'
import fs from 'node:fs'
export async function test(options: RunOptions) {
//see https://github.com/laravel/vite-plugin/blob/73466441b0c9eb0c1a5ce0a0e937bd83eaef4b70/.github/workflows/tests.yml#L10
process.env.LARAVEL_BYPASS_ENV_CHECK = '1'
await runInRepo({
...options,
repo: 'laravel/vite-plugin',
branch: '2.x',
build: 'build',
async beforeTest() {
// Add `vitest.config.ts` to exclude Vite from inlined by Vitest.
// Otherwise the mock here doesn't work.
// https://github.com/laravel/vite-plugin/blob/3f7bf9eddc69580796c26890c99065d7259c785e/tests/index.test.ts#L7-L22
const dir = path.resolve(options.workspace, 'vite-plugin')
const vitestConfigFile = path.join(dir, 'vitest.config.ts')
fs.writeFileSync(
vitestConfigFile,
getVitestConfig(options.vitePath),
'utf-8',
)
},
test: 'test',
agent: 'npm',
})
}
const getVitestConfig = (viteRepoPath: string) => /* ts */ `
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
deps: {
moduleDirectories: [
${JSON.stringify(path.resolve(viteRepoPath, 'packages'))},
],
},
},
});
`
================================================
FILE: tests/marko.ts
================================================
import { runInRepo } from '../utils.ts'
import type { RunOptions } from '../types.d.ts'
export async function test(options: RunOptions) {
await runInRepo({
...options,
repo: 'marko-js/vite',
dir: 'marko', // default is last segment of repo, which would be vite and confusing
build: 'build',
beforeTest: 'pnpm playwright install chromium',
test: 'test',
overrides: {
esbuild: true,
},
})
}
================================================
FILE: tests/nuxt.ts
================================================
import { runInRepo } from '../utils.ts'
import type { RunOptions } from '../types.d.ts'
export async function test(options: RunOptions) {
await runInRepo({
...options,
repo: 'nuxt/nuxt',
overrides: {
'@vitejs/plugin-vue': true,
},
build: ['dev:prepare', 'build'],
beforeTest: 'pnpm playwright-core install',
test: ['test:fixtures', 'test:fixtures:dev', 'test:types'],
})
}
================================================
FILE: tests/nx.ts
================================================
import { runInRepo } from '../utils.ts'
import type { RunOptions } from '../types.d.ts'
export async function test(options: RunOptions) {
await runInRepo({
...options,
repo: 'nrwl/nx',
branch: 'master',
build: { script: 'build-project', args: ['vite', '--skip-nx-cache'] },
test: [
{ script: 'test', args: ['vite', '--skip-nx-cache'] },
{ script: 'e2e', args: ['e2e-vite', '--skip-nx-cache'] },
],
overrides: {
rollup: false,
},
})
}
================================================
FILE: tests/one.ts
================================================
import type { RunOptions } from '../types.d.ts'
import { runInRepo } from '../utils.ts'
export async function test(options: RunOptions) {
await runInRepo({
...options,
repo: 'onejs/one',
branch: 'main',
build: ['clean:build', 'build'],
beforeTest: 'yarn playwright install chromium',
test: 'test:vite-ecosystem-ci',
})
}
export const rolldownViteExpectedFailureReason = `
needs to be updated on one side (type incompatibility)
`
================================================
FILE: tests/quasar.ts
================================================
import { runInRepo } from '../utils.ts'
import type { RunOptions } from '../types.d.ts'
export async function test(options: RunOptions) {
await runInRepo({
...options,
repo: 'quasarframework/quasar',
branch: 'dev',
overrides: {
'@vitejs/plugin-vue': true,
},
build: 'vite-ecosystem-ci:build',
test: 'vite-ecosystem-ci:test',
})
}
================================================
FILE: tests/qwik.ts
================================================
import { runInRepo } from '../utils.ts'
import type { RunOptions } from '../types.d.ts'
export async function test(options: RunOptions) {
await runInRepo({
...options,
repo: 'BuilderIO/qwik',
build: 'build.vite',
beforeTest: 'pnpm playwright install chromium',
test: 'test.vite',
})
}
================================================
FILE: tests/rakkas.ts
================================================
import { runInRepo } from '../utils.ts'
import type { RunOptions } from '../types.d.ts'
import { execSync } from 'node:child_process'
export async function test(options: RunOptions) {
await runInRepo({
...options,
repo: 'rakkasjs/rakkasjs',
branch: 'main',
build: 'build',
// This is needed to run puppeteer in Ubuntu 23+
// https://github.com/puppeteer/puppeteer/pull/13196
beforeTest: [
process.env.GITHUB_ACTIONS
? async () => {
execSync(
'echo 0 | sudo tee /proc/sys/kernel/apparmor_restrict_unprivileged_userns',
)
}
: null,
'pnpm --dir testbed/examples exec puppeteer browsers install chrome',
].filter((x) => x != null),
test: 'vite-ecosystem-ci',
})
}
export const rolldownViteExpectedFailureReason = `
needs to be updated on rakkas side ("moduleResolution" should be "bundler" or "nodenext")
`
================================================
FILE: tests/react-router.ts
================================================
import { runInRepo } from '../utils.ts'
import type { RunOptions } from '../types.d.ts'
export async function test(options: RunOptions) {
await runInRepo({
...options,
repo: 'remix-run/react-router',
branch: 'dev',
build: 'vite-ecosystem-ci:build',
beforeTest: 'vite-ecosystem-ci:before-test',
test: 'vite-ecosystem-ci:test',
overrides: {
'@vitejs/plugin-rsc': true,
},
})
}
export const rolldownViteExpectedFailureReason = `
needs to be updated on react-router side (incorrect tests)
`
================================================
FILE: tests/redwoodjs.ts
================================================
import { runInRepo } from '../utils.ts'
import type { RunOptions } from '../types.d.ts'
export async function test(options: RunOptions) {
await runInRepo({
...options,
repo: 'redwoodjs/redwood',
build: { script: 'build', args: ['--skip-nx-cache'] },
test: { script: 'test-ci', args: ['--skip-nx-cache'] },
})
}
================================================
FILE: tests/storybook.ts
================================================
import { runInRepo } from '../utils.ts'
import type { RunOptions } from '../types.d.ts'
export async function test(options: RunOptions) {
await runInRepo({
...options,
repo: 'storybookjs/storybook',
branch: 'next',
build: 'vite-ecosystem-ci:build',
beforeTest: 'vite-ecosystem-ci:before-test',
test: 'vite-ecosystem-ci:test',
})
}
================================================
FILE: tests/sveltekit.ts
================================================
import { runInRepo } from '../utils.ts'
import type { RunOptions } from '../types.d.ts'
export async function test(options: RunOptions) {
await runInRepo({
...options,
repo: 'sveltejs/kit',
branch: 'main',
overrides: {
svelte: 'latest',
'@sveltejs/vite-plugin-svelte': true,
'@sveltejs/vite-plugin-svelte-inspector': true,
},
beforeTest: 'pnpm playwright install chromium',
test: [
'test:vite-ecosystem-ci',
'pnpm --dir packages/kit check', // only run checks for kit package, not the whole repo
],
})
}
================================================
FILE: tests/tanstack-start.ts
================================================
import { runInRepo } from '../utils.ts'
import type { RunOptions } from '../types.d.ts'
export async function test(options: RunOptions) {
await runInRepo({
...options,
repo: 'TanStack/router',
branch: 'main',
build: 'vite-ecosystem-ci:build',
beforeTest: 'vite-ecosystem-ci:before-test',
test: 'vite-ecosystem-ci:test',
})
}
================================================
FILE: tests/unocss.ts
================================================
import { runInRepo } from '../utils.ts'
import type { RunOptions } from '../types.d.ts'
export async function test(options: RunOptions) {
await runInRepo({
...options,
repo: 'unocss/unocss',
build: 'build',
test: 'test',
})
}
================================================
FILE: tests/vike.ts
================================================
import { runInRepo } from '../utils.ts'
import type { RunOptions } from '../types.d.ts'
export async function test(options: RunOptions) {
await runInRepo({
...options,
repo: 'vikejs/vike',
branch: 'main',
overrides: {
'@vitejs/plugin-react': true,
'@vitejs/plugin-react-swc': true,
'@vitejs/plugin-vue': true,
},
build: 'build',
beforeTest: 'pnpm exec playwright install chromium',
test: 'test:vite-ecosystem-ci',
})
}
================================================
FILE: tests/vite-environment-examples.ts
================================================
import { runInRepo } from '../utils.ts'
import type { RunOptions } from '../types.d.ts'
export async function test(options: RunOptions) {
if (options.viteMajor < 6) {
return
}
await runInRepo({
...options,
repo: 'hi-ogawa/vite-environment-examples',
branch: 'main',
build: 'vite-ecosystem-ci:build',
beforeTest: 'vite-ecosystem-ci:before-test',
test: 'vite-ecosystem-ci:test',
})
}
================================================
FILE: tests/vite-plugin-cloudflare.ts
================================================
import { runInRepo } from '../utils.ts'
import type { RunOptions } from '../types.d.ts'
export async function test(options: RunOptions) {
await runInRepo({
...options,
repo: 'cloudflare/workers-sdk',
test: 'pnpm test:ci -F @vite-plugin-cloudflare/playground',
})
}
================================================
FILE: tests/vite-plugin-laravel.ts
================================================
import { runInRepo } from '../utils.ts'
import type { RunOptions } from '../types.d.ts'
export async function test(options: RunOptions) {
await runInRepo({
...options,
repo: 'innocenzi/laravel-vite',
build: 'build',
test: 'test:vite',
})
}
================================================
FILE: tests/vite-plugin-pwa.ts
================================================
import { runInRepo } from '../utils.ts'
import type { RunOptions } from '../types.d.ts'
export async function test(options: RunOptions) {
await runInRepo({
...options,
repo: 'vite-pwa/vite-plugin-pwa',
branch: 'main',
beforeTest: 'pnpm playwright install chromium',
build: 'build',
test: 'test:vite-ecosystem-ci',
})
}
================================================
FILE: tests/vite-plugin-react.ts
================================================
import { runInRepo } from '../utils.ts'
import type { RunOptions } from '../types.d.ts'
export async function test(options: RunOptions) {
await runInRepo({
...options,
repo: 'vitejs/vite-plugin-react',
build: 'build',
beforeTest: 'pnpm playwright install chromium',
test: ['test', 'typecheck'],
})
}
================================================
FILE: tests/vite-plugin-rsc.ts
================================================
import { runInRepo } from '../utils.ts'
import type { RunOptions } from '../types.d.ts'
export async function test(options: RunOptions) {
await runInRepo({
...options,
repo: 'vitejs/vite-plugin-react',
build: 'build',
beforeTest: 'pnpm playwright install chromium',
test: [
'pnpm -C packages/plugin-rsc test-e2e',
'pnpm -C packages/plugin-rsc tsc',
],
})
}
================================================
FILE: tests/vite-plugin-svelte.ts
================================================
import { runInRepo } from '../utils.ts'
import type { RunOptions } from '../types.d.ts'
export async function test(options: RunOptions) {
await runInRepo({
...options,
repo: 'sveltejs/vite-plugin-svelte',
branch: options.viteMajor < 8 ? 'v6' : 'main',
beforeTest: 'pnpm playwright install chromium',
test: ['check:lint', 'check:types', 'test'],
})
}
================================================
FILE: tests/vite-plugin-vue.ts
================================================
import { runInRepo } from '../utils.ts'
import type { RunOptions } from '../types.d.ts'
export async function test(options: RunOptions) {
await runInRepo({
...options,
repo: 'vitejs/vite-plugin-vue',
build: 'build',
beforeTest: 'pnpm playwright install chromium',
test: 'test',
})
}
================================================
FILE: tests/vite-setup-catalogue.ts
================================================
import { runInRepo } from '../utils.ts'
import type { RunOptions } from '../types.d.ts'
export async function test(options: RunOptions) {
await runInRepo({
...options,
repo: 'sapphi-red/vite-setup-catalogue',
branch: 'main',
test: 'test-for-ecosystem-ci',
})
}
================================================
FILE: tests/vitepress.ts
================================================
import { runInRepo } from '../utils.ts'
import type { RunOptions } from '../types.d.ts'
export async function test(options: RunOptions) {
await runInRepo({
...options,
repo: 'vuejs/vitepress',
overrides: {
'@vitejs/plugin-vue': true,
},
branch: 'main',
build: 'build',
beforeTest: 'pnpm playwright install chromium',
test: 'test',
})
}
================================================
FILE: tests/vitest.ts
================================================
import { runInRepo } from '../utils.ts'
import type { RunOptions } from '../types.d.ts'
export async function test(options: RunOptions) {
await runInRepo({
...options,
repo: 'vitest-dev/vitest',
build: 'build',
test: ['test:ecosystem-ci', 'test:examples'],
beforeTest: 'pnpm playwright install chromium',
})
}
================================================
FILE: tests/vuepress.ts
================================================
import { runInRepo } from '../utils.ts'
import type { RunOptions } from '../types.d.ts'
export async function test(options: RunOptions) {
await runInRepo({
...options,
repo: 'vuepress/core',
overrides: {
'@vitejs/plugin-vue': true,
},
branch: 'main',
build: 'build',
beforeTest: 'pnpm --filter e2e exec playwright install chromium',
test: 'test',
})
}
================================================
FILE: tests/waku.ts
================================================
import { runInRepo } from '../utils.ts'
import type { RunOptions } from '../types.d.ts'
export async function test(options: RunOptions) {
await runInRepo({
...options,
repo: 'dai-shi/waku',
branch: 'main',
build: 'compile',
beforeTest: 'pnpm playwright install chromium',
test: 'test-vite-ecosystem-ci',
overrides: {
'@vitejs/plugin-rsc': true,
// It uses Vitest 3.2+ so we don't need to inject the overrides.
// If we inject overrides, the following error happens due to how waku sets overrides for the test.
//
// npm error code EINVALIDTAGNAME
// npm error Invalid tag name "<3.2.0>vite" of package "vitest@<3.2.0>vite": Tags may not have any characters that encodeURIComponent encodes.
vitest: false,
},
})
}
================================================
FILE: tsconfig.json
================================================
{
"include": ["./**/*.ts"],
"exclude": ["**/node_modules/**", "./workspace/**"],
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"noEmit": true,
"skipLibCheck": true,
"strict": true,
"declaration": true,
"noImplicitOverride": true,
"noUnusedLocals": true,
"esModuleInterop": true,
"useUnknownInCatchVariables": false,
"allowSyntheticDefaultImports": true,
"lib": ["esnext"],
"sourceMap": true,
"erasableSyntaxOnly": true,
"verbatimModuleSyntax": true
}
}
================================================
FILE: types.d.ts
================================================
import type { AGENTS } from '@antfu/ni'
export interface EnvironmentData {
root: string
workspace: string
vitePath: string
cwd: string
env: ProcessEnv
}
export interface RunOptions {
workspace: string
root: string
vitePath: string
viteMajor: number
verify?: boolean
skipGit?: boolean
release?: string
rolldownRelease?: string
agent?: (typeof AGENTS)[number]
build?: Task | Task[]
test?: Task | Task[]
beforeInstall?: Task | Task[]
beforeBuild?: Task | Task[]
beforeTest?: Task | Task[]
}
type Task = string | { script: string; args?: string[] } | (() => Promise<any>)
export interface CommandOptions {
suites?: string[]
repo?: string
branch?: string
tag?: string
commit?: string
release?: string
verify?: boolean
skipGit?: boolean
rolldownRef?: string
}
export interface RepoOptions {
repo: string
dir?: string
branch?: string
tag?: string
commit?: string
shallow?: boolean
overrides?: Overrides
}
export interface Overrides {
[key: string]: string | boolean
}
export interface ProcessEnv {
[key: string]: string | undefined
}
interface DependencyInfo {
from: string
version: string
resolved: string
path: string
}
interface PackageInfo {
name: string
version: string
path: string
private: boolean
dependencies: Record<string, DependencyInfo>
devDependencies: Record<string, DependencyInfo>
optionalDependencies: Record<string, DependencyInfo>
}
================================================
FILE: utils.ts
================================================
import path from 'path'
import fs from 'fs'
import { fileURLToPath, pathToFileURL } from 'url'
import { execaCommand } from 'execa'
import type {
PackageInfo,
EnvironmentData,
Overrides,
ProcessEnv,
RepoOptions,
RunOptions,
Task,
} from './types.d.ts'
import { detect, AGENTS, getCommand, serializeCommand } from '@antfu/ni'
import * as actionsCore from '@actions/core'
import * as semver from 'semver'
import pacote from 'pacote'
const isGitHubActions = !!process.env.GITHUB_ACTIONS
let vitePath: string
let cwd: string
let env: ProcessEnv
function cd(dir: string) {
cwd = path.resolve(cwd, dir)
}
export async function $(literals: TemplateStringsArray, ...values: any[]) {
const cmd = literals.reduce(
(result, current, i) =>
result + current + (values?.[i] != null ? `${values[i]}` : ''),
'',
)
if (isGitHubActions) {
actionsCore.startGroup(`${cwd} $> ${cmd}`)
} else {
console.log(`${cwd} $> ${cmd}`)
}
const proc = execaCommand(cmd, {
env,
stdio: 'pipe',
cwd,
})
if (proc.stdin) process.stdin.pipe(proc.stdin)
if (proc.stdout) proc.stdout.pipe(process.stdout)
if (proc.stderr) proc.stderr.pipe(process.stderr)
let result
try {
result = await proc
} catch (error) {
// Since we already piped the io to the parent process, we remove the duplicated
// messages here so it's easier to read the error message.
if (error.stdout) error.stdout = 'value removed by vite-ecosystem-ci'
if (error.stderr) error.stderr = 'value removed by vite-ecosystem-ci'
if (error.stdio) error.stdio = ['value removed by vite-ecosystem-ci']
throw error
}
if (isGitHubActions) {
actionsCore.endGroup()
}
return result.stdout
}
export async function setupEnvironment(): Promise<EnvironmentData> {
const root = dirnameFrom(import.meta.url)
const workspace = path.resolve(root, 'workspace')
vitePath = path.resolve(workspace, 'vite')
cwd = process.cwd()
env = {
...process.env,
CI: 'true',
TURBO_FORCE: 'true', // disable turbo caching, ecosystem-ci modifies things and we don't want replays
YARN_ENABLE_IMMUTABLE_INSTALLS: 'false', // to avoid errors with mutated lockfile due to overrides
NODE_OPTIONS: '--max-old-space-size=6144', // GITHUB CI has 7GB max, stay below
ECOSYSTEM_CI: 'true', // flag for tests, can be used to conditionally skip irrelevant tests.
TURBO_TELEMETRY_DISABLED: '1', // # see https://turbo.build/repo/docs/telemetry#how-do-i-opt-out
DO_NOT_TRACK: '1',
}
initWorkspace(workspace)
return { root, workspace, vitePath, cwd, env }
}
function initWorkspace(workspace: string) {
if (!fs.existsSync(workspace)) {
fs.mkdirSync(workspace, { recursive: true })
}
const eslintrc = path.join(workspace, '.eslintrc.json')
if (!fs.existsSync(eslintrc)) {
fs.writeFileSync(eslintrc, '{"root":true}\n', 'utf-8')
}
const editorconfig = path.join(workspace, '.editorconfig')
if (!fs.existsSync(editorconfig)) {
fs.writeFileSync(editorconfig, 'root = true\n', 'utf-8')
}
const tsconfig = path.join(workspace, 'tsconfig.json')
if (!fs.existsSync(tsconfig)) {
fs.writeFileSync(tsconfig, '{}\n', 'utf-8')
}
}
export async function setupRepo(options: RepoOptions) {
if (options.branch == null) {
options.branch = 'main'
}
if (options.shallow == null) {
options.shallow = true
}
let { repo, commit, branch, tag, dir, shallow } = options
if (!dir) {
throw new Error('setupRepo must be called with options.dir')
}
if (!repo.includes(':')) {
repo = `https://github.com/${repo}.git`
}
let needClone = true
if (fs.existsSync(dir)) {
const _cwd = cwd
cd(dir)
let currentClonedRepo: string | undefined
try {
currentClonedRepo = await $`git ls-remote --get-url`
} catch {
// when not a git repo
}
if (repo === currentClonedRepo) {
const isShallow =
(await $`git rev-parse --is-shallow-repository`).trim() === 'true'
if (isShallow === shallow) {
needClone = false
}
}
cd(_cwd)
if (needClone) {
fs.rmSync(dir, { recursive: true, force: true })
}
}
if (needClone) {
await $`git -c advice.detachedHead=false clone ${
shallow ? '--depth=1 --no-tags' : ''
} --branch ${tag || branch} ${repo} ${dir}`
}
cd(dir)
await $`git clean -fdxq`
if (!needClone && shallow && !commit) {
await $`git remote set-branches origin ${branch}`
}
await $`git fetch ${shallow ? '--depth=1 --no-tags' : '--tags'} origin ${
tag ? `tag ${tag}` : `${commit || branch}`
}`
if (shallow) {
await $`git -c advice.detachedHead=false checkout ${
tag ? `tags/${tag}` : `${commit || branch}`
}`
} else {
await $`git checkout ${branch}`
await $`git merge FETCH_HEAD`
if (tag || commit) {
await $`git reset --hard ${tag || commit}`
}
}
}
function toCommand(
task: Task | Task[] | void,
agent: (typeof AGENTS)[number],
): ((scripts: any) => Promise<any>) | void {
return async (scripts: any) => {
const tasks = Array.isArray(task) ? task : [task]
for (const task of tasks) {
if (task == null || task === '') {
continue
} else if (typeof task === 'string') {
if (scripts[task] != null) {
const runTaskWithAgent = getCommand(agent, 'run', [task])
await $`${serializeCommand(runTaskWithAgent)}`
} else {
await $`${task}`
}
} else if (typeof task === 'function') {
await task()
} else if (task?.script) {
if (scripts[task.script] != null) {
const runTaskWithAgent = getCommand(agent, 'run', [
task.script,
...(task.args ?? []),
])
await $`${serializeCommand(runTaskWithAgent)}`
} else {
throw new Error(
`invalid task, script "${task.script}" does not exist in package.json`,
)
}
} else {
throw new Error(
`invalid task, expected string or function but got ${typeof task}: ${task}`,
)
}
}
}
}
export async function runInRepo(options: RunOptions & RepoOptions) {
if (options.verify == null) {
options.verify = true
}
if (options.skipGit == null) {
options.skipGit = false
}
if (options.branch == null) {
options.branch = 'main'
}
const {
build,
test,
repo,
branch,
tag,
commit,
skipGit,
verify,
beforeInstall,
beforeBuild,
beforeTest,
} = options
const dir = path.resolve(
options.workspace,
options.dir || repo.substring(repo.lastIndexOf('/') + 1),
)
if (!skipGit) {
await setupRepo({ repo, dir, branch, tag, commit })
} else {
cd(dir)
}
if (options.agent == null) {
const detectedAgent = await detect({ cwd: dir, autoInstall: false })
if (detectedAgent == null) {
throw new Error(`Failed to detect packagemanager in ${dir}`)
}
options.agent = detectedAgent
}
if (!AGENTS.includes(options.agent)) {
throw new Error(
`Invalid agent ${options.agent}. Allowed values: ${AGENTS.join(', ')}`,
)
}
const agent = options.agent
const beforeInstallCommand = toCommand(beforeInstall, agent)
const beforeBuildCommand = toCommand(beforeBuild, agent)
const beforeTestCommand = toCommand(beforeTest, agent)
const buildCommand = toCommand(build, agent)
const testCommand = toCommand(test, agent)
const pkgFile = path.join(dir, 'package.json')
const pkg = JSON.parse(await fs.promises.readFile(pkgFile, 'utf-8'))
await beforeInstallCommand?.(pkg.scripts)
if (verify && test) {
const frozenInstall = getCommand(agent, 'frozen')
await $`${serializeCommand(frozenInstall)}`
await beforeBuildCommand?.(pkg.scripts)
await buildCommand?.(pkg.scripts)
await beforeTestCommand?.(pkg.scripts)
await testCommand?.(pkg.scripts)
}
let overrides = options.overrides || {}
if (options.release) {
if (overrides.vite && overrides.vite !== options.release) {
throw new Error(
`conflicting overrides.vite=${overrides.vite} and --release=${options.release} config. Use either one or the other`,
)
} else {
overrides.vite = options.release
}
if (
overrides.rollup !== false ||
overrides.esbuild === true ||
overrides.vitest !== false
) {
const viteManifest = await pacote.manifest(`vite@${options.release}`, {
retry: {
// enable retry with same options with pnpm (https://pnpm.io/settings#fetchretries)
retries: 2,
factor: 10,
minTimeout: 10 * 1000,
maxTimeout: 60 * 1000,
},
})
// skip if `overrides.rollup` is `false`
if (overrides.rollup !== false) {
overrides.rollup = viteManifest.dependencies!.rollup
}
// apply if `overrides.esbuild` is `true`
if (overrides.esbuild === true) {
overrides.esbuild = viteManifest.dependencies!.esbuild
}
// skip if `overrides.vitest` is `false`
if (overrides.vitest !== false && agent === 'pnpm') {
overrides['vitest@<3.2.0>vite'] = '^6.3.5'
}
}
} else {
overrides.vite ||= `${options.vitePath}/packages/vite`
overrides[`@vitejs/plugin-legacy`] ||=
`${options.vitePath}/packages/plugin-legacy`
const vitePackageInfo = await getVitePackageInfo(options.vitePath)
// skip if `overrides.rollup` is `false`
if (
vitePackageInfo.dependencies.rollup?.version &&
overrides.rollup !== false
) {
overrides.rollup = vitePackageInfo.dependencies.rollup.version
}
// apply if `overrides.esbuild` is `true`
if (
vitePackageInfo.dependencies.esbuild?.version &&
overrides.esbuild === true
) {
overrides.esbuild = vitePackageInfo.dependencies.esbuild.version
}
// skip if `overrides.vitest` is `false`
if (overrides.vitest !== false && agent === 'pnpm') {
overrides['vitest@<3.2.0>vite'] = '^6.3.5'
}
// build and apply local overrides
const localOverrides = await buildOverrides(pkg, options, overrides)
cd(dir) // buildOverrides changed dir, change it back
overrides = {
...overrides,
...localOverrides,
}
}
if (options.rolldownRelease) {
overrides.rolldown = options.rolldownRelease
}
await applyPackageOverrides(agent, dir, pkg, overrides)
await beforeBuildCommand?.(pkg.scripts)
await buildCommand?.(pkg.scripts)
if (test) {
await beforeTestCommand?.(pkg.scripts)
await testCommand?.(pkg.scripts)
}
return { dir }
}
export async function setupViteRepo(options: Partial<RepoOptions>) {
const repo = options.repo || 'vitejs/vite'
await setupRepo({
repo,
dir: vitePath,
branch: 'main',
shallow: true,
...options,
})
try {
const rootPackageJsonFile = path.join(vitePath, 'package.json')
const rootPackageJson = JSON.parse(
await fs.promises.readFile(rootPackageJsonFile, 'utf-8'),
)
const viteMonoRepoNames = ['@vitejs/vite-monorepo', 'vite-monorepo']
const { name } = rootPackageJson
if (!viteMonoRepoNames.includes(name)) {
throw new Error(
`expected "name" field of ${repo}/package.json to indicate vite monorepo, but got ${name}.`,
)
}
const needsWrite = await overridePackageManagerVersion(
rootPackageJson,
'pnpm',
)
if (needsWrite) {
fs.writeFileSync(
rootPackageJsonFile,
JSON.stringify(rootPackageJson, null, 2),
'utf-8',
)
if (rootPackageJson.devDependencies?.pnpm) {
await $`pnpm install -Dw pnpm --lockfile-only`
}
}
} catch (e) {
throw new Error(`Failed to setup vite repo`, { cause: e })
}
}
export async function getPermanentRef() {
cd(vitePath)
try {
const ref = await $`git log -1 --pretty=format:%H`
return ref
} catch (e) {
console.warn(`Failed to obtain perm ref. ${e}`)
return undefined
}
}
export async function buildVite({
verify = false,
rolldownRelease,
}: { verify?: boolean; rolldownRelease?: string } = {}) {
cd(vitePath)
const frozenInstall = getCommand('pnpm', 'frozen')
const runBuild = getCommand('pnpm', 'run', ['build'])
const runTest = getCommand('pnpm', 'run', ['test'])
if (rolldownRelease) {
const pkgFile = path.join(vitePath, 'package.json')
const pkg = JSON.parse(await fs.promises.readFile(pkgFile, 'utf-8'))
// Override rolldown in vite's monorepo so it builds against the specified version
await applyPackageOverrides('pnpm', vitePath, pkg, {
rolldown: rolldownRelease,
})
console.log(`overridden rolldown in vite repo with ${rolldownRelease}`)
} else {
await $`${serializeCommand(frozenInstall)}`
}
await $`${serializeCommand(runBuild)}`
if (verify) {
await $`${serializeCommand(runTest)}`
}
}
export async function bisectVite(
good: string,
runSuite: () => Promise<Error | void>,
) {
// sometimes vite build modifies files in git, e.g. LICENSE.md
// this would stop bisect, so to reset those changes
const resetChanges = async () => $`git reset --hard HEAD`
try {
cd(vitePath)
await resetChanges()
await $`git bisect start`
await $`git bisect bad`
await $`git bisect good ${good}`
let bisecting = true
while (bisecting) {
const commitMsg = await $`git log -1 --format=%s`
const isNonCodeCommit = commitMsg.match(/^(?:release|docs)[:(]/)
if (isNonCodeCommit) {
await $`git bisect skip`
continue // see if next commit can be skipped too
}
const error = await runSuite()
cd(vitePath)
await resetChanges()
const bisectOut = await $`git bisect ${error ? 'bad' : 'good'}`
bisecting = bisectOut.substring(0, 10).toLowerCase() === 'bisecting:' // as long as git prints 'bisecting: ' there are more revisions to test
}
} catch (e) {
console.log('error while bisecting', e)
} finally {
try {
cd(vitePath)
await $`git bisect reset`
} catch (e) {
console.log('Error while resetting bisect', e)
}
}
}
function isLocalOverride(v: string): boolean {
if (!v.includes('/') || v.startsWith('@')) {
// not path-like (either a version number or a package name)
return false
}
try {
return !!fs.lstatSync(v)?.isDirectory()
} catch (e) {
if (e.code !== 'ENOENT') {
throw e
}
return false
}
}
/**
* utility to override packageManager version
*
* @param pkg parsed package.json
* @param pm package manager to override eg. `pnpm`
* @returns {boolean} true if pkg was updated, caller is responsible for writing it to disk
*/
async function overridePackageManagerVersion(
pkg: { [key: string]: any },
pm: string,
): Promise<boolean> {
const versionInUse = pkg.packageManager?.startsWith(`${pm}@`)
? pkg.packageManager.substring(pm.length + 1)
: await $`${pm} --version`
let overrideWithVersion: string | null = null
if (pm === 'pnpm') {
if (semver.eq(versionInUse, '7.18.0')) {
// avoid bug with absolute overrides in pnpm 7.18.0
overrideWithVersion = '7.18.1'
}
}
if (overrideWithVersion) {
console.warn(
`detected ${pm}@${versionInUse} used in ${pkg.name}, changing pkg.packageManager and pkg.engines.${pm} to enforce use of ${pm}@${overrideWithVersion}`,
)
// corepack reads this and uses pnpm @ newVersion then
pkg.packageManager = `${pm}@${overrideWithVersion}`
if (!pkg.engines) {
pkg.engines = {}
}
pkg.engines[pm] = overrideWithVersion
if (pkg.devDependencies?.[pm]) {
// if for some reason the pm is in devDependencies, that would be a local version that'd be preferred over our forced global
// so ensure it here too.
pkg.devDependencies[pm] = overrideWithVersion
}
return true
}
return false
}
export async function applyPackageOverrides(
agent: (typeof AGENTS)[number],
dir: string,
pkg: any,
overrides: Overrides = {},
) {
const useFileProtocol = (v: string) =>
isLocalOverride(v) ? `file:${path.resolve(v)}` : v
// remove boolean flags
overrides = Object.fromEntries(
Object.entries(overrides)
//eslint-disable-next-line @typescript-eslint/no-unused-vars
.filter(([key, value]) => typeof value === 'string')
.map(([key, value]) => [key, useFileProtocol(value as string)]),
)
await $`git clean -fdxq` // remove current install
// Remove version from agent string:
// yarn@berry => yarn
// pnpm@6, pnpm@7 => pnpm
const pm = agent?.split('@')[0]
await overridePackageManagerVersion(pkg, pm)
if (pm === 'pnpm') {
const overridesWithoutSpecialSyntax = Object.fromEntries(
Object.entries(overrides)
//eslint-disable-next-line @typescript-eslint/no-unused-vars
.filter(([key, value]) => !key.includes('>')),
)
if (!pkg.devDependencies) {
pkg.devDependencies = {}
}
pkg.devDependencies = {
...pkg.devDependencies,
...overridesWithoutSpecialSyntax, // overrides must be present in devDependencies or dependencies otherwise they may not work
}
if (!pkg.pnpm) {
pkg.pnpm = {}
}
pkg.pnpm.overrides = {
...pkg.pnpm.overrides,
...overrides,
}
// check `overrides` in pnpm-workspace.yaml
const pnpmWorkspaceFile = path.join(dir, 'pnpm-workspace.yaml')
if (fs.existsSync(pnpmWorkspaceFile)) {
let content = await fs.promises.readFile(pnpmWorkspaceFile, 'utf-8')
let modified = false
if (/^overrides:/m.test(content)) {
delete pkg.pnpm.overrides // remove pnpm.overrides from package.json so that pnpm-workspace.yaml's one is used
// merge with existing overrides
const output = await $`pnpm config list --json --location project`
const currentOverrides = JSON.parse(output).overrides
const mergedOverrides = { ...currentOverrides, ...overrides }
// replace all indented lines in `overrides` section
content = content.replace(
/^overrides:\n((?:[ \t]+.+\n)*)/m,
() =>
`overrides:\n${Object.entries(mergedOverrides)
.map(
([name, version]) =>
` ${JSON.stringify(name)}: ${JSON.stringify(version)}\n`,
)
.join('')}`,
)
modified = true
}
if (content.includes('minimumReleaseAge:')) {
// disable with comment to avoid error on installation if ecosystem-ci overrides pull in violating updates
content = content.replace(
/^([ \t]*minimumReleaseAge[ \t]*:[ \t]*\d+[^\r\n]*)$/m,
'# $1 -- disabled by ecosystem-ci',
)
modified = true
}
if (content.includes('blockExoticSubdeps:')) {
// disable with comment to avoid error on installation if ecosystem-ci overrides pull in tarball URLs
content = content.replace(
/^([ \t]*blockExoticSubdeps[ \t]*:[ \t]*\w+[^\r\n]*)$/m,
'# $1 -- disabled by ecosystem-ci',
)
modified = true
}
if (modified) {
await fs.promises.writeFile(pnpmWorkspaceFile, content, 'utf-8')
}
}
} else if (pm === 'yarn') {
pkg.resolutions = {
...pkg.resolutions,
...overrides,
}
} else if (pm === 'npm') {
pkg.overrides = {
...pkg.overrides,
...overrides,
}
// npm does not allow overriding direct dependencies, force it by updating the blocks themselves
for (const [name, version] of Object.entries(overrides)) {
if (pkg.dependencies?.[name]) {
pkg.dependencies[name] = version
}
if (pkg.devDependencies?.[name]) {
pkg.devDependencies[name] = version
}
}
} else {
throw new Error(`unsupported package manager detected: ${pm}`)
}
const pkgFile = path.join(dir, 'package.json')
await fs.promises.writeFile(pkgFile, JSON.stringify(pkg, null, 2), 'utf-8')
// use of `ni` command here could cause lockfile violation errors so fall back to native commands that avoid these
if (pm === 'pnpm') {
await $`pnpm install --prefer-frozen-lockfile --strict-peer-dependencies false`
} else if (pm === 'yarn') {
await $`yarn install`
} else if (pm === 'npm') {
await $`npm install`
}
}
export function dirnameFrom(url: string) {
return path.dirname(fileURLToPath(url))
}
export function parseViteMajor(vitePath: string): number {
const content = fs.readFileSync(
path.join(vitePath, 'packages', 'vite', 'package.json'),
'utf-8',
)
const pkg = JSON.parse(content)
return parseMajorVersion(pkg.version)
}
export function parseMajorVersion(version: string) {
return parseInt(version.split('.', 1)[0], 10)
}
async function buildOverrides(
pkg: any,
options: RunOptions,
repoOverrides: Overrides,
) {
const { root } = options
const buildsPath = path.join(root, 'builds')
const buildFiles: string[] = fs
.readdirSync(buildsPath)
.filter((f: string) => !f.startsWith('_') && f.endsWith('.ts'))
.map((f) => path.join(buildsPath, f))
const buildDefinitions: {
packages: { [key: string]: string }
build: (options: RunOptions) => Promise<{ dir: string }>
dir?: string
}[] = await Promise.all(buildFiles.map((f) => import(pathToFileURL(f).href)))
const deps = new Set([
...Object.keys(pkg.dependencies ?? {}),
...Object.keys(pkg.devDependencies ?? {}),
...Object.keys(pkg.peerDependencies ?? {}),
])
const needsOverride = (p: string) =>
repoOverrides[p] === true || (deps.has(p) && repoOverrides[p] == null)
const buildsToRun = buildDefinitions.filter(({ packages }) =>
Object.keys(packages).some(needsOverride),
)
const overrides: Overrides = {}
for (const buildDef of buildsToRun) {
const { dir } = await buildDef.build({
root: options.root,
workspace: options.workspace,
vitePath: options.vitePath,
viteMajor: options.viteMajor,
skipGit: options.skipGit,
release: options.release,
verify: options.verify,
// do not pass along scripts
})
for (const [name, path] of Object.entries(buildDef.packages)) {
if (needsOverride(name)) {
overrides[name] = `${dir}/${path}`
}
}
}
return overrides
}
/**
* use pnpm ls to get information about installed dependency versions of vite
* @param vitePath - workspace vite root
*/
async function getVitePackageInfo(vitePath: string): Promise<PackageInfo> {
try {
// run in vite dir to avoid package manager mismatch error from corepack
const current = cwd
cd(`${vitePath}/packages/vite`)
const lsOutput = $`pnpm ls --json`
cd(current)
const lsParsed = JSON.parse(await lsOutput)
return lsParsed[0] as PackageInfo
} catch (e) {
console.error('failed to retrieve vite package infos', e)
throw e
}
}
gitextract_ju04i1kh/ ├── .editorconfig ├── .github/ │ ├── renovate.json5 │ └── workflows/ │ ├── ci.yml │ ├── ecosystem-ci-from-pr.yml │ ├── ecosystem-ci-selected.yml │ └── ecosystem-ci.yml ├── .gitignore ├── .prettierrc.json ├── LICENSE ├── README.md ├── builds/ │ ├── vite-plugin-react.ts │ ├── vite-plugin-svelte.ts │ └── vite-plugin-vue.ts ├── discord-webhook.ts ├── docs/ │ └── pr-comment-setup.md ├── ecosystem-ci.ts ├── eslint.config.js ├── package.json ├── pnpm-workspace.yaml ├── tests/ │ ├── _selftest.ts │ ├── analogjs.ts │ ├── astro.ts │ ├── histoire.ts │ ├── hydrogen.ts │ ├── iles.ts │ ├── ladle.ts │ ├── laravel.ts │ ├── marko.ts │ ├── nuxt.ts │ ├── nx.ts │ ├── one.ts │ ├── quasar.ts │ ├── qwik.ts │ ├── rakkas.ts │ ├── react-router.ts │ ├── redwoodjs.ts │ ├── storybook.ts │ ├── sveltekit.ts │ ├── tanstack-start.ts │ ├── unocss.ts │ ├── vike.ts │ ├── vite-environment-examples.ts │ ├── vite-plugin-cloudflare.ts │ ├── vite-plugin-laravel.ts │ ├── vite-plugin-pwa.ts │ ├── vite-plugin-react.ts │ ├── vite-plugin-rsc.ts │ ├── vite-plugin-svelte.ts │ ├── vite-plugin-vue.ts │ ├── vite-setup-catalogue.ts │ ├── vitepress.ts │ ├── vitest.ts │ ├── vuepress.ts │ └── waku.ts ├── tsconfig.json ├── types.d.ts └── utils.ts
SYMBOL INDEX (79 symbols across 42 files)
FILE: builds/vite-plugin-react.ts
function build (line 4) | async function build(options: RunOptions) {
FILE: builds/vite-plugin-svelte.ts
function build (line 4) | async function build(options: RunOptions) {
FILE: builds/vite-plugin-vue.ts
function build (line 4) | async function build(options: RunOptions) {
FILE: discord-webhook.ts
type RefType (line 3) | type RefType = 'branch' | 'tag' | 'commit' | 'release'
type Status (line 4) | type Status = 'success' | 'failure' | 'cancelled'
type Env (line 5) | type Env = {
function run (line 35) | async function run() {
function assertEnv (line 107) | function assertEnv<T>(
function loadExpectedFailureReason (line 116) | async function loadExpectedFailureReason(suite: string) {
function createRunUrl (line 122) | async function createRunUrl(suite: string) {
type GitHubActionsJob (line 145) | interface GitHubActionsJob {
function fetchJobs (line 150) | async function fetchJobs() {
function createDescription (line 176) | async function createDescription(
function createTargetText (line 196) | function createTargetText(
FILE: ecosystem-ci.ts
function run (line 169) | async function run(suite: string, options: RunOptions) {
function getSuitesToRun (line 177) | function getSuitesToRun(suites: string[], root: string) {
FILE: tests/_selftest.ts
function test (line 6) | async function test(options: RunOptions) {
FILE: tests/analogjs.ts
function test (line 4) | async function test(options: RunOptions) {
FILE: tests/astro.ts
function test (line 4) | async function test(options: RunOptions) {
FILE: tests/histoire.ts
function test (line 4) | async function test(options: RunOptions) {
FILE: tests/hydrogen.ts
function test (line 4) | async function test(options: RunOptions) {
FILE: tests/iles.ts
function test (line 4) | async function test(options: RunOptions) {
FILE: tests/ladle.ts
function test (line 4) | async function test(options: RunOptions) {
FILE: tests/laravel.ts
function test (line 6) | async function test(options: RunOptions) {
FILE: tests/marko.ts
function test (line 4) | async function test(options: RunOptions) {
FILE: tests/nuxt.ts
function test (line 4) | async function test(options: RunOptions) {
FILE: tests/nx.ts
function test (line 4) | async function test(options: RunOptions) {
FILE: tests/one.ts
function test (line 4) | async function test(options: RunOptions) {
FILE: tests/quasar.ts
function test (line 4) | async function test(options: RunOptions) {
FILE: tests/qwik.ts
function test (line 4) | async function test(options: RunOptions) {
FILE: tests/rakkas.ts
function test (line 5) | async function test(options: RunOptions) {
FILE: tests/react-router.ts
function test (line 4) | async function test(options: RunOptions) {
FILE: tests/redwoodjs.ts
function test (line 4) | async function test(options: RunOptions) {
FILE: tests/storybook.ts
function test (line 4) | async function test(options: RunOptions) {
FILE: tests/sveltekit.ts
function test (line 4) | async function test(options: RunOptions) {
FILE: tests/tanstack-start.ts
function test (line 4) | async function test(options: RunOptions) {
FILE: tests/unocss.ts
function test (line 4) | async function test(options: RunOptions) {
FILE: tests/vike.ts
function test (line 4) | async function test(options: RunOptions) {
FILE: tests/vite-environment-examples.ts
function test (line 4) | async function test(options: RunOptions) {
FILE: tests/vite-plugin-cloudflare.ts
function test (line 4) | async function test(options: RunOptions) {
FILE: tests/vite-plugin-laravel.ts
function test (line 4) | async function test(options: RunOptions) {
FILE: tests/vite-plugin-pwa.ts
function test (line 4) | async function test(options: RunOptions) {
FILE: tests/vite-plugin-react.ts
function test (line 4) | async function test(options: RunOptions) {
FILE: tests/vite-plugin-rsc.ts
function test (line 4) | async function test(options: RunOptions) {
FILE: tests/vite-plugin-svelte.ts
function test (line 4) | async function test(options: RunOptions) {
FILE: tests/vite-plugin-vue.ts
function test (line 4) | async function test(options: RunOptions) {
FILE: tests/vite-setup-catalogue.ts
function test (line 4) | async function test(options: RunOptions) {
FILE: tests/vitepress.ts
function test (line 4) | async function test(options: RunOptions) {
FILE: tests/vitest.ts
function test (line 4) | async function test(options: RunOptions) {
FILE: tests/vuepress.ts
function test (line 4) | async function test(options: RunOptions) {
FILE: tests/waku.ts
function test (line 4) | async function test(options: RunOptions) {
FILE: types.d.ts
type EnvironmentData (line 2) | interface EnvironmentData {
type RunOptions (line 10) | interface RunOptions {
type Task (line 27) | type Task = string | { script: string; args?: string[] } | (() => Promis...
type CommandOptions (line 29) | interface CommandOptions {
type RepoOptions (line 41) | interface RepoOptions {
type Overrides (line 51) | interface Overrides {
type ProcessEnv (line 55) | interface ProcessEnv {
type DependencyInfo (line 59) | interface DependencyInfo {
type PackageInfo (line 65) | interface PackageInfo {
FILE: utils.ts
function cd (line 25) | function cd(dir: string) {
function $ (line 29) | async function $(literals: TemplateStringsArray, ...values: any[]) {
function setupEnvironment (line 70) | async function setupEnvironment(): Promise<EnvironmentData> {
function initWorkspace (line 89) | function initWorkspace(workspace: string) {
function setupRepo (line 107) | async function setupRepo(options: RepoOptions) {
function toCommand (line 173) | function toCommand(
function runInRepo (line 212) | async function runInRepo(options: RunOptions & RepoOptions) {
function setupViteRepo (line 368) | async function setupViteRepo(options: Partial<RepoOptions>) {
function getPermanentRef (line 409) | async function getPermanentRef() {
function buildVite (line 420) | async function buildVite({
function bisectVite (line 447) | async function bisectVite(
function isLocalOverride (line 487) | function isLocalOverride(v: string): boolean {
function overridePackageManagerVersion (line 509) | async function overridePackageManagerVersion(
function applyPackageOverrides (line 545) | async function applyPackageOverrides(
function dirnameFrom (line 669) | function dirnameFrom(url: string) {
function parseViteMajor (line 673) | function parseViteMajor(vitePath: string): number {
function parseMajorVersion (line 682) | function parseMajorVersion(version: string) {
function buildOverrides (line 686) | async function buildOverrides(
function getVitePackageInfo (line 738) | async function getVitePackageInfo(vitePath: string): Promise<PackageInfo> {
Condensed preview — 57 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (90K chars).
[
{
"path": ".editorconfig",
"chars": 182,
"preview": "root = true\n\n[*]\nend_of_line = lf\ninsert_final_newline = true\nindent_style = tab\nindent_size = 2\ncharset = utf-8\ntrim_tr"
},
{
"path": ".github/renovate.json5",
"chars": 336,
"preview": "{\n\t\"$schema\": \"https://docs.renovatebot.com/renovate-schema.json\",\n\t\"extends\": [\"config:base\", \"schedule:weekly\", \"group"
},
{
"path": ".github/workflows/ci.yml",
"chars": 1482,
"preview": "name: CI\n\nenv:\n # 7 GiB by default on GitHub, setting to 6 GiB\n # https://docs.github.com/en/actions/using-github-host"
},
{
"path": ".github/workflows/ecosystem-ci-from-pr.yml",
"chars": 11884,
"preview": "# integration tests for vite ecosystem - run from pr comments\nname: vite-ecosystem-ci-from-pr\n\nenv:\n # 7 GiB by default"
},
{
"path": ".github/workflows/ecosystem-ci-selected.yml",
"chars": 3890,
"preview": "# integration tests for vite ecosystem - single run of selected testsuite\nname: vite-ecosystem-ci-selected\n\nenv:\n # 7 G"
},
{
"path": ".github/workflows/ecosystem-ci.yml",
"chars": 4178,
"preview": "# integration tests for vite ecosystem projects - scheduled or manual run for all suites\nname: vite-ecosystem-ci\n\nenv:\n "
},
{
"path": ".gitignore",
"chars": 84,
"preview": ".DS_Store\n.DS_Store?\nnode_modules\nvite\nworkspace\n.pnpm-debug.log\n.idea\n.eslintcache\n"
},
{
"path": ".prettierrc.json",
"chars": 516,
"preview": "{\n\t\"useTabs\": true,\n\t\"semi\": false,\n\t\"tabWidth\": 2,\n\t\"singleQuote\": true,\n\t\"printWidth\": 80,\n\t\"trailingComma\": \"all\",\n\t\""
},
{
"path": "LICENSE",
"chars": 1082,
"preview": "MIT License\n\nCopyright (c) 2021-present, Vite contributors\n\nPermission is hereby granted, free of charge, to any person "
},
{
"path": "README.md",
"chars": 2023,
"preview": "# vite-ecosystem-ci\n\nThis repository is used to run integration tests for vite ecosystem projects\n\n## via github workflo"
},
{
"path": "builds/vite-plugin-react.ts",
"chars": 515,
"preview": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function build(opt"
},
{
"path": "builds/vite-plugin-svelte.ts",
"chars": 488,
"preview": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function build(opt"
},
{
"path": "builds/vite-plugin-vue.ts",
"chars": 361,
"preview": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function build(opt"
},
{
"path": "discord-webhook.ts",
"chars": 5196,
"preview": "import { getPermanentRef, setupEnvironment } from './utils.ts'\n\ntype RefType = 'branch' | 'tag' | 'commit' | 'release'\nt"
},
{
"path": "docs/pr-comment-setup.md",
"chars": 1397,
"preview": "# Setting up \"PR comment trigger\" feature\n\n## (1) Create a GitHub App\n\n1. [Create a GitHub App](https://docs.github.com/"
},
{
"path": "ecosystem-ci.ts",
"chars": 5984,
"preview": "import fs from 'fs'\nimport path from 'path'\nimport process from 'process'\nimport { cac } from 'cac'\n\nimport {\n\tsetupEnvi"
},
{
"path": "eslint.config.js",
"chars": 840,
"preview": "// @ts-check\nimport eslint from '@eslint/js'\nimport n from 'eslint-plugin-n'\nimport tseslint from 'typescript-eslint'\nim"
},
{
"path": "package.json",
"chars": 1710,
"preview": "{\n \"name\": \"vite-ecosystem-ci\",\n \"private\": true,\n \"version\": \"0.0.1\",\n \"description\": \"Vite Ecosystem CI\",\n \"scrip"
},
{
"path": "pnpm-workspace.yaml",
"chars": 185,
"preview": "engineStrict: true\nstrictPeerDependencies: false\npackageManagerStrict: false\nonlyBuiltDependencies:\n - esbuild\n - simp"
},
{
"path": "tests/_selftest.ts",
"chars": 905,
"preview": "import path from 'path'\nimport fs from 'fs'\nimport { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../"
},
{
"path": "tests/analogjs.ts",
"chars": 323,
"preview": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function test(opti"
},
{
"path": "tests/astro.ts",
"chars": 268,
"preview": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function test(opti"
},
{
"path": "tests/histoire.ts",
"chars": 388,
"preview": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function test(opti"
},
{
"path": "tests/hydrogen.ts",
"chars": 248,
"preview": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function test(opti"
},
{
"path": "tests/iles.ts",
"chars": 361,
"preview": "import { runInRepo, $ } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function test(o"
},
{
"path": "tests/ladle.ts",
"chars": 302,
"preview": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function test(opti"
},
{
"path": "tests/laravel.ts",
"chars": 1296,
"preview": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\nimport path from 'node:path'\nimp"
},
{
"path": "tests/marko.ts",
"chars": 411,
"preview": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function test(opti"
},
{
"path": "tests/nuxt.ts",
"chars": 393,
"preview": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function test(opti"
},
{
"path": "tests/nx.ts",
"chars": 462,
"preview": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function test(opti"
},
{
"path": "tests/one.ts",
"chars": 445,
"preview": "import type { RunOptions } from '../types.d.ts'\nimport { runInRepo } from '../utils.ts'\n\nexport async function test(opti"
},
{
"path": "tests/quasar.ts",
"chars": 350,
"preview": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function test(opti"
},
{
"path": "tests/qwik.ts",
"chars": 298,
"preview": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function test(opti"
},
{
"path": "tests/rakkas.ts",
"chars": 863,
"preview": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\nimport { execSync } from 'node:c"
},
{
"path": "tests/react-router.ts",
"chars": 510,
"preview": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function test(opti"
},
{
"path": "tests/redwoodjs.ts",
"chars": 322,
"preview": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function test(opti"
},
{
"path": "tests/storybook.ts",
"chars": 346,
"preview": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function test(opti"
},
{
"path": "tests/sveltekit.ts",
"chars": 538,
"preview": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function test(opti"
},
{
"path": "tests/tanstack-start.ts",
"chars": 340,
"preview": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function test(opti"
},
{
"path": "tests/unocss.ts",
"chars": 237,
"preview": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function test(opti"
},
{
"path": "tests/vike.ts",
"chars": 447,
"preview": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function test(opti"
},
{
"path": "tests/vite-environment-examples.ts",
"chars": 401,
"preview": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function test(opti"
},
{
"path": "tests/vite-plugin-cloudflare.ts",
"chars": 274,
"preview": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function test(opti"
},
{
"path": "tests/vite-plugin-laravel.ts",
"chars": 251,
"preview": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function test(opti"
},
{
"path": "tests/vite-plugin-pwa.ts",
"chars": 334,
"preview": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function test(opti"
},
{
"path": "tests/vite-plugin-react.ts",
"chars": 313,
"preview": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function test(opti"
},
{
"path": "tests/vite-plugin-rsc.ts",
"chars": 378,
"preview": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function test(opti"
},
{
"path": "tests/vite-plugin-svelte.ts",
"chars": 363,
"preview": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function test(opti"
},
{
"path": "tests/vite-plugin-vue.ts",
"chars": 296,
"preview": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function test(opti"
},
{
"path": "tests/vite-setup-catalogue.ts",
"chars": 272,
"preview": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function test(opti"
},
{
"path": "tests/vitepress.ts",
"chars": 358,
"preview": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function test(opti"
},
{
"path": "tests/vitest.ts",
"chars": 323,
"preview": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function test(opti"
},
{
"path": "tests/vuepress.ts",
"chars": 374,
"preview": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function test(opti"
},
{
"path": "tests/waku.ts",
"chars": 753,
"preview": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function test(opti"
},
{
"path": "tsconfig.json",
"chars": 573,
"preview": "{\n\t\"include\": [\"./**/*.ts\"],\n\t\"exclude\": [\"**/node_modules/**\", \"./workspace/**\"],\n\t\"compilerOptions\": {\n\t\t\"target\": \"es"
},
{
"path": "types.d.ts",
"chars": 1401,
"preview": "import type { AGENTS } from '@antfu/ni'\nexport interface EnvironmentData {\n\troot: string\n\tworkspace: string\n\tvitePath: s"
},
{
"path": "utils.ts",
"chars": 21563,
"preview": "import path from 'path'\nimport fs from 'fs'\nimport { fileURLToPath, pathToFileURL } from 'url'\nimport { execaCommand } f"
}
]
About this extraction
This page contains the full source code of the vitejs/vite-ecosystem-ci GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 57 files (78.7 KB), approximately 23.5k tokens, and a symbol index with 79 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.