[
  {
    "path": ".eleventy.js",
    "content": "import EleventyVite from \"./EleventyVite.js\";\n\nimport path from \"node:path\";\nimport { createRequire } from \"module\";\nconst require = createRequire(import.meta.url);\nconst pkg = require(\"./package.json\");\n\n/**\n * Options which can be passed to eleventy-plugin-vite\n * @typedef {Object} EleventyViteOptions\n * @property {string} tempFolderName\n * @property {import(\"vite\").InlineConfig} [viteOptions]\n * @property {Object} [serverOptions]\n */\n\n/**\n * @param {import('@11ty/eleventy/src/UserConfig').default} eleventyConfig\n * @param {EleventyViteOptions} options\n */\nexport default function (eleventyConfig, options = {}) {\n\ttry {\n\t\televentyConfig.versionCheck(pkg[\"11ty\"].compatibility);\n\t} catch (error) {\n\t\televentyConfig.logger.warn(\n\t\t\t`Warning: Eleventy Plugin (${pkg.name}) Compatibility: ${error.message}`,\n\t\t);\n\t}\n\n\tconst eleventyVite = new EleventyVite(eleventyConfig, options);\n\n\tconst publicDir = eleventyVite.options.viteOptions?.publicDir || \"public\";\n\n\tif (!path.relative(eleventyConfig.directories.output, publicDir)) {\n\t\tthrow new Error(\n\t\t\t`${EleventyVite.LOGGER_PREFIX} Misconfiguration: Can't use the same directory for 11ty output and vite public directory`,\n\t\t);\n\t}\n\n\t// Add publicDir to passthrough copy\n\televentyConfig.addPassthroughCopy(publicDir);\n\n\t// Add tempFolder to ignores\n\televentyConfig.ignores.add(eleventyVite.getIgnoreDirectory());\n\n\tconst serverOptions = Object.assign(\n\t\t{\n\t\t\tmodule: \"@11ty/eleventy-dev-server\",\n\t\t\tdomDiff: false,\n\t\t},\n\t\toptions.serverOptions,\n\t);\n\n\tserverOptions.setup = async () => {\n\t\t// Use Vite as Middleware\n\t\tconst viteDevServer = await eleventyVite.getServer();\n\n\t\tprocess.on(\"SIGINT\", async () => {\n\t\t\tawait viteDevServer.close();\n\t\t});\n\n\t\treturn {\n\t\t\tmiddleware: [viteDevServer.middlewares],\n\t\t};\n\t};\n\n\televentyConfig.setServerOptions(serverOptions);\n\n\t// Run Vite build\n\t// TODO use `build.write` option to work with json or ndjson outputs\n\televentyConfig.on(\"eleventy.after\", async ({ dir, runMode, outputMode, results }) => {\n\t\t// Skips the Vite build if:\n\t\t//   --serve\n\t\t//   --to=json\n\t\t//   --to=ndjson\n\t\t//   or 0 output files from Eleventy build\n\t\t// Notably, this *does* run Vite build in --watch mode\n\t\tif (\n\t\t\trunMode === \"serve\" ||\n\t\t\toutputMode === \"json\" ||\n\t\t\toutputMode === \"ndjson\" ||\n\t\t\tresults.length === 0\n\t\t) {\n\t\t\treturn;\n\t\t}\n\n\t\tawait eleventyVite.runBuild(results);\n\t});\n}\n"
  },
  {
    "path": ".git-blame-ignore-revs",
    "content": "# chore: add prettier\nd76b34ffaf428752558b2d99f0e4a4163edda760\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: npm\n    directory: /\n    schedule:\n      interval: weekly\n    assignees: [KiwiKilian]\n\n  - package-ecosystem: github-actions\n    directories: [/, \".github/workflows/**\"]\n    schedule:\n      interval: weekly\n    assignees: [KiwiKilian]\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Publish Release to npm\non:\n  release:\n    types: [published]\npermissions: read-all\njobs:\n  build:\n    runs-on: ubuntu-latest\n    environment: GitHub Publish\n    permissions:\n      contents: read\n      id-token: write\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # 6.0.2\n      - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # 6.4.0\n        with:\n          node-version: \"24\"\n          registry-url: \"https://registry.npmjs.org\"\n      - run: npm install -g npm@latest\n      - run: npm ci\n      - if: ${{ github.event.release.tag_name != '' && env.NPM_PUBLISH_TAG != '' }}\n        run: npm publish --provenance --access=public --tag=${{ env.NPM_PUBLISH_TAG }}\n        env:\n          NPM_PUBLISH_TAG: ${{ contains(github.event.release.tag_name, '-alpha.') && 'alpha' || 'latest' }}\n"
  },
  {
    "path": ".github/workflows/test.yml",
    "content": "name: Run Tests\non:\n  push:\n    branches: [main, alpha]\n  pull_request:\n    branches: [main, alpha]\n  merge_group:\n    branches: [main, alpha]\njobs:\n  test:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # 4.2.2\n      - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # 4.4.0\n        with:\n          node-version: \"24\"\n      - run: npm ci\n      - run: npm test\n"
  },
  {
    "path": ".gitignore",
    "content": "# Build results\n_site\n\n# IDEs\n/.idea/\n/.vscode/\n\n# Package managers\nnode_modules\n.npm\n\n# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# Artefacts\n.DS_Store\n"
  },
  {
    "path": ".prettierrc",
    "content": "{\n\t\"useTabs\": true,\n\t\"singleQuote\": false,\n\t\"semi\": true,\n\t\"endOfLine\": \"lf\",\n\t\"arrowParens\": \"always\",\n\t\"printWidth\": 100\n}\n"
  },
  {
    "path": "EleventyVite.js",
    "content": "import { promises as fsp } from \"node:fs\";\nimport path from \"node:path\";\nimport { DeepCopy, Merge } from \"@11ty/eleventy-utils\";\nimport { build, createServer } from \"vite\";\n\n/** @type {Required<import(\".eleventy.js\").EleventyViteOptions>} */\nconst DEFAULT_OPTIONS = {\n\ttempFolderName: \".11ty-vite\",\n\tviteOptions: {\n\t\tclearScreen: false,\n\t\tappType: \"mpa\",\n\t\tserver: {\n\t\t\tmiddlewareMode: true,\n\t\t},\n\t\tbuild: {\n\t\t\temptyOutDir: true,\n\t\t\trolldownOptions: {\n\t\t\t\t// HTML files will be injected and merged into `input` for MPA\n\t\t\t},\n\t\t},\n\t\tresolve: {\n\t\t\talias: {\n\t\t\t\t// Allow references to `node_modules` directly for bundling.\n\t\t\t\t\"/node_modules\": path.resolve(\".\", \"node_modules\"),\n\t\t\t\t// Note that bare module specifiers are also supported\n\t\t\t},\n\t\t},\n\t},\n};\n\nexport default class EleventyVite {\n\tstatic LOGGER_PREFIX = \"[11ty/eleventy-plugin-vite]\";\n\n\t/** @type {import(\"@11ty/eleventy/src/Util/ProjectDirectories.js\").default} */\n\tdirectories;\n\n\t/** @type {import(\"@11ty/eleventy/src/Util/ConsoleLogger.js\").default} */\n\tlogger;\n\n\t/** @type {Required<import(\".eleventy.js\").EleventyViteOptions>} */\n\toptions;\n\n\tconstructor(eleventyConfig, pluginOptions = {}) {\n\t\tthis.directories = eleventyConfig.directories;\n\t\tthis.logger = eleventyConfig.logger;\n\n\t\t// Relevant until `rollupOptions` are removed\n\t\tpluginOptions = DeepCopy({}, pluginOptions);\n\t\tconst userBuildOptions = pluginOptions.viteOptions?.build;\n\t\tif (userBuildOptions?.rollupOptions && !userBuildOptions?.rolldownOptions) {\n\t\t\tthis.logger.logWithOptions({\n\t\t\t\tprefix: EleventyVite.LOGGER_PREFIX,\n\t\t\t\tmessage: \"Usage of `rollupOptions` is deprecated, use `rolldownOptions` instead\",\n\t\t\t\ttype: \"info\",\n\t\t\t});\n\n\t\t\tuserBuildOptions.rolldownOptions = userBuildOptions.rollupOptions;\n\t\t\tdelete userBuildOptions.rollupOptions;\n\t\t}\n\n\t\tthis.options = Merge({}, DEFAULT_OPTIONS, pluginOptions);\n\t}\n\n\tgetServer() {\n\t\t/** @type {import(\"vite\").InlineConfig} */\n\t\tconst viteOptions = DeepCopy({}, this.options.viteOptions);\n\t\tviteOptions.root = this.directories.output;\n\n\t\treturn createServer(viteOptions);\n\t}\n\n\tgetIgnoreDirectory() {\n\t\treturn path.join(this.options.tempFolderName, \"**\");\n\t}\n\n\tget tempFolderPath() {\n\t\treturn path.resolve(this.options.tempFolderName);\n\t}\n\n\tstatic getEntryPointName(filePath) {\n\t\treturn path\n\t\t\t.join(path.dirname(filePath), path.basename(filePath, path.extname(filePath)))\n\t\t\t.replace(/^\\/+/, \"\");\n\t}\n\n\tgetEleventyRolldownOptionsInput(input) {\n\t\treturn (\n\t\t\tinput\n\t\t\t\t// Filter out `false` serverless routes\n\t\t\t\t.filter((entry) => !!entry.outputPath)\n\t\t\t\t// Only HTML output\n\t\t\t\t.filter((entry) => (entry.outputPath ?? \"\").endsWith(\".html\"))\n\t\t\t\t.reduce((result, entry) => {\n\t\t\t\t\tif (!entry.outputPath.startsWith(this.directories.output)) {\n\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t`Unexpected output path (was not in output directory ${this.directories.output}): ${entry.outputPath}`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tconst filePath = entry.outputPath.substring(this.directories.output.length);\n\t\t\t\t\tresult[EleventyVite.getEntryPointName(filePath)] = path.resolve(\n\t\t\t\t\t\tthis.tempFolderPath,\n\t\t\t\t\t\tfilePath,\n\t\t\t\t\t);\n\n\t\t\t\t\treturn result;\n\t\t\t\t}, {})\n\t\t);\n\t}\n\n\tgetUserRolldownOptionsInput(input) {\n\t\tlet userInput = {};\n\n\t\tif (input) {\n\t\t\tif (Array.isArray(input)) {\n\t\t\t\tinput.forEach((file) => {\n\t\t\t\t\tuserInput[EleventyVite.getEntryPointName(file)] = file;\n\t\t\t\t});\n\t\t\t} else if (typeof input === \"object\") {\n\t\t\t\tuserInput = input;\n\t\t\t} else if (typeof input === \"string\") {\n\t\t\t\tuserInput[EleventyVite.getEntryPointName(input)] = input;\n\t\t\t}\n\t\t}\n\n\t\treturn userInput;\n\t}\n\n\tasync runBuild(input) {\n\t\tawait fsp.rename(this.directories.output, this.tempFolderPath);\n\n\t\ttry {\n\t\t\t/** @type {import(\"vite\").InlineConfig} */\n\t\t\tconst viteOptions = DeepCopy({}, this.options.viteOptions);\n\t\t\tviteOptions.root = this.tempFolderPath;\n\t\t\tviteOptions.build.outDir = path.resolve(\".\", this.directories.output);\n\t\t\tviteOptions.build.rolldownOptions.input = {\n\t\t\t\t...this.getEleventyRolldownOptionsInput(input),\n\t\t\t\t...this.getUserRolldownOptionsInput(viteOptions.build.rolldownOptions.input),\n\t\t\t};\n\n\t\t\tthis.logger.logWithOptions({\n\t\t\t\tprefix: EleventyVite.LOGGER_PREFIX,\n\t\t\t\tmessage: \"Starting Vite build\",\n\t\t\t\ttype: \"info\",\n\t\t\t});\n\n\t\t\tawait build(viteOptions);\n\n\t\t\tthis.logger.logWithOptions({\n\t\t\t\tprefix: EleventyVite.LOGGER_PREFIX,\n\t\t\t\tmessage: \"Finished Vite build\",\n\t\t\t\ttype: \"info\",\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tthis.logger.logWithOptions({\n\t\t\t\tprefix: EleventyVite.LOGGER_PREFIX,\n\t\t\t\tmessage: `Encountered a Vite build error, restoring original Eleventy output to ${this.directories.output}`,\n\t\t\t\ttype: \"error\",\n\t\t\t\tcolor: \"red\",\n\t\t\t});\n\t\t\tthis.logger.logWithOptions({\n\t\t\t\tprefix: EleventyVite.LOGGER_PREFIX,\n\t\t\t\tmessage: \"Vite error:\",\n\t\t\t\ttype: \"error\",\n\t\t\t});\n\t\t\tthis.logger.logWithOptions({\n\t\t\t\tprefix: EleventyVite.LOGGER_PREFIX,\n\t\t\t\tmessage: JSON.stringify(error, null, 2),\n\t\t\t\ttype: \"error\",\n\t\t\t\tcolor: \"cyan\",\n\t\t\t});\n\n\t\t\tawait fsp.rename(this.tempFolderPath, this.directories.output);\n\n\t\t\tthrow error;\n\t\t} finally {\n\t\t\tawait fsp.rm(this.tempFolderPath, { force: true, recursive: true });\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "README.md",
    "content": "<p align=\"center\"><img src=\"https://www.11ty.dev/img/logo-github.svg\" width=\"200\" height=\"200\" alt=\"11ty Logo\">&#160;&#160;<img src=\"https://v1.image.11ty.dev/https%3A%2F%2Fvitejs.dev%2Flogo.svg/png/200x200/\" alt=\"Vite logo\" width=\"200\" height=\"200\"></p>\n\n# eleventy-plugin-vite 🕚⚡️🎈🐀\n\nA plugin to use [Vite](https://vitejs.dev/) with Eleventy.\n\nThis plugin:\n\n- Runs Vite as Middleware in Eleventy Dev Server (try with Eleventy’s `--incremental`)\n- Runs Vite build to postprocess your Eleventy build output\n\n## Eleventy Housekeeping\n\n- Please star [Eleventy on GitHub](https://github.com/11ty/eleventy/)!\n- Follow us on Mastodon [@eleventy@fosstodon.org](https://fosstodon.org/@eleventy) or Twitter [@eleven_ty](https://twitter.com/eleven_ty)\n- Join us on [Discord](https://www.11ty.dev/blog/discord/)\n- Support [11ty on Open Collective](https://opencollective.com/11ty)\n- [11ty on npm](https://www.npmjs.com/org/11ty)\n- [11ty on GitHub](https://github.com/11ty)\n\n[![npm Version](https://img.shields.io/npm/v/@11ty/eleventy-plugin-vite.svg?style=for-the-badge)](https://www.npmjs.com/package/@11ty/eleventy-plugin-vite)\n\n## Installation\n\n```\nnpm install @11ty/eleventy-plugin-vite@alpha --save-dev\n```\n\n### ESM `.eleventy.js` Config\n\n```js\nimport EleventyVitePlugin from \"@11ty/eleventy-plugin-vite\";\n\nexport default function (eleventyConfig) {\n\televentyConfig.addPlugin(EleventyVitePlugin);\n}\n```\n\n### CommonJS `.eleventy.js` Config\n\n> [!NOTE]\n> This plugin is written in ESM, therefore `require` is not possible. If your .eleventy.js config uses CommonJS, make it async and create a dynamic import as shown below.\n\n```js\nmodule.exports = async function (eleventyConfig) {\n\tconst EleventyPluginVite = (await import(\"@11ty/eleventy-plugin-vite\")).default;\n\n\televentyConfig.addPlugin(EleventyPluginVite);\n};\n```\n\nRead more about ESM vs CommonJS on the [Eleventy documentation](https://www.11ty.dev/docs/cjs-esm/).\n\n### Options\n\nThese are the default options of the plugin. There's no need to specify them unless you want to change them.\n\n```js\nimport EleventyVitePlugin from \"@11ty/eleventy-plugin-vite\";\n\nexport default function (eleventyConfig) {\n\televentyConfig.addPlugin(EleventyVitePlugin, {\n        // Default name of the temp folder\n\t\ttempFolderName: \".11ty-vite\",\n\n\t\t// Eleventy Dev Server Options\n\t\tserverOptions: {\n\t\t\tmodule: \"@11ty/eleventy-dev-server\",\n\t\t\tdomDiff: false,\n\t\t},\n\n\t\t// Vite Config\n\t\tviteOptions: {\n\t\t\tclearScreen: false,\n\t\t\tappType: \"mpa\",\n\t\t\tserver: {\n\t\t\t\tmiddlewareMode: true,\n\t\t\t},\n\t\t\tbuild: {\n\t\t\t\temptyOutDir: true,\n\t\t\t\trolldownOptions: {\n\t\t\t\t\tinput: {\n\t\t\t\t\t\t// HTML entry points will be injected automatically\n\t\t\t\t\t\t// Custom input will be merged\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tresolve: {\n\t\t\t\talias: {\n\t\t\t\t\t// Allow references to `node_modules` directly for bundling.\n\t\t\t\t\t\"/node_modules\": path.resolve(\".\", \"node_modules\"),\n\t\t\t\t\t// Note that bare module specifiers are also supported\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t});\n}\n```\n\nView the [full list of Vite configuration options](https://vitejs.dev/config/). Custom viteOptions will be deeply merged with the defaults.\n\n## Related Projects\n\n- [`eleventy-plus-vite`](https://github.com/matthiasott/eleventy-plus-vite) by @matthiasott: A starter template using this plugin\n- Currently unmaintained:\n  - [`slinkity`](https://slinkity.dev/) by @Holben888: A much deeper and more comprehensive integration with Vite! Offers partial hydration and can use shortcodes to render framework components in Eleventy!\n  - [`vite-plugin-eleventy`](https://www.npmjs.com/package/vite-plugin-eleventy) by @Snugug: Uses Eleventy as Middleware in Vite (instead of the post-processing approach used here)\n"
  },
  {
    "path": "example/.eleventy.js",
    "content": "import EleventyVitePlugin from \"../.eleventy.js\";\n\nexport default function (eleventyConfig) {\n\televentyConfig.addPassthroughCopy(\"src/assets\");\n\n\televentyConfig.addPlugin(EleventyVitePlugin);\n}\n\nexport const config = {\n\tdir: {\n\t\tinput: \"src\",\n\t},\n};\n"
  },
  {
    "path": "example/package.json",
    "content": "{\n\t\"name\": \"eleventy-plugin-vite-example\",\n\t\"private\": true,\n\t\"type\": \"module\"\n}\n"
  },
  {
    "path": "example/src/assets/main.css",
    "content": "body {\n\tbackground-color: black;\n\tcolor: white;\n\tfont-family: sans-serif;\n}\n"
  },
  {
    "path": "example/src/index.njk",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <title>Eleventy Plugin Vite</title>\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n\n    <link rel=\"stylesheet\" href=\"{{ '/assets/main.css' | url }}\" />\n  </head>\n\n  <body>\n    <h1>Eleventy Plugin Vite</h1>\n  </body>\n</html>\n"
  },
  {
    "path": "package.json",
    "content": "{\n\t\"name\": \"@11ty/eleventy-plugin-vite\",\n\t\"version\": \"8.0.0\",\n\t\"description\": \"A plugin to use Vite as a development server and run Vite to postprocess your Eleventy build.\",\n\t\"license\": \"MIT\",\n\t\"engines\": {\n\t\t\"node\": \"^20.19.0 || >=22.12.0\"\n\t},\n\t\"funding\": {\n\t\t\"type\": \"opencollective\",\n\t\t\"url\": \"https://opencollective.com/11ty\"\n\t},\n\t\"keywords\": [\n\t\t\"eleventy\",\n\t\t\"server\"\n\t],\n\t\"11ty\": {\n\t\t\"compatibility\": \">=3.0.0\"\n\t},\n\t\"publishConfig\": {\n\t\t\"access\": \"public\"\n\t},\n\t\"contributors\": [\n\t\t{\n\t\t\t\"name\": \"Zach Leatherman\",\n\t\t\t\"email\": \"zachleatherman@gmail.com\",\n\t\t\t\"url\": \"https://zachleat.com/\"\n\t\t},\n\t\t{\n\t\t\t\"name\": \"Kilian Finger\",\n\t\t\t\"email\": \"npm@kilianfinger.com\",\n\t\t\t\"url\": \"https://www.kilianfinger.com/\"\n\t\t}\n\t],\n\t\"repository\": {\n\t\t\"type\": \"git\",\n\t\t\"url\": \"git://github.com/11ty/eleventy-plugin-vite.git\"\n\t},\n\t\"bugs\": \"https://github.com/11ty/eleventy-plugin-vite/issues\",\n\t\"homepage\": \"https://github.com/11ty/eleventy-plugin-vite/\",\n\t\"main\": \"./.eleventy.js\",\n\t\"type\": \"module\",\n\t\"exports\": {\n\t\t\".\": \"./.eleventy.js\",\n\t\t\"./EleventyVite\": \"./EleventyVite.js\",\n\t\t\"./package.json\": \"./package.json\"\n\t},\n\t\"files\": [\n\t\t\".eleventy.js\",\n\t\t\"EleventyVite.js\"\n\t],\n\t\"scripts\": {\n\t\t\"test\": \"vitest\",\n\t\t\"format\": \"prettier . --write\",\n\t\t\"example\": \"cd example && eleventy\",\n\t\t\"example:start\": \"npm run example -- --serve\",\n\t\t\"example:build\": \"npm run example\",\n\t\t\"example:clean\": \"rimraf ./example/_site\"\n\t},\n\t\"dependencies\": {\n\t\t\"@11ty/eleventy-utils\": \"^2.0.7\",\n\t\t\"vite\": \"^8.0.9\"\n\t},\n\t\"devDependencies\": {\n\t\t\"@11ty/eleventy\": \"3.1.5\",\n\t\t\"prettier\": \"3.8.3\",\n\t\t\"vitest\": \"3.2.4\"\n\t}\n}\n"
  },
  {
    "path": "test/.eleventy.test.js",
    "content": "import { describe, it, expect, vi } from \"vitest\";\nimport eleventyPlugin from \"../.eleventy.js\";\n\ndescribe(\".eleventy.js\", () => {\n\tit(\"sets up passthrough, ignores, and server options\", () => {\n\t\tconst ignores = new Set();\n\n\t\tconst eleventyConfig = {\n\t\t\tdirectories: { output: \"dist\" },\n\t\t\taddPassthroughCopy: vi.fn(),\n\t\t\tignores,\n\t\t\tsetServerOptions: vi.fn(),\n\t\t\ton: vi.fn(),\n\t\t\tlogger: { warn: vi.fn() },\n\t\t\tversionCheck: vi.fn(),\n\t\t};\n\n\t\televentyPlugin(eleventyConfig, { tempFolderName: \".test-11ty-vite\" });\n\n\t\texpect(eleventyConfig.addPassthroughCopy).toHaveBeenCalled();\n\t\texpect([...ignores].some((i) => i.includes(\".test-11ty-vite\"))).toBe(true);\n\t\texpect(eleventyConfig.setServerOptions).toHaveBeenCalled();\n\t\texpect(typeof eleventyConfig.setServerOptions.mock.calls[0][0].setup).toBe(\"function\");\n\t\texpect(eleventyConfig.on).toHaveBeenCalled();\n\t});\n});\n"
  },
  {
    "path": "test/EleventyVite.test.js",
    "content": "import { describe, it, beforeEach, afterEach, expect } from \"vitest\";\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport EleventyVite from \"../EleventyVite.js\";\n\nconst outputPath = \".test-output\";\nconst htmlFilePath = path.join(outputPath, \"index.html\");\nconst tempFolderName = \".test-11ty-vite\";\n\nconst mockEleventyConfig = {\n\tdirectories: { output: outputPath },\n\tlogger: {\n\t\tlogWithOptions: () => {},\n\t},\n};\nconst pluginOptions = {\n\ttempFolderName,\n\tviteOptions: {\n\t\tlogLevel: \"silent\",\n\t},\n};\n\ndescribe(\"EleventyVite\", () => {\n\tbeforeEach(async () => {\n\t\tawait fs.rm(outputPath, { recursive: true, force: true }).catch(() => {});\n\t\tawait fs.mkdir(outputPath, { recursive: true });\n\t\tawait fs.writeFile(path.join(outputPath, \"style.css\"), \"body { color: red; }\");\n\t\tawait fs.writeFile(\n\t\t\thtmlFilePath,\n\t\t\t`<html><head><link rel=\"stylesheet\" href=\"style.css\"></head><body></body></html>`,\n\t\t);\n\t});\n\n\tafterEach(async () => {\n\t\tawait fs.rm(outputPath, { recursive: true, force: true }).catch(() => {});\n\t\tawait fs.rm(tempFolderName, { recursive: true, force: true }).catch(() => {});\n\t});\n\n\tit(\"constructor merges options\", () => {\n\t\tconst plugin = new EleventyVite(mockEleventyConfig, pluginOptions);\n\t\texpect(plugin.options.tempFolderName).toBe(tempFolderName);\n\t\texpect(plugin.options.viteOptions).toBeTruthy();\n\t\texpect(plugin.options.viteOptions.appType).toBe(\"mpa\");\n\t});\n\n\tit(\"getServer returns a Vite dev server\", async () => {\n\t\tconst plugin = new EleventyVite(mockEleventyConfig, pluginOptions);\n\t\tconst server = await plugin.getServer();\n\t\texpect(server.middlewares).toBeTruthy();\n\t\tawait server.close();\n\t});\n\n\tit(\"getIgnoreDirectory returns correct path\", () => {\n\t\tconst plugin = new EleventyVite(mockEleventyConfig, pluginOptions);\n\t\texpect(plugin.getIgnoreDirectory()).toBe(path.join(tempFolderName, \"**\"));\n\t});\n\n\tit(\"outputs HTML file to output path during build\", async () => {\n\t\tconst plugin = new EleventyVite(mockEleventyConfig, pluginOptions);\n\t\tconst input = [{ outputPath: htmlFilePath }];\n\n\t\tawait plugin.runBuild(input);\n\n\t\tconst stat = await fs.stat(htmlFilePath);\n\t\texpect(stat.isFile()).toBe(true);\n\t});\n\n\tit(\"runBuild runs Vite build and cleans up\", async () => {\n\t\tconst plugin = new EleventyVite(mockEleventyConfig, pluginOptions);\n\t\tconst input = [{ outputPath: htmlFilePath }];\n\t\tawait expect(plugin.runBuild(input)).resolves.not.toThrow();\n\n\t\tconst exists = await fs.stat(tempFolderName).then(\n\t\t\t() => true,\n\t\t\t() => false,\n\t\t);\n\t\texpect(exists).toBe(false);\n\t});\n\n\tit(\"references CSS file with hash in HTML after build\", async () => {\n\t\tconst plugin = new EleventyVite(mockEleventyConfig, pluginOptions);\n\t\tconst input = [{ outputPath: htmlFilePath }];\n\n\t\tawait plugin.runBuild(input);\n\n\t\tconst html = await fs.readFile(htmlFilePath, \"utf8\");\n\t\tconst match = html.match(\n\t\t\t/<link[^>]+href=[\"']([^\"']*assets\\/index-[a-zA-Z0-9]+\\.css)[\"'][^>]*>/,\n\t\t);\n\t\texpect(match).toBeTruthy();\n\n\t\tconst cssPath = match[1];\n\t\tconst cssFile = path.join(outputPath, cssPath);\n\t\tconst stat = await fs.stat(cssFile);\n\t\texpect(stat.isFile()).toBe(true);\n\t});\n\n\tit(\"getEleventyRolldownOptionsInput returns correct input object\", () => {\n\t\tconst entries = [\n\t\t\t{ outputPath: `${outputPath}index.html` },\n\t\t\t{ outputPath: `${outputPath}posts/index.html` },\n\t\t\t{ outputPath: `${outputPath}posts/hello/index.html` },\n\t\t\t{ outputPath: `${outputPath}robots.txt` },\n\t\t\t{ outputPath: false },\n\t\t];\n\n\t\tconst plugin = new EleventyVite(mockEleventyConfig, pluginOptions);\n\n\t\tconst result = plugin.getEleventyRolldownOptionsInput(entries);\n\n\t\texpect(result).toEqual({\n\t\t\tindex: path.resolve(`${tempFolderName}/index.html`),\n\t\t\t\"posts/index\": path.resolve(`${tempFolderName}/posts/index.html`),\n\t\t\t\"posts/hello/index\": path.resolve(`${tempFolderName}/posts/hello/index.html`),\n\t\t});\n\t});\n\n\tit(\"getUserRolldownOptionsInput handles array input\", () => {\n\t\tconst plugin = new EleventyVite(mockEleventyConfig, pluginOptions);\n\t\tconst result = plugin.getUserRolldownOptionsInput([\"script.js\", \"styles/main.css\"]);\n\n\t\texpect(result).toEqual({\n\t\t\tscript: \"script.js\",\n\t\t\t\"styles/main\": \"styles/main.css\",\n\t\t});\n\t});\n\n\tit(\"getUserRolldownOptionsInput handles object input\", () => {\n\t\tconst plugin = new EleventyVite(mockEleventyConfig, pluginOptions);\n\t\tconst result = plugin.getUserRolldownOptionsInput({\n\t\t\tscript: \"script.js\",\n\t\t\t\"styles/main\": \"styles/main.css\",\n\t\t});\n\n\t\texpect(result).toEqual({\n\t\t\tscript: \"script.js\",\n\t\t\t\"styles/main\": \"styles/main.css\",\n\t\t});\n\t});\n\n\tit(\"maps rollupOptions to rolldownOptions and logs a deprecation warning\", () => {\n\t\tconst logCalls = [];\n\t\tconst mockConfigWithLogger = {\n\t\t\t...mockEleventyConfig,\n\t\t\tlogger: {\n\t\t\t\tlogWithOptions: (opts) => logCalls.push(opts),\n\t\t\t},\n\t\t};\n\n\t\tconst rollupInput = { main: \"src/main.js\" };\n\t\tconst options = {\n\t\t\ttempFolderName,\n\t\t\tviteOptions: {\n\t\t\t\tbuild: {\n\t\t\t\t\trollupOptions: { input: rollupInput },\n\t\t\t\t},\n\t\t\t},\n\t\t};\n\n\t\tconst plugin = new EleventyVite(mockConfigWithLogger, options);\n\n\t\texpect(plugin.options.viteOptions.build.rolldownOptions.input).toEqual(rollupInput);\n\t\texpect(plugin.options.viteOptions.build.rollupOptions).toBeUndefined();\n\n\t\tconst warning = logCalls.find((c) => c.message.includes(\"rollupOptions\"));\n\t\texpect(warning).toBeTruthy();\n\t\texpect(warning.type).toBe(\"info\");\n\n\t\t// original pluginOptions object must not be mutated\n\t\texpect(options.viteOptions.build.rollupOptions).toEqual({ input: rollupInput });\n\t\texpect(options.viteOptions.build.rolldownOptions).toBeUndefined();\n\t});\n\n\tit(\"getUserRolldownOptionsInput handles string input\", () => {\n\t\tconst plugin = new EleventyVite(mockEleventyConfig, pluginOptions);\n\t\tconst result = plugin.getUserRolldownOptionsInput(\"styles/main.css\");\n\n\t\texpect(result).toEqual({\n\t\t\t\"styles/main\": \"styles/main.css\",\n\t\t});\n\t});\n});\n"
  }
]