Full Code of vitejs/vite-ecosystem-ci for AI

main a0c409db48ea cached
57 files
78.7 KB
23.5k tokens
79 symbols
1 requests
Download .txt
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.
   ![GitHub App ID](github_app_id.png)
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.
   ![GitHub App private key](github_app_private_key.png)

## (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
	}
}
Download .txt
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
Download .txt
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.

Copied to clipboard!