[
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\nend_of_line = lf\ninsert_final_newline = true\nindent_style = tab\nindent_size = 2\ncharset = utf-8\ntrim_trailing_whitespace = true\n\n[package.json]\nindent_style = space\n"
  },
  {
    "path": ".github/renovate.json5",
    "content": "{\n\t\"$schema\": \"https://docs.renovatebot.com/renovate-schema.json\",\n\t\"extends\": [\"config:base\", \"schedule:weekly\", \"group:allNonMajor\"],\n\t\"labels\": [\"dependencies\"],\n\t\"ignorePaths\": [],\n\t\"rangeStrategy\": \"bump\",\n\t\"packageRules\": [\n\t\t{\n\t\t\t\"depTypeList\": [\"peerDependencies\", \"engines\"],\n\t\t\t\"enabled\": false,\n\t\t},\n\t],\n\t\"ignoreDeps\": [],\n}\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\nenv:\n  # 7 GiB by default on GitHub, setting to 6 GiB\n  # https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources\n  NODE_OPTIONS: --max-old-space-size=6144\n  # configure corepack to be strict but not download newer versions or change anything\n  COREPACK_DEFAULT_TO_LATEST: 0\n  COREPACK_ENABLE_AUTO_PIN: 0\n  COREPACK_ENABLE_STRICT: 1\n  # see https://turbo.build/repo/docs/telemetry#how-do-i-opt-out\n  TURBO_TELEMETRY_DISABLED: 1\n  DO_NOT_TRACK: 1\non:\n  push:\n    branches:\n      - main\n  pull_request:\n    branches:\n      - main\n\njobs:\n  ci:\n    timeout-minutes: 10\n    runs-on: ubuntu-latest\n    permissions:\n      contents: read # to clone the repo\n    steps:\n      - uses: actions/checkout@v6\n      - uses: actions/setup-node@v6\n        with:\n          node-version: ^24.14.1\n      - run: corepack enable\n      - run: pnpm --version\n      - uses: actions/setup-node@v6\n        with:\n          node-version: ^24.14.1\n          cache: \"pnpm\"\n      - name: install\n        run: pnpm install --frozen-lockfile --prefer-offline\n      - name: format\n        run: pnpm format\n      - name: lint\n        run: pnpm run lint\n      - name: typecheck\n        run: pnpm run typecheck\n      - name: audit\n        if: (${{ success() }} || ${{ failure() }})\n        run: pnpm audit --prod --audit-level moderate\n      - name: test\n        if: (${{ success() }} || ${{ failure() }})\n        run: pnpm test:self\n"
  },
  {
    "path": ".github/workflows/ecosystem-ci-from-pr.yml",
    "content": "# integration tests for vite ecosystem - run from pr comments\nname: vite-ecosystem-ci-from-pr\n\nenv:\n  # 7 GiB by default on GitHub, setting to 6 GiB\n  # https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources\n  NODE_OPTIONS: --max-old-space-size=6144\n  # configure corepack to be strict but not download newer versions or change anything\n  COREPACK_DEFAULT_TO_LATEST: 0\n  COREPACK_ENABLE_AUTO_PIN: 0\n  COREPACK_ENABLE_STRICT: 1\n  # see https://turbo.build/repo/docs/telemetry#how-do-i-opt-out\n  TURBO_TELEMETRY_DISABLED: 1\n  DO_NOT_TRACK: 1\non:\n  workflow_dispatch:\n    inputs:\n      prNumber:\n        description: \"PR number (e.g. 9887)\"\n        required: true\n        type: string\n      branchName:\n        description: \"vite branch to use\"\n        required: true\n        type: string\n        default: \"main\"\n      repo:\n        description: \"vite repository to use\"\n        required: true\n        type: string\n        default: \"vitejs/vite\"\n      commit:\n        description: \"vite commit sha to use\"\n        type: string\n      suite:\n        description: \"testsuite to run. runs all testsuits when `-`.\"\n        required: false\n        type: choice\n        options:\n          - \"-\"\n          - analogjs\n          - astro\n          # - histoire # disabled temporarily\n          - hydrogen\n          - iles\n          # - ladle # disabled until its CI is fixed\n          - laravel\n          - marko\n          - nuxt\n          - nx\n          - one\n          - quasar\n          - qwik\n          # - rakkas # disabled temporarily\n          - react-router\n          # - redwoodjs # disabled temporarily\n          - storybook\n          - sveltekit\n          - tanstack-start\n          - unocss\n          - vike\n          - vite-environment-examples\n          - vite-plugin-pwa\n          - vite-plugin-react\n          - vite-plugin-svelte\n          - vite-plugin-vue\n          - vite-plugin-cloudflare\n          - vite-plugin-rsc\n          - vite-setup-catalogue\n          - vitepress\n          - vitest\n          - vuepress\n          - waku\njobs:\n  init:\n    runs-on: ubuntu-latest\n    outputs:\n      comment-id: ${{ steps.create-comment.outputs.result }}\n    permissions: {}\n    steps:\n      - id: generate-token\n        uses: actions/create-github-app-token@v3\n        with:\n          app-id: ${{ secrets.PR_GITHUB_APP_ID }}\n          private-key: ${{ secrets.PR_GITHUB_APP_PRIVATE_KEY }}\n          repositories: vite\n      - id: create-comment\n        uses: actions/github-script@v8\n        with:\n          github-token: ${{ steps.generate-token.outputs.token }}\n          result-encoding: string\n          script: |\n            const url = `${context.serverUrl}//${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`\n            const urlLink = `[Open](${url})`\n\n            const { data: comment } = await github.rest.issues.createComment({\n              issue_number: context.payload.inputs.prNumber,\n              owner: context.repo.owner,\n              repo: 'vite',\n              body: `⏳ Triggered ecosystem CI: ${urlLink}`\n            })\n            return comment.id\n\n  execute-selected-suite:\n    timeout-minutes: 30\n    runs-on: ubuntu-latest\n    needs: init\n    if: \"inputs.suite != '-'\"\n    outputs:\n      ref: ${{ steps.get-ref.outputs.ref }}\n    permissions:\n      contents: read # to clone the repo\n    steps:\n      - uses: actions/checkout@v6\n      - uses: actions/setup-node@v6\n        with:\n          node-version: ^24.14.1\n      - run: corepack enable\n      - run: pnpm --version\n      - run: pnpm i --frozen-lockfile\n      - run: >-\n          node ecosystem-ci.ts\n          --branch \"$BRANCH_NAME\"\n          --repo \"$REPO\"\n          $(if [ -n \"$COMMIT\" ] ; then echo \"--commit $COMMIT\"; fi)\n          \"$SUITE\"\n        env:\n          BRANCH_NAME: ${{ inputs.branchName }}\n          REPO: ${{ inputs.repo }}\n          COMMIT: ${{ inputs.commit }}\n          SUITE: ${{ inputs.suite }}\n      - id: get-ref\n        if: always()\n        run: |\n          REF=\"${REF:-\"$(git log -1 --pretty=format:%H)\"}\"\n          echo \"ref=$REF\" >> $GITHUB_OUTPUT\n        env:\n          COMMIT: ${{ inputs.commit }}\n        working-directory: ${{ inputs.commit && '.' || 'workspace/vite' }}\n\n  execute-all:\n    timeout-minutes: 30\n    runs-on: ubuntu-latest\n    needs: init\n    if: \"inputs.suite == '-'\"\n    outputs:\n      ref: ${{ steps.get-ref.outputs.ref }}\n    permissions:\n      contents: read # to clone the repo\n    strategy:\n      matrix:\n        suite:\n          - analogjs\n          - astro\n          # - histoire # disabled temporarily\n          # - hydrogen # disabled until they complete they migration back to Vite\n          # - iles # disabled until its CI is fixed\n          # - ladle # disabled until its CI is fixed\n          - laravel\n          - marko\n          - nuxt\n          # - nx # disabled temporarily\n          # - one # disabled until we figured out how to support bun\n          - quasar\n          - qwik\n          # - rakkas # disabled temporarily\n          - react-router\n          # - redwoodjs # disabled temporarily\n          - storybook\n          - sveltekit\n          - tanstack-start\n          - unocss\n          - vike\n          - vite-environment-examples\n          - vite-plugin-pwa\n          - vite-plugin-react\n          - vite-plugin-svelte\n          - vite-plugin-vue\n          - vite-plugin-cloudflare\n          - vite-plugin-rsc\n          - vite-setup-catalogue\n          - vitepress\n          - vitest\n          - vuepress\n          - waku\n      fail-fast: false\n    steps:\n      - uses: actions/checkout@v6\n      - uses: actions/setup-node@v6\n        with:\n          node-version: ^24.14.1\n      - run: corepack enable\n      - run: pnpm --version\n      - run: pnpm i --frozen-lockfile\n      - run: >-\n          node ecosystem-ci.ts\n          --branch \"$BRANCH_NAME\"\n          --repo \"$REPO\"\n          $(if [ -n \"$COMMIT\" ] ; then echo \"--commit $COMMIT\"; fi)\n          \"$SUITE\"\n        env:\n          BRANCH_NAME: ${{ inputs.branchName }}\n          REPO: ${{ inputs.repo }}\n          COMMIT: ${{ inputs.commit }}\n          SUITE: ${{ matrix.suite }}\n      - id: get-ref\n        if: always()\n        run: |\n          REF=\"${REF:-\"$(git log -1 --pretty=format:%H)\"}\"\n          echo \"ref=$REF\" >> $GITHUB_OUTPUT\n        env:\n          COMMIT: ${{ inputs.commit }}\n        working-directory: ${{ inputs.commit && '.' || 'workspace/vite' }}\n\n  update-comment:\n    runs-on: ubuntu-latest\n    needs: [init, execute-selected-suite, execute-all]\n    if: always()\n    permissions: {}\n    steps:\n      - id: generate-token\n        uses: actions/create-github-app-token@v3\n        with:\n          app-id: ${{ secrets.PR_GITHUB_APP_ID }}\n          private-key: ${{ secrets.PR_GITHUB_APP_PRIVATE_KEY }}\n          repositories: |\n            vite\n            vite-ecosystem-ci\n      - uses: actions/github-script@v8\n        with:\n          github-token: ${{ steps.generate-token.outputs.token }}\n          script: |\n            const mainRepoName = 'vite'\n            const ref = process.env.REF\n            const refLink = `[\\`${ref.slice(0, 7)}\\`](${context.serverUrl}/${context.repo.owner}/${mainRepoName}/pull/${context.payload.inputs.prNumber}/commits/${ref})`\n\n            const { data: { jobs } } = await github.rest.actions.listJobsForWorkflowRun({\n              owner: context.repo.owner,\n              repo: context.repo.repo,\n              run_id: context.runId,\n              per_page: 100\n            });\n\n            const selectedSuite = context.payload.inputs.suite\n            let results\n            if (selectedSuite !== \"-\") {\n              const { conclusion, html_url } = jobs.find(job => job.name === \"execute-selected-suite\")\n              results = [{ suite: selectedSuite, conclusion, link: html_url }]\n            } else {\n              results = jobs\n                .filter(job => job.name.startsWith('execute-all '))\n                .map(job => {\n                  const suite = job.name.replace(/^execute-all \\(([^)]+)\\)$/, \"$1\")\n                  return { suite, conclusion: job.conclusion, link: job.html_url }\n                })\n            }\n\n            const url = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`\n            const urlLink = `[Open](${url})`\n\n            const conclusionEmoji = {\n              success: \":white_check_mark:\",\n              failure: \":x:\",\n              cancelled: \":stop_button:\"\n            }\n\n            // check for previous ecosystem-ci runs against the main branch\n\n            // first, list workflow runs for ecosystem-ci.yml\n            const { data: { workflow_runs } } = await github.rest.actions.listWorkflowRuns({\n              owner: context.repo.owner,\n              repo: context.repo.repo,\n              workflow_id: 'ecosystem-ci.yml'\n            });\n\n            // for simplity, we only take the latest completed scheduled run\n            // otherwise we would have to check the inputs for every maunally triggerred runs, which is an overkill\n            const latestScheduledRun = workflow_runs.find(run => run.event === \"schedule\" && run.status === \"completed\")\n\n            // get the jobs for the latest scheduled run\n            const { data: { jobs: scheduledJobs } } = await github.rest.actions.listJobsForWorkflowRun({\n              owner: context.repo.owner,\n              repo: context.repo.repo,\n              run_id: latestScheduledRun.id\n            });\n            const scheduledResults = scheduledJobs\n              .filter(job => job.name.startsWith('test-ecosystem '))\n              .map(job => {\n                const suite = job.name.replace(/^test-ecosystem \\(([^)]+)\\)$/, \"$1\")\n                return { suite, conclusion: job.conclusion, link: job.html_url }\n              })\n\n            const rows = []\n            const successfulSuitesWithoutChanges = []\n            results.forEach(current => {\n              const latest = scheduledResults.find(s => s.suite === current.suite) || {} // in case a new suite is added after latest scheduled\n\n              if (current.conclusion === \"success\" && latest.conclusion === \"success\") {\n                successfulSuitesWithoutChanges.push(`[${current.suite}](${current.link})`)\n              }\n              else {\n                const firstColumn = current.suite\n                const secondColumn = `${conclusionEmoji[current.conclusion]} [${current.conclusion}](${current.link})`\n                const thirdColumn = `${conclusionEmoji[latest.conclusion]} [${latest.conclusion}](${latest.link})`\n\n                rows.push(`| ${firstColumn} | ${secondColumn} | ${thirdColumn} |`)\n              }\n            })\n\n            let body = `\n            📝 Ran ecosystem CI on ${refLink}: ${urlLink}\n\n            `\n            if (rows.length > 0) {\n              body += `| suite | result | [latest scheduled](${latestScheduledRun.html_url}) |\n            |-------|--------|----------------|\n            ${rows.join(\"\\n\")}\n\n            ${conclusionEmoji.success} ${successfulSuitesWithoutChanges.join(\", \")}\n            `\n            } else {\n              body += `${conclusionEmoji.success} ${successfulSuitesWithoutChanges.join(\", \")}\n            `\n            }\n\n            await github.rest.issues.createComment({\n              issue_number: context.payload.inputs.prNumber,\n              owner: context.repo.owner,\n              repo: mainRepoName,\n              body\n            })\n\n            await github.rest.issues.deleteComment({\n              owner: context.repo.owner,\n              repo: mainRepoName,\n              comment_id: +process.env.COMMENT_ID\n            })\n        env:\n          REF: ${{ needs.execute-all.outputs.ref || needs.execute-selected-suite.outputs.ref }}\n          COMMENT_ID: ${{ needs.init.outputs.comment-id }}\n"
  },
  {
    "path": ".github/workflows/ecosystem-ci-selected.yml",
    "content": "# integration tests for vite ecosystem - single run of selected testsuite\nname: vite-ecosystem-ci-selected\n\nenv:\n  # 7 GiB by default on GitHub, setting to 6 GiB\n  # https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources\n  NODE_OPTIONS: --max-old-space-size=6144\n  # configure corepack to be strict but not download newer versions or change anything\n  COREPACK_DEFAULT_TO_LATEST: 0\n  COREPACK_ENABLE_AUTO_PIN: 0\n  COREPACK_ENABLE_STRICT: 1\n  # see https://turbo.build/repo/docs/telemetry#how-do-i-opt-out\n  TURBO_TELEMETRY_DISABLED: 1\n  DO_NOT_TRACK: 1\non:\n  workflow_dispatch:\n    inputs:\n      refType:\n        description: \"type of vite ref to use\"\n        required: true\n        type: choice\n        options:\n          - branch\n          - tag\n          - commit\n          - release\n        default: \"branch\"\n      ref:\n        description: \"vite ref to use\"\n        required: true\n        type: string\n        default: \"main\"\n      repo:\n        description: \"vite repository to use\"\n        required: true\n        type: string\n        default: \"vitejs/vite\"\n      vite_plugin_react_ref:\n        description: \"vite-plugin-react ref to use\"\n        type: string\n      vite_plugin_react_repo:\n        description: \"vite-plugin-react repository to use\"\n        type: string\n      rolldownRef:\n        description: \"rolldown commit sha to use from pkg.pr.new\"\n        type: string\n      suite:\n        description: \"testsuite to run\"\n        required: true\n        type: choice\n        options:\n          - analogjs\n          - astro\n          - histoire\n          - hydrogen\n          - iles\n          - ladle\n          - laravel\n          - marko\n          - nuxt\n          - nx\n          # - one # disabled until we figured out how to support bun\n          - quasar\n          - qwik\n          - rakkas\n          - react-router\n          - redwoodjs\n          - storybook\n          - sveltekit\n          - tanstack-start\n          - unocss\n          - vike\n          - vite-environment-examples\n          - vite-plugin-pwa\n          - vite-plugin-react\n          - vite-plugin-svelte\n          - vite-plugin-vue\n          - vite-plugin-cloudflare\n          - vite-plugin-rsc\n          - vite-setup-catalogue\n          - vitepress\n          - vitest\n          - vuepress\n          - waku\n      sendDiscordReport:\n        description: \"send results to discord\"\n        type: boolean\njobs:\n  execute-selected-suite:\n    timeout-minutes: 30\n    runs-on: ubuntu-latest\n    permissions:\n      contents: read # to clone the repo\n    steps:\n      - uses: actions/checkout@v6\n      - uses: actions/setup-node@v6\n        with:\n          node-version: ^24.14.1\n        id: setup-node\n      - run: corepack enable\n      - run: pnpm --version\n      - run: pnpm i --frozen-lockfile\n      - run: >-\n          node ecosystem-ci.ts\n          \"--$REF_TYPE\" \"$REF\"\n          --repo \"$REPO\"\n          ${ROLLDOWN_REF:+--rolldown-ref \"$ROLLDOWN_REF\"}\n          \"$SUITE\"\n        id: ecosystem-ci-run\n        env:\n          REF_TYPE: ${{ inputs.refType }}\n          REF: ${{ inputs.ref }}\n          REPO: ${{ inputs.repo }}\n          ROLLDOWN_REF: ${{ inputs.rolldownRef }}\n          SUITE: ${{ inputs.suite }}\n          VITE_PLUGIN_REACT_REF: ${{ inputs.vite_plugin_react_ref }}\n          VITE_PLUGIN_REACT_REPO: ${{ inputs.vite_plugin_react_repo }}\n      - if: always() && (inputs.sendDiscordReport || github.event_name != 'workflow_dispatch')\n        run: node discord-webhook.ts\n        env:\n          WORKFLOW_NAME: ci-selected\n          REF_TYPE: ${{ inputs.refType }}\n          REF: ${{ inputs.ref }}\n          REPO: ${{ inputs.repo }}\n          SUITE: ${{ inputs.suite }}\n          STATUS: ${{ job.status }}\n          DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/ecosystem-ci.yml",
    "content": "# integration tests for vite ecosystem projects - scheduled or manual run for all suites\nname: vite-ecosystem-ci\n\nenv:\n  # 7 GiB by default on GitHub, setting to 6 GiB\n  # https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources\n  NODE_OPTIONS: --max-old-space-size=6144\n  # configure corepack to be strict but not download newer versions or change anything\n  COREPACK_DEFAULT_TO_LATEST: 0\n  COREPACK_ENABLE_AUTO_PIN: 0\n  COREPACK_ENABLE_STRICT: 1\n  # see https://turbo.build/repo/docs/telemetry#how-do-i-opt-out\n  TURBO_TELEMETRY_DISABLED: 1\n  DO_NOT_TRACK: 1\non:\n  schedule:\n    - cron: \"0 5 * * 1-5\" # Monday-Friday 5AM\n  workflow_dispatch:\n    inputs:\n      refType:\n        description: \"type of ref\"\n        required: true\n        type: choice\n        options:\n          - branch\n          - tag\n          - commit\n          - release\n        default: \"branch\"\n      ref:\n        description: \"vite ref to use\"\n        required: true\n        type: string\n        default: \"main\"\n      repo:\n        description: \"vite repository to use\"\n        required: true\n        type: string\n        default: \"vitejs/vite\"\n      rolldownRef:\n        description: \"rolldown commit sha to use from pkg.pr.new\"\n        type: string\n      sendDiscordReport:\n        description: \"send results to discord\"\n        type: boolean\n  repository_dispatch:\n    types: [ecosystem-ci]\njobs:\n  test-ecosystem:\n    timeout-minutes: 30\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        suite:\n          - analogjs\n          - astro\n          # - histoire # disabled temporarily\n          # - hydrogen # disabled until they complete they migration back to Vite\n          # - iles # disabled until its CI is fixed\n          # - ladle # disabled until its CI is fixed\n          - laravel\n          - marko\n          - nuxt\n          # - one # disabled until we figured out how to support bun\n          # - nx # disabled temporarily\n          - quasar\n          - qwik\n          # - rakkas # disabled temporarily\n          - react-router\n          # - redwoodjs # disabled temporarily\n          - storybook\n          - sveltekit\n          - tanstack-start\n          - unocss\n          - vike\n          - vite-environment-examples\n          - vite-plugin-pwa\n          - vite-plugin-react\n          - vite-plugin-svelte\n          - vite-plugin-vue\n          - vite-plugin-cloudflare\n          - vite-plugin-rsc\n          - vite-setup-catalogue\n          - vitepress\n          - vitest\n          - vuepress\n          - waku\n      fail-fast: false\n    permissions:\n      contents: read # to clone the repo\n    steps:\n      - uses: actions/checkout@v6\n      - uses: actions/setup-node@v6\n        with:\n          node-version: ^24.14.1\n        id: setup-node\n      - run: corepack enable\n      - run: pnpm --version\n      - run: pnpm i --frozen-lockfile\n      - run: >-\n          node ecosystem-ci.ts\n          \"--$REF_TYPE\" \"$REF\"\n          --repo \"$REPO\"\n          ${ROLLDOWN_REF:+--rolldown-ref \"$ROLLDOWN_REF\"}\n          \"$SUITE\"\n        id: ecosystem-ci-run\n        env:\n          REF_TYPE: ${{ inputs.refType || github.event.client_payload.refType || 'branch' }}\n          REF: ${{ inputs.ref || github.event.client_payload.ref || 'main' }}\n          REPO: ${{ inputs.repo || github.event.client_payload.repo || 'vitejs/vite' }}\n          ROLLDOWN_REF: ${{ inputs.rolldownRef || github.event.client_payload.rolldownRef }}\n          SUITE: ${{ matrix.suite }}\n      - if: always() && (inputs.sendDiscordReport || github.event_name != 'workflow_dispatch')\n        run: node discord-webhook.ts\n        env:\n          WORKFLOW_NAME: ci\n          REF_TYPE: ${{ inputs.refType || github.event.client_payload.refType || 'branch' }}\n          REF: ${{ inputs.ref || github.event.client_payload.ref || 'main' }}\n          REPO: ${{ inputs.repo || github.event.client_payload.repo || 'vitejs/vite' }}\n          SUITE: ${{ matrix.suite }}\n          STATUS: ${{ job.status }}\n          DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          IS_ROLLDOWN_VITE: \"1\"\n"
  },
  {
    "path": ".gitignore",
    "content": ".DS_Store\n.DS_Store?\nnode_modules\nvite\nworkspace\n.pnpm-debug.log\n.idea\n.eslintcache\n"
  },
  {
    "path": ".prettierrc.json",
    "content": "{\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\"overrides\": [\n\t\t{\n\t\t\t\"files\": [\"*.json5\"],\n\t\t\t\"options\": {\n\t\t\t\t\"singleQuote\": false,\n\t\t\t\t\"quoteProps\": \"preserve\"\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"files\": [\"*.yml\"],\n\t\t\t\"options\": {\n\t\t\t\t\"singleQuote\": false\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"files\": \"**/pnpm-lock.yaml\",\n\t\t\t\"options\": {\n\t\t\t\t\"requirePragma\": true\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"files\": \"**/package.json\",\n\t\t\t\"options\": {\n\t\t\t\t\"useTabs\": false,\n\t\t\t\t\"tabWidth\": 2\n\t\t\t}\n\t\t}\n\t]\n}\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2021-present, Vite contributors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE."
  },
  {
    "path": "README.md",
    "content": "# vite-ecosystem-ci\n\nThis repository is used to run integration tests for vite ecosystem projects\n\n## via github workflow\n\n### scheduled\n\nWorkflows are scheduled to run automatically every Monday, Wednesday and Friday\n\n### manually\n\n- open [workflow](../../actions/workflows/ecosystem-ci-selected.yml)\n- click 'Run workflow' button on top right of the list\n- select suite to run in dropdown\n- start workflow\n\n## via shell script\n\n- clone this repo\n- run `pnpm i`\n- run `pnpm test` to run all suites\n- or `pnpm test <suitename>` to select a suite\n\nYou can pass `--tag v2.8.0-beta.1`, `--branch somebranch` or `--commit abcd1234` option to select a specific vite version to build.\nIf you pass `--release 2.7.13`, vite build will be skipped and vite is fetched from the registry instead\n\nThe repositories are checked out into `workspace` subdirectory as shallow clones\n\n## via comment on PR\n\n- comment `/ecosystem-ci run` on a PR\n- or `/ecosystem-ci run <suitename>` to select a suite\n\nUsers with triage permission to vitejs/vite repository can only use this.\n\nSee [docs/pr-comment-setup.md](./docs/pr-comment-setup.md) for how to setup this feature.\n\n# how to add a new integration test\n\n- check out the existing [tests](./tests) and add one yourself. Thanks to some utilities it is really easy\n- once you are confident the suite works, add it to the lists of suites in the [workflows](../../actions/)\n\n# reporting results\n\n## Discord\n\nResults are posted automatically to `#ecosystem-ci` on [vite discord](https://chat.vitejs.dev/)\n\n### on your own server\n\n- Go to `Server settings > Integrations > Webhooks` and click `New Webhook`\n- Give it a name, icon and a channel to post to\n- copy the webhook url\n- get in touch with admins of this repo so they can add the webhook\n\n#### how to add a discord webhook here\n\n- Go to `<github repo>/settings/secrets/actions` and click on `New repository secret`\n- set `Name` as `DISCORD_WEBHOOK_URL`\n- paste the discord webhook url you copied from above into `Value`\n- Click `Add secret`\n"
  },
  {
    "path": "builds/vite-plugin-react.ts",
    "content": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function build(options: RunOptions) {\n\treturn runInRepo({\n\t\t...options,\n\t\trepo: process.env.VITE_PLUGIN_REACT_REPO || 'vitejs/vite-plugin-react',\n\t\tbranch: process.env.VITE_PLUGIN_REACT_REF || 'main',\n\t\tbuild: 'build',\n\t})\n}\n\nexport const packages = {\n\t'@vitejs/plugin-react': 'packages/plugin-react',\n\t'@vitejs/plugin-react-swc': 'packages/plugin-react-swc/dist',\n\t'@vitejs/plugin-rsc': 'packages/plugin-rsc',\n}\n"
  },
  {
    "path": "builds/vite-plugin-svelte.ts",
    "content": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function build(options: RunOptions) {\n\treturn runInRepo({\n\t\t...options,\n\t\trepo: 'sveltejs/vite-plugin-svelte',\n\t\tbranch: options.viteMajor < 8 ? 'v6' : 'main',\n\t\toverrides: {\n\t\t\tsvelte: 'latest',\n\t\t},\n\t})\n}\n\nexport const packages = {\n\t'@sveltejs/vite-plugin-svelte': 'packages/vite-plugin-svelte',\n\t'@sveltejs/vite-plugin-svelte-inspector':\n\t\t'packages/vite-plugin-svelte-inspector',\n}\n"
  },
  {
    "path": "builds/vite-plugin-vue.ts",
    "content": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function build(options: RunOptions) {\n\treturn runInRepo({\n\t\t...options,\n\t\trepo: 'vitejs/vite-plugin-vue',\n\t\tbuild: 'build',\n\t})\n}\n\nexport const packages = {\n\t'@vitejs/plugin-vue': 'packages/plugin-vue',\n\t'@vitejs/plugin-vue-jsx': 'packages/plugin-vue-jsx',\n}\n"
  },
  {
    "path": "discord-webhook.ts",
    "content": "import { getPermanentRef, setupEnvironment } from './utils.ts'\n\ntype RefType = 'branch' | 'tag' | 'commit' | 'release'\ntype Status = 'success' | 'failure' | 'cancelled'\ntype Env = {\n\tWORKFLOW_NAME?: string\n\tREF_TYPE?: RefType\n\tREF?: string\n\tREPO?: string\n\tSUITE?: string\n\tSTATUS?: Status\n\tDISCORD_WEBHOOK_URL?: string\n\tIS_ROLLDOWN_VITE?: '1'\n}\n\nconst statusConfig = {\n\tsuccess: {\n\t\tcolor: parseInt('57ab5a', 16),\n\t\temoji: ':white_check_mark:',\n\t},\n\texpectedFailure: {\n\t\tcolor: parseInt('c69026', 16),\n\t\temoji: ':construction:',\n\t},\n\tfailure: {\n\t\tcolor: parseInt('e5534b', 16),\n\t\temoji: ':x:',\n\t},\n\tcancelled: {\n\t\tcolor: parseInt('768390', 16),\n\t\temoji: ':stop_button:',\n\t},\n}\n\nasync function run() {\n\tif (!process.env.GITHUB_ACTIONS) {\n\t\tthrow new Error('This script can only run on GitHub Actions.')\n\t}\n\tif (!process.env.DISCORD_WEBHOOK_URL) {\n\t\tconsole.warn(\n\t\t\t\"Skipped beacuse process.env.DISCORD_WEBHOOK_URL was empty or didn't exist\",\n\t\t)\n\t\treturn\n\t}\n\tif (!process.env.GITHUB_TOKEN) {\n\t\tconsole.warn(\n\t\t\t\"Not using a token because process.env.GITHUB_TOKEN was empty or didn't exist\",\n\t\t)\n\t}\n\n\tconst env = process.env as Env\n\n\tassertEnv('WORKFLOW_NAME', env.WORKFLOW_NAME)\n\tassertEnv('REF_TYPE', env.REF_TYPE)\n\tassertEnv('REF', env.REF)\n\tassertEnv('REPO', env.REPO)\n\tassertEnv('SUITE', env.SUITE)\n\tassertEnv('STATUS', env.STATUS)\n\tassertEnv('DISCORD_WEBHOOK_URL', env.DISCORD_WEBHOOK_URL)\n\tconst isRolldownVite = !!env.IS_ROLLDOWN_VITE\n\tconst expectedFailureReason = isRolldownVite\n\t\t? await loadExpectedFailureReason(env.SUITE)\n\t\t: undefined\n\tconst status =\n\t\tenv.STATUS === 'failure' && expectedFailureReason\n\t\t\t? 'expectedFailure'\n\t\t\t: env.STATUS\n\n\tawait setupEnvironment()\n\n\tconst refType = env.REF_TYPE\n\t// vite repo is not cloned when release\n\tconst permRef = refType === 'release' ? undefined : await getPermanentRef()\n\n\tconst targetText = createTargetText(refType, env.REF, permRef, env.REPO)\n\n\tconst webhookContent = {\n\t\tusername: `vite-ecosystem-ci (${env.WORKFLOW_NAME})`,\n\t\tavatar_url: 'https://github.com/vitejs.png',\n\t\tembeds: [\n\t\t\t{\n\t\t\t\ttitle: `${statusConfig[status].emoji}  ${env.SUITE}`,\n\t\t\t\tdescription: await createDescription(\n\t\t\t\t\tenv.SUITE,\n\t\t\t\t\ttargetText,\n\t\t\t\t\texpectedFailureReason,\n\t\t\t\t),\n\t\t\t\tcolor: statusConfig[status].color,\n\t\t\t},\n\t\t],\n\t}\n\n\tconst res = await fetch(env.DISCORD_WEBHOOK_URL, {\n\t\tmethod: 'POST',\n\t\theaders: {\n\t\t\t'Content-Type': 'application/json',\n\t\t},\n\t\tbody: JSON.stringify(webhookContent),\n\t})\n\tif (res.ok) {\n\t\tconsole.log('Sent Webhook')\n\t} else {\n\t\tconsole.error(`Webhook failed ${res.status}:`, await res.text())\n\t}\n}\n\nfunction assertEnv<T>(\n\tname: string,\n\tvalue: T,\n): asserts value is Exclude<T, undefined> {\n\tif (!value) {\n\t\tthrow new Error(`process.env.${name} is empty or does not exist.`)\n\t}\n}\n\nasync function loadExpectedFailureReason(suite: string) {\n\tconst module = await import(`./tests/${suite}.ts`)\n\tconst reason: string | undefined = module.rolldownViteExpectedFailureReason\n\treturn reason?.trim()\n}\n\nasync function createRunUrl(suite: string) {\n\tconst result = await fetchJobs()\n\tif (!result) {\n\t\treturn undefined\n\t}\n\n\tif (result.total_count <= 0) {\n\t\tconsole.warn('total_count was 0')\n\t\treturn undefined\n\t}\n\n\tconst job = result.jobs.find((job) => job.name === process.env.GITHUB_JOB)\n\tif (job) {\n\t\treturn job.html_url\n\t}\n\n\t// when matrix\n\tconst jobM = result.jobs.find(\n\t\t(job) => job.name === `${process.env.GITHUB_JOB} (${suite})`,\n\t)\n\treturn jobM?.html_url\n}\n\ninterface GitHubActionsJob {\n\tname: string\n\thtml_url: string\n}\n\nasync function fetchJobs() {\n\tconst url = `${process.env.GITHUB_API_URL}/repos/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}/jobs`\n\tconst res = await fetch(url, {\n\t\theaders: {\n\t\t\tAccept: 'application/vnd.github.v3+json',\n\t\t\t...(process.env.GITHUB_TOKEN\n\t\t\t\t? {\n\t\t\t\t\t\tAuthorization: `token ${process.env.GITHUB_TOKEN}`,\n\t\t\t\t\t}\n\t\t\t\t: undefined),\n\t\t},\n\t})\n\tif (!res.ok) {\n\t\tconsole.warn(\n\t\t\t`Failed to fetch jobs (${res.status} ${res.statusText}): ${res.text()}`,\n\t\t)\n\t\treturn null\n\t}\n\n\tconst result = await res.json()\n\treturn result as {\n\t\ttotal_count: number\n\t\tjobs: GitHubActionsJob[]\n\t}\n}\n\nasync function createDescription(\n\tsuite: string,\n\ttargetText: string,\n\texpectedFailureReason: string | undefined,\n) {\n\tconst runUrl = await createRunUrl(suite)\n\tconst open = runUrl === undefined ? 'Null' : `[Open](${runUrl})`\n\tlet message = `\n:scroll:\\u00a0\\u00a0${open}\\u3000\\u3000:zap:\\u00a0\\u00a0${targetText}\n`.trim()\n\tif (expectedFailureReason) {\n\t\tmessage +=\n\t\t\t'\\n' +\n\t\t\t`\n:bulb:\\u00a0\\u00a0${expectedFailureReason}\n`.trim()\n\t}\n\treturn message\n}\n\nfunction createTargetText(\n\trefType: RefType,\n\tref: string,\n\tpermRef: string | undefined,\n\trepo: string,\n) {\n\tconst repoText = repo !== 'vitejs/vite' ? `${repo}:` : ''\n\tif (refType === 'branch') {\n\t\tconst shortRef = permRef?.slice(0, 7)\n\t\tconst link = `https://github.com/${repo}/commits/${permRef || ref}`\n\t\treturn `[${repoText}${ref} (${shortRef || 'unknown'})](${link})`\n\t}\n\n\tconst refTypeText = refType === 'release' ? ' (release)' : ''\n\tconst link = `https://github.com/${repo}/commits/${ref}`\n\treturn `[${repoText}${ref}${refTypeText}](${link})`\n}\n\nrun().catch((e) => {\n\tconsole.error('Error sending webhook:', e)\n})\n"
  },
  {
    "path": "docs/pr-comment-setup.md",
    "content": "# Setting up \"PR comment trigger\" feature\n\n## (1) Create a GitHub App\n\n1. [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:\n   - Metadata: Read only\n   - Actions: Read and Write\n   - Issues: Read and Write\n   - Pull requests: Read and Write\n1. Install that App to the organization/user. Give that App access to vitejs/vite and vitejs/vite-ecosystem-ci.\n1. Check the App ID. It's written on `https://github.com/settings/apps/<github-app-name-slug>`. This is used later.\n   ![GitHub App ID](github_app_id.png)\n1. 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.\n   ![GitHub App private key](github_app_private_key.png)\n\n## (2) Adding secrets to vitejs/vite and vitejs/vite-ecosystem-ci\n\n- vitejs/vite\n  - `ECOSYSTEM_CI_GITHUB_APP_ID`: ID of the created GitHub App\n  - `ECOSYSTEM_CI_GITHUB_APP_PRIVATE_KEY`: the content of the private key of the created GitHub App\n- vitejs/vite-ecosystem-ci\n  - `PR_GITHUB_APP_ID`: ID of the created GitHub App\n  - `PR_GITHUB_APP_PRIVATE_KEY`: the content of the private key of the created GitHub App\n\n## (3) Adding workflows to vitejs/vite\n\nAdd [this workflow](https://github.com/vitejs/vite/blob/main/.github/workflows/ecosystem-ci-trigger.yml).\n"
  },
  {
    "path": "ecosystem-ci.ts",
    "content": "import fs from 'fs'\nimport path from 'path'\nimport process from 'process'\nimport { cac } from 'cac'\n\nimport {\n\tsetupEnvironment,\n\tsetupViteRepo,\n\tbuildVite,\n\tbisectVite,\n\tparseViteMajor,\n\tparseMajorVersion,\n} from './utils.ts'\nimport type { CommandOptions, RunOptions } from './types.d.ts'\n\nconst cli = cac()\ncli\n\t.command('[...suites]', 'build vite and run selected suites')\n\t.option('--verify', 'verify checkouts by running tests', { default: false })\n\t.option('--repo <repo>', 'vite repository to use', { default: 'vitejs/vite' })\n\t.option('--branch <branch>', 'vite branch to use', { default: 'main' })\n\t.option('--tag <tag>', 'vite tag to use')\n\t.option('--commit <commit>', 'vite commit sha to use')\n\t.option('--release <version>', 'vite release to use from npm registry')\n\t.option(\n\t\t'--rolldown-ref <commit>',\n\t\t'rolldown commit sha to use from pkg.pr.new',\n\t)\n\t.action(async (suites, options: CommandOptions) => {\n\t\tif (options.commit) {\n\t\t\tconst url = `https://pkg.pr.new/vite@${options.commit}`\n\t\t\tconst { status } = await fetch(url)\n\t\t\tif (status === 200) {\n\t\t\t\toptions.release = url\n\t\t\t\tdelete options.commit\n\n\t\t\t\tconsole.log(`continuous release available on ${url}`)\n\t\t\t}\n\t\t}\n\t\tlet rolldownRelease: string | undefined\n\t\tif (options.rolldownRef) {\n\t\t\tconst url = `https://pkg.pr.new/rolldown@${options.rolldownRef}`\n\t\t\tconst { status } = await fetch(url)\n\t\t\tif (status === 200) {\n\t\t\t\trolldownRelease = url\n\t\t\t\tconsole.log(`rolldown continuous release available on ${url}`)\n\t\t\t} else {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`rolldown continuous release not found for ref ${options.rolldownRef} (HTTP ${status}): ${url}`,\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t\tconst { root, vitePath, workspace } = await setupEnvironment()\n\t\tconst suitesToRun = getSuitesToRun(suites, root)\n\t\tlet viteMajor\n\t\tif (!options.release) {\n\t\t\tawait setupViteRepo(options)\n\t\t\tawait buildVite({ verify: options.verify, rolldownRelease })\n\t\t\tviteMajor = parseViteMajor(vitePath)\n\t\t} else {\n\t\t\tviteMajor = parseMajorVersion(options.release)\n\t\t}\n\t\tconst runOptions: RunOptions = {\n\t\t\troot,\n\t\t\tvitePath,\n\t\t\tviteMajor,\n\t\t\tworkspace,\n\t\t\trelease: options.release,\n\t\t\trolldownRelease,\n\t\t\tverify: options.verify,\n\t\t\tskipGit: false,\n\t\t}\n\t\tfor (const suite of suitesToRun) {\n\t\t\tawait run(suite, runOptions)\n\t\t}\n\t})\n\ncli\n\t.command('build-vite', 'build vite only')\n\t.option('--verify', 'verify vite checkout by running tests', {\n\t\tdefault: false,\n\t})\n\t.option('--repo <repo>', 'vite repository to use', { default: 'vitejs/vite' })\n\t.option('--branch <branch>', 'vite branch to use', { default: 'main' })\n\t.option('--tag <tag>', 'vite tag to use')\n\t.option('--commit <commit>', 'vite commit sha to use')\n\t.action(async (options: CommandOptions) => {\n\t\tawait setupEnvironment()\n\t\tawait setupViteRepo(options)\n\t\tawait buildVite({ verify: options.verify })\n\t})\n\ncli\n\t.command('run-suites [...suites]', 'run single suite with pre-built vite')\n\t.option(\n\t\t'--verify',\n\t\t'verify checkout by running tests before using local vite',\n\t\t{ default: false },\n\t)\n\t.option('--repo <repo>', 'vite repository to use', { default: 'vitejs/vite' })\n\t.option('--release <version>', 'vite release to use from npm registry')\n\t.action(async (suites, options: CommandOptions) => {\n\t\tconst { root, vitePath, workspace } = await setupEnvironment()\n\t\tconst suitesToRun = getSuitesToRun(suites, root)\n\t\tconst runOptions: RunOptions = {\n\t\t\t...options,\n\t\t\troot,\n\t\t\tvitePath,\n\t\t\tviteMajor: parseViteMajor(vitePath),\n\t\t\tworkspace,\n\t\t}\n\t\tfor (const suite of suitesToRun) {\n\t\t\tawait run(suite, runOptions)\n\t\t}\n\t})\n\ncli\n\t.command(\n\t\t'bisect [...suites]',\n\t\t'use git bisect to find a commit in vite that broke suites',\n\t)\n\t.option('--good <ref>', 'last known good ref, e.g. a previous tag. REQUIRED!')\n\t.option('--verify', 'verify checkouts by running tests', { default: false })\n\t.option('--repo <repo>', 'vite repository to use', { default: 'vitejs/vite' })\n\t.option('--branch <branch>', 'vite branch to use', { default: 'main' })\n\t.option('--tag <tag>', 'vite tag to use')\n\t.option('--commit <commit>', 'vite commit sha to use')\n\t.action(async (suites, options: CommandOptions & { good: string }) => {\n\t\tif (!options.good) {\n\t\t\tconsole.log(\n\t\t\t\t'you have to specify a known good version with `--good <commit|tag>`',\n\t\t\t)\n\t\t\tprocess.exit(1)\n\t\t}\n\t\tconst { root, vitePath, workspace } = await setupEnvironment()\n\t\tconst suitesToRun = getSuitesToRun(suites, root)\n\t\tlet isFirstRun = true\n\t\tconst { verify } = options\n\t\tconst runSuite = async () => {\n\t\t\ttry {\n\t\t\t\tawait buildVite({ verify: isFirstRun && verify })\n\t\t\t\tfor (const suite of suitesToRun) {\n\t\t\t\t\tawait run(suite, {\n\t\t\t\t\t\tverify: !!(isFirstRun && verify),\n\t\t\t\t\t\tskipGit: !isFirstRun,\n\t\t\t\t\t\troot,\n\t\t\t\t\t\tvitePath,\n\t\t\t\t\t\tviteMajor: parseViteMajor(vitePath),\n\t\t\t\t\t\tworkspace,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\tisFirstRun = false\n\t\t\t\treturn null\n\t\t\t} catch (e) {\n\t\t\t\treturn e\n\t\t\t}\n\t\t}\n\t\tawait setupViteRepo({ ...options, shallow: false })\n\t\tconst initialError = await runSuite()\n\t\tif (initialError) {\n\t\t\tawait bisectVite(options.good, runSuite)\n\t\t} else {\n\t\t\tconsole.log(`no errors for starting commit, cannot bisect`)\n\t\t}\n\t})\ncli.help()\ncli.parse()\n\nasync function run(suite: string, options: RunOptions) {\n\tconst { test } = await import(`./tests/${suite}.ts`)\n\tawait test({\n\t\t...options,\n\t\tworkspace: path.resolve(options.workspace, suite),\n\t})\n}\n\nfunction getSuitesToRun(suites: string[], root: string) {\n\tlet suitesToRun: string[] = suites\n\tconst availableSuites: string[] = fs\n\t\t.readdirSync(path.join(root, 'tests'))\n\t\t.filter((f: string) => !f.startsWith('_') && f.endsWith('.ts'))\n\t\t.map((f: string) => f.slice(0, -3))\n\tavailableSuites.sort()\n\tif (suitesToRun.length === 0) {\n\t\tsuitesToRun = availableSuites\n\t} else {\n\t\tconst invalidSuites = suitesToRun.filter(\n\t\t\t(x) => !x.startsWith('_') && !availableSuites.includes(x),\n\t\t)\n\t\tif (invalidSuites.length) {\n\t\t\tconsole.log(`invalid suite(s): ${invalidSuites.join(', ')}`)\n\t\t\tconsole.log(`available suites: ${availableSuites.join(', ')}`)\n\t\t\tprocess.exit(1)\n\t\t}\n\t}\n\treturn suitesToRun\n}\n"
  },
  {
    "path": "eslint.config.js",
    "content": "// @ts-check\nimport eslint from '@eslint/js'\nimport n from 'eslint-plugin-n'\nimport tseslint from 'typescript-eslint'\nimport prettierConfig from 'eslint-config-prettier/flat'\n\nexport default tseslint.config([\n\t{\n\t\tname: 'local/ignores',\n\t\tignores: ['workspace/**'],\n\t},\n\teslint.configs.recommended,\n\ttseslint.configs.recommended,\n\tn.configs['flat/recommended-module'],\n\tprettierConfig,\n\t{\n\t\tname: 'local/rules',\n\t\tfiles: ['**/*.{js,ts}'],\n\t\trules: {\n\t\t\teqeqeq: ['warn', 'always', { null: 'never' }],\n\t\t\t'no-debugger': ['error'],\n\t\t\t'no-empty': ['warn', { allowEmptyCatch: true }],\n\t\t\t'no-process-exit': 'off',\n\t\t\t'no-useless-escape': 'off',\n\t\t\t'prefer-const': [\n\t\t\t\t'warn',\n\t\t\t\t{\n\t\t\t\t\tdestructuring: 'all',\n\t\t\t\t},\n\t\t\t],\n\t\t\t'n/no-process-exit': 'off',\n\t\t\t'@typescript-eslint/no-explicit-any': 'off', // we use any in some places\n\t\t},\n\t},\n])\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"vite-ecosystem-ci\",\n  \"private\": true,\n  \"version\": \"0.0.1\",\n  \"description\": \"Vite Ecosystem CI\",\n  \"scripts\": {\n    \"prepare\": \"pnpm exec simple-git-hooks\",\n    \"lint\": \"eslint --cache '**/*.{js,ts}'\",\n    \"lint:fix\": \"pnpm lint --fix\",\n    \"typecheck\": \"tsc\",\n    \"format\": \"prettier --ignore-path .gitignore --check .\",\n    \"format:fix\": \"pnpm format --write\",\n    \"test:self\": \"node ecosystem-ci.ts _selftest\",\n    \"test\": \"node ecosystem-ci.ts\",\n    \"bisect\": \"node ecosystem-ci.ts bisect\"\n  },\n  \"simple-git-hooks\": {\n    \"pre-commit\": \"pnpm exec lint-staged --concurrent false\"\n  },\n  \"lint-staged\": {\n    \"*\": [\n      \"prettier --write --ignore-unknown\"\n    ],\n    \"*.{js,ts}\": [\n      \"eslint --fix\"\n    ]\n  },\n  \"packageManager\": \"pnpm@10.32.1\",\n  \"type\": \"module\",\n  \"engines\": {\n    \"node\": \">=24\",\n    \"pnpm\": \"^10.0.0\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/vitejs/vite-ecosystem-ci.git\"\n  },\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/vitejs/vite-ecosystem-ci/issues\"\n  },\n  \"homepage\": \"https://github.com/vitejs/vite-ecosystem-ci#readme\",\n  \"dependencies\": {\n    \"@actions/core\": \"^3.0.0\",\n    \"cac\": \"^7.0.0\",\n    \"execa\": \"^9.6.1\"\n  },\n  \"devDependencies\": {\n    \"@antfu/ni\": \"^30.0.0\",\n    \"@eslint/js\": \"^10.0.1\",\n    \"@types/node\": \"^25.5.0\",\n    \"@types/pacote\": \"^11.1.8\",\n    \"@types/semver\": \"^7.7.1\",\n    \"eslint\": \"^10.1.0\",\n    \"eslint-config-prettier\": \"^10.1.8\",\n    \"eslint-plugin-n\": \"^17.24.0\",\n    \"lint-staged\": \"^16.4.0\",\n    \"pacote\": \"^21.5.0\",\n    \"prettier\": \"^3.8.1\",\n    \"semver\": \"^7.7.4\",\n    \"simple-git-hooks\": \"^2.13.1\",\n    \"typescript\": \"^5.9.3\",\n    \"typescript-eslint\": \"^8.57.2\"\n  }\n}\n"
  },
  {
    "path": "pnpm-workspace.yaml",
    "content": "engineStrict: true\nstrictPeerDependencies: false\npackageManagerStrict: false\nonlyBuiltDependencies:\n  - esbuild\n  - simple-git-hooks\noverrides:\n  'cross-spawn@>=7.0.0 <7.0.5': '^7.0.6'\n"
  },
  {
    "path": "tests/_selftest.ts",
    "content": "import path from 'path'\nimport fs from 'fs'\nimport { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function test(options: RunOptions) {\n\tawait runInRepo({\n\t\t...options,\n\t\trepo: 'vitejs/vite-ecosystem-ci',\n\t\tbuild: async () => {\n\t\t\tconst dir = path.resolve(options.workspace, 'vite-ecosystem-ci')\n\t\t\tconst pkgFile = path.join(dir, 'package.json')\n\t\t\tconst pkg = JSON.parse(await fs.promises.readFile(pkgFile, 'utf-8'))\n\t\t\tif (pkg.name !== 'vite-ecosystem-ci') {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`invalid checkout, expected package.json with \"name\":\"vite-ecosystem-ci\" in ${dir}`,\n\t\t\t\t)\n\t\t\t}\n\t\t\tpkg.scripts.selftestscript =\n\t\t\t\t\"[ -d ../../vite/packages/vite/dist ] || (echo 'vite build failed' && exit 1)\"\n\t\t\tawait fs.promises.writeFile(\n\t\t\t\tpkgFile,\n\t\t\t\tJSON.stringify(pkg, null, 2),\n\t\t\t\t'utf-8',\n\t\t\t)\n\t\t},\n\t\ttest: 'pnpm run selftestscript',\n\t\tverify: false,\n\t})\n}\n"
  },
  {
    "path": "tests/analogjs.ts",
    "content": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function test(options: RunOptions) {\n\tawait runInRepo({\n\t\t...options,\n\t\trepo: 'analogjs/analog',\n\t\tbranch: 'beta',\n\t\tbuild: 'build:vite-ci',\n\t\tbeforeTest: 'pnpm playwright install chromium',\n\t\ttest: 'test:vite-ci',\n\t})\n}\n"
  },
  {
    "path": "tests/astro.ts",
    "content": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function test(options: RunOptions) {\n\tawait runInRepo({\n\t\t...options,\n\t\trepo: 'withastro/astro',\n\t\tbranch: 'main',\n\t\tbuild: 'build:ci',\n\t\ttest: 'test:vite-ci',\n\t})\n}\n"
  },
  {
    "path": "tests/histoire.ts",
    "content": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function test(options: RunOptions) {\n\tawait runInRepo({\n\t\t...options,\n\t\trepo: 'histoire-dev/histoire',\n\t\tbranch: 'main',\n\t\tbuild: 'build',\n\t\ttest: ['test', 'test:examples'],\n\t})\n}\n\nexport const rolldownViteExpectedFailureReason = `\nneeds to be updated on histoire side (manualChunks)\n`\n"
  },
  {
    "path": "tests/hydrogen.ts",
    "content": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function test(options: RunOptions) {\n\tawait runInRepo({\n\t\t...options,\n\t\trepo: 'Shopify/hydrogen',\n\t\tbuild: 'build',\n\t\ttest: 'test:vite-ci',\n\t})\n}\n"
  },
  {
    "path": "tests/iles.ts",
    "content": "import { runInRepo, $ } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function test(options: RunOptions) {\n\tawait runInRepo({\n\t\t...options,\n\t\trepo: 'ElMassimo/iles',\n\t\toverrides: {\n\t\t\t'@vitejs/plugin-vue': true,\n\t\t},\n\t\tbeforeInstall: async () => $`git lfs install && git lfs pull`,\n\t\tbuild: 'build:all',\n\t\ttest: 'test',\n\t})\n}\n"
  },
  {
    "path": "tests/ladle.ts",
    "content": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function test(options: RunOptions) {\n\tawait runInRepo({\n\t\t...options,\n\t\trepo: 'tajo/ladle',\n\t\tbranch: 'main',\n\t\tbuild: 'build',\n\t\tbeforeTest: 'pnpm playwright install chromium',\n\t\ttest: 'test',\n\t})\n}\n"
  },
  {
    "path": "tests/laravel.ts",
    "content": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\nimport path from 'node:path'\nimport fs from 'node:fs'\n\nexport async function test(options: RunOptions) {\n\t//see https://github.com/laravel/vite-plugin/blob/73466441b0c9eb0c1a5ce0a0e937bd83eaef4b70/.github/workflows/tests.yml#L10\n\tprocess.env.LARAVEL_BYPASS_ENV_CHECK = '1'\n\tawait runInRepo({\n\t\t...options,\n\t\trepo: 'laravel/vite-plugin',\n\t\tbranch: '2.x',\n\t\tbuild: 'build',\n\t\tasync beforeTest() {\n\t\t\t// Add `vitest.config.ts` to exclude Vite from inlined by Vitest.\n\t\t\t// Otherwise the mock here doesn't work.\n\t\t\t// https://github.com/laravel/vite-plugin/blob/3f7bf9eddc69580796c26890c99065d7259c785e/tests/index.test.ts#L7-L22\n\t\t\tconst dir = path.resolve(options.workspace, 'vite-plugin')\n\t\t\tconst vitestConfigFile = path.join(dir, 'vitest.config.ts')\n\t\t\tfs.writeFileSync(\n\t\t\t\tvitestConfigFile,\n\t\t\t\tgetVitestConfig(options.vitePath),\n\t\t\t\t'utf-8',\n\t\t\t)\n\t\t},\n\t\ttest: 'test',\n\t\tagent: 'npm',\n\t})\n}\n\nconst getVitestConfig = (viteRepoPath: string) => /* ts */ `\nimport { defineConfig } from \"vitest/config\";\n\nexport default defineConfig({\n    test: {\n        deps: {\n            moduleDirectories: [\n                ${JSON.stringify(path.resolve(viteRepoPath, 'packages'))},\n            ],\n        },\n    },\n});\n`\n"
  },
  {
    "path": "tests/marko.ts",
    "content": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function test(options: RunOptions) {\n\tawait runInRepo({\n\t\t...options,\n\t\trepo: 'marko-js/vite',\n\t\tdir: 'marko', // default is last segment of repo, which would be vite and confusing\n\t\tbuild: 'build',\n\t\tbeforeTest: 'pnpm playwright install chromium',\n\t\ttest: 'test',\n\t\toverrides: {\n\t\t\tesbuild: true,\n\t\t},\n\t})\n}\n"
  },
  {
    "path": "tests/nuxt.ts",
    "content": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function test(options: RunOptions) {\n\tawait runInRepo({\n\t\t...options,\n\t\trepo: 'nuxt/nuxt',\n\t\toverrides: {\n\t\t\t'@vitejs/plugin-vue': true,\n\t\t},\n\t\tbuild: ['dev:prepare', 'build'],\n\t\tbeforeTest: 'pnpm playwright-core install',\n\t\ttest: ['test:fixtures', 'test:fixtures:dev', 'test:types'],\n\t})\n}\n"
  },
  {
    "path": "tests/nx.ts",
    "content": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function test(options: RunOptions) {\n\tawait runInRepo({\n\t\t...options,\n\t\trepo: 'nrwl/nx',\n\t\tbranch: 'master',\n\t\tbuild: { script: 'build-project', args: ['vite', '--skip-nx-cache'] },\n\t\ttest: [\n\t\t\t{ script: 'test', args: ['vite', '--skip-nx-cache'] },\n\t\t\t{ script: 'e2e', args: ['e2e-vite', '--skip-nx-cache'] },\n\t\t],\n\t\toverrides: {\n\t\t\trollup: false,\n\t\t},\n\t})\n}\n"
  },
  {
    "path": "tests/one.ts",
    "content": "import type { RunOptions } from '../types.d.ts'\nimport { runInRepo } from '../utils.ts'\n\nexport async function test(options: RunOptions) {\n\tawait runInRepo({\n\t\t...options,\n\t\trepo: 'onejs/one',\n\t\tbranch: 'main',\n\t\tbuild: ['clean:build', 'build'],\n\t\tbeforeTest: 'yarn playwright install chromium',\n\t\ttest: 'test:vite-ecosystem-ci',\n\t})\n}\n\nexport const rolldownViteExpectedFailureReason = `\nneeds to be updated on one side (type incompatibility)\n`\n"
  },
  {
    "path": "tests/quasar.ts",
    "content": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function test(options: RunOptions) {\n\tawait runInRepo({\n\t\t...options,\n\t\trepo: 'quasarframework/quasar',\n\t\tbranch: 'dev',\n\t\toverrides: {\n\t\t\t'@vitejs/plugin-vue': true,\n\t\t},\n\t\tbuild: 'vite-ecosystem-ci:build',\n\t\ttest: 'vite-ecosystem-ci:test',\n\t})\n}\n"
  },
  {
    "path": "tests/qwik.ts",
    "content": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function test(options: RunOptions) {\n\tawait runInRepo({\n\t\t...options,\n\t\trepo: 'BuilderIO/qwik',\n\t\tbuild: 'build.vite',\n\t\tbeforeTest: 'pnpm playwright install chromium',\n\t\ttest: 'test.vite',\n\t})\n}\n"
  },
  {
    "path": "tests/rakkas.ts",
    "content": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\nimport { execSync } from 'node:child_process'\n\nexport async function test(options: RunOptions) {\n\tawait runInRepo({\n\t\t...options,\n\t\trepo: 'rakkasjs/rakkasjs',\n\t\tbranch: 'main',\n\t\tbuild: 'build',\n\t\t// This is needed to run puppeteer in Ubuntu 23+\n\t\t// https://github.com/puppeteer/puppeteer/pull/13196\n\t\tbeforeTest: [\n\t\t\tprocess.env.GITHUB_ACTIONS\n\t\t\t\t? async () => {\n\t\t\t\t\t\texecSync(\n\t\t\t\t\t\t\t'echo 0 | sudo tee /proc/sys/kernel/apparmor_restrict_unprivileged_userns',\n\t\t\t\t\t\t)\n\t\t\t\t\t}\n\t\t\t\t: null,\n\t\t\t'pnpm --dir testbed/examples exec puppeteer browsers install chrome',\n\t\t].filter((x) => x != null),\n\t\ttest: 'vite-ecosystem-ci',\n\t})\n}\n\nexport const rolldownViteExpectedFailureReason = `\nneeds to be updated on rakkas side (\"moduleResolution\" should be \"bundler\" or \"nodenext\")\n`\n"
  },
  {
    "path": "tests/react-router.ts",
    "content": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function test(options: RunOptions) {\n\tawait runInRepo({\n\t\t...options,\n\t\trepo: 'remix-run/react-router',\n\t\tbranch: 'dev',\n\t\tbuild: 'vite-ecosystem-ci:build',\n\t\tbeforeTest: 'vite-ecosystem-ci:before-test',\n\t\ttest: 'vite-ecosystem-ci:test',\n\t\toverrides: {\n\t\t\t'@vitejs/plugin-rsc': true,\n\t\t},\n\t})\n}\n\nexport const rolldownViteExpectedFailureReason = `\nneeds to be updated on react-router side (incorrect tests)\n`\n"
  },
  {
    "path": "tests/redwoodjs.ts",
    "content": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function test(options: RunOptions) {\n\tawait runInRepo({\n\t\t...options,\n\t\trepo: 'redwoodjs/redwood',\n\t\tbuild: { script: 'build', args: ['--skip-nx-cache'] },\n\t\ttest: { script: 'test-ci', args: ['--skip-nx-cache'] },\n\t})\n}\n"
  },
  {
    "path": "tests/storybook.ts",
    "content": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function test(options: RunOptions) {\n\tawait runInRepo({\n\t\t...options,\n\t\trepo: 'storybookjs/storybook',\n\t\tbranch: 'next',\n\t\tbuild: 'vite-ecosystem-ci:build',\n\t\tbeforeTest: 'vite-ecosystem-ci:before-test',\n\t\ttest: 'vite-ecosystem-ci:test',\n\t})\n}\n"
  },
  {
    "path": "tests/sveltekit.ts",
    "content": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function test(options: RunOptions) {\n\tawait runInRepo({\n\t\t...options,\n\t\trepo: 'sveltejs/kit',\n\t\tbranch: 'main',\n\t\toverrides: {\n\t\t\tsvelte: 'latest',\n\t\t\t'@sveltejs/vite-plugin-svelte': true,\n\t\t\t'@sveltejs/vite-plugin-svelte-inspector': true,\n\t\t},\n\t\tbeforeTest: 'pnpm playwright install chromium',\n\t\ttest: [\n\t\t\t'test:vite-ecosystem-ci',\n\t\t\t'pnpm --dir packages/kit check', // only run checks for kit package, not the whole repo\n\t\t],\n\t})\n}\n"
  },
  {
    "path": "tests/tanstack-start.ts",
    "content": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function test(options: RunOptions) {\n\tawait runInRepo({\n\t\t...options,\n\t\trepo: 'TanStack/router',\n\t\tbranch: 'main',\n\t\tbuild: 'vite-ecosystem-ci:build',\n\t\tbeforeTest: 'vite-ecosystem-ci:before-test',\n\t\ttest: 'vite-ecosystem-ci:test',\n\t})\n}\n"
  },
  {
    "path": "tests/unocss.ts",
    "content": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function test(options: RunOptions) {\n\tawait runInRepo({\n\t\t...options,\n\t\trepo: 'unocss/unocss',\n\t\tbuild: 'build',\n\t\ttest: 'test',\n\t})\n}\n"
  },
  {
    "path": "tests/vike.ts",
    "content": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function test(options: RunOptions) {\n\tawait runInRepo({\n\t\t...options,\n\t\trepo: 'vikejs/vike',\n\t\tbranch: 'main',\n\t\toverrides: {\n\t\t\t'@vitejs/plugin-react': true,\n\t\t\t'@vitejs/plugin-react-swc': true,\n\t\t\t'@vitejs/plugin-vue': true,\n\t\t},\n\t\tbuild: 'build',\n\t\tbeforeTest: 'pnpm exec playwright install chromium',\n\t\ttest: 'test:vite-ecosystem-ci',\n\t})\n}\n"
  },
  {
    "path": "tests/vite-environment-examples.ts",
    "content": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function test(options: RunOptions) {\n\tif (options.viteMajor < 6) {\n\t\treturn\n\t}\n\tawait runInRepo({\n\t\t...options,\n\t\trepo: 'hi-ogawa/vite-environment-examples',\n\t\tbranch: 'main',\n\t\tbuild: 'vite-ecosystem-ci:build',\n\t\tbeforeTest: 'vite-ecosystem-ci:before-test',\n\t\ttest: 'vite-ecosystem-ci:test',\n\t})\n}\n"
  },
  {
    "path": "tests/vite-plugin-cloudflare.ts",
    "content": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function test(options: RunOptions) {\n\tawait runInRepo({\n\t\t...options,\n\t\trepo: 'cloudflare/workers-sdk',\n\t\ttest: 'pnpm test:ci -F @vite-plugin-cloudflare/playground',\n\t})\n}\n"
  },
  {
    "path": "tests/vite-plugin-laravel.ts",
    "content": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function test(options: RunOptions) {\n\tawait runInRepo({\n\t\t...options,\n\t\trepo: 'innocenzi/laravel-vite',\n\t\tbuild: 'build',\n\t\ttest: 'test:vite',\n\t})\n}\n"
  },
  {
    "path": "tests/vite-plugin-pwa.ts",
    "content": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function test(options: RunOptions) {\n\tawait runInRepo({\n\t\t...options,\n\t\trepo: 'vite-pwa/vite-plugin-pwa',\n\t\tbranch: 'main',\n\t\tbeforeTest: 'pnpm playwright install chromium',\n\t\tbuild: 'build',\n\t\ttest: 'test:vite-ecosystem-ci',\n\t})\n}\n"
  },
  {
    "path": "tests/vite-plugin-react.ts",
    "content": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function test(options: RunOptions) {\n\tawait runInRepo({\n\t\t...options,\n\t\trepo: 'vitejs/vite-plugin-react',\n\t\tbuild: 'build',\n\t\tbeforeTest: 'pnpm playwright install chromium',\n\t\ttest: ['test', 'typecheck'],\n\t})\n}\n"
  },
  {
    "path": "tests/vite-plugin-rsc.ts",
    "content": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function test(options: RunOptions) {\n\tawait runInRepo({\n\t\t...options,\n\t\trepo: 'vitejs/vite-plugin-react',\n\t\tbuild: 'build',\n\t\tbeforeTest: 'pnpm playwright install chromium',\n\t\ttest: [\n\t\t\t'pnpm -C packages/plugin-rsc test-e2e',\n\t\t\t'pnpm -C packages/plugin-rsc tsc',\n\t\t],\n\t})\n}\n"
  },
  {
    "path": "tests/vite-plugin-svelte.ts",
    "content": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function test(options: RunOptions) {\n\tawait runInRepo({\n\t\t...options,\n\t\trepo: 'sveltejs/vite-plugin-svelte',\n\t\tbranch: options.viteMajor < 8 ? 'v6' : 'main',\n\t\tbeforeTest: 'pnpm playwright install chromium',\n\t\ttest: ['check:lint', 'check:types', 'test'],\n\t})\n}\n"
  },
  {
    "path": "tests/vite-plugin-vue.ts",
    "content": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function test(options: RunOptions) {\n\tawait runInRepo({\n\t\t...options,\n\t\trepo: 'vitejs/vite-plugin-vue',\n\t\tbuild: 'build',\n\t\tbeforeTest: 'pnpm playwright install chromium',\n\t\ttest: 'test',\n\t})\n}\n"
  },
  {
    "path": "tests/vite-setup-catalogue.ts",
    "content": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function test(options: RunOptions) {\n\tawait runInRepo({\n\t\t...options,\n\t\trepo: 'sapphi-red/vite-setup-catalogue',\n\t\tbranch: 'main',\n\t\ttest: 'test-for-ecosystem-ci',\n\t})\n}\n"
  },
  {
    "path": "tests/vitepress.ts",
    "content": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function test(options: RunOptions) {\n\tawait runInRepo({\n\t\t...options,\n\t\trepo: 'vuejs/vitepress',\n\t\toverrides: {\n\t\t\t'@vitejs/plugin-vue': true,\n\t\t},\n\t\tbranch: 'main',\n\t\tbuild: 'build',\n\t\tbeforeTest: 'pnpm playwright install chromium',\n\t\ttest: 'test',\n\t})\n}\n"
  },
  {
    "path": "tests/vitest.ts",
    "content": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function test(options: RunOptions) {\n\tawait runInRepo({\n\t\t...options,\n\t\trepo: 'vitest-dev/vitest',\n\t\tbuild: 'build',\n\t\ttest: ['test:ecosystem-ci', 'test:examples'],\n\t\tbeforeTest: 'pnpm playwright install chromium',\n\t})\n}\n"
  },
  {
    "path": "tests/vuepress.ts",
    "content": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function test(options: RunOptions) {\n\tawait runInRepo({\n\t\t...options,\n\t\trepo: 'vuepress/core',\n\t\toverrides: {\n\t\t\t'@vitejs/plugin-vue': true,\n\t\t},\n\t\tbranch: 'main',\n\t\tbuild: 'build',\n\t\tbeforeTest: 'pnpm --filter e2e exec playwright install chromium',\n\t\ttest: 'test',\n\t})\n}\n"
  },
  {
    "path": "tests/waku.ts",
    "content": "import { runInRepo } from '../utils.ts'\nimport type { RunOptions } from '../types.d.ts'\n\nexport async function test(options: RunOptions) {\n\tawait runInRepo({\n\t\t...options,\n\t\trepo: 'dai-shi/waku',\n\t\tbranch: 'main',\n\t\tbuild: 'compile',\n\t\tbeforeTest: 'pnpm playwright install chromium',\n\t\ttest: 'test-vite-ecosystem-ci',\n\t\toverrides: {\n\t\t\t'@vitejs/plugin-rsc': true,\n\t\t\t// It uses Vitest 3.2+ so we don't need to inject the overrides.\n\t\t\t// If we inject overrides, the following error happens due to how waku sets overrides for the test.\n\t\t\t//\n\t\t\t// npm error code EINVALIDTAGNAME\n\t\t\t// 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.\n\t\t\tvitest: false,\n\t\t},\n\t})\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n\t\"include\": [\"./**/*.ts\"],\n\t\"exclude\": [\"**/node_modules/**\", \"./workspace/**\"],\n\t\"compilerOptions\": {\n\t\t\"target\": \"esnext\",\n\t\t\"module\": \"esnext\",\n\t\t\"moduleResolution\": \"bundler\",\n\t\t\"allowImportingTsExtensions\": true,\n\t\t\"noEmit\": true,\n\t\t\"skipLibCheck\": true,\n\t\t\"strict\": true,\n\t\t\"declaration\": true,\n\t\t\"noImplicitOverride\": true,\n\t\t\"noUnusedLocals\": true,\n\t\t\"esModuleInterop\": true,\n\t\t\"useUnknownInCatchVariables\": false,\n\t\t\"allowSyntheticDefaultImports\": true,\n\t\t\"lib\": [\"esnext\"],\n\t\t\"sourceMap\": true,\n\t\t\"erasableSyntaxOnly\": true,\n\t\t\"verbatimModuleSyntax\": true\n\t}\n}\n"
  },
  {
    "path": "types.d.ts",
    "content": "import type { AGENTS } from '@antfu/ni'\nexport interface EnvironmentData {\n\troot: string\n\tworkspace: string\n\tvitePath: string\n\tcwd: string\n\tenv: ProcessEnv\n}\n\nexport interface RunOptions {\n\tworkspace: string\n\troot: string\n\tvitePath: string\n\tviteMajor: number\n\tverify?: boolean\n\tskipGit?: boolean\n\trelease?: string\n\trolldownRelease?: string\n\tagent?: (typeof AGENTS)[number]\n\tbuild?: Task | Task[]\n\ttest?: Task | Task[]\n\tbeforeInstall?: Task | Task[]\n\tbeforeBuild?: Task | Task[]\n\tbeforeTest?: Task | Task[]\n}\n\ntype Task = string | { script: string; args?: string[] } | (() => Promise<any>)\n\nexport interface CommandOptions {\n\tsuites?: string[]\n\trepo?: string\n\tbranch?: string\n\ttag?: string\n\tcommit?: string\n\trelease?: string\n\tverify?: boolean\n\tskipGit?: boolean\n\trolldownRef?: string\n}\n\nexport interface RepoOptions {\n\trepo: string\n\tdir?: string\n\tbranch?: string\n\ttag?: string\n\tcommit?: string\n\tshallow?: boolean\n\toverrides?: Overrides\n}\n\nexport interface Overrides {\n\t[key: string]: string | boolean\n}\n\nexport interface ProcessEnv {\n\t[key: string]: string | undefined\n}\n\ninterface DependencyInfo {\n\tfrom: string\n\tversion: string\n\tresolved: string\n\tpath: string\n}\ninterface PackageInfo {\n\tname: string\n\tversion: string\n\tpath: string\n\tprivate: boolean\n\tdependencies: Record<string, DependencyInfo>\n\tdevDependencies: Record<string, DependencyInfo>\n\toptionalDependencies: Record<string, DependencyInfo>\n}\n"
  },
  {
    "path": "utils.ts",
    "content": "import path from 'path'\nimport fs from 'fs'\nimport { fileURLToPath, pathToFileURL } from 'url'\nimport { execaCommand } from 'execa'\nimport type {\n\tPackageInfo,\n\tEnvironmentData,\n\tOverrides,\n\tProcessEnv,\n\tRepoOptions,\n\tRunOptions,\n\tTask,\n} from './types.d.ts'\nimport { detect, AGENTS, getCommand, serializeCommand } from '@antfu/ni'\nimport * as actionsCore from '@actions/core'\nimport * as semver from 'semver'\nimport pacote from 'pacote'\n\nconst isGitHubActions = !!process.env.GITHUB_ACTIONS\n\nlet vitePath: string\nlet cwd: string\nlet env: ProcessEnv\n\nfunction cd(dir: string) {\n\tcwd = path.resolve(cwd, dir)\n}\n\nexport async function $(literals: TemplateStringsArray, ...values: any[]) {\n\tconst cmd = literals.reduce(\n\t\t(result, current, i) =>\n\t\t\tresult + current + (values?.[i] != null ? `${values[i]}` : ''),\n\t\t'',\n\t)\n\n\tif (isGitHubActions) {\n\t\tactionsCore.startGroup(`${cwd} $> ${cmd}`)\n\t} else {\n\t\tconsole.log(`${cwd} $> ${cmd}`)\n\t}\n\n\tconst proc = execaCommand(cmd, {\n\t\tenv,\n\t\tstdio: 'pipe',\n\t\tcwd,\n\t})\n\tif (proc.stdin) process.stdin.pipe(proc.stdin)\n\tif (proc.stdout) proc.stdout.pipe(process.stdout)\n\tif (proc.stderr) proc.stderr.pipe(process.stderr)\n\n\tlet result\n\ttry {\n\t\tresult = await proc\n\t} catch (error) {\n\t\t// Since we already piped the io to the parent process, we remove the duplicated\n\t\t// messages here so it's easier to read the error message.\n\t\tif (error.stdout) error.stdout = 'value removed by vite-ecosystem-ci'\n\t\tif (error.stderr) error.stderr = 'value removed by vite-ecosystem-ci'\n\t\tif (error.stdio) error.stdio = ['value removed by vite-ecosystem-ci']\n\t\tthrow error\n\t}\n\n\tif (isGitHubActions) {\n\t\tactionsCore.endGroup()\n\t}\n\n\treturn result.stdout\n}\n\nexport async function setupEnvironment(): Promise<EnvironmentData> {\n\tconst root = dirnameFrom(import.meta.url)\n\tconst workspace = path.resolve(root, 'workspace')\n\tvitePath = path.resolve(workspace, 'vite')\n\tcwd = process.cwd()\n\tenv = {\n\t\t...process.env,\n\t\tCI: 'true',\n\t\tTURBO_FORCE: 'true', // disable turbo caching, ecosystem-ci modifies things and we don't want replays\n\t\tYARN_ENABLE_IMMUTABLE_INSTALLS: 'false', // to avoid errors with mutated lockfile due to overrides\n\t\tNODE_OPTIONS: '--max-old-space-size=6144', // GITHUB CI has 7GB max, stay below\n\t\tECOSYSTEM_CI: 'true', // flag for tests, can be used to conditionally skip irrelevant tests.\n\t\tTURBO_TELEMETRY_DISABLED: '1', //   # see https://turbo.build/repo/docs/telemetry#how-do-i-opt-out\n\t\tDO_NOT_TRACK: '1',\n\t}\n\tinitWorkspace(workspace)\n\treturn { root, workspace, vitePath, cwd, env }\n}\n\nfunction initWorkspace(workspace: string) {\n\tif (!fs.existsSync(workspace)) {\n\t\tfs.mkdirSync(workspace, { recursive: true })\n\t}\n\tconst eslintrc = path.join(workspace, '.eslintrc.json')\n\tif (!fs.existsSync(eslintrc)) {\n\t\tfs.writeFileSync(eslintrc, '{\"root\":true}\\n', 'utf-8')\n\t}\n\tconst editorconfig = path.join(workspace, '.editorconfig')\n\tif (!fs.existsSync(editorconfig)) {\n\t\tfs.writeFileSync(editorconfig, 'root = true\\n', 'utf-8')\n\t}\n\tconst tsconfig = path.join(workspace, 'tsconfig.json')\n\tif (!fs.existsSync(tsconfig)) {\n\t\tfs.writeFileSync(tsconfig, '{}\\n', 'utf-8')\n\t}\n}\n\nexport async function setupRepo(options: RepoOptions) {\n\tif (options.branch == null) {\n\t\toptions.branch = 'main'\n\t}\n\tif (options.shallow == null) {\n\t\toptions.shallow = true\n\t}\n\n\tlet { repo, commit, branch, tag, dir, shallow } = options\n\tif (!dir) {\n\t\tthrow new Error('setupRepo must be called with options.dir')\n\t}\n\tif (!repo.includes(':')) {\n\t\trepo = `https://github.com/${repo}.git`\n\t}\n\n\tlet needClone = true\n\tif (fs.existsSync(dir)) {\n\t\tconst _cwd = cwd\n\t\tcd(dir)\n\t\tlet currentClonedRepo: string | undefined\n\t\ttry {\n\t\t\tcurrentClonedRepo = await $`git ls-remote --get-url`\n\t\t} catch {\n\t\t\t// when not a git repo\n\t\t}\n\t\tif (repo === currentClonedRepo) {\n\t\t\tconst isShallow =\n\t\t\t\t(await $`git rev-parse --is-shallow-repository`).trim() === 'true'\n\t\t\tif (isShallow === shallow) {\n\t\t\t\tneedClone = false\n\t\t\t}\n\t\t}\n\t\tcd(_cwd)\n\n\t\tif (needClone) {\n\t\t\tfs.rmSync(dir, { recursive: true, force: true })\n\t\t}\n\t}\n\n\tif (needClone) {\n\t\tawait $`git -c advice.detachedHead=false clone ${\n\t\t\tshallow ? '--depth=1 --no-tags' : ''\n\t\t} --branch ${tag || branch} ${repo} ${dir}`\n\t}\n\tcd(dir)\n\tawait $`git clean -fdxq`\n\tif (!needClone && shallow && !commit) {\n\t\tawait $`git remote set-branches origin ${branch}`\n\t}\n\tawait $`git fetch ${shallow ? '--depth=1 --no-tags' : '--tags'} origin ${\n\t\ttag ? `tag ${tag}` : `${commit || branch}`\n\t}`\n\tif (shallow) {\n\t\tawait $`git -c advice.detachedHead=false checkout ${\n\t\t\ttag ? `tags/${tag}` : `${commit || branch}`\n\t\t}`\n\t} else {\n\t\tawait $`git checkout ${branch}`\n\t\tawait $`git merge FETCH_HEAD`\n\t\tif (tag || commit) {\n\t\t\tawait $`git reset --hard ${tag || commit}`\n\t\t}\n\t}\n}\n\nfunction toCommand(\n\ttask: Task | Task[] | void,\n\tagent: (typeof AGENTS)[number],\n): ((scripts: any) => Promise<any>) | void {\n\treturn async (scripts: any) => {\n\t\tconst tasks = Array.isArray(task) ? task : [task]\n\t\tfor (const task of tasks) {\n\t\t\tif (task == null || task === '') {\n\t\t\t\tcontinue\n\t\t\t} else if (typeof task === 'string') {\n\t\t\t\tif (scripts[task] != null) {\n\t\t\t\t\tconst runTaskWithAgent = getCommand(agent, 'run', [task])\n\t\t\t\t\tawait $`${serializeCommand(runTaskWithAgent)}`\n\t\t\t\t} else {\n\t\t\t\t\tawait $`${task}`\n\t\t\t\t}\n\t\t\t} else if (typeof task === 'function') {\n\t\t\t\tawait task()\n\t\t\t} else if (task?.script) {\n\t\t\t\tif (scripts[task.script] != null) {\n\t\t\t\t\tconst runTaskWithAgent = getCommand(agent, 'run', [\n\t\t\t\t\t\ttask.script,\n\t\t\t\t\t\t...(task.args ?? []),\n\t\t\t\t\t])\n\t\t\t\t\tawait $`${serializeCommand(runTaskWithAgent)}`\n\t\t\t\t} else {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`invalid task, script \"${task.script}\" does not exist in package.json`,\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`invalid task, expected string or function but got ${typeof task}: ${task}`,\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t}\n}\n\nexport async function runInRepo(options: RunOptions & RepoOptions) {\n\tif (options.verify == null) {\n\t\toptions.verify = true\n\t}\n\tif (options.skipGit == null) {\n\t\toptions.skipGit = false\n\t}\n\tif (options.branch == null) {\n\t\toptions.branch = 'main'\n\t}\n\n\tconst {\n\t\tbuild,\n\t\ttest,\n\t\trepo,\n\t\tbranch,\n\t\ttag,\n\t\tcommit,\n\t\tskipGit,\n\t\tverify,\n\t\tbeforeInstall,\n\t\tbeforeBuild,\n\t\tbeforeTest,\n\t} = options\n\n\tconst dir = path.resolve(\n\t\toptions.workspace,\n\t\toptions.dir || repo.substring(repo.lastIndexOf('/') + 1),\n\t)\n\n\tif (!skipGit) {\n\t\tawait setupRepo({ repo, dir, branch, tag, commit })\n\t} else {\n\t\tcd(dir)\n\t}\n\tif (options.agent == null) {\n\t\tconst detectedAgent = await detect({ cwd: dir, autoInstall: false })\n\t\tif (detectedAgent == null) {\n\t\t\tthrow new Error(`Failed to detect packagemanager in ${dir}`)\n\t\t}\n\t\toptions.agent = detectedAgent\n\t}\n\tif (!AGENTS.includes(options.agent)) {\n\t\tthrow new Error(\n\t\t\t`Invalid agent ${options.agent}. Allowed values: ${AGENTS.join(', ')}`,\n\t\t)\n\t}\n\tconst agent = options.agent\n\tconst beforeInstallCommand = toCommand(beforeInstall, agent)\n\tconst beforeBuildCommand = toCommand(beforeBuild, agent)\n\tconst beforeTestCommand = toCommand(beforeTest, agent)\n\tconst buildCommand = toCommand(build, agent)\n\tconst testCommand = toCommand(test, agent)\n\n\tconst pkgFile = path.join(dir, 'package.json')\n\tconst pkg = JSON.parse(await fs.promises.readFile(pkgFile, 'utf-8'))\n\n\tawait beforeInstallCommand?.(pkg.scripts)\n\n\tif (verify && test) {\n\t\tconst frozenInstall = getCommand(agent, 'frozen')\n\t\tawait $`${serializeCommand(frozenInstall)}`\n\t\tawait beforeBuildCommand?.(pkg.scripts)\n\t\tawait buildCommand?.(pkg.scripts)\n\t\tawait beforeTestCommand?.(pkg.scripts)\n\t\tawait testCommand?.(pkg.scripts)\n\t}\n\tlet overrides = options.overrides || {}\n\tif (options.release) {\n\t\tif (overrides.vite && overrides.vite !== options.release) {\n\t\t\tthrow new Error(\n\t\t\t\t`conflicting overrides.vite=${overrides.vite} and --release=${options.release} config. Use either one or the other`,\n\t\t\t)\n\t\t} else {\n\t\t\toverrides.vite = options.release\n\t\t}\n\n\t\tif (\n\t\t\toverrides.rollup !== false ||\n\t\t\toverrides.esbuild === true ||\n\t\t\toverrides.vitest !== false\n\t\t) {\n\t\t\tconst viteManifest = await pacote.manifest(`vite@${options.release}`, {\n\t\t\t\tretry: {\n\t\t\t\t\t// enable retry with same options with pnpm (https://pnpm.io/settings#fetchretries)\n\t\t\t\t\tretries: 2,\n\t\t\t\t\tfactor: 10,\n\t\t\t\t\tminTimeout: 10 * 1000,\n\t\t\t\t\tmaxTimeout: 60 * 1000,\n\t\t\t\t},\n\t\t\t})\n\n\t\t\t// skip if `overrides.rollup` is `false`\n\t\t\tif (overrides.rollup !== false) {\n\t\t\t\toverrides.rollup = viteManifest.dependencies!.rollup\n\t\t\t}\n\n\t\t\t// apply if `overrides.esbuild` is `true`\n\t\t\tif (overrides.esbuild === true) {\n\t\t\t\toverrides.esbuild = viteManifest.dependencies!.esbuild\n\t\t\t}\n\n\t\t\t// skip if `overrides.vitest` is `false`\n\t\t\tif (overrides.vitest !== false && agent === 'pnpm') {\n\t\t\t\toverrides['vitest@<3.2.0>vite'] = '^6.3.5'\n\t\t\t}\n\t\t}\n\t} else {\n\t\toverrides.vite ||= `${options.vitePath}/packages/vite`\n\n\t\toverrides[`@vitejs/plugin-legacy`] ||=\n\t\t\t`${options.vitePath}/packages/plugin-legacy`\n\n\t\tconst vitePackageInfo = await getVitePackageInfo(options.vitePath)\n\t\t// skip if `overrides.rollup` is `false`\n\t\tif (\n\t\t\tvitePackageInfo.dependencies.rollup?.version &&\n\t\t\toverrides.rollup !== false\n\t\t) {\n\t\t\toverrides.rollup = vitePackageInfo.dependencies.rollup.version\n\t\t}\n\n\t\t// apply if `overrides.esbuild` is `true`\n\t\tif (\n\t\t\tvitePackageInfo.dependencies.esbuild?.version &&\n\t\t\toverrides.esbuild === true\n\t\t) {\n\t\t\toverrides.esbuild = vitePackageInfo.dependencies.esbuild.version\n\t\t}\n\n\t\t// skip if `overrides.vitest` is `false`\n\t\tif (overrides.vitest !== false && agent === 'pnpm') {\n\t\t\toverrides['vitest@<3.2.0>vite'] = '^6.3.5'\n\t\t}\n\n\t\t// build and apply local overrides\n\t\tconst localOverrides = await buildOverrides(pkg, options, overrides)\n\t\tcd(dir) // buildOverrides changed dir, change it back\n\t\toverrides = {\n\t\t\t...overrides,\n\t\t\t...localOverrides,\n\t\t}\n\t}\n\tif (options.rolldownRelease) {\n\t\toverrides.rolldown = options.rolldownRelease\n\t}\n\tawait applyPackageOverrides(agent, dir, pkg, overrides)\n\tawait beforeBuildCommand?.(pkg.scripts)\n\tawait buildCommand?.(pkg.scripts)\n\tif (test) {\n\t\tawait beforeTestCommand?.(pkg.scripts)\n\t\tawait testCommand?.(pkg.scripts)\n\t}\n\treturn { dir }\n}\n\nexport async function setupViteRepo(options: Partial<RepoOptions>) {\n\tconst repo = options.repo || 'vitejs/vite'\n\tawait setupRepo({\n\t\trepo,\n\t\tdir: vitePath,\n\t\tbranch: 'main',\n\t\tshallow: true,\n\t\t...options,\n\t})\n\n\ttry {\n\t\tconst rootPackageJsonFile = path.join(vitePath, 'package.json')\n\t\tconst rootPackageJson = JSON.parse(\n\t\t\tawait fs.promises.readFile(rootPackageJsonFile, 'utf-8'),\n\t\t)\n\t\tconst viteMonoRepoNames = ['@vitejs/vite-monorepo', 'vite-monorepo']\n\t\tconst { name } = rootPackageJson\n\t\tif (!viteMonoRepoNames.includes(name)) {\n\t\t\tthrow new Error(\n\t\t\t\t`expected  \"name\" field of ${repo}/package.json to indicate vite monorepo, but got ${name}.`,\n\t\t\t)\n\t\t}\n\t\tconst needsWrite = await overridePackageManagerVersion(\n\t\t\trootPackageJson,\n\t\t\t'pnpm',\n\t\t)\n\t\tif (needsWrite) {\n\t\t\tfs.writeFileSync(\n\t\t\t\trootPackageJsonFile,\n\t\t\t\tJSON.stringify(rootPackageJson, null, 2),\n\t\t\t\t'utf-8',\n\t\t\t)\n\t\t\tif (rootPackageJson.devDependencies?.pnpm) {\n\t\t\t\tawait $`pnpm install -Dw pnpm --lockfile-only`\n\t\t\t}\n\t\t}\n\t} catch (e) {\n\t\tthrow new Error(`Failed to setup vite repo`, { cause: e })\n\t}\n}\n\nexport async function getPermanentRef() {\n\tcd(vitePath)\n\ttry {\n\t\tconst ref = await $`git log -1 --pretty=format:%H`\n\t\treturn ref\n\t} catch (e) {\n\t\tconsole.warn(`Failed to obtain perm ref. ${e}`)\n\t\treturn undefined\n\t}\n}\n\nexport async function buildVite({\n\tverify = false,\n\trolldownRelease,\n}: { verify?: boolean; rolldownRelease?: string } = {}) {\n\tcd(vitePath)\n\n\tconst frozenInstall = getCommand('pnpm', 'frozen')\n\tconst runBuild = getCommand('pnpm', 'run', ['build'])\n\tconst runTest = getCommand('pnpm', 'run', ['test'])\n\n\tif (rolldownRelease) {\n\t\tconst pkgFile = path.join(vitePath, 'package.json')\n\t\tconst pkg = JSON.parse(await fs.promises.readFile(pkgFile, 'utf-8'))\n\t\t// Override rolldown in vite's monorepo so it builds against the specified version\n\t\tawait applyPackageOverrides('pnpm', vitePath, pkg, {\n\t\t\trolldown: rolldownRelease,\n\t\t})\n\t\tconsole.log(`overridden rolldown in vite repo with ${rolldownRelease}`)\n\t} else {\n\t\tawait $`${serializeCommand(frozenInstall)}`\n\t}\n\tawait $`${serializeCommand(runBuild)}`\n\tif (verify) {\n\t\tawait $`${serializeCommand(runTest)}`\n\t}\n}\n\nexport async function bisectVite(\n\tgood: string,\n\trunSuite: () => Promise<Error | void>,\n) {\n\t// sometimes vite build modifies files in git, e.g. LICENSE.md\n\t// this would stop bisect, so to reset those changes\n\tconst resetChanges = async () => $`git reset --hard HEAD`\n\n\ttry {\n\t\tcd(vitePath)\n\t\tawait resetChanges()\n\t\tawait $`git bisect start`\n\t\tawait $`git bisect bad`\n\t\tawait $`git bisect good ${good}`\n\t\tlet bisecting = true\n\t\twhile (bisecting) {\n\t\t\tconst commitMsg = await $`git log -1 --format=%s`\n\t\t\tconst isNonCodeCommit = commitMsg.match(/^(?:release|docs)[:(]/)\n\t\t\tif (isNonCodeCommit) {\n\t\t\t\tawait $`git bisect skip`\n\t\t\t\tcontinue // see if next commit can be skipped too\n\t\t\t}\n\t\t\tconst error = await runSuite()\n\t\t\tcd(vitePath)\n\t\t\tawait resetChanges()\n\t\t\tconst bisectOut = await $`git bisect ${error ? 'bad' : 'good'}`\n\t\t\tbisecting = bisectOut.substring(0, 10).toLowerCase() === 'bisecting:' // as long as git prints 'bisecting: ' there are more revisions to test\n\t\t}\n\t} catch (e) {\n\t\tconsole.log('error while bisecting', e)\n\t} finally {\n\t\ttry {\n\t\t\tcd(vitePath)\n\t\t\tawait $`git bisect reset`\n\t\t} catch (e) {\n\t\t\tconsole.log('Error while resetting bisect', e)\n\t\t}\n\t}\n}\n\nfunction isLocalOverride(v: string): boolean {\n\tif (!v.includes('/') || v.startsWith('@')) {\n\t\t// not path-like (either a version number or a package name)\n\t\treturn false\n\t}\n\ttry {\n\t\treturn !!fs.lstatSync(v)?.isDirectory()\n\t} catch (e) {\n\t\tif (e.code !== 'ENOENT') {\n\t\t\tthrow e\n\t\t}\n\t\treturn false\n\t}\n}\n\n/**\n * utility to override packageManager version\n *\n * @param pkg parsed package.json\n * @param pm package manager to override eg. `pnpm`\n * @returns {boolean} true if pkg was updated, caller is responsible for writing it to disk\n */\nasync function overridePackageManagerVersion(\n\tpkg: { [key: string]: any },\n\tpm: string,\n): Promise<boolean> {\n\tconst versionInUse = pkg.packageManager?.startsWith(`${pm}@`)\n\t\t? pkg.packageManager.substring(pm.length + 1)\n\t\t: await $`${pm} --version`\n\tlet overrideWithVersion: string | null = null\n\tif (pm === 'pnpm') {\n\t\tif (semver.eq(versionInUse, '7.18.0')) {\n\t\t\t// avoid bug with absolute overrides in pnpm 7.18.0\n\t\t\toverrideWithVersion = '7.18.1'\n\t\t}\n\t}\n\tif (overrideWithVersion) {\n\t\tconsole.warn(\n\t\t\t`detected ${pm}@${versionInUse} used in ${pkg.name}, changing pkg.packageManager and pkg.engines.${pm} to enforce use of ${pm}@${overrideWithVersion}`,\n\t\t)\n\t\t// corepack reads this and uses pnpm @ newVersion then\n\t\tpkg.packageManager = `${pm}@${overrideWithVersion}`\n\t\tif (!pkg.engines) {\n\t\t\tpkg.engines = {}\n\t\t}\n\t\tpkg.engines[pm] = overrideWithVersion\n\n\t\tif (pkg.devDependencies?.[pm]) {\n\t\t\t// if for some reason the pm is in devDependencies, that would be a local version that'd be preferred over our forced global\n\t\t\t// so ensure it here too.\n\t\t\tpkg.devDependencies[pm] = overrideWithVersion\n\t\t}\n\n\t\treturn true\n\t}\n\treturn false\n}\n\nexport async function applyPackageOverrides(\n\tagent: (typeof AGENTS)[number],\n\tdir: string,\n\tpkg: any,\n\toverrides: Overrides = {},\n) {\n\tconst useFileProtocol = (v: string) =>\n\t\tisLocalOverride(v) ? `file:${path.resolve(v)}` : v\n\t// remove boolean flags\n\toverrides = Object.fromEntries(\n\t\tObject.entries(overrides)\n\t\t\t//eslint-disable-next-line @typescript-eslint/no-unused-vars\n\t\t\t.filter(([key, value]) => typeof value === 'string')\n\t\t\t.map(([key, value]) => [key, useFileProtocol(value as string)]),\n\t)\n\tawait $`git clean -fdxq` // remove current install\n\n\t// Remove version from agent string:\n\t// yarn@berry => yarn\n\t// pnpm@6, pnpm@7 => pnpm\n\tconst pm = agent?.split('@')[0]\n\n\tawait overridePackageManagerVersion(pkg, pm)\n\n\tif (pm === 'pnpm') {\n\t\tconst overridesWithoutSpecialSyntax = Object.fromEntries(\n\t\t\tObject.entries(overrides)\n\t\t\t\t//eslint-disable-next-line @typescript-eslint/no-unused-vars\n\t\t\t\t.filter(([key, value]) => !key.includes('>')),\n\t\t)\n\n\t\tif (!pkg.devDependencies) {\n\t\t\tpkg.devDependencies = {}\n\t\t}\n\t\tpkg.devDependencies = {\n\t\t\t...pkg.devDependencies,\n\t\t\t...overridesWithoutSpecialSyntax, // overrides must be present in devDependencies or dependencies otherwise they may not work\n\t\t}\n\t\tif (!pkg.pnpm) {\n\t\t\tpkg.pnpm = {}\n\t\t}\n\t\tpkg.pnpm.overrides = {\n\t\t\t...pkg.pnpm.overrides,\n\t\t\t...overrides,\n\t\t}\n\t\t// check `overrides` in pnpm-workspace.yaml\n\t\tconst pnpmWorkspaceFile = path.join(dir, 'pnpm-workspace.yaml')\n\t\tif (fs.existsSync(pnpmWorkspaceFile)) {\n\t\t\tlet content = await fs.promises.readFile(pnpmWorkspaceFile, 'utf-8')\n\t\t\tlet modified = false\n\t\t\tif (/^overrides:/m.test(content)) {\n\t\t\t\tdelete pkg.pnpm.overrides // remove pnpm.overrides from package.json so that pnpm-workspace.yaml's one is used\n\t\t\t\t// merge with existing overrides\n\t\t\t\tconst output = await $`pnpm config list --json --location project`\n\t\t\t\tconst currentOverrides = JSON.parse(output).overrides\n\t\t\t\tconst mergedOverrides = { ...currentOverrides, ...overrides }\n\t\t\t\t// replace all indented lines in `overrides` section\n\t\t\t\tcontent = content.replace(\n\t\t\t\t\t/^overrides:\\n((?:[ \\t]+.+\\n)*)/m,\n\t\t\t\t\t() =>\n\t\t\t\t\t\t`overrides:\\n${Object.entries(mergedOverrides)\n\t\t\t\t\t\t\t.map(\n\t\t\t\t\t\t\t\t([name, version]) =>\n\t\t\t\t\t\t\t\t\t`  ${JSON.stringify(name)}: ${JSON.stringify(version)}\\n`,\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t.join('')}`,\n\t\t\t\t)\n\t\t\t\tmodified = true\n\t\t\t}\n\t\t\tif (content.includes('minimumReleaseAge:')) {\n\t\t\t\t// disable with comment to avoid error on installation if ecosystem-ci overrides pull in violating updates\n\t\t\t\tcontent = content.replace(\n\t\t\t\t\t/^([ \\t]*minimumReleaseAge[ \\t]*:[ \\t]*\\d+[^\\r\\n]*)$/m,\n\t\t\t\t\t'# $1 -- disabled by ecosystem-ci',\n\t\t\t\t)\n\t\t\t\tmodified = true\n\t\t\t}\n\t\t\tif (content.includes('blockExoticSubdeps:')) {\n\t\t\t\t// disable with comment to avoid error on installation if ecosystem-ci overrides pull in tarball URLs\n\t\t\t\tcontent = content.replace(\n\t\t\t\t\t/^([ \\t]*blockExoticSubdeps[ \\t]*:[ \\t]*\\w+[^\\r\\n]*)$/m,\n\t\t\t\t\t'# $1 -- disabled by ecosystem-ci',\n\t\t\t\t)\n\t\t\t\tmodified = true\n\t\t\t}\n\t\t\tif (modified) {\n\t\t\t\tawait fs.promises.writeFile(pnpmWorkspaceFile, content, 'utf-8')\n\t\t\t}\n\t\t}\n\t} else if (pm === 'yarn') {\n\t\tpkg.resolutions = {\n\t\t\t...pkg.resolutions,\n\t\t\t...overrides,\n\t\t}\n\t} else if (pm === 'npm') {\n\t\tpkg.overrides = {\n\t\t\t...pkg.overrides,\n\t\t\t...overrides,\n\t\t}\n\t\t// npm does not allow overriding direct dependencies, force it by updating the blocks themselves\n\t\tfor (const [name, version] of Object.entries(overrides)) {\n\t\t\tif (pkg.dependencies?.[name]) {\n\t\t\t\tpkg.dependencies[name] = version\n\t\t\t}\n\t\t\tif (pkg.devDependencies?.[name]) {\n\t\t\t\tpkg.devDependencies[name] = version\n\t\t\t}\n\t\t}\n\t} else {\n\t\tthrow new Error(`unsupported package manager detected: ${pm}`)\n\t}\n\tconst pkgFile = path.join(dir, 'package.json')\n\tawait fs.promises.writeFile(pkgFile, JSON.stringify(pkg, null, 2), 'utf-8')\n\n\t// use of `ni` command here could cause lockfile violation errors so fall back to native commands that avoid these\n\tif (pm === 'pnpm') {\n\t\tawait $`pnpm install --prefer-frozen-lockfile --strict-peer-dependencies false`\n\t} else if (pm === 'yarn') {\n\t\tawait $`yarn install`\n\t} else if (pm === 'npm') {\n\t\tawait $`npm install`\n\t}\n}\n\nexport function dirnameFrom(url: string) {\n\treturn path.dirname(fileURLToPath(url))\n}\n\nexport function parseViteMajor(vitePath: string): number {\n\tconst content = fs.readFileSync(\n\t\tpath.join(vitePath, 'packages', 'vite', 'package.json'),\n\t\t'utf-8',\n\t)\n\tconst pkg = JSON.parse(content)\n\treturn parseMajorVersion(pkg.version)\n}\n\nexport function parseMajorVersion(version: string) {\n\treturn parseInt(version.split('.', 1)[0], 10)\n}\n\nasync function buildOverrides(\n\tpkg: any,\n\toptions: RunOptions,\n\trepoOverrides: Overrides,\n) {\n\tconst { root } = options\n\tconst buildsPath = path.join(root, 'builds')\n\tconst buildFiles: string[] = fs\n\t\t.readdirSync(buildsPath)\n\t\t.filter((f: string) => !f.startsWith('_') && f.endsWith('.ts'))\n\t\t.map((f) => path.join(buildsPath, f))\n\tconst buildDefinitions: {\n\t\tpackages: { [key: string]: string }\n\t\tbuild: (options: RunOptions) => Promise<{ dir: string }>\n\t\tdir?: string\n\t}[] = await Promise.all(buildFiles.map((f) => import(pathToFileURL(f).href)))\n\tconst deps = new Set([\n\t\t...Object.keys(pkg.dependencies ?? {}),\n\t\t...Object.keys(pkg.devDependencies ?? {}),\n\t\t...Object.keys(pkg.peerDependencies ?? {}),\n\t])\n\n\tconst needsOverride = (p: string) =>\n\t\trepoOverrides[p] === true || (deps.has(p) && repoOverrides[p] == null)\n\tconst buildsToRun = buildDefinitions.filter(({ packages }) =>\n\t\tObject.keys(packages).some(needsOverride),\n\t)\n\tconst overrides: Overrides = {}\n\tfor (const buildDef of buildsToRun) {\n\t\tconst { dir } = await buildDef.build({\n\t\t\troot: options.root,\n\t\t\tworkspace: options.workspace,\n\t\t\tvitePath: options.vitePath,\n\t\t\tviteMajor: options.viteMajor,\n\t\t\tskipGit: options.skipGit,\n\t\t\trelease: options.release,\n\t\t\tverify: options.verify,\n\t\t\t// do not pass along scripts\n\t\t})\n\t\tfor (const [name, path] of Object.entries(buildDef.packages)) {\n\t\t\tif (needsOverride(name)) {\n\t\t\t\toverrides[name] = `${dir}/${path}`\n\t\t\t}\n\t\t}\n\t}\n\treturn overrides\n}\n\n/**\n * \tuse pnpm ls to get information about installed dependency versions of vite\n * @param vitePath - workspace vite root\n */\nasync function getVitePackageInfo(vitePath: string): Promise<PackageInfo> {\n\ttry {\n\t\t// run in vite dir to avoid package manager mismatch error from corepack\n\t\tconst current = cwd\n\t\tcd(`${vitePath}/packages/vite`)\n\t\tconst lsOutput = $`pnpm ls --json`\n\t\tcd(current)\n\t\tconst lsParsed = JSON.parse(await lsOutput)\n\t\treturn lsParsed[0] as PackageInfo\n\t} catch (e) {\n\t\tconsole.error('failed to retrieve vite package infos', e)\n\t\tthrow e\n\t}\n}\n"
  }
]