[
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\nindent_style = tab\nend_of_line = lf\ninsert_final_newline = true\ntrim_trailing_whitespace = true\ncharset = utf-8\n\n[*.yml]\nindent_style = space\n\n[/test/**/*]\nindent_style = space\n\n[/test/stubs*/**]\ninsert_final_newline = false\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": ".git-blame-ignore-revs",
    "content": "# Switch the entire codebase to tabs, for accessibility #3098\n358ec48f779fa34e14abef057cc1fa0c1a10aa45"
  },
  {
    "path": ".github/CODEOWNERS",
    "content": "# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners\n* @zachleat\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "contact_links:\n  - name: I have a question about Eleventy\n    url: https://github.com/11ty/eleventy/discussions/\n    about: General education topics should be filed on our Discussions board e.g. “How do I do this in Eleventy?” or “Can Eleventy do this?” (Please search existing discussions first!)\n  - name: I have a feature request for Eleventy\n    url: https://github.com/11ty/eleventy/discussions/new?category=enhancement-queue\n    about: Enhancement or new Features. e.g. “I wish Eleventy did this.” Suggest an idea! (Please search existing discussions first!)\n  - name: I wish the docs were different!\n    url: https://github.com/11ty/11ty-website/issues/new/choose\n    about: Something missing from the documentation? Something wrong? Something confusing? You want the 11ty-website repo!\n  - name: Discord Community\n    url: https://discord.gg/GBkBy9u\n    about: Ask the community on Discord\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/possible-bug.yml",
    "content": "name: I’m having trouble with Eleventy\ndescription: Have a problem? It might be a bug! Create a report to help us improve.\nlabels: [needs-triage]\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        Before opening a bug report, please search for the behavior in the existing issues.\n\n        ---\n\n        Thank you for taking the time to file a bug report. To address this bug as fast as possible, we need some information.\n  - type: input\n    id: os\n    attributes:\n      label: Operating system\n      description: Which operating system do you use?\n      placeholder: macOS Big Sur 11.5.2\n    validations:\n      required: true\n  - type: input\n    id: eleventy\n    attributes:\n      label: Eleventy\n      description: Which version of Eleventy do you use?\n      placeholder: eleventy --version or npx @11ty/eleventy --version\n    validations:\n      required: true\n  - type: textarea\n    id: bug-description\n    attributes:\n      label: Describe the bug\n      description: A clear and concise description of how to reproduce the bug\n      value: |\n        1. I ran *this* command with *these* flags '...'\n        2. I used the following configuration and template syntax '....'\n        3. I got *this* error or I expected this to happen and it didn’t\n    validations:\n      required: true\n  - type: input\n    id: repro-url\n    attributes:\n      label: Reproduction Source Code URL\n      description: \"Optional: The URL to the **public** repository for the reproduction. _[parser:url]_\"\n      placeholder: e.g. https://github.com/zachleat/zachleat.com\n    validations:\n      required: false\n  - type: textarea\n    id: screenshots\n    attributes:\n      label: Screenshots\n      description: \"Optional: If applicable, add screenshots to help explain your problem.\"\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "# To get started with Dependabot version updates, you'll need to specify which\n# package ecosystems to update and where the package manifests are located.\n# Please see the documentation for all configuration options:\n# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file\n\nversion: 2\nupdates:\n  - package-ecosystem: \"npm\"\n    directory: \"/\"\n    schedule:\n      interval: weekly\n    cooldown:\n      default-days: 7\n    labels:\n      - \"dependency-updates\"\n    versioning-strategy: increase\n    allow:\n      - dependency-type: \"production\"\n    assignees: [zachleat]\n\n  - package-ecosystem: github-actions\n    directories: [\".github/workflows/**\"]\n    schedule:\n      interval: weekly\n    cooldown:\n      default-days: 7\n    assignees: [zachleat]\n"
  },
  {
    "path": ".github/opencollective.yml",
    "content": "collective: 11ty\ntiers:\n  - tiers: '*'\n    labels: [\"oc-supporter\"]\n    message: \"Hey <author>, thanks for supporting us on Open Collective!\"\ninvitation: |\n  Hey <author> :wave:,\n\n  Thank you for opening an issue. We will get back to you as soon as we can.\n\n  Also, check out our [Open Collective](https://opencollective.com/11ty) and consider backing us—every little bit helps!\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: Unit Tests\non: push\npermissions: read-all\njobs:\n  server:\n    name: Node.js v${{ matrix.node }} on ${{ matrix.os }}\n    runs-on: ${{ matrix.os }}\n    strategy:\n      matrix:\n        os: [\"ubuntu-latest\", \"macos-latest\", \"windows-latest\"]\n        node: [\"20\", \"22\", \"24\"]\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # 6.0.2\n      - name: Setup node\n        uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # 6.2.0\n        with:\n          node-version: ${{ matrix.node }}\n          # cache: npm\n      - run: npm ci\n      - run: npm run test:server\n  client:\n    name: Vitest on ${{ matrix.os }} (Node.js v${{ matrix.node }})\n    runs-on: ${{ matrix.os }}\n    strategy:\n      matrix:\n        os: [\"ubuntu-latest\", \"macos-latest\", \"windows-latest\"]\n        node: [\"22\"]\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # 6.0.2\n      - name: Setup node\n        uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # 6.2.0\n        with:\n          node-version: ${{ matrix.node }}\n      - run: npm ci\n      - run: npx playwright install\n      - run: npm run test:client\nenv:\n  YARN_GPG: no\n"
  },
  {
    "path": ".github/workflows/codeql.yml",
    "content": "# For most projects, this workflow file will not need changing; you simply need\n# to commit it to your repository.\n#\n# You may wish to alter this file to override the set of languages analyzed,\n# or to provide custom queries or build logic.\n#\n# ******** NOTE ********\n# We have attempted to detect the languages in your repository. Please check\n# the `language` matrix defined below to confirm you have the correct set of\n# supported CodeQL languages.\n#\nname: \"CodeQL Advanced\"\n\non:\n  push:\n    branches: [ \"main\" ]\n  pull_request:\n    branches: [ \"main\" ]\n  schedule:\n    - cron: '21 17 * * 5'\n\n# Declare default permissions as read only.\npermissions: read-all\n\njobs:\n  analyze:\n    name: Analyze (${{ matrix.language }})\n    # Runner size impacts CodeQL analysis time. To learn more, please see:\n    #   - https://gh.io/recommended-hardware-resources-for-running-codeql\n    #   - https://gh.io/supported-runners-and-hardware-resources\n    #   - https://gh.io/using-larger-runners (GitHub.com only)\n    # Consider using larger runners or machines with greater resources for possible analysis time improvements.\n    runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}\n    permissions:\n      # required for all workflows\n      security-events: write\n\n      # required to fetch internal or private CodeQL packs\n      packages: read\n\n      # only required for workflows in private repositories\n      actions: read\n      contents: read\n\n    strategy:\n      fail-fast: false\n      matrix:\n        include:\n        - language: javascript-typescript\n          build-mode: none\n        # CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift'\n        # Use `c-cpp` to analyze code written in C, C++ or both\n        # Use 'java-kotlin' to analyze code written in Java, Kotlin or both\n        # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both\n        # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,\n        # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.\n        # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how\n        # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages\n    steps:\n    - name: Checkout repository\n      uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # 6.0.2\n\n    # Initializes the CodeQL tools for scanning.\n    - name: Initialize CodeQL\n      uses: github/codeql-action/init@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 #4.31.9\n      with:\n        languages: ${{ matrix.language }}\n        build-mode: ${{ matrix.build-mode }}\n        # If you wish to specify custom queries, you can do so here or in a config file.\n        # By default, queries listed here will override any specified in a config file.\n        # Prefix the list here with \"+\" to use these queries and those in the config file.\n\n        # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs\n        # queries: security-extended,security-and-quality\n\n    # If the analyze step fails for one of the languages you are analyzing with\n    # \"We were unable to automatically build your code\", modify the matrix above\n    # to set the build mode to \"manual\" for that language. Then modify this step\n    # to build your code.\n    # ℹ️ Command-line programs to run using the OS shell.\n    # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun\n    - if: matrix.build-mode == 'manual'\n      shell: bash\n      run: |\n        echo 'If you are using a \"manual\" build mode for one or more of the' \\\n          'languages you are analyzing, replace this with the commands to build' \\\n          'your code, for example:'\n        echo '  make bootstrap'\n        echo '  make release'\n        exit 1\n\n    - name: Perform CodeQL Analysis\n      uses: github/codeql-action/analyze@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 #4.31.9\n      with:\n        category: \"/language:${{matrix.language}}\"\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Publish Release to npm\non:\n  release:\n    types: [published]\npermissions: read-all\njobs:\n  release:\n    # see https://github.com/11ty/eleventy/settings/environments\n    environment: GitHub Publish\n    runs-on: ubuntu-latest\n    permissions:\n      contents: read\n      id-token: write\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # 6.0.2\n      - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # 6.2.0\n        with:\n          node-version: \"22\"\n          registry-url: 'https://registry.npmjs.org'\n      - run: npm install -g npm@latest\n      - if: ${{ github.event.release.tag_name != '' && env.NPM_PUBLISH_TAG != '' }}\n        # Also runs npm ci and npm test\n        run: ./scripts/release.sh\n        env:\n          NPM_PUBLISH_TAG: ${{ contains(github.event.release.tag_name, '-beta.') && 'beta' || contains(github.event.release.tag_name, '-alpha.') && 'canary' || 'latest' }}\n"
  },
  {
    "path": ".github/workflows/scorecard.yml",
    "content": "# This workflow uses actions that are not certified by GitHub. They are provided\n# by a third-party and are governed by separate terms of service, privacy\n# policy, and support documentation.\n\nname: Scorecard supply-chain security\non:\n  # For Branch-Protection check. Only the default branch is supported. See\n  # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection\n  branch_protection_rule:\n  # To guarantee Maintained check is occasionally updated. See\n  # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained\n  schedule:\n    - cron: '35 7 * * 2'\n  push:\n    branches: [ \"main\" ]\n\n# Declare default permissions as read only.\npermissions: read-all\n\njobs:\n  analysis:\n    name: Scorecard analysis\n    runs-on: ubuntu-latest\n    permissions:\n      # Needed to upload the results to code-scanning dashboard.\n      security-events: write\n      # Needed to publish results and get a badge (see publish_results below).\n      id-token: write\n      # Uncomment the permissions below if installing in a private repository.\n      # contents: read\n      # actions: read\n\n    steps:\n      - name: \"Checkout code\"\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n        with:\n          persist-credentials: false\n\n      - name: \"Run analysis\"\n        uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3\n        with:\n          results_file: results.sarif\n          results_format: sarif\n          # (Optional) \"write\" PAT token. Uncomment the `repo_token` line below if:\n          # - you want to enable the Branch-Protection check on a *public* repository, or\n          # - you are installing Scorecard on a *private* repository\n          # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action?tab=readme-ov-file#authentication-with-fine-grained-pat-optional.\n          # repo_token: ${{ secrets.SCORECARD_TOKEN }}\n\n          # Public repositories:\n          #   - Publish results to OpenSSF REST API for easy access by consumers\n          #   - Allows the repository to include the Scorecard badge.\n          #   - See https://github.com/ossf/scorecard-action#publishing-results.\n          # For private repositories:\n          #   - `publish_results` will always be set to `false`, regardless\n          #     of the value entered here.\n          publish_results: true\n\n      # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF\n      # format to the repository Actions tab.\n      # - name: \"Upload artifact\"\n      #   uses: actions/upload-artifact@97a0fba1372883ab732affbe8f94b823f91727db # v3.pre.node20\n      #   with:\n      #     name: SARIF file\n      #     path: results.sarif\n      #     retention-days: 5\n\n      # Upload the results to GitHub's code scanning dashboard (optional).\n      # Commenting out will disable upload of results to your repo's Code Scanning dashboard\n      # - name: \"Upload to code-scanning\"\n      #   uses: github/codeql-action/upload-sarif@v3\n      #   with:\n      #     sarif_file: results.sarif\n"
  },
  {
    "path": ".gitignore",
    "content": "# Generated files\ndist/\npackages/*/visualize/*\n\n# Ignore installed npm modules\nnode_modules/\n\n# Ignore build tool output, e.g. code coverage\n.nyc_output/\ncoverage/\ndocs/_data/coverage.json\n\n# Ignore API documentation\napi-docs/\n\n# Ignore folders from source code editors\n.vscode\n.idea\n\n# Ignore eleventy output when doing manual tests\n_site/\n\n# Ignore test files\n.cache\ntest/stubs-layout-cache/_includes/*.js\n"
  },
  {
    "path": ".npmignore",
    "content": "docs\ndocs-src\ntest\ntest_node\ncoverage\neslint.config.js\n.*\npackages/client/\n"
  },
  {
    "path": ".nvmrc",
    "content": "22\n"
  },
  {
    "path": ".prettierignore",
    "content": "test\n"
  },
  {
    "path": ".prettierrc.json",
    "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": "CODE_OF_CONDUCT.md",
    "content": "# Eleventy Community Code of Conduct\n\nView the [Code of Conduct](https://www.11ty.dev/docs/code-of-conduct/) on 11ty.dev\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.\n\n## Our Standards\n\nExamples of behavior that contributes to creating a positive environment include:\n\n- Using welcoming and inclusive language\n- Being respectful of differing viewpoints and experiences\n- Gracefully accepting constructive criticism\n- Focusing on what is best for the community\n- Showing empathy towards other community members\n\nExamples of unacceptable behavior by participants include:\n\n- The use of sexualized language or imagery and unwelcome sexual attention or advances\n- Trolling, insulting/derogatory comments, and personal or political attacks\n- Public or private harassment\n- Publishing others' private information, such as a physical or electronic address, without explicit permission\n- Other conduct which could reasonably be considered inappropriate in a professional setting\n\n## Our Responsibilities\n\nProject maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.\n\nProject maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, chat messages, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.\n\n## Scope\n\nThis Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at eleventy@zachleat.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.\n\nProject maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]\n\n[homepage]: http://contributor-covenant.org\n[version]: http://contributor-covenant.org/version/1/4/\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2017–2024 Zach Leatherman @zachleat\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.\n"
  },
  {
    "path": "README.md",
    "content": "<p align=\"center\"><img src=\"https://www.11ty.dev/img/logo-github.svg\" width=\"200\" height=\"200\" alt=\"eleventy Logo\"></p>\n\n# eleventy 🕚⚡️🎈🐀\n\nA simpler static site generator. An alternative to Jekyll. Written in JavaScript. Transforms a directory of templates (of varying types) into HTML.\n\nWorks with HTML, Markdown, JavaScript, Liquid, Nunjucks, with addons for WebC, Sass, Vue, Svelte, TypeScript, JSX, and many others!\n\n## ➡ [Documentation](https://www.11ty.dev/docs/)\n\n- Star [this repo on GitHub](https://github.com/11ty/eleventy/)!\n- Follow us [on Mastodon `@11ty@neighborhood.11ty.dev`](https://neighborhood.11ty.dev/@11ty)\n- Follow us [on Bluesky `@11ty.dev`](https://bsky.app/profile/11ty.dev)\n- Install [from npm](https://www.npmjs.com/org/11ty)\n- Follow [on GitHub](https://github.com/11ty)\n- Watch us [on YouTube](https://www.youtube.com/c/EleventyVideo)\n- Chat on [Discord](https://www.11ty.dev/blog/discord/)\n- Latest: [![npm Version](https://img.shields.io/npm/v/@11ty/eleventy.svg?style=for-the-badge)](https://www.npmjs.com/package/@11ty/eleventy)\n\n## Installation\n\n```\nnpm install @11ty/eleventy --save-dev\n```\n\nRead our [Getting Started guide](https://www.11ty.dev/docs/getting-started/).\n\n## Tests\n\n```\nnpm test\n```\n\nWe have a few test suites, for various reasons:\n\n- [ava JavaScript test runner](https://github.com/avajs/ava) ([assertions docs](https://github.com/avajs/ava/blob/main/docs/03-assertions.md)) (primary test suite in `test/`)\n- [Node.js Test runner](https://nodejs.org/api/test.html) (secondary test suite in `test_node/`)\n- [Vitest (in Browser Mode)](https://vitest.dev/guide/browser/) (client tests in `packages/client/test/`)\n- [Benchmark for Performance Regressions](https://github.com/11ty/eleventy-benchmark)\n\nThese run in various environments:\n\n- [Continuous Integration on GitHub Actions](https://github.com/11ty/eleventy/actions/workflows/ci.yml)\n- [Code Coverage Statistics](https://github.com/11ty/eleventy/blob/master/docs/coverage.md)\n\n## Community Roadmap\n\n- [Top Feature Requests](https://github.com/11ty/eleventy/discussions/categories/enhancement-queue?discussions_q=is%3Aopen+category%3A%22Enhancement+Queue%22+sort%3Atop) (Vote for your favorites!)\n- [Top Bugs 😱](https://github.com/11ty/eleventy/issues?q=is%3Aissue+is%3Aopen+label%3Abug+sort%3Areactions) (Add your own votes using the 👍 reaction)\n\n## Plugins\n\nSee the [official docs on plugins](https://www.11ty.dev/docs/plugins/).\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security Policy\n\n## Reporting a Vulnerability\n\nPrivately report a security issue by navigating to https://github.com/11ty/eleventy/security and using the “Report a vulnerability” button.\n\nRead more at: https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability\n\nAlternatively, you may report security issues via an email to `security@11ty.dev`.\n"
  },
  {
    "path": "cmd.cjs",
    "content": "#!/usr/bin/env node\n\n// This file intentionally uses older code conventions to be as friendly\n// as possible with error messaging to folks on older runtimes.\n\nconst pkg = require(\"./package.json\");\nrequire(\"@11ty/node-version-check\")(pkg, {\n\tmessage: function (requiredVersion) {\n\t\treturn (\n\t\t\t\"Eleventy \" +\n\t\t\tpkg.version +\n\t\t\t\" requires Node \" +\n\t\t\trequiredVersion +\n\t\t\t\". You will need to upgrade Node to use Eleventy!\"\n\t\t);\n\t},\n});\n\nconst minimist = require(\"minimist\");\nconst debug = require(\"debug\")(\"Eleventy:cmd\");\n\nclass SimpleError extends Error {\n\tconstructor(...args) {\n\t\tsuper(...args);\n\t\tthis.skipOriginalStack = true;\n\t}\n}\n\nasync function exec() {\n\t// Notes about friendly error messaging with outdated Node versions: https://github.com/11ty/eleventy/issues/3761\n\tconst { EleventyErrorHandler } = await import(\"./src/Errors/EleventyErrorHandler.js\");\n\n\t// Defensive use of Node 22.8+ Module Compile Cache\n\tif(!process.env?.ELEVENTY_SKIP_NODE_COMPILE_CACHE) {\n\t\ttry {\n\t\t\tconst nodeMod = await import('node:module').then(mod => mod.default);\n\t\t\tnodeMod.enableCompileCache?.();\n\t\t} catch(e) {\n\t\t\tdebug(\"Node compile cache error (optional API) %o\", e);\n\t\t}\n\t}\n\n\ttry {\n\t\tconst argv = minimist(process.argv.slice(2), {\n\t\t\tstring: [\"input\", \"output\", \"formats\", \"config\", \"pathprefix\", \"port\", \"to\", \"incremental\", \"loader\"],\n\t\t\tboolean: [\n\t\t\t\t\"quiet\",\n\t\t\t\t\"version\",\n\t\t\t\t\"watch\",\n\t\t\t\t\"dryrun\",\n\t\t\t\t\"help\",\n\t\t\t\t\"serve\",\n\t\t\t\t\"ignore-initial\",\n\t\t\t],\n\t\t\tdefault: {\n\t\t\t\tquiet: null,\n\t\t\t\t\"ignore-initial\": false,\n\t\t\t\t\"to\": \"fs\",\n\t\t\t},\n\t\t\tunknown: function (unknownArgument) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`We don’t know what '${unknownArgument}' is. Use --help to see the list of supported commands.`,\n\t\t\t\t);\n\t\t\t},\n\t\t});\n\n\t\tdebug(\"command: eleventy %o\", argv);\n\t\tconst { Eleventy } = await import(\"./src/Eleventy.js\");\n\n\t\tlet ErrorHandler = new EleventyErrorHandler();\n\n\t\tprocess.on(\"unhandledRejection\", (error, promise) => {\n\t\t\tErrorHandler.fatal(error, \"Unhandled rejection in promise\");\n\t\t});\n\t\tprocess.on(\"uncaughtException\", (error) => {\n\t\t\tErrorHandler.fatal(error, \"Uncaught exception\");\n\t\t});\n\t\tprocess.on(\"rejectionHandled\", (promise) => {\n\t\t\tErrorHandler.warn(promise, \"A promise rejection was handled asynchronously\");\n\t\t});\n\n\t\tif (argv.version) {\n\t\t\tconsole.log(Eleventy.getVersion());\n\t\t\treturn;\n\t\t} else if (argv.help) {\n\t\t\tconsole.log(Eleventy.getHelp());\n\t\t\treturn;\n\t\t}\n\n\t\tlet elev = new Eleventy(argv.input, argv.output, {\n\t\t\tsource: \"cli\",\n\t\t\t// --quiet and --quiet=true both resolve to true\n\t\t\tquietMode: argv.quiet,\n\t\t\tconfigPath: argv.config,\n\t\t\tpathPrefix: argv.pathprefix,\n\t\t\trunMode: argv.serve ? \"serve\" : argv.watch ? \"watch\" : \"build\",\n\t\t\tdryRun: argv.dryrun,\n\t\t\tloader: argv.loader,\n\t\t});\n\n\t\t// reuse ErrorHandler instance in Eleventy\n\t\tErrorHandler = elev.errorHandler;\n\n\t\t// Before init\n\t\telev.setFormats(argv.formats);\n\n\t\tawait elev.init();\n\n\t\tif (argv.to === \"json\") {\n\t\t\t// override logging output\n\t\t\telev.setIsVerbose(false);\n\t\t}\n\n\t\t// Only relevant for watch/serve\n\t\telev.setIgnoreInitial(argv[\"ignore-initial\"]);\n\n\t\tif(argv.incremental) {\n\t\t\telev.setIncrementalFile(argv.incremental);\n\t\t} else if(argv.incremental !== undefined) {\n\t\t\telev.setIncrementalBuild(argv.incremental === \"\" || argv.incremental);\n\t\t}\n\n\t\tif (argv.serve || argv.watch) {\n\t\t\tif(argv.to === \"json\") {\n\t\t\t\tthrow new SimpleError(\"--to=json is not compatible with --serve or --watch.\");\n\t\t\t}\n\n\t\t\tawait elev.watch();\n\n\t\t\tif (argv.serve) {\n\t\t\t\t// TODO await here?\n\t\t\t\telev.serve(argv.port);\n\t\t\t}\n\n\t\t\tprocess.on(\"SIGINT\", async () => {\n\t\t\t\tawait elev.stopWatch();\n\t\t\t\tprocess.exitCode = 0;\n\t\t\t});\n\t\t} else {\n\t\t\t// `fs:templates` will skip passthrough copy\n\t\t\tif (!argv.to || argv.to === \"fs\" || argv.to.startsWith(\"fs:\")) {\n\t\t\t\tawait elev.write(argv.to);\n\t\t\t} else if (argv.to === \"json\") {\n\t\t\t\tlet result = await elev.toJSON()\n\t\t\t\tconsole.log(JSON.stringify(result, null, 2));\n\t\t\t} else {\n\t\t\t\tthrow new SimpleError(\n\t\t\t\t\t`Invalid --to value: ${argv.to}. Supported values: \\`fs\\` (default), \\`json\\`.`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t} catch (error) {\n\t\tif(typeof EleventyErrorHandler !== \"undefined\") {\n\t\t\tlet ErrorHandler = new EleventyErrorHandler();\n\t\t\tErrorHandler.fatal(error, \"Eleventy Fatal Error (CLI)\");\n\t\t} else {\n\t\t\tconsole.error(error);\n\t\t\tprocess.exitCode = 1;\n\t\t}\n\t}\n}\n\n// await\nexec();\n"
  },
  {
    "path": "docs/coverage.md",
    "content": "# Code Coverage for Eleventy v3.1.2\n\n| Filename                                                   | % Lines | % Statements | % Functions | % Branches |\n| ---------------------------------------------------------- | ------- | ------------ | ----------- | ---------- |\n| `total`                                                    | 90.67%  | 90.67%       | 90.51%      | 89.18%     |\n| `cmd.cjs`                                                  | 71.61%  | 71.61%       | 25%         | 68.18%     |\n| `src/Eleventy.js`                                          | 91.11%  | 91.11%       | 87.09%      | 84.15%     |\n| `src/EleventyExtensionMap.js`                              | 97.18%  | 97.18%       | 96.15%      | 94.31%     |\n| `src/EleventyFiles.js`                                     | 95.39%  | 95.39%       | 95.74%      | 90.99%     |\n| `src/EleventyServe.js`                                     | 52.33%  | 52.33%       | 69.56%      | 58.33%     |\n| `src/EleventyWatch.js`                                     | 93.12%  | 93.12%       | 94.44%      | 91.66%     |\n| `src/EleventyWatchTargets.js`                              | 93.29%  | 93.29%       | 90%         | 90.47%     |\n| `src/EventBus.js`                                          | 100%    | 100%         | 100%        | 100%       |\n| `src/FileSystemSearch.js`                                  | 100%    | 100%         | 100%        | 100%       |\n| `src/GlobalDependencyMap.js`                               | 81.85%  | 81.85%       | 85%         | 88.23%     |\n| `src/LayoutCache.js`                                       | 77.55%  | 77.55%       | 87.5%       | 87.5%      |\n| `src/Template.js`                                          | 94.91%  | 94.91%       | 93.84%      | 90.73%     |\n| `src/TemplateBehavior.js`                                  | 90.58%  | 90.58%       | 100%        | 84.21%     |\n| `src/TemplateCollection.js`                                | 94.8%   | 94.8%        | 87.5%       | 95.23%     |\n| `src/TemplateConfig.js`                                    | 91.85%  | 91.85%       | 82.35%      | 92.66%     |\n| `src/TemplateContent.js`                                   | 90.24%  | 90.24%       | 91.83%      | 87.02%     |\n| `src/TemplateFileSlug.js`                                  | 100%    | 100%         | 100%        | 100%       |\n| `src/TemplateGlob.js`                                      | 94.28%  | 94.28%       | 100%        | 91.66%     |\n| `src/TemplateLayout.js`                                    | 90.83%  | 90.83%       | 83.33%      | 88.57%     |\n| `src/TemplateLayoutPathResolver.js`                        | 88.97%  | 88.97%       | 75%         | 90.9%      |\n| `src/TemplateMap.js`                                       | 95.17%  | 95.17%       | 94.87%      | 94.65%     |\n| `src/TemplatePassthrough.js`                               | 92.8%   | 92.8%        | 100%        | 90%        |\n| `src/TemplatePassthroughManager.js`                        | 86.95%  | 86.95%       | 96.29%      | 83.52%     |\n| `src/TemplatePermalink.js`                                 | 87.69%  | 87.69%       | 91.66%      | 92.95%     |\n| `src/TemplateRender.js`                                    | 90.06%  | 90.06%       | 100%        | 91.5%      |\n| `src/TemplateWriter.js`                                    | 84.64%  | 84.64%       | 83.33%      | 74.69%     |\n| `src/UserConfig.js`                                        | 90.96%  | 90.96%       | 78.57%      | 87.36%     |\n| `src/defaultConfig.js`                                     | 96.06%  | 96.06%       | 100%        | 66.66%     |\n| `src/Benchmark/Benchmark.js`                               | 98.18%  | 98.18%       | 100%        | 92.3%      |\n| `src/Benchmark/BenchmarkGroup.js`                          | 92.59%  | 92.59%       | 81.81%      | 95.45%     |\n| `src/Benchmark/BenchmarkManager.js`                        | 90.41%  | 90.41%       | 77.77%      | 87.5%      |\n| `src/Data/ComputedData.js`                                 | 100%    | 100%         | 100%        | 100%       |\n| `src/Data/ComputedDataProxy.js`                            | 97.7%   | 97.7%        | 100%        | 94.44%     |\n| `src/Data/ComputedDataQueue.js`                            | 100%    | 100%         | 100%        | 100%       |\n| `src/Data/ComputedDataTemplateString.js`                   | 95.71%  | 95.71%       | 100%        | 85.71%     |\n| `src/Data/TemplateData.js`                                 | 93.09%  | 93.09%       | 94.11%      | 88.12%     |\n| `src/Data/TemplateDataInitialGlobalData.js`                | 95%     | 95%          | 100%        | 83.33%     |\n| `src/Engines/Custom.js`                                    | 88.2%   | 88.2%        | 100%        | 86.59%     |\n| `src/Engines/Html.js`                                      | 100%    | 100%         | 100%        | 100%       |\n| `src/Engines/JavaScript.js`                                | 81.25%  | 81.25%       | 93.75%      | 86.44%     |\n| `src/Engines/Liquid.js`                                    | 99.09%  | 99.09%       | 100%        | 95.94%     |\n| `src/Engines/Markdown.js`                                  | 96%     | 96%          | 85.71%      | 92.59%     |\n| `src/Engines/Nunjucks.js`                                  | 92.32%  | 92.32%       | 100%        | 87.5%      |\n| `src/Engines/TemplateEngine.js`                            | 89.32%  | 89.32%       | 83.87%      | 92.68%     |\n| `src/Engines/TemplateEngineManager.js`                     | 93.78%  | 93.78%       | 100%        | 92.64%     |\n| `src/Engines/FrontMatter/JavaScript.js`                    | 100%    | 100%         | 100%        | 100%       |\n| `src/Engines/Util/ContextAugmenter.js`                     | 91.04%  | 91.04%       | 50%         | 88.23%     |\n| `src/Errors/DuplicatePermalinkOutputError.js`              | 100%    | 100%         | 100%        | 100%       |\n| `src/Errors/EleventyBaseError.js`                          | 100%    | 100%         | 100%        | 100%       |\n| `src/Errors/EleventyErrorHandler.js`                       | 94.07%  | 94.07%       | 100%        | 77.77%     |\n| `src/Errors/EleventyErrorUtil.js`                          | 100%    | 100%         | 100%        | 100%       |\n| `src/Errors/TemplateContentPrematureUseError.js`           | 100%    | 100%         | 100%        | 100%       |\n| `src/Errors/TemplateContentUnrenderedTemplateError.js`     | 100%    | 100%         | 100%        | 100%       |\n| `src/Errors/UsingCircularTemplateContentReferenceError.js` | 100%    | 100%         | 100%        | 100%       |\n| `src/Filters/GetCollectionItem.js`                         | 100%    | 100%         | 100%        | 87.5%      |\n| `src/Filters/GetCollectionItemIndex.js`                    | 88.23%  | 88.23%       | 100%        | 77.77%     |\n| `src/Filters/GetLocaleCollectionItem.js`                   | 12.76%  | 12.76%       | 0%          | 100%       |\n| `src/Filters/Slug.js`                                      | 100%    | 100%         | 100%        | 100%       |\n| `src/Filters/Slugify.js`                                   | 100%    | 100%         | 100%        | 100%       |\n| `src/Filters/Url.js`                                       | 88.57%  | 88.57%       | 100%        | 93.75%     |\n| `src/Plugins/HtmlBasePlugin.js`                            | 85%     | 85%          | 100%        | 86.95%     |\n| `src/Plugins/HtmlRelativeCopyPlugin.js`                    | 100%    | 100%         | 100%        | 100%       |\n| `src/Plugins/I18nPlugin.js`                                | 82.96%  | 82.96%       | 100%        | 80.55%     |\n| `src/Plugins/IdAttributePlugin.js`                         | 97.27%  | 97.27%       | 100%        | 90%        |\n| `src/Plugins/InputPathToUrl.js`                            | 90.05%  | 90.05%       | 100%        | 78.12%     |\n| `src/Plugins/Pagination.js`                                | 90.23%  | 90.23%       | 95%         | 84%        |\n| `src/Plugins/RenderPlugin.js`                              | 87.69%  | 87.69%       | 86.36%      | 77.77%     |\n| `src/Util/ArrayUtil.js`                                    | 100%    | 100%         | 100%        | 100%       |\n| `src/Util/AsyncEventEmitter.js`                            | 95.45%  | 95.45%       | 100%        | 89.47%     |\n| `src/Util/Compatibility.js`                                | 79.66%  | 79.66%       | 75%         | 77.77%     |\n| `src/Util/ConsoleLogger.js`                                | 100%    | 100%         | 95%         | 100%       |\n| `src/Util/DateGitFirstAdded.js`                            | 100%    | 100%         | 100%        | 100%       |\n| `src/Util/DateGitLastUpdated.js`                           | 100%    | 100%         | 100%        | 100%       |\n| `src/Util/DirContains.js`                                  | 100%    | 100%         | 100%        | 100%       |\n| `src/Util/EsmResolver.js`                                  | 84.9%   | 84.9%        | 100%        | 85.71%     |\n| `src/Util/ExistsCache.js`                                  | 96.77%  | 96.77%       | 85.71%      | 100%       |\n| `src/Util/FilePathUtil.js`                                 | 47.36%  | 47.36%       | 50%         | 100%       |\n| `src/Util/FileSystemManager.js`                            | 72.91%  | 72.91%       | 66.66%      | 87.5%      |\n| `src/Util/GetJavaScriptData.js`                            | 100%    | 100%         | 100%        | 100%       |\n| `src/Util/GlobMatcher.js`                                  | 90.9%   | 90.9%        | 100%        | 66.66%     |\n| `src/Util/GlobRemap.js`                                    | 97.64%  | 97.64%       | 90%         | 100%       |\n| `src/Util/HtmlRelativeCopy.js`                             | 90.6%   | 90.6%        | 100%        | 89.18%     |\n| `src/Util/HtmlTransformer.js`                              | 90.11%  | 90.11%       | 88.88%      | 90.69%     |\n| `src/Util/ImportJsonSync.js`                               | 84.41%  | 84.41%       | 83.33%      | 92.3%      |\n| `src/Util/IsAsyncFunction.js`                              | 100%    | 100%         | 50%         | 100%       |\n| `src/Util/JavaScriptDependencies.js`                       | 89.09%  | 89.09%       | 50%         | 85.71%     |\n| `src/Util/MemoizeFunction.js`                              | 100%    | 100%         | 100%        | 100%       |\n| `src/Util/PassthroughCopyBehaviorCheck.js`                 | 100%    | 100%         | 100%        | 100%       |\n| `src/Util/PathNormalizer.js`                               | 93.1%   | 93.1%        | 100%        | 86.66%     |\n| `src/Util/PathPrefixer.js`                                 | 100%    | 100%         | 100%        | 100%       |\n| `src/Util/Pluralize.js`                                    | 100%    | 100%         | 100%        | 100%       |\n| `src/Util/ProjectDirectories.js`                           | 96.74%  | 96.74%       | 97.29%      | 96.11%     |\n| `src/Util/ProjectTemplateFormats.js`                       | 94.02%  | 94.02%       | 90%         | 94.73%     |\n| `src/Util/PromiseUtil.js`                                  | 46.66%  | 46.66%       | 100%        | 66.66%     |\n| `src/Util/Require.js`                                      | 82.94%  | 82.94%       | 75%         | 82.05%     |\n| `src/Util/ReservedData.js`                                 | 97.1%   | 97.1%        | 100%        | 92.85%     |\n| `src/Util/SetUnion.js`                                     | 100%    | 100%         | 100%        | 100%       |\n| `src/Util/SpawnAsync.js`                                   | 96.55%  | 96.55%       | 100%        | 87.5%      |\n| `src/Util/TemplateDepGraph.js`                             | 96.25%  | 96.25%       | 100%        | 93.61%     |\n| `src/Util/TransformsUtil.js`                               | 94.28%  | 94.28%       | 100%        | 83.33%     |\n| `src/Util/ValidUrl.js`                                     | 100%    | 100%         | 100%        | 100%       |\n| `src/Util/Objects/DeepFreeze.js`                           | 90%     | 90%          | 100%        | 80%        |\n| `src/Util/Objects/ObjectFilter.js`                         | 100%    | 100%         | 100%        | 80%        |\n| `src/Util/Objects/ProxyWrap.js`                            | 96.61%  | 96.61%       | 100%        | 94.73%     |\n| `src/Util/Objects/Sortable.js`                             | 100%    | 100%         | 100%        | 100%       |\n| `src/Util/Objects/Unique.js`                               | 100%    | 100%         | 100%        | 100%       |\n"
  },
  {
    "path": "docs/coverage.njk",
    "content": "---\npermalink: coverage.md\n---\n# Code Coverage for Eleventy v{{ pkg.version }}\n\n| Filename | % Lines | % Statements | % Functions | % Branches |\n| --- | --- | --- | --- | --- |\n{% for file, line in coverage -%}\n| `{{ file | removeDir }}` | {{ line.lines.pct }}% | {{ line.statements.pct }}% | {{ line.functions.pct }}% | {{ line.branches.pct }}% |\n{% endfor -%}\n"
  },
  {
    "path": "docs/eleventy.coverage.js",
    "content": "import { dirname } from \"path\";\nimport { fileURLToPath } from \"url\";\nimport { TemplatePath } from \"@11ty/eleventy-utils\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\nexport default function (eleventyConfig) {\n\televentyConfig.addFilter(\"removeDir\", function (str) {\n\t\treturn TemplatePath.stripLeadingSubPath(str, TemplatePath.join(__dirname, \"..\"));\n\t});\n\n\treturn {\n\t\ttemplateFormats: [\"njk\"],\n\t\tdir: {\n\t\t\tinput: \"docs/coverage.njk\",\n\t\t\toutput: \"docs/\", // root relative\n\t\t},\n\t};\n}\n"
  },
  {
    "path": "docs/release-instructions.md",
    "content": "# Dependency notes\n\n- (dev dep only) `@iarna/toml` has a 3.0 that we have never been on but it was released the same day as the last 2.x https://github.com/BinaryMuse/toml-node/commits/master (needs more investigation)\n\n# Release Procedure\n\n1. (Optional) Update minor dependencies in package.json\n   - `npx npm-check-updates --target minor -u --dep prod`\n   - or `npm outdated` + `npm update --save`\n1. Stable release only: make sure there aren’t any `@11ty/*` dependencies on pre-release versions alpha/beta/canary\n1. If the minimum Node version changed, make sure you update `package.json` engines property.\n   - Make sure the error message works correctly for Node versions less than 10.\n     - 0.12.x+ requires Node 10+\n     - 1.x+ requires Node 12+\n     - 2.x+ requires Node 14+\n     - 3.x+ requires Node 18+\n     - 4.x+ requires Node 20+\n1. `rm -rf node_modules && rm -f package-lock.json && npm install`\n1. `npm audit --omit=dev`\n1. Make sure `npm run check` (eslint) runs okay\n1. Make sure `npm run test` (ava) runs okay\n1. Update version in `package.json`\n   - (Alpha) Use `-alpha.1` suffix\n   - (Beta) Use `-beta.1` suffix\n1. Run `npm run coverage`\n1. Check it all in and commit\n1. Tag new version\n1. Wait for GitHub Actions to complete to know that the build did not fail.\n1. Publish a release on GitHub at https://github.com/11ty/eleventy/releases pointing to the tag of the release. Hitting the publish button on this workflow will use GitHub Actions to publish the package to npm on the correct dist-tag and includes npm package provenance for the release.\n\n- Main release: no version suffix publishes to `latest` (default) tag on npm\n  - Make sure to include OpenCollective usernames for release notes here https://www.11ty.dev/supporters-for-release-notes/\n- Canary release: `-alpha.` version suffix in `package.json` publishes to `canary` tag on npm: https://github.com/11ty/eleventy/issues/2758\n- Beta release: `-beta.` version suffix publishes to `beta` tag on npm\n\nUnfortunate note about npm and tags (specifically `canary` here): if you push a 1.0.0-canary.x to `canary` (even though `2.0.0-canary.x` exists), it will use the last pushed tag when you npm install from `@canary` (not the highest version number)\n\n# Docs/Website (Main releases only)\n\n1. Maybe search for `-alpha.` (`-canary.`?) or `-beta.` in the docs copy to update to the stable release, if applicable.\n1. Check in a new `11ty-website` site with updated `package.json` version.\n1. Add version to `11ty-website` `versions.json`\n1. Commit it\n1. Create a new branch for branched version\n1. (Main) Check out the previous version git branch and add `outdated: true` to `_data/config.json` and commit/push.\n1. Go to https://app.netlify.com/sites/11ty/settings/domain and set up a subdomain for it.\n\n# Downstream dependencies\n\n1. Update `eleventy-base-blog` to use new version\n1. Update `11ty-website` to use new version\n"
  },
  {
    "path": "eslint.config.js",
    "content": "import globals from \"globals\";\nimport pluginJs from \"@eslint/js\";\nimport stylisticJs from \"@stylistic/eslint-plugin-js\";\nimport prettier from \"eslint-config-prettier\";\n\nexport const GLOB_SRC_EXT = \"?([cm])[jt]s?(x)\";\n\nexport const GLOB_TESTS = [\n\t`**/test/**/*.${GLOB_SRC_EXT}`,\n\t`**/__tests__/**/*.${GLOB_SRC_EXT}`,\n\t`**/*.spec.${GLOB_SRC_EXT}`,\n\t`**/*.test.${GLOB_SRC_EXT}`,\n\t`**/*.bench.${GLOB_SRC_EXT}`,\n\t`**/*.benchmark.${GLOB_SRC_EXT}`,\n];\n\nexport default [\n\t{\n\t\tname: \"11ty/setup/js\",\n\t\t...pluginJs.configs.recommended,\n\t},\n\t{\n\t\tname: \"11ty/rules/project-specific\",\n\t\tplugins: {\n\t\t\t\"@stylistic/js\": stylisticJs,\n\t\t},\n\t\tlanguageOptions: {\n\t\t\tglobals: {\n\t\t\t\t...globals.node,\n\t\t\t},\n\t\t\tecmaVersion: \"latest\",\n\t\t\tsourceType: \"module\",\n\t\t},\n\t\trules: {\n\t\t\t\"no-async-promise-executor\": \"warn\",\n\t\t\t\"no-prototype-builtins\": \"warn\",\n\t\t\t\"no-unused-vars\": \"warn\",\n\t\t\t\"@stylistic/js/space-unary-ops\": \"error\",\n\t\t},\n\t},\n\t{\n\t\tname: \"11ty/ignores\",\n\t\tfiles: GLOB_TESTS,\n\t\trules: {\n\t\t\t\"no-unused-vars\": \"off\",\n\t\t},\n\t},\n\t{\n\t\tname: \"11ty/setup/prettier\",\n\t\t...prettier,\n\t},\n];\n"
  },
  {
    "path": "package.json",
    "content": "{\n\t\"name\": \"@11ty/eleventy\",\n\t\"version\": \"4.0.0-alpha.6\",\n\t\"description\": \"A simpler static site generator.\",\n\t\"publishConfig\": {\n\t\t\"access\": \"public\",\n\t\t\"provenance\": true\n\t},\n\t\"type\": \"module\",\n\t\"main\": \"./src/Eleventy.js\",\n\t\"exports\": {\n\t\t\".\": {\n\t\t\t\"import\": \"./src/Eleventy.js\",\n\t\t\t\"require\": \"./src/EleventyCommonJs.cjs\"\n\t\t},\n\t\t\"./UserConfig\": {\n\t\t\t\"types\": \"./src/UserConfig.js\"\n\t\t},\n\t\t\"./utils/git\": \"./src/Util/Git.js\"\n\t},\n\t\"bin\": {\n\t\t\"eleventy\": \"cmd.cjs\"\n\t},\n\t\"license\": \"MIT\",\n\t\"engines\": {\n\t\t\"node\": \">=20.19\"\n\t},\n\t\"workspaces\": [\n\t\t\"packages/client\"\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\"static-site-generator\",\n\t\t\"static-site\",\n\t\t\"ssg\",\n\t\t\"documentation\",\n\t\t\"website\",\n\t\t\"jekyll\",\n\t\t\"blog\",\n\t\t\"templates\",\n\t\t\"generator\",\n\t\t\"framework\",\n\t\t\"eleventy\",\n\t\t\"11ty\",\n\t\t\"html\",\n\t\t\"markdown\",\n\t\t\"liquid\",\n\t\t\"nunjucks\"\n\t],\n\t\"scripts\": {\n\t\t\"default\": \"npm run test\",\n\t\t\"test\": \"npm run test:server && npm run test:client\",\n\t\t\"test:server\": \"npm run test:node && npm run test:ava\",\n\t\t\"test:ava\": \"ava --verbose --timeout 20s\",\n\t\t\"test:node\": \"node --test test_node/tests.js\",\n\t\t\"test:client\": \"cross-env CI=true npm run test --workspaces\",\n\t\t\"format\": \"prettier . --write\",\n\t\t\"check\": \"eslint src\",\n\t\t\"check-types\": \"tsc\",\n\t\t\"nano-staged\": \"nano-staged\",\n\t\t\"coverage\": \"npx c8 ava && npx c8 report --reporter=json-summary && cp coverage/coverage-summary.json docs/_data/coverage.json && node cmd.cjs --config=docs/eleventy.coverage.js\",\n\t\t\"prepare\": \"simple-git-hooks\"\n\t},\n\t\"author\": \"Zach Leatherman <zachleatherman@gmail.com> (https://zachleat.com/)\",\n\t\"repository\": {\n\t\t\"type\": \"git\",\n\t\t\"url\": \"git+https://github.com/11ty/eleventy.git\"\n\t},\n\t\"bugs\": \"https://github.com/11ty/eleventy/issues\",\n\t\"homepage\": \"https://www.11ty.dev/\",\n\t\"ava\": {\n\t\t\"environmentVariables\": {},\n\t\t\"failFast\": true,\n\t\t\"files\": [\n\t\t\t\"./test/*.js\",\n\t\t\t\"./test/_issues/**/*test.js\"\n\t\t],\n\t\t\"watchMode\": {\n\t\t\t\"ignoreChanges\": [\n\t\t\t\t\"./test/stubs*/**/*\",\n\t\t\t\t\"./test/**/_site/**/*\",\n\t\t\t\t\".cache\"\n\t\t\t]\n\t\t}\n\t},\n\t\"nano-staged\": {\n\t\t\"*.{js,css,md}\": [\n\t\t\t\"prettier --write\"\n\t\t]\n\t},\n\t\"simple-git-hooks\": {\n\t\t\"pre-commit\": \"npm test && npm run nano-staged\",\n\t\t\"pre-push\": \"npm test\"\n\t},\n\t\"devDependencies\": {\n\t\t\"@11ty/eleventy-img\": \"^6.0.4\",\n\t\t\"@11ty/eleventy-plugin-rss\": \"^2.0.4\",\n\t\t\"@11ty/eleventy-plugin-syntaxhighlight\": \"^5.0.2\",\n\t\t\"@11ty/eleventy-plugin-webc\": \"^0.12.0-beta.7\",\n\t\t\"@eslint/js\": \"^10.0.1\",\n\t\t\"@iarna/toml\": \"^2.2.5\",\n\t\t\"@mdx-js/node-loader\": \"^3.1.1\",\n\t\t\"@stylistic/eslint-plugin-js\": \"^4.4.1\",\n\t\t\"@types/node\": \"^25.5.0\",\n\t\t\"@vue/server-renderer\": \"^3.5.30\",\n\t\t\"@zachleat/noop\": \"^1.0.7\",\n\t\t\"ava\": \"^6.4.1\",\n\t\t\"c8\": \"^10.1.3\",\n\t\t\"cross-env\": \"^10.1.0\",\n\t\t\"eslint\": \"^10.0.3\",\n\t\t\"eslint-config-prettier\": \"^10.1.8\",\n\t\t\"globals\": \"^17.4.0\",\n\t\t\"jsx-async-runtime\": \"^2.0.3\",\n\t\t\"luxon\": \"^3.7.2\",\n\t\t\"markdown-it-abbr\": \"^2.0.0\",\n\t\t\"markdown-it-emoji\": \"^3.0.0\",\n\t\t\"marked\": \"^17.0.4\",\n\t\t\"nano-staged\": \"^0.9.0\",\n\t\t\"prettier\": \"^3.8.1\",\n\t\t\"pretty\": \"^2.0.0\",\n\t\t\"react\": \"^19.2.4\",\n\t\t\"react-dom\": \"^19.2.4\",\n\t\t\"sass\": \"^1.98.0\",\n\t\t\"simple-git-hooks\": \"^2.13.1\",\n\t\t\"tsx\": \"^4.21.0\",\n\t\t\"typescript\": \"^5.9.3\",\n\t\t\"vue\": \"^3.5.30\",\n\t\t\"zod\": \"^4.3.6\",\n\t\t\"zod-validation-error\": \"^5.0.0\"\n\t},\n\t\"dependencies\": {\n\t\t\"@11ty/dependency-tree\": \"^4.0.2\",\n\t\t\"@11ty/dependency-tree-esm\": \"^2.0.4\",\n\t\t\"@11ty/dependency-tree-typescript\": \"^1.0.0\",\n\t\t\"@11ty/eleventy-dev-server\": \"^3.0.0-alpha.6\",\n\t\t\"@11ty/eleventy-plugin-bundle\": \"^3.0.7\",\n\t\t\"@11ty/eleventy-utils\": \"^2.0.7\",\n\t\t\"@11ty/gray-matter\": \"^2.0.1\",\n\t\t\"@11ty/lodash-custom\": \"^4.17.21\",\n\t\t\"@11ty/node-version-check\": \"^1.0.1\",\n\t\t\"@11ty/nunjucks\": \"^4.0.0-alpha.1\",\n\t\t\"@11ty/parse-date-strings\": \"^2.0.6\",\n\t\t\"@11ty/posthtml-urls\": \"^1.0.2\",\n\t\t\"@11ty/recursive-copy\": \"^5.0.2\",\n\t\t\"@sindresorhus/slugify\": \"^3.0.0\",\n\t\t\"bcp-47-normalize\": \"^2.3.0\",\n\t\t\"chokidar\": \"^5.0.0\",\n\t\t\"debug\": \"^4.4.3\",\n\t\t\"dependency-graph\": \"^1.0.0\",\n\t\t\"entities\": \"^7.0.1\",\n\t\t\"import-module-string\": \"^2.0.3\",\n\t\t\"iso-639-1\": \"^3.1.5\",\n\t\t\"kleur\": \"^4.1.5\",\n\t\t\"liquidjs\": \"^10.25.0\",\n\t\t\"markdown-it\": \"^14.1.1\",\n\t\t\"minimist\": \"^1.2.8\",\n\t\t\"moo\": \"0.5.2\",\n\t\t\"node-retrieve-globals\": \"^6.0.1\",\n\t\t\"picomatch\": \"^4.0.3\",\n\t\t\"posthtml\": \"^0.16.7\",\n\t\t\"posthtml-match-helper\": \"^2.0.3\",\n\t\t\"semver\": \"^7.7.4\",\n\t\t\"tinyglobby\": \"^0.2.15\"\n\t},\n\t\"overrides\": {\n\t\t\"fdir\": \"6.4.6\"\n\t}\n}\n"
  },
  {
    "path": "packages/client/README.md",
    "content": "<p align=\"center\"><img src=\"https://www.11ty.dev/img/logo-github.svg\" width=\"200\" height=\"200\" alt=\"eleventy Logo\"></p>\n\n# `@11ty/client`\n\nThe client (browser-friendly) version of `@11ty/eleventy` Eleventy, a simpler static site generator.\n\n## ➡ [Documentation](https://www.11ty.dev/docs/)\n\n- Star [this repo on GitHub](https://github.com/11ty/eleventy/)!\n- Follow us [on Mastodon `@11ty@neighborhood.11ty.dev`](https://neighborhood.11ty.dev/@11ty)\n- Follow us [on Bluesky `@11ty.dev`](https://bsky.app/profile/11ty.dev)\n- Install [from npm](https://www.npmjs.com/org/11ty)\n- Follow [on GitHub](https://github.com/11ty)\n- Watch us [on YouTube](https://www.youtube.com/c/EleventyVideo)\n- Chat on [Discord](https://www.11ty.dev/blog/discord/)\n- Latest: [![npm Version](https://img.shields.io/npm/v/@11ty/client.svg?style=for-the-badge)](https://www.npmjs.com/package/@11ty/client)\n\n## Installation\n\n```\nnpm install @11ty/client --save\n```\n\nWith exports for:\n\n- `@11ty/client`\n- `@11ty/client/md` (Markdown Template Engine)\n- `@11ty/client/njk` (Nunjucks Template Engine)\n- `@11ty/client/liquid` (Liquid Template Engine)\n- `@11ty/client/i18n` (i18n Plugin)\n"
  },
  {
    "path": "packages/client/generate-bundle.js",
    "content": "import fs from \"node:fs\";\nimport { default as bundleClient } from \"@11ty/package-bundler\";\n\nimport pkg from \"../../package.json\" with { type: \"json\" };\nimport { readableFileSize } from \"../../src/Util/FileSize.js\";\n\nconst PREFIX = `[11ty/bundle/client] `;\n\nfunction size(filepath) {\n\treturn readableFileSize(fs.statSync(filepath).size);\n}\n\nawait bundleClient(\"./src/BundleCore.js\", \"./dist/eleventy.core.js\", {\n\tname: `Eleventy v${pkg.version} (@11ty/client Bundle)`,\n\tmoduleRoot: \"../../\",\n\t// No core-bundled plugins, reduced feature set\n\tadapterSuffixes: [\".client.js\", \".core.js\", \".core.cjs\"],\n\texternal: [\"node:fs\", \"node:crypto\", \"@sindresorhus/slugify\"],\n\tesbuild: {\n\t\tkeepNames: false,\n\t\t// minify: true\n\t},\n});\n\nconsole.log(`${PREFIX}Wrote dist/eleventy.core.js: ${size(\"./dist/eleventy.core.js\")}`);\n\n// Careful, this one is big!\nawait bundleClient(\"./src/BundleEleventy.js\", `./dist/eleventy.js`, {\n\tname: `Eleventy v${pkg.version} (@11ty/client/eleventy Bundle)`,\n\tmoduleRoot: \"../../\",\n\tadapterSuffixes: [\".core.js\", \".core.cjs\"],\n\t// Adds named export FileSystem for using the file system in other packages\n\tfileSystemMode: \"publish\",\n});\nconsole.log(`${PREFIX}Wrote dist/eleventy.js: ${size(\"./dist/eleventy.js\")}`);\n\n// fs.mkdirSync(\"./visualize/\", { recursive: true });\n// fs.writeFileSync(\"./visualize/meta.json\", JSON.stringify(result.metafile));\n// npx esbuild-visualizer --metadata ./packages/client/visualize/meta.json --filename packages/client/visualize/index.html\n\nawait bundleClient(\n\timport.meta.resolve(\"./src/BundleLiquid.js\"),\n\t`./dist/formats/eleventy-liquid.js`,\n\t{\n\t\tname: `Eleventy v${pkg.version} (@11ty/client/liquid Engine Bundle)`,\n\t\tmoduleRoot: \"../../\",\n\t\tadapterSuffixes: [\".core.js\", \".core.cjs\"],\n\t},\n);\nconsole.log(\n\t`${PREFIX}Wrote dist/formats/eleventy-liquid.js: ${size(\"./dist/formats/eleventy-liquid.js\")}`,\n);\n\nawait bundleClient(\n\timport.meta.resolve(\"./src/BundleNunjucks.js\"),\n\t`./dist/formats/eleventy-nunjucks.js`,\n\t{\n\t\tname: `Eleventy v${pkg.version} (@11ty/client/njk Engine Bundle)`,\n\t\tmoduleRoot: \"../../\",\n\t},\n);\nconsole.log(\n\t`${PREFIX}Wrote dist/formats/eleventy-nunjucks.js: ${size(\"./dist/formats/eleventy-nunjucks.js\")}`,\n);\n\nawait bundleClient(\n\timport.meta.resolve(\"./src/BundleMarkdown.js\"),\n\t`./dist/formats/eleventy-markdown.js`,\n\t{\n\t\tname: `Eleventy v${pkg.version} (@11ty/client/md Engine Bundle)`,\n\t\tmoduleRoot: \"../../\",\n\t\tadapterSuffixes: [\".core.js\", \".core.cjs\"],\n\t},\n);\nconsole.log(\n\t`${PREFIX}Wrote dist/formats/eleventy-markdown.js: ${size(\"./dist/formats/eleventy-markdown.js\")}`,\n);\n\nawait bundleClient(\n\timport.meta.resolve(\"./src/BundleI18nPlugin.js\"),\n\t`./dist/plugins/eleventy-plugin-i18n.js`,\n\t{\n\t\tname: `Eleventy v${pkg.version} (i18n Plugin)`,\n\t\tmoduleRoot: \"../../\",\n\t\tadapterSuffixes: [\".core.js\", \".core.cjs\"],\n\t},\n);\nconsole.log(\n\t`${PREFIX}Wrote dist/plugins/eleventy-plugin-i18n.js: ${size(\"./dist/plugins/eleventy-plugin-i18n.js\")}`,\n);\n"
  },
  {
    "path": "packages/client/package.json",
    "content": "{\n  \"name\": \"@11ty/client\",\n  \"description\": \"Run Eleventy in your browser.\",\n  \"version\": \"PRIVATE\",\n  \"private\": true,\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"provenance\": true\n  },\n  \"type\": \"module\",\n  \"main\": \"./dist/eleventy.core.js\",\n  \"exports\": {\n    \".\": \"./dist/eleventy.core.js\",\n    \"./eleventy\": \"./dist/eleventy.js\",\n    \"./liquid\": \"./dist/formats/eleventy-liquid.js\",\n    \"./njk\": \"./dist/formats/eleventy-nunjucks.js\",\n    \"./md\": \"./dist/formats/eleventy-markdown.js\",\n    \"./i18n\": \"./dist/plugins/eleventy-plugin-i18n.js\"\n  },\n  \"files\": [\n    \"./dist/**/*.js\"\n  ],\n  \"license\": \"MIT\",\n  \"funding\": {\n    \"type\": \"opencollective\",\n    \"url\": \"https://opencollective.com/11ty\"\n  },\n  \"scripts\": {\n    \"build\": \"node generate-bundle.js\",\n    \"test\": \"npm run build && npx vitest\",\n    \"prepare\": \"npm run build\"\n  },\n  \"author\": \"Zach Leatherman <zachleatherman@gmail.com> (https://zachleat.com/)\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git://github.com/11ty/eleventy.git\"\n  },\n  \"bugs\": \"https://github.com/11ty/eleventy/issues\",\n  \"homepage\": \"https://www.11ty.dev/\",\n  \"devDependencies\": {\n    \"@11ty/package-bundler\": \"^0.5.5\",\n    \"@vitest/browser\": \"^4.0.5\",\n    \"@vitest/browser-playwright\": \"^4.0.5\",\n    \"playwright\": \"^1.56.1\",\n    \"vitest\": \"^4.0.5\"\n  }\n}\n"
  },
  {
    "path": "packages/client/src/BundleCore.js",
    "content": "// see BundleEleventy.js for Core WITH bundled Eleventy core plugins\nimport \"./shims/shim-core.js\";\n\nexport { MinimalCore as Eleventy } from \"../../../src/CoreMinimal.js\";\n"
  },
  {
    "path": "packages/client/src/BundleEleventy.js",
    "content": "import \"./shims/shim-core.js\";\n\n// @11ty/eleventy-plugin-bundle is not exported here (differing from Node package) but *is* bundled (and exposed via Configuration API)\nexport { IdAttributePlugin } from \"../../../src/Plugins/IdAttributePlugin.js\";\nexport { default as HtmlBasePlugin } from \"../../../src/Plugins/HtmlBasePlugin.js\";\nexport { TransformPlugin as InputPathToUrlTransformPlugin } from \"../../../src/Plugins/InputPathToUrl.js\";\nexport { default as RenderPlugin } from \"../../../src/Plugins/RenderPlugin.js\";\n// i18n Plugin is separate (see BundleI18nPlugin.js and @11ty/client/i18n)\n\n// Note for future visitors, an attempt was made to separate these plugins the bundle (so that they could be exported separately)\n// - HtmlBasePlugin and InputPathToUrl were moved to async in the ResolvePlugin.js adapter.\n// - Extended configuration was removed from defaultConfig.js\n// This saved ~400KB (unmin) from the bundle but the separate bundle was way larger than the savings (> 1MB)\n\nexport { Core as Eleventy } from \"../../../src/Core.js\";\n\nexport { default as FileSystem } from \"node:fs\";\n"
  },
  {
    "path": "packages/client/src/BundleI18nPlugin.js",
    "content": "export { default as I18nPlugin } from \"../../../src/Plugins/I18nPlugin.js\";\n"
  },
  {
    "path": "packages/client/src/BundleLiquid.js",
    "content": "export { default as Liquid } from \"../../../src/Engines/Liquid.js\";\n"
  },
  {
    "path": "packages/client/src/BundleMarkdown.js",
    "content": "export { default as Markdown } from \"../../../src/Engines/Markdown.js\";\n"
  },
  {
    "path": "packages/client/src/BundleNunjucks.js",
    "content": "export { default as Nunjucks } from \"../../../src/Engines/Nunjucks.js\";\n"
  },
  {
    "path": "packages/client/src/shims/process.cjs",
    "content": "// MIT Licensed\n// https://github.com/defunctzombie/node-process/blob/master/browser.js\n// shim for using process in browser\nvar process = module.exports = {};\n\n// cached from whatever global is present so that test runners that stub it\n// don't break things.  But we need to wrap it in a try catch in case it is\n// wrapped in strict mode code which doesn't define any globals.  It's inside a\n// function because try/catches deoptimize in certain engines.\n\nvar cachedSetTimeout;\nvar cachedClearTimeout;\n\nfunction defaultSetTimout() {\n    throw new Error('setTimeout has not been defined');\n}\nfunction defaultClearTimeout () {\n    throw new Error('clearTimeout has not been defined');\n}\n(function () {\n    try {\n        if (typeof setTimeout === 'function') {\n            cachedSetTimeout = setTimeout;\n        } else {\n            cachedSetTimeout = defaultSetTimout;\n        }\n    } catch (e) {\n        cachedSetTimeout = defaultSetTimout;\n    }\n    try {\n        if (typeof clearTimeout === 'function') {\n            cachedClearTimeout = clearTimeout;\n        } else {\n            cachedClearTimeout = defaultClearTimeout;\n        }\n    } catch (e) {\n        cachedClearTimeout = defaultClearTimeout;\n    }\n} ())\nfunction runTimeout(fun) {\n    if (cachedSetTimeout === setTimeout) {\n        //normal enviroments in sane situations\n        return setTimeout(fun, 0);\n    }\n    // if setTimeout wasn't available but was latter defined\n    if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) {\n        cachedSetTimeout = setTimeout;\n        return setTimeout(fun, 0);\n    }\n    try {\n        // when when somebody has screwed with setTimeout but no I.E. maddness\n        return cachedSetTimeout(fun, 0);\n    } catch(e){\n        try {\n            // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally\n            return cachedSetTimeout.call(null, fun, 0);\n        } catch(e){\n            // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error\n            return cachedSetTimeout.call(this, fun, 0);\n        }\n    }\n\n\n}\nfunction runClearTimeout(marker) {\n    if (cachedClearTimeout === clearTimeout) {\n        //normal enviroments in sane situations\n        return clearTimeout(marker);\n    }\n    // if clearTimeout wasn't available but was latter defined\n    if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) {\n        cachedClearTimeout = clearTimeout;\n        return clearTimeout(marker);\n    }\n    try {\n        // when when somebody has screwed with setTimeout but no I.E. maddness\n        return cachedClearTimeout(marker);\n    } catch (e){\n        try {\n            // When we are in I.E. but the script has been evaled so I.E. doesn't  trust the global object when called normally\n            return cachedClearTimeout.call(null, marker);\n        } catch (e){\n            // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error.\n            // Some versions of I.E. have different rules for clearTimeout vs setTimeout\n            return cachedClearTimeout.call(this, marker);\n        }\n    }\n\n\n\n}\nvar queue = [];\nvar draining = false;\nvar currentQueue;\nvar queueIndex = -1;\n\nfunction cleanUpNextTick() {\n    if (!draining || !currentQueue) {\n        return;\n    }\n    draining = false;\n    if (currentQueue.length) {\n        queue = currentQueue.concat(queue);\n    } else {\n        queueIndex = -1;\n    }\n    if (queue.length) {\n        drainQueue();\n    }\n}\n\nfunction drainQueue() {\n    if (draining) {\n        return;\n    }\n    var timeout = runTimeout(cleanUpNextTick);\n    draining = true;\n\n    var len = queue.length;\n    while(len) {\n        currentQueue = queue;\n        queue = [];\n        while (++queueIndex < len) {\n            if (currentQueue) {\n                currentQueue[queueIndex].run();\n            }\n        }\n        queueIndex = -1;\n        len = queue.length;\n    }\n    currentQueue = null;\n    draining = false;\n    runClearTimeout(timeout);\n}\n\nprocess.nextTick = function (fun) {\n    var args = new Array(arguments.length - 1);\n    if (arguments.length > 1) {\n        for (var i = 1; i < arguments.length; i++) {\n            args[i - 1] = arguments[i];\n        }\n    }\n    queue.push(new Item(fun, args));\n    if (queue.length === 1 && !draining) {\n        runTimeout(drainQueue);\n    }\n};\n\n// v8 likes predictible objects\nfunction Item(fun, array) {\n    this.fun = fun;\n    this.array = array;\n}\nItem.prototype.run = function () {\n    this.fun.apply(null, this.array);\n};\nprocess.title = 'browser';\nprocess.browser = true;\nprocess.env = {};\nprocess.argv = [];\nprocess.version = ''; // empty string to avoid regexp issues\nprocess.versions = {};\n\nfunction noop() {}\n\nprocess.on = noop;\nprocess.addListener = noop;\nprocess.once = noop;\nprocess.off = noop;\nprocess.removeListener = noop;\nprocess.removeAllListeners = noop;\nprocess.emit = noop;\nprocess.prependListener = noop;\nprocess.prependOnceListener = noop;\n\nprocess.listeners = function (name) { return [] }\n\nprocess.binding = function (name) {\n    throw new Error('process.binding is not supported');\n};\n\nprocess.cwd = function () { return '/' };\nprocess.chdir = function (dir) {\n    throw new Error('process.chdir is not supported');\n};\nprocess.umask = function() { return 0; };\n"
  },
  {
    "path": "packages/client/src/shims/shim-core.js",
    "content": "import * as process from \"./process.cjs\";\n\n// `path` polyfill needs this\nwindow.process = globalThis.process = process;\n\n// `recursive-copy` needs this (not necessary for Core.js)\nwindow.global = globalThis || window;\n\n// @11ty/eleventy needs this\nclass Buffer {\n\tstatic [Symbol.hasInstance](instance) {\n\t\treturn this.isBuffer(instance);\n\t}\n\n\tstatic isBuffer() {\n\t\treturn false;\n\t}\n}\n\nwindow.Buffer = globalThis.Buffer = Buffer;\n"
  },
  {
    "path": "packages/client/test/client-core.test.js",
    "content": "import { Eleventy } from \"../dist/eleventy.core.js\";\nimport sharedTests from \"./shared-tests.js\";\n\nsharedTests(Eleventy);\n"
  },
  {
    "path": "packages/client/test/client-eleventy.test.js",
    "content": "import { Eleventy } from \"../dist/eleventy.js\";\nimport sharedTests from \"./shared-tests.js\";\n\nsharedTests(Eleventy);\n"
  },
  {
    "path": "packages/client/test/shared-tests.js",
    "content": "import { assert, test } from \"vitest\";\nimport { Markdown } from \"../dist/formats/eleventy-markdown.js\";\nimport { Liquid } from \"../dist/formats/eleventy-liquid.js\";\nimport { Nunjucks } from \"../dist/formats/eleventy-nunjucks.js\";\nimport { I18nPlugin } from \"../dist/plugins/eleventy-plugin-i18n.js\";\n\nexport default function(Eleventy) {\n\ttest(\"Get version number\", async () => {\n\t\tassert.typeOf(Eleventy.getVersion(), \"string\");\n\t});\n\n\ttest(\"Markdown (no preprocessor) template\", async () => {\n\t\tlet elev = new Eleventy({\n\t\t\tconfig(eleventyConfig) {\n\t\t\t\televentyConfig.addEngine(\"md\", Markdown);\n\t\t\t\televentyConfig.setMarkdownTemplateEngine(false);\n\t\t\t\televentyConfig.setHtmlTemplateEngine(false);\n\t\t\t\televentyConfig.setTemplateFormats(\"md\");\n\t\t\t\televentyConfig.addTemplate(\"index.md\", `# Heading`);\n\n\t\t\t}\n\t\t});\n\n\t\tlet json = await elev.toJSON();\n\t\tassert.strictEqual(json[0].content.trim(), `<h1>Heading</h1>`);\n\t});\n\n\ttest(\"Markdown (via Liquid) template\", async () => {\n\t\tlet elev = new Eleventy({\n\t\t\tconfig(eleventyConfig) {\n\t\t\t\televentyConfig.addEngine(\"md\", Markdown);\n\t\t\t\televentyConfig.addEngine(\"liquid\", Liquid);\n\t\t\t\televentyConfig.setTemplateFormats(\"md\");\n\t\t\t\televentyConfig.addTemplate(\"index.md\", `# {{ title }}`, {\n\t\t\t\t\ttitle: \"Heading\"\n\t\t\t\t});\n\n\t\t\t}\n\t\t});\n\n\t\tlet json = await elev.toJSON();\n\t\tassert.strictEqual(json[0].content.trim(), `<h1>Heading</h1>`);\n\t});\n\n\ttest(\"Liquid template\", async () => {\n\t\tlet elev = new Eleventy({\n\t\t\tconfig(eleventyConfig) {\n\t\t\t\televentyConfig.addEngine(\"liquid\", Liquid);\n\t\t\t\televentyConfig.setTemplateFormats(\"liquid\");\n\t\t\t\televentyConfig.addTemplate(\"index.liquid\", `<h1>{{ title }}</h1>`, { title: \"Heading\" });\n\t\t\t}\n\t\t});\n\n\t\tlet json = await elev.toJSON();\n\t\tassert.strictEqual(json[0].content.trim(), `<h1>Heading</h1>`);\n\t});\n\n\ttest(\"Nunjucks template\", async () => {\n\t\tlet elev = new Eleventy({\n\t\t\tconfig(eleventyConfig) {\n\t\t\t\televentyConfig.addEngine(\"njk\", Nunjucks);\n\t\t\t\televentyConfig.setTemplateFormats(\"njk\");\n\t\t\t\televentyConfig.addTemplate(\"index.njk\", `<h1>{{ title }}</h1>`, { title: \"Heading\" });\n\n\t\t\t}\n\t\t});\n\n\t\tlet json = await elev.toJSON();\n\t\tassert.strictEqual(json[0].content.trim(), `<h1>Heading</h1>`);\n\t});\n\n\ttest(\"i18n Plugin Use (with 11ty.js)\", async () => {\n\t\tlet elev = new Eleventy({\n\t\t\tconfig(eleventyConfig) {\n\t\t\t\televentyConfig.addPlugin(I18nPlugin, {\n\t\t\t\t\tdefaultLanguage: \"en\"\n\t\t\t\t});\n\t\t\t\televentyConfig.addTemplate(\"./en/index.11ty.js\", function (data) {\n\t\t\t\t\treturn `<a href=\"${this.locale_url(\"/\")}\">Home</a>`;\n\t\t\t\t});\n\t\t\t\televentyConfig.addTemplate(\"./es/index.11ty.js\", function (data) {\n\t\t\t\t\treturn `<a href=\"${this.locale_url(\"/\")}\">Home</a>`;\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\n\t\tlet json = await elev.toJSON();\n\t\tassert.strictEqual(json[0].content.trim(), `<a href=\"/en/\">Home</a>`);\n\t\tassert.strictEqual(json[1].content.trim(), `<a href=\"/es/\">Home</a>`);\n\t});\n\n\t// Careful, `@11ty/client` will resolve slugify via Vite instead of it bundled with the package\n\ttest(\"slugify Filter in Liquid\", async () => {\n\t\tlet elev = new Eleventy({\n\t\t\tconfig(eleventyConfig) {\n\t\t\t\televentyConfig.addEngine(\"liquid\", Liquid);\n\t\t\t\televentyConfig.setTemplateFormats(\"liquid\");\n\t\t\t\televentyConfig.addTemplate(\"index.liquid\", `{{ title | slugify }}`, { title: \"This is a heading\" });\n\t\t\t}\n\t\t});\n\n\t\tlet json = await elev.toJSON();\n\t\tassert.strictEqual(json[0].content.trim(), `this-is-a-heading`);\n\t});\n\n\t// Careful, `@11ty/client` will resolve slugify via Vite instead of it bundled with the package\n\ttest(\"slugify Filter in Nunjucks\", async () => {\n\t\tlet elev = new Eleventy({\n\t\t\tconfig(eleventyConfig) {\n\t\t\t\televentyConfig.addEngine(\"njk\", Nunjucks);\n\t\t\t\televentyConfig.setTemplateFormats(\"njk\");\n\t\t\t\televentyConfig.addTemplate(\"index.njk\", `{{ title | slugify }}`, { title: \"This is a heading\" });\n\t\t\t}\n\t\t});\n\n\t\tlet json = await elev.toJSON();\n\t\tassert.strictEqual(json[0].content.trim(), `this-is-a-heading`);\n\t});\n}\n"
  },
  {
    "path": "packages/client/update-package-json.js",
    "content": "// Intended to run from repository root in release.sh script\n\nimport fs from \"node:fs\";\nimport corePkg from \"../../package.json\" with { type: \"json\" };\n\n// assign new version in local package.json from core package.json\nimport clientPkg from \"./package.json\" with { type: \"json\" };\n\nclientPkg.version = corePkg.version;\ndelete clientPkg.private; // allow publish\n\nif (\n\tcorePkg.name !== \"@11ty/eleventy\" ||\n\tclientPkg.name !== \"@11ty/client\" ||\n\t!fs.existsSync(\"./packages/client/package.json\")\n) {\n\tthrow new Error(\"Did you run this script from the wrong directory?\");\n}\n\nfs.writeFileSync(\"./packages/client/package.json\", JSON.stringify(clientPkg, null, 2), \"utf8\");\nconsole.log(`[11ty/bundle/client] Updated @11ty/client package version to ${corePkg.version}`);\n"
  },
  {
    "path": "packages/client/vitest.config.js",
    "content": "import { defineConfig } from \"vitest/config\";\nimport { playwright } from \"@vitest/browser-playwright\";\nimport os from \"node:os\";\n\nlet instances = [{ browser: \"chromium\" }, { browser: \"firefox\" }];\n\nif (os.type() === \"Darwin\") {\n\tinstances.push({ browser: \"webkit\" });\n}\n\nexport default defineConfig({\n\ttest: {\n\t\tbrowser: {\n\t\t\tenabled: true,\n\t\t\tprovider: playwright(),\n\t\t\theadless: true,\n\t\t\tscreenshotFailures: false,\n\t\t\t// https://vitest.dev/guide/browser/playwright\n\t\t\tinstances,\n\t\t},\n\t},\n});\n"
  },
  {
    "path": "scripts/release-dryrun.sh",
    "content": "export NPM_PUBLISH_TAG=\"canary\"\nexport DRY_RUN=\"--dry-run\" # leave that space as-is\n\necho \"Running @11ty/eleventy and @11ty/client publish dry run test\"\n\n./scripts/release.sh\n"
  },
  {
    "path": "scripts/release.sh",
    "content": "if ! npm ci; then\n\techo 'Release error: npm ci command failed.'\n\texit 1\nfi\n\nif ! npx playwright install; then\n\techo 'Release error: npx playwright install command failed (for Vitest Browser Mode).'\n\texit 1\nfi\n\n# This step includes running packages/ test suites\nif ! npm test; then\n\techo 'Release error: npm test command failed.'\n\texit 1\nfi\n\nif [ -z \"$NPM_PUBLISH_TAG\" ]; then\n  echo 'Release error: missing NPM_PUBLISH_TAG environment variable'\n\texit 1\nfi\n\nnode packages/client/update-package-json.js\n\n# Will skip publishing root if publishing workspaces fails\nif npm publish --workspaces --provenance --access=public --tag=$NPM_PUBLISH_TAG $DRY_RUN; then\n  npm publish --provenance --access=public --tag=$NPM_PUBLISH_TAG $DRY_RUN\nfi\n"
  },
  {
    "path": "src/Adapters/Engines/Liquid.core.js",
    "content": "// Must load dynamically in standard core\nexport default undefined;\n"
  },
  {
    "path": "src/Adapters/Engines/Liquid.js",
    "content": "export { default } from \"../../Engines/Liquid.js\";\n"
  },
  {
    "path": "src/Adapters/Engines/Markdown.core.js",
    "content": "// Must load dynamically in standard core\nexport default undefined;\n"
  },
  {
    "path": "src/Adapters/Engines/Markdown.js",
    "content": "export { default } from \"../../Engines/Markdown.js\";\n"
  },
  {
    "path": "src/Adapters/Engines/Nunjucks.core.js",
    "content": "// Must load dynamically in standard core\nexport default undefined;\n"
  },
  {
    "path": "src/Adapters/Engines/Nunjucks.js",
    "content": "export { default } from \"../../Engines/Nunjucks.js\";\n"
  },
  {
    "path": "src/Adapters/Packages/chalk.client.js",
    "content": "function noop(arg) {\n\treturn arg;\n}\nexport default {\n\tbold: noop,\n\tred: noop,\n\tgray: noop,\n\tcyan: noop,\n\tyellow: noop,\n};\n"
  },
  {
    "path": "src/Adapters/Packages/chalk.js",
    "content": "import chalk from \"kleur\";\n\nexport default chalk;\n"
  },
  {
    "path": "src/Adapters/Packages/inspect.core.js",
    "content": "export function inspect(target) {\n\treturn JSON.stringify(target, null, 2);\n}\n"
  },
  {
    "path": "src/Adapters/Packages/inspect.js",
    "content": "import { inspect as nodeInspect } from \"node:util\";\n\nexport function inspect(target) {\n\treturn nodeInspect(target, { showHidden: false, depth: null });\n}\n"
  },
  {
    "path": "src/Adapters/Packages/semver.client.js",
    "content": "export function satisfies(version, range) {\n\t// Always return true\n\treturn true;\n}\n"
  },
  {
    "path": "src/Adapters/Packages/semver.js",
    "content": "// Costs ~18 KB\nimport semverSatisfies from \"semver/functions/satisfies.js\";\n\nexport { semverSatisfies as satisfies };\n"
  },
  {
    "path": "src/Adapters/Packages/url.core.js",
    "content": "import path from \"node:path\";\n\n// TODO move this into package-bundler as a shim?\n// Thank you bare-url!\n// Apache-2.0 LICENSE https://github.com/holepunchto/bare-url/blob/main/LICENSE\nexport function fileURLToPath(url) {\n\tif (typeof url === \"string\") {\n\t\turl = new URL(url);\n\t}\n\n\tif (url.protocol !== \"file:\") {\n\t\tthrow new Error(\"The URL must use the file: protocol\");\n\t}\n\n\tif (url.hostname) {\n\t\tthrow new Error(\"The file: URL host must be 'localhost' or empty\");\n\t}\n\n\tif (/%2f/i.test(url.pathname)) {\n\t\tthrow new Error(\"The file: URL path must not include encoded / characters\");\n\t}\n\n\treturn path.normalize(decodeURIComponent(url.pathname));\n}\n"
  },
  {
    "path": "src/Adapters/Packages/url.js",
    "content": "export { fileURLToPath } from \"node:url\";\n"
  },
  {
    "path": "src/Adapters/getDefaultConfig.core.js",
    "content": "import defaultConfig from \"../defaultConfig.js\";\n\n// Standard and minimal bundle import directly for bundling\nexport default async function () {\n\treturn defaultConfig;\n}\n"
  },
  {
    "path": "src/Adapters/getDefaultConfig.js",
    "content": "// Standard\nexport default async function () {\n\treturn import(\"../defaultConfig.js\").then((mod) => mod.default);\n}\n"
  },
  {
    "path": "src/Benchmark/Benchmark.js",
    "content": "export default class Benchmark {\n\tconstructor() {\n\t\t// TypeScript slop\n\t\tthis.timeSpent = 0;\n\t\tthis.timesCalled = 0;\n\t\tthis.beforeTimers = [];\n\t}\n\n\treset() {\n\t\tthis.timeSpent = 0;\n\t\tthis.timesCalled = 0;\n\t\tthis.beforeTimers = [];\n\t}\n\n\tgetNewTimestamp() {\n\t\tif (performance) {\n\t\t\treturn performance.now();\n\t\t}\n\t\treturn new Date().getTime();\n\t}\n\n\tincrementCount() {\n\t\tthis.timesCalled++;\n\t}\n\n\t// TODO(slightlyoff):\n\t//    disable all of these hrtime requests when not benchmarking\n\tbefore() {\n\t\tthis.timesCalled++;\n\t\tthis.beforeTimers.push(this.getNewTimestamp());\n\t}\n\n\tafter() {\n\t\tif (!this.beforeTimers.length) {\n\t\t\tthrow new Error(\"You called Benchmark after() without a before().\");\n\t\t}\n\n\t\tlet before = this.beforeTimers.pop();\n\t\tif (!this.beforeTimers.length) {\n\t\t\tthis.timeSpent += this.getNewTimestamp() - before;\n\t\t}\n\t}\n\n\tgetTimesCalled() {\n\t\treturn this.timesCalled;\n\t}\n\n\tgetTotal() {\n\t\treturn this.timeSpent;\n\t}\n}\n"
  },
  {
    "path": "src/Benchmark/BenchmarkGroup.js",
    "content": "import debugUtil from \"debug\";\n\nimport ConsoleLogger from \"../Util/ConsoleLogger.js\";\nimport isAsyncFunction from \"../Util/IsAsyncFunction.js\";\nimport Benchmark from \"./Benchmark.js\";\n\nconst debugBenchmark = debugUtil(\"Eleventy:Benchmark\");\n\nclass BenchmarkGroup {\n\tconstructor() {\n\t\tthis.benchmarks = {};\n\t\t// Warning: aggregate benchmarks automatically default to false via BenchmarkManager->getBenchmarkGroup\n\t\tthis.isVerbose = true;\n\t\tthis.logger = new ConsoleLogger();\n\t\tthis.minimumThresholdMs = 50;\n\t\tthis.minimumThresholdPercent = 8;\n\t}\n\n\tsetIsVerbose(isVerbose) {\n\t\tthis.isVerbose = isVerbose;\n\t\tthis.logger.isVerbose = isVerbose;\n\t}\n\n\treset() {\n\t\tfor (var type in this.benchmarks) {\n\t\t\tthis.benchmarks[type].reset();\n\t\t}\n\t}\n\n\t// TODO use addAsync everywhere instead\n\tadd(type, callback) {\n\t\tlet benchmark = (this.benchmarks[type] = new Benchmark());\n\n\t\t/** @this {any} */\n\t\tlet fn = function (...args) {\n\t\t\tbenchmark.before();\n\t\t\tlet ret = callback.call(this, ...args);\n\t\t\tbenchmark.after();\n\t\t\treturn ret;\n\t\t};\n\n\t\tObject.defineProperty(fn, \"__eleventyInternal\", {\n\t\t\tvalue: {\n\t\t\t\ttype: isAsyncFunction(callback) ? \"async\" : \"sync\",\n\t\t\t\tcallback,\n\t\t\t},\n\t\t});\n\n\t\treturn fn;\n\t}\n\n\t// callback must return a promise\n\t// async addAsync(type, callback) {\n\t//   let benchmark = (this.benchmarks[type] = new Benchmark());\n\n\t//   benchmark.before();\n\t//   // don’t await here.\n\t//   let promise = callback.call(this);\n\t//   promise.then(function() {\n\t//     benchmark.after();\n\t//   });\n\t//   return promise;\n\t// }\n\n\tsetMinimumThresholdMs(minimumThresholdMs) {\n\t\tlet val = parseInt(minimumThresholdMs, 10);\n\t\tif (isNaN(val)) {\n\t\t\tthrow new Error(\"`setMinimumThresholdMs` expects a number argument.\");\n\t\t}\n\t\tthis.minimumThresholdMs = val;\n\t}\n\n\tsetMinimumThresholdPercent(minimumThresholdPercent) {\n\t\tlet val = parseInt(minimumThresholdPercent, 10);\n\t\tif (isNaN(val)) {\n\t\t\tthrow new Error(\"`setMinimumThresholdPercent` expects a number argument.\");\n\t\t}\n\t\tthis.minimumThresholdPercent = val;\n\t}\n\n\thas(type) {\n\t\treturn !!this.benchmarks[type];\n\t}\n\n\tget(type) {\n\t\tif (!this.benchmarks[type]) {\n\t\t\tthis.benchmarks[type] = new Benchmark();\n\t\t}\n\t\treturn this.benchmarks[type];\n\t}\n\n\tpadNumber(num, length) {\n\t\tif ((\"\" + num).length >= length) {\n\t\t\treturn num;\n\t\t}\n\n\t\tlet prefix = new Array(length + 1).join(\" \");\n\t\treturn (prefix + num).slice(-1 * length);\n\t}\n\n\tfinish(label, totalTimeSpent) {\n\t\tfor (var type in this.benchmarks) {\n\t\t\tlet bench = this.benchmarks[type];\n\t\t\tlet isAbsoluteMinimumComparison = this.minimumThresholdMs > 0;\n\t\t\tlet totalForBenchmark = bench.getTotal();\n\t\t\tlet percent = Math.round((totalForBenchmark * 100) / totalTimeSpent);\n\t\t\tlet callCount = bench.getTimesCalled();\n\n\t\t\tlet output = {\n\t\t\t\tms: this.padNumber(totalForBenchmark.toFixed(0), 6),\n\t\t\t\tpercent: this.padNumber(percent, 3),\n\t\t\t\tcalls: this.padNumber(callCount, 5),\n\t\t\t};\n\t\t\tlet str = `Benchmark ${output.ms}ms ${output.percent}% ${output.calls}× (${label}) ${type}`;\n\n\t\t\tif (\n\t\t\t\tisAbsoluteMinimumComparison &&\n\t\t\t\ttotalForBenchmark >= this.minimumThresholdMs &&\n\t\t\t\tpercent > this.minimumThresholdPercent\n\t\t\t) {\n\t\t\t\tthis.logger.warn(str);\n\t\t\t}\n\n\t\t\t// Opt out of logging if low count (1× or 2×) or 0ms / 1%\n\t\t\tif (\n\t\t\t\tcallCount > 1 || // called more than once\n\t\t\t\tMath.round(totalForBenchmark) > 0 // more than 0.5ms\n\t\t\t) {\n\t\t\t\tdebugBenchmark(str);\n\t\t\t}\n\t\t}\n\t}\n}\n\nexport default BenchmarkGroup;\n"
  },
  {
    "path": "src/Benchmark/BenchmarkManager.js",
    "content": "import BenchmarkGroup from \"./BenchmarkGroup.js\";\n\n// TODO this should not be a singleton, it belongs in the config or somewhere on the Eleventy instance.\n\nclass BenchmarkManager {\n\tconstructor() {\n\t\tthis.benchmarkGroups = {};\n\t\tthis.isVerbose = true;\n\t\tthis.start = this.getNewTimestamp();\n\t}\n\n\treset() {\n\t\tthis.start = this.getNewTimestamp();\n\n\t\tfor (var j in this.benchmarkGroups) {\n\t\t\tthis.benchmarkGroups[j].reset();\n\t\t}\n\t}\n\n\tgetNewTimestamp() {\n\t\tif (performance) {\n\t\t\treturn performance.now();\n\t\t}\n\t\treturn new Date().getTime();\n\t}\n\n\tsetVerboseOutput(isVerbose) {\n\t\tthis.isVerbose = !!isVerbose;\n\t}\n\n\thasBenchmarkGroup(name) {\n\t\treturn name in this.benchmarkGroups;\n\t}\n\n\tgetBenchmarkGroup(name) {\n\t\tif (!this.benchmarkGroups[name]) {\n\t\t\tthis.benchmarkGroups[name] = new BenchmarkGroup();\n\n\t\t\t// Special behavior for aggregate benchmarks\n\t\t\t// so they don’t console.log every time\n\t\t\tif (name === \"Aggregate\") {\n\t\t\t\tthis.benchmarkGroups[name].setIsVerbose(false);\n\t\t\t} else {\n\t\t\t\tthis.benchmarkGroups[name].setIsVerbose(this.isVerbose);\n\t\t\t}\n\t\t}\n\n\t\treturn this.benchmarkGroups[name];\n\t}\n\n\tgetAll() {\n\t\treturn this.benchmarkGroups;\n\t}\n\n\tget(name) {\n\t\tif (name) {\n\t\t\treturn this.getBenchmarkGroup(name);\n\t\t}\n\n\t\treturn this.getAll();\n\t}\n\n\tfinish() {\n\t\tlet totalTimeSpentBenchmarking = this.getNewTimestamp() - this.start;\n\t\tfor (var j in this.benchmarkGroups) {\n\t\t\tthis.benchmarkGroups[j].finish(j, totalTimeSpentBenchmarking);\n\t\t}\n\t}\n}\n\nexport default BenchmarkManager;\n"
  },
  {
    "path": "src/Core.js",
    "content": "import { MinimalCore } from \"./CoreMinimal.js\";\nimport FileSystemSearch from \"./FileSystemSearch.js\";\nimport EleventyFiles from \"./EleventyFiles.js\";\nimport TemplatePassthroughManager from \"./TemplatePassthroughManager.js\";\n\n// Core with File System support (but without Dev Server or Chokidar or Bundled Plugins)\nexport class Core extends MinimalCore {\n\tasync initializeConfig(initOverrides) {\n\t\tawait super.initializeConfig(initOverrides);\n\n\t\t/** @type {object} */\n\t\tthis.fileSystemSearch = new FileSystemSearch();\n\t}\n\n\tasync init(options = {}) {\n\t\tawait super.init(options);\n\n\t\tthis.templateData.setFileSystemSearch(this.fileSystemSearch);\n\n\t\tthis.passthroughManager = new TemplatePassthroughManager(this.eleventyConfig);\n\t\tthis.passthroughManager.setRunMode(this.runMode);\n\t\tthis.passthroughManager.setDryRun(this.isDryRun);\n\t\tthis.passthroughManager.extensionMap = this.extensionMap;\n\t\tthis.passthroughManager.setFileSystemSearch(this.fileSystemSearch);\n\n\t\tlet formats = this.templateFormats.getTemplateFormats();\n\t\tthis.eleventyFiles = new EleventyFiles(formats, this.eleventyConfig);\n\t\tthis.eleventyFiles.setPassthroughManager(this.passthroughManager);\n\t\tthis.eleventyFiles.setFileSystemSearch(this.fileSystemSearch);\n\t\tthis.eleventyFiles.setRunMode(this.runMode);\n\t\tthis.eleventyFiles.extensionMap = this.extensionMap;\n\t\t// This needs to be set before init or it’ll construct a new one\n\t\tthis.eleventyFiles.templateData = this.templateData;\n\t\tthis.eleventyFiles.init();\n\n\t\tthis.writer.setPassthroughManager(this.passthroughManager);\n\t\tthis.writer.setEleventyFiles(this.eleventyFiles);\n\t}\n\n\t/**\n\t * Restarts Eleventy.\n\t */\n\tasync restart() {\n\t\tawait super.restart();\n\n\t\t// TODO\n\t\tthis.passthroughManager.reset();\n\t\t// TODO\n\t\tthis.eleventyFiles.restart();\n\t}\n}\n"
  },
  {
    "path": "src/CoreMinimal.js",
    "content": "import debugUtil from \"debug\";\nimport { isPlainObject, TemplatePath } from \"@11ty/eleventy-utils\";\n\nimport chalk from \"./Adapters/Packages/chalk.js\";\n\nimport TemplateData from \"./Data/TemplateData.js\";\nimport TemplateWriter from \"./TemplateWriter.js\";\nimport EleventyExtensionMap from \"./EleventyExtensionMap.js\";\nimport { EleventyErrorHandler } from \"./Errors/EleventyErrorHandler.js\";\nimport TemplateConfig from \"./TemplateConfig.js\";\nimport TemplateEngineManager from \"./Engines/TemplateEngineManager.js\";\n\n/* Utils */\nimport { readableFileSize } from \"./Util/FileSize.js\";\nimport simplePlural from \"./Util/Pluralize.js\";\nimport ConsoleLogger from \"./Util/ConsoleLogger.js\";\nimport ProjectDirectories from \"./Util/ProjectDirectories.js\";\nimport {\n\tgetEleventyPackageJson,\n\timportJsonSync,\n\tgetWorkingProjectPackageJsonPath,\n} from \"./Util/ImportJsonSync.js\";\nimport ProjectTemplateFormats from \"./Util/ProjectTemplateFormats.js\";\n\nconst pkg = getEleventyPackageJson();\nconst debug = debugUtil(\"Eleventy\");\n\n/**\n * Eleventy’s programmatic API\n * @module 11ty/eleventy/Eleventy\n */\n\nexport class MinimalCore {\n\t/**\n\t * Userspace package.json file contents\n\t * @type {object|undefined}\n\t */\n\t#projectPackageJson;\n\t/** @type {string} */\n\t#projectPackageJsonPath;\n\t/** @type {ProjectTemplateFormats|undefined} */\n\t#templateFormats;\n\t/** @type {ConsoleLogger|undefined} */\n\t#logger;\n\t/** @type {ProjectDirectories|undefined} */\n\t#directories;\n\t/** @type {boolean|undefined} */\n\t#verboseOverride;\n\t/** @type {boolean} */\n\t#isVerboseMode = true;\n\t/** @type {boolean|undefined} */\n\t#preInitVerbose;\n\t/** @type {boolean} */\n\t#hasConfigInitialized = false;\n\t/** @type {boolean} */\n\t#needsInit = true;\n\t/** @type {Promise|undefined} */\n\t#initPromise;\n\t/** @type {EleventyErrorHandler|undefined} */\n\t#errorHandler;\n\t/** @type {Map} */\n\t#privateCaches = new Map();\n\t/** @type {boolean|undefined} */\n\t#isEsm;\n\t/** @type {string} */\n\t#activeConfigurationPath;\n\n\t// Support both new Eleventy(options) and new Eleventy(input, output, options)\n\t#normalizeConstructorArguments(...args) {\n\t\tlet input;\n\t\tlet output;\n\t\tlet options;\n\t\tlet eleventyConfig;\n\n\t\tif (isPlainObject(args[0])) {\n\t\t\toptions = args[0] || {};\n\t\t\tinput = options.input;\n\t\t\toutput = options.output;\n\t\t\televentyConfig = args[1];\n\t\t} else {\n\t\t\tinput = args[0];\n\t\t\toutput = args[1];\n\t\t\toptions = args[2] || {};\n\t\t\televentyConfig = args[3];\n\t\t}\n\n\t\treturn {\n\t\t\tinput,\n\t\t\toutput,\n\t\t\toptions,\n\t\t\televentyConfig,\n\t\t};\n\t}\n\n\t/**\n\t * @typedef {object} EleventyOptions\n\t * @property {'cli'|'script'=} source\n\t * @property {'build'|'serve'|'watch'=} runMode\n\t * @property {boolean=} dryRun\n\t * @property {string=} configPath\n\t * @property {string=} pathPrefix\n\t * @property {boolean=} quietMode\n\t * @property {Function=} config\n\t * @property {string=} inputDir\n\n\t * @param {string} [input] - Directory or filename for input/sources files.\n\t * @param {string} [output] - Directory serving as the target for writing the output files.\n\t * @param {EleventyOptions} [options={}]\n\t * @param {TemplateConfig} [eleventyConfig]\n\t */\n\tconstructor(...args) {\n\t\tlet {\n\t\t\tinput,\n\t\t\toutput,\n\t\t\toptions = {},\n\t\t\televentyConfig = null,\n\t\t} = this.#normalizeConstructorArguments(...args);\n\n\t\t/**\n\t\t * @type {string|undefined}\n\t\t * @description Holds the path to the input (might be a file or folder)\n\t\t */\n\t\tthis.rawInput = input || undefined;\n\n\t\t/**\n\t\t * @type {string|undefined}\n\t\t * @description holds the path to the output directory\n\t\t */\n\t\tthis.rawOutput = output || undefined;\n\n\t\t/**\n\t\t * @type {module:11ty/eleventy/TemplateConfig}\n\t\t * @description Override the config instance (for centralized config re-use)\n\t\t */\n\t\tthis.eleventyConfig = eleventyConfig;\n\n\t\t/**\n\t\t * @type {EleventyOptions}\n\t\t * @description Options object passed to the Eleventy constructor\n\t\t * @default {}\n\t\t */\n\t\tthis.options = options;\n\n\t\t/**\n\t\t * @type {'cli'|'script'}\n\t\t * @description Called via CLI (`cli`) or Programmatically (`script`)\n\t\t * @default \"script\"\n\t\t */\n\t\tthis.source = options.source || \"script\";\n\n\t\t/**\n\t\t * @type {string}\n\t\t * @description One of build, serve, or watch\n\t\t * @default \"build\"\n\t\t */\n\t\tthis.runMode = options.runMode || \"build\";\n\n\t\t/**\n\t\t * @type {boolean}\n\t\t * @description Is Eleventy running in dry mode?\n\t\t * @default false\n\t\t */\n\t\tthis.isDryRun = options.dryRun ?? false;\n\n\t\t/**\n\t\t * @type {boolean}\n\t\t * @description Is this an incremental build? (only operates on a subset of input files)\n\t\t * @default false\n\t\t */\n\t\tthis.isIncremental = false;\n\n\t\t/**\n\t\t * @type {string|undefined}\n\t\t * @description If an incremental build, this is the file we’re operating on.\n\t\t * @default null\n\t\t */\n\t\tthis.programmaticApiIncrementalFile = undefined;\n\n\t\t/**\n\t\t * @type {boolean}\n\t\t * @description Should we process files on first run? (The --ignore-initial feature)\n\t\t * @default true\n\t\t */\n\t\tthis.isRunInitialBuild = true;\n\n\t\t/**\n\t\t * @type {Number}\n\t\t * @description Number of builds run on this instance.\n\t\t * @default 0\n\t\t */\n\t\tthis.buildCount = 0;\n\n\t\t/**\n\t\t * @member {String} - Force ESM or CJS mode instead of detecting from package.json. Either cjs, esm, or auto.\n\t\t * @default \"auto\"\n\t\t */\n\t\tthis.loader = this.options.loader ?? \"auto\";\n\n\t\t/**\n\t\t * @type {Number}\n\t\t * @description The timestamp of Eleventy start.\n\t\t */\n\t\tthis.start = this.getNewTimestamp();\n\t}\n\n\t/**\n\t * @type {string|undefined}\n\t * @description An override of Eleventy's default config file paths\n\t * @default undefined\n\t */\n\tget configPath() {\n\t\treturn this.options.configPath;\n\t}\n\n\t/**\n\t * @type {string}\n\t * @description The top level directory the site pretends to reside in\n\t * @default \"/\"\n\t */\n\tget pathPrefix() {\n\t\treturn this.options.pathPrefix || \"/\";\n\t}\n\n\t/**\n\t * Reads the version of Eleventy.\n\t *\n\t * @static\n\t * @returns {string} - The version of Eleventy.\n\t */\n\tstatic getVersion() {\n\t\treturn pkg.version;\n\t}\n\n\t/**\n\t * @deprecated since 1.0.1, use static Eleventy.getVersion()\n\t */\n\tgetVersion() {\n\t\treturn MinimalCore.getVersion();\n\t}\n\n\tasync initializeConfig(initOverrides) {\n\t\tif (!this.eleventyConfig) {\n\t\t\tthis.eleventyConfig = new TemplateConfig(null, this.configPath);\n\t\t} else if (this.configPath) {\n\t\t\tawait this.eleventyConfig.setProjectConfigPath(this.configPath);\n\t\t}\n\n\t\tthis.#activeConfigurationPath =\n\t\t\tthis.configPath ?? this.eleventyConfig.getLocalProjectConfigFile();\n\n\t\tthis.eleventyConfig.setRunMode(this.runMode);\n\t\tthis.eleventyConfig.setProjectUsingEsm(this.isEsm);\n\t\tthis.eleventyConfig.setLogger(this.logger);\n\t\tthis.eleventyConfig.setDirectories(this.directories);\n\t\tthis.eleventyConfig.setTemplateFormats(this.templateFormats);\n\n\t\tif (this.pathPrefix || this.pathPrefix === \"\") {\n\t\t\tthis.eleventyConfig.setPathPrefix(this.pathPrefix);\n\t\t}\n\n\t\t// Debug mode should always run quiet (all output goes to debug logger)\n\t\tif (process.env.DEBUG) {\n\t\t\tthis.#verboseOverride = false;\n\t\t} else if (this.options.quietMode === true || this.options.quietMode === false) {\n\t\t\tthis.#verboseOverride = !this.options.quietMode;\n\t\t}\n\n\t\t// Moved before config merges: https://github.com/11ty/eleventy/issues/3316\n\t\tif (this.#verboseOverride === true || this.#verboseOverride === false) {\n\t\t\tthis.eleventyConfig.userConfig._setQuietModeOverride(!this.#verboseOverride);\n\t\t}\n\n\t\tthis.eleventyConfig.userConfig.directories = this.directories;\n\n\t\t/* Programmatic API config */\n\t\tif (this.options.config && typeof this.options.config === \"function\") {\n\t\t\tdebug(\"Running options.config configuration callback (passed to Eleventy constructor)\");\n\t\t\t// TODO use return object here?\n\t\t\tawait this.options.config(this.eleventyConfig.userConfig);\n\t\t}\n\n\t\t/**\n\t\t * @type {object}\n\t\t * @description Initialize Eleventy environment variables\n\t\t * @default null\n\t\t */\n\t\t// this.runMode need to be set before this\n\t\tthis.env = this.getEnvironmentVariableValues();\n\t\tthis.initializeEnvironmentVariables(this.env);\n\n\t\t// Async initialization of configuration\n\t\tawait this.eleventyConfig.init(initOverrides);\n\n\t\t/**\n\t\t * @type {object}\n\t\t * @description Initialize Eleventy’s configuration, including the user config file\n\t\t */\n\t\tthis.config = this.eleventyConfig.getConfig();\n\n\t\t/**\n\t\t * @type {object}\n\t\t * @description Singleton BenchmarkManager instance\n\t\t */\n\t\tthis.bench = this.config.benchmarkManager;\n\n\t\tif (performance) {\n\t\t\tdebug(\"Eleventy warm up time: %o (ms)\", performance.now());\n\t\t}\n\n\t\tthis.#hasConfigInitialized = true;\n\n\t\t// after #hasConfigInitialized above\n\t\tthis.setIsVerbose(this.#preInitVerbose ?? !this.config.quietMode);\n\t}\n\n\tgetNewTimestamp() {\n\t\tif (performance) {\n\t\t\treturn performance.now();\n\t\t}\n\t\treturn new Date().getTime();\n\t}\n\n\t/** @type {ProjectDirectories} */\n\tget directories() {\n\t\tif (!this.#directories) {\n\t\t\tthis.#directories = new ProjectDirectories();\n\t\t\tthis.#directories.setInput(this.rawInput, this.options.inputDir);\n\t\t\tthis.#directories.setOutput(this.rawOutput);\n\n\t\t\tif (this.source == \"cli\" && (this.rawInput !== undefined || this.rawOutput !== undefined)) {\n\t\t\t\tthis.#directories.freeze();\n\t\t\t}\n\t\t}\n\n\t\treturn this.#directories;\n\t}\n\n\t/** @type {string} */\n\tget input() {\n\t\treturn this.directories.inputFile || this.directories.input || this.config.dir.input;\n\t}\n\n\t/** @type {string} */\n\tget inputFile() {\n\t\treturn this.directories.inputFile;\n\t}\n\n\t/** @type {string} */\n\tget inputDir() {\n\t\treturn this.directories.input;\n\t}\n\n\t// Not used internally, removed in 3.0.\n\tsetInputDir() {\n\t\tthrow new Error(\n\t\t\t\"Eleventy->setInputDir was removed in 3.0. Use the inputDir option to the constructor\",\n\t\t);\n\t}\n\n\t/** @type {string} */\n\tget outputDir() {\n\t\treturn this.directories.output || this.config.dir.output;\n\t}\n\n\t/**\n\t * Updates the dry-run mode of Eleventy.\n\t *\n\t * @param {boolean} isDryRun - Shall Eleventy run in dry mode?\n\t */\n\tsetDryRun(isDryRun) {\n\t\tthis.isDryRun = !!isDryRun;\n\t}\n\n\t/**\n\t * Sets the incremental build mode.\n\t *\n\t * @param {boolean} isIncremental - Shall Eleventy run in incremental build mode and only write the files that trigger watch updates\n\t */\n\tsetIncrementalBuild(isIncremental) {\n\t\tthis.isIncremental = !!isIncremental;\n\n\t\tif (this.writer) {\n\t\t\tthis.writer.setIncrementalBuild(this.isIncremental);\n\t\t}\n\t}\n\n\t/**\n\t * Set whether or not to do an initial build\n\t *\n\t * @param {boolean} ignoreInitialBuild - Shall Eleventy ignore the default initial build before watching in watch/serve mode?\n\t * @default true\n\t */\n\tsetIgnoreInitial(ignoreInitialBuild) {\n\t\tthis.isRunInitialBuild = !ignoreInitialBuild;\n\n\t\tif (this.writer) {\n\t\t\tthis.writer.setRunInitialBuild(this.isRunInitialBuild);\n\t\t}\n\t}\n\n\t/**\n\t * Updates the path prefix used in the config.\n\t *\n\t * @param {string} pathPrefix - The new path prefix.\n\t */\n\tsetPathPrefix(pathPrefix) {\n\t\tif (pathPrefix || pathPrefix === \"\") {\n\t\t\tthis.eleventyConfig.setPathPrefix(pathPrefix);\n\t\t\t// TODO reset config\n\t\t\t// this.config = this.eleventyConfig.getConfig();\n\t\t}\n\t}\n\n\t/**\n\t * Restarts Eleventy.\n\t */\n\tasync restart() {\n\t\tdebug(\"Restarting.\");\n\t\tthis.start = this.getNewTimestamp();\n\n\t\tthis.extensionMap.reset();\n\t\tthis.bench.reset();\n\t}\n\n\t#cache(key, inst) {\n\t\tif (!(\"caches\" in inst)) {\n\t\t\tthrow new Error(\"To use #cache you need a `caches` getter object\");\n\t\t}\n\n\t\t// Restore from cache\n\t\tif (this.#privateCaches.has(key)) {\n\t\t\tlet c = this.#privateCaches.get(key);\n\t\t\tfor (let cacheKey in c) {\n\t\t\t\tinst[cacheKey] = c[cacheKey];\n\t\t\t}\n\t\t} else {\n\t\t\t// Set cache\n\t\t\tlet c = {};\n\t\t\tfor (let cacheKey of inst.caches || []) {\n\t\t\t\tc[cacheKey] = inst[cacheKey];\n\t\t\t}\n\t\t\tthis.#privateCaches.set(key, c);\n\t\t}\n\t}\n\n\t/**\n\t * Starts Eleventy.\n\t */\n\tasync init(options = {}) {\n\t\tlet { viaConfigReset } = Object.assign({ viaConfigReset: false }, options);\n\t\tif (!this.#hasConfigInitialized) {\n\t\t\tawait this.initializeConfig();\n\t\t} else {\n\t\t\t// Note: Global event bus is different from user config event bus\n\t\t\tthis.config.events.reset();\n\t\t}\n\n\t\tawait this.config.events.emit(\"eleventy.config\", this.eleventyConfig);\n\n\t\tif (this.env) {\n\t\t\tawait this.config.events.emit(\"eleventy.env\", this.env);\n\t\t}\n\n\t\tlet formats = this.templateFormats.getTemplateFormats();\n\t\tlet engineManager = new TemplateEngineManager(this.eleventyConfig);\n\t\tthis.extensionMap = new EleventyExtensionMap(this.eleventyConfig);\n\t\tthis.extensionMap.setFormats(formats);\n\t\tthis.extensionMap.engineManager = engineManager;\n\t\tawait this.config.events.emit(\"eleventy.extensionmap\", this.extensionMap);\n\n\t\tthis.templateData = new TemplateData(this.eleventyConfig);\n\t\tthis.templateData.setProjectUsingEsm(this.isEsm);\n\t\tthis.templateData.extensionMap = this.extensionMap;\n\t\tif (this.env) {\n\t\t\tthis.templateData.environmentVariables = this.env;\n\t\t}\n\n\t\t// Note these directories are all project root relative\n\t\tthis.config.events.emit(\"eleventy.directories\", this.directories.getUserspaceInstance());\n\n\t\tthis.writer = new TemplateWriter(formats, this.templateData, this.eleventyConfig);\n\n\t\tif (!viaConfigReset) {\n\t\t\t// set or restore cache\n\t\t\tthis.#cache(\"TemplateWriter\", this.writer);\n\t\t}\n\n\t\tthis.writer.logger = this.logger;\n\t\tthis.writer.extensionMap = this.extensionMap;\n\t\tthis.writer.setRunInitialBuild(this.isRunInitialBuild);\n\t\tthis.writer.setIncrementalBuild(this.isIncremental);\n\n\t\tlet debugStr = `Directories:\n  Input:\n    Directory: ${this.directories.input}\n    File: ${this.directories.inputFile || false}\n    Glob: ${this.directories.inputGlob || false}\n  Data: ${this.directories.data}\n  Includes: ${this.directories.includes}\n  Layouts: ${this.directories.layouts || false}\n  Output: ${this.directories.output}\nTemplate Formats: ${formats.join(\",\")}\nVerbose Output: ${this.verboseMode}`;\n\t\tdebug(debugStr);\n\n\t\tthis.writer.setVerboseOutput(this.verboseMode);\n\t\tthis.writer.setDryRun(this.isDryRun);\n\n\t\tthis.#needsInit = false;\n\t}\n\n\t// These are all set as initial global data under eleventy.env.* (see TemplateData->environmentVariables)\n\tgetEnvironmentVariableValues() {\n\t\tlet values = {\n\t\t\tsource: this.source,\n\t\t\trunMode: this.runMode,\n\t\t};\n\n\t\tif (this.#activeConfigurationPath) {\n\t\t\tvalues.config = TemplatePath.absolutePath(this.#activeConfigurationPath);\n\t\t}\n\n\t\t// Fixed: instead of configuration directory, explicit root or working directory\n\t\tvalues.root = TemplatePath.getWorkingDir();\n\n\t\tvalues.source = this.source;\n\n\t\t// Backwards compatibility\n\t\tObject.defineProperty(values, \"isServerless\", {\n\t\t\tenumerable: false,\n\t\t\tvalue: false,\n\t\t});\n\n\t\treturn values;\n\t}\n\n\t/**\n\t * Set process.ENV variables for use in Eleventy projects\n\t *\n\t * @method\n\t */\n\tinitializeEnvironmentVariables(env) {\n\t\t// Recognize that global data `eleventy.version` is coerced to remove prerelease tags\n\t\t// and this is the raw version (3.0.0 versus 3.0.0-alpha.6).\n\t\t// `eleventy.env.version` does not yet exist (unnecessary)\n\t\tprocess.env.ELEVENTY_VERSION = MinimalCore.getVersion();\n\n\t\tprocess.env.ELEVENTY_ROOT = env.root;\n\t\tdebug(\"Setting process.env.ELEVENTY_ROOT: %o\", env.root);\n\n\t\tprocess.env.ELEVENTY_SOURCE = env.source;\n\t\tprocess.env.ELEVENTY_RUN_MODE = env.runMode;\n\t}\n\n\t/** @param {boolean} value */\n\tset verboseMode(value) {\n\t\tthis.setIsVerbose(value);\n\t}\n\n\t/** @type {boolean} */\n\tget verboseMode() {\n\t\treturn this.#isVerboseMode;\n\t}\n\n\t/** @type {ConsoleLogger} */\n\tget logger() {\n\t\tif (!this.#logger) {\n\t\t\tthis.#logger = new ConsoleLogger();\n\t\t\tthis.#logger.isVerbose = this.verboseMode;\n\t\t}\n\n\t\treturn this.#logger;\n\t}\n\n\t/** @param {ConsoleLogger} logger */\n\tset logger(logger) {\n\t\tthis.eleventyConfig.setLogger(logger);\n\t\tthis.#logger = logger;\n\t}\n\n\tdisableLogger() {\n\t\tthis.logger.overrideLogger(false);\n\t}\n\n\t/** @type {EleventyErrorHandler} */\n\tget errorHandler() {\n\t\tif (!this.#errorHandler) {\n\t\t\tthis.#errorHandler = new EleventyErrorHandler();\n\t\t\tthis.#errorHandler.isVerbose = this.verboseMode;\n\t\t\tthis.#errorHandler.logger = this.logger;\n\t\t}\n\n\t\treturn this.#errorHandler;\n\t}\n\n\t/**\n\t * Updates the verbose mode of Eleventy.\n\t *\n\t * @method\n\t * @param {boolean} isVerbose - Shall Eleventy run in verbose mode?\n\t */\n\tsetIsVerbose(isVerbose) {\n\t\tif (!this.#hasConfigInitialized) {\n\t\t\tthis.#preInitVerbose = !!isVerbose;\n\t\t\treturn;\n\t\t}\n\n\t\t// always defer to --quiet if override happened\n\t\tisVerbose = this.#verboseOverride ?? !!isVerbose;\n\n\t\tthis.#isVerboseMode = isVerbose;\n\n\t\tif (this.logger) {\n\t\t\tthis.logger.isVerbose = isVerbose;\n\t\t}\n\n\t\tthis.bench.setVerboseOutput(isVerbose);\n\n\t\tif (this.writer) {\n\t\t\tthis.writer.setVerboseOutput(isVerbose);\n\t\t}\n\n\t\tif (this.errorHandler) {\n\t\t\tthis.errorHandler.isVerbose = isVerbose;\n\t\t}\n\n\t\t// Set verbose mode in config file\n\t\tthis.eleventyConfig.verbose = isVerbose;\n\t}\n\n\tget templateFormats() {\n\t\tif (!this.#templateFormats) {\n\t\t\tlet tf = new ProjectTemplateFormats();\n\t\t\tthis.#templateFormats = tf;\n\t\t}\n\n\t\treturn this.#templateFormats;\n\t}\n\n\t/**\n\t * Updates the template formats of Eleventy.\n\t *\n\t * @method\n\t * @param {string} formats - The new template formats.\n\t */\n\tsetFormats(formats) {\n\t\tthis.templateFormats.setViaCommandLine(formats);\n\t}\n\n\t/**\n\t * Updates the run mode of Eleventy.\n\t *\n\t * @method\n\t * @param {string} runMode - One of \"build\", \"watch\", or \"serve\"\n\t */\n\tsetRunMode(runMode) {\n\t\tthis.runMode = runMode;\n\t}\n\n\t/**\n\t * Set the file that needs to be rendered/compiled/written for an incremental build.\n\t * This method is also wired up to the CLI --incremental=incrementalFile\n\t *\n\t * @method\n\t * @param {string} incrementalFile - File path (added or modified in a project)\n\t */\n\tsetIncrementalFile(incrementalFile) {\n\t\tif (incrementalFile) {\n\t\t\t// This used to also setIgnoreInitial(true) but was changed in 3.0.0-alpha.14\n\t\t\tthis.setIncrementalBuild(true);\n\n\t\t\tthis.programmaticApiIncrementalFile = TemplatePath.addLeadingDotSlash(incrementalFile);\n\n\t\t\t// Used to determind template relevance for compile cache keys\n\t\t\tthis.eleventyConfig.setPreviousBuildModifiedFile(incrementalFile);\n\t\t}\n\t}\n\n\tunsetIncrementalFile() {\n\t\t// only applies to initial build, no re-runs (--watch/--serve)\n\t\tif (this.programmaticApiIncrementalFile) {\n\t\t\t// this.setIgnoreInitial(false);\n\t\t\tthis.programmaticApiIncrementalFile = undefined;\n\t\t}\n\n\t\t// reset back to false\n\t\tthis.setIgnoreInitial(false);\n\t}\n\n\t/**\n\t * Resets the config of Eleventy.\n\t *\n\t * @method\n\t */\n\tasync resetConfig() {\n\t\tdelete this.eleventyConfig;\n\n\t\t// ensures `initializeConfig()` will run when `init()` is called next\n\t\tthis.#hasConfigInitialized = false;\n\t}\n\n\t// fetch from project’s package.json\n\tget projectPackageJsonPath() {\n\t\tif (this.#projectPackageJsonPath === undefined) {\n\t\t\tthis.#projectPackageJsonPath = getWorkingProjectPackageJsonPath() || false;\n\t\t}\n\t\treturn this.#projectPackageJsonPath;\n\t}\n\n\tget projectPackageJson() {\n\t\tif (!this.#projectPackageJson) {\n\t\t\tlet p = this.projectPackageJsonPath;\n\t\t\tthis.#projectPackageJson = p ? importJsonSync(p) : {};\n\t\t}\n\t\treturn this.#projectPackageJson;\n\t}\n\n\tget isEsm() {\n\t\tif (this.#isEsm !== undefined) {\n\t\t\treturn this.#isEsm;\n\t\t}\n\n\t\tif (this.loader == \"esm\") {\n\t\t\tthis.#isEsm = true;\n\t\t} else if (this.loader == \"cjs\") {\n\t\t\tthis.#isEsm = false;\n\t\t} else if (this.loader == \"auto\") {\n\t\t\t// Note: Node defaults to CommonJS if missing, Deno defaults to ESM\n\t\t\t// https://docs.deno.com/runtime/fundamentals/node/#commonjs-support\n\t\t\tif (typeof Deno !== \"undefined\") {\n\t\t\t\tthis.#isEsm = this.projectPackageJson?.type !== \"commonjs\";\n\t\t\t} else {\n\t\t\t\tthis.#isEsm = this.projectPackageJson?.type === \"module\";\n\t\t\t}\n\t\t} else {\n\t\t\tthrow new Error(\"The 'loader' option must be one of 'esm', 'cjs', or 'auto'\");\n\t\t}\n\t\treturn this.#isEsm;\n\t}\n\n\t/**\n\t * Writes templates to the file system.\n\t *\n\t * @async\n\t * @method\n\t * @param {String} subtype - (optional) or \"templates\" (skips passthrough copy) or \"copy\" (skips templates)\n\t * @returns {Promise<{Array}>}\n\t */\n\tasync write(subtype) {\n\t\tif (subtype) {\n\t\t\tif (subtype !== \"fs\" && !subtype?.startsWith(\"fs:\")) {\n\t\t\t\tsubtype = `fs:${subtype}`;\n\t\t\t}\n\t\t\treturn this.executeBuild(subtype);\n\t\t}\n\n\t\treturn this.executeBuild(\"fs\");\n\t}\n\n\t/**\n\t * Renders templates to a JSON object.\n\t *\n\t * @async\n\t * @method\n\t * @returns {Promise<{Array}>}\n\t */\n\tasync toJSON() {\n\t\treturn this.executeBuild(\"json\");\n\t}\n\n\ttoNDJSON() {\n\t\tthrow new Error(\"Feature removed in Eleventy v4: https://github.com/11ty/eleventy/issues/3382\");\n\t}\n\n\t/**\n\t * tbd.\n\t *\n\t * @async\n\t * @method\n\t * @returns {Promise<{Array}>} ret - tbd.\n\t */\n\tasync executeBuild(to = \"fs\") {\n\t\tif (this.#needsInit) {\n\t\t\tif (!this.#initPromise) {\n\t\t\t\tthis.#initPromise = this.init();\n\t\t\t}\n\t\t\tawait this.#initPromise.then(() => {\n\t\t\t\t// #needsInit also set to false at the end of `init()`\n\t\t\t\tthis.#needsInit = false;\n\t\t\t\tthis.#initPromise = undefined;\n\t\t\t});\n\t\t}\n\n\t\tif (!this.writer) {\n\t\t\tthrow new Error(\n\t\t\t\t\"Internal error: Eleventy didn’t run init() properly and wasn’t able to create a TemplateWriter.\",\n\t\t\t);\n\t\t}\n\n\t\tlet incrementalFile =\n\t\t\tthis.programmaticApiIncrementalFile || this.watchQueue?.getIncrementalFile();\n\t\tif (incrementalFile) {\n\t\t\tthis.writer.setIncrementalFile(incrementalFile);\n\t\t}\n\n\t\tlet returnObj;\n\t\tlet hasError = false;\n\t\tlet outputMode = String(to);\n\t\t// normalize fs:templates or fs:copy to `fs`\n\t\tif (outputMode.includes(\":\")) {\n\t\t\toutputMode = outputMode.split(\":\").shift();\n\t\t}\n\n\t\ttry {\n\t\t\tlet directories = this.directories.getUserspaceInstance();\n\t\t\tlet eventsArg = {\n\t\t\t\tdirectories,\n\n\t\t\t\t// v3.0.0-alpha.6, changed to use `directories` instead (this was only used by serverless plugin)\n\t\t\t\tinputDir: directories.input,\n\n\t\t\t\t// Deprecated (not normalized) use `directories` instead.\n\t\t\t\tdir: this.config.dir,\n\n\t\t\t\trunMode: this.runMode,\n\t\t\t\toutputMode,\n\t\t\t\tincremental: this.isIncremental,\n\t\t\t};\n\n\t\t\tawait this.config.events.emit(\"beforeBuild\", eventsArg);\n\t\t\tawait this.config.events.emit(\"eleventy.before\", eventsArg);\n\n\t\t\tlet promise;\n\t\t\tif (to === \"fs\") {\n\t\t\t\tpromise = this.writer.write();\n\t\t\t} else if (to === \"fs:templates\") {\n\t\t\t\tpromise = this.writer.writeTemplates();\n\t\t\t} else if (to === \"json\") {\n\t\t\t\tpromise = this.writer.getJSON(\"json\");\n\t\t\t} else {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Invalid argument for \\`Eleventy->executeBuild(${to})\\`, expected \"json\", \"fs\", or \"fs:templates\".`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tlet resolved = await promise;\n\n\t\t\t// Passing the processed output to the eleventy.after event (2.0+)\n\t\t\teventsArg.results = resolved.templates;\n\n\t\t\tif (to === \"json\" || to === \"fs:templates\") {\n\t\t\t\t// Backwards compat\n\t\t\t\treturnObj = resolved.templates;\n\t\t\t} else {\n\t\t\t\t// Backwards compat\n\t\t\t\treturnObj = [resolved.passthroughCopy, resolved.templates];\n\t\t\t}\n\n\t\t\tthis.unsetIncrementalFile();\n\t\t\tthis.writer.resetIncrementalFile();\n\n\t\t\teventsArg.uses = this.eleventyConfig.usesGraph.map;\n\t\t\tawait this.config.events.emit(\"afterBuild\", eventsArg);\n\t\t\tawait this.config.events.emit(\"eleventy.after\", eventsArg);\n\n\t\t\tthis.buildCount++;\n\t\t} catch (error) {\n\t\t\thasError = true;\n\n\t\t\t// Issue #2405: Don’t change the exitCode for programmatic scripts\n\t\t\tlet errorSeverity = this.source === \"script\" ? \"error\" : \"fatal\";\n\t\t\tthis.errorHandler.once(errorSeverity, error, \"Problem writing Eleventy templates\");\n\n\t\t\tthrow error;\n\t\t} finally {\n\t\t\tthis.bench.finish();\n\n\t\t\tif (outputMode === \"fs\") {\n\t\t\t\tthis.logger.logWithOptions({\n\t\t\t\t\tmessage: this.logFinished(),\n\t\t\t\t\tcolor: hasError ? \"red\" : \"green\",\n\t\t\t\t\tforce: true,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tdebug(\"Finished.\");\n\n\t\t\tdebug(`\nHave a suggestion/feature request/feedback? Feeling frustrated? I want to hear it!\nOpen an issue: https://github.com/11ty/eleventy/issues/new`);\n\t\t}\n\n\t\treturn returnObj;\n\t}\n\n\t/**\n\t * Logs some statistics after a complete run of Eleventy.\n\t *\n\t * @returns {string} ret - The log message.\n\t */\n\tlogFinished() {\n\t\tif (!this.writer) {\n\t\t\tthrow new Error(\n\t\t\t\t\"Did you call Eleventy.init to create the TemplateWriter instance? Hint: you probably didn’t.\",\n\t\t\t);\n\t\t}\n\n\t\tlet ret = [];\n\n\t\tlet {\n\t\t\tcopyCount,\n\t\t\tcopySize,\n\t\t\tskipCount,\n\t\t\twriteCount,\n\t\t\t// renderCount, // files that render (costly) but may not write to disk\n\t\t} = this.writer.getMetadata();\n\n\t\tlet slashRet = [];\n\n\t\tif (copyCount) {\n\t\t\tdebug(\"Total passthrough copy aggregate size: %o\", readableFileSize(copySize));\n\t\t\tslashRet.push(`Copied ${chalk.bold(copyCount)}`);\n\t\t}\n\n\t\tslashRet.push(\n\t\t\t`Wrote ${chalk.bold(writeCount)} ${simplePlural(writeCount, \"file\", \"files\")}${\n\t\t\t\tskipCount ? ` (skipped ${skipCount})` : \"\"\n\t\t\t}`,\n\t\t);\n\n\t\t// slashRet.push(\n\t\t// \t`${renderCount} rendered`\n\t\t// )\n\n\t\tif (slashRet.length) {\n\t\t\tret.push(slashRet.join(\" \"));\n\t\t}\n\n\t\tlet time = (this.getNewTimestamp() - this.start) / 1000;\n\t\tret.push(\n\t\t\t`in ${chalk.bold(time.toFixed(2))} ${simplePlural(time.toFixed(2), \"second\", \"seconds\")}`,\n\t\t);\n\n\t\tlet cfgStr = this.#activeConfigurationPath\n\t\t\t? `, ${TemplatePath.stripLeadingDotSlash(this.#activeConfigurationPath)}`\n\t\t\t: \" no config file\";\n\t\t// More than 1 second total, show estimate of per-template time\n\t\tif (time >= 1 && writeCount > 1) {\n\t\t\tret.push(\n\t\t\t\tchalk.gray(`(${((time * 1000) / writeCount).toFixed(1)}ms each, v${pkg.version}${cfgStr})`),\n\t\t\t);\n\t\t} else {\n\t\t\tret.push(chalk.gray(`(v${MinimalCore.getVersion()}${cfgStr})`));\n\t\t}\n\n\t\treturn ret.join(\" \");\n\t}\n}\n"
  },
  {
    "path": "src/Data/ComputedData.js",
    "content": "import lodash from \"@11ty/lodash-custom\";\nimport debugUtil from \"debug\";\n\nimport ComputedDataQueue from \"./ComputedDataQueue.js\";\nimport ComputedDataTemplateString from \"./ComputedDataTemplateString.js\";\nimport ComputedDataProxy from \"./ComputedDataProxy.js\";\n\nconst { set: lodashSet, get: lodashGet } = lodash;\nconst debug = debugUtil(\"Eleventy:ComputedData\");\n\nclass ComputedData {\n\tconstructor(config) {\n\t\tthis.computed = {};\n\t\tthis.symbolParseFunctions = {};\n\t\tthis.templateStringKeyLookup = {};\n\t\tthis.computedKeys = new Set();\n\t\tthis.declaredDependencies = {};\n\t\tthis.queue = new ComputedDataQueue();\n\t\tthis.config = config;\n\t}\n\n\tadd(key, renderFn, declaredDependencies = [], symbolParseFn, templateInstance) {\n\t\tthis.computedKeys.add(key);\n\t\tthis.declaredDependencies[key] = declaredDependencies;\n\n\t\t// bind config filters/JS functions\n\t\tif (typeof renderFn === \"function\") {\n\t\t\tlet fns = {};\n\t\t\t// TODO bug? no access to non-universal config things?\n\t\t\tif (this.config) {\n\t\t\t\tfns = {\n\t\t\t\t\t...this.config.javascriptFunctions,\n\t\t\t\t};\n\t\t\t}\n\t\t\tfns.tmpl = templateInstance;\n\n\t\t\trenderFn = renderFn.bind(fns);\n\t\t}\n\n\t\tlodashSet(this.computed, key, renderFn);\n\n\t\tif (symbolParseFn) {\n\t\t\tlodashSet(this.symbolParseFunctions, key, symbolParseFn);\n\t\t}\n\t}\n\n\taddTemplateString(key, renderFn, declaredDependencies = [], symbolParseFn, templateInstance) {\n\t\tthis.add(key, renderFn, declaredDependencies, symbolParseFn, templateInstance);\n\t\tthis.templateStringKeyLookup[key] = true;\n\t}\n\n\tasync resolveVarOrder(data) {\n\t\tlet proxyByTemplateString = new ComputedDataTemplateString(this.computedKeys);\n\t\tlet proxyByProxy = new ComputedDataProxy(this.computedKeys);\n\n\t\tfor (let key of this.computedKeys) {\n\t\t\tlet computed = lodashGet(this.computed, key);\n\n\t\t\tif (typeof computed !== \"function\") {\n\t\t\t\t// add nodes for non functions (primitives like booleans, etc)\n\t\t\t\t// This will not handle template strings, as they are normalized to functions\n\t\t\t\tthis.queue.addNode(key);\n\t\t\t} else {\n\t\t\t\tthis.queue.uses(key, this.declaredDependencies[key]);\n\n\t\t\t\tlet symbolParseFn = lodashGet(this.symbolParseFunctions, key);\n\t\t\t\tlet varsUsed = [];\n\t\t\t\tif (symbolParseFn) {\n\t\t\t\t\t// use the parseForSymbols function in the TemplateEngine\n\t\t\t\t\tvarsUsed = symbolParseFn();\n\t\t\t\t} else if (symbolParseFn !== false) {\n\t\t\t\t\t// skip resolution is this is false (just use declaredDependencies)\n\t\t\t\t\tlet isTemplateString = !!this.templateStringKeyLookup[key];\n\t\t\t\t\tlet proxy = isTemplateString ? proxyByTemplateString : proxyByProxy;\n\t\t\t\t\tvarsUsed = await proxy.findVarsUsed(computed, data);\n\t\t\t\t}\n\n\t\t\t\tdebug(\"%o accesses %o variables\", key, varsUsed);\n\t\t\t\tlet filteredVarsUsed = varsUsed.filter((varUsed) => {\n\t\t\t\t\treturn (\n\t\t\t\t\t\t(varUsed !== key && this.computedKeys.has(varUsed)) ||\n\t\t\t\t\t\tvarUsed.startsWith(\"collections.\")\n\t\t\t\t\t);\n\t\t\t\t});\n\t\t\t\tthis.queue.uses(key, filteredVarsUsed);\n\t\t\t}\n\t\t}\n\t}\n\n\tasync _setupDataEntry(data, order) {\n\t\tdebug(\"Computed data order of execution: %o\", order);\n\t\tfor (let key of order) {\n\t\t\tlet computed = lodashGet(this.computed, key);\n\n\t\t\tif (typeof computed === \"function\") {\n\t\t\t\tlet ret = await computed(data);\n\t\t\t\tlodashSet(data, key, ret);\n\t\t\t} else if (computed !== undefined) {\n\t\t\t\tlodashSet(data, key, computed);\n\t\t\t}\n\t\t}\n\t}\n\n\tasync setupData(data, orderFilter) {\n\t\tawait this.resolveVarOrder(data);\n\n\t\tawait this.processRemainingData(data, orderFilter);\n\t}\n\n\tasync processRemainingData(data, orderFilter) {\n\t\t// process all variables\n\t\tlet order = this.queue.getOrder();\n\t\tif (orderFilter && typeof orderFilter === \"function\") {\n\t\t\torder = order.filter(orderFilter.bind(this.queue));\n\t\t}\n\n\t\tawait this._setupDataEntry(data, order);\n\t\tthis.queue.markComputed(order);\n\t}\n}\n\nexport default ComputedData;\n"
  },
  {
    "path": "src/Data/ComputedDataProxy.js",
    "content": "import lodash from \"@11ty/lodash-custom\";\nimport { isPlainObject } from \"@11ty/eleventy-utils\";\n\nconst { set: lodashSet, get: lodashGet } = lodash;\n\n/* Calculates computed data using Proxies */\nclass ComputedDataProxy {\n\tconstructor(computedKeys) {\n\t\tif (Array.isArray(computedKeys)) {\n\t\t\tthis.computedKeys = new Set(computedKeys);\n\t\t} else {\n\t\t\tthis.computedKeys = computedKeys;\n\t\t}\n\t}\n\n\tisArrayOrPlainObject(data) {\n\t\treturn Array.isArray(data) || isPlainObject(data);\n\t}\n\n\tgetProxyData(data, keyRef) {\n\t\t// WARNING: SIDE EFFECTS\n\t\t// Set defaults for keys not already set on parent data\n\n\t\t// TODO should make another effort to get rid of this,\n\t\t// See the ProxyWrap util for more proxy handlers that will likely fix this\n\t\tlet undefinedValue = \"__11TY_UNDEFINED__\";\n\t\tif (this.computedKeys) {\n\t\t\tfor (let key of this.computedKeys) {\n\t\t\t\tif (lodashGet(data, key, undefinedValue) === undefinedValue) {\n\t\t\t\t\tlodashSet(data, key, \"\");\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tlet proxyData = this._getProxyData(data, keyRef);\n\t\treturn proxyData;\n\t}\n\n\t_getProxyForObject(dataObj, keyRef, parentKey = \"\") {\n\t\treturn new Proxy(\n\t\t\t{},\n\t\t\t{\n\t\t\t\tget: (obj, key) => {\n\t\t\t\t\tif (typeof key !== \"string\") {\n\t\t\t\t\t\treturn obj[key];\n\t\t\t\t\t}\n\n\t\t\t\t\tlet newKey = `${parentKey ? `${parentKey}.` : \"\"}${key}`;\n\n\t\t\t\t\t// Issue #1137\n\t\t\t\t\t// Special case for Collections, always return an Array for collection keys\n\t\t\t\t\t// so they it works fine with Array methods like `filter`, `map`, etc\n\t\t\t\t\tif (newKey === \"collections\") {\n\t\t\t\t\t\tkeyRef.add(newKey);\n\t\t\t\t\t\treturn new Proxy(\n\t\t\t\t\t\t\t{},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tget: (target, key) => {\n\t\t\t\t\t\t\t\t\tif (typeof key === \"string\") {\n\t\t\t\t\t\t\t\t\t\tkeyRef.add(`collections.${key}`);\n\t\t\t\t\t\t\t\t\t\treturn [];\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\treturn target[key];\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tlet newData = this._getProxyData(dataObj[key], keyRef, newKey);\n\t\t\t\t\tif (!this.isArrayOrPlainObject(newData)) {\n\t\t\t\t\t\tkeyRef.add(newKey);\n\t\t\t\t\t}\n\t\t\t\t\treturn newData;\n\t\t\t\t},\n\t\t\t},\n\t\t);\n\t}\n\n\t_getProxyForArray(dataArr, keyRef, parentKey = \"\") {\n\t\treturn new Proxy(new Array(dataArr.length), {\n\t\t\tget: (obj, key) => {\n\t\t\t\tif (Array.prototype.hasOwnProperty(key)) {\n\t\t\t\t\t// remove `filter`, `constructor`, `map`, etc\n\t\t\t\t\tkeyRef.add(parentKey);\n\t\t\t\t\treturn obj[key];\n\t\t\t\t}\n\n\t\t\t\t// Hm, this needs to be better\n\t\t\t\tif (key === \"then\") {\n\t\t\t\t\tkeyRef.add(parentKey);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tlet newKey = `${parentKey}[${key}]`;\n\t\t\t\tlet newData = this._getProxyData(dataArr[key], keyRef, newKey);\n\t\t\t\tif (!this.isArrayOrPlainObject(newData)) {\n\t\t\t\t\tkeyRef.add(newKey);\n\t\t\t\t}\n\t\t\t\treturn newData;\n\t\t\t},\n\t\t});\n\t}\n\n\t_getProxyData(data, keyRef, parentKey = \"\") {\n\t\tif (isPlainObject(data)) {\n\t\t\treturn this._getProxyForObject(data, keyRef, parentKey);\n\t\t} else if (Array.isArray(data)) {\n\t\t\treturn this._getProxyForArray(data, keyRef, parentKey);\n\t\t}\n\n\t\t// everything else!\n\t\treturn data;\n\t}\n\n\tasync findVarsUsed(fn, data = {}) {\n\t\tlet keyRef = new Set();\n\n\t\t// careful, logging proxyData will mess with test results!\n\t\tlet proxyData = this.getProxyData(data, keyRef);\n\n\t\t// squelch console logs for this fake proxy data pass 😅\n\t\t// let savedLog = console.log;\n\t\t// console.log = () => {};\n\t\tawait fn(proxyData);\n\t\t// console.log = savedLog;\n\n\t\treturn Array.from(keyRef);\n\t}\n}\n\nexport default ComputedDataProxy;\n"
  },
  {
    "path": "src/Data/ComputedDataQueue.js",
    "content": "import { DepGraph as DependencyGraph } from \"dependency-graph\";\n\n/* Keeps track of the dependency graph between computed data variables\n * Removes keys from the graph when they are computed.\n */\nclass ComputedDataQueue {\n\tconstructor() {\n\t\tthis.graph = new DependencyGraph();\n\t}\n\n\tgetOrder() {\n\t\treturn this.graph.overallOrder();\n\t}\n\n\tgetOrderFor(name) {\n\t\treturn this.graph.dependenciesOf(name);\n\t}\n\n\tgetDependsOn(name) {\n\t\treturn this.graph.dependantsOf(name);\n\t}\n\n\tisUsesStartsWith(name, prefix) {\n\t\tif (name.startsWith(prefix)) {\n\t\t\treturn true;\n\t\t}\n\t\treturn (\n\t\t\tthis.graph.dependenciesOf(name).filter((entry) => {\n\t\t\t\treturn entry.startsWith(prefix);\n\t\t\t}).length > 0\n\t\t);\n\t}\n\n\taddNode(name) {\n\t\tif (!this.graph.hasNode(name)) {\n\t\t\tthis.graph.addNode(name);\n\t\t}\n\t}\n\n\t_uses(graph, name, varsUsed = []) {\n\t\tif (!graph.hasNode(name)) {\n\t\t\tgraph.addNode(name);\n\t\t}\n\n\t\tfor (let varUsed of varsUsed) {\n\t\t\tif (!graph.hasNode(varUsed)) {\n\t\t\t\tgraph.addNode(varUsed);\n\t\t\t}\n\t\t\tgraph.addDependency(name, varUsed);\n\t\t}\n\t}\n\n\tuses(name, varsUsed = []) {\n\t\tthis._uses(this.graph, name, varsUsed);\n\t}\n\n\tmarkComputed(varsComputed = []) {\n\t\tfor (let varComputed of varsComputed) {\n\t\t\tthis.graph.removeNode(varComputed);\n\t\t}\n\t}\n}\n\nexport default ComputedDataQueue;\n"
  },
  {
    "path": "src/Data/ComputedDataTemplateString.js",
    "content": "import lodash from \"@11ty/lodash-custom\";\nimport debugUtil from \"debug\";\n\nconst { set: lodashSet } = lodash;\nconst debug = debugUtil(\"Eleventy:ComputedDataTemplateString\");\n\n/* Calculates computed data in Template Strings.\n * Ideally we would use the Proxy approach but it doesn’t work\n * in some template languages that visit all available data even if\n * it isn’t used in the template (Nunjucks)\n */\nclass ComputedDataTemplateString {\n\tconstructor(computedKeys) {\n\t\tif (Array.isArray(computedKeys)) {\n\t\t\tthis.computedKeys = new Set(computedKeys);\n\t\t} else {\n\t\t\tthis.computedKeys = computedKeys;\n\t\t}\n\n\t\t// is this ¯\\_(lisp)_/¯\n\t\t// must be strings that won’t be escaped by template languages\n\t\tthis.prefix = \"(((11ty(((\";\n\t\tthis.suffix = \")))11ty)))\";\n\t}\n\n\tgetProxyData() {\n\t\tlet proxyData = {};\n\n\t\t// use these special strings as a workaround to check the rendered output\n\t\t// can’t use proxies here as some template languages trigger proxy for all\n\t\t// keys in data\n\t\tfor (let key of this.computedKeys) {\n\t\t\t// TODO don’t allow to set eleventyComputed.page? other disallowed computed things?\n\t\t\tlodashSet(proxyData, key, this.prefix + key + this.suffix);\n\t\t}\n\n\t\treturn proxyData;\n\t}\n\n\tfindVarsInOutput(output = \"\") {\n\t\tlet vars = new Set();\n\t\tlet splits = output.split(this.prefix);\n\t\tfor (let split of splits) {\n\t\t\tlet varName = split.slice(0, split.indexOf(this.suffix) < 0 ? 0 : split.indexOf(this.suffix));\n\t\t\tif (varName) {\n\t\t\t\tvars.add(varName);\n\t\t\t}\n\t\t}\n\t\treturn Array.from(vars);\n\t}\n\n\tasync findVarsUsed(fn) {\n\t\tlet proxyData = this.getProxyData();\n\t\tlet output;\n\t\t// Mitigation for #1061, errors with filters in the first pass shouldn’t fail the whole thing.\n\t\ttry {\n\t\t\toutput = await fn(proxyData);\n\t\t} catch (e) {\n\t\t\tdebug(\"Computed Data first pass data resolution error: %o\", e);\n\t\t}\n\n\t\t// page.outputPath on serverless urls returns false.\n\t\tif (typeof output === \"string\") {\n\t\t\treturn this.findVarsInOutput(output);\n\t\t}\n\t\treturn [];\n\t}\n}\n\nexport default ComputedDataTemplateString;\n"
  },
  {
    "path": "src/Data/TemplateData.js",
    "content": "import path from \"node:path\";\nimport lodash from \"@11ty/lodash-custom\";\nimport { Merge, TemplatePath, isPlainObject } from \"@11ty/eleventy-utils\";\nimport debugUtil from \"debug\";\n\nimport { inspect } from \"../Adapters/Packages/inspect.js\";\nimport unique from \"../Util/Objects/Unique.js\";\nimport TemplateGlob from \"../TemplateGlob.js\";\nimport EleventyBaseError from \"../Errors/EleventyBaseError.js\";\nimport TemplateDataInitialGlobalData from \"./TemplateDataInitialGlobalData.js\";\nimport { getEleventyPackageJson, getWorkingProjectPackageJson } from \"../Util/ImportJsonSync.js\";\nimport { EleventyImport, EleventyLoadContent } from \"../Util/Require.js\";\nimport { DeepFreeze } from \"../Util/Objects/DeepFreeze.js\";\nimport { coerce } from \"../Util/SemverCoerce.js\";\nimport ReservedData from \"../Util/ReservedData.js\";\nimport { isTypeScriptSupported } from \"../Util/FeatureTests.cjs\";\n\nconst { set: lodashSet, get: lodashGet } = lodash;\n\nconst debugWarn = debugUtil(\"Eleventy:Warnings\");\nconst debug = debugUtil(\"Eleventy:TemplateData\");\nconst debugDev = debugUtil(\"Dev:Eleventy:TemplateData\");\n\nclass TemplateDataParseError extends EleventyBaseError {}\n\nclass TemplateData {\n\tconstructor(templateConfig) {\n\t\tif (!templateConfig || templateConfig.constructor.name !== \"TemplateConfig\") {\n\t\t\tthrow new Error(\n\t\t\t\t\"Internal error: Missing `templateConfig` or was not an instance of `TemplateConfig`.\",\n\t\t\t);\n\t\t}\n\n\t\tthis.templateConfig = templateConfig;\n\t\tthis.config = this.templateConfig.getConfig();\n\n\t\tthis.benchmarks = {\n\t\t\tdata: this.config.benchmarkManager.get(\"Data\"),\n\t\t\taggregate: this.config.benchmarkManager.get(\"Aggregate\"),\n\t\t};\n\n\t\tthis.rawImports = {};\n\t\tthis.globalData = null;\n\t\tthis.templateDirectoryData = {};\n\t\tthis.isEsm = false;\n\n\t\tthis.initialGlobalData = new TemplateDataInitialGlobalData(this.templateConfig);\n\t}\n\n\tget dirs() {\n\t\treturn this.templateConfig.directories;\n\t}\n\n\tget inputDir() {\n\t\treturn this.dirs.input;\n\t}\n\n\t// if this was set but `falsy` we would fallback to inputDir\n\tget dataDir() {\n\t\treturn this.dirs.data;\n\t}\n\n\tget absoluteDataDir() {\n\t\treturn TemplatePath.absolutePath(this.dataDir);\n\t}\n\n\t// This was async in 2.0 and prior but doesn’t need to be any more.\n\tgetInputDir() {\n\t\treturn this.dirs.input;\n\t}\n\n\tgetDataDir() {\n\t\treturn this.dataDir;\n\t}\n\n\texists(pathname) {\n\t\t// It's common for data files not to exist, so we avoid going to the FS to\n\t\t// re-check if they do via a quick-and-dirty cache.\n\t\treturn this.templateConfig.existsCache.exists(pathname);\n\t}\n\n\tsetFileSystemSearch(fileSystemSearch) {\n\t\tthis.fileSystemSearch = fileSystemSearch;\n\t}\n\n\tsetProjectUsingEsm(isEsmProject) {\n\t\tthis.isEsm = !!isEsmProject;\n\t}\n\n\tget extensionMap() {\n\t\tif (!this._extensionMap) {\n\t\t\tthrow new Error(\"Internal error: missing `extensionMap` in TemplateData.\");\n\t\t}\n\t\treturn this._extensionMap;\n\t}\n\n\tset extensionMap(map) {\n\t\tthis._extensionMap = map;\n\t}\n\n\tget environmentVariables() {\n\t\treturn this._env;\n\t}\n\n\tset environmentVariables(env) {\n\t\tthis._env = env;\n\t}\n\n\t/* Used by tests */\n\t_setConfig(config) {\n\t\tthis.config = config;\n\t}\n\n\tgetRawImports() {\n\t\tif (!this.config.keys.package) {\n\t\t\tdebug(\n\t\t\t\t\"Opted-out of package.json assignment for global data with falsy value for `keys.package` configuration.\",\n\t\t\t);\n\t\t\treturn this.rawImports;\n\t\t} else if (Object.keys(this.rawImports).length > 0) {\n\t\t\treturn this.rawImports;\n\t\t}\n\n\t\tlet pkgJson = getWorkingProjectPackageJson();\n\t\tthis.rawImports[this.config.keys.package] = pkgJson;\n\n\t\tif (this.config.freezeReservedData) {\n\t\t\tDeepFreeze(this.rawImports);\n\t\t}\n\n\t\treturn this.rawImports;\n\t}\n\n\tclearData() {\n\t\tthis.globalData = null;\n\t\tthis.configApiGlobalData = null;\n\t\tthis.templateDirectoryData = {};\n\t}\n\n\t_getGlobalDataGlobByExtension(extension) {\n\t\treturn TemplateGlob.normalizePath(this.dataDir, `/**/*.${extension}`);\n\t}\n\n\t// This is a backwards compatibility helper with the old `jsDataFileSuffix` configuration API\n\tgetDataFileSuffixes() {\n\t\t// New API\n\t\tif (Array.isArray(this.config.dataFileSuffixes)) {\n\t\t\treturn this.config.dataFileSuffixes;\n\t\t}\n\n\t\t// Backwards compatibility\n\t\tif (this.config.jsDataFileSuffix) {\n\t\t\tlet suffixes = [];\n\t\t\tsuffixes.push(this.config.jsDataFileSuffix); // e.g. filename.11tydata.json\n\t\t\tsuffixes.push(\"\"); // suffix-less for free with old API, e.g. filename.json\n\t\t\treturn suffixes;\n\t\t}\n\t\treturn []; // if both of these entries are set to false, use no files\n\t}\n\n\t// This is used exclusively for --watch and --serve chokidar targets\n\tasync getTemplateDataFileGlob() {\n\t\tlet suffixes = this.getDataFileSuffixes();\n\t\tlet globSuffixesWithLeadingDot = new Set();\n\t\tglobSuffixesWithLeadingDot.add(\"json\"); // covers .11tydata.json too\n\t\tlet globSuffixesWithoutLeadingDot = new Set();\n\n\t\t// Typically using [ '.11tydata', '' ] suffixes to find data files\n\t\tfor (let suffix of suffixes) {\n\t\t\t// TODO the `suffix` truthiness check is purely for backwards compat?\n\t\t\tif (suffix && typeof suffix === \"string\") {\n\t\t\t\tif (suffix.startsWith(\".\")) {\n\t\t\t\t\t// .suffix.js\n\t\t\t\t\tglobSuffixesWithLeadingDot.add(`${suffix.slice(1)}.mjs`);\n\t\t\t\t\tglobSuffixesWithLeadingDot.add(`${suffix.slice(1)}.cjs`);\n\t\t\t\t\tglobSuffixesWithLeadingDot.add(`${suffix.slice(1)}.js`);\n\n\t\t\t\t\tif (isTypeScriptSupported()) {\n\t\t\t\t\t\tglobSuffixesWithLeadingDot.add(`${suffix.slice(1)}.mts`);\n\t\t\t\t\t\tglobSuffixesWithLeadingDot.add(`${suffix.slice(1)}.cts`);\n\t\t\t\t\t\tglobSuffixesWithLeadingDot.add(`${suffix.slice(1)}.ts`);\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t// \"suffix.js\" without leading dot\n\t\t\t\t\tglobSuffixesWithoutLeadingDot.add(`${suffix || \"\"}.mjs`);\n\t\t\t\t\tglobSuffixesWithoutLeadingDot.add(`${suffix || \"\"}.cjs`);\n\t\t\t\t\tglobSuffixesWithoutLeadingDot.add(`${suffix || \"\"}.js`);\n\n\t\t\t\t\tif (isTypeScriptSupported()) {\n\t\t\t\t\t\tglobSuffixesWithoutLeadingDot.add(`${suffix || \"\"}.mts`);\n\t\t\t\t\t\tglobSuffixesWithoutLeadingDot.add(`${suffix || \"\"}.cts`);\n\t\t\t\t\t\tglobSuffixesWithoutLeadingDot.add(`${suffix || \"\"}.ts`);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Configuration Data Extensions e.g. yaml\n\t\tif (this.hasUserDataExtensions()) {\n\t\t\tfor (let extension of this.getUserDataExtensions()) {\n\t\t\t\tglobSuffixesWithLeadingDot.add(extension); // covers .11tydata.{extension} too\n\t\t\t}\n\t\t}\n\n\t\tlet paths = [];\n\t\tif (globSuffixesWithLeadingDot.size > 0) {\n\t\t\tpaths.push(`${this.inputDir}**/*.{${Array.from(globSuffixesWithLeadingDot).join(\",\")}}`);\n\t\t}\n\t\tif (globSuffixesWithoutLeadingDot.size > 0) {\n\t\t\tpaths.push(`${this.inputDir}**/*{${Array.from(globSuffixesWithoutLeadingDot).join(\",\")}}`);\n\t\t}\n\n\t\treturn TemplatePath.addLeadingDotSlashArray(paths);\n\t}\n\n\t// For spidering dependencies\n\t// TODO Can we reuse getTemplateDataFileGlob instead? Maybe just filter off the .json files before scanning for dependencies\n\tgetTemplateJavaScriptDataFileGlob() {\n\t\tlet paths = [];\n\t\tlet suffixes = this.getDataFileSuffixes();\n\t\tfor (let suffix of suffixes) {\n\t\t\tif (suffix) {\n\t\t\t\t// TODO this check is purely for backwards compat and I kinda feel like it shouldn’t be here\n\t\t\t\t// paths.push(`${this.inputDir}/**/*${suffix || \"\"}.cjs`); // Same as above\n\t\t\t\tpaths.push(`${this.inputDir}**/*${suffix || \"\"}.js`); // TODO typescript?\n\t\t\t}\n\t\t}\n\n\t\treturn TemplatePath.addLeadingDotSlashArray(paths);\n\t}\n\n\tgetGlobalDataGlob() {\n\t\tlet extGlob = this.getGlobalDataExtensionPriorities().join(\",\");\n\t\treturn [this._getGlobalDataGlobByExtension(\"{\" + extGlob + \"}\")];\n\t}\n\n\tgetWatchPathCache() {\n\t\treturn this.pathCache;\n\t}\n\n\tgetGlobalDataExtensionPriorities() {\n\t\treturn this.getUserDataExtensions().concat([\"json\", \"mjs\", \"cjs\", \"js\"]);\n\t}\n\n\tstatic calculateExtensionPriority(path, priorities) {\n\t\tfor (let i = 0; i < priorities.length; i++) {\n\t\t\tlet ext = priorities[i];\n\t\t\tif (path.endsWith(ext)) {\n\t\t\t\treturn i;\n\t\t\t}\n\t\t}\n\t\treturn priorities.length;\n\t}\n\n\tasync getGlobalDataFiles() {\n\t\tlet priorities = this.getGlobalDataExtensionPriorities();\n\n\t\tlet fsBench = this.benchmarks.aggregate.get(\"Searching the file system (data)\");\n\t\tfsBench.before();\n\t\tlet globs = this.getGlobalDataGlob();\n\t\tlet paths = [];\n\t\tif (this.fileSystemSearch) {\n\t\t\tpaths = await this.fileSystemSearch.search(\"global-data\", globs);\n\t\t}\n\t\tfsBench.after();\n\n\t\t// sort paths according to extension priorities\n\t\t// here we use reverse ordering, because paths with bigger index in array will override the first ones\n\t\t// example [path/file.json, path/file.js] here js will override json\n\t\tpaths = paths.sort((first, second) => {\n\t\t\tlet p1 = TemplateData.calculateExtensionPriority(first, priorities);\n\t\t\tlet p2 = TemplateData.calculateExtensionPriority(second, priorities);\n\t\t\tif (p1 < p2) {\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t\tif (p1 > p2) {\n\t\t\t\treturn 1;\n\t\t\t}\n\t\t\treturn 0;\n\t\t});\n\n\t\tthis.pathCache = paths;\n\t\treturn paths;\n\t}\n\n\tgetObjectPathForDataFile(dataFilePath) {\n\t\tlet absoluteDataFilePath = TemplatePath.absolutePath(dataFilePath);\n\t\tlet reducedPath = TemplatePath.stripLeadingSubPath(absoluteDataFilePath, this.absoluteDataDir);\n\t\tlet parsed = path.parse(reducedPath);\n\t\tlet folders = parsed.dir ? parsed.dir.split(\"/\") : [];\n\t\tfolders.push(parsed.name);\n\n\t\treturn folders;\n\t}\n\n\tasync getAllGlobalData() {\n\t\tlet globalData = {};\n\t\tlet files = TemplatePath.addLeadingDotSlashArray(await this.getGlobalDataFiles());\n\n\t\tthis.config.events.emit(\"eleventy.globalDataFiles\", files);\n\n\t\tlet dataFileConflicts = {};\n\n\t\tfor (let j = 0, k = files.length; j < k; j++) {\n\t\t\tlet data = await this.getDataValue(files[j]);\n\t\t\tlet objectPathTarget = this.getObjectPathForDataFile(files[j]);\n\n\t\t\t// Since we're joining directory paths and an array is not usable as an objectkey since two identical arrays are not double equal,\n\t\t\t// we can just join the array by a forbidden character (\"/\"\" is chosen here, since it works on Linux, Mac and Windows).\n\t\t\t// If at some point this isn't enough anymore, it would be possible to just use JSON.stringify(objectPathTarget) since that\n\t\t\t// is guaranteed to work but is signifivcantly slower.\n\t\t\tlet objectPathTargetString = objectPathTarget.join(path.sep);\n\n\t\t\t// if two global files have the same path (but different extensions)\n\t\t\t// and conflict, let’s merge them.\n\t\t\tif (dataFileConflicts[objectPathTargetString]) {\n\t\t\t\tdebugWarn(\n\t\t\t\t\t`merging global data from ${files[j]} with an already existing global data file (${dataFileConflicts[objectPathTargetString]}). Overriding existing keys.`,\n\t\t\t\t);\n\n\t\t\t\tlet oldData = lodashGet(globalData, objectPathTarget);\n\t\t\t\tdata = Merge(oldData, data);\n\t\t\t}\n\n\t\t\tdataFileConflicts[objectPathTargetString] = files[j];\n\t\t\tdebug(`Found global data file ${files[j]} and adding as: ${objectPathTarget}`);\n\t\t\tlodashSet(globalData, objectPathTarget, data);\n\n\t\t\tif (this.config.freezeReservedData) {\n\t\t\t\tReservedData.check(globalData, files[j]);\n\t\t\t}\n\t\t}\n\n\t\treturn globalData;\n\t}\n\n\tasync #getInitialGlobalData() {\n\t\tlet globalData = await this.initialGlobalData.getData();\n\n\t\tif (!(\"eleventy\" in globalData)) {\n\t\t\tglobalData.eleventy = {};\n\t\t}\n\n\t\t// #2293 for meta[name=generator]\n\t\tconst pkg = getEleventyPackageJson();\n\t\tglobalData.eleventy.version = coerce(pkg.version).toString();\n\t\tglobalData.eleventy.generator = `Eleventy v${globalData.eleventy.version}`;\n\n\t\tif (this.environmentVariables) {\n\t\t\tif (!(\"env\" in globalData.eleventy)) {\n\t\t\t\tglobalData.eleventy.env = {};\n\t\t\t}\n\n\t\t\tObject.assign(globalData.eleventy.env, this.environmentVariables);\n\t\t}\n\n\t\tif (this.dirs) {\n\t\t\tif (!(\"directories\" in globalData.eleventy)) {\n\t\t\t\tglobalData.eleventy.directories = {};\n\t\t\t}\n\n\t\t\tObject.assign(globalData.eleventy.directories, this.dirs.getUserspaceInstance());\n\t\t}\n\n\t\t// Reserved\n\t\tif (this.config.freezeReservedData) {\n\t\t\tDeepFreeze(globalData.eleventy);\n\t\t}\n\n\t\treturn globalData;\n\t}\n\n\tasync getInitialGlobalData() {\n\t\tif (!this.configApiGlobalData) {\n\t\t\tthis.configApiGlobalData = this.#getInitialGlobalData();\n\t\t}\n\n\t\treturn this.configApiGlobalData;\n\t}\n\n\tasync #getGlobalData() {\n\t\tlet rawImports = this.getRawImports();\n\t\tlet configApiGlobalData = await this.getInitialGlobalData();\n\n\t\tlet globalJson = await this.getAllGlobalData();\n\t\tlet mergedGlobalData = Merge(globalJson, configApiGlobalData);\n\n\t\t// OK: Shallow merge when combining rawImports (pkg) with global data files\n\t\treturn Object.assign({}, mergedGlobalData, rawImports);\n\t}\n\n\tasync getGlobalData() {\n\t\tif (!this.globalData) {\n\t\t\tthis.globalData = this.#getGlobalData();\n\t\t}\n\n\t\treturn this.globalData;\n\t}\n\n\t/* Template and Directory data files */\n\tasync combineLocalData(localDataPaths) {\n\t\tlet localData = {};\n\t\tif (!Array.isArray(localDataPaths)) {\n\t\t\tlocalDataPaths = [localDataPaths];\n\t\t}\n\n\t\t// Filter out files we know don't exist to avoid overhead for checking\n\t\tlocalDataPaths = localDataPaths.filter((path) => {\n\t\t\treturn this.exists(path);\n\t\t});\n\n\t\tthis.config.events.emit(\"eleventy.dataFiles\", localDataPaths);\n\n\t\tif (!localDataPaths.length) {\n\t\t\treturn localData;\n\t\t}\n\n\t\tlet dataSource = {};\n\t\tfor (let path of localDataPaths) {\n\t\t\tlet dataForPath = await this.getDataValue(path);\n\t\t\tif (!isPlainObject(dataForPath)) {\n\t\t\t\tdebug(\n\t\t\t\t\t\"Warning: Template and Directory data files expect an object to be returned, instead `%o` returned `%o`\",\n\t\t\t\t\tpath,\n\t\t\t\t\tdataForPath,\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\t// clean up data for template/directory data files only.\n\t\t\t\tlet cleanedDataForPath = TemplateData.cleanupData(dataForPath, {\n\t\t\t\t\tfile: path,\n\t\t\t\t});\n\t\t\t\tfor (let key in cleanedDataForPath) {\n\t\t\t\t\tif (Object.prototype.hasOwnProperty.call(dataSource, key)) {\n\t\t\t\t\t\tdebugWarn(\n\t\t\t\t\t\t\t\"Local data files have conflicting data. Overwriting '%s' with data from '%s'. Previous data location was from '%s'\",\n\t\t\t\t\t\t\tkey,\n\t\t\t\t\t\t\tpath,\n\t\t\t\t\t\t\tdataSource[key],\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tdataSource[key] = path;\n\t\t\t\t}\n\t\t\t\tMerge(localData, cleanedDataForPath);\n\t\t\t}\n\t\t}\n\t\treturn localData;\n\t}\n\n\tasync getTemplateDirectoryData(templatePath) {\n\t\tif (!this.templateDirectoryData[templatePath]) {\n\t\t\tlet localDataPaths = await this.getLocalDataPaths(templatePath);\n\t\t\tlet importedData = await this.combineLocalData(localDataPaths);\n\n\t\t\tthis.templateDirectoryData[templatePath] = importedData;\n\t\t}\n\t\treturn this.templateDirectoryData[templatePath];\n\t}\n\n\tgetUserDataExtensions() {\n\t\tif (!this.config.dataExtensions) {\n\t\t\treturn [];\n\t\t}\n\n\t\t// returning extensions in reverse order to create proper extension order\n\t\t// later added formats will override first ones\n\t\treturn Array.from(this.config.dataExtensions.keys()).reverse();\n\t}\n\n\tgetUserDataParser(extension) {\n\t\treturn this.config.dataExtensions.get(extension);\n\t}\n\n\tisUserDataExtension(extension) {\n\t\treturn this.config.dataExtensions && this.config.dataExtensions.has(extension);\n\t}\n\n\thasUserDataExtensions() {\n\t\treturn this.config.dataExtensions && this.config.dataExtensions.size > 0;\n\t}\n\n\tasync _parseDataFile(path, parser, options = {}) {\n\t\tlet readFile = !(\"read\" in options) || options.read === true;\n\t\tlet rawInput;\n\n\t\tif (readFile) {\n\t\t\trawInput = EleventyLoadContent(path, options);\n\t\t}\n\n\t\tif (readFile && !rawInput) {\n\t\t\treturn {};\n\t\t}\n\n\t\ttry {\n\t\t\tif (readFile) {\n\t\t\t\treturn parser(rawInput, path);\n\t\t\t} else {\n\t\t\t\t// path as a first argument is when `read: false`\n\t\t\t\t// path as a second argument is for consistency with `read: true` API\n\t\t\t\treturn parser(path, path);\n\t\t\t}\n\t\t} catch (e) {\n\t\t\tthrow new TemplateDataParseError(`Having trouble parsing data file ${path}`, e);\n\t\t}\n\t}\n\n\t// ignoreProcessing = false for global data files\n\t// ignoreProcessing = true for local data files\n\tasync getDataValue(path) {\n\t\tlet extension = TemplatePath.getExtension(path);\n\n\t\tif (extension === \"js\" || extension === \"cjs\" || extension === \"mjs\") {\n\t\t\t// JS data file or require’d JSON (no preprocessing needed)\n\t\t\tif (!this.exists(path)) {\n\t\t\t\treturn {};\n\t\t\t}\n\n\t\t\tlet aggregateDataBench = this.benchmarks.aggregate.get(\"Data File\");\n\t\t\taggregateDataBench.before();\n\t\t\tlet dataBench = this.benchmarks.data.get(`\\`${path}\\``);\n\t\t\tdataBench.before();\n\n\t\t\tlet type = \"cjs\";\n\t\t\tif (extension === \"mjs\" || (extension === \"js\" && this.isEsm)) {\n\t\t\t\ttype = \"esm\";\n\t\t\t}\n\n\t\t\t// We always need to use `import()`, as `require` isn’t available in ESM.\n\t\t\tlet returnValue = await EleventyImport(path, type);\n\n\t\t\t// TODO special exception for Global data `permalink.js`\n\t\t\t// module.exports = (data) => `${data.page.filePathStem}/`; // Does not work\n\t\t\t// module.exports = () => ((data) => `${data.page.filePathStem}/`); // Works\n\t\t\tif (typeof returnValue === \"function\") {\n\t\t\t\tlet configApiGlobalData = await this.getInitialGlobalData();\n\t\t\t\treturnValue = await returnValue(configApiGlobalData || {});\n\t\t\t}\n\n\t\t\tdataBench.after();\n\t\t\taggregateDataBench.after();\n\n\t\t\treturn returnValue;\n\t\t} else if (this.isUserDataExtension(extension)) {\n\t\t\t// Other extensions\n\t\t\tlet { parser, options } = this.getUserDataParser(extension);\n\n\t\t\treturn this._parseDataFile(path, parser, options);\n\t\t} else if (extension === \"json\") {\n\t\t\t// File to string, parse with JSON (preprocess)\n\t\t\tconst parser = (content) => JSON.parse(content);\n\t\t\treturn this._parseDataFile(path, parser);\n\t\t} else {\n\t\t\tthrow new TemplateDataParseError(\n\t\t\t\t`Could not find an appropriate data parser for ${path}. Do you need to add a plugin to your config file?`,\n\t\t\t);\n\t\t}\n\t}\n\n\t_pushExtensionsToPaths(paths, curpath, extensions) {\n\t\tfor (let extension of extensions) {\n\t\t\tpaths.push(curpath + \".\" + extension);\n\t\t}\n\t}\n\n\t_addBaseToPaths(paths, base, extensions, nonEmptySuffixesOnly = false) {\n\t\tlet suffixes = this.getDataFileSuffixes();\n\n\t\tfor (let suffix of suffixes) {\n\t\t\tsuffix = suffix || \"\";\n\n\t\t\tif (nonEmptySuffixesOnly && suffix === \"\") {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// data suffix\n\t\t\tif (suffix) {\n\t\t\t\tpaths.push(base + suffix + \".js\");\n\t\t\t\tpaths.push(base + suffix + \".cjs\");\n\t\t\t\tpaths.push(base + suffix + \".mjs\");\n\t\t\t}\n\t\t\tpaths.push(base + suffix + \".json\"); // default: .11tydata.json\n\n\t\t\t// inject user extensions\n\t\t\tthis._pushExtensionsToPaths(paths, base + suffix, extensions);\n\t\t}\n\t}\n\n\tasync getLocalDataPaths(templatePath) {\n\t\tlet paths = [];\n\t\tlet parsed = path.parse(templatePath);\n\t\tlet inputDir = this.inputDir;\n\n\t\tdebugDev(\"getLocalDataPaths(%o)\", templatePath);\n\t\tdebugDev(\"parsed.dir: %o\", parsed.dir);\n\n\t\tlet userExtensions = this.getUserDataExtensions();\n\n\t\tif (parsed.dir) {\n\t\t\tlet fileNameNoExt = this.extensionMap.removeTemplateExtension(parsed.base);\n\n\t\t\t// default dataSuffix: .11tydata, is appended in _addBaseToPaths\n\t\t\tdebug(\"Using %o suffixes to find data files.\", this.getDataFileSuffixes());\n\n\t\t\t// Template data file paths\n\t\t\tlet filePathNoExt = parsed.dir + \"/\" + fileNameNoExt;\n\t\t\tthis._addBaseToPaths(paths, filePathNoExt, userExtensions);\n\n\t\t\t// Directory data file paths\n\t\t\tlet allDirs = TemplatePath.getAllDirs(parsed.dir);\n\n\t\t\tdebugDev(\"allDirs: %o\", allDirs);\n\t\t\tfor (let dir of allDirs) {\n\t\t\t\tlet lastDir = TemplatePath.getLastPathSegment(dir);\n\t\t\t\tlet dirPathNoExt = dir + \"/\" + lastDir;\n\n\t\t\t\tif (inputDir) {\n\t\t\t\t\tdebugDev(\"dirStr: %o; inputDir: %o\", dir, inputDir);\n\t\t\t\t}\n\t\t\t\t// TODO use DirContains\n\t\t\t\tif (!inputDir || (dir.startsWith(inputDir) && dir !== inputDir)) {\n\t\t\t\t\tif (this.config.dataFileDirBaseNameOverride) {\n\t\t\t\t\t\tlet indexDataFile = dir + \"/\" + this.config.dataFileDirBaseNameOverride;\n\t\t\t\t\t\tthis._addBaseToPaths(paths, indexDataFile, userExtensions, true);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis._addBaseToPaths(paths, dirPathNoExt, userExtensions);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// 0.11.0+ include root input dir files\n\t\t\t// if using `docs/` as input dir, looks for docs/docs.json et al\n\t\t\tif (inputDir) {\n\t\t\t\tlet lastInputDir = TemplatePath.addLeadingDotSlash(\n\t\t\t\t\tTemplatePath.join(inputDir, TemplatePath.getLastPathSegment(inputDir)),\n\t\t\t\t);\n\n\t\t\t\t// in root input dir, search for index.11tydata.json et al\n\t\t\t\tif (this.config.dataFileDirBaseNameOverride) {\n\t\t\t\t\tlet indexDataFile =\n\t\t\t\t\t\tTemplatePath.getDirFromFilePath(lastInputDir) +\n\t\t\t\t\t\t\"/\" +\n\t\t\t\t\t\tthis.config.dataFileDirBaseNameOverride;\n\t\t\t\t\tthis._addBaseToPaths(paths, indexDataFile, userExtensions, true);\n\t\t\t\t} else if (lastInputDir !== \"./\") {\n\t\t\t\t\tthis._addBaseToPaths(paths, lastInputDir, userExtensions);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tdebug(\"getLocalDataPaths(%o): %o\", templatePath, paths);\n\t\treturn unique(paths).reverse();\n\t}\n\n\t/* Like cleanupData() but does not mutate */\n\tstatic getCleanedTagsImmutable(data, options = {}) {\n\t\tlet tags = [];\n\n\t\tif (isPlainObject(data) && data.tags) {\n\t\t\tif (typeof data.tags === \"string\") {\n\t\t\t\ttags = (data.tags || \"\").split(\",\");\n\t\t\t} else if (Array.isArray(data.tags)) {\n\t\t\t\ttags = data.tags;\n\t\t\t} else if (data.tags) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`String or Array expected for \\`tags\\`${options.file ? ` in ${options.isVirtualTemplate ? \"virtual \" : \"\"}template: ${options.file}` : \"\"}. Received: ${inspect(data.tags)}`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// Deduplicate tags\n\t\t\t// Coerce to string #3875\n\t\t\treturn [...new Set(tags)].map((entry) => String(entry));\n\t\t}\n\n\t\treturn tags;\n\t}\n\n\tstatic cleanupData(data, options = {}) {\n\t\tif (isPlainObject(data) && \"tags\" in data) {\n\t\t\tdata.tags = this.getCleanedTagsImmutable(data, options);\n\t\t}\n\n\t\treturn data;\n\t}\n\n\tstatic getNormalizedExcludedCollections(data) {\n\t\tlet excludes = [];\n\t\tlet key = \"eleventyExcludeFromCollections\";\n\n\t\tif (data?.[key] !== true) {\n\t\t\tif (Array.isArray(data[key])) {\n\t\t\t\texcludes = data[key];\n\t\t\t} else if (typeof data[key] === \"string\") {\n\t\t\t\texcludes = (data[key] || \"\").split(\",\");\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\texcludes,\n\t\t\texcludeAll: data?.eleventyExcludeFromCollections === true,\n\t\t};\n\t}\n\n\tstatic getIncludedCollectionNames(data) {\n\t\tlet tags = TemplateData.getCleanedTagsImmutable(data);\n\n\t\tlet { excludes, excludeAll } = TemplateData.getNormalizedExcludedCollections(data);\n\t\tif (excludeAll) {\n\t\t\treturn [];\n\t\t}\n\n\t\treturn [\"all\", ...tags].filter((tag) => !excludes.includes(tag));\n\t}\n\n\tstatic getIncludedTagNames(data) {\n\t\treturn this.getIncludedCollectionNames(data).filter((tagName) => tagName !== \"all\");\n\t}\n}\n\nexport default TemplateData;\n"
  },
  {
    "path": "src/Data/TemplateDataInitialGlobalData.js",
    "content": "import lodash from \"@11ty/lodash-custom\";\n\nimport ReservedData from \"../Util/ReservedData.js\";\nimport EleventyBaseError from \"../Errors/EleventyBaseError.js\";\n\nconst { set: lodashSet } = lodash;\n\nclass TemplateDataConfigError extends EleventyBaseError {}\n\nclass TemplateDataInitialGlobalData {\n\tconstructor(templateConfig) {\n\t\tif (!templateConfig || templateConfig.constructor.name !== \"TemplateConfig\") {\n\t\t\tthrow new TemplateDataConfigError(\"Missing or invalid `templateConfig` (via Render plugin).\");\n\t\t}\n\t\tthis.templateConfig = templateConfig;\n\t\tthis.config = this.templateConfig.getConfig();\n\t}\n\n\tasync getData() {\n\t\tlet globalData = {};\n\n\t\t// via eleventyConfig.addGlobalData\n\t\tif (this.config.globalData) {\n\t\t\tlet keys = Object.keys(this.config.globalData);\n\t\t\tfor (let key of keys) {\n\t\t\t\tlet returnValue = this.config.globalData[key];\n\n\t\t\t\t// This section is problematic when used with eleventyComputed #3389\n\t\t\t\tif (typeof returnValue === \"function\") {\n\t\t\t\t\treturnValue = await returnValue();\n\t\t\t\t}\n\n\t\t\t\tlodashSet(globalData, key, returnValue);\n\t\t\t}\n\t\t}\n\n\t\tif (this.config.freezeReservedData) {\n\t\t\t// TODO-ish might come from the `config` callback too\n\t\t\tReservedData.check(globalData, this.templateConfig.getActiveConfigPath());\n\t\t}\n\n\t\treturn globalData;\n\t}\n}\n\nexport default TemplateDataInitialGlobalData;\n"
  },
  {
    "path": "src/Eleventy.js",
    "content": "import { relative } from \"node:path\";\nimport debugUtil from \"debug\";\n\nimport { TemplatePath } from \"@11ty/eleventy-utils\";\n\nimport { Core } from \"./Core.js\";\nimport EleventyServe from \"./EleventyServe.js\";\nimport { Watch } from \"./Watch.js\";\nimport WatchQueue from \"./WatchQueue.js\";\nimport WatchTargets from \"./WatchTargets.js\";\nimport EleventyBaseError from \"./Errors/EleventyBaseError.js\";\n\n// Utils\nimport checkPassthroughCopyBehavior from \"./Util/PassthroughCopyBehaviorCheck.js\";\nimport PathPrefixer from \"./Util/PathPrefixer.js\";\nimport PathNormalizer from \"./Util/PathNormalizer.js\";\nimport { isGlobMatch } from \"./Util/GlobMatcher.js\";\nimport eventBus from \"./EventBus.js\";\nimport { withResolvers } from \"./Util/PromiseUtil.js\";\n\nconst debug = debugUtil(\"Eleventy\");\n\nexport default class Eleventy extends Core {\n\t/** @type {boolean} */\n\t#isStopping = false;\n\n\t/** @type {WatchQueue} */\n\t#watchQueue;\n\n\t// constructor(input, output, options = {}, eleventyConfig = null) {\n\t// \tsuper(input, output, options, eleventyConfig);\n\t// }\n\n\t/**\n\t * Sets the incremental build mode.\n\t *\n\t * @param {boolean} isIncremental - Shall Eleventy run in incremental build mode and only write the files that trigger watch updates\n\t */\n\tsetIncrementalBuild(isIncremental) {\n\t\tsuper.setIncrementalBuild(isIncremental);\n\n\t\tif (this.#watchQueue) {\n\t\t\tthis.watchQueue.incremental = !!isIncremental;\n\t\t}\n\t}\n\n\tget watchQueue() {\n\t\tif (!this.#watchQueue) {\n\t\t\tthis.#watchQueue = new WatchQueue();\n\t\t\tthis.#watchQueue.incremental = this.isIncremental;\n\t\t}\n\t\treturn this.#watchQueue;\n\t}\n\n\tasync initializeConfig(initOverrides) {\n\t\tawait super.initializeConfig(initOverrides);\n\n\t\t// Careful to make sure the previous server closes on SIGINT, issue #3873\n\t\tif (!this.eleventyServe) {\n\t\t\t/** @type {object} */\n\t\t\tthis.eleventyServe = new EleventyServe();\n\t\t}\n\t\tthis.eleventyServe.eleventyConfig = this.eleventyConfig;\n\n\t\t/** @type {object} */\n\t\tthis.watchTargets = new WatchTargets(this.eleventyConfig);\n\t\tthis.watchTargets.add(this.config.additionalWatchTargets);\n\t}\n\n\tasync resetConfig() {\n\t\tawait super.resetConfig();\n\n\t\t// TODO set this.eleventyServe with this.getChokidarConfig()\n\t\tif (checkPassthroughCopyBehavior(this.config, this.runMode)) {\n\t\t\tthis.eleventyServe.resetConfig();\n\t\t}\n\t}\n\n\t/**\n\t * Starts Eleventy.\n\t */\n\tasync init(options = {}) {\n\t\tawait super.init(options);\n\n\t\t// eleventyServe is always available, even when not in --serve mode\n\t\t// TODO directorynorm\n\t\tthis.eleventyServe.setOutputDir(this.outputDir);\n\n\t\tif (checkPassthroughCopyBehavior(this.config, this.runMode)) {\n\t\t\tthis.eleventyServe.watchPassthroughCopy(\n\t\t\t\tthis.eleventyFiles.getGlobWatcherFilesForPassthroughCopy(),\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * @param {string} changedFilePath - File that triggered a re-run (added or modified)\n\t * @param {boolean} [isResetConfig] - are we doing a config reset\n\t */\n\tasync #addFileToWatchQueue(changedFilePath, isResetConfig) {\n\t\t// Currently this is only for 11ty.js deps but should be extended with usesGraph\n\t\tlet usedByDependants = [];\n\t\tif (this.watchTargets) {\n\t\t\tusedByDependants = this.watchTargets.getDependantsOf(\n\t\t\t\tTemplatePath.addLeadingDotSlash(changedFilePath),\n\t\t\t);\n\t\t}\n\n\t\tlet relevantLayouts = this.eleventyConfig.usesGraph.getLayoutsUsedBy(changedFilePath);\n\n\t\t// `eleventy.templateModified` is no longer used internally, remove in a future major version.\n\t\teventBus.emit(\"eleventy.templateModified\", changedFilePath, {\n\t\t\tusedByDependants,\n\t\t\trelevantLayouts,\n\t\t});\n\n\t\t// These listeners are *global*, not cleared even on config reset\n\t\teventBus.emit(\"eleventy.resourceModified\", changedFilePath, usedByDependants, {\n\t\t\tviaConfigReset: isResetConfig,\n\t\t\trelevantLayouts,\n\t\t});\n\n\t\tthis.config.events.emit(\"eleventy#templateModified\", changedFilePath);\n\n\t\tthis.watchQueue.addToPendingQueue(changedFilePath);\n\t}\n\n\tshouldTriggerConfigReset(changedFiles) {\n\t\t// looks for all eligible config files (not just the active one, handles config file rename)\n\t\tlet configFilePaths = new Set(this.eleventyConfig.getLocalProjectConfigFiles());\n\n\t\t// https://www.11ty.dev/docs/watch-serve/#reset-configuration\n\t\tlet resetConfigGlobs = WatchTargets.normalizeToGlobs(\n\t\t\tArray.from(this.eleventyConfig.userConfig.watchTargetsConfigReset),\n\t\t);\n\n\t\tfor (let filePath of changedFiles) {\n\t\t\tif (configFilePaths.has(filePath)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\tif (isGlobMatch(filePath, resetConfigGlobs)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\tfor (let configFilePath of configFilePaths) {\n\t\t\t// Any dependencies of the config file changed\n\t\t\tlet configFileDependencies = new Set(this.watchTargets.getDependenciesOf(configFilePath));\n\n\t\t\tfor (let filePath of changedFiles) {\n\t\t\t\tif (configFileDependencies.has(filePath)) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n\n\t// Checks the build queue to see if any configuration related files have changed\n\t#shouldResetConfig(activeQueue = []) {\n\t\tif (!activeQueue.length) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn this.shouldTriggerConfigReset(\n\t\t\tactiveQueue.map((path) => {\n\t\t\t\treturn PathNormalizer.normalizeSeperator(TemplatePath.addLeadingDotSlash(path));\n\t\t\t}),\n\t\t);\n\t}\n\n\tasync #rewatch(isResetConfig = false) {\n\t\tif (this.watchQueue.isBuildRunning()) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.watchQueue.setBuildRunning();\n\n\t\tlet queue = this.watchQueue.getActiveQueue();\n\n\t\tawait this.config.events.emit(\"beforeWatch\", queue);\n\t\tawait this.config.events.emit(\"eleventy.beforeWatch\", queue);\n\n\t\t// Clear `import` cache for all files that triggered the rebuild (sync event)\n\t\tthis.watchTargets.clearImportCacheFor(queue);\n\n\t\t// reset and reload global configuration\n\t\tif (isResetConfig) {\n\t\t\t// important: run this before config resets otherwise the handlers will disappear.\n\t\t\tawait this.config.events.emit(\"eleventy.reset\");\n\t\t\tthis.resetConfig();\n\t\t}\n\n\t\tawait this.restart();\n\t\tawait this.init({ viaConfigReset: isResetConfig });\n\n\t\ttry {\n\t\t\tlet [passthroughCopyResults, templateResults] = await this.write();\n\n\t\t\tif (isResetConfig) {\n\t\t\t\t// make sure this happens after write()\n\t\t\t\tawait this.startWatch();\n\t\t\t}\n\n\t\t\tthis.watchTargets.reset();\n\n\t\t\tawait this.#initWatchDependencies();\n\n\t\t\t// Add new deps to chokidar\n\t\t\tlet newWatchTargets = this.watchTargets.getNewTargetsSinceLastReset();\n\t\t\tthis.watcher.watchTargets(newWatchTargets);\n\n\t\t\t// Is a CSS input file and is not in the includes folder\n\t\t\t// TODO check output path file extension of this template (not input path)\n\t\t\t// TODO add additional API for this, maybe a config callback?\n\t\t\tlet onlyCssChanges = this.watchQueue.hasAllQueueFiles((path) => {\n\t\t\t\treturn (\n\t\t\t\t\tpath.endsWith(\".css\") &&\n\t\t\t\t\t// TODO how to make this work with relative includes?\n\t\t\t\t\t!TemplatePath.startsWithSubPath(path, this.eleventyFiles.getIncludesDir())\n\t\t\t\t);\n\t\t\t});\n\n\t\t\tlet files = this.watchQueue.getActiveQueue();\n\n\t\t\t// Maps passthrough copy files to output URLs for CSS live reload\n\t\t\tlet stylesheetUrls = new Set();\n\t\t\tfor (let entry of passthroughCopyResults) {\n\t\t\t\tfor (let filepath in entry.map) {\n\t\t\t\t\tif (\n\t\t\t\t\t\tfilepath.endsWith(\".css\") &&\n\t\t\t\t\t\tfiles.includes(TemplatePath.addLeadingDotSlash(filepath))\n\t\t\t\t\t) {\n\t\t\t\t\t\tstylesheetUrls.add(\n\t\t\t\t\t\t\t\"/\" + TemplatePath.stripLeadingSubPath(entry.map[filepath], this.outputDir),\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tlet normalizedPathPrefix = PathPrefixer.normalizePathPrefix(this.config.pathPrefix);\n\t\t\tlet matchingTemplates = templateResults\n\t\t\t\t.flat()\n\t\t\t\t.filter((entry) => Boolean(entry))\n\t\t\t\t.map((entry) => {\n\t\t\t\t\t// only `url`, `inputPath`, and `content` are used: https://github.com/11ty/eleventy-dev-server/blob/1c658605f75224fdc76f68aebe7a412eeb4f1bc9/client/reload-client.js#L140\n\t\t\t\t\tentry.url = PathPrefixer.joinUrlParts(normalizedPathPrefix, entry.url);\n\t\t\t\t\tdelete entry.rawInput; // Issue #3481\n\t\t\t\t\treturn entry;\n\t\t\t\t});\n\n\t\t\tawait this.eleventyServe.reload({\n\t\t\t\tfiles,\n\t\t\t\tsubtype: onlyCssChanges ? \"css\" : undefined,\n\t\t\t\tbuild: {\n\t\t\t\t\tstylesheets: Array.from(stylesheetUrls),\n\t\t\t\t\ttemplates: matchingTemplates,\n\t\t\t\t},\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tthis.eleventyServe.sendError({\n\t\t\t\terror,\n\t\t\t});\n\t\t}\n\n\t\tthis.watchQueue.setBuildFinished();\n\n\t\tlet queueSize = this.watchQueue.getPendingQueueSize();\n\t\tif (queueSize > 0) {\n\t\t\tthis.logger.log(\n\t\t\t\t`You saved while Eleventy was running, let’s run again. (${queueSize} change${\n\t\t\t\t\tqueueSize !== 1 ? \"s\" : \"\"\n\t\t\t\t})`,\n\t\t\t);\n\t\t\tawait this.#rewatch();\n\t\t}\n\t}\n\n\t/**\n\t * @returns {module:11ty/eleventy/src/Benchmark/BenchmarkGroup~BenchmarkGroup}\n\t */\n\tget watcherBench() {\n\t\treturn this.bench.get(\"Watcher\");\n\t}\n\n\t/**\n\t * Set up watchers and benchmarks.\n\t *\n\t * @async\n\t * @method\n\t */\n\tasync startWatch() {\n\t\tif (this.projectPackageJsonPath) {\n\t\t\tthis.watchTargets.add([relative(TemplatePath.getWorkingDir(), this.projectPackageJsonPath)]);\n\t\t}\n\t\tthis.watchTargets.add(this.eleventyFiles.getGlobWatcherFiles());\n\t\tthis.watchTargets.add(this.eleventyFiles.getIgnoreFiles());\n\n\t\t// Watch the local project config file\n\t\tthis.watchTargets.add(this.eleventyConfig.getActiveConfigPath());\n\n\t\t// Template and Directory Data Files\n\t\tthis.watchTargets.add(await this.eleventyFiles.getGlobWatcherTemplateDataFiles());\n\n\t\tlet benchmark = this.watcherBench.get(\n\t\t\t\"Watching JavaScript Dependencies (disable with `eleventyConfig.setWatchJavaScriptDependencies(false)`)\",\n\t\t);\n\t\tbenchmark.before();\n\t\tawait this.#initWatchDependencies();\n\t\tbenchmark.after();\n\n\t\t// Close previous watcher\n\t\tif (this.watcher) {\n\t\t\tawait this.watcher.close();\n\t\t}\n\n\t\t// TODO improve unwatching if JS dependencies are removed (or files are deleted)\n\t\tlet { targets, ignores } = await this.getWatchedTargets();\n\t\tdebug(\"Watching for changes to: %o\", targets);\n\n\t\tthis.watcher = new Watch(this.eleventyConfig);\n\t\tthis.watcher.watchTargets(targets);\n\t\tthis.watcher.addIgnores(ignores);\n\n\t\tawait this.watcher.start();\n\n\t\tthis.logger.forceLog(\"Watching…\");\n\n\t\tlet watchDelay;\n\t\tlet watchRun = async (path) => {\n\t\t\tpath = TemplatePath.normalize(path);\n\t\t\ttry {\n\t\t\t\tlet isResetConfig = this.#shouldResetConfig([path]);\n\t\t\t\tthis.#addFileToWatchQueue(path, isResetConfig);\n\n\t\t\t\tclearTimeout(watchDelay);\n\n\t\t\t\tlet { promise, resolve, reject } = withResolvers();\n\n\t\t\t\twatchDelay = setTimeout(async () => {\n\t\t\t\t\tthis.#rewatch(isResetConfig).then(resolve, reject);\n\t\t\t\t}, this.config.watchThrottleWaitTime);\n\n\t\t\t\tawait promise;\n\t\t\t} catch (e) {\n\t\t\t\tif (e instanceof EleventyBaseError) {\n\t\t\t\t\tthis.errorHandler.error(e, \"Eleventy watch error\");\n\t\t\t\t\tthis.watchQueue.setBuildFinished();\n\t\t\t\t} else {\n\t\t\t\t\tthis.errorHandler.fatal(e, \"Eleventy fatal watch error\");\n\t\t\t\t\tawait this.close();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis.config.events.emit(\"eleventy.afterwatch\");\n\t\t};\n\n\t\tthis.watcher.on(\"change\", async (path) => {\n\t\t\t// Emulated passthrough copy logs from the server\n\t\t\tif (!this.eleventyServe.isEmulatedPassthroughCopyMatch(path)) {\n\t\t\t\tthis.logger.forceLog(\n\t\t\t\t\t`File changed: ${TemplatePath.stripLeadingDotSlash(TemplatePath.standardizeFilePath(path))}`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tawait watchRun(path);\n\t\t});\n\n\t\tthis.watcher.on(\"add\", async (path) => {\n\t\t\t// Emulated passthrough copy logs from the server\n\t\t\tif (!this.eleventyServe.isEmulatedPassthroughCopyMatch(path)) {\n\t\t\t\tthis.logger.forceLog(\n\t\t\t\t\t`File added: ${TemplatePath.stripLeadingDotSlash(TemplatePath.standardizeFilePath(path))}`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tthis.fileSystemSearch.add(path);\n\t\t\tawait watchRun(path);\n\t\t});\n\n\t\tthis.watcher.on(\"unlink\", async (path) => {\n\t\t\t// Emulated passthrough copy logs from the server\n\t\t\tif (!this.eleventyServe.isEmulatedPassthroughCopyMatch(path)) {\n\t\t\t\tthis.logger.forceLog(\n\t\t\t\t\t`File deleted: ${TemplatePath.stripLeadingDotSlash(TemplatePath.standardizeFilePath(path))}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tthis.fileSystemSearch.delete(path);\n\t\t\tawait watchRun(path);\n\t\t});\n\n\t\t// For testability\n\t\treturn watchRun;\n\t}\n\n\t/**\n\t * Starts watching dependencies.\n\t */\n\tasync #initWatchDependencies() {\n\t\tif (!this.eleventyConfig.shouldSpiderJavaScriptDependencies()) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Lazy resolve isEsm only for --watch\n\t\tthis.watchTargets.setProjectUsingEsm(this.isEsm);\n\n\t\t// Template files .11ty.js\n\t\tlet templateFiles = await this.eleventyFiles.getWatchPathCache();\n\t\tawait this.watchTargets.addDependencies(templateFiles);\n\n\t\t// TODO use DirContains\n\t\tlet dataDir = TemplatePath.stripLeadingDotSlash(this.templateData.getDataDir());\n\t\tfunction filterOutGlobalDataFiles(path) {\n\t\t\treturn !dataDir || !TemplatePath.stripLeadingDotSlash(path).startsWith(dataDir);\n\t\t}\n\n\t\t// Config file dependencies\n\t\tawait this.watchTargets.addDependencies(\n\t\t\tthis.eleventyConfig.getActiveConfigPath(),\n\t\t\tfilterOutGlobalDataFiles,\n\t\t);\n\n\t\t// Deps from Global Data (that aren’t in the global data directory, everything is watched there)\n\t\tlet globalDataDeps = this.templateData.getWatchPathCache();\n\t\tawait this.watchTargets.addDependencies(globalDataDeps, filterOutGlobalDataFiles);\n\n\t\tawait this.watchTargets.addDependencies(\n\t\t\tawait this.eleventyFiles.getWatcherTemplateJavaScriptDataFiles(),\n\t\t);\n\t}\n\n\t/**\n\t * Returns all watched paths\n\t *\n\t * @async\n\t * @method\n\t * @returns {Object} `targets` file paths, and `ignores` globs Array\n\t */\n\tasync getWatchedTargets() {\n\t\treturn {\n\t\t\ttargets: await this.watchTargets.getTargets(),\n\t\t\tignores: this.eleventyFiles.getGlobWatcherIgnores(),\n\t\t};\n\t}\n\n\t/**\n\t * Start watching files\n\t *\n\t * @async\n\t * @method\n\t */\n\tasync watch() {\n\t\tthis.watcherBench.setMinimumThresholdMs(500);\n\t\tthis.watcherBench.reset();\n\n\t\t// Note that watching indirectly depends on this for fetching dependencies from JS files\n\t\t// See: TemplateWriter:pathCache and WatchTargets\n\t\tawait this.write();\n\n\t\tlet initWatchBench = this.watcherBench.get(\"Start up --watch\");\n\t\tinitWatchBench.before();\n\n\t\tlet watchRun = await this.startWatch();\n\n\t\tinitWatchBench.after();\n\n\t\tthis.watcherBench.finish(\"Watch\");\n\n\t\t// Returns for testability\n\t\treturn watchRun;\n\t}\n\n\t// Renamed to close()\n\tasync stopWatch() {\n\t\treturn this.close();\n\t}\n\n\tasync close() {\n\t\t// Prevent multiple invocations.\n\t\tif (this.#isStopping) {\n\t\t\treturn this.#isStopping;\n\t\t}\n\n\t\tdebug(\"Cleaning up chokidar and server instances, if they exist.\");\n\t\tthis.#isStopping = Promise.all([this.eleventyServe.close(), this.watcher?.close()]).then(() => {\n\t\t\tthis.#isStopping = false;\n\t\t});\n\n\t\treturn this.#isStopping;\n\t}\n\n\t/**\n\t * Serve Eleventy on this port.\n\t *\n\t * @param {Number} port - The HTTP port to serve Eleventy from.\n\t */\n\tasync serve(port) {\n\t\t// Port is optional and in this case likely via --port on the command line\n\t\t// May defer to configuration API options `port` property\n\t\treturn this.eleventyServe.serve(port);\n\t}\n\n\t/**\n\t * Shows a help message including usage.\n\t *\n\t * @static\n\t * @returns {string} - The help message.\n\t */\n\tstatic getHelp() {\n\t\treturn `Usage: eleventy\n       eleventy --input=. --output=./_site\n       eleventy --serve\n\nArguments:\n\n     --version\n\n     --input=.\n       Input template files (default: \\`.\\`)\n\n     --output=_site\n       Write HTML output to this folder (default: \\`_site\\`)\n\n     --serve\n       Run web server on --port (default 8080) and watch them too\n\n     --port\n       Run the --serve web server on this port (default 8080)\n\n     --watch\n       Wait for files to change and automatically rewrite (no web server)\n\n     --incremental\n       Only build the files that have changed. Best with watch/serve.\n\n     --incremental=filename.md\n       Does not require watch/serve. Run an incremental build targeting a single file.\n\n     --ignore-initial\n       Start without a build; build when files change. Works best with watch/serve/incremental.\n\n     --formats=liquid,md\n       Allow only certain template types (default: \\`*\\`)\n\n     --quiet\n       Don’t print all written files (off by default)\n\n     --config=filename.js\n       Override the eleventy config file path (default: \\`.eleventy.js\\`)\n\n     --pathprefix='/'\n       Change all url template filters to use this subdirectory.\n\n     --dryrun\n       Don’t write any files. Useful in DEBUG mode, for example: \\`DEBUG=Eleventy* npx @11ty/eleventy --dryrun\\`\n\n     --loader\n       Set to \"esm\" to force ESM mode, \"cjs\" to force CommonJS mode, or \"auto\" (default) to infer it from package.json.\n\n     --to=json\n       Change the output to JSON (default: \\`fs\\`)\n\n     --to=fs:templates\n       Writes templates, skips passthrough copy\n\n     --help`;\n\t}\n\n\t/**\n\t * @deprecated since 1.0.1, use static Eleventy.getHelp()\n\t */\n\tgetHelp() {\n\t\treturn Eleventy.getHelp();\n\t}\n\n\t/* Removed methods */\n\tinitWatch() {\n\t\tthrow new Error(\n\t\t\t\"Eleventy#initWatch() was removed in v4. Use Eleventy#startWatch() instead (initializes and starts the watcher)\",\n\t\t);\n\t}\n\tgetWatchedFiles() {\n\t\tthrow new Error(\n\t\t\t\"Eleventy#getWatchedFiles() was removed in Eleventy v4. Use Eleventy#getWatchedTargets().targets instead.\",\n\t\t);\n\t}\n}\n\nexport { Eleventy };\n\n/* Utils */\nexport { EleventyImport as ImportFile } from \"./Util/Require.js\";\n\n// TODO(breaking) remove these and recommend folks use package level exports e.g. \"@11ty/eleventy/plugins/i18n\"\n\n/* Plugins */\nexport { default as BundlePlugin } from \"@11ty/eleventy-plugin-bundle\";\n\n// Eleventy*Plugin names are legacy names\nexport {\n\tdefault as RenderPlugin,\n\tdefault as EleventyRenderPlugin,\n} from \"./Plugins/RenderPlugin.js\";\nexport { default as I18nPlugin, default as EleventyI18nPlugin } from \"./Plugins/I18nPlugin.js\";\nexport {\n\tdefault as HtmlBasePlugin,\n\tdefault as EleventyHtmlBasePlugin,\n} from \"./Plugins/HtmlBasePlugin.js\";\nexport { TransformPlugin as InputPathToUrlTransformPlugin } from \"./Plugins/InputPathToUrl.js\";\nexport { IdAttributePlugin } from \"./Plugins/IdAttributePlugin.js\";\n\nexport { PreserveClosingTagsPlugin } from \"./Plugins/PreserveClosingTagsPlugin.js\";\n\n// Error messages for Removed plugins\nexport function EleventyServerlessBundlerPlugin() {\n\tthrow new Error(\n\t\t\"Following feedback from our Community Survey, low interest in this plugin prompted its removal from Eleventy core in 3.0 as we refocus on static sites. Learn more: https://v3.11ty.dev/docs/plugins/serverless/\",\n\t);\n}\n\nexport { EleventyServerlessBundlerPlugin as EleventyServerless };\n\nexport function EleventyEdgePlugin() {\n\tthrow new Error(\n\t\t\"Following feedback from our Community Survey, low interest in this plugin prompted its removal from Eleventy core in 3.0 as we refocus on static sites. Learn more: https://v3.11ty.dev/docs/plugins/edge/\",\n\t);\n}\n"
  },
  {
    "path": "src/EleventyCommonJs.cjs",
    "content": "function canRequireModules() {\n\t// via --experimental-require-module or newer than Node 22 support when this flag is no longer necessary\n\ttry {\n\t\trequire(\"./Util/Objects/SampleModule.mjs\");\n\t\treturn true;\n\t} catch(e) {\n\t\tif(e.code === \"ERR_REQUIRE_ESM\") {\n\t\t\treturn false;\n\t\t}\n\n\t\t// Rethrow if not an ESM require error.\n\t\tthrow e;\n\t}\n}\n\nif(!canRequireModules()) {\n\tlet error = new Error(`\\`require(\"@11ty/eleventy\")\\` is incompatible with Eleventy v3 and this version of Node. You have a few options:\n   1. (Easiest) Change the \\`require\\` to use a dynamic import inside of an asynchronous CommonJS configuration\n      callback, for example:\n\n      module.exports = async function {\n        const {RenderPlugin, I18nPlugin, HtmlBasePlugin} = await import(\"@11ty/eleventy\");\n      }\n\n   2. (Easier) Update the JavaScript syntax in your configuration file from CommonJS to ESM (change \\`require\\`\n      to use \\`import\\` and rename the file to have an \\`.mjs\\` file extension).\n\n   3. (More work) Change your project to use ESM-first by adding \\`\"type\": \"module\"\\` to your package.json. Any\n      \\`.js\\` will need to be ported to use ESM syntax (or renamed to \\`.cjs\\`.)\n\n   4. Upgrade your Node version (at time of writing, v20.19 or newer) to enable this behavior. If you use a version\n      of Node older than v20.19, try the --experimental-require-module command line flag in Node. Read more:\n      https://nodejs.org/api/modules.html#loading-ecmascript-modules-using-require`);\n\n\terror.skipOriginalStack = true;\n\n\tthrow error;\n}\n\n// If we made it here require(ESM) works fine (via --experimental-require-module or newer Node.js defaults)\nlet mod = require(\"./Eleventy.js\");\n\nmodule.exports = mod;\n"
  },
  {
    "path": "src/EleventyExtensionMap.js",
    "content": "import { TemplatePath } from \"@11ty/eleventy-utils\";\n\nimport { isTypeScriptSupported } from \"./Util/FeatureTests.cjs\";\n\nclass EleventyExtensionMap {\n\t#engineManager;\n\n\tconstructor(config) {\n\t\tthis.setTemplateConfig(config);\n\t\tthis._spiderJsDepsCache = {};\n\n\t\t/** @type {Array} */\n\t\tthis.validTemplateLanguageKeys;\n\t}\n\n\tsetFormats(formatKeys = []) {\n\t\t// raw\n\t\tthis.formatKeys = formatKeys;\n\n\t\tthis.unfilteredFormatKeys = formatKeys.map(function (key) {\n\t\t\treturn key.trim().toLowerCase();\n\t\t});\n\n\t\tthis.validTemplateLanguageKeys = this.unfilteredFormatKeys.filter((key) =>\n\t\t\tthis.hasExtension(key),\n\t\t);\n\n\t\tthis.passthroughCopyKeys = this.unfilteredFormatKeys.filter((key) => !this.hasExtension(key));\n\t}\n\n\tsetTemplateConfig(config) {\n\t\tif (!config || config.constructor.name !== \"TemplateConfig\") {\n\t\t\tthrow new Error(\"Internal error: Missing or invalid `config` argument.\");\n\t\t}\n\n\t\tthis.templateConfig = config;\n\t}\n\n\tget config() {\n\t\treturn this.templateConfig.getConfig();\n\t}\n\n\tget engineManager() {\n\t\tif (!this.#engineManager) {\n\t\t\tthrow new Error(\"Internal error: Missing `#engineManager` in EleventyExtensionMap.\");\n\t\t}\n\n\t\treturn this.#engineManager;\n\t}\n\n\tset engineManager(mgr) {\n\t\tthis.#engineManager = mgr;\n\t}\n\n\treset() {\n\t\tthis.#engineManager.reset();\n\t}\n\n\t/* Used for layout path resolution */\n\tgetFileList(path, dir) {\n\t\tif (!path) {\n\t\t\treturn [];\n\t\t}\n\n\t\tlet files = [];\n\t\tthis.validTemplateLanguageKeys.forEach((key) => {\n\t\t\tthis.getExtensionsFromKey(key).forEach(function (extension) {\n\t\t\t\tfiles.push((dir ? dir + \"/\" : \"\") + path + \".\" + extension);\n\t\t\t});\n\t\t});\n\n\t\treturn files;\n\t}\n\n\t// Warning: this would false positive on an include, but is only used\n\t// on paths found from the file system glob search.\n\t// TODO: Method name might just need to be renamed to something more accurate.\n\tisFullTemplateFilePath(path) {\n\t\tfor (let extension of this.validTemplateLanguageKeys) {\n\t\t\tif (path.endsWith(`.${extension}`)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n\n\tgetCustomExtensionEntry(extension) {\n\t\tif (!this.config.extensionMap) {\n\t\t\treturn;\n\t\t}\n\n\t\tfor (let entry of this.config.extensionMap) {\n\t\t\tif (entry.extension === extension) {\n\t\t\t\treturn entry;\n\t\t\t}\n\t\t}\n\t}\n\n\tgetValidExtensionsForPath(path) {\n\t\tlet extensions = new Set();\n\t\tfor (let extension in this.extensionToKeyMap) {\n\t\t\tif (path.endsWith(`.${extension}`)) {\n\t\t\t\textensions.add(extension);\n\t\t\t}\n\t\t}\n\n\t\t// if multiple extensions are valid, sort from longest to shortest\n\t\t// e.g. .11ty.js and .js\n\t\tlet sorted = Array.from(extensions)\n\t\t\t.filter((extension) => this.validTemplateLanguageKeys.includes(extension))\n\t\t\t.sort((a, b) => b.length - a.length);\n\n\t\treturn sorted;\n\t}\n\n\tasync shouldSpiderJavaScriptDependencies(path) {\n\t\tlet extensions = this.getValidExtensionsForPath(path);\n\t\tfor (let extension of extensions) {\n\t\t\tif (extension in this._spiderJsDepsCache) {\n\t\t\t\treturn this._spiderJsDepsCache[extension];\n\t\t\t}\n\n\t\t\tlet cls = await this.engineManager.getEngineClassByExtension(extension);\n\t\t\tif (cls) {\n\t\t\t\tlet entry = this.getCustomExtensionEntry(extension);\n\t\t\t\tlet shouldSpider = cls.shouldSpiderJavaScriptDependencies(entry);\n\t\t\t\tthis._spiderJsDepsCache[extension] = shouldSpider;\n\t\t\t\treturn shouldSpider;\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n\n\tgetPassthroughCopyGlobs(inputDir) {\n\t\treturn this._getGlobs(this.passthroughCopyKeys, inputDir);\n\t}\n\n\tgetValidGlobs(inputDir) {\n\t\treturn this._getGlobs(this.validTemplateLanguageKeys, inputDir);\n\t}\n\n\tgetGlobs(inputDir) {\n\t\treturn this._getGlobs(this.unfilteredFormatKeys, inputDir);\n\t}\n\n\t_getGlobs(formatKeys, inputDir = \"\") {\n\t\tlet extensions = new Set();\n\n\t\tfor (let key of formatKeys) {\n\t\t\tif (this.hasExtension(key)) {\n\t\t\t\tfor (let extension of this.getExtensionsFromKey(key)) {\n\t\t\t\t\textensions.add(extension);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\textensions.add(key);\n\t\t\t}\n\t\t}\n\n\t\tlet dir = TemplatePath.convertToRecursiveGlobSync(inputDir);\n\t\tif (extensions.size === 1) {\n\t\t\treturn [`${dir}/*.${Array.from(extensions)[0]}`];\n\t\t} else if (extensions.size > 1) {\n\t\t\treturn [\n\t\t\t\t// extra curly brackets /*.{cjs,txt}\n\t\t\t\t`${dir}/*.{${Array.from(extensions).join(\",\")}}`,\n\t\t\t];\n\t\t}\n\n\t\treturn [];\n\t}\n\n\thasExtension(key) {\n\t\tfor (let extension in this.extensionToKeyMap) {\n\t\t\tif (\n\t\t\t\tthis.extensionToKeyMap[extension].key === key ||\n\t\t\t\tthis.extensionToKeyMap[extension].aliasKey === key\n\t\t\t) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n\n\tgetExtensionsFromKey(key) {\n\t\tlet extensions = new Set();\n\t\tfor (let extension in this.extensionToKeyMap) {\n\t\t\tif (this.extensionToKeyMap[extension].aliasKey) {\n\t\t\t\t// only add aliased extension if explicitly referenced in formats\n\t\t\t\t// overrides will not have an aliasKey (md => md)\n\t\t\t\tif (this.extensionToKeyMap[extension].aliasKey === key) {\n\t\t\t\t\textensions.add(extension);\n\t\t\t\t}\n\t\t\t} else if (this.extensionToKeyMap[extension].key === key) {\n\t\t\t\textensions.add(extension);\n\t\t\t}\n\t\t}\n\n\t\treturn Array.from(extensions);\n\t}\n\n\t// Only `addExtension` configuration API extensions\n\tgetExtensionEntriesFromKey(key) {\n\t\tlet entries = new Set();\n\t\tif (\"extensionMap\" in this.config) {\n\t\t\tfor (let entry of this.config.extensionMap) {\n\t\t\t\tif (entry.key === key) {\n\t\t\t\t\tentries.add(entry);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn Array.from(entries);\n\t}\n\n\t// Determines whether a path is a passthrough copy file or a template (via TemplateWriter)\n\thasEngine(pathOrKey) {\n\t\treturn !!this.getKey(pathOrKey);\n\t}\n\n\tgetKey(pathOrKey) {\n\t\tpathOrKey = (pathOrKey || \"\").toLowerCase();\n\t\tfor (let extension in this.extensionToKeyMap) {\n\t\t\tif (pathOrKey === extension || pathOrKey.endsWith(\".\" + extension)) {\n\t\t\t\tlet key =\n\t\t\t\t\tthis.extensionToKeyMap[extension].aliasKey || this.extensionToKeyMap[extension].key;\n\t\t\t\t// must be a valid format key passed (e.g. via --formats)\n\t\t\t\tif (this.validTemplateLanguageKeys.includes(key)) {\n\t\t\t\t\treturn key;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tgetExtensionEntry(pathOrKey) {\n\t\tpathOrKey = (pathOrKey || \"\").toLowerCase();\n\t\tfor (let extension in this.extensionToKeyMap) {\n\t\t\tif (pathOrKey === extension || pathOrKey.endsWith(\".\" + extension)) {\n\t\t\t\treturn this.extensionToKeyMap[extension];\n\t\t\t}\n\t\t}\n\t}\n\n\tremoveTemplateExtension(path) {\n\t\tfor (let extension in this.extensionToKeyMap) {\n\t\t\tif (path === extension || path.endsWith(\".\" + extension)) {\n\t\t\t\treturn path.slice(\n\t\t\t\t\t0,\n\t\t\t\t\tpath.length - 1 - extension.length < 0 ? 0 : path.length - 1 - extension.length,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t\treturn path;\n\t}\n\n\t// keys are file extensions\n\t// values are template language keys\n\tget extensionToKeyMap() {\n\t\tif (!this._extensionToKeyMap) {\n\t\t\tthis._extensionToKeyMap = {\n\t\t\t\tmd: { key: \"md\", extension: \"md\" },\n\t\t\t\thtml: { key: \"html\", extension: \"html\" },\n\t\t\t\tnjk: { key: \"njk\", extension: \"njk\" },\n\t\t\t\tliquid: { key: \"liquid\", extension: \"liquid\" },\n\t\t\t\t\"11ty.js\": { key: \"11ty.js\", extension: \"11ty.js\" },\n\t\t\t\t\"11ty.cjs\": { key: \"11ty.js\", extension: \"11ty.cjs\" },\n\t\t\t\t\"11ty.mjs\": { key: \"11ty.js\", extension: \"11ty.mjs\" },\n\t\t\t};\n\n\t\t\tif (isTypeScriptSupported()) {\n\t\t\t\tthis._extensionToKeyMap[\"11ty.ts\"] = { key: \"11ty.js\", extension: \"11ty.ts\" };\n\t\t\t\tthis._extensionToKeyMap[\"11ty.cts\"] = { key: \"11ty.js\", extension: \"11ty.cts\" };\n\t\t\t\tthis._extensionToKeyMap[\"11ty.mts\"] = { key: \"11ty.js\", extension: \"11ty.mts\" };\n\t\t\t}\n\n\t\t\tif (\"extensionMap\" in this.config) {\n\t\t\t\tfor (let entry of this.config.extensionMap) {\n\t\t\t\t\t// extension and key are only different when aliasing.\n\t\t\t\t\tthis._extensionToKeyMap[entry.extension] = entry;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn this._extensionToKeyMap;\n\t}\n\n\tgetReadableFileExtensions() {\n\t\treturn Object.keys(this.extensionToKeyMap).join(\" \");\n\t}\n}\n\nexport default EleventyExtensionMap;\n"
  },
  {
    "path": "src/EleventyFiles.js",
    "content": "import { existsSync, statSync, readFileSync } from \"node:fs\";\n\nimport { TemplatePath } from \"@11ty/eleventy-utils\";\nimport debugUtil from \"debug\";\n\nimport DirContains from \"./Util/DirContains.js\";\nimport TemplateData from \"./Data/TemplateData.js\";\nimport TemplateGlob from \"./TemplateGlob.js\";\nimport checkPassthroughCopyBehavior from \"./Util/PassthroughCopyBehaviorCheck.js\";\n\nconst debug = debugUtil(\"Eleventy:EleventyFiles\");\n\nclass EleventyFiles {\n\t#extensionMap;\n\t#watcherGlobs;\n\n\tconstructor(formats, templateConfig) {\n\t\tif (!templateConfig) {\n\t\t\tthrow new Error(\"Internal error: Missing `templateConfig`` argument.\");\n\t\t}\n\n\t\tthis.templateConfig = templateConfig;\n\t\tthis.config = templateConfig.getConfig();\n\t\tthis.aggregateBench = this.config.benchmarkManager.get(\"Aggregate\");\n\n\t\tthis.formats = formats;\n\t\tthis.eleventyIgnoreContent = false;\n\t}\n\n\tget dirs() {\n\t\treturn this.templateConfig.directories;\n\t}\n\n\tget inputDir() {\n\t\treturn this.dirs.input;\n\t}\n\n\tget outputDir() {\n\t\treturn this.dirs.output;\n\t}\n\n\tget includesDir() {\n\t\treturn this.dirs.includes;\n\t}\n\n\tget layoutsDir() {\n\t\treturn this.dirs.layouts;\n\t}\n\n\tget dataDir() {\n\t\treturn this.dirs.data;\n\t}\n\n\t// Backwards compat\n\tgetDataDir() {\n\t\treturn this.dataDir;\n\t}\n\n\tsetFileSystemSearch(fileSystemSearch) {\n\t\tthis.fileSystemSearch = fileSystemSearch;\n\t}\n\n\tinit() {\n\t\tif (this.dirs.inputFile || this.dirs.inputGlob) {\n\t\t\tthis.templateGlobs = TemplateGlob.map([this.dirs.inputFile || this.dirs.inputGlob]);\n\t\t} else {\n\t\t\t// Input is a directory\n\t\t\tthis.templateGlobs = this.extensionMap.getGlobs(this.inputDir);\n\t\t}\n\n\t\tthis.setupGlobs();\n\t}\n\n\t#getWatcherGlobs() {\n\t\tif (!this.#watcherGlobs) {\n\t\t\tlet globs;\n\t\t\t// Input is a file\n\t\t\tif (this.inputFile) {\n\t\t\t\tglobs = this.templateGlobs;\n\t\t\t} else {\n\t\t\t\t// input is a directory\n\t\t\t\tglobs = this.extensionMap.getValidGlobs(this.inputDir);\n\t\t\t}\n\t\t\tthis.#watcherGlobs = globs;\n\t\t}\n\n\t\treturn this.#watcherGlobs;\n\t}\n\n\tget passthroughGlobs() {\n\t\tlet paths = new Set();\n\t\t// stuff added in addPassthroughCopy()\n\t\tfor (let path of this.passthroughManager.getConfigPathGlobs()) {\n\t\t\tpaths.add(path);\n\t\t}\n\t\t// non-template language extensions\n\t\tfor (let path of this.extensionMap.getPassthroughCopyGlobs(this.inputDir)) {\n\t\t\tpaths.add(path);\n\t\t}\n\t\treturn Array.from(paths);\n\t}\n\n\trestart() {\n\t\tthis.setupGlobs();\n\t\tthis._glob = null;\n\t}\n\n\t/* For testing */\n\t_setConfig(config) {\n\t\tif (!config.ignores) {\n\t\t\tconfig.ignores = new Set();\n\t\t\tconfig.ignores.add(\"**/node_modules/**\");\n\t\t}\n\n\t\tthis.config = config;\n\n\t\tthis.init();\n\t}\n\n\t/* Set command root for local project paths */\n\t// This is only used by tests\n\t_setLocalPathRoot(dir) {\n\t\tthis.localPathRoot = dir;\n\t}\n\n\tset extensionMap(extensionMap) {\n\t\tthis.#extensionMap = extensionMap;\n\t}\n\n\tget extensionMap() {\n\t\t// for tests\n\t\tif (!this.#extensionMap) {\n\t\t\tthrow new Error(\"Internal error: missing `extensionMap` in EleventyFiles.\");\n\t\t}\n\t\treturn this.#extensionMap;\n\t}\n\n\tsetRunMode(runMode) {\n\t\tthis.runMode = runMode;\n\t}\n\n\tsetPassthroughManager(mgr) {\n\t\tthis.passthroughManager = mgr;\n\t}\n\n\tset templateData(templateData) {\n\t\tthis._templateData = templateData;\n\t}\n\n\tget templateData() {\n\t\treturn this._templateData;\n\t}\n\n\tsetupGlobs() {\n\t\tthis.fileIgnores = this.getIgnores();\n\t\tthis.extraIgnores = this.getIncludesAndDataDirs();\n\t\tthis.uniqueIgnores = this.getIgnoreGlobs();\n\n\t\t// Conditional added for tests that don’t have a config\n\t\tif (this.config?.events) {\n\t\t\tthis.config.events.emit(\"eleventy.ignores\", this.uniqueIgnores);\n\t\t}\n\n\t\tthis.normalizedTemplateGlobs = this.templateGlobs;\n\t}\n\n\tnormalizeIgnoreEntry(entry) {\n\t\tif (!entry.startsWith(\"**/\")) {\n\t\t\treturn TemplateGlob.normalizePath(this.localPathRoot || \".\", entry);\n\t\t}\n\t\treturn entry;\n\t}\n\n\tgetIgnoreGlobs() {\n\t\tlet uniqueIgnores = new Set();\n\t\tfor (let ignore of this.fileIgnores) {\n\t\t\tuniqueIgnores.add(ignore);\n\t\t}\n\t\tfor (let ignore of this.extraIgnores) {\n\t\t\tuniqueIgnores.add(ignore);\n\t\t}\n\n\t\t// Placing the config ignores last here is important to the tests\n\t\tfor (let ignore of this.config.ignores) {\n\t\t\tuniqueIgnores.add(this.normalizeIgnoreEntry(ignore));\n\t\t}\n\n\t\treturn Array.from(uniqueIgnores);\n\t}\n\n\tstatic getFileIgnores(ignoreFiles) {\n\t\tif (!Array.isArray(ignoreFiles)) {\n\t\t\tignoreFiles = [ignoreFiles];\n\t\t}\n\n\t\tlet ignores = [];\n\t\tfor (let ignorePath of ignoreFiles) {\n\t\t\tignorePath = TemplatePath.normalize(ignorePath);\n\n\t\t\tlet dir = TemplatePath.getDirFromFilePath(ignorePath);\n\n\t\t\tif (existsSync(ignorePath)) {\n\t\t\t\tlet ignoreContent = readFileSync(ignorePath, \"utf8\");\n\t\t\t\tif (ignoreContent) {\n\t\t\t\t\tignores = ignores.concat(EleventyFiles.normalizeIgnoreContent(dir, ignoreContent));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tignores.forEach((path) => debug(`${ignoreFiles} ignoring: ${path}`));\n\n\t\treturn ignores;\n\t}\n\n\tstatic normalizeIgnoreContent(dir, ignoreContent) {\n\t\tlet ignores = [];\n\n\t\tif (ignoreContent) {\n\t\t\tignores = ignoreContent\n\t\t\t\t.split(\"\\n\")\n\t\t\t\t.map((line) => {\n\t\t\t\t\treturn line.trim();\n\t\t\t\t})\n\t\t\t\t.filter((line) => {\n\t\t\t\t\tif (line.charAt(0) === \"!\") {\n\t\t\t\t\t\tdebug(\n\t\t\t\t\t\t\t\">>> When processing .gitignore/.eleventyignore, Eleventy does not currently support negative patterns but encountered one:\",\n\t\t\t\t\t\t);\n\t\t\t\t\t\tdebug(\">>>\", line);\n\t\t\t\t\t\tdebug(\"Follow along at https://github.com/11ty/eleventy/issues/693 to track support.\");\n\t\t\t\t\t}\n\n\t\t\t\t\t// empty lines or comments get filtered out\n\t\t\t\t\treturn line.length > 0 && line.charAt(0) !== \"#\" && line.charAt(0) !== \"!\";\n\t\t\t\t})\n\t\t\t\t.map((line) => {\n\t\t\t\t\tlet path = TemplateGlob.normalizePath(dir, \"/\", line);\n\t\t\t\t\tpath = TemplatePath.addLeadingDotSlash(TemplatePath.relativePath(path));\n\n\t\t\t\t\ttry {\n\t\t\t\t\t\t// Note these folders must exist to get /** suffix\n\t\t\t\t\t\tlet stat = statSync(path);\n\t\t\t\t\t\tif (stat.isDirectory()) {\n\t\t\t\t\t\t\treturn path + \"/**\";\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn path;\n\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\treturn path;\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t}\n\n\t\treturn ignores;\n\t}\n\n\t/* Tests only */\n\t_setEleventyIgnoreContent(content) {\n\t\tthis.eleventyIgnoreContent = content;\n\t}\n\n\tgetIgnores() {\n\t\tlet files = new Set();\n\n\t\tfor (let ignore of EleventyFiles.getFileIgnores(this.getIgnoreFiles())) {\n\t\t\tfiles.add(ignore);\n\t\t}\n\n\t\t// testing API\n\t\tif (this.eleventyIgnoreContent !== false) {\n\t\t\tfiles.add(this.eleventyIgnoreContent);\n\t\t}\n\n\t\t// Make sure output dir isn’t in the input dir (or it will ignore all input!)\n\t\t// input: . and output: . (skip ignore)\n\t\t// input: ./content and output . (skip ignore)\n\t\t// input: . and output: ./_site (add ignore)\n\t\tlet outputContainsInputDir = DirContains(this.outputDir, this.inputDir);\n\t\tif (!outputContainsInputDir) {\n\t\t\t// both are already normalized in 3.0\n\t\t\tfiles.add(TemplateGlob.map(this.outputDir + \"/**\"));\n\t\t}\n\n\t\treturn Array.from(files);\n\t}\n\n\tgetIgnoreFiles() {\n\t\tlet ignoreFiles = new Set();\n\t\tlet rootDirectory = this.localPathRoot || \".\";\n\n\t\tif (this.config.useGitIgnore) {\n\t\t\tignoreFiles.add(TemplatePath.join(rootDirectory, \".gitignore\"));\n\t\t}\n\n\t\tif (this.eleventyIgnoreContent === false) {\n\t\t\tlet absoluteInputDir = TemplatePath.absolutePath(this.inputDir);\n\t\t\tignoreFiles.add(TemplatePath.join(rootDirectory, \".eleventyignore\"));\n\n\t\t\tif (rootDirectory !== absoluteInputDir) {\n\t\t\t\tignoreFiles.add(TemplatePath.join(this.inputDir, \".eleventyignore\"));\n\t\t\t}\n\t\t}\n\n\t\treturn Array.from(ignoreFiles);\n\t}\n\n\t/* Backwards compat */\n\tgetIncludesDir() {\n\t\treturn this.includesDir;\n\t}\n\n\t/* Backwards compat */\n\tgetLayoutsDir() {\n\t\treturn this.layoutsDir;\n\t}\n\n\tgetFileGlobs() {\n\t\treturn this.normalizedTemplateGlobs;\n\t}\n\n\tgetRawFiles() {\n\t\treturn this.templateGlobs;\n\t}\n\n\tasync getWatchPathCache() {\n\t\t// Issue #1325: make sure passthrough copy files are not included here\n\t\tif (!this.pathCache) {\n\t\t\tthrow new Error(\"Watching requires `.getFiles()` to be called first in EleventyFiles\");\n\t\t}\n\n\t\tlet ret = [];\n\t\t// Filter out the passthrough copy paths.\n\t\tfor (let path of this.pathCache) {\n\t\t\tif (\n\t\t\t\tthis.extensionMap.isFullTemplateFilePath(path) &&\n\t\t\t\t(await this.extensionMap.shouldSpiderJavaScriptDependencies(path))\n\t\t\t) {\n\t\t\t\tret.push(path);\n\t\t\t}\n\t\t}\n\t\treturn ret;\n\t}\n\n\t_globSearch() {\n\t\tlet globs = this.getFileGlobs();\n\n\t\t// returns a promise\n\t\tdebug(\"Searching for: %o\", globs);\n\t\treturn this.fileSystemSearch.search(\"templates\", globs, {\n\t\t\tignore: this.uniqueIgnores,\n\t\t});\n\t}\n\n\tasync getFiles() {\n\t\tlet bench = this.aggregateBench.get(\"Searching the file system (templates)\");\n\t\tbench.before();\n\t\tlet globResults = await this._globSearch();\n\t\tlet paths = TemplatePath.addLeadingDotSlashArray(globResults);\n\t\tbench.after();\n\n\t\t// Note 2.0.0-canary.19 removed a `filter` option for custom template syntax here that was unpublished and unused.\n\t\tthis.pathCache = paths;\n\n\t\treturn paths;\n\t}\n\n\tgetFileShape(paths, filePath) {\n\t\tif (!filePath) {\n\t\t\treturn;\n\t\t}\n\t\tif (this.isPassthroughCopyFile(paths, filePath)) {\n\t\t\treturn \"copy\";\n\t\t}\n\t\tif (this.isFullTemplateFile(paths, filePath)) {\n\t\t\treturn \"template\";\n\t\t}\n\t\t// include/layout/unknown\n\t}\n\n\tisPassthroughCopyFile(paths, filePath) {\n\t\treturn this.passthroughManager.isPassthroughCopyFile(paths, filePath);\n\t}\n\n\t// Assumption here that filePath is not a passthrough copy file\n\tisFullTemplateFile(paths, filePath) {\n\t\tif (!filePath) {\n\t\t\treturn false;\n\t\t}\n\n\t\tfor (let path of paths) {\n\t\t\tif (path === filePath) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n\n\t/* For `eleventy --watch` */\n\tgetGlobWatcherFiles() {\n\t\tlet directoryGlobs = this.getIncludesAndDataDirs();\n\n\t\tlet globs = this.#getWatcherGlobs();\n\n\t\tif (checkPassthroughCopyBehavior(this.config, this.runMode)) {\n\t\t\treturn globs.concat(directoryGlobs);\n\t\t}\n\n\t\t// Revert to old passthroughcopy copy files behavior\n\t\treturn globs.concat(this.passthroughGlobs).concat(directoryGlobs);\n\t}\n\n\t/* For `eleventy --watch` */\n\tgetGlobWatcherFilesForPassthroughCopy() {\n\t\treturn this.passthroughGlobs;\n\t}\n\n\t/* For `eleventy --watch` */\n\tasync getGlobWatcherTemplateDataFiles() {\n\t\tlet templateData = this.templateData;\n\t\treturn await templateData.getTemplateDataFileGlob();\n\t}\n\n\t/* For `eleventy --watch` */\n\t// TODO this isn’t great but reduces complexity avoiding using TemplateData:getLocalDataPaths for each template in the cache\n\tasync getWatcherTemplateJavaScriptDataFiles() {\n\t\tlet globs = this.templateData.getTemplateJavaScriptDataFileGlob();\n\t\tlet bench = this.aggregateBench.get(\"Searching the file system (watching)\");\n\t\tbench.before();\n\t\tlet results = TemplatePath.addLeadingDotSlashArray(\n\t\t\tawait this.fileSystemSearch.search(\"js-dependencies\", globs, {\n\t\t\t\tignore: [\n\t\t\t\t\t\"**/node_modules/**\",\n\t\t\t\t\t\".git/**\",\n\t\t\t\t\t// TODO outputDir\n\t\t\t\t\t// this.outputDir,\n\t\t\t\t],\n\t\t\t}),\n\t\t);\n\t\tbench.after();\n\t\treturn results;\n\t}\n\n\t/* Ignored by `eleventy --watch` */\n\tgetGlobWatcherIgnores() {\n\t\t// convert to format without ! since they are passed in as a separate argument to glob watcher\n\t\tlet entries = new Set(\n\t\t\tthis.fileIgnores.map((ignore) => TemplatePath.stripLeadingDotSlash(ignore)),\n\t\t);\n\n\t\tfor (let ignore of this.config.watchIgnores) {\n\t\t\tentries.add(this.normalizeIgnoreEntry(ignore));\n\t\t}\n\n\t\t// de-duplicated\n\t\treturn Array.from(entries);\n\t}\n\n\tgetIncludesAndDataDirs() {\n\t\tlet rawPaths = new Set();\n\t\trawPaths.add(this.includesDir);\n\t\tif (this.layoutsDir) {\n\t\t\trawPaths.add(this.layoutsDir);\n\t\t}\n\t\trawPaths.add(this.dataDir);\n\n\t\treturn Array.from(rawPaths)\n\t\t\t.filter((entry) => {\n\t\t\t\t// never ignore the input directory (even if config file returns \"\" for these)\n\t\t\t\treturn entry && entry !== this.inputDir;\n\t\t\t})\n\t\t\t.map((entry) => {\n\t\t\t\treturn TemplateGlob.map(entry + \"**\");\n\t\t\t});\n\t}\n}\n\nexport default EleventyFiles;\n"
  },
  {
    "path": "src/EleventyServe.js",
    "content": "import debugUtil from \"debug\";\nimport { Merge, TemplatePath } from \"@11ty/eleventy-utils\";\n\nimport { Watch } from \"./Watch.js\";\n\nfunction stringifyOptions(options) {\n\treturn JSON.stringify(options, function replacer(key, value) {\n\t\tif (typeof value === \"function\") {\n\t\t\treturn value.toString();\n\t\t}\n\n\t\treturn value;\n\t});\n}\n\nimport EleventyBaseError from \"./Errors/EleventyBaseError.js\";\nimport ConsoleLogger from \"./Util/ConsoleLogger.js\";\nimport PathPrefixer from \"./Util/PathPrefixer.js\";\nimport checkPassthroughCopyBehavior from \"./Util/PassthroughCopyBehaviorCheck.js\";\nimport { getModulePackageJson } from \"./Util/ImportJsonSync.js\";\nimport { EleventyImport } from \"./Util/Require.js\";\nimport { isGlobMatch } from \"./Util/GlobMatcher.js\";\n\nconst debug = debugUtil(\"Eleventy:EleventyServe\");\n\nclass EleventyServeConfigError extends EleventyBaseError {}\n\nconst DEFAULT_SERVER_OPTIONS = {\n\tmodule: \"@11ty/eleventy-dev-server\",\n\tport: 8080,\n\t// pathPrefix: \"/\",\n\t// setup: function() {},\n\t// ready: function(server) {},\n\t// logger: { info: function() {}, error: function() {} }\n};\n\nclass EleventyServe {\n\t#eleventyConfig;\n\t#savedConfigOptions;\n\t#aliases;\n\t#initOptionsFetched = false;\n\t#chokidar;\n\t// these are *not* normalized\n\t#watchTargets = new Set();\n\n\tconstructor() {\n\t\tthis.logger = new ConsoleLogger();\n\t}\n\n\tget config() {\n\t\tif (!this.eleventyConfig) {\n\t\t\tthrow new EleventyServeConfigError(\n\t\t\t\t\"You need to set the eleventyConfig property on EleventyServe.\",\n\t\t\t);\n\t\t}\n\n\t\treturn this.eleventyConfig.getConfig();\n\t}\n\n\tset config(config) {\n\t\tthrow new Error(\"It’s not allowed to set config on EleventyServe. Set eleventyConfig instead.\");\n\t}\n\n\tsetAliases(aliases) {\n\t\tthis.#aliases = aliases;\n\n\t\tif (this._server && \"setAliases\" in this._server) {\n\t\t\tthis._server.setAliases(aliases);\n\t\t}\n\t}\n\n\tget eleventyConfig() {\n\t\tif (!this.#eleventyConfig) {\n\t\t\tthrow new EleventyServeConfigError(\n\t\t\t\t\"You need to set the eleventyConfig property on EleventyServe.\",\n\t\t\t);\n\t\t}\n\n\t\treturn this.#eleventyConfig;\n\t}\n\n\tset eleventyConfig(config) {\n\t\tthis.#eleventyConfig = config;\n\n\t\tif (checkPassthroughCopyBehavior(this.#eleventyConfig.userConfig, \"serve\")) {\n\t\t\tthis.#eleventyConfig.userConfig.events.on(\"eleventy.passthrough\", ({ map }) => {\n\t\t\t\t// for-free passthrough copy\n\t\t\t\tthis.setAliases(map);\n\t\t\t});\n\t\t}\n\t}\n\n\t// TODO directorynorm\n\tsetOutputDir(outputDir) {\n\t\t// TODO check if this is different and if so, restart server (if already running)\n\t\t// This applies if you change the output directory in your config file during watch/serve\n\t\tthis.outputDir = outputDir;\n\t}\n\n\tstatic getDevServer() {\n\t\t// This happens on demand for performance purposes when not used by builds\n\t\t// https://github.com/11ty/eleventy/pull/3689\n\t\treturn import(\"@11ty/eleventy-dev-server\").then((i) => i.default);\n\t}\n\n\tasync getServerModule(name) {\n\t\ttry {\n\t\t\tif (!name || name === DEFAULT_SERVER_OPTIONS.module) {\n\t\t\t\treturn EleventyServe.getDevServer();\n\t\t\t}\n\n\t\t\t// Look for peer dep in local project\n\t\t\tlet projectNodeModulesPath = TemplatePath.absolutePath(\"./node_modules/\");\n\t\t\tlet serverPath = TemplatePath.absolutePath(projectNodeModulesPath, name);\n\t\t\t// No references outside of the project node_modules are allowed\n\t\t\tif (!serverPath.startsWith(projectNodeModulesPath)) {\n\t\t\t\tthrow new Error(\"Invalid node_modules name for Eleventy server instance, received:\" + name);\n\t\t\t}\n\n\t\t\tlet serverPackageJson = getModulePackageJson(serverPath);\n\t\t\t// Normalize with `main` entry from\n\t\t\tif (TemplatePath.isDirectorySync(serverPath)) {\n\t\t\t\tif (serverPackageJson.main) {\n\t\t\t\t\tserverPath = TemplatePath.absolutePath(\n\t\t\t\t\t\tprojectNodeModulesPath,\n\t\t\t\t\t\tname,\n\t\t\t\t\t\tserverPackageJson.main,\n\t\t\t\t\t);\n\t\t\t\t} else {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Eleventy server ${name} is missing a \\`main\\` entry in its package.json file. Traversed up from ${serverPath}.`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tlet module = await EleventyImport(serverPath);\n\n\t\t\tif (!(\"getServer\" in module)) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Eleventy server module requires a \\`getServer\\` static method. Could not find one on module: \\`${name}\\``,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tif (serverPackageJson[\"11ty\"]?.compatibility) {\n\t\t\t\ttry {\n\t\t\t\t\tthis.eleventyConfig.userConfig.versionCheck(serverPackageJson[\"11ty\"].compatibility);\n\t\t\t\t} catch (e) {\n\t\t\t\t\tthis.logger.warn(`Warning: \\`${name}\\` Plugin Compatibility: ${e.message}`);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn module;\n\t\t} catch (e) {\n\t\t\tthis.logger.error(\n\t\t\t\t\"There was an error with your custom Eleventy server. We’re using the default server instead.\\n\" +\n\t\t\t\t\te.message,\n\t\t\t);\n\t\t\tdebug(\"Eleventy server error %o\", e);\n\n\t\t\treturn EleventyServe.getDevServer();\n\t\t}\n\t}\n\n\tget options() {\n\t\tif (this._options) {\n\t\t\treturn this._options;\n\t\t}\n\n\t\tthis._options = Object.assign(\n\t\t\t{\n\t\t\t\tpathPrefix: PathPrefixer.normalizePathPrefix(this.config.pathPrefix),\n\t\t\t\tlogger: this.logger,\n\t\t\t},\n\t\t\tDEFAULT_SERVER_OPTIONS,\n\t\t\tthis.config.serverOptions,\n\t\t);\n\n\t\tthis.#savedConfigOptions = this.config.serverOptions;\n\n\t\tif (!this.#initOptionsFetched && this.getSetupCallback()) {\n\t\t\tthrow new Error(\n\t\t\t\t\"Init options have not yet been fetched in the setup callback. This probably means that `init()` has not yet been called.\",\n\t\t\t);\n\t\t}\n\n\t\treturn this._options;\n\t}\n\n\tget server() {\n\t\tif (!this._server) {\n\t\t\tthrow new Error(\"Missing server instance. Did you call .initServerInstance?\");\n\t\t}\n\n\t\treturn this._server;\n\t}\n\n\tasync initServerInstance() {\n\t\tif (this._server) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet serverModule = await this.getServerModule(this.options.module);\n\n\t\tif (this.options.module === DEFAULT_SERVER_OPTIONS.module) {\n\t\t\t// Fix for missing globs in Chokidar@4\n\t\t\tlet copyWatch = new Watch(this.eleventyConfig);\n\t\t\tcopyWatch.watchTargets(this.#watchTargets);\n\t\t\t// Careful with ignores here: https://github.com/11ty/eleventy/issues/1134\n\t\t\t// copyWatch.addIgnores();\n\n\t\t\tawait copyWatch.start();\n\n\t\t\tthis.#chokidar = copyWatch;\n\t\t\tthis.options.chokidar = copyWatch;\n\t\t}\n\n\t\t// Static method `getServer` was already checked in `getServerModule`\n\t\tthis._server = serverModule.getServer(\"eleventy-server\", this.outputDir, this.options);\n\n\t\tthis.setAliases(this.#aliases);\n\t}\n\n\tgetSetupCallback() {\n\t\tlet setupCallback = this.config.serverOptions.setup;\n\t\tif (setupCallback && typeof setupCallback === \"function\") {\n\t\t\treturn setupCallback;\n\t\t}\n\t}\n\n\tasync #init() {\n\t\tlet setupCallback = this.getSetupCallback();\n\t\tif (setupCallback) {\n\t\t\tlet opts = await setupCallback();\n\t\t\tthis.#initOptionsFetched = true;\n\n\t\t\tif (opts) {\n\t\t\t\tMerge(this.options, opts);\n\t\t\t}\n\t\t}\n\t}\n\n\tasync init() {\n\t\tif (!this._initPromise) {\n\t\t\tthis._initPromise = this.#init();\n\t\t}\n\n\t\treturn this._initPromise;\n\t}\n\n\t// Port comes in here from --port on the command line\n\tasync serve(port) {\n\t\tthis._commandLinePort = port;\n\n\t\tawait this.init();\n\t\tawait this.initServerInstance();\n\n\t\tthis.server.serve(port || this.options.port);\n\n\t\tif (typeof this.config.serverOptions?.ready === \"function\") {\n\t\t\tif (typeof this.server.ready === \"function\") {\n\t\t\t\t// Dev Server 2.0.7+\n\t\t\t\t// wait for ready promise to resolve before triggering ready callback\n\t\t\t\tawait this.server.ready();\n\t\t\t\tawait this.config.serverOptions?.ready(this.server);\n\t\t\t} else {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t\"The `ready` option in Eleventy’s `setServerOptions` method requires a `ready` function on the Dev Server instance. If you’re using Eleventy Dev Server, you will need Dev Server 2.0.7+ or newer to use this feature.\",\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\tasync close() {\n\t\tif (this._server) {\n\t\t\tawait this._server.close();\n\n\t\t\tthis._server = undefined;\n\t\t}\n\t}\n\n\tasync sendError({ error }) {\n\t\tif (this._server) {\n\t\t\tawait this.server.sendError({\n\t\t\t\terror,\n\t\t\t});\n\t\t}\n\t}\n\n\t// when the configuration file changes (but server options *may* not, which would otherwise trigger restart())\n\tresetConfig() {\n\t\tthis.#watchTargets = new Set();\n\t}\n\n\t// Restart the server entirely\n\t// We don’t want to use a native `restart` method (e.g. restart() in Vite) so that\n\t// we can correctly handle a `module` property change (changing the server type)\n\tasync restart() {\n\t\t// Blow away cached options\n\t\tdelete this._options;\n\n\t\tawait this.close();\n\n\t\t// saved --port in `serve()`\n\t\tawait this.serve(this._commandLinePort);\n\t}\n\n\t// checkPassthroughCopyBehavior check is called upstream in Eleventy.js\n\twatchPassthroughCopy(globs = []) {\n\t\tfor (let glob of globs) {\n\t\t\tthis.#watchTargets.add(glob);\n\t\t}\n\n\t\t// if the watcher has already started, add the targets\n\t\tif (this.#chokidar) {\n\t\t\tthis.#chokidar.watchTargets(globs);\n\t\t}\n\t}\n\n\tisEmulatedPassthroughCopyMatch(filepath) {\n\t\treturn isGlobMatch(filepath, Array.from(this.#watchTargets));\n\t}\n\n\thasOptionsChanged() {\n\t\treturn (\n\t\t\tstringifyOptions(this.config.serverOptions) !== stringifyOptions(this.#savedConfigOptions)\n\t\t);\n\t}\n\n\t// Live reload the server\n\tasync reload(reloadEvent = {}) {\n\t\tif (!this._server) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Restart the server if the options have changed\n\t\tif (this.hasOptionsChanged()) {\n\t\t\tdebug(\"Server options changed, we’re restarting the server\");\n\t\t\tawait this.restart();\n\t\t} else {\n\t\t\tawait this.server.reload(reloadEvent);\n\t\t}\n\t}\n}\n\nexport default EleventyServe;\n"
  },
  {
    "path": "src/Engines/Custom.js",
    "content": "import TemplateEngine from \"./TemplateEngine.js\";\nimport getJavaScriptData from \"../Util/GetJavaScriptData.js\";\n\nexport default class CustomEngine extends TemplateEngine {\n\tconstructor(name, eleventyConfig) {\n\t\tsuper(name, eleventyConfig);\n\n\t\tthis.entry = this.getExtensionMapEntry();\n\t\tthis.needsInit = \"init\" in this.entry && typeof this.entry.init === \"function\";\n\n\t\tthis.setDefaultEngine(undefined);\n\t}\n\n\tgetExtensionMapEntry() {\n\t\tif (\"extensionMap\" in this.config) {\n\t\t\tlet name = this.name.toLowerCase();\n\t\t\t// Iterates over only the user config `addExtension` entries\n\t\t\tfor (let entry of this.config.extensionMap) {\n\t\t\t\tlet entryKey = (entry.aliasKey || entry.key || \"\").toLowerCase();\n\t\t\t\tif (entryKey === name) {\n\t\t\t\t\treturn entry;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tthrow Error(\n\t\t\t`Could not find a custom extension for ${this.name}. Did you add it to your config file?`,\n\t\t);\n\t}\n\n\tsetDefaultEngine(defaultEngine) {\n\t\tthis._defaultEngine = defaultEngine;\n\t}\n\n\tget cacheable() {\n\t\t// Enable cacheability for this template\n\t\tif (this.entry?.compileOptions?.cache !== undefined) {\n\t\t\treturn this.entry.compileOptions.cache;\n\t\t} else if (this.needsToReadFileContents()) {\n\t\t\treturn true;\n\t\t} else if (this._defaultEngine?.cacheable !== undefined) {\n\t\t\treturn this._defaultEngine.cacheable;\n\t\t}\n\n\t\treturn super.cacheable;\n\t}\n\n\tasync getInstanceFromInputPath(inputPath) {\n\t\tif (\n\t\t\t\"getInstanceFromInputPath\" in this.entry &&\n\t\t\ttypeof this.entry.getInstanceFromInputPath === \"function\"\n\t\t) {\n\t\t\t// returns Promise\n\t\t\treturn this.entry.getInstanceFromInputPath(inputPath);\n\t\t}\n\n\t\t// aliased upstream type\n\t\tif (\n\t\t\tthis._defaultEngine &&\n\t\t\t\"getInstanceFromInputPath\" in this._defaultEngine &&\n\t\t\ttypeof this._defaultEngine.getInstanceFromInputPath === \"function\"\n\t\t) {\n\t\t\t// returns Promise\n\t\t\treturn this._defaultEngine.getInstanceFromInputPath(inputPath);\n\t\t}\n\n\t\treturn false;\n\t}\n\n\t/**\n\t * Whether to use the module loader directly\n\t *\n\t * @override\n\t */\n\tuseJavaScriptImport() {\n\t\tif (\"useJavaScriptImport\" in this.entry) {\n\t\t\treturn this.entry.useJavaScriptImport;\n\t\t}\n\n\t\tif (\n\t\t\tthis._defaultEngine &&\n\t\t\t\"useJavaScriptImport\" in this._defaultEngine &&\n\t\t\ttypeof this._defaultEngine.useJavaScriptImport === \"function\"\n\t\t) {\n\t\t\treturn this._defaultEngine.useJavaScriptImport();\n\t\t}\n\n\t\treturn false;\n\t}\n\n\t/**\n\t * @override\n\t */\n\tneedsToReadFileContents() {\n\t\tif (\"read\" in this.entry) {\n\t\t\treturn this.entry.read;\n\t\t}\n\n\t\t// Handle aliases to `11ty.js` templates, avoid reading files in the alias, see #2279\n\t\t// Here, we are short circuiting fallback to defaultRenderer, does not account for compile\n\t\t// functions that call defaultRenderer explicitly\n\t\tif (this._defaultEngine && \"needsToReadFileContents\" in this._defaultEngine) {\n\t\t\treturn this._defaultEngine.needsToReadFileContents();\n\t\t}\n\n\t\treturn true;\n\t}\n\n\t// If we init from multiple places, wait for the first init to finish before continuing on.\n\tasync _runningInit() {\n\t\tif (this.needsInit) {\n\t\t\tif (!this._initing) {\n\t\t\t\tthis._initBench = this.benchmarks.aggregate.get(`Engine (${this.name}) Init`);\n\t\t\t\tthis._initBench.before();\n\t\t\t\tthis._initing = this.entry.init.bind({\n\t\t\t\t\tconfig: this.config,\n\t\t\t\t\tbench: this.benchmarks.aggregate,\n\t\t\t\t})();\n\t\t\t}\n\t\t\tawait this._initing;\n\t\t\tthis.needsInit = false;\n\n\t\t\tif (this._initBench) {\n\t\t\t\tthis._initBench.after();\n\t\t\t\tthis._initBench = undefined;\n\t\t\t}\n\t\t}\n\t}\n\n\tasync getExtraDataFromFile(inputPath) {\n\t\tif (this.entry.getData === false) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (!(\"getData\" in this.entry)) {\n\t\t\t// Handle aliases to `11ty.js` templates, use upstream default engine data fetch, see #2279\n\t\t\tif (this._defaultEngine && \"getExtraDataFromFile\" in this._defaultEngine) {\n\t\t\t\treturn this._defaultEngine.getExtraDataFromFile(inputPath);\n\t\t\t}\n\n\t\t\treturn;\n\t\t}\n\n\t\tawait this._runningInit();\n\n\t\tif (typeof this.entry.getData === \"function\") {\n\t\t\tlet dataBench = this.benchmarks.aggregate.get(\n\t\t\t\t`Engine (${this.name}) Get Data From File (Function)`,\n\t\t\t);\n\t\t\tdataBench.before();\n\t\t\tlet data = this.entry.getData(inputPath);\n\t\t\tdataBench.after();\n\t\t\treturn data;\n\t\t}\n\n\t\tlet keys = new Set();\n\t\tif (this.entry.getData === true) {\n\t\t\tkeys.add(\"data\");\n\t\t} else if (Array.isArray(this.entry.getData)) {\n\t\t\tfor (let key of this.entry.getData) {\n\t\t\t\tkeys.add(key);\n\t\t\t}\n\t\t}\n\n\t\tlet dataBench = this.benchmarks.aggregate.get(`Engine (${this.name}) Get Data From File`);\n\t\tdataBench.before();\n\n\t\tlet inst = await this.getInstanceFromInputPath(inputPath);\n\n\t\tif (inst === false) {\n\t\t\tdataBench.after();\n\n\t\t\treturn Promise.reject(\n\t\t\t\tnew Error(\n\t\t\t\t\t`\\`getInstanceFromInputPath\\` callback missing from '${this.name}' template engine plugin. It is required when \\`getData\\` is in use. You can set \\`getData: false\\` to opt-out of this.`,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\n\t\t// override keys set at the plugin level in the individual template\n\t\tif (inst.eleventyDataKey) {\n\t\t\tkeys = new Set(inst.eleventyDataKey);\n\t\t}\n\n\t\tlet mixins;\n\t\tif (this.config) {\n\t\t\t// Object.assign usage: see TemplateRenderCustomTest.js: `JavaScript functions should not be mutable but not *that* mutable`\n\t\t\tmixins = Object.assign({}, this.config.javascriptFunctions);\n\t\t}\n\n\t\tlet promises = [];\n\t\tfor (let key of keys) {\n\t\t\tpromises.push(\n\t\t\t\tgetJavaScriptData(inst, inputPath, key, {\n\t\t\t\t\tmixins,\n\t\t\t\t\tisObjectRequired: key === \"data\",\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\n\t\tlet results = await Promise.all(promises);\n\t\tlet data = {};\n\t\tfor (let result of results) {\n\t\t\tObject.assign(data, result);\n\t\t}\n\t\tdataBench.after();\n\n\t\treturn data;\n\t}\n\n\tasync compile(str, inputPath, ...args) {\n\t\tawait this._runningInit();\n\t\tlet defaultCompilationFn;\n\t\tif (this._defaultEngine) {\n\t\t\tdefaultCompilationFn = async (data) => {\n\t\t\t\tconst renderFn = await this._defaultEngine.compile(str, inputPath, ...args);\n\t\t\t\treturn renderFn(data);\n\t\t\t};\n\t\t}\n\n\t\t// Fall back to default compiler if the user does not provide their own\n\t\tif (!this.entry.compile) {\n\t\t\tif (defaultCompilationFn) {\n\t\t\t\treturn defaultCompilationFn;\n\t\t\t} else {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Missing \\`compile\\` property for custom template syntax definition eleventyConfig.addExtension(\"${this.name}\"). This is not necessary when aliasing to an existing template syntax.`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\t// TODO generalize this (look at JavaScript.js)\n\t\tlet compiledFn = this.entry.compile.bind({\n\t\t\tconfig: this.config,\n\t\t\taddDependencies: (from, toArray = []) => {\n\t\t\t\tthis.config.uses.addDependency(from, toArray);\n\t\t\t},\n\t\t\tdefaultRenderer: defaultCompilationFn, // bind defaultRenderer to compile function\n\t\t})(str, inputPath);\n\n\t\t// Support `undefined` to skip compile/render\n\t\tif (compiledFn) {\n\t\t\t// Bind defaultRenderer to render function\n\t\t\tif (\"then\" in compiledFn && typeof compiledFn.then === \"function\") {\n\t\t\t\t// Promise, wait to bind\n\t\t\t\treturn compiledFn.then((fn) => {\n\t\t\t\t\tif (typeof fn === \"function\") {\n\t\t\t\t\t\treturn fn.bind({\n\t\t\t\t\t\t\tdefaultRenderer: defaultCompilationFn,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\treturn fn;\n\t\t\t\t});\n\t\t\t} else if (\"bind\" in compiledFn && typeof compiledFn.bind === \"function\") {\n\t\t\t\treturn compiledFn.bind({\n\t\t\t\t\tdefaultRenderer: defaultCompilationFn,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\treturn compiledFn;\n\t}\n\n\tget defaultTemplateFileExtension() {\n\t\treturn this.entry.outputFileExtension ?? \"html\";\n\t}\n\n\t// Whether or not to wrap in Eleventy layouts\n\tuseLayouts() {\n\t\t// TODO future change fallback to `this.defaultTemplateFileExtension === \"html\"`\n\t\treturn this.entry.useLayouts ?? true;\n\t}\n\n\thasDependencies(inputPath) {\n\t\tif (this.config.uses.getDependencies(inputPath) === false) {\n\t\t\treturn false;\n\t\t}\n\t\treturn true;\n\t}\n\n\tisFileRelevantTo(inputPath, comparisonFile, includeLayouts) {\n\t\treturn this.config.uses.isFileRelevantTo(inputPath, comparisonFile, includeLayouts);\n\t}\n\n\tgetCompileCacheKey(str, inputPath) {\n\t\tlet lastModifiedFile = this.eleventyConfig.getPreviousBuildModifiedFile();\n\t\t// Return this separately so we know whether or not to use the cached version\n\t\t// but still return a key to cache this new render for next time\n\t\tlet isRelevant = this.isFileRelevantTo(inputPath, lastModifiedFile, false);\n\t\tlet useCache = !isRelevant;\n\n\t\tif (this.entry.compileOptions && \"getCacheKey\" in this.entry.compileOptions) {\n\t\t\tif (typeof this.entry.compileOptions.getCacheKey !== \"function\") {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`\\`compileOptions.getCacheKey\\` must be a function in addExtension for the ${this.name} type`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tuseCache,\n\t\t\t\tkey: this.entry.compileOptions.getCacheKey(str, inputPath),\n\t\t\t};\n\t\t}\n\n\t\tlet { key } = super.getCompileCacheKey(str, inputPath);\n\t\treturn {\n\t\t\tuseCache,\n\t\t\tkey,\n\t\t};\n\t}\n\n\tpermalinkNeedsCompilation(/*str*/) {\n\t\tif (this.entry.compileOptions && \"permalink\" in this.entry.compileOptions) {\n\t\t\tlet p = this.entry.compileOptions.permalink;\n\t\t\tif (p === \"raw\") {\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\t// permalink: false is aliased to permalink: () => false\n\t\t\tif (p === false) {\n\t\t\t\treturn () => false;\n\t\t\t}\n\n\t\t\treturn this.entry.compileOptions.permalink;\n\t\t}\n\n\t\t// Breaking: default changed from `true` to `false` in 3.0.0-alpha.13\n\t\t// Note: `false` is the same as \"raw\" here.\n\t\treturn false;\n\t}\n\n\tstatic shouldSpiderJavaScriptDependencies(entry) {\n\t\tif (entry.compileOptions && \"spiderJavaScriptDependencies\" in entry.compileOptions) {\n\t\t\treturn entry.compileOptions.spiderJavaScriptDependencies;\n\t\t}\n\n\t\treturn false;\n\t}\n}\n"
  },
  {
    "path": "src/Engines/FrontMatter/JavaScript.js",
    "content": "import { RetrieveGlobals } from \"../../Util/RetrieveGlobals.js\";\n\n// `javascript` Front Matter Type\nexport default function (frontMatterCode, context = {}) {\n\tlet { filePath } = context;\n\n\t// Legacy `javascript` type was removed in @11ty/gray-matter@2 to avoid eval()\n\tlet trimmed = frontMatterCode.trimStart();\n\tif (trimmed.startsWith(\"{\")) {\n\t\treturn RetrieveGlobals(`export default ${trimmed}`, filePath, {\n\t\t\tisJavaScriptFrontMatterCompat: true, // turns off implicit exports\n\t\t}).then((res) => {\n\t\t\treturn res.default;\n\t\t});\n\t}\n\n\treturn RetrieveGlobals(frontMatterCode, filePath);\n}\n"
  },
  {
    "path": "src/Engines/Html.js",
    "content": "import TemplateEngine from \"./TemplateEngine.js\";\n\nexport default class Html extends TemplateEngine {\n\tconstructor(name, eleventyConfig) {\n\t\tsuper(name, eleventyConfig);\n\t}\n\n\tget cacheable() {\n\t\treturn true;\n\t}\n\n\tasync #getPreEngine(preTemplateEngine) {\n\t\treturn this.engineManager.getEngine(preTemplateEngine, this.extensionMap);\n\t}\n\n\tasync compile(str, inputPath, preTemplateEngine) {\n\t\tif (preTemplateEngine) {\n\t\t\tlet engine = await this.#getPreEngine(preTemplateEngine);\n\t\t\tlet fnReady = engine.compile(str, inputPath);\n\n\t\t\treturn async function (data) {\n\t\t\t\tlet fn = await fnReady;\n\n\t\t\t\treturn fn(data);\n\t\t\t};\n\t\t}\n\n\t\treturn function () {\n\t\t\t// do nothing with data if preTemplateEngine is falsy\n\t\t\treturn str;\n\t\t};\n\t}\n}\n"
  },
  {
    "path": "src/Engines/JavaScript.js",
    "content": "import { TemplatePath, isPlainObject } from \"@11ty/eleventy-utils\";\n\nimport TemplateEngine from \"./TemplateEngine.js\";\nimport EleventyBaseError from \"../Errors/EleventyBaseError.js\";\nimport getJavaScriptData from \"../Util/GetJavaScriptData.js\";\nimport { EleventyImport } from \"../Util/Require.js\";\nimport { augmentFunction, augmentObject } from \"./Util/ContextAugmenter.js\";\n\nclass JavaScriptTemplateNotDefined extends EleventyBaseError {}\n\nexport default class JavaScript extends TemplateEngine {\n\tconstructor(name, templateConfig) {\n\t\tsuper(name, templateConfig);\n\t\tthis.instances = {};\n\n\t\tthis.config.events.on(\"eleventy#templateModified\", (inputPath, metadata = {}) => {\n\t\t\tlet { usedByDependants, relevantLayouts } = metadata;\n\t\t\t// Remove from cached instances when modified\n\t\t\tlet instancesToDelete = [\n\t\t\t\tinputPath,\n\t\t\t\t...(usedByDependants || []),\n\t\t\t\t...(relevantLayouts || []),\n\t\t\t].map((entry) => TemplatePath.addLeadingDotSlash(entry));\n\t\t\tfor (let inputPath of instancesToDelete) {\n\t\t\t\tif (inputPath in this.instances) {\n\t\t\t\t\tdelete this.instances[inputPath];\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t}\n\n\tget cacheable() {\n\t\treturn false;\n\t}\n\n\tnormalize(result) {\n\t\tif (Buffer.isBuffer(result)) {\n\t\t\treturn result.toString();\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t// String, Buffer, Promise\n\t// Function, Class\n\t// Object\n\t// Module\n\t_getInstance(mod) {\n\t\tlet noop = function () {\n\t\t\treturn \"\";\n\t\t};\n\n\t\tlet originalModData = mod?.data;\n\n\t\tif (typeof mod === \"object\" && mod.default && this.eleventyConfig.getIsProjectUsingEsm()) {\n\t\t\tmod = mod.default;\n\t\t}\n\n\t\tif (typeof mod === \"string\" || mod instanceof Buffer || mod.then) {\n\t\t\treturn { render: () => mod };\n\t\t} else if (typeof mod === \"function\") {\n\t\t\tif (mod.prototype?.data || mod.prototype?.render) {\n\t\t\t\tif (!(\"render\" in mod.prototype)) {\n\t\t\t\t\tmod.prototype.render = noop;\n\t\t\t\t}\n\n\t\t\t\tif (!(\"data\" in mod.prototype) && !mod.data && originalModData) {\n\t\t\t\t\tmod.prototype.data = originalModData;\n\t\t\t\t}\n\n\t\t\t\treturn new mod();\n\t\t\t} else {\n\t\t\t\t// JavaScript lol\n\t\t\t\tif (mod.toString().trimStart().startsWith(\"class \")) {\n\t\t\t\t\t// we already know that mod.prototype.data and mod.prototype.render do not exist (per above)\n\t\t\t\t\t// now we look for instance properties for `data` or `render`, issue #1645\n\t\t\t\t\tlet modInstance = new mod();\n\t\t\t\t\tif (Object.hasOwn(modInstance, \"data\") || Object.hasOwn(modInstance, \"render\")) {\n\t\t\t\t\t\tif (!Object.hasOwn(modInstance, \"render\")) {\n\t\t\t\t\t\t\tmod.prototype.render = noop;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (!Object.hasOwn(modInstance, \"data\") && !mod.data && originalModData) {\n\t\t\t\t\t\t\tmod.prototype.data = originalModData;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn modInstance;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t\"Invalid class signature for an 11ty.js template: needs a render or data instance property.\",\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn {\n\t\t\t\t\t...(originalModData ? { data: originalModData } : undefined),\n\t\t\t\t\trender: mod,\n\t\t\t\t};\n\t\t\t}\n\t\t} else if (\"data\" in mod || \"render\" in mod) {\n\t\t\tif (!mod.render) {\n\t\t\t\tmod.render = noop;\n\t\t\t}\n\t\t\tif (!mod.data && originalModData) {\n\t\t\t\tmod.data = originalModData;\n\t\t\t}\n\t\t\treturn mod;\n\t\t}\n\t}\n\n\tasync #getInstanceFromInputPath(inputPath) {\n\t\tlet mod;\n\t\tlet relativeInputPath =\n\t\t\tthis.eleventyConfig.directories.getInputPathRelativeToInputDirectory(inputPath);\n\t\tif (this.eleventyConfig.userConfig.isVirtualTemplate(relativeInputPath)) {\n\t\t\tmod = this.eleventyConfig.userConfig.virtualTemplates[relativeInputPath].content;\n\t\t} else {\n\t\t\tlet isEsm = this.eleventyConfig.getIsProjectUsingEsm();\n\t\t\tlet cacheBust = !this.cacheable || !this.config.useTemplateCache;\n\t\t\tmod = await EleventyImport(inputPath, isEsm ? \"esm\" : \"cjs\", {\n\t\t\t\tcacheBust,\n\t\t\t});\n\t\t}\n\n\t\tlet inst = this._getInstance(mod);\n\t\tif (inst) {\n\t\t\tthis.instances[inputPath] = inst;\n\t\t} else {\n\t\t\tthrow new JavaScriptTemplateNotDefined(\n\t\t\t\t`No JavaScript template returned from ${inputPath}. Did you assign module.exports (CommonJS) or export (ESM)?`,\n\t\t\t);\n\t\t}\n\t\treturn inst;\n\t}\n\n\tasync getInstanceFromInputPath(inputPath) {\n\t\tif (!this.instances[inputPath]) {\n\t\t\tthis.instances[inputPath] = this.#getInstanceFromInputPath(inputPath);\n\t\t}\n\n\t\treturn this.instances[inputPath];\n\t}\n\n\t/**\n\t * JavaScript files defer to the module loader rather than read the files to strings\n\t *\n\t * @override\n\t */\n\tneedsToReadFileContents() {\n\t\treturn false;\n\t}\n\n\t/**\n\t * Use the module loader directly\n\t *\n\t * @override\n\t */\n\tuseJavaScriptImport() {\n\t\treturn true;\n\t}\n\n\tasync getExtraDataFromFile(inputPath) {\n\t\tlet inst = await this.getInstanceFromInputPath(inputPath);\n\t\treturn getJavaScriptData(inst, inputPath);\n\t}\n\n\tgetJavaScriptFunctions(inst) {\n\t\tlet fns = {};\n\t\tlet configFns = this.config.javascriptFunctions;\n\n\t\tfor (let key in configFns) {\n\t\t\t// prefer pre-existing `page` javascriptFunction, if one exists\n\t\t\tfns[key] = augmentFunction(configFns[key], {\n\t\t\t\tsource: inst,\n\t\t\t\toverwrite: false,\n\t\t\t});\n\t\t}\n\t\treturn fns;\n\t}\n\n\t// Backwards compat\n\tstatic wrapJavaScriptFunction(inst, fn) {\n\t\treturn augmentFunction(fn, {\n\t\t\tsource: inst,\n\t\t});\n\t}\n\n\taddExportsToBundles(inst, url) {\n\t\tlet cfg = this.eleventyConfig.userConfig;\n\t\tif (!(\"getBundleManagers\" in cfg)) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet managers = cfg.getBundleManagers();\n\t\tfor (let name in managers) {\n\t\t\tlet mgr = managers[name];\n\t\t\tlet key = mgr.getBundleExportKey();\n\t\t\tif (!key) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (typeof inst[key] === \"string\") {\n\t\t\t\t// export const css = ``;\n\t\t\t\tmgr.addToPage(url, inst[key]);\n\t\t\t} else if (isPlainObject(inst[key])) {\n\t\t\t\tif (typeof inst[key][name] === \"string\") {\n\t\t\t\t\t// Object with bundle names:\n\t\t\t\t\t// export const bundle = {\n\t\t\t\t\t//   css: ``\n\t\t\t\t\t// };\n\t\t\t\t\tmgr.addToPage(url, inst[key][name]);\n\t\t\t\t} else if (isPlainObject(inst[key][name])) {\n\t\t\t\t\t// Object with bucket names:\n\t\t\t\t\t// export const bundle = {\n\t\t\t\t\t//   css: {\n\t\t\t\t\t//     default: ``\n\t\t\t\t\t//   }\n\t\t\t\t\t// };\n\t\t\t\t\tfor (let bucketName in inst[key][name]) {\n\t\t\t\t\t\tmgr.addToPage(url, inst[key][name][bucketName], bucketName);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tasync compile(str, inputPath) {\n\t\tlet inst;\n\t\tif (str) {\n\t\t\t// When str has a value, it's being used for permalinks in data\n\t\t\tinst = this._getInstance(str);\n\t\t} else {\n\t\t\t// For normal templates, str will be falsy.\n\t\t\tinst = await this.getInstanceFromInputPath(inputPath);\n\t\t}\n\n\t\tif (inst?.render) {\n\t\t\treturn (data = {}) => {\n\t\t\t\t// TODO does this do anything meaningful for non-classes?\n\t\t\t\t// `inst` should have a normalized `render` function from _getInstance\n\n\t\t\t\t// Map exports to bundles\n\t\t\t\tif (data.page?.url) {\n\t\t\t\t\tthis.addExportsToBundles(inst, data.page.url);\n\t\t\t\t}\n\n\t\t\t\taugmentObject(inst, {\n\t\t\t\t\tsource: data,\n\t\t\t\t\toverwrite: false,\n\t\t\t\t});\n\n\t\t\t\tObject.assign(inst, this.getJavaScriptFunctions(inst));\n\n\t\t\t\treturn this.normalize(inst.render.call(inst, data));\n\t\t\t};\n\t\t}\n\t}\n\n\tstatic shouldSpiderJavaScriptDependencies() {\n\t\treturn true;\n\t}\n}\n"
  },
  {
    "path": "src/Engines/Liquid.js",
    "content": "import moo from \"moo\";\nimport { Tokenizer, TokenKind, evalToken, Liquid as LiquidJs } from \"liquidjs\";\nimport { TemplatePath } from \"@11ty/eleventy-utils\";\n// import debugUtil from \"debug\";\n\nimport TemplateEngine from \"./TemplateEngine.js\";\nimport { augmentObject } from \"./Util/ContextAugmenter.js\";\n\n// const debug = debugUtil(\"Eleventy:Liquid\");\n\nexport default class Liquid extends TemplateEngine {\n\tstatic argumentLexerOptions = {\n\t\tnumber: /[0-9]+\\.*[0-9]*/,\n\t\tdoubleQuoteString: /\"(?:\\\\[\"\\\\]|[^\\n\"\\\\])*\"/,\n\t\tsingleQuoteString: /'(?:\\\\['\\\\]|[^\\n'\\\\])*'/,\n\t\tkeyword: /[a-zA-Z0-9.\\-_]+/,\n\t\t\"ignore:whitespace\": /[, \\t]+/, // includes comma separator\n\t};\n\n\tconstructor(name, eleventyConfig) {\n\t\tsuper(name, eleventyConfig);\n\n\t\tthis.liquidOptions = this.config.liquidOptions || {};\n\n\t\tthis.setLibrary(this.config.libraryOverrides.liquid);\n\n\t\tthis.argLexer = moo.compile(Liquid.argumentLexerOptions);\n\t}\n\n\tget cacheable() {\n\t\treturn true;\n\t}\n\n\tsetLibrary(override) {\n\t\t// warning, the include syntax supported here does not exactly match what Jekyll uses.\n\t\tthis.liquidLib = override || new LiquidJs(this.getLiquidOptions());\n\t\tthis.setEngineLib(this.liquidLib, Boolean(this.config.libraryOverrides.liquid));\n\n\t\tthis.addFilters(this.config.liquidFilters);\n\n\t\t// TODO these all go to the same place (addTag), add warnings for overwrites\n\t\tthis.addCustomTags(this.config.liquidTags);\n\t\tthis.addAllShortcodes(this.config.liquidShortcodes);\n\t\tthis.addAllPairedShortcodes(this.config.liquidPairedShortcodes);\n\t}\n\n\tgetLiquidOptions() {\n\t\tlet defaults = {\n\t\t\troot: [this.dirs.includes, this.dirs.input], // supplemented in compile with inputPath below\n\t\t\textname: \".liquid\",\n\t\t\tstrictFilters: true,\n\t\t\t// cache: true,\n\t\t\tjsTruthy: true, // Breaking in v4 #3507\n\t\t};\n\n\t\tlet options = Object.assign(defaults, this.liquidOptions || {});\n\t\t// debug(\"Liquid constructor options: %o\", options);\n\n\t\treturn options;\n\t}\n\n\tstatic wrapFilter(name, fn) {\n\t\t/**\n\t\t * @this {object}\n\t\t */\n\t\treturn function (...args) {\n\t\t\t// Set this.eleventy and this.page\n\t\t\tif (typeof this.context?.get === \"function\") {\n\t\t\t\taugmentObject(this, {\n\t\t\t\t\tsource: this.context,\n\t\t\t\t\tgetter: (key, context) => context.get([key]),\n\n\t\t\t\t\tlazy: this.context.strictVariables,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// We *don’t* wrap this in an EleventyFilterError because Liquid has a better error message with line/column information in the template\n\t\t\treturn fn.call(this, ...args);\n\t\t};\n\t}\n\n\t// Shortcodes\n\tstatic normalizeScope(context) {\n\t\tlet obj = {};\n\t\tif (context) {\n\t\t\tobj.ctx = context; // Full context available on `ctx`\n\n\t\t\t// Set this.eleventy and this.page\n\t\t\taugmentObject(obj, {\n\t\t\t\tsource: context,\n\t\t\t\tgetter: (key, context) => context.get([key]),\n\t\t\t\tlazy: context.strictVariables,\n\t\t\t});\n\t\t}\n\n\t\treturn obj;\n\t}\n\n\taddCustomTags(tags) {\n\t\tfor (let name in tags) {\n\t\t\tthis.addTag(name, tags[name]);\n\t\t}\n\t}\n\n\taddFilters(filters) {\n\t\tfor (let name in filters) {\n\t\t\tthis.addFilter(name, filters[name]);\n\t\t}\n\t}\n\n\taddFilter(name, filter) {\n\t\tthis.liquidLib.registerFilter(name, Liquid.wrapFilter(name, filter));\n\t}\n\n\taddTag(name, tagFn) {\n\t\tlet tagObj;\n\t\tif (typeof tagFn === \"function\") {\n\t\t\ttagObj = tagFn(this.liquidLib, { evalToken });\n\t\t} else {\n\t\t\tthrow new Error(\n\t\t\t\t\"Liquid.addTag expects a callback function to be passed in: addTag(name, function(liquidEngine) { return { parse: …, render: … } })\",\n\t\t\t);\n\t\t}\n\t\tthis.liquidLib.registerTag(name, tagObj);\n\t}\n\n\taddAllShortcodes(shortcodes) {\n\t\tfor (let name in shortcodes) {\n\t\t\tthis.addShortcode(name, shortcodes[name]);\n\t\t}\n\t}\n\n\taddAllPairedShortcodes(shortcodes) {\n\t\tfor (let name in shortcodes) {\n\t\t\tthis.addPairedShortcode(name, shortcodes[name]);\n\t\t}\n\t}\n\n\tstatic parseArguments(lexer, str) {\n\t\tlet argArray = [];\n\n\t\tif (!lexer) {\n\t\t\tlexer = moo.compile(Liquid.argumentLexerOptions);\n\t\t}\n\n\t\tif (typeof str === \"string\") {\n\t\t\tlexer.reset(str);\n\n\t\t\tlet arg = lexer.next();\n\t\t\twhile (arg) {\n\t\t\t\t/*{\n\t\t\t\t\ttype: 'doubleQuoteString',\n\t\t\t\t\tvalue: '\"test 2\"',\n\t\t\t\t\ttext: '\"test 2\"',\n\t\t\t\t\ttoString: [Function: tokenToString],\n\t\t\t\t\toffset: 0,\n\t\t\t\t\tlineBreaks: 0,\n\t\t\t\t\tline: 1,\n\t\t\t\t\tcol: 1 }*/\n\t\t\t\tif (arg.type.indexOf(\"ignore:\") === -1) {\n\t\t\t\t\t// Push the promise into an array instead of awaiting it here.\n\t\t\t\t\t// This forces the promises to run in order with the correct scope value for each arg.\n\t\t\t\t\t// Otherwise they run out of order and can lead to undefined values for arguments in layout template shortcodes.\n\t\t\t\t\t// console.log( arg.value, scope, engine );\n\t\t\t\t\targArray.push(arg.value);\n\t\t\t\t}\n\t\t\t\targ = lexer.next();\n\t\t\t}\n\t\t}\n\n\t\treturn argArray;\n\t}\n\n\tstatic parseArgumentsBuiltin(args) {\n\t\tlet tokenizer = new Tokenizer(args);\n\t\tlet parsedArgs = [];\n\n\t\tlet value = tokenizer.readValue();\n\t\twhile (value) {\n\t\t\tparsedArgs.push(value);\n\t\t\ttokenizer.skipBlank();\n\t\t\tif (tokenizer.peek() === \",\") {\n\t\t\t\ttokenizer.advance();\n\t\t\t}\n\t\t\tvalue = tokenizer.readValue();\n\t\t}\n\t\ttokenizer.end();\n\n\t\treturn parsedArgs;\n\t}\n\n\taddShortcode(shortcodeName, shortcodeFn) {\n\t\tlet _t = this;\n\t\tthis.addTag(shortcodeName, function (liquidEngine) {\n\t\t\treturn {\n\t\t\t\tparse(tagToken) {\n\t\t\t\t\tthis.name = tagToken.name;\n\t\t\t\t\tif (_t.config.liquidParameterParsing === \"builtin\") {\n\t\t\t\t\t\tthis.orderedArgs = Liquid.parseArgumentsBuiltin(tagToken.args);\n\t\t\t\t\t\t// note that Liquid does have a Hash class for name-based argument parsing but offers no easy to support both modes in one class\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.legacyArgs = tagToken.args;\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\trender: function* (ctx) {\n\t\t\t\t\tlet argArray = [];\n\n\t\t\t\t\tif (this.legacyArgs) {\n\t\t\t\t\t\tlet rawArgs = Liquid.parseArguments(_t.argLexer, this.legacyArgs);\n\t\t\t\t\t\tfor (let arg of rawArgs) {\n\t\t\t\t\t\t\tlet b = yield liquidEngine.evalValue(arg, ctx);\n\t\t\t\t\t\t\targArray.push(b);\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (this.orderedArgs) {\n\t\t\t\t\t\tfor (let arg of this.orderedArgs) {\n\t\t\t\t\t\t\tlet b = yield evalToken(arg, ctx);\n\t\t\t\t\t\t\targArray.push(b);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tlet ret = yield shortcodeFn.call(Liquid.normalizeScope(ctx), ...argArray);\n\t\t\t\t\treturn ret;\n\t\t\t\t},\n\t\t\t};\n\t\t});\n\t}\n\n\taddPairedShortcode(shortcodeName, shortcodeFn) {\n\t\tlet _t = this;\n\t\tthis.addTag(shortcodeName, function (liquidEngine) {\n\t\t\treturn {\n\t\t\t\tparse(tagToken, remainTokens) {\n\t\t\t\t\tthis.name = tagToken.name;\n\n\t\t\t\t\tif (_t.config.liquidParameterParsing === \"builtin\") {\n\t\t\t\t\t\tthis.orderedArgs = Liquid.parseArgumentsBuiltin(tagToken.args);\n\t\t\t\t\t\t// note that Liquid does have a Hash class for name-based argument parsing but offers no easy to support both modes in one class\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.legacyArgs = tagToken.args;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.templates = [];\n\n\t\t\t\t\tvar stream = liquidEngine.parser\n\t\t\t\t\t\t.parseStream(remainTokens)\n\t\t\t\t\t\t.on(\"template\", (tpl) => this.templates.push(tpl))\n\t\t\t\t\t\t.on(\"tag:end\" + shortcodeName, () => stream.stop())\n\t\t\t\t\t\t.on(\"end\", () => {\n\t\t\t\t\t\t\tthrow new Error(`tag ${tagToken.raw} not closed`);\n\t\t\t\t\t\t});\n\n\t\t\t\t\tstream.start();\n\t\t\t\t},\n\t\t\t\trender: function* (ctx /*, emitter*/) {\n\t\t\t\t\tlet argArray = [];\n\t\t\t\t\tif (this.legacyArgs) {\n\t\t\t\t\t\tlet rawArgs = Liquid.parseArguments(_t.argLexer, this.legacyArgs);\n\t\t\t\t\t\tfor (let arg of rawArgs) {\n\t\t\t\t\t\t\tlet b = yield liquidEngine.evalValue(arg, ctx);\n\t\t\t\t\t\t\targArray.push(b);\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (this.orderedArgs) {\n\t\t\t\t\t\tfor (let arg of this.orderedArgs) {\n\t\t\t\t\t\t\tlet b = yield evalToken(arg, ctx);\n\t\t\t\t\t\t\targArray.push(b);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tconst html = yield liquidEngine.renderer.renderTemplates(this.templates, ctx);\n\n\t\t\t\t\tlet ret = yield shortcodeFn.call(Liquid.normalizeScope(ctx), html, ...argArray);\n\n\t\t\t\t\treturn ret;\n\t\t\t\t},\n\t\t\t};\n\t\t});\n\t}\n\n\tparseForSymbols(str) {\n\t\tif (!str) {\n\t\t\treturn [];\n\t\t}\n\n\t\tlet tokenizer = new Tokenizer(str);\n\t\t/** @type {Array} */\n\t\tlet tokens = tokenizer.readTopLevelTokens();\n\t\tlet symbols = tokens\n\t\t\t.filter((token) => token.kind === TokenKind.Output)\n\t\t\t.map((token) => {\n\t\t\t\t// manually remove filters 😅\n\t\t\t\treturn token.content.split(\"|\").map((entry) => entry.trim())[0];\n\t\t\t});\n\t\treturn symbols;\n\t}\n\n\t// Don’t return a boolean if permalink is a function (see TemplateContent->renderPermalink)\n\t/** @returns {boolean|undefined} */\n\tpermalinkNeedsCompilation(str) {\n\t\tif (typeof str === \"string\") {\n\t\t\treturn this.needsCompilation(str);\n\t\t}\n\t}\n\n\tneedsCompilation(str) {\n\t\tlet options = this.liquidLib.options;\n\n\t\treturn (\n\t\t\tstr.indexOf(options.tagDelimiterLeft) !== -1 ||\n\t\t\tstr.indexOf(options.outputDelimiterLeft) !== -1\n\t\t);\n\t}\n\n\tasync compile(str, inputPath) {\n\t\tlet engine = this.liquidLib;\n\t\tlet liquidOptions = this.liquidOptions;\n\t\tlet tmplReady = engine.parse(str, inputPath);\n\n\t\t// Required for relative includes\n\t\tlet options = {};\n\t\tif (!inputPath || inputPath === \"liquid\" || inputPath === \"md\") {\n\t\t\t// do nothing\n\t\t} else {\n\t\t\toptions.root = [TemplatePath.getDirFromFilePath(inputPath)];\n\t\t}\n\n\t\treturn async function (data) {\n\t\t\tlet tmpl = await tmplReady;\n\n\t\t\toptions.globals = Object.assign(\n\t\t\t\t{\n\t\t\t\t\tpage: data?.page,\n\t\t\t\t\televenty: data?.eleventy,\n\t\t\t\t\tcollections: data?.collections,\n\t\t\t\t},\n\t\t\t\tliquidOptions?.globals,\n\t\t\t);\n\n\t\t\treturn engine.render(tmpl, data, options);\n\t\t};\n\t}\n}\n"
  },
  {
    "path": "src/Engines/Markdown.js",
    "content": "import markdownIt from \"markdown-it\";\n\nimport TemplateEngine from \"./TemplateEngine.js\";\n\nexport default class Markdown extends TemplateEngine {\n\tconstructor(name, eleventyConfig) {\n\t\tsuper(name, eleventyConfig);\n\n\t\tthis.markdownOptions = {};\n\n\t\tthis.setLibrary(this.config.libraryOverrides.md);\n\t}\n\n\tget cacheable() {\n\t\treturn true;\n\t}\n\n\tsetLibrary(mdLib) {\n\t\tthis.mdLib = mdLib || markdownIt(this.getMarkdownOptions());\n\n\t\t// Overrides a highlighter set in `markdownOptions`\n\t\t// This is separate so devs can pass in a new mdLib and still use the official eleventy plugin for markdown highlighting\n\t\tif (this.config.markdownHighlighter && typeof this.mdLib.set === \"function\") {\n\t\t\tthis.mdLib.set({\n\t\t\t\thighlight: this.config.markdownHighlighter,\n\t\t\t});\n\t\t}\n\n\t\tif (typeof this.mdLib.disable === \"function\") {\n\t\t\t// Disable indented code blocks by default (Issue #2438)\n\t\t\tthis.mdLib.disable(\"code\");\n\t\t}\n\n\t\tthis.setEngineLib(this.mdLib, Boolean(this.config.libraryOverrides.md));\n\t}\n\n\tsetMarkdownOptions(options) {\n\t\tthis.markdownOptions = options;\n\t}\n\n\tgetMarkdownOptions() {\n\t\t// work with \"mode\" presets https://github.com/markdown-it/markdown-it#init-with-presets-and-options\n\t\tif (typeof this.markdownOptions === \"string\") {\n\t\t\treturn this.markdownOptions;\n\t\t}\n\n\t\treturn Object.assign(\n\t\t\t{\n\t\t\t\thtml: true,\n\t\t\t},\n\t\t\tthis.markdownOptions || {},\n\t\t);\n\t}\n\n\t// TODO use preTemplateEngine to help inform this\n\t// needsCompilation() {\n\t// \treturn super.needsCompilation();\n\t// }\n\n\tasync #getPreEngine(preTemplateEngine) {\n\t\tif (typeof preTemplateEngine === \"string\") {\n\t\t\treturn this.engineManager.getEngine(preTemplateEngine, this.extensionMap);\n\t\t}\n\n\t\treturn preTemplateEngine;\n\t}\n\n\tasync compile(str, inputPath, preTemplateEngine, bypassMarkdown) {\n\t\tlet mdlib = this.mdLib;\n\n\t\tif (preTemplateEngine) {\n\t\t\tlet engine = await this.#getPreEngine(preTemplateEngine);\n\t\t\tlet fnReady = engine.compile(str, inputPath);\n\n\t\t\tif (bypassMarkdown) {\n\t\t\t\treturn async function (data) {\n\t\t\t\t\tlet fn = await fnReady;\n\t\t\t\t\treturn fn(data);\n\t\t\t\t};\n\t\t\t} else {\n\t\t\t\treturn async function (data) {\n\t\t\t\t\tlet fn = await fnReady;\n\t\t\t\t\tlet preTemplateEngineRender = await fn(data);\n\t\t\t\t\tlet finishedRender = mdlib.render(preTemplateEngineRender, data);\n\t\t\t\t\treturn finishedRender;\n\t\t\t\t};\n\t\t\t}\n\t\t} else {\n\t\t\tif (bypassMarkdown) {\n\t\t\t\treturn function () {\n\t\t\t\t\treturn str;\n\t\t\t\t};\n\t\t\t} else {\n\t\t\t\treturn function (data) {\n\t\t\t\t\treturn mdlib.render(str, data);\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/Engines/Nunjucks.js",
    "content": "import debugUtil from \"debug\";\nimport { TemplatePath } from \"@11ty/eleventy-utils\";\n\n// Direct reference to avoid use of `browser` Nunjucks variant in bundles\nimport {\n\tdefault as NunjucksLib,\n\tEnvironment,\n\tFileSystemLoader,\n\tTemplate,\n} from \"@11ty/nunjucks/index.js\";\nimport TemplateEngine from \"./TemplateEngine.js\";\nimport EleventyBaseError from \"../Errors/EleventyBaseError.js\";\nimport { augmentObject } from \"./Util/ContextAugmenter.js\";\nimport { withResolvers } from \"../Util/PromiseUtil.js\";\n\nconst debug = debugUtil(\"Eleventy:Nunjucks\");\n\nclass EleventyNunjucksError extends EleventyBaseError {}\n\nexport default class Nunjucks extends TemplateEngine {\n\tconstructor(name, eleventyConfig) {\n\t\tsuper(name, eleventyConfig);\n\n\t\tthis.nunjucksEnvironmentOptions = this.config.nunjucksEnvironmentOptions || { dev: true };\n\n\t\tthis.nunjucksPrecompiledTemplates = this.config.nunjucksPrecompiledTemplates || {};\n\t\tthis._usingPrecompiled = Object.keys(this.nunjucksPrecompiledTemplates).length > 0;\n\n\t\tthis.setLibrary(this.config.libraryOverrides.njk);\n\t}\n\n\t// v3.1.0-alpha.1 we’ve moved to use Nunjucks’ internal cache instead of Eleventy’s\n\t// get cacheable() {\n\t// \treturn false;\n\t// }\n\n\t#getFileSystemDirs() {\n\t\tlet paths = new Set();\n\t\tpaths.add(super.getIncludesDir());\n\t\tpaths.add(TemplatePath.getWorkingDir());\n\n\t\t// Filter out undefined paths\n\t\treturn Array.from(paths).filter(Boolean);\n\t}\n\n\t#setEnv(override) {\n\t\tif (override) {\n\t\t\tthis.njkEnv = override;\n\t\t} else if (this._usingPrecompiled) {\n\t\t\t// Precompiled templates to avoid eval!\n\t\t\tconst NodePrecompiledLoader = function () {};\n\n\t\t\tNodePrecompiledLoader.prototype.getSource = (name) => {\n\t\t\t\t// https://github.com/mozilla/nunjucks/blob/fd500902d7c88672470c87170796de52fc0f791a/nunjucks/src/precompiled-loader.js#L5\n\t\t\t\treturn {\n\t\t\t\t\tsrc: {\n\t\t\t\t\t\ttype: \"code\",\n\t\t\t\t\t\tobj: this.nunjucksPrecompiledTemplates[name],\n\t\t\t\t\t},\n\t\t\t\t\t// Maybe add this?\n\t\t\t\t\t// path,\n\t\t\t\t\t// noCache: true\n\t\t\t\t};\n\t\t\t};\n\n\t\t\tthis.njkEnv = new Environment(new NodePrecompiledLoader(), this.nunjucksEnvironmentOptions);\n\t\t} else {\n\t\t\tlet loaders = [];\n\t\t\tloaders.push(new FileSystemLoader(this.#getFileSystemDirs()));\n\n\t\t\t// These need to come after FileSystemLoader\n\t\t\tfor (let loaderOptions of this.config.nunjucksLoaders) {\n\t\t\t\tlet loader = NunjucksLib.Loader.extend(loaderOptions);\n\t\t\t\tloaders.push(new loader());\n\t\t\t}\n\n\t\t\tthis.njkEnv = new Environment(loaders, this.nunjucksEnvironmentOptions);\n\t\t}\n\n\t\tthis.config.events.emit(\"eleventy.engine.njk\", {\n\t\t\tnunjucks: NunjucksLib,\n\t\t\tenvironment: this.njkEnv,\n\t\t});\n\t}\n\n\tsetLibrary(override) {\n\t\tthis.#setEnv(override);\n\n\t\t// Note that a new Nunjucks engine instance is created for subsequent builds\n\t\t// Eleventy Nunjucks is set to `cacheable` false above to opt out of Eleventy cache\n\t\tthis.config.events.on(\"eleventy#templateModified\", (templatePath) => {\n\t\t\t// NunjucksEnvironment:\n\t\t\t// loader.pathToNames: {'ABSOLUTE_PATH/src/_includes/components/possum-home.css': 'components/possum-home.css'}\n\t\t\t// loader.cache: { 'components/possum-home.css': [Template] }\n\t\t\t// Nunjucks stores these as Operating System native paths\n\t\t\tlet absTmplPath = TemplatePath.normalizeOperatingSystemFilePath(\n\t\t\t\tTemplatePath.absolutePath(templatePath),\n\t\t\t);\n\t\t\tfor (let loader of this.njkEnv.loaders) {\n\t\t\t\tlet nunjucksName = loader.pathsToNames[absTmplPath];\n\t\t\t\tif (nunjucksName) {\n\t\t\t\t\tdebug(\n\t\t\t\t\t\t\"Match found in Nunjucks cache via templateModified for %o, clearing this entry\",\n\t\t\t\t\t\ttemplatePath,\n\t\t\t\t\t);\n\t\t\t\t\tdelete loader.pathsToNames[absTmplPath];\n\t\t\t\t\tdelete loader.cache[nunjucksName];\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Behavior prior to v3.1.0-alpha.1:\n\t\t\t// this.njkEnv.invalidateCache();\n\t\t});\n\n\t\tthis.setEngineLib(this.njkEnv, Boolean(this.config.libraryOverrides.njk));\n\n\t\tthis.addFilters(this.config.nunjucksFilters);\n\t\tthis.addFilters(this.config.nunjucksAsyncFilters, true);\n\n\t\t// TODO these all go to the same place (addTag), add warnings for overwrites\n\t\t// TODO(zachleat): variableName should work with quotes or without quotes (same as {% set %})\n\t\t// This was changed to be an async function in v4 but notably previous versions of synchronous paired shortcodes used CallExtensionAsync\n\t\tthis.addPairedShortcode(\n\t\t\t\"setAsync\",\n\t\t\tasync function (content, variableName) {\n\t\t\t\tthis.ctx[variableName] = content;\n\t\t\t\treturn \"\";\n\t\t\t},\n\t\t\ttrue,\n\t\t);\n\n\t\tthis.addCustomTags(this.config.nunjucksTags);\n\t\tthis.addAllShortcodes(this.config.nunjucksShortcodes);\n\t\tthis.addAllShortcodes(this.config.nunjucksAsyncShortcodes, true);\n\t\tthis.addAllPairedShortcodes(this.config.nunjucksPairedShortcodes);\n\t\tthis.addAllPairedShortcodes(this.config.nunjucksAsyncPairedShortcodes, true);\n\t\tthis.addGlobals(this.config.nunjucksGlobals);\n\t}\n\n\taddFilters(filters, isAsync) {\n\t\tfor (let name in filters) {\n\t\t\tthis.njkEnv.addFilter(name, Nunjucks.wrapFilter(name, filters[name]), isAsync);\n\t\t}\n\t}\n\n\tstatic wrapFilter(name, fn) {\n\t\treturn function (...args) {\n\t\t\ttry {\n\t\t\t\taugmentObject(this, {\n\t\t\t\t\tsource: this.ctx,\n\t\t\t\t\tlazy: false, // context.env?.opts.throwOnUndefined,\n\t\t\t\t});\n\n\t\t\t\treturn fn.call(this, ...args);\n\t\t\t} catch (e) {\n\t\t\t\tthrow new EleventyNunjucksError(\n\t\t\t\t\t`Error in Nunjucks Filter \\`${name}\\`${this.page ? ` (${this.page.inputPath})` : \"\"}`,\n\t\t\t\t\te,\n\t\t\t\t);\n\t\t\t}\n\t\t};\n\t}\n\n\t// Shortcodes\n\tstatic normalizeContext(context) {\n\t\tlet obj = {};\n\t\tif (context.ctx) {\n\t\t\tobj.ctx = context.ctx;\n\t\t\tobj.env = context.env;\n\n\t\t\taugmentObject(obj, {\n\t\t\t\tsource: context.ctx,\n\t\t\t\tlazy: false, // context.env?.opts.throwOnUndefined,\n\t\t\t});\n\t\t}\n\t\treturn obj;\n\t}\n\n\taddCustomTags(tags) {\n\t\tfor (let name in tags) {\n\t\t\tthis.addTag(name, tags[name]);\n\t\t}\n\t}\n\n\taddTag(name, tagFn) {\n\t\tlet tagObj;\n\t\tif (typeof tagFn === \"function\") {\n\t\t\ttagObj = tagFn(NunjucksLib, this.njkEnv);\n\t\t} else {\n\t\t\tthrow new Error(\n\t\t\t\t\"Nunjucks.addTag expects a callback function to be passed in: addTag(name, function(nunjucksEngine) {})\",\n\t\t\t);\n\t\t}\n\n\t\tthis.njkEnv.addExtension(name, tagObj);\n\t}\n\n\taddGlobals(globals) {\n\t\tfor (let name in globals) {\n\t\t\tthis.addGlobal(name, globals[name]);\n\t\t}\n\t}\n\n\taddGlobal(name, globalFn) {\n\t\tthis.njkEnv.addGlobal(name, globalFn);\n\t}\n\n\taddAllShortcodes(shortcodes, isAsync = false) {\n\t\tfor (let name in shortcodes) {\n\t\t\tthis.addShortcode(name, shortcodes[name], isAsync);\n\t\t}\n\t}\n\n\taddAllPairedShortcodes(shortcodes, isAsync = false) {\n\t\tfor (let name in shortcodes) {\n\t\t\tthis.addPairedShortcode(name, shortcodes[name], isAsync);\n\t\t}\n\t}\n\n\t_getShortcodeFn(shortcodeName, shortcodeFn, isAsync = false) {\n\t\treturn function ShortcodeFunction() {\n\t\t\tthis.tags = [shortcodeName];\n\n\t\t\tthis.parse = function (parser, nodes) {\n\t\t\t\tlet args;\n\t\t\t\tlet tok = parser.nextToken();\n\n\t\t\t\targs = parser.parseSignature(true, true);\n\n\t\t\t\t// Nunjucks bug with non-paired custom tags bug still exists even\n\t\t\t\t// though this issue is closed. Works fine for paired.\n\t\t\t\t// https://github.com/mozilla/nunjucks/issues/158\n\t\t\t\t// https://github.com/11ty/eleventy/issues/372\n\t\t\t\tif (args.children.length === 0) {\n\t\t\t\t\t// Changed from an empty string to an empty NodeList\n\t\t\t\t\t// https://github.com/11ty/eleventy/issues/3788\n\t\t\t\t\targs.addChild(new nodes.NodeList());\n\t\t\t\t}\n\n\t\t\t\tparser.advanceAfterBlockEnd(tok.value);\n\t\t\t\tif (isAsync) {\n\t\t\t\t\treturn new nodes.CallExtensionAsync(this, \"run\", args);\n\t\t\t\t}\n\t\t\t\treturn new nodes.CallExtension(this, \"run\", args);\n\t\t\t};\n\n\t\t\tthis.run = function (...args) {\n\t\t\t\tlet resolve;\n\t\t\t\tif (isAsync) {\n\t\t\t\t\tresolve = args.pop();\n\t\t\t\t}\n\n\t\t\t\tlet [context, ...argArray] = args;\n\n\t\t\t\tif (isAsync) {\n\t\t\t\t\tlet ret = shortcodeFn.call(Nunjucks.normalizeContext(context), ...argArray);\n\n\t\t\t\t\t// #3286 error messaging when the shortcode is not a promise\n\t\t\t\t\tif (!ret?.then) {\n\t\t\t\t\t\tresolve(\n\t\t\t\t\t\t\tnew EleventyNunjucksError(\n\t\t\t\t\t\t\t\t`Error with Nunjucks shortcode \\`${shortcodeName}\\`: it was defined as asynchronous but was actually synchronous. This is important for Nunjucks.`,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tret.then(\n\t\t\t\t\t\tfunction (returnValue) {\n\t\t\t\t\t\t\tresolve(null, new NunjucksLib.runtime.SafeString(\"\" + returnValue));\n\t\t\t\t\t\t},\n\t\t\t\t\t\tfunction (e) {\n\t\t\t\t\t\t\tresolve(\n\t\t\t\t\t\t\t\tnew EleventyNunjucksError(`Error with Nunjucks shortcode \\`${shortcodeName}\\``, e),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t},\n\t\t\t\t\t);\n\t\t\t\t} else {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tlet ret = shortcodeFn.call(Nunjucks.normalizeContext(context), ...argArray);\n\t\t\t\t\t\treturn new NunjucksLib.runtime.SafeString(\"\" + ret);\n\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\tthrow new EleventyNunjucksError(\n\t\t\t\t\t\t\t`Error with Nunjucks shortcode \\`${shortcodeName}\\``,\n\t\t\t\t\t\t\te,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t};\n\t\t};\n\t}\n\n\t_getPairedShortcodeFn(shortcodeName, shortcodeFn, isAsync = false) {\n\t\treturn function PairedShortcodeFunction() {\n\t\t\tthis.tags = [shortcodeName];\n\n\t\t\tif (isAsync) {\n\t\t\t\tthis.parse = function (parser, nodes) {\n\t\t\t\t\tvar tok = parser.nextToken();\n\n\t\t\t\t\tvar args = parser.parseSignature(true, true);\n\t\t\t\t\tparser.advanceAfterBlockEnd(tok.value);\n\n\t\t\t\t\tvar body = parser.parseUntilBlocks(\"end\" + shortcodeName);\n\t\t\t\t\tparser.advanceAfterBlockEnd();\n\n\t\t\t\t\treturn new nodes.CallExtensionAsync(this, \"run\", args, [body]);\n\t\t\t\t};\n\n\t\t\t\tthis.run = function (...args) {\n\t\t\t\t\tlet resolve = args.pop();\n\t\t\t\t\tlet body = args.pop();\n\t\t\t\t\tlet [context, ...argArray] = args;\n\n\t\t\t\t\tbody(function (e, bodyContent) {\n\t\t\t\t\t\tif (e) {\n\t\t\t\t\t\t\tresolve(\n\t\t\t\t\t\t\t\tnew EleventyNunjucksError(\n\t\t\t\t\t\t\t\t\t`Error with Nunjucks paired shortcode \\`${shortcodeName}\\``,\n\t\t\t\t\t\t\t\t\te,\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tlet ret = shortcodeFn.call(\n\t\t\t\t\t\t\tNunjucks.normalizeContext(context),\n\t\t\t\t\t\t\tbodyContent,\n\t\t\t\t\t\t\t...argArray,\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\t// #3286 error messaging when the shortcode is not a promise\n\t\t\t\t\t\tif (!ret?.then) {\n\t\t\t\t\t\t\tthrow new EleventyNunjucksError(\n\t\t\t\t\t\t\t\t`Error with Nunjucks shortcode \\`${shortcodeName}\\`: it was defined as asynchronous but was actually synchronous. This is important for Nunjucks.`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tret.then(\n\t\t\t\t\t\t\tfunction (returnValue) {\n\t\t\t\t\t\t\t\tresolve(null, new NunjucksLib.runtime.SafeString(returnValue));\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tfunction (e) {\n\t\t\t\t\t\t\t\tresolve(\n\t\t\t\t\t\t\t\t\tnew EleventyNunjucksError(\n\t\t\t\t\t\t\t\t\t\t`Error with Nunjucks paired shortcode \\`${shortcodeName}\\``,\n\t\t\t\t\t\t\t\t\t\te,\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t);\n\t\t\t\t\t});\n\t\t\t\t};\n\t\t\t} else {\n\t\t\t\tthis.parse = function (parser, nodes) {\n\t\t\t\t\tvar tok = parser.nextToken();\n\n\t\t\t\t\tvar args = parser.parseSignature(true, true);\n\t\t\t\t\tparser.advanceAfterBlockEnd(tok.value);\n\n\t\t\t\t\tvar body = parser.parseUntilBlocks(\"end\" + shortcodeName);\n\t\t\t\t\tparser.advanceAfterBlockEnd();\n\n\t\t\t\t\treturn new nodes.CallExtension(this, \"run\", args, [body]);\n\t\t\t\t};\n\n\t\t\t\tthis.run = function (...args) {\n\t\t\t\t\tlet body = args.pop();\n\t\t\t\t\tlet [context, ...argArray] = args;\n\t\t\t\t\tlet bodyContent = body();\n\n\t\t\t\t\ttry {\n\t\t\t\t\t\treturn new NunjucksLib.runtime.SafeString(\n\t\t\t\t\t\t\tshortcodeFn.call(Nunjucks.normalizeContext(context), bodyContent, ...argArray),\n\t\t\t\t\t\t);\n\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\tthrow new EleventyNunjucksError(\n\t\t\t\t\t\t\t`Error with Nunjucks paired shortcode \\`${shortcodeName}\\``,\n\t\t\t\t\t\t\te,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t}\n\t\t};\n\t}\n\n\taddShortcode(shortcodeName, shortcodeFn, isAsync = false) {\n\t\tlet fn = this._getShortcodeFn(shortcodeName, shortcodeFn, isAsync);\n\t\tthis.njkEnv.addExtension(shortcodeName, new fn());\n\t}\n\n\taddPairedShortcode(shortcodeName, shortcodeFn, isAsync = false) {\n\t\tlet fn = this._getPairedShortcodeFn(shortcodeName, shortcodeFn, isAsync);\n\t\tthis.njkEnv.addExtension(shortcodeName, new fn());\n\t}\n\n\t// Don’t return a boolean if permalink is a function (see TemplateContent->renderPermalink)\n\tpermalinkNeedsCompilation(str) {\n\t\tif (typeof str === \"string\") {\n\t\t\treturn this.needsCompilation(str);\n\t\t}\n\t}\n\n\tneedsCompilation(str) {\n\t\t// Defend against syntax customisations:\n\t\t//    https://mozilla.github.io/nunjucks/api.html#customizing-syntax\n\t\tlet optsTags = this.njkEnv.opts.tags || {};\n\t\tlet blockStart = optsTags.blockStart || \"{%\";\n\t\tlet variableStart = optsTags.variableStart || \"{{\";\n\t\tlet commentStart = optsTags.variableStart || \"{#\";\n\n\t\treturn (\n\t\t\tstr.indexOf(blockStart) !== -1 ||\n\t\t\tstr.indexOf(variableStart) !== -1 ||\n\t\t\tstr.indexOf(commentStart) !== -1\n\t\t);\n\t}\n\n\t_getParseExtensions() {\n\t\tif (this._parseExtensions) {\n\t\t\treturn this._parseExtensions;\n\t\t}\n\n\t\t// add extensions so the parser knows about our custom tags/blocks\n\t\tlet ext = [];\n\t\tfor (let name in this.config.nunjucksTags) {\n\t\t\tlet fn = this._getShortcodeFn(name, () => {});\n\t\t\text.push(new fn());\n\t\t}\n\t\tfor (let name in this.config.nunjucksShortcodes) {\n\t\t\tlet fn = this._getShortcodeFn(name, () => {});\n\t\t\text.push(new fn());\n\t\t}\n\t\tfor (let name in this.config.nunjucksAsyncShortcodes) {\n\t\t\tlet fn = this._getShortcodeFn(name, () => {}, true);\n\t\t\text.push(new fn());\n\t\t}\n\t\tfor (let name in this.config.nunjucksPairedShortcodes) {\n\t\t\tlet fn = this._getPairedShortcodeFn(name, () => {});\n\t\t\text.push(new fn());\n\t\t}\n\t\tfor (let name in this.config.nunjucksAsyncPairedShortcodes) {\n\t\t\tlet fn = this._getPairedShortcodeFn(name, () => {}, true);\n\t\t\text.push(new fn());\n\t\t}\n\n\t\tthis._parseExtensions = ext;\n\t\treturn ext;\n\t}\n\n\t/* Outputs an Array of lodash get selectors */\n\tparseForSymbols(str) {\n\t\tif (!str) {\n\t\t\treturn [];\n\t\t}\n\t\tconst { parser, nodes } = NunjucksLib;\n\t\tlet obj = parser.parse(str, this._getParseExtensions());\n\t\tif (!obj) {\n\t\t\treturn [];\n\t\t}\n\t\tlet linesplit = str.split(\"\\n\");\n\t\tlet values = obj.findAll(nodes.Value);\n\t\tlet symbols = obj.findAll(nodes.Symbol).map((entry) => {\n\t\t\tlet name = [entry.value];\n\t\t\tlet nestedIndex = -1;\n\t\t\tfor (let val of values) {\n\t\t\t\tif (nestedIndex > -1) {\n\t\t\t\t\t/* deep.object.syntax */\n\t\t\t\t\tif (linesplit[val.lineno].charAt(nestedIndex) === \".\") {\n\t\t\t\t\t\tname.push(val.value);\n\t\t\t\t\t\tnestedIndex += val.value.length + 1;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tnestedIndex = -1;\n\t\t\t\t\t}\n\t\t\t\t} else if (\n\t\t\t\t\tval.lineno === entry.lineno &&\n\t\t\t\t\tval.colno === entry.colno &&\n\t\t\t\t\tval.value === entry.value\n\t\t\t\t) {\n\t\t\t\t\tnestedIndex = entry.colno + entry.value.length;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn name.join(\".\");\n\t\t});\n\n\t\tlet uniqueSymbols = Array.from(new Set(symbols));\n\t\treturn uniqueSymbols;\n\t}\n\n\tasync compile(str, inputPath) {\n\t\tlet tmpl;\n\n\t\t// *All* templates are precompiled to avoid runtime eval\n\t\tif (this._usingPrecompiled) {\n\t\t\ttmpl = this.njkEnv.getTemplate(str, true);\n\t\t} else if (!inputPath || inputPath === \"njk\" || inputPath === \"md\") {\n\t\t\t// Template(content, environment, path, eagerCompile)\n\t\t\ttmpl = new Template(str, this.njkEnv, null, false);\n\t\t} else {\n\t\t\t// Template(content, environment, path, eagerCompile)\n\t\t\ttmpl = new Template(str, this.njkEnv, inputPath, false);\n\t\t}\n\n\t\treturn function (data) {\n\t\t\tlet { promise, resolve, reject } = withResolvers();\n\n\t\t\ttmpl.render(data, (error, result) => {\n\t\t\t\tif (error) {\n\t\t\t\t\treject(error);\n\t\t\t\t} else {\n\t\t\t\t\tresolve(result);\n\t\t\t\t}\n\t\t\t});\n\n\t\t\treturn promise;\n\t\t};\n\t}\n}\n"
  },
  {
    "path": "src/Engines/TemplateEngine.js",
    "content": "import debugUtil from \"debug\";\nimport EleventyBaseError from \"../Errors/EleventyBaseError.js\";\n\nclass TemplateEngineConfigError extends EleventyBaseError {}\n\nconst debug = debugUtil(\"Eleventy:TemplateEngine\");\n\nconst AMENDED_INSTANCES = new Set();\n\nexport default class TemplateEngine {\n\t#extensionMap;\n\t#engineManager;\n\t#benchmarks;\n\n\tconstructor(name, eleventyConfig) {\n\t\tthis.name = name;\n\n\t\tthis.engineLib = null;\n\n\t\tif (!eleventyConfig) {\n\t\t\tthrow new TemplateEngineConfigError(\"Missing `eleventyConfig` argument.\");\n\t\t}\n\t\tthis.eleventyConfig = eleventyConfig;\n\t}\n\n\tget cacheable() {\n\t\treturn false;\n\t}\n\n\tget dirs() {\n\t\treturn this.eleventyConfig.directories;\n\t}\n\n\tget inputDir() {\n\t\treturn this.dirs.input;\n\t}\n\n\tget includesDir() {\n\t\treturn this.dirs.includes;\n\t}\n\n\tget config() {\n\t\tif (this.eleventyConfig.constructor.name !== \"TemplateConfig\") {\n\t\t\tthrow new Error(\"Expecting a TemplateConfig instance.\");\n\t\t}\n\n\t\treturn this.eleventyConfig.getConfig();\n\t}\n\n\tget benchmarks() {\n\t\tif (!this.#benchmarks) {\n\t\t\tthis.#benchmarks = {\n\t\t\t\taggregate: this.config.benchmarkManager.get(\"Aggregate\"),\n\t\t\t};\n\t\t}\n\t\treturn this.#benchmarks;\n\t}\n\n\tget engineManager() {\n\t\treturn this.#engineManager;\n\t}\n\n\tset engineManager(manager) {\n\t\tthis.#engineManager = manager;\n\t}\n\n\tget extensionMap() {\n\t\tif (!this.#extensionMap) {\n\t\t\tthrow new Error(\"Internal error: missing `extensionMap` in TemplateEngine.\");\n\t\t}\n\t\treturn this.#extensionMap;\n\t}\n\n\tset extensionMap(map) {\n\t\tthis.#extensionMap = map;\n\t}\n\n\tget extensions() {\n\t\tif (!this._extensions) {\n\t\t\tthis._extensions = this.extensionMap.getExtensionsFromKey(this.name);\n\t\t}\n\t\treturn this._extensions;\n\t}\n\n\tget extensionEntries() {\n\t\tif (!this._extensionEntries) {\n\t\t\tthis._extensionEntries = this.extensionMap.getExtensionEntriesFromKey(this.name);\n\t\t}\n\t\treturn this._extensionEntries;\n\t}\n\n\tgetName() {\n\t\treturn this.name;\n\t}\n\n\t// Backwards compat\n\tgetIncludesDir() {\n\t\treturn this.includesDir;\n\t}\n\n\t/**\n\t * @protected\n\t */\n\tsetEngineLib(engineLib, isOverrideViaSetLibrary = false) {\n\t\tthis.engineLib = engineLib;\n\n\t\t// Run engine amendments (via issue #2438)\n\t\t// Issue #3816: this isn’t ideal but there is no other way to reset a markdown instance if it was also overridden by addLibrary\n\t\tif (AMENDED_INSTANCES.has(engineLib)) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (isOverrideViaSetLibrary) {\n\t\t\tAMENDED_INSTANCES.add(engineLib);\n\t\t}\n\t\tdebug(\n\t\t\t\"Running amendLibrary for %o (number of amendments: %o)\",\n\t\t\tthis.name,\n\t\t\tthis.config.libraryAmendments[this.name]?.length,\n\t\t);\n\n\t\tfor (let amendment of this.config.libraryAmendments[this.name] || []) {\n\t\t\t// TODO it’d be nice if this were async friendly\n\t\t\tamendment(engineLib);\n\t\t}\n\t}\n\n\tgetEngineLib() {\n\t\treturn this.engineLib;\n\t}\n\n\tasync _testRender(str, data) {\n\t\t// @ts-ignore\n\t\tlet fn = await this.compile(str);\n\t\treturn fn(data);\n\t}\n\n\tuseJavaScriptImport() {\n\t\treturn false;\n\t}\n\n\t// JavaScript files defer to the module loader rather than read the files to strings\n\tneedsToReadFileContents() {\n\t\treturn true;\n\t}\n\n\tgetExtraDataFromFile() {\n\t\treturn {};\n\t}\n\n\tgetCompileCacheKey(str, inputPath) {\n\t\t// Changing to use inputPath and contents, using only file contents (`str`) caused issues when two\n\t\t// different files had identical content (2.0.0-canary.16)\n\n\t\t// Caches are now segmented based on inputPath so using inputPath here is superfluous (2.0.0-canary.19)\n\t\t// But we do want a non-falsy value here even if `str` is an empty string.\n\t\treturn {\n\t\t\tuseCache: true,\n\t\t\tkey: inputPath + str,\n\t\t};\n\t}\n\n\tget defaultTemplateFileExtension() {\n\t\treturn \"html\";\n\t}\n\n\t// Whether or not to wrap in Eleventy layouts\n\tuseLayouts() {\n\t\treturn true;\n\t}\n\n\t/** @returns {boolean|undefined} */\n\tpermalinkNeedsCompilation(str) {\n\t\treturn this.needsCompilation();\n\t}\n\n\t// whether or not compile is needed or can we return the plaintext?\n\tneedsCompilation(str) {\n\t\treturn true;\n\t}\n\n\t/**\n\t * Make sure compile is implemented downstream.\n\t * @abstract\n\t * @return {Promise}\n\t */\n\tasync compile() {\n\t\tthrow new Error(\"compile() must be implemented by engine\");\n\t}\n\n\t// See https://v3.11ty.dev/docs/watch-serve/#watch-javascript-dependencies\n\tstatic shouldSpiderJavaScriptDependencies() {\n\t\treturn false;\n\t}\n\n\thasDependencies(inputPath) {\n\t\tif (this.config.uses.getDependencies(inputPath) === false) {\n\t\t\treturn false;\n\t\t}\n\t\treturn true;\n\t}\n\n\tisFileRelevantTo(inputPath, comparisonFile) {\n\t\treturn this.config.uses.isFileRelevantTo(inputPath, comparisonFile);\n\t}\n}\n"
  },
  {
    "path": "src/Engines/TemplateEngineManager.js",
    "content": "import debugUtil from \"debug\";\nimport EleventyBaseError from \"../Errors/EleventyBaseError.js\";\n\nconst debug = debugUtil(\"Eleventy:TemplateEngineManager\");\n\nclass TemplateEngineManager {\n\t#CustomEngine;\n\n\tconstructor(eleventyConfig) {\n\t\tif (!eleventyConfig || eleventyConfig.constructor.name !== \"TemplateConfig\") {\n\t\t\tthrow new EleventyBaseError(\"Missing or invalid `config` argument.\");\n\t\t}\n\t\tthis.eleventyConfig = eleventyConfig;\n\n\t\tthis.engineCache = {};\n\t\tthis.importCache = {};\n\t}\n\n\tget config() {\n\t\treturn this.eleventyConfig.getConfig();\n\t}\n\n\tstatic isAlias(entry) {\n\t\tif (entry.aliasKey) {\n\t\t\treturn true;\n\t\t}\n\n\t\treturn entry.key !== entry.extension;\n\t}\n\n\tstatic isSimpleAlias(entry) {\n\t\tif (!this.isAlias(entry)) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// has keys other than key, extension, and aliasKey\n\t\treturn (\n\t\t\tObject.keys(entry).some((key) => {\n\t\t\t\treturn key !== \"key\" && key !== \"extension\" && key !== \"aliasKey\";\n\t\t\t}) === false\n\t\t);\n\t}\n\n\tget keyToClassNameMap() {\n\t\tif (!this._keyToClassNameMap) {\n\t\t\tthis._keyToClassNameMap = {\n\t\t\t\tmd: \"Markdown\",\n\t\t\t\thtml: \"Html\",\n\t\t\t\tnjk: \"Nunjucks\",\n\t\t\t\tliquid: \"Liquid\",\n\t\t\t\t\"11ty.js\": \"JavaScript\",\n\t\t\t};\n\n\t\t\t// Custom entries *can* overwrite default entries above\n\t\t\tif (\"extensionMap\" in this.config) {\n\t\t\t\tfor (let entry of this.config.extensionMap) {\n\t\t\t\t\t// either the key does not already exist or it is not a simple alias and is an override: https://v3.11ty.dev/docs/languages/custom/#overriding-an-existing-template-language\n\t\t\t\t\tlet existingTarget = this._keyToClassNameMap[entry.key];\n\t\t\t\t\tlet isAlias = TemplateEngineManager.isAlias(entry);\n\n\t\t\t\t\tif (!existingTarget && isAlias) {\n\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t`An attempt to alias ${entry.aliasKey} to ${entry.key} was made, but ${entry.key} is not a recognized template syntax.`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tif (isAlias) {\n\t\t\t\t\t\t// only `key` and `extension`, not `compile` or other options\n\t\t\t\t\t\tif (!TemplateEngineManager.isSimpleAlias(entry)) {\n\t\t\t\t\t\t\tthis._keyToClassNameMap[entry.aliasKey] = \"Custom\";\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tthis._keyToClassNameMap[entry.aliasKey] = this._keyToClassNameMap[entry.key];\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// not an alias, so `key` and `extension` are the same here.\n\t\t\t\t\t\t// *can* override a built-in extension!\n\t\t\t\t\t\tthis._keyToClassNameMap[entry.key] = \"Custom\";\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn this._keyToClassNameMap;\n\t}\n\n\treset() {\n\t\tthis.engineCache = {};\n\t}\n\n\tgetClassNameFromTemplateKey(key) {\n\t\treturn this.keyToClassNameMap[key];\n\t}\n\n\thasEngine(name) {\n\t\treturn !!this.getClassNameFromTemplateKey(name);\n\t}\n\n\tasync getEngineClassByExtension(extension) {\n\t\tif (this.config.extensionMapClasses[extension]) {\n\t\t\treturn this.config.extensionMapClasses[extension];\n\t\t}\n\t\tif (this.importCache[extension]) {\n\t\t\treturn this.importCache[extension];\n\t\t}\n\n\t\tlet promise;\n\n\t\t// We include these as raw strings (and not more readable variables) so they’re parsed by a bundler.\n\t\tif (extension === \"md\") {\n\t\t\tpromise = import(\"../Adapters/Engines/Markdown.js\").then((mod) => mod.default);\n\t\t} else if (extension === \"html\") {\n\t\t\tpromise = import(\"./Html.js\").then((mod) => mod.default);\n\t\t} else if (extension === \"njk\") {\n\t\t\tpromise = import(\"../Adapters/Engines/Nunjucks.js\").then((mod) => mod.default);\n\t\t} else if (extension === \"liquid\") {\n\t\t\tpromise = import(\"../Adapters/Engines/Liquid.js\").then((mod) => mod.default);\n\t\t} else if (extension === \"11ty.js\") {\n\t\t\t// ~4KB cost, fine to keep\n\t\t\tpromise = import(\"./JavaScript.js\").then((mod) => mod.default);\n\t\t} else {\n\t\t\tpromise = this.getCustomEngineClass();\n\t\t}\n\n\t\tif (promise) {\n\t\t\tthis.importCache[extension] = promise;\n\t\t} else {\n\t\t\tthrow new Error(\"Missing engine for file extension: \" + extension);\n\t\t}\n\n\t\treturn promise;\n\t}\n\n\tasync getCustomEngineClass() {\n\t\tif (!this.#CustomEngine) {\n\t\t\tthis.#CustomEngine = import(\"./Custom.js\").then((mod) => mod.default);\n\t\t}\n\t\treturn this.#CustomEngine;\n\t}\n\n\tasync #getEngine(name, extensionMap) {\n\t\tlet cls = await this.getEngineClassByExtension(name);\n\t\tif (!cls) {\n\t\t\tthrow new Error(`Missing engine for ${name}. Do you need to use eleventyConfig.addEngine?`);\n\t\t}\n\n\t\tlet instance = new cls(name, this.eleventyConfig);\n\t\tinstance.extensionMap = extensionMap;\n\t\tinstance.engineManager = this;\n\n\t\tlet extensionEntry = extensionMap.getExtensionEntry(name);\n\n\t\t// Override a built-in extension (md => md)\n\t\t// If provided a \"Custom\" engine using addExtension, but that engine's instance is *not* custom,\n\t\t// The user must be overriding a built-in engine i.e. addExtension('md', { ...overrideBehavior })\n\t\tlet className = this.getClassNameFromTemplateKey(name);\n\n\t\tif (className === \"Custom\" && instance.constructor.name !== \"CustomEngine\") {\n\t\t\tlet CustomEngine = await this.getCustomEngineClass();\n\t\t\tlet overrideCustomEngine = new CustomEngine(name, this.eleventyConfig);\n\n\t\t\t// Keep track of the \"default\" engine 11ty would normally use\n\t\t\t// This allows the user to access the default engine in their override\n\t\t\toverrideCustomEngine.setDefaultEngine(instance);\n\n\t\t\tinstance = overrideCustomEngine;\n\t\t\t// Alias to a built-in extension (11ty.tsx => 11ty.js)\n\t\t} else if (\n\t\t\tinstance.constructor.name === \"CustomEngine\" &&\n\t\t\tTemplateEngineManager.isAlias(extensionEntry)\n\t\t) {\n\t\t\t// add defaultRenderer for complex aliases with their own compile functions.\n\t\t\tlet originalEngineInstance = await this.getEngine(extensionEntry.key, extensionMap);\n\t\t\tinstance.setDefaultEngine(originalEngineInstance);\n\t\t}\n\n\t\treturn instance;\n\t}\n\n\tisEngineRemovedFromCore(name) {\n\t\treturn [\"ejs\", \"hbs\", \"mustache\", \"haml\", \"pug\"].includes(name) && !this.hasEngine(name);\n\t}\n\n\tasync getEngine(name, extensionMap) {\n\t\t// Bundled engine deprecation\n\t\tif (this.isEngineRemovedFromCore(name)) {\n\t\t\tthrow new Error(\n\t\t\t\t`Per the 11ty Community Survey (2023), the \"${name}\" template language was moved from core to an officially supported plugin in v3.0. These plugins live here: https://github.com/11ty/eleventy-plugin-template-languages and are documented on their respective template language docs at https://v3.11ty.dev/docs/languages/ You are also empowered to implement *any* template language yourself using https://v3.11ty.dev/docs/languages/custom/`,\n\t\t\t);\n\t\t}\n\n\t\tif (!this.hasEngine(name)) {\n\t\t\tthrow new Error(`Template Engine ${name} does not exist in getEngine()`);\n\t\t}\n\t\t// TODO these cached engines should be based on extensions not name, then we can remove the error in\n\t\t//  \"Double override (not aliases) throws an error\" test in TemplateRenderCustomTest.js\n\t\tif (!this.engineCache[name]) {\n\t\t\tdebug(\"Engine cache miss %o (should only happen once per engine type)\", name);\n\t\t\t// Make sure cache key is based on name and not path\n\t\t\t// Custom class is used for all plugins, cache once per plugin\n\t\t\tthis.engineCache[name] = this.#getEngine(name, extensionMap);\n\t\t}\n\n\t\treturn this.engineCache[name];\n\t}\n}\n\nexport default TemplateEngineManager;\n"
  },
  {
    "path": "src/Engines/Util/ContextAugmenter.js",
    "content": "const DATA_KEYS = [\"page\", \"eleventy\"];\n\nfunction augmentFunction(fn, options = {}) {\n\tlet t = typeof fn;\n\tif (t !== \"function\") {\n\t\tthrow new Error(\n\t\t\t\"Invalid type passed to `augmentFunction`. A function was expected and received: \" + t,\n\t\t);\n\t}\n\n\t/** @this {object} */\n\treturn function (...args) {\n\t\tlet context = augmentObject(this || {}, options);\n\t\treturn fn.call(context, ...args);\n\t};\n}\n\nfunction augmentObject(targetObject, options = {}) {\n\toptions = Object.assign(\n\t\t{\n\t\t\tsource: undefined, // where to copy from\n\t\t\toverwrite: true,\n\t\t\tlazy: false, // lazily fetch the property\n\t\t\t// getter: function() {},\n\t\t},\n\t\toptions,\n\t);\n\n\tfor (let key of DATA_KEYS) {\n\t\t// Skip if overwrite: false and prop already exists on target\n\t\tif (!options.overwrite && targetObject[key]) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (options.lazy) {\n\t\t\tlet value;\n\t\t\tif (typeof options.getter == \"function\") {\n\t\t\t\tvalue = () => options.getter(key, options.source);\n\t\t\t} else {\n\t\t\t\tvalue = () => options.source?.[key];\n\t\t\t}\n\n\t\t\t// lazy getter important for Liquid strictVariables support\n\t\t\tObject.defineProperty(targetObject, key, {\n\t\t\t\twritable: true,\n\t\t\t\tconfigurable: true,\n\t\t\t\tenumerable: true,\n\t\t\t\tvalue,\n\t\t\t});\n\t\t} else {\n\t\t\tlet value;\n\t\t\tif (typeof options.getter == \"function\") {\n\t\t\t\tvalue = options.getter(key, options.source);\n\t\t\t} else {\n\t\t\t\tvalue = options.source?.[key];\n\t\t\t}\n\n\t\t\tif (value) {\n\t\t\t\ttargetObject[key] = value;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn targetObject;\n}\n\nexport { DATA_KEYS as augmentKeys, augmentFunction, augmentObject };\n"
  },
  {
    "path": "src/Errors/DuplicatePermalinkOutputError.js",
    "content": "import EleventyBaseError from \"./EleventyBaseError.js\";\n\nclass DuplicatePermalinkOutputError extends EleventyBaseError {\n\tget removeDuplicateErrorStringFromOutput() {\n\t\treturn true;\n\t}\n}\n\nexport default DuplicatePermalinkOutputError;\n"
  },
  {
    "path": "src/Errors/EleventyBaseError.js",
    "content": "/**\n * This class serves as basis for all Eleventy-specific errors.\n * @ignore\n */\nclass EleventyBaseError extends Error {\n\t/**\n\t * @param {string} message - The error message to display.\n\t * @param {unknown} [originalError] - The original error caught.\n\t */\n\tconstructor(message, originalError) {\n\t\tif (originalError) {\n\t\t\t// @ts-ignore\n\t\t\tsuper(message, { cause: originalError });\n\t\t} else {\n\t\t\tsuper(message);\n\t\t}\n\n\t\tthis.name = this.constructor.name;\n\n\t\tif (Error.captureStackTrace) {\n\t\t\tError.captureStackTrace(this, this.constructor);\n\t\t}\n\n\t\tif (originalError) {\n\t\t\tthis.originalError = originalError;\n\t\t}\n\t}\n}\nexport default EleventyBaseError;\n"
  },
  {
    "path": "src/Errors/EleventyErrorHandler.js",
    "content": "import debugUtil from \"debug\";\n\nimport { inspect } from \"../Adapters/Packages/inspect.js\";\nimport ConsoleLogger from \"../Util/ConsoleLogger.js\";\nimport EleventyErrorUtil from \"./EleventyErrorUtil.js\";\n\nconst debug = debugUtil(\"Eleventy:EleventyErrorHandler\");\n\nclass EleventyErrorHandler {\n\tconstructor() {\n\t\tthis._isVerbose = true;\n\t}\n\n\tget isVerbose() {\n\t\treturn this._isVerbose;\n\t}\n\n\tset isVerbose(verbose) {\n\t\tthis._isVerbose = !!verbose;\n\t\tthis.logger.isVerbose = !!verbose;\n\t}\n\n\tget logger() {\n\t\tif (!this._logger) {\n\t\t\tthis._logger = new ConsoleLogger();\n\t\t\tthis._logger.isVerbose = this.isVerbose;\n\t\t}\n\n\t\treturn this._logger;\n\t}\n\n\tset logger(logger) {\n\t\tthis._logger = logger;\n\t}\n\n\twarn(e, msg) {\n\t\tthis.log(e, \"warn\", \"yellow\", undefined, [`${msg}:`]);\n\t}\n\n\tfatal(e, msg) {\n\t\tthis.error(e, msg);\n\t\tprocess.exitCode = 1;\n\t}\n\n\tonce(type, e, msg) {\n\t\tif (e.__errorAlreadyLogged) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis[type || \"error\"](e, msg);\n\n\t\tObject.defineProperty(e, \"__errorAlreadyLogged\", {\n\t\t\tvalue: true,\n\t\t});\n\t}\n\n\terror(e, msg) {\n\t\tthis.log(e, \"error\", \"red\", true, [`${msg}:`]);\n\t}\n\n\tstatic getTotalErrorCount(e) {\n\t\tlet totalErrorCount = 0;\n\t\tlet errorCountRef = e;\n\t\twhile (errorCountRef) {\n\t\t\ttotalErrorCount++;\n\t\t\terrorCountRef = errorCountRef.originalError;\n\t\t}\n\t\treturn totalErrorCount;\n\t}\n\n\t//https://nodejs.org/api/process.html\n\tlog(e, type = \"log\", chalkColor = \"\", forceToConsole = false, messages = []) {\n\t\tif (process.env.DEBUG) {\n\t\t\tdebug(\"Full error object: %o\", inspect(e));\n\t\t}\n\n\t\tlet showStack = true;\n\t\tif (e.skipOriginalStack) {\n\t\t\t// Don’t show the full error stack trace\n\t\t\tshowStack = false;\n\t\t}\n\n\t\tlet totalErrorCount = EleventyErrorHandler.getTotalErrorCount(e);\n\t\tlet ref = e;\n\t\tlet index = 1;\n\t\tlet debugs = [];\n\t\twhile (ref) {\n\t\t\tlet nextRef = ref.originalError;\n\n\t\t\t// Unwrap cause from error and assign it to what Eleventy expects\n\t\t\tif (nextRef?.cause) {\n\t\t\t\tnextRef.originalError = nextRef.cause?.originalError ?? nextRef?.cause;\n\t\t\t}\n\n\t\t\tif (!nextRef && EleventyErrorUtil.hasEmbeddedError(ref.message)) {\n\t\t\t\tnextRef = EleventyErrorUtil.deconvertErrorToObject(ref);\n\t\t\t}\n\n\t\t\tif (nextRef?.skipOriginalStack) {\n\t\t\t\tshowStack = false;\n\t\t\t}\n\n\t\t\tmessages.push(\n\t\t\t\t`${totalErrorCount > 1 ? `${index}. ` : \"\"}${(\n\t\t\t\t\tEleventyErrorUtil.cleanMessage(ref.message) || \"(No error message provided)\"\n\t\t\t\t).trim()}${ref.name !== \"Error\" ? ` (via ${ref.name})` : \"\"}`,\n\t\t\t);\n\n\t\t\tif (process.env.DEBUG) {\n\t\t\t\tdebugs.push(`(${type} stack): ${ref.stack}`);\n\t\t\t} else if (!nextRef) {\n\t\t\t\t// last error in the loop\n\n\t\t\t\t// remove duplicate error messages if the stack contains the original message output above\n\t\t\t\tlet stackStr = ref.stack || \"\";\n\t\t\t\tif (e.removeDuplicateErrorStringFromOutput) {\n\t\t\t\t\tstackStr = stackStr.replace(\n\t\t\t\t\t\t`${ref.name}: ${ref.message}`,\n\t\t\t\t\t\t\"(Repeated output has been truncated…)\",\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tif (showStack) {\n\t\t\t\t\tmessages.push(\"\\nOriginal error stack trace: \" + stackStr);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tref = nextRef;\n\t\t\tindex++;\n\t\t}\n\n\t\tif (messages.length) {\n\t\t\tthis.logger.message(messages.join(\"\\n\"), type, chalkColor, forceToConsole);\n\t\t}\n\n\t\tif (debugs.length > 0) {\n\t\t\tfor (let msg of debugs) {\n\t\t\t\tdebug(msg);\n\t\t\t}\n\t\t}\n\t}\n}\n\nexport { EleventyErrorHandler };\n"
  },
  {
    "path": "src/Errors/EleventyErrorUtil.js",
    "content": "import TemplateContentPrematureUseError from \"./TemplateContentPrematureUseError.js\";\n\n/* Hack to workaround the variety of error handling schemes in template languages */\nclass EleventyErrorUtil {\n\tstatic get prefix() {\n\t\treturn \">>>>>11ty>>>>>\";\n\t}\n\tstatic get suffix() {\n\t\treturn \"<<<<<11ty<<<<<\";\n\t}\n\n\tstatic hasEmbeddedError(msg) {\n\t\tif (!msg) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn msg.includes(EleventyErrorUtil.prefix) && msg.includes(EleventyErrorUtil.suffix);\n\t}\n\n\tstatic cleanMessage(msg) {\n\t\tif (!msg) {\n\t\t\treturn \"\";\n\t\t}\n\n\t\tif (!EleventyErrorUtil.hasEmbeddedError(msg)) {\n\t\t\treturn \"\" + msg;\n\t\t}\n\n\t\treturn msg.slice(0, Math.max(0, msg.indexOf(EleventyErrorUtil.prefix)));\n\t}\n\n\tstatic deconvertErrorToObject(error) {\n\t\tif (!error || !error.message) {\n\t\t\tthrow new Error(`Could not convert error object from: ${error}`);\n\t\t}\n\t\tif (!EleventyErrorUtil.hasEmbeddedError(error.message)) {\n\t\t\treturn error;\n\t\t}\n\n\t\tlet msg = error.message;\n\t\tlet objectString = msg.substring(\n\t\t\tmsg.indexOf(EleventyErrorUtil.prefix) + EleventyErrorUtil.prefix.length,\n\t\t\tmsg.lastIndexOf(EleventyErrorUtil.suffix),\n\t\t);\n\t\tlet obj = JSON.parse(objectString);\n\t\tobj.name = error.name;\n\t\treturn obj;\n\t}\n\n\t// pass an error through a random template engine’s error handling unscathed\n\tstatic convertErrorToString(error) {\n\t\treturn (\n\t\t\tEleventyErrorUtil.prefix +\n\t\t\tJSON.stringify({ message: error.message, stack: error.stack }) +\n\t\t\tEleventyErrorUtil.suffix\n\t\t);\n\t}\n\n\tstatic isPrematureTemplateContentError(e) {\n\t\t// TODO the rest of the template engines\n\t\treturn (\n\t\t\te instanceof TemplateContentPrematureUseError ||\n\t\t\te?.cause instanceof TemplateContentPrematureUseError || // Custom (per Node-convention)\n\t\t\t[\"RenderError\", \"UndefinedVariableError\"].includes(e?.originalError?.name) && e?.originalError?.originalError instanceof TemplateContentPrematureUseError || // Liquid\n\t\t\te?.message?.includes(\"TemplateContentPrematureUseError\") // Nunjucks\n\t\t);\n\t}\n}\n\nexport default EleventyErrorUtil;\n"
  },
  {
    "path": "src/Errors/TemplateContentPrematureUseError.js",
    "content": "import EleventyBaseError from \"./EleventyBaseError.js\";\n\nclass TemplateContentPrematureUseError extends EleventyBaseError {}\n\nexport default TemplateContentPrematureUseError;\n"
  },
  {
    "path": "src/Errors/TemplateContentUnrenderedTemplateError.js",
    "content": "import EleventyBaseError from \"./EleventyBaseError.js\";\n\nclass TemplateContentUnrenderedTemplateError extends EleventyBaseError {}\n\nexport default TemplateContentUnrenderedTemplateError;\n"
  },
  {
    "path": "src/Errors/UsingCircularTemplateContentReferenceError.js",
    "content": "import EleventyBaseError from \"./EleventyBaseError.js\";\n\nclass UsingCircularTemplateContentReferenceError extends EleventyBaseError {}\n\nexport default UsingCircularTemplateContentReferenceError;\n"
  },
  {
    "path": "src/EventBus.js",
    "content": "import debugUtil from \"debug\";\n\nimport EventEmitter from \"./Util/AsyncEventEmitter.js\";\n\nconst debug = debugUtil(\"Eleventy:EventBus\");\n\n/**\n * @module 11ty/eleventy/EventBus\n * @ignore\n */\n\ndebug(\"Setting up global EventBus.\");\n/**\n * Provides a global event bus that modules deep down in the stack can\n * subscribe to from a global singleton for decoupled pub/sub.\n * @type {module:11ty/eleventy/Util/AsyncEventEmitter~AsyncEventEmitter}\n */\nlet bus = new EventEmitter();\nbus.setMaxListeners(100); // defaults to 10\n\ndebug(\"EventBus max listener count: %o\", bus.getMaxListeners());\n\nexport default bus;\n"
  },
  {
    "path": "src/FileSystemSearch.js",
    "content": "import { glob } from \"tinyglobby\";\nimport { TemplatePath } from \"@11ty/eleventy-utils\";\nimport debugUtil from \"debug\";\n\nimport GlobRemap from \"./Util/GlobRemap.js\";\nimport { isGlobMatch } from \"./Util/GlobMatcher.js\";\n\nconst debug = debugUtil(\"Eleventy:FileSystemSearch\");\n\nclass FileSystemSearch {\n\tconstructor() {\n\t\tthis.inputs = {};\n\t\tthis.outputs = {};\n\t\tthis.promises = {};\n\t\tthis.count = 0;\n\t}\n\n\tgetCacheKey(key, globs, options) {\n\t\tif (Array.isArray(globs)) {\n\t\t\tglobs = globs.sort();\n\t\t}\n\t\treturn key + JSON.stringify(globs) + JSON.stringify(options);\n\t}\n\n\t// returns a promise\n\tsearch(key, globs, options = {}) {\n\t\tdebug(\"Glob search (%o) searching for: %o\", key, globs);\n\n\t\tif (!Array.isArray(globs)) {\n\t\t\tglobs = [globs];\n\t\t}\n\n\t\t// Strip leading slashes from everything!\n\t\tglobs = globs.map((entry) => {\n\t\t\treturn TemplatePath.stripLeadingDotSlash(entry);\n\t\t});\n\n\t\tlet cwd = GlobRemap.getCwd(globs);\n\t\tif (cwd) {\n\t\t\toptions.cwd = cwd;\n\t\t}\n\n\t\tif (options.ignore && Array.isArray(options.ignore)) {\n\t\t\toptions.ignore = options.ignore.map((entry) => {\n\t\t\t\tentry = TemplatePath.stripLeadingDotSlash(entry);\n\n\t\t\t\treturn GlobRemap.remapInput(entry, cwd);\n\t\t\t});\n\t\t\tdebug(\"Glob search (%o) ignoring: %o\", key, options.ignore);\n\t\t}\n\n\t\tlet cacheKey = this.getCacheKey(key, globs, options);\n\n\t\t// Only after the promise has resolved\n\t\tif (this.outputs[cacheKey]) {\n\t\t\treturn Array.from(this.outputs[cacheKey]);\n\t\t}\n\n\t\tif (!this.promises[cacheKey]) {\n\t\t\tthis.inputs[cacheKey] = {\n\t\t\t\tinput: globs,\n\t\t\t\toptions,\n\t\t\t};\n\n\t\t\tthis.count++;\n\n\t\t\tglobs = globs.map((entry) => {\n\t\t\t\tif (cwd && entry.startsWith(cwd)) {\n\t\t\t\t\treturn GlobRemap.remapInput(entry, cwd);\n\t\t\t\t}\n\n\t\t\t\treturn entry;\n\t\t\t});\n\n\t\t\toptions.caseSensitiveMatch ??= false; // insensitive\n\t\t\toptions.dot ??= true;\n\n\t\t\tthis.promises[cacheKey] = glob(globs, options).then((results) => {\n\t\t\t\tthis.outputs[cacheKey] = new Set(\n\t\t\t\t\tresults.map((entry) => {\n\t\t\t\t\t\tlet remapped = GlobRemap.remapOutput(entry, options.cwd);\n\t\t\t\t\t\treturn TemplatePath.standardizeFilePath(remapped);\n\t\t\t\t\t}),\n\t\t\t\t);\n\n\t\t\t\treturn Array.from(this.outputs[cacheKey]);\n\t\t\t});\n\t\t}\n\n\t\t// may be an unresolved promise\n\t\treturn this.promises[cacheKey];\n\t}\n\n\t_modify(path, setOperation) {\n\t\tpath = TemplatePath.stripLeadingDotSlash(path);\n\n\t\tlet normalized = TemplatePath.standardizeFilePath(path);\n\n\t\tfor (let key in this.inputs) {\n\t\t\tlet { input, options } = this.inputs[key];\n\t\t\tif (\n\t\t\t\tisGlobMatch(path, input, {\n\t\t\t\t\tignore: options.ignore,\n\t\t\t\t})\n\t\t\t) {\n\t\t\t\tthis.outputs[key][setOperation](normalized);\n\t\t\t}\n\t\t}\n\t}\n\n\tadd(path) {\n\t\tthis._modify(path, \"add\");\n\t}\n\n\tdelete(path) {\n\t\tthis._modify(path, \"delete\");\n\t}\n\n\t// Issue #3859 get rid of chokidar globs\n\t// getAllOutputFiles() {\n\t// \treturn Object.values(this.outputs).map(set => Array.from(set)).flat();\n\t// }\n}\n\nexport default FileSystemSearch;\n"
  },
  {
    "path": "src/Filters/GetCollectionItem.js",
    "content": "export default function getCollectionItem(collection, page, modifier = 0) {\n\tlet j = 0;\n\tlet index;\n\tfor (let item of collection) {\n\t\tif (\n\t\t\titem.inputPath === page.inputPath &&\n\t\t\t(item.outputPath === page.outputPath || item.url === page.url)\n\t\t) {\n\t\t\tindex = j;\n\t\t\tbreak;\n\t\t}\n\t\tj++;\n\t}\n\n\tif (index !== undefined && collection?.length) {\n\t\tif (index + modifier >= 0 && index + modifier < collection.length) {\n\t\t\treturn collection[index + modifier];\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/Filters/GetCollectionItemIndex.js",
    "content": "// TODO locale-friendly, see GetLocaleCollectionItem.js)\nexport default function getCollectionItemIndex(collection, page) {\n\tif (!page) {\n\t\tpage = this.page;\n\t}\n\n\tlet j = 0;\n\tfor (let item of collection) {\n\t\tif (\n\t\t\titem.inputPath === page.inputPath &&\n\t\t\t(item.outputPath === page.outputPath || item.url === page.url)\n\t\t) {\n\t\t\treturn j;\n\t\t}\n\t\tj++;\n\t}\n}\n"
  },
  {
    "path": "src/Filters/GetLocaleCollectionItem.js",
    "content": "import getCollectionItem from \"./GetCollectionItem.js\";\n\n// Work with I18n Plugin src/Plugins/I18nPlugin.js to retrieve root pages (not i18n pages)\nfunction resolveRootPage(config, pageOverride, languageCode) {\n\tlet localeFilter = config.getFilter(\"locale_page\");\n\tif (!localeFilter || typeof localeFilter !== \"function\") {\n\t\treturn pageOverride;\n\t}\n\n\t// returns root/default-language `page` object\n\treturn localeFilter.call(this, pageOverride, languageCode);\n}\n\nfunction getLocaleCollectionItem(config, collection, pageOverride, langCode, indexModifier = 0) {\n\tif (!langCode) {\n\t\t// if page.lang exists (2.0.0-canary.14 and i18n plugin added, use page language)\n\t\tif (this.page.lang) {\n\t\t\tlangCode = this.page.lang;\n\t\t} else {\n\t\t\treturn getCollectionItem(collection, pageOverride || this.page, indexModifier);\n\t\t}\n\t}\n\n\tlet rootPage = resolveRootPage.call(this, config, pageOverride); // implied current page, default language\n\tlet modifiedRootItem = getCollectionItem(collection, rootPage, indexModifier);\n\tif (!modifiedRootItem) {\n\t\treturn; // no root item exists for the previous/next page\n\t}\n\n\t// Resolve modified root `page` back to locale `page`\n\t// This will return a non localized version of the page as a fallback\n\tlet modifiedLocalePage = resolveRootPage.call(this, config, modifiedRootItem.data.page, langCode);\n\t// already localized (or default language)\n\tif (!(\"__locale_page_resolved\" in modifiedLocalePage)) {\n\t\treturn modifiedRootItem;\n\t}\n\n\t// find the modified locale `page` again in `collections.all`\n\tlet all =\n\t\tthis.collections?.all ||\n\t\tthis.ctx?.collections?.all ||\n\t\tthis.context?.environments?.collections?.all ||\n\t\t[];\n\treturn getCollectionItem(all, modifiedLocalePage, 0);\n}\n\nexport default getLocaleCollectionItem;\n"
  },
  {
    "path": "src/Filters/Url.js",
    "content": "import { TemplatePath } from \"@11ty/eleventy-utils\";\n\nimport { isValidUrl } from \"../Util/UrlUtil.js\";\n\n// Note: This filter is used in the Eleventy Navigation plugin in versions prior to 0.3.4\nexport default function (url, pathPrefix) {\n\t// work with undefined\n\turl = url || \"\";\n\n\tif (isValidUrl(url) || (url.startsWith(\"//\") && url !== \"//\")) {\n\t\treturn url;\n\t}\n\n\tif (pathPrefix === undefined || typeof pathPrefix !== \"string\") {\n\t\t// When you retrieve this with config.getFilter(\"url\") it\n\t\t// grabs the pathPrefix argument from your config for you (see defaultConfig.js)\n\t\tthrow new Error(\"pathPrefix (String) is required in the `url` filter.\");\n\t}\n\n\tlet normUrl = TemplatePath.normalizeUrlPath(url);\n\tlet normRootDir = TemplatePath.normalizeUrlPath(\"/\", pathPrefix);\n\tlet normFull = TemplatePath.normalizeUrlPath(\"/\", pathPrefix, url);\n\tlet isRootDirTrailingSlash =\n\t\tnormRootDir.length && normRootDir.charAt(normRootDir.length - 1) === \"/\";\n\n\t// minor difference with straight `normalize`, \"\" resolves to root dir and not \".\"\n\t// minor difference with straight `normalize`, \"/\" resolves to root dir\n\tif (normUrl === \"/\" || normUrl === normRootDir) {\n\t\treturn normRootDir + (!isRootDirTrailingSlash ? \"/\" : \"\");\n\t} else if (normUrl.indexOf(\"/\") === 0) {\n\t\treturn normFull;\n\t}\n\n\treturn normUrl;\n}\n"
  },
  {
    "path": "src/GlobalDependencyMap.js",
    "content": "import debugUtil from \"debug\";\nimport { TemplatePath } from \"@11ty/eleventy-utils\";\n\nimport JavaScriptDependencies from \"./Util/JavaScriptDependencies.js\";\nimport PathNormalizer from \"./Util/PathNormalizer.js\";\nimport { TemplateDepGraph } from \"./Util/TemplateDepGraph.js\";\n\nconst debug = debugUtil(\"Eleventy:Dependencies\");\n\nclass GlobalDependencyMap {\n\t// dependency-graph requires these keys to be alphabetic strings\n\tstatic LAYOUT_KEY = \"layout\";\n\tstatic COLLECTION_PREFIX = \"__collection:\"; // must match TemplateDepGraph key\n\n\t#map;\n\t#templateConfig;\n\t#cachedUserConfigurationCollectionApiNames;\n\n\tstatic isCollection(entry) {\n\t\treturn entry.startsWith(this.COLLECTION_PREFIX);\n\t}\n\n\tstatic getTagName(entry) {\n\t\tif (this.isCollection(entry)) {\n\t\t\treturn entry.slice(this.COLLECTION_PREFIX.length);\n\t\t}\n\t}\n\n\tstatic getCollectionKeyForEntry(entry) {\n\t\treturn `${GlobalDependencyMap.COLLECTION_PREFIX}${entry}`;\n\t}\n\n\treset() {\n\t\tthis.#map = undefined;\n\t}\n\n\tsetIsEsm(isEsm) {\n\t\tthis.isEsm = isEsm;\n\t}\n\n\tsetTemplateConfig(templateConfig) {\n\t\tthis.#templateConfig = templateConfig;\n\n\t\t// These have leading dot slashes, but so do the paths from Eleventy\n\t\tthis.#templateConfig.userConfig.events.once(\"eleventy.layouts\", async (layouts) => {\n\t\t\tawait this.addLayoutsToMap(layouts);\n\t\t});\n\t}\n\n\tget userConfigurationCollectionApiNames() {\n\t\tif (this.#cachedUserConfigurationCollectionApiNames) {\n\t\t\treturn this.#cachedUserConfigurationCollectionApiNames;\n\t\t}\n\t\treturn Object.keys(this.#templateConfig.userConfig.getCollections()) || [];\n\t}\n\n\tinitializeUserConfigurationApiCollections() {\n\t\tthis.addCollectionApiNames(this.userConfigurationCollectionApiNames);\n\t}\n\n\t// For Testing\n\tsetCollectionApiNames(names = []) {\n\t\tthis.#cachedUserConfigurationCollectionApiNames = names;\n\t}\n\n\taddCollectionApiNames(names = []) {\n\t\tif (!names || names.length === 0) {\n\t\t\treturn;\n\t\t}\n\n\t\tfor (let collectionName of names) {\n\t\t\tthis.map.addConfigCollectionName(collectionName);\n\t\t}\n\t}\n\n\tfilterOutLayouts(nodes = []) {\n\t\treturn nodes.filter((node) => {\n\t\t\tif (GlobalDependencyMap.isLayoutNode(this.map, node)) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\treturn true;\n\t\t});\n\t}\n\n\tfilterOutCollections(nodes = []) {\n\t\treturn nodes.filter((node) => !node.startsWith(GlobalDependencyMap.COLLECTION_PREFIX));\n\t}\n\n\tstatic removeLayoutNodes(graph, nodeList) {\n\t\treturn nodeList.filter((node) => {\n\t\t\tif (this.isLayoutNode(graph, node)) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\treturn true;\n\t\t});\n\t}\n\n\tremoveLayoutNodes(normalizedLayouts) {\n\t\tlet nodes = this.map.overallOrder();\n\t\tfor (let node of nodes) {\n\t\t\tif (!GlobalDependencyMap.isLayoutNode(this.map, node)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// previous layout is not in the new layout map (no templates are using it)\n\t\t\tif (!normalizedLayouts[node]) {\n\t\t\t\tthis.map.removeNode(node);\n\t\t\t}\n\t\t\t// important: if the layout map changed to have different templates (but was not removed)\n\t\t\t// this is already handled by `resetNode` called via TemplateMap\n\t\t}\n\t}\n\n\t// Eleventy Layouts don’t show up in the dependency graph, so we handle those separately\n\tasync addLayoutsToMap(layouts) {\n\t\tlet normalizedLayouts = this.normalizeLayoutsObject(layouts);\n\n\t\t// Clear out any previous layout relationships to make way for the new ones\n\t\tthis.removeLayoutNodes(normalizedLayouts);\n\n\t\tfor (let layout in normalizedLayouts) {\n\t\t\t// We add this pre-emptively to add the `layout` data\n\t\t\tif (!this.map.hasNode(layout)) {\n\t\t\t\tthis.map.addNode(layout, {\n\t\t\t\t\ttype: GlobalDependencyMap.LAYOUT_KEY,\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tthis.map.setNodeData(layout, {\n\t\t\t\t\ttype: GlobalDependencyMap.LAYOUT_KEY,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// Potential improvement: only add the first template in the chain for a template and manage any upstream layouts by their own relationships\n\t\t\tfor (let pageTemplate of normalizedLayouts[layout]) {\n\t\t\t\tthis.addDependency(pageTemplate, [layout]);\n\t\t\t}\n\n\t\t\tif (this.#templateConfig?.shouldSpiderJavaScriptDependencies()) {\n\t\t\t\tlet deps = await JavaScriptDependencies.getDependencies([layout], this.isEsm);\n\t\t\t\tthis.addDependency(layout, deps);\n\t\t\t}\n\t\t}\n\t}\n\n\tget map() {\n\t\tif (!this.#map) {\n\t\t\t// this.#map = new DepGraph({ circular: true });\n\t\t\tthis.#map = new TemplateDepGraph();\n\t\t}\n\n\t\treturn this.#map;\n\t}\n\n\tset map(graph) {\n\t\tthis.#map = graph;\n\t}\n\n\tnormalizeNode(node) {\n\t\tif (!node) {\n\t\t\treturn;\n\t\t}\n\n\t\t// TODO tests for this\n\t\t// Fix URL objects passed in (sass does this)\n\t\tif (typeof node !== \"string\" && \"toString\" in node) {\n\t\t\tnode = node.toString();\n\t\t}\n\n\t\tif (typeof node !== \"string\") {\n\t\t\tthrow new Error(\"`addDependencies` files must be strings. Received:\" + node);\n\t\t}\n\n\t\treturn PathNormalizer.fullNormalization(node);\n\t}\n\n\tnormalizeLayoutsObject(layouts) {\n\t\tlet o = {};\n\t\tfor (let rawLayout in layouts) {\n\t\t\tlet layout = this.normalizeNode(rawLayout);\n\t\t\to[layout] = layouts[rawLayout].map((entry) => this.normalizeNode(entry));\n\t\t}\n\t\treturn o;\n\t}\n\n\tgetDependantsFor(node) {\n\t\tif (!node) {\n\t\t\treturn [];\n\t\t}\n\n\t\tnode = this.normalizeNode(node);\n\n\t\tif (!this.map.hasNode(node)) {\n\t\t\treturn [];\n\t\t}\n\n\t\t// Direct dependants and dependencies, both publish and consume from collections\n\t\treturn this.map.directDependantsOf(node);\n\t}\n\n\thasNode(node) {\n\t\treturn this.map.hasNode(this.normalizeNode(node));\n\t}\n\n\tfindCollectionsRemovedFrom(node, collectionNames) {\n\t\tif (!this.hasNode(node)) {\n\t\t\treturn new Set();\n\t\t}\n\n\t\tlet prevDeps = this.getDependantsFor(node)\n\t\t\t.map((entry) => GlobalDependencyMap.getTagName(entry))\n\t\t\t.filter(Boolean);\n\n\t\tlet prevDepsSet = new Set(prevDeps);\n\t\tlet deleted = new Set();\n\t\tfor (let dep of prevDepsSet) {\n\t\t\tif (!collectionNames.has(dep)) {\n\t\t\t\tdeleted.add(dep);\n\t\t\t}\n\t\t}\n\n\t\treturn deleted;\n\t}\n\n\tresetNode(node) {\n\t\tnode = this.normalizeNode(node);\n\n\t\tif (!this.map.hasNode(node)) {\n\t\t\treturn;\n\t\t}\n\n\t\t// We don’t want to remove relationships that consume this, controlled by the upstream content\n\t\t// for (let dep of this.map.directDependantsOf(node)) {\n\t\t//   this.map.removeDependency(dep, node);\n\t\t// }\n\n\t\tfor (let dep of this.map.directDependenciesOf(node)) {\n\t\t\tthis.map.removeDependency(node, dep);\n\t\t}\n\t}\n\n\tgetTemplatesThatConsumeCollections(collectionNames) {\n\t\tlet templates = new Set();\n\t\tfor (let name of collectionNames) {\n\t\t\tlet collectionKey = GlobalDependencyMap.getCollectionKeyForEntry(name);\n\t\t\tif (!this.map.hasNode(collectionKey)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tfor (let node of this.map.dependantsOf(collectionKey)) {\n\t\t\t\tif (!node.startsWith(GlobalDependencyMap.COLLECTION_PREFIX)) {\n\t\t\t\t\tif (!GlobalDependencyMap.isLayoutNode(this.map, node)) {\n\t\t\t\t\t\ttemplates.add(node);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn templates;\n\t}\n\n\tstatic isLayoutNode(graph, node) {\n\t\tif (!graph.hasNode(node)) {\n\t\t\treturn false;\n\t\t}\n\t\treturn graph.getNodeData(node)?.type === GlobalDependencyMap.LAYOUT_KEY;\n\t}\n\n\tgetLayoutsUsedBy(node) {\n\t\tnode = this.normalizeNode(node);\n\n\t\tif (!this.map.hasNode(node)) {\n\t\t\treturn [];\n\t\t}\n\n\t\tlet layouts = [];\n\n\t\t// include self, if layout\n\t\tif (GlobalDependencyMap.isLayoutNode(this.map, node)) {\n\t\t\tlayouts.push(node);\n\t\t}\n\n\t\tthis.map.dependantsOf(node).forEach((node) => {\n\t\t\t// we only want layouts\n\t\t\tif (GlobalDependencyMap.isLayoutNode(this.map, node)) {\n\t\t\t\treturn layouts.push(node);\n\t\t\t}\n\t\t});\n\n\t\treturn layouts;\n\t}\n\n\t// In order\n\t// Does not include original templatePaths (unless *they* are second-order relevant)\n\tgetTemplatesRelevantToTemplateList(templatePaths) {\n\t\tlet overallOrder = this.map.overallOrder();\n\t\toverallOrder = this.filterOutLayouts(overallOrder);\n\t\toverallOrder = this.filterOutCollections(overallOrder);\n\n\t\tlet relevantLookup = {};\n\t\tfor (let inputPath of templatePaths) {\n\t\t\tinputPath = TemplatePath.stripLeadingDotSlash(inputPath);\n\n\t\t\tlet deps = this.getDependencies(inputPath, false);\n\n\t\t\tif (Array.isArray(deps)) {\n\t\t\t\tlet paths = this.filterOutCollections(deps);\n\t\t\t\tfor (let node of paths) {\n\t\t\t\t\trelevantLookup[node] = true;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn overallOrder.filter((node) => {\n\t\t\tif (relevantLookup[node]) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\treturn false;\n\t\t});\n\t}\n\n\t// Layouts are not relevant to compile cache and can be ignored\n\tgetDependencies(node, includeLayouts = true) {\n\t\tnode = this.normalizeNode(node);\n\n\t\t// `false` means the Node was unknown\n\t\tif (!this.map.hasNode(node)) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif (includeLayouts) {\n\t\t\treturn this.map.dependenciesOf(node).filter(Boolean);\n\t\t}\n\n\t\treturn GlobalDependencyMap.removeLayoutNodes(this.map, this.map.dependenciesOf(node));\n\t}\n\n\t#addNode(name) {\n\t\tif (this.map.hasNode(name)) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.map.addNode(name);\n\t}\n\n\t// node arguments are already normalized\n\t#addDependency(from, toArray = []) {\n\t\tthis.#addNode(from); // only if not already added\n\n\t\tif (!Array.isArray(toArray)) {\n\t\t\tthrow new Error(\"Second argument to `addDependency` must be an Array.\");\n\t\t}\n\n\t\t// debug(\"%o depends on %o\", from, toArray);\n\t\tfor (let to of toArray) {\n\t\t\tthis.#addNode(to); // only if not already added\n\t\t\tif (from !== to) {\n\t\t\t\tthis.map.addDependency(from, to);\n\t\t\t}\n\t\t}\n\t}\n\n\taddDependency(from, toArray = []) {\n\t\tthis.#addDependency(\n\t\t\tthis.normalizeNode(from),\n\t\t\ttoArray.map((to) => this.normalizeNode(to)),\n\t\t);\n\t}\n\n\taddNewNodeRelationships(from, consumes = [], publishes = []) {\n\t\tconsumes = consumes.filter(Boolean);\n\t\tpublishes = publishes.filter(Boolean);\n\n\t\tdebug(\"%o consumes %o and publishes to %o\", from, consumes, publishes);\n\t\tfrom = this.normalizeNode(from);\n\n\t\tthis.map.addTemplate(from, consumes, publishes);\n\t}\n\n\t// Layouts are not relevant to compile cache and can be ignored\n\thasDependency(from, to, includeLayouts) {\n\t\tto = this.normalizeNode(to);\n\n\t\tlet deps = this.getDependencies(from, includeLayouts); // normalizes `from`\n\n\t\tif (!deps) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn deps.includes(to);\n\t}\n\n\t// Layouts are not relevant to compile cache and can be ignored\n\tisFileRelevantTo(fullTemplateInputPath, comparisonFile, includeLayouts) {\n\t\tfullTemplateInputPath = this.normalizeNode(fullTemplateInputPath);\n\t\tcomparisonFile = this.normalizeNode(comparisonFile);\n\n\t\t// No watch/serve changed file\n\t\tif (!comparisonFile) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// The file that changed is the relevant file\n\t\tif (fullTemplateInputPath === comparisonFile) {\n\t\t\treturn true;\n\t\t}\n\n\t\t// The file that changed is a dependency of the template\n\t\t// comparisonFile is used by fullTemplateInputPath\n\t\tif (this.hasDependency(fullTemplateInputPath, comparisonFile, includeLayouts)) {\n\t\t\treturn true;\n\t\t}\n\n\t\treturn false;\n\t}\n\n\tisFileUsedBy(parent, child, includeLayouts) {\n\t\tif (this.hasDependency(parent, child, includeLayouts)) {\n\t\t\t// child is used by parent\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\n\tgetTemplateOrder() {\n\t\tlet order = [];\n\t\tfor (let entry of this.map.overallOrder()) {\n\t\t\torder.push(entry);\n\t\t}\n\n\t\treturn order;\n\t}\n\n\tstringify() {\n\t\treturn JSON.stringify(this.map, function replacer(key, value) {\n\t\t\t// Serialize internal Map objects.\n\t\t\tif (value instanceof Map) {\n\t\t\t\tlet obj = {};\n\t\t\t\tfor (let [k, v] of value) {\n\t\t\t\t\tobj[k] = v;\n\t\t\t\t}\n\t\t\t\treturn obj;\n\t\t\t}\n\n\t\t\treturn value;\n\t\t});\n\t}\n\n\trestore(persisted) {\n\t\tlet obj = JSON.parse(persisted);\n\t\tlet graph = new TemplateDepGraph();\n\n\t\t// https://github.com/jriecken/dependency-graph/issues/44\n\t\t// Restore top level serialized Map objects (in stringify above)\n\t\tfor (let key in obj) {\n\t\t\tlet map = graph[key];\n\t\t\tfor (let k in obj[key]) {\n\t\t\t\tlet v = obj[key][k];\n\t\t\t\tmap.set(k, v);\n\t\t\t}\n\t\t}\n\t\tthis.map = graph;\n\t}\n}\n\nexport default GlobalDependencyMap;\n"
  },
  {
    "path": "src/LayoutCache.js",
    "content": "import { TemplatePath } from \"@11ty/eleventy-utils\";\n\nimport eventBus from \"./EventBus.js\";\n\n// Note: this is only used for TemplateLayout right now but could be used for more\n// Just be careful because right now the TemplateLayout cache keys are not directly mapped to paths\n// So you may get collisions if you use this for other things.\nclass LayoutCache {\n\tconstructor() {\n\t\tthis.cache = {};\n\t\tthis.cacheByInputPath = {};\n\t}\n\n\tclear() {\n\t\tthis.cache = {};\n\t\tthis.cacheByInputPath = {};\n\t}\n\n\t// alias\n\tremoveAll() {\n\t\tfor (let layoutFilePath in this.cacheByInputPath) {\n\t\t\tthis.remove(layoutFilePath);\n\t\t}\n\t\tthis.clear();\n\t}\n\n\tsize() {\n\t\treturn Object.keys(this.cacheByInputPath).length;\n\t}\n\n\tadd(layoutTemplate) {\n\t\tlet keys = new Set();\n\n\t\tif (typeof layoutTemplate === \"string\") {\n\t\t\tthrow new Error(\n\t\t\t\t\"Invalid argument type passed to LayoutCache->add(). Should be a TemplateLayout.\",\n\t\t\t);\n\t\t}\n\n\t\tif (\"getFullKey\" in layoutTemplate) {\n\t\t\tkeys.add(layoutTemplate.getFullKey());\n\t\t}\n\n\t\tif (\"getKey\" in layoutTemplate) {\n\t\t\t// if `key` was an alias, also set to the pathed layout value too\n\t\t\t// e.g. `layout: \"default\"` and `layout: \"default.liquid\"` will both map to the same template.\n\t\t\tkeys.add(layoutTemplate.getKey());\n\t\t}\n\n\t\tfor (let key of keys) {\n\t\t\tthis.cache[key] = layoutTemplate;\n\t\t}\n\n\t\t// also the full template input path for use with eleventy --serve/--watch e.g. `_includes/default.liquid` (see `remove` below)\n\t\tlet fullPath = TemplatePath.stripLeadingDotSlash(layoutTemplate.inputPath);\n\t\tthis.cacheByInputPath[fullPath] = layoutTemplate;\n\t}\n\n\thas(key) {\n\t\treturn key in this.cache;\n\t}\n\n\tget(key) {\n\t\tif (!this.has(key)) {\n\t\t\tthrow new Error(`Could not find ${key} in LayoutCache.`);\n\t\t}\n\n\t\treturn this.cache[key];\n\t}\n\n\tremove(layoutFilePath) {\n\t\tlayoutFilePath = TemplatePath.stripLeadingDotSlash(layoutFilePath);\n\t\tif (!this.cacheByInputPath[layoutFilePath]) {\n\t\t\t// not a layout file\n\t\t\treturn;\n\t\t}\n\n\t\tlet layoutTemplate = this.cacheByInputPath[layoutFilePath];\n\t\tlayoutTemplate.resetCaches();\n\n\t\tlet keys = layoutTemplate.getCacheKeys();\n\t\tfor (let key of keys) {\n\t\t\tdelete this.cache[key];\n\t\t}\n\n\t\tdelete this.cacheByInputPath[layoutFilePath];\n\t}\n}\n\nlet layoutCache = new LayoutCache();\n\neventBus.on(\"eleventy.resourceModified\", () => {\n\t// https://github.com/11ty/eleventy-plugin-bundle/issues/10\n\tlayoutCache.removeAll();\n});\n\n// singleton\nexport default layoutCache;\n"
  },
  {
    "path": "src/Plugins/HtmlBasePlugin.js",
    "content": "import { DeepCopy } from \"@11ty/eleventy-utils\";\nimport urlFilter from \"../Filters/Url.js\";\nimport PathPrefixer from \"../Util/PathPrefixer.js\";\nimport { HtmlTransformer } from \"../Util/HtmlTransformer.js\";\nimport { isValidUrl } from \"../Util/UrlUtil.js\";\n\nfunction addPathPrefixToUrl(url, pathPrefix, base) {\n\tlet u;\n\tif (base) {\n\t\tu = new URL(url, base);\n\t} else {\n\t\tu = new URL(url);\n\t}\n\n\t// Add pathPrefix **after** url is transformed using base\n\tif (pathPrefix) {\n\t\tu.pathname = PathPrefixer.joinUrlParts(pathPrefix, u.pathname);\n\t}\n\treturn u.toString();\n}\n\n// pathprefix is only used when overrideBase is a full URL\nfunction transformUrl(url, base, opts = {}) {\n\tlet { pathPrefix, pageUrl, htmlContext } = opts;\n\n\t// Warning, this will not work with HtmlTransformer, as we’ll receive \"false\" (string) here instead of `false` (boolean)\n\tif (url === false) {\n\t\tthrow new Error(\n\t\t\t`Invalid url transformed in the HTML \\`<base>\\` plugin.${url === false ? ` Did you attempt to link to a \\`permalink: false\\` page?` : \"\"} Received: ${url}`,\n\t\t);\n\t}\n\n\t// full URL, return as-is\n\tif (isValidUrl(url)) {\n\t\treturn url;\n\t}\n\n\t// Not a full URL, but with a full base URL\n\t// e.g. relative urls like \"subdir/\", \"../subdir\", \"./subdir\"\n\tif (isValidUrl(base)) {\n\t\t// convert relative paths to absolute path first using pageUrl\n\t\tif (pageUrl && !url.startsWith(\"/\")) {\n\t\t\tlet urlObj = new URL(url, `http://example.com${pageUrl}`);\n\t\t\turl = urlObj.pathname + (urlObj.hash || \"\");\n\t\t}\n\n\t\treturn addPathPrefixToUrl(url, pathPrefix, base);\n\t}\n\n\t// Not a full URL, nor a full base URL (call the built-in `url` filter)\n\treturn urlFilter(url, base);\n}\n\nfunction HtmlBasePlugin(eleventyConfig, defaultOptions = {}) {\n\tlet opts = DeepCopy(\n\t\t{\n\t\t\t// eleventyConfig.pathPrefix is new in Eleventy 2.0.0-canary.15\n\t\t\t// `base` can be a directory (for path prefix transformations)\n\t\t\t//    OR a full URL with origin and pathname\n\t\t\tbaseHref: eleventyConfig.pathPrefix,\n\n\t\t\textensions: \"html\",\n\t\t},\n\t\tdefaultOptions,\n\t);\n\n\t// `filters` option to rename filters was removed in 3.0.0-alpha.13\n\t// Renaming these would cause issues in other plugins (e.g. RSS)\n\tif (opts.filters !== undefined) {\n\t\tthrow new Error(\n\t\t\t\"The `filters` option in the HTML Base plugin was removed to prevent future cross-plugin compatibility issues.\",\n\t\t);\n\t}\n\n\tif (opts.baseHref === undefined) {\n\t\tthrow new Error(\"The `baseHref` option is required in the HTML Base plugin.\");\n\t}\n\n\televentyConfig.addFilter(\"addPathPrefixToFullUrl\", function (url) {\n\t\treturn addPathPrefixToUrl(url, eleventyConfig.pathPrefix);\n\t});\n\n\t// Apply to one URL\n\televentyConfig.addFilter(\n\t\t\"htmlBaseUrl\",\n\n\t\t/** @this {object} */\n\t\tfunction (url, baseOverride, pageUrlOverride) {\n\t\t\tlet base = baseOverride || opts.baseHref;\n\n\t\t\t// Do nothing with a default base\n\t\t\tif (base === \"/\") {\n\t\t\t\treturn url;\n\t\t\t}\n\n\t\t\treturn transformUrl(url, base, {\n\t\t\t\tpathPrefix: eleventyConfig.pathPrefix,\n\t\t\t\tpageUrl: pageUrlOverride || this.page?.url,\n\t\t\t});\n\t\t},\n\t);\n\n\t// Apply to a block of HTML\n\televentyConfig.addAsyncFilter(\n\t\t\"transformWithHtmlBase\",\n\n\t\t/** @this {object} */\n\t\tfunction (content, baseOverride, pageUrlOverride) {\n\t\t\tlet base = baseOverride || opts.baseHref;\n\n\t\t\t// Do nothing with a default base\n\t\t\tif (base === \"/\") {\n\t\t\t\treturn content;\n\t\t\t}\n\n\t\t\treturn HtmlTransformer.transformStandalone(content, (url, htmlContext) => {\n\t\t\t\treturn transformUrl(url.trim(), base, {\n\t\t\t\t\tpathPrefix: eleventyConfig.pathPrefix,\n\t\t\t\t\tpageUrl: pageUrlOverride || this.page?.url,\n\t\t\t\t\thtmlContext,\n\t\t\t\t});\n\t\t\t});\n\t\t},\n\t);\n\n\t// Apply to all HTML output in your project\n\televentyConfig.htmlTransformer.addUrlTransform(\n\t\topts.extensions,\n\n\t\t/** @this {object} */\n\t\tfunction (urlInMarkup, htmlContext) {\n\t\t\t// baseHref override is via renderTransforms filter for adding the absolute URL (e.g. https://example.com/pathPrefix/) for RSS/Atom/JSON feeds\n\t\t\treturn transformUrl(urlInMarkup.trim(), this.baseHref || opts.baseHref, {\n\t\t\t\tpathPrefix: eleventyConfig.pathPrefix,\n\t\t\t\tpageUrl: this.url,\n\t\t\t\thtmlContext,\n\t\t\t});\n\t\t},\n\t\t{\n\t\t\tpriority: -2, // priority is descending, so this runs last (especially after AutoCopy and InputPathToUrl transform)\n\t\t\tenabled: function (context) {\n\t\t\t\t// Enabled when pathPrefix is non-default or via renderTransforms\n\t\t\t\treturn Boolean(context.baseHref) || opts.baseHref !== \"/\";\n\t\t\t},\n\t\t},\n\t);\n}\n\nObject.defineProperty(HtmlBasePlugin, \"eleventyPackage\", {\n\tvalue: \"@11ty/eleventy/html-base-plugin\",\n});\n\nObject.defineProperty(HtmlBasePlugin, \"eleventyPluginOptions\", {\n\tvalue: {\n\t\tunique: true,\n\t},\n});\n\n// CommonJS friendly exports on .default\nObject.assign(HtmlBasePlugin, {\n\tapplyBaseToUrl: transformUrl,\n});\n\nexport default HtmlBasePlugin;\n\nexport { transformUrl as applyBaseToUrl };\n"
  },
  {
    "path": "src/Plugins/HtmlRelativeCopyPlugin.js",
    "content": "import { HtmlRelativeCopy } from \"../Util/HtmlRelativeCopy.js\";\n\n// https://github.com/11ty/eleventy/pull/3573\n\n// one HtmlRelativeCopy instance per entry\nfunction init(eleventyConfig, options) {\n\tif (!eleventyConfig.htmlTransformer) {\n\t\tthrow new Error(\n\t\t\t\"html-relative Passthrough Copy requires eleventyConfig.htmlTransformer support. Are you using the `@11ty/client` bundle? If so, try the `@11ty/client/eleventy` bundle instead.\",\n\t\t);\n\t}\n\n\tlet opts = Object.assign(\n\t\t{\n\t\t\textensions: \"html\",\n\t\t\tmatch: false, // can be one glob string or an array of globs\n\t\t\tpaths: [], // directories to also look in for files\n\t\t\tfailOnError: true, // fails when a path matches (via `match`) but not found on file system\n\t\t\tcopyOptions: undefined,\n\t\t},\n\t\toptions,\n\t);\n\n\tlet htmlrel = new HtmlRelativeCopy();\n\thtmlrel.setUserConfig(eleventyConfig);\n\thtmlrel.addMatchingGlob(opts.match);\n\thtmlrel.setFailOnError(opts.failOnError);\n\thtmlrel.setCopyOptions(opts.copyOptions);\n\n\televentyConfig.htmlTransformer.addUrlTransform(\n\t\topts.extensions,\n\t\tfunction (targetFilepathOrUrl) {\n\t\t\t// @ts-ignore\n\t\t\thtmlrel.copy(targetFilepathOrUrl, this.page.inputPath, this.page.outputPath);\n\n\t\t\t// TODO front matter option for manual copy\n\t\t\treturn targetFilepathOrUrl;\n\t\t},\n\t\t{\n\t\t\tenabled: () => htmlrel.isEnabled(),\n\t\t\t// - MUST run after other plugins but BEFORE HtmlBase plugin\n\t\t\tpriority: -1,\n\t\t},\n\t);\n\n\thtmlrel.addPaths(opts.paths);\n}\n\nfunction HtmlRelativeCopyPlugin(eleventyConfig) {\n\t// Important: if this is empty, no URL transforms are added\n\tfor (let options of eleventyConfig.passthroughCopiesHtmlRelative) {\n\t\tinit(eleventyConfig, options);\n\t}\n}\n\nObject.defineProperty(HtmlRelativeCopyPlugin, \"eleventyPackage\", {\n\tvalue: \"@11ty/eleventy/html-relative-copy-plugin\",\n});\n\nexport { HtmlRelativeCopyPlugin };\n"
  },
  {
    "path": "src/Plugins/I18nPlugin.js",
    "content": "import { bcp47Normalize } from \"bcp-47-normalize\";\nimport iso639 from \"iso-639-1\";\nimport { DeepCopy } from \"@11ty/eleventy-utils\";\n\n// pathPrefix note:\n// When using `locale_url` filter with the `url` filter, `locale_url` must run first like\n// `| locale_url | url`. If you run `| url | locale_url` it won’t match correctly.\n\n// TODO improvement would be to throw an error if `locale_url` finds a url with the\n// path prefix at the beginning? Would need a better way to know `url` has transformed a string\n// rather than just raw comparison.\n// e.g. --pathprefix=/en/ should return `/en/en/` for `/en/index.liquid`\n\nclass LangUtils {\n\tstatic getLanguageCodeFromInputPath(filepath) {\n\t\treturn (filepath || \"\").split(\"/\").find((entry) => Comparator.isLangCode(entry));\n\t}\n\n\tstatic getLanguageCodeFromUrl(url) {\n\t\tlet s = (url || \"\").split(\"/\");\n\t\treturn s.length > 0 && Comparator.isLangCode(s[1]) ? s[1] : \"\";\n\t}\n\n\tstatic swapLanguageCodeNoCheck(str, langCode) {\n\t\tlet found = false;\n\t\treturn str\n\t\t\t.split(\"/\")\n\t\t\t.map((entry) => {\n\t\t\t\t// only match the first one\n\t\t\t\tif (!found && Comparator.isLangCode(entry)) {\n\t\t\t\t\tfound = true;\n\t\t\t\t\treturn langCode;\n\t\t\t\t}\n\t\t\t\treturn entry;\n\t\t\t})\n\t\t\t.join(\"/\");\n\t}\n\n\tstatic swapLanguageCode(str, langCode) {\n\t\tif (!Comparator.isLangCode(langCode)) {\n\t\t\treturn str;\n\t\t}\n\n\t\treturn LangUtils.swapLanguageCodeNoCheck(str, langCode);\n\t}\n}\n\nclass Comparator {\n\t// https://en.wikipedia.org/wiki/IETF_language_tag#Relation_to_other_standards\n\t// Requires a ISO-639-1 language code at the start (2 characters before the first -)\n\tstatic isLangCode(code) {\n\t\tlet [s] = (code || \"\").split(\"-\");\n\t\tif (!iso639.validate(s)) {\n\t\t\treturn false;\n\t\t}\n\t\tif (!bcp47Normalize(code)) {\n\t\t\treturn false;\n\t\t}\n\t\treturn true;\n\t}\n\n\tstatic urlHasLangCode(url, code) {\n\t\tif (!Comparator.isLangCode(code)) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn url.split(\"/\").some((entry) => entry === code);\n\t}\n}\n\nfunction normalizeInputPath(inputPath, extensionMap) {\n\tif (extensionMap) {\n\t\treturn extensionMap.removeTemplateExtension(inputPath);\n\t}\n\treturn inputPath;\n}\n\n/*\n * Input: {\n *   '/en-us/test/': './test/stubs-i18n/en-us/test.11ty.js',\n *   '/en/test/': './test/stubs-i18n/en/test.liquid',\n *   '/es/test/': './test/stubs-i18n/es/test.njk',\n *   '/non-lang-file/': './test/stubs-i18n/non-lang-file.njk'\n * }\n *\n * Output: {\n *   '/en-us/test/': [ { url: '/en/test/' }, { url: '/es/test/' } ],\n *   '/en/test/': [ { url: '/en-us/test/' }, { url: '/es/test/' } ],\n *   '/es/test/': [ { url: '/en-us/test/' }, { url: '/en/test/' } ]\n * }\n */\nfunction getLocaleUrlsMap(urlToInputPath, extensionMap, options = {}) {\n\tlet filemap = {};\n\n\tfor (let url in urlToInputPath) {\n\t\t// Group number comes from Pagination.js\n\t\tlet { inputPath: originalFilepath, groupNumber } = urlToInputPath[url];\n\t\tlet filepath = normalizeInputPath(originalFilepath, extensionMap);\n\t\tlet replaced =\n\t\t\tLangUtils.swapLanguageCodeNoCheck(filepath, \"__11ty_i18n\") + `_group:${groupNumber}`;\n\n\t\tif (!filemap[replaced]) {\n\t\t\tfilemap[replaced] = [];\n\t\t}\n\n\t\tlet langCode = LangUtils.getLanguageCodeFromInputPath(originalFilepath);\n\t\tif (!langCode) {\n\t\t\tlangCode = LangUtils.getLanguageCodeFromUrl(url);\n\t\t}\n\t\tif (!langCode) {\n\t\t\tlangCode = options.defaultLanguage;\n\t\t}\n\n\t\tif (langCode) {\n\t\t\tfilemap[replaced].push({\n\t\t\t\turl,\n\t\t\t\tlang: langCode,\n\t\t\t\tlabel: iso639.getNativeName(langCode.split(\"-\")[0]),\n\t\t\t});\n\t\t} else {\n\t\t\tfilemap[replaced].push({ url });\n\t\t}\n\t}\n\n\t// Default sorted by lang code\n\tfor (let key in filemap) {\n\t\tfilemap[key].sort(function (a, b) {\n\t\t\tif (a.lang < b.lang) {\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t\tif (a.lang > b.lang) {\n\t\t\t\treturn 1;\n\t\t\t}\n\t\t\treturn 0;\n\t\t});\n\t}\n\n\t// map of input paths => array of localized urls\n\tlet urlMap = {};\n\tfor (let filepath in filemap) {\n\t\tfor (let entry of filemap[filepath]) {\n\t\t\tlet url = entry.url;\n\t\t\tif (!urlMap[url]) {\n\t\t\t\turlMap[url] = filemap[filepath].filter((entry) => {\n\t\t\t\t\tif (entry.lang) {\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\t\t\t\t\treturn entry.url !== url;\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\treturn urlMap;\n}\n\nfunction I18nPlugin(eleventyConfig, opts = {}) {\n\tlet options = DeepCopy(\n\t\t{\n\t\t\tdefaultLanguage: \"\",\n\t\t\tfilters: {\n\t\t\t\turl: \"locale_url\",\n\t\t\t\tlinks: \"locale_links\",\n\t\t\t},\n\t\t\terrorMode: \"strict\", // allow-fallback, never\n\t\t},\n\t\topts,\n\t);\n\n\tif (!options.defaultLanguage) {\n\t\tthrow new Error(\n\t\t\t\"You must specify a `defaultLanguage` in Eleventy’s Internationalization (I18N) plugin.\",\n\t\t);\n\t}\n\n\tlet extensionMap;\n\televentyConfig.on(\"eleventy.extensionmap\", (map) => {\n\t\textensionMap = map;\n\t});\n\n\tlet bench = eleventyConfig.benchmarkManager.get(\"Aggregate\");\n\tlet contentMaps = {};\n\televentyConfig.on(\"eleventy.contentMap\", function ({ urlToInputPath, inputPathToUrl }) {\n\t\tlet b = bench.get(\"(i18n Plugin) Setting up content map.\");\n\t\tb.before();\n\t\tcontentMaps.inputPathToUrl = inputPathToUrl;\n\t\tcontentMaps.urlToInputPath = urlToInputPath;\n\n\t\tcontentMaps.localeUrlsMap = getLocaleUrlsMap(urlToInputPath, extensionMap, options);\n\t\tb.after();\n\t});\n\n\televentyConfig.addGlobalData(\"eleventyComputed.page.lang\", () => {\n\t\t// if addGlobalData receives a function it will execute it immediately,\n\t\t// so we return a nested function for computed data\n\t\treturn (data) => {\n\t\t\treturn LangUtils.getLanguageCodeFromUrl(data.page.url) || options.defaultLanguage;\n\t\t};\n\t});\n\n\t// Normalize a theoretical URL based on the current page’s language\n\t// If a non-localized file exists, returns the URL without a language assigned\n\t// Fails if no file exists (localized and not localized)\n\televentyConfig.addFilter(options.filters.url, function (url, langCodeOverride) {\n\t\tlet langCode =\n\t\t\tlangCodeOverride ||\n\t\t\tLangUtils.getLanguageCodeFromUrl(this.page?.url) ||\n\t\t\toptions.defaultLanguage;\n\n\t\t// Already has a language code on it and has a relevant url with the target language code\n\t\tif (\n\t\t\tcontentMaps.localeUrlsMap[url] ||\n\t\t\t(!url.endsWith(\"/\") && contentMaps.localeUrlsMap[`${url}/`])\n\t\t) {\n\t\t\tfor (let existingUrlObj of contentMaps.localeUrlsMap[url] ||\n\t\t\t\tcontentMaps.localeUrlsMap[`${url}/`]) {\n\t\t\t\tif (Comparator.urlHasLangCode(existingUrlObj.url, langCode)) {\n\t\t\t\t\treturn existingUrlObj.url;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Needs the language code prepended to the URL\n\t\tlet prependedLangCodeUrl = `/${langCode}${url}`;\n\t\tif (\n\t\t\tcontentMaps.localeUrlsMap[prependedLangCodeUrl] ||\n\t\t\t(!prependedLangCodeUrl.endsWith(\"/\") && contentMaps.localeUrlsMap[`${prependedLangCodeUrl}/`])\n\t\t) {\n\t\t\treturn prependedLangCodeUrl;\n\t\t}\n\n\t\tif (\n\t\t\tcontentMaps.urlToInputPath[url] ||\n\t\t\t(!url.endsWith(\"/\") && contentMaps.urlToInputPath[`${url}/`])\n\t\t) {\n\t\t\t// this is not a localized file (independent of a language code)\n\t\t\tif (options.errorMode === \"strict\") {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Localized file for URL ${prependedLangCodeUrl} was not found in your project. A non-localized version does exist—are you sure you meant to use the \\`${options.filters.url}\\` filter for this? You can bypass this error using the \\`errorMode\\` option in the I18N plugin (current value: \"${options.errorMode}\").`,\n\t\t\t\t);\n\t\t\t}\n\t\t} else if (options.errorMode === \"allow-fallback\") {\n\t\t\t// You’re linking to a localized file that doesn’t exist!\n\t\t\tthrow new Error(\n\t\t\t\t`Localized file for URL ${prependedLangCodeUrl} was not found in your project! You will need to add it if you want to link to it using the \\`${options.filters.url}\\` filter. You can bypass this error using the \\`errorMode\\` option in the I18N plugin (current value: \"${options.errorMode}\").`,\n\t\t\t);\n\t\t}\n\n\t\treturn url;\n\t});\n\n\t// Refactor to use url\n\t// Find the links that are localized alternates to the inputPath argument\n\televentyConfig.addFilter(options.filters.links, function (urlOverride) {\n\t\tlet url = urlOverride || this.page?.url;\n\t\treturn (contentMaps.localeUrlsMap[url] || []).filter((entry) => {\n\t\t\treturn entry.url !== url;\n\t\t});\n\t});\n\n\t// Returns a `page`-esque variable for the root default language page\n\t// If paginated, returns first result only\n\televentyConfig.addFilter(\n\t\t\"locale_page\", // This is not exposed in `options` because it is an Eleventy internals filter (used in get*CollectionItem filters)\n\t\tfunction (pageOverride, languageCode) {\n\t\t\t// both args here are optional\n\t\t\tif (!languageCode) {\n\t\t\t\tlanguageCode = options.defaultLanguage;\n\t\t\t}\n\n\t\t\tlet page = pageOverride || this.page;\n\t\t\tlet url; // new url\n\t\t\tif (contentMaps.localeUrlsMap[page.url]) {\n\t\t\t\tfor (let entry of contentMaps.localeUrlsMap[page.url]) {\n\t\t\t\t\tif (entry.lang === languageCode) {\n\t\t\t\t\t\turl = entry.url;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tlet inputPath = LangUtils.swapLanguageCode(page.inputPath, languageCode);\n\n\t\t\tif (\n\t\t\t\t!url ||\n\t\t\t\t!Array.isArray(contentMaps.inputPathToUrl[inputPath]) ||\n\t\t\t\tcontentMaps.inputPathToUrl[inputPath].length === 0\n\t\t\t) {\n\t\t\t\t// no internationalized pages found\n\t\t\t\treturn page;\n\t\t\t}\n\n\t\t\tlet result = {\n\t\t\t\t// // note that the permalink/slug may be different for the localized file!\n\t\t\t\turl,\n\t\t\t\tinputPath,\n\t\t\t\tfilePathStem: LangUtils.swapLanguageCode(page.filePathStem, languageCode),\n\t\t\t\t// outputPath is omitted here, not necessary for GetCollectionItem.js if url is provided\n\t\t\t\t__locale_page_resolved: true,\n\t\t\t};\n\t\t\treturn result;\n\t\t},\n\t);\n}\n\nexport { Comparator, LangUtils };\n\nObject.defineProperty(I18nPlugin, \"eleventyPackage\", {\n\tvalue: \"@11ty/eleventy/i18n-plugin\",\n});\n\nObject.defineProperty(I18nPlugin, \"eleventyPluginOptions\", {\n\tvalue: {\n\t\tunique: true,\n\t},\n});\n\n// CommonJS friendly exports on .default\nObject.assign(I18nPlugin, {\n\tComparator,\n\tLangUtils,\n});\n\nexport default I18nPlugin;\n"
  },
  {
    "path": "src/Plugins/IdAttributePlugin.js",
    "content": "import matchHelper from \"posthtml-match-helper\";\nimport { decodeHTML } from \"entities\";\n\nconst POSTHTML_PLUGIN_NAME = \"11ty/eleventy/id-attribute\";\n\nfunction getTextNodeContent(node) {\n\tlet ignoredAttr = node.attrs?.[\"eleventy:id-ignore\"];\n\tif (ignoredAttr === \"\" || ignoredAttr === true) {\n\t\tdelete node.attrs[\"eleventy:id-ignore\"];\n\t\treturn \"\";\n\t}\n\tif (!node.content) {\n\t\treturn \"\";\n\t}\n\n\treturn node.content\n\t\t.map((entry) => {\n\t\t\tif (typeof entry === \"string\") {\n\t\t\t\treturn entry;\n\t\t\t}\n\t\t\tif (Array.isArray(entry.content)) {\n\t\t\t\treturn getTextNodeContent(entry);\n\t\t\t}\n\t\t\treturn \"\";\n\t\t})\n\t\t.join(\"\");\n}\n\nexport function IdAttributePlugin(eleventyConfig, options = {}) {\n\tif (!options.slugify) {\n\t\toptions.slugify = eleventyConfig.getFilter(\"slugify\");\n\t}\n\tif (!options.selector) {\n\t\toptions.selector = \"[id],h1,h2,h3,h4,h5,h6\";\n\t}\n\toptions.decodeEntities = options.decodeEntities ?? true;\n\toptions.checkDuplicates = options.checkDuplicates ?? \"error\";\n\n\televentyConfig.htmlTransformer.addPosthtmlPlugin(\n\t\t\"html\",\n\t\tfunction idAttributePosthtmlPlugin(pluginOptions = {}) {\n\t\t\tif (typeof options.filter === \"function\") {\n\t\t\t\tif (options.filter(pluginOptions) === false) {\n\t\t\t\t\treturn function () {};\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn function (tree) {\n\t\t\t\t// One per page\n\t\t\t\tlet conflictCheck = {};\n\t\t\t\t// Cache heading nodes for conflict resolution\n\t\t\t\tlet headingNodes = {};\n\n\t\t\t\ttree.match(matchHelper(options.selector), function (node) {\n\t\t\t\t\tif (node.attrs?.id) {\n\t\t\t\t\t\tlet id = node.attrs?.id;\n\t\t\t\t\t\tif (conflictCheck[id]) {\n\t\t\t\t\t\t\tconflictCheck[id]++;\n\t\t\t\t\t\t\tif (headingNodes[id]) {\n\t\t\t\t\t\t\t\t// Rename conflicting assigned heading id\n\t\t\t\t\t\t\t\tlet newId = `${id}-${conflictCheck[id]}`;\n\t\t\t\t\t\t\t\theadingNodes[newId] = headingNodes[id];\n\t\t\t\t\t\t\t\theadingNodes[newId].attrs.id = newId;\n\t\t\t\t\t\t\t\tdelete headingNodes[id];\n\t\t\t\t\t\t\t} else if (options.checkDuplicates === \"error\") {\n\t\t\t\t\t\t\t\t// Existing `id` conflicts with assigned heading id, throw error\n\t\t\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t\t\t'You have more than one HTML `id` attribute using the same value (id=\"' +\n\t\t\t\t\t\t\t\t\t\tid +\n\t\t\t\t\t\t\t\t\t\t'\") in your template (' +\n\t\t\t\t\t\t\t\t\t\tpluginOptions.page.inputPath +\n\t\t\t\t\t\t\t\t\t\t\"). You can disable this error in the IdAttribute plugin with the `checkDuplicates: false` option.\",\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tconflictCheck[id] = 1;\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (!node.attrs?.id && node.content) {\n\t\t\t\t\t\tnode.attrs = node.attrs || {};\n\t\t\t\t\t\tlet textContent = getTextNodeContent(node);\n\t\t\t\t\t\tif (options.decodeEntities) {\n\t\t\t\t\t\t\ttextContent = decodeHTML(textContent);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tlet id = options.slugify(textContent);\n\n\t\t\t\t\t\tif (conflictCheck[id]) {\n\t\t\t\t\t\t\tconflictCheck[id]++;\n\t\t\t\t\t\t\tid = `${id}-${conflictCheck[id]}`;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tconflictCheck[id] = 1;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\theadingNodes[id] = node;\n\t\t\t\t\t\tnode.attrs.id = id;\n\t\t\t\t\t}\n\n\t\t\t\t\treturn node;\n\t\t\t\t});\n\t\t\t};\n\t\t},\n\t\t{\n\t\t\t// pluginOptions\n\t\t\tname: POSTHTML_PLUGIN_NAME,\n\t\t},\n\t);\n}\n"
  },
  {
    "path": "src/Plugins/InputPathToUrl.js",
    "content": "import path from \"node:path\";\nimport { TemplatePath } from \"@11ty/eleventy-utils\";\nimport { isValidUrl } from \"../Util/UrlUtil.js\";\n\nfunction getValidPath(contentMap, testPath) {\n\t// if the path is coming from Markdown, it may be encoded\n\tlet normalized = TemplatePath.addLeadingDotSlash(decodeURIComponent(testPath));\n\n\t// it must exist in the content map to be valid\n\tif (contentMap[normalized]) {\n\t\treturn normalized;\n\t}\n}\n\nfunction normalizeInputPath(targetInputPath, inputDir, sourceInputPath, contentMap) {\n\t// inputDir is optional at the beginning of the developer supplied-path\n\n\t// Input directory already on the input path\n\tif (TemplatePath.join(targetInputPath).startsWith(TemplatePath.join(inputDir))) {\n\t\tlet absolutePath = getValidPath(contentMap, targetInputPath);\n\t\tif (absolutePath) {\n\t\t\treturn absolutePath;\n\t\t}\n\t}\n\n\t// Relative to project input directory\n\tlet relativeToInputDir = getValidPath(contentMap, TemplatePath.join(inputDir, targetInputPath));\n\tif (relativeToInputDir) {\n\t\treturn relativeToInputDir;\n\t}\n\n\tif (targetInputPath && !path.isAbsolute(targetInputPath)) {\n\t\t// Relative to source file’s input path\n\t\tlet sourceInputDir = TemplatePath.getDirFromFilePath(sourceInputPath);\n\t\tlet relativeToSourceFile = getValidPath(\n\t\t\tcontentMap,\n\t\t\tTemplatePath.join(sourceInputDir, targetInputPath),\n\t\t);\n\t\tif (relativeToSourceFile) {\n\t\t\treturn relativeToSourceFile;\n\t\t}\n\t}\n\n\t// the transform may have sent in a URL so we just return it as-is\n\treturn targetInputPath;\n}\n\nfunction parseFilePath(filepath) {\n\tif (filepath.startsWith(\"#\") || filepath.startsWith(\"?\")) {\n\t\treturn [filepath, \"\"];\n\t}\n\n\ttry {\n\t\t/* u: URL {\n\t\t\thref: 'file:///tmpl.njk#anchor',\n\t\t\torigin: 'null',\n\t\t\tprotocol: 'file:',\n\t\t\tusername: '',\n\t\t\tpassword: '',\n\t\t\thost: '',\n\t\t\thostname: '',\n\t\t\tport: '',\n\t\t\tpathname: '/tmpl.njk',\n\t\t\tsearch: '',\n\t\t\tsearchParams: URLSearchParams {},\n\t\t\thash: '#anchor'\n\t\t} */\n\n\t\t// Note that `node:url` -> pathToFileURL creates an absolute path, which we don’t want\n\t\t// URL(`file:#anchor`) gives back a pathname of `/`\n\t\tlet u = new URL(`file:${filepath}`);\n\t\tfilepath = filepath.replace(u.search, \"\"); // includes ?\n\t\tfilepath = filepath.replace(u.hash, \"\"); // includes #\n\n\t\treturn [\n\t\t\t// search includes ?, hash includes #\n\t\t\tu.search + u.hash,\n\t\t\tfilepath,\n\t\t];\n\t} catch (e) {\n\t\treturn [\"\", filepath];\n\t}\n}\n\nfunction FilterPlugin(eleventyConfig) {\n\tlet contentMap;\n\televentyConfig.on(\"eleventy.contentMap\", function ({ inputPathToUrl }) {\n\t\tcontentMap = inputPathToUrl;\n\t});\n\n\televentyConfig.addFilter(\"inputPathToUrl\", function (targetFilePath) {\n\t\tif (!contentMap) {\n\t\t\tthrow new Error(\"Internal error: contentMap not available for `inputPathToUrl` filter.\");\n\t\t}\n\n\t\tif (isValidUrl(targetFilePath)) {\n\t\t\treturn targetFilePath;\n\t\t}\n\n\t\tlet inputDir = eleventyConfig.directories.input;\n\t\tlet suffix;\n\t\t[suffix, targetFilePath] = parseFilePath(targetFilePath);\n\t\tif (targetFilePath) {\n\t\t\ttargetFilePath = normalizeInputPath(\n\t\t\t\ttargetFilePath,\n\t\t\t\tinputDir,\n\t\t\t\t// @ts-ignore\n\t\t\t\tthis.page.inputPath,\n\t\t\t\tcontentMap,\n\t\t\t);\n\t\t}\n\n\t\tlet urls = contentMap[targetFilePath];\n\t\tif (!urls || urls.length === 0) {\n\t\t\tthrow new Error(\n\t\t\t\t\"`inputPathToUrl` filter could not find a matching target for \" + targetFilePath,\n\t\t\t);\n\t\t}\n\n\t\treturn `${urls[0]}${suffix}`;\n\t});\n}\n\nfunction TransformPlugin(eleventyConfig, defaultOptions = {}) {\n\tlet opts = Object.assign(\n\t\t{\n\t\t\textensions: \"html\",\n\t\t},\n\t\tdefaultOptions,\n\t);\n\n\tlet contentMap = null;\n\televentyConfig.on(\"eleventy.contentMap\", function ({ inputPathToUrl }) {\n\t\tcontentMap = inputPathToUrl;\n\t});\n\n\televentyConfig.htmlTransformer.addUrlTransform(opts.extensions, function (targetFilepathOrUrl) {\n\t\tif (!contentMap) {\n\t\t\tthrow new Error(\"Internal error: contentMap not available for the `pathToUrl` Transform.\");\n\t\t}\n\t\tif (isValidUrl(targetFilepathOrUrl)) {\n\t\t\treturn targetFilepathOrUrl;\n\t\t}\n\n\t\tlet inputDir = eleventyConfig.directories.input;\n\n\t\tlet suffix;\n\t\t[suffix, targetFilepathOrUrl] = parseFilePath(targetFilepathOrUrl);\n\t\tif (targetFilepathOrUrl) {\n\t\t\ttargetFilepathOrUrl = normalizeInputPath(\n\t\t\t\ttargetFilepathOrUrl,\n\t\t\t\tinputDir,\n\t\t\t\t// @ts-ignore\n\t\t\t\tthis.page.inputPath,\n\t\t\t\tcontentMap,\n\t\t\t);\n\t\t}\n\n\t\tlet urls = contentMap[targetFilepathOrUrl];\n\t\tif (!targetFilepathOrUrl || !urls || urls.length === 0) {\n\t\t\t// fallback, transforms don’t error on missing paths (though the pathToUrl filter does)\n\t\t\treturn `${targetFilepathOrUrl}${suffix}`;\n\t\t}\n\n\t\treturn `${urls[0]}${suffix}`;\n\t});\n}\n\nObject.defineProperty(FilterPlugin, \"eleventyPackage\", {\n\tvalue: \"@11ty/eleventy/inputpath-to-url-filter-plugin\",\n});\n\nObject.defineProperty(FilterPlugin, \"eleventyPluginOptions\", {\n\tvalue: {\n\t\tunique: true,\n\t},\n});\n\nObject.defineProperty(TransformPlugin, \"eleventyPackage\", {\n\tvalue: \"@11ty/eleventy/inputpath-to-url-transform-plugin\",\n});\n\nObject.defineProperty(TransformPlugin, \"eleventyPluginOptions\", {\n\tvalue: {\n\t\tunique: true,\n\t},\n});\n\nexport default TransformPlugin;\n\nexport { FilterPlugin, TransformPlugin };\n"
  },
  {
    "path": "src/Plugins/Pagination.js",
    "content": "import { isPlainObject } from \"@11ty/eleventy-utils\";\nimport lodash from \"@11ty/lodash-custom\";\nimport { DeepCopy } from \"@11ty/eleventy-utils\";\n\nimport EleventyBaseError from \"../Errors/EleventyBaseError.js\";\nimport { ProxyWrap } from \"../Util/Objects/ProxyWrap.js\";\n// import { DeepFreeze } from \"../Util/Objects/DeepFreeze.js\";\nimport TemplateData from \"../Data/TemplateData.js\";\n\nconst { set: lodashSet, get: lodashGet, chunk: lodashChunk } = lodash;\n\nclass PaginationConfigError extends EleventyBaseError {}\nclass PaginationError extends EleventyBaseError {}\n\nclass Pagination {\n\tconstructor(tmpl, data, config) {\n\t\tif (!config) {\n\t\t\tthrow new PaginationConfigError(\"Expected `config` argument to Pagination class.\");\n\t\t}\n\n\t\tthis.config = config;\n\n\t\tthis.setTemplate(tmpl);\n\t\tthis.setData(data);\n\t}\n\n\tget inputPathForErrorMessages() {\n\t\tif (this.template) {\n\t\t\treturn ` (${this.template.inputPath})`;\n\t\t}\n\t\treturn \"\";\n\t}\n\n\tstatic hasPagination(data) {\n\t\treturn \"pagination\" in data;\n\t}\n\n\thasPagination() {\n\t\tif (!this.data) {\n\t\t\tthrow new Error(\n\t\t\t\t`Missing \\`setData\\` call for Pagination object${this.inputPathForErrorMessages}`,\n\t\t\t);\n\t\t}\n\t\treturn Pagination.hasPagination(this.data);\n\t}\n\n\tcircularReferenceCheck(data) {\n\t\tlet key = data.pagination.data;\n\t\tlet includedTags = TemplateData.getIncludedTagNames(data);\n\n\t\tfor (let tag of includedTags) {\n\t\t\tif (`collections.${tag}` === key) {\n\t\t\t\tthrow new PaginationError(\n\t\t\t\t\t`Pagination circular reference${this.inputPathForErrorMessages}, data:\\`${key}\\` iterates over both the \\`${tag}\\` collection and also supplies pages to that collection.`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\tsetData(data) {\n\t\tthis.data = data || {};\n\t\tthis.target = [];\n\n\t\tif (!this.hasPagination()) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (!data.pagination) {\n\t\t\tthrow new Error(\n\t\t\t\t`Misconfigured pagination data in template front matter${this.inputPathForErrorMessages} (YAML front matter precaution: did you use tabs and not spaces for indentation?).`,\n\t\t\t);\n\t\t} else if (!(\"size\" in data.pagination)) {\n\t\t\tthrow new Error(\n\t\t\t\t`Missing pagination size in front matter data${this.inputPathForErrorMessages}`,\n\t\t\t);\n\t\t}\n\t\tthis.circularReferenceCheck(data);\n\n\t\tthis.size = data.pagination.size;\n\t\tthis.alias = data.pagination.alias;\n\t\tthis.fullDataSet = this._get(this.data, this._getDataKey());\n\t\t// this returns an array\n\t\tthis.target = this._resolveItems();\n\t\tthis.chunkedItems = this.pagedItems;\n\t}\n\n\tsetTemplate(tmpl) {\n\t\tthis.template = tmpl;\n\t}\n\n\t_getDataKey() {\n\t\treturn this.data.pagination.data;\n\t}\n\n\tshouldResolveDataToObjectValues() {\n\t\tif (\"resolve\" in this.data.pagination) {\n\t\t\treturn this.data.pagination.resolve === \"values\";\n\t\t}\n\t\treturn false;\n\t}\n\n\tisFiltered(value) {\n\t\tif (\"filter\" in this.data.pagination) {\n\t\t\tlet filtered = this.data.pagination.filter;\n\t\t\tif (Array.isArray(filtered)) {\n\t\t\t\treturn filtered.indexOf(value) > -1;\n\t\t\t}\n\n\t\t\treturn filtered === value;\n\t\t}\n\n\t\treturn false;\n\t}\n\n\t_has(target, key) {\n\t\tlet notFoundValue = \"__NOT_FOUND_ERROR__\";\n\t\tlet data = lodashGet(target, key, notFoundValue);\n\t\treturn data !== notFoundValue;\n\t}\n\n\t_get(target, key) {\n\t\tlet notFoundValue = \"__NOT_FOUND_ERROR__\";\n\t\tlet data = lodashGet(target, key, notFoundValue);\n\t\tif (data === notFoundValue) {\n\t\t\tthrow new Error(\n\t\t\t\t`Could not find pagination data${this.inputPathForErrorMessages}, went looking for: ${key}`,\n\t\t\t);\n\t\t}\n\t\treturn data;\n\t}\n\n\t_resolveItems() {\n\t\tlet keys;\n\t\tif (Array.isArray(this.fullDataSet)) {\n\t\t\tkeys = this.fullDataSet;\n\t\t\tthis.paginationTargetType = \"array\";\n\t\t} else if (isPlainObject(this.fullDataSet)) {\n\t\t\tthis.paginationTargetType = \"object\";\n\t\t\tif (this.shouldResolveDataToObjectValues()) {\n\t\t\t\tkeys = Object.values(this.fullDataSet);\n\t\t\t} else {\n\t\t\t\tkeys = Object.keys(this.fullDataSet);\n\t\t\t}\n\t\t} else {\n\t\t\tthrow new Error(\n\t\t\t\t`Unexpected data found in pagination target${this.inputPathForErrorMessages}: expected an Array or an Object.`,\n\t\t\t);\n\t\t}\n\n\t\t// keys must be an array\n\t\tlet result = keys.slice();\n\n\t\tif (this.data.pagination.before && typeof this.data.pagination.before === \"function\") {\n\t\t\t// we don’t need to make a copy of this because we .slice() above to create a new copy\n\t\t\tlet fns = {};\n\t\t\tif (this.config) {\n\t\t\t\tfns = this.config.javascriptFunctions;\n\t\t\t}\n\t\t\tresult = this.data.pagination.before.call(fns, result, this.data);\n\t\t}\n\n\t\tif (this.data.pagination.reverse === true) {\n\t\t\tresult = result.reverse();\n\t\t}\n\n\t\tif (this.data.pagination.filter) {\n\t\t\tresult = result.filter((value) => !this.isFiltered(value));\n\t\t}\n\n\t\treturn result;\n\t}\n\n\tget pagedItems() {\n\t\tif (!this.data) {\n\t\t\tthrow new Error(\n\t\t\t\t`Missing \\`setData\\` call for Pagination object${this.inputPathForErrorMessages}`,\n\t\t\t);\n\t\t}\n\n\t\tconst chunks = lodashChunk(this.target, this.size);\n\t\tif (this.data.pagination?.generatePageOnEmptyData) {\n\t\t\treturn chunks.length ? chunks : [[]];\n\t\t} else {\n\t\t\treturn chunks;\n\t\t}\n\t}\n\n\tgetPageCount() {\n\t\tif (!this.hasPagination()) {\n\t\t\treturn 0;\n\t\t}\n\n\t\treturn this.chunkedItems.length;\n\t}\n\n\tgetNormalizedItems(pageItems) {\n\t\treturn this.size === 1 ? pageItems[0] : pageItems;\n\t}\n\n\tgetOverrideDataPages(items, pageNumber) {\n\t\treturn {\n\t\t\t// See Issue #345 for more examples\n\t\t\tpage: {\n\t\t\t\tprevious: pageNumber > 0 ? this.getNormalizedItems(items[pageNumber - 1]) : null,\n\t\t\t\tnext: pageNumber < items.length - 1 ? this.getNormalizedItems(items[pageNumber + 1]) : null,\n\t\t\t\tfirst: items.length ? this.getNormalizedItems(items[0]) : null,\n\t\t\t\tlast: items.length ? this.getNormalizedItems(items[items.length - 1]) : null,\n\t\t\t},\n\n\t\t\tpageNumber,\n\t\t};\n\t}\n\n\tgetOverrideDataLinks(pageNumber, templateCount, links) {\n\t\tlet obj = {};\n\n\t\t// links are okay but hrefs are better\n\t\tobj.previousPageLink = pageNumber > 0 ? links[pageNumber - 1] : null;\n\t\tobj.previous = obj.previousPageLink;\n\n\t\tobj.nextPageLink = pageNumber < templateCount - 1 ? links[pageNumber + 1] : null;\n\t\tobj.next = obj.nextPageLink;\n\n\t\tobj.firstPageLink = links.length > 0 ? links[0] : null;\n\t\tobj.lastPageLink = links.length > 0 ? links[links.length - 1] : null;\n\n\t\tobj.links = links;\n\t\t// todo deprecated, consistency with collections and use links instead\n\t\tobj.pageLinks = links;\n\t\treturn obj;\n\t}\n\n\tgetOverrideDataHrefs(pageNumber, templateCount, hrefs) {\n\t\tlet obj = {};\n\n\t\t// hrefs are better than links\n\t\tobj.previousPageHref = pageNumber > 0 ? hrefs[pageNumber - 1] : null;\n\t\tobj.nextPageHref = pageNumber < templateCount - 1 ? hrefs[pageNumber + 1] : null;\n\n\t\tobj.firstPageHref = hrefs.length > 0 ? hrefs[0] : null;\n\t\tobj.lastPageHref = hrefs.length > 0 ? hrefs[hrefs.length - 1] : null;\n\n\t\tobj.hrefs = hrefs;\n\n\t\t// better names\n\t\tobj.href = {\n\t\t\tprevious: obj.previousPageHref,\n\t\t\tnext: obj.nextPageHref,\n\t\t\tfirst: obj.firstPageHref,\n\t\t\tlast: obj.lastPageHref,\n\t\t};\n\n\t\treturn obj;\n\t}\n\n\tasync getPageTemplates() {\n\t\tif (!this.data) {\n\t\t\tthrow new Error(\n\t\t\t\t`Missing \\`setData\\` call for Pagination object${this.inputPathForErrorMessages}`,\n\t\t\t);\n\t\t}\n\n\t\tif (!this.hasPagination()) {\n\t\t\treturn [];\n\t\t}\n\n\t\tlet entries = [];\n\t\tlet items = this.chunkedItems;\n\t\tlet pages = this.size === 1 ? items.map((entry) => entry[0]) : items;\n\n\t\tlet links = [];\n\t\tlet hrefs = [];\n\n\t\tlet hasPermalinkField =\n\t\t\tBoolean(this.data[this.config.keys.permalink]) ||\n\t\t\tBoolean(this.data.eleventyComputed?.[this.config.keys.permalink]);\n\n\t\t// Do *not* pass collections through DeepCopy, we’ll re-add them back in later.\n\t\tlet collections = this.data.collections;\n\t\tif (collections) {\n\t\t\tdelete this.data.collections;\n\t\t}\n\n\t\tlet parentData = DeepCopy(\n\t\t\t{\n\t\t\t\tpagination: {\n\t\t\t\t\tdata: this.data.pagination.data,\n\t\t\t\t\tsize: this.data.pagination.size,\n\t\t\t\t\talias: this.alias,\n\t\t\t\t\tpages,\n\t\t\t\t},\n\t\t\t},\n\t\t\tthis.data,\n\t\t);\n\n\t\t// Restore skipped collections\n\t\tif (collections) {\n\t\t\tthis.data.collections = collections;\n\t\t\t// Keep the original reference to the collections, no deep copy!!\n\t\t\tparentData.collections = collections;\n\t\t}\n\n\t\t// TODO this does work fine but let’s wait on enabling it.\n\t\t// DeepFreeze(parentData, [\"collections\"]);\n\n\t\t// TODO future improvement dea: use a light Template wrapper for paged template clones (PagedTemplate?)\n\t\t// so that we don’t have the memory cost of the full template (and can reuse the parent\n\t\t// template for some things)\n\n\t\tlet indices = new Set();\n\t\tfor (let j = 0; j <= items.length - 1; j++) {\n\t\t\tindices.add(j);\n\t\t}\n\n\t\tfor (let pageNumber of indices) {\n\t\t\tlet cloned = await this.template.clone();\n\n\t\t\tif (pageNumber > 0 && !hasPermalinkField) {\n\t\t\t\tcloned.setExtraOutputSubdirectory(pageNumber);\n\t\t\t}\n\n\t\t\tlet paginationData = {\n\t\t\t\tpagination: {\n\t\t\t\t\titems: items[pageNumber],\n\t\t\t\t},\n\t\t\t\tpage: {},\n\t\t\t};\n\t\t\tObject.assign(paginationData.pagination, this.getOverrideDataPages(items, pageNumber));\n\n\t\t\tif (this.alias) {\n\t\t\t\tlodashSet(paginationData, this.alias, this.getNormalizedItems(items[pageNumber]));\n\t\t\t}\n\n\t\t\t// Do *not* deep merge pagination data! See https://github.com/11ty/eleventy/issues/147#issuecomment-440802454\n\t\t\tlet clonedData = ProxyWrap(paginationData, parentData);\n\n\t\t\t// Previous method:\n\t\t\t// let clonedData = DeepCopy(paginationData, parentData);\n\n\t\t\tlet { /*linkInstance,*/ rawPath, path, href, dir } =\n\t\t\t\tawait cloned.getOutputLocations(clonedData);\n\t\t\t// TODO subdirectory to links if the site doesn’t live at /\n\t\t\tif (rawPath) {\n\t\t\t\tlinks.push(\"/\" + rawPath);\n\t\t\t}\n\n\t\t\threfs.push(href);\n\n\t\t\t// page.url and page.outputPath are used to avoid another getOutputLocations call later, see Template->addComputedData\n\t\t\tclonedData.page.url = href;\n\t\t\tclonedData.page.outputPath = path;\n\t\t\tclonedData.page.dir = dir;\n\n\t\t\tentries.push({\n\t\t\t\tpageNumber,\n\n\t\t\t\t// This is used by i18n Plugin to allow subgroups of nested pagination to be separate\n\t\t\t\tgroupNumber: items[pageNumber]?.[0]?.eleventyPaginationGroupNumber,\n\n\t\t\t\ttemplate: cloned,\n\t\t\t\tdata: clonedData,\n\t\t\t});\n\t\t}\n\n\t\t// we loop twice to pass in the appropriate prev/next links (already full generated now)\n\t\tlet index = 0;\n\t\tfor (let pageEntry of entries) {\n\t\t\tlet linksObj = this.getOverrideDataLinks(index, items.length, links);\n\n\t\t\tObject.assign(pageEntry.data.pagination, linksObj);\n\n\t\t\tlet hrefsObj = this.getOverrideDataHrefs(index, items.length, hrefs);\n\t\t\tObject.assign(pageEntry.data.pagination, hrefsObj);\n\t\t\tindex++;\n\t\t}\n\n\t\treturn entries;\n\t}\n}\n\nexport default Pagination;\n"
  },
  {
    "path": "src/Plugins/PreserveClosingTagsPlugin.js",
    "content": "const POSTHTML_PLUGIN_NAME = \"11ty/eleventy/preserve-closing-tags\";\n\nexport function PreserveClosingTagsPlugin(eleventyConfig, options = {}) {\n\t// TODO error on non-void eligible tag names\n\tif (!options.tags) {\n\t\t// \"meta\"\n\t\toptions.tags = [];\n\t}\n\n\tif (!Array.isArray(options.tags)) {\n\t\tthrow new Error(\"`tags` passed to the Preserve Closing Tags plugin must be an Array\");\n\t}\n\n\tconst tagMatches = options.tags.map((tag) => ({ tag }));\n\tif (tagMatches.length === 0) {\n\t\treturn;\n\t}\n\n\televentyConfig.htmlTransformer.addPosthtmlPlugin(\n\t\t\"html\",\n\t\tfunction preserveClosingTagsPlugin(pluginOptions = {}) {\n\t\t\treturn function (tree) {\n\t\t\t\ttree.match(tagMatches, function (node) {\n\t\t\t\t\tnode.closeAs = \"slash\"; // close eligible tags as `<meta />` and not `<meta>`\n\t\t\t\t\treturn node;\n\t\t\t\t});\n\t\t\t};\n\t\t},\n\t\t{\n\t\t\t// pluginOptions\n\t\t\tname: POSTHTML_PLUGIN_NAME,\n\t\t},\n\t);\n}\n"
  },
  {
    "path": "src/Plugins/RenderPlugin.js",
    "content": "import { readFileSync } from \"node:fs\";\nimport { Merge, TemplatePath, isPlainObject } from \"@11ty/eleventy-utils\";\n\n// TODO add a first-class Markdown component to expose this using Markdown-only syntax (will need to be synchronous for markdown-it)\n\nimport { ProxyWrap } from \"../Util/Objects/ProxyWrap.js\";\nimport TemplateDataInitialGlobalData from \"../Data/TemplateDataInitialGlobalData.js\";\nimport EleventyBaseError from \"../Errors/EleventyBaseError.js\";\nimport TemplateRender from \"../TemplateRender.js\";\nimport ProjectDirectories from \"../Util/ProjectDirectories.js\";\nimport TemplateConfig from \"../TemplateConfig.js\";\nimport EleventyExtensionMap from \"../EleventyExtensionMap.js\";\nimport TemplateEngineManager from \"../Engines/TemplateEngineManager.js\";\nimport Liquid from \"../Adapters/Engines/Liquid.js\";\n\nclass EleventyNunjucksError extends EleventyBaseError {}\n\n/** @this {object} */\nasync function compile(content, templateLang, options = {}) {\n\tlet { templateConfig, extensionMap } = options;\n\tlet strictMode = options.strictMode ?? false;\n\n\tif (!templateConfig) {\n\t\ttemplateConfig = new TemplateConfig(null, false);\n\t\ttemplateConfig.setDirectories(new ProjectDirectories());\n\t\tawait templateConfig.init();\n\t}\n\n\t// Breaking change in 2.0+, previous default was `html` and now we default to the page template syntax\n\tif (!templateLang) {\n\t\ttemplateLang = this.page.templateSyntax;\n\t}\n\n\tif (!extensionMap) {\n\t\tif (strictMode) {\n\t\t\tthrow new Error(\"Internal error: missing `extensionMap` in RenderPlugin->compile.\");\n\t\t}\n\t\textensionMap = new EleventyExtensionMap(templateConfig);\n\t\textensionMap.engineManager = new TemplateEngineManager(templateConfig);\n\t}\n\tlet tr = new TemplateRender(templateLang, templateConfig);\n\ttr.extensionMap = extensionMap;\n\n\tif (templateLang) {\n\t\tawait tr.setEngineOverride(templateLang);\n\t} else {\n\t\tawait tr.init();\n\t}\n\n\t// TODO tie this to the class, not the extension\n\tif (\n\t\ttr.engine.name === \"11ty.js\" ||\n\t\ttr.engine.name === \"11ty.cjs\" ||\n\t\ttr.engine.name === \"11ty.mjs\" ||\n\t\t// TODO Node 22+\n\t\ttr.engine.name === \"11ty.ts\" ||\n\t\ttr.engine.name === \"11ty.cts\" ||\n\t\ttr.engine.name === \"11ty.mts\"\n\t) {\n\t\tthrow new Error(\n\t\t\t\"11ty.js is not yet supported as a template engine for `renderTemplate`. Use `renderFile` instead!\",\n\t\t);\n\t}\n\n\treturn tr.getCompiledTemplate(content);\n}\n\n// No templateLang default, it should infer from the inputPath.\nasync function compileFile(inputPath, options = {}, templateLang) {\n\tlet { templateConfig, extensionMap, config } = options;\n\tlet strictMode = options.strictMode ?? false;\n\tif (!inputPath) {\n\t\tthrow new Error(\"Missing file path argument passed to the `renderFile` shortcode.\");\n\t}\n\n\tlet wasTemplateConfigMissing = false;\n\tif (!templateConfig) {\n\t\ttemplateConfig = new TemplateConfig(null, false);\n\t\ttemplateConfig.setDirectories(new ProjectDirectories());\n\t\twasTemplateConfigMissing = true;\n\t}\n\tif (config && typeof config === \"function\") {\n\t\tawait config(templateConfig.userConfig);\n\t}\n\tif (wasTemplateConfigMissing) {\n\t\tawait templateConfig.init();\n\t}\n\n\tlet normalizedPath = TemplatePath.normalizeOperatingSystemFilePath(inputPath);\n\t// Prefer the exists cache, if it’s available\n\tif (!templateConfig.existsCache.exists(normalizedPath)) {\n\t\tthrow new Error(\n\t\t\t\"Could not find render plugin file for the `renderFile` shortcode, looking for: \" + inputPath,\n\t\t);\n\t}\n\n\tif (!extensionMap) {\n\t\tif (strictMode) {\n\t\t\tthrow new Error(\"Internal error: missing `extensionMap` in RenderPlugin->compileFile.\");\n\t\t}\n\n\t\textensionMap = new EleventyExtensionMap(templateConfig);\n\t\textensionMap.engineManager = new TemplateEngineManager(templateConfig);\n\t}\n\tlet tr = new TemplateRender(inputPath, templateConfig);\n\ttr.extensionMap = extensionMap;\n\n\tif (templateLang) {\n\t\tawait tr.setEngineOverride(templateLang);\n\t} else {\n\t\tawait tr.init();\n\t}\n\n\tif (!tr.engine.needsToReadFileContents()) {\n\t\treturn tr.getCompiledTemplate(null);\n\t}\n\n\t// TODO we could make this work with full templates (with front matter?)\n\tlet content = readFileSync(inputPath, \"utf8\");\n\treturn tr.getCompiledTemplate(content);\n}\n\n/** @this {object} */\nasync function renderShortcodeFn(fn, data) {\n\tif (fn === undefined) {\n\t\treturn;\n\t} else if (typeof fn !== \"function\") {\n\t\tthrow new Error(`The \\`compile\\` function did not return a function. Received ${fn}`);\n\t}\n\n\t// if the user passes a string or other literal, remap to an object.\n\tif (!isPlainObject(data)) {\n\t\tdata = {\n\t\t\t_: data,\n\t\t};\n\t}\n\n\tif (\"data\" in this && isPlainObject(this.data)) {\n\t\t// when options.accessGlobalData is true, this allows the global data\n\t\t// to be accessed inside of the shortcode as a fallback\n\n\t\tdata = ProxyWrap(data, this.data);\n\t} else {\n\t\t// save `page` and `eleventy` for reuse\n\t\tdata.page = this.page;\n\t\tdata.eleventy = this.eleventy;\n\t}\n\n\treturn fn(data);\n}\n\n/**\n * @module 11ty/eleventy/Plugins/RenderPlugin\n */\n\n/**\n * A plugin to add shortcodes to render an Eleventy template\n * string (or file) inside of another template. {@link https://v3.11ty.dev/docs/plugins/render/}\n *\n * @since 1.0.0\n * @param {module:11ty/eleventy/UserConfig} eleventyConfig - User-land configuration instance.\n * @param {object} options - Plugin options\n */\nfunction RenderPlugin(eleventyConfig, options = {}) {\n\tlet templateConfig;\n\televentyConfig.on(\"eleventy.config\", (tmplConfigInstance) => {\n\t\ttemplateConfig = tmplConfigInstance;\n\t});\n\n\tlet extensionMap;\n\televentyConfig.on(\"eleventy.extensionmap\", (map) => {\n\t\textensionMap = map;\n\t});\n\n\t/**\n\t * @typedef {object} options\n\t * @property {string} [tagName] - The shortcode name to render a template string.\n\t * @property {string} [tagNameFile] - The shortcode name to render a template file.\n\t * @property {module:11ty/eleventy/TemplateConfig} [templateConfig] - Configuration object\n\t * @property {boolean} [accessGlobalData] - Whether or not the template has access to the page’s data.\n\t */\n\n\toptions.tagName ??= \"renderTemplate\";\n\toptions.tagNameFile ??= \"renderFile\";\n\toptions.filterName ??= \"renderContent\";\n\toptions.templateConfig ??= null;\n\toptions.accessGlobalData ??= false;\n\n\tfunction liquidTemplateTag(liquidEngine, tagName, extras) {\n\t\tconst { evalToken } = extras;\n\t\t// via https://github.com/harttle/liquidjs/blob/b5a22fa0910c708fe7881ef170ed44d3594e18f3/src/builtin/tags/raw.ts\n\t\treturn {\n\t\t\tparse: function (tagToken, remainTokens) {\n\t\t\t\tthis.name = tagToken.name;\n\n\t\t\t\tif (eleventyConfig.liquid.parameterParsing === \"builtin\") {\n\t\t\t\t\tthis.orderedArgs = Liquid.parseArgumentsBuiltin(tagToken.args);\n\t\t\t\t\t// note that Liquid does have a Hash class for name-based argument parsing but offers no easy to support both modes in one class\n\t\t\t\t} else {\n\t\t\t\t\tthis.legacyArgs = tagToken.args;\n\t\t\t\t}\n\n\t\t\t\tthis.tokens = [];\n\n\t\t\t\tvar stream = liquidEngine.parser\n\t\t\t\t\t.parseStream(remainTokens)\n\t\t\t\t\t.on(\"token\", (token) => {\n\t\t\t\t\t\tif (token.name === \"end\" + tagName) stream.stop();\n\t\t\t\t\t\telse this.tokens.push(token);\n\t\t\t\t\t})\n\t\t\t\t\t.on(\"end\", () => {\n\t\t\t\t\t\tthrow new Error(`tag ${tagToken.getText()} not closed`);\n\t\t\t\t\t});\n\n\t\t\t\tstream.start();\n\t\t\t},\n\t\t\trender: function* (ctx) {\n\t\t\t\tlet normalizedContext = {};\n\t\t\t\tif (ctx) {\n\t\t\t\t\tif (options.accessGlobalData) {\n\t\t\t\t\t\t// parent template data cascade\n\t\t\t\t\t\tnormalizedContext.data = ctx.getAll();\n\t\t\t\t\t}\n\n\t\t\t\t\tnormalizedContext.page = ctx.get([\"page\"]);\n\t\t\t\t\tnormalizedContext.eleventy = ctx.get([\"eleventy\"]);\n\t\t\t\t}\n\n\t\t\t\tlet argArray = [];\n\t\t\t\tif (this.legacyArgs) {\n\t\t\t\t\tlet rawArgs = Liquid.parseArguments(null, this.legacyArgs);\n\t\t\t\t\tfor (let arg of rawArgs) {\n\t\t\t\t\t\tlet b = yield liquidEngine.evalValue(arg, ctx);\n\t\t\t\t\t\targArray.push(b);\n\t\t\t\t\t}\n\t\t\t\t} else if (this.orderedArgs) {\n\t\t\t\t\tfor (let arg of this.orderedArgs) {\n\t\t\t\t\t\tlet b = yield evalToken(arg, ctx);\n\t\t\t\t\t\targArray.push(b);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// plaintext paired shortcode content\n\t\t\t\tlet body = this.tokens.map((token) => token.getText()).join(\"\");\n\n\t\t\t\tlet ret = _renderStringShortcodeFn.call(\n\t\t\t\t\tnormalizedContext,\n\t\t\t\t\tbody,\n\t\t\t\t\t// templateLang, data\n\t\t\t\t\t...argArray,\n\t\t\t\t);\n\t\t\t\tyield ret;\n\t\t\t\treturn ret;\n\t\t\t},\n\t\t};\n\t}\n\n\t// TODO I don’t think this works with whitespace control, e.g. {%- endrenderTemplate %}\n\tfunction nunjucksTemplateTag(NunjucksLib, tagName) {\n\t\treturn new (function () {\n\t\t\tthis.tags = [tagName];\n\n\t\t\tthis.parse = function (parser, nodes) {\n\t\t\t\tvar tok = parser.nextToken();\n\n\t\t\t\tvar args = parser.parseSignature(true, true);\n\t\t\t\tconst begun = parser.advanceAfterBlockEnd(tok.value);\n\n\t\t\t\t// This code was ripped from the Nunjucks parser for `raw`\n\t\t\t\t// https://github.com/mozilla/nunjucks/blob/fd500902d7c88672470c87170796de52fc0f791a/nunjucks/src/parser.js#L655\n\t\t\t\tconst endTagName = \"end\" + tagName;\n\t\t\t\t// Look for upcoming raw blocks (ignore all other kinds of blocks)\n\t\t\t\tconst rawBlockRegex = new RegExp(\n\t\t\t\t\t\"([\\\\s\\\\S]*?){%\\\\s*(\" + tagName + \"|\" + endTagName + \")\\\\s*(?=%})%}\",\n\t\t\t\t);\n\t\t\t\tlet rawLevel = 1;\n\t\t\t\tlet str = \"\";\n\t\t\t\tlet matches;\n\n\t\t\t\t// Exit when there's nothing to match\n\t\t\t\t// or when we've found the matching \"endraw\" block\n\t\t\t\twhile ((matches = parser.tokens._extractRegex(rawBlockRegex)) && rawLevel > 0) {\n\t\t\t\t\tconst all = matches[0];\n\t\t\t\t\tconst pre = matches[1];\n\t\t\t\t\tconst blockName = matches[2];\n\n\t\t\t\t\t// Adjust rawlevel\n\t\t\t\t\tif (blockName === tagName) {\n\t\t\t\t\t\trawLevel += 1;\n\t\t\t\t\t} else if (blockName === endTagName) {\n\t\t\t\t\t\trawLevel -= 1;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Add to str\n\t\t\t\t\tif (rawLevel === 0) {\n\t\t\t\t\t\t// We want to exclude the last \"endraw\"\n\t\t\t\t\t\tstr += pre;\n\t\t\t\t\t\t// Move tokenizer to beginning of endraw block\n\t\t\t\t\t\tparser.tokens.backN(all.length - pre.length);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tstr += all;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tlet body = new nodes.Output(begun.lineno, begun.colno, [\n\t\t\t\t\tnew nodes.TemplateData(begun.lineno, begun.colno, str),\n\t\t\t\t]);\n\t\t\t\treturn new nodes.CallExtensionAsync(this, \"run\", args, [body]);\n\t\t\t};\n\n\t\t\tthis.run = function (...args) {\n\t\t\t\tlet resolve = args.pop();\n\t\t\t\tlet body = args.pop();\n\t\t\t\tlet [context, ...argArray] = args;\n\n\t\t\t\tlet normalizedContext = {};\n\t\t\t\tif (context.ctx?.page) {\n\t\t\t\t\tnormalizedContext.ctx = context.ctx;\n\n\t\t\t\t\t// TODO .data\n\t\t\t\t\t// if(options.accessGlobalData) {\n\t\t\t\t\t//   normalizedContext.data = context.ctx;\n\t\t\t\t\t// }\n\n\t\t\t\t\tnormalizedContext.page = context.ctx.page;\n\t\t\t\t\tnormalizedContext.eleventy = context.ctx.eleventy;\n\t\t\t\t}\n\n\t\t\t\tbody(function (e, bodyContent) {\n\t\t\t\t\tif (e) {\n\t\t\t\t\t\tresolve(\n\t\t\t\t\t\t\tnew EleventyNunjucksError(`Error with Nunjucks paired shortcode \\`${tagName}\\``, e),\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tPromise.resolve(\n\t\t\t\t\t\t_renderStringShortcodeFn.call(\n\t\t\t\t\t\t\tnormalizedContext,\n\t\t\t\t\t\t\tbodyContent,\n\t\t\t\t\t\t\t// templateLang, data\n\t\t\t\t\t\t\t...argArray,\n\t\t\t\t\t\t),\n\t\t\t\t\t).then(\n\t\t\t\t\t\tfunction (returnValue) {\n\t\t\t\t\t\t\tresolve(null, new NunjucksLib.runtime.SafeString(returnValue));\n\t\t\t\t\t\t},\n\t\t\t\t\t\tfunction (e) {\n\t\t\t\t\t\t\tresolve(\n\t\t\t\t\t\t\t\tnew EleventyNunjucksError(`Error with Nunjucks paired shortcode \\`${tagName}\\``, e),\n\t\t\t\t\t\t\t\tnull,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t},\n\t\t\t\t\t);\n\t\t\t\t});\n\t\t\t};\n\t\t})();\n\t}\n\n\t/** @this {object} */\n\tasync function _renderStringShortcodeFn(content, templateLang, data = {}) {\n\t\t// Default is fn(content, templateLang, data) but we want to support fn(content, data) too\n\t\tif (typeof templateLang !== \"string\") {\n\t\t\tdata = templateLang;\n\t\t\ttemplateLang = false;\n\t\t}\n\n\t\t// TODO Render plugin `templateLang` is feeding bad input paths to the addDependencies call in Custom.js\n\t\tlet fn = await compile.call(this, content, templateLang, {\n\t\t\ttemplateConfig: options.templateConfig || templateConfig,\n\t\t\textensionMap,\n\t\t});\n\n\t\treturn renderShortcodeFn.call(this, fn, data);\n\t}\n\n\t/** @this {object} */\n\tasync function _renderFileShortcodeFn(inputPath, data = {}, templateLang) {\n\t\tlet renderFileOptions = {\n\t\t\ttemplateConfig: options.templateConfig || templateConfig,\n\t\t\textensionMap,\n\t\t};\n\n\t\tlet fn = await compileFile.call(this, inputPath, renderFileOptions, templateLang);\n\n\t\treturn renderShortcodeFn.call(this, fn, data);\n\t}\n\n\t// Render strings\n\tif (options.tagName) {\n\t\t// use falsy to opt-out\n\t\televentyConfig.addJavaScriptFunction(options.tagName, _renderStringShortcodeFn);\n\n\t\televentyConfig.addLiquidTag(options.tagName, function (liquidEngine, extras) {\n\t\t\treturn liquidTemplateTag(liquidEngine, options.tagName, extras);\n\t\t});\n\n\t\televentyConfig.addNunjucksTag(options.tagName, function (nunjucksLib) {\n\t\t\treturn nunjucksTemplateTag(nunjucksLib, options.tagName);\n\t\t});\n\t}\n\n\t// Filter for rendering strings\n\tif (options.filterName) {\n\t\televentyConfig.addAsyncFilter(options.filterName, _renderStringShortcodeFn);\n\t}\n\n\t// Render File\n\t// use `false` to opt-out\n\tif (options.tagNameFile) {\n\t\televentyConfig.addAsyncShortcode(options.tagNameFile, _renderFileShortcodeFn);\n\t}\n}\n\n// Will re-use the same configuration instance both at a top level and across any nested renders\nclass RenderManager {\n\t/** @type {Promise|undefined} */\n\t#hasConfigInitialized;\n\t#extensionMap;\n\t#templateConfig;\n\n\tconstructor() {\n\t\tthis.templateConfig = new TemplateConfig(null, false);\n\t\tthis.templateConfig.setDirectories(new ProjectDirectories());\n\t}\n\n\tget templateConfig() {\n\t\treturn this.#templateConfig;\n\t}\n\n\tset templateConfig(templateConfig) {\n\t\tif (!templateConfig || templateConfig === this.#templateConfig) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.#templateConfig = templateConfig;\n\n\t\t// This is the only plugin running on the Edge\n\t\tthis.#templateConfig.userConfig.addPlugin(RenderPlugin, {\n\t\t\ttemplateConfig: this.#templateConfig,\n\t\t\taccessGlobalData: true,\n\t\t});\n\n\t\tthis.#extensionMap = new EleventyExtensionMap(this.#templateConfig);\n\t\tthis.#extensionMap.engineManager = new TemplateEngineManager(this.#templateConfig);\n\t}\n\n\tasync init() {\n\t\tif (this.#hasConfigInitialized) {\n\t\t\treturn this.#hasConfigInitialized;\n\t\t}\n\t\tif (this.templateConfig.hasInitialized()) {\n\t\t\treturn true;\n\t\t}\n\t\tthis.#hasConfigInitialized = this.templateConfig.init();\n\t\tawait this.#hasConfigInitialized;\n\n\t\treturn true;\n\t}\n\n\t// `callback` is async-friendly but requires await upstream\n\tconfig(callback) {\n\t\t// run an extra `function(eleventyConfig)` configuration callbacks\n\t\tif (callback && typeof callback === \"function\") {\n\t\t\treturn callback(this.templateConfig.userConfig);\n\t\t}\n\t}\n\n\tget initialGlobalData() {\n\t\tif (!this._data) {\n\t\t\tthis._data = new TemplateDataInitialGlobalData(this.templateConfig);\n\t\t}\n\t\treturn this._data;\n\t}\n\n\t// because we don’t have access to the full data cascade—but\n\t// we still want configuration data added via `addGlobalData`\n\tasync getData(...data) {\n\t\tawait this.init();\n\n\t\tlet globalData = await this.initialGlobalData.getData();\n\t\tlet merged = Merge({}, globalData, ...data);\n\t\treturn merged;\n\t}\n\n\tasync compile(content, templateLang, options = {}) {\n\t\tawait this.init();\n\n\t\toptions.templateConfig = this.templateConfig;\n\t\toptions.extensionMap = this.#extensionMap;\n\t\toptions.strictMode = true;\n\n\t\t// We don’t need `compile.call(this)` here because the Edge always uses \"liquid\" as the template lang (instead of relying on this.page.templateSyntax)\n\t\t// returns promise\n\t\treturn compile(content, templateLang, options);\n\t}\n\n\tasync render(fn, edgeData, buildTimeData) {\n\t\tawait this.init();\n\n\t\tlet mergedData = await this.getData(edgeData);\n\t\t// Set .data for options.accessGlobalData feature\n\t\tlet context = {\n\t\t\tdata: mergedData,\n\t\t};\n\n\t\treturn renderShortcodeFn.call(context, fn, buildTimeData);\n\t}\n}\n\nObject.defineProperty(RenderPlugin, \"eleventyPackage\", {\n\tvalue: \"@11ty/eleventy/render-plugin\",\n});\n\nObject.defineProperty(RenderPlugin, \"eleventyPluginOptions\", {\n\tvalue: {\n\t\tunique: true,\n\t},\n});\n\n// CommonJS friendly exports on .default\nObject.assign(RenderPlugin, {\n\tFile: compileFile,\n\tString: compile,\n\tRenderManager,\n});\n\nexport default RenderPlugin;\n\nexport { compileFile as File, compile as String, RenderManager };\n"
  },
  {
    "path": "src/Template.js",
    "content": "import { parse } from \"node:path\";\nimport { statSync } from \"node:fs\";\n\nimport lodash from \"@11ty/lodash-custom\";\nimport { Merge, TemplatePath, isPlainObject } from \"@11ty/eleventy-utils\";\nimport debugUtil from \"debug\";\n\nimport chalk from \"./Adapters/Packages/chalk.js\";\nimport ConsoleLogger from \"./Util/ConsoleLogger.js\";\nimport { getCreatedTimestamp, getUpdatedTimestamp } from \"./Util/Git.js\";\nimport TemplateContent from \"./TemplateContent.js\";\nimport TemplatePermalink from \"./TemplatePermalink.js\";\nimport TemplateLayout from \"./TemplateLayout.js\";\nimport TemplateFileSlug from \"./TemplateFileSlug.js\";\nimport ComputedData from \"./Data/ComputedData.js\";\nimport Pagination from \"./Plugins/Pagination.js\";\nimport TemplateBehavior from \"./TemplateBehavior.js\";\nimport TemplateContentPrematureUseError from \"./Errors/TemplateContentPrematureUseError.js\";\nimport TemplateContentUnrenderedTemplateError from \"./Errors/TemplateContentUnrenderedTemplateError.js\";\nimport EleventyBaseError from \"./Errors/EleventyBaseError.js\";\nimport { fromISOtoDateUTC } from \"./Util/DateParse.js\";\nimport ReservedData from \"./Util/ReservedData.js\";\nimport TransformsUtil from \"./Util/TransformsUtil.js\";\nimport { FileSystemManager } from \"./Util/FileSystemManager.js\";\nimport { TemplatePreprocessors } from \"./TemplatePreprocessors.js\";\nimport PathNormalizer from \"./Util/PathNormalizer.js\";\nimport { getDirectoryFromUrl } from \"./Util/UrlUtil.js\";\n\nconst { set: lodashSet, get: lodashGet } = lodash;\n\nconst debug = debugUtil(\"Eleventy:Template\");\nconst debugDev = debugUtil(\"Dev:Eleventy:Template\");\n\nclass Template extends TemplateContent {\n\t#logger;\n\t#fsManager;\n\t#stats;\n\t#preprocessorCache;\n\n\tconstructor(templatePath, templateData, extensionMap, config) {\n\t\tdebugDev(\"new Template(%o)\", templatePath);\n\t\tsuper(templatePath, config);\n\n\t\tthis.parsed = parse(templatePath);\n\n\t\t// for pagination\n\t\tthis.extraOutputSubdirectory = \"\";\n\n\t\tthis.extensionMap = extensionMap;\n\t\tthis.templateData = templateData;\n\t\tthis.#initFileSlug();\n\n\t\tthis.linters = [];\n\t\tthis.transforms = {};\n\n\t\tthis.isVerbose = true;\n\t\tthis.isDryRun = false;\n\t\tthis.writeCount = 0;\n\n\t\tthis.fileSlug = new TemplateFileSlug(this.inputPath, this.extensionMap, this.eleventyConfig);\n\t\tthis.fileSlugStr = this.fileSlug.getSlug();\n\t\tthis.filePathStem = this.fileSlug.getFullPathWithoutExtension();\n\n\t\tthis.outputFormat = \"fs\";\n\n\t\tthis.behavior = new TemplateBehavior(this.config);\n\t\tthis.behavior.setOutputFormat(this.outputFormat);\n\n\t\tthis.templatePreprocessor = new TemplatePreprocessors(this.config.preprocessors);\n\t}\n\n\t#initFileSlug() {\n\t\tthis.fileSlug = new TemplateFileSlug(this.inputPath, this.extensionMap, this.eleventyConfig);\n\t\tthis.fileSlugStr = this.fileSlug.getSlug();\n\t\tthis.filePathStem = this.fileSlug.getFullPathWithoutExtension();\n\t}\n\n\t/* mimic constructor arg order */\n\tresetCachedTemplate({ templateData, extensionMap, eleventyConfig }) {\n\t\tsuper.resetCachedTemplate({ eleventyConfig });\n\t\tthis.templateData = templateData;\n\t\tthis.extensionMap = extensionMap;\n\t\t// this.#fsManager = undefined;\n\t\tthis.#initFileSlug();\n\t}\n\n\tget fsManager() {\n\t\tif (!this.#fsManager) {\n\t\t\tthis.#fsManager = new FileSystemManager(this.eleventyConfig);\n\t\t}\n\t\treturn this.#fsManager;\n\t}\n\n\tget logger() {\n\t\tif (!this.#logger) {\n\t\t\tthis.#logger = new ConsoleLogger();\n\t\t\tthis.#logger.isVerbose = this.isVerbose;\n\t\t}\n\t\treturn this.#logger;\n\t}\n\n\t/* Setter for Logger */\n\tset logger(logger) {\n\t\tthis.#logger = logger;\n\t}\n\n\tisRenderable() {\n\t\treturn this.behavior.isRenderable();\n\t}\n\n\tisRenderableDisabled() {\n\t\treturn this.behavior.isRenderableDisabled();\n\t}\n\n\tisRenderableOptional() {\n\t\t// A template that is lazily rendered once if used by a second order dependency of another template dependency.\n\t\t// e.g. You change firstpost.md, which is used by feed.xml, but secondpost.md (also used by feed.xml)\n\t\t// has not yet rendered and needs to be rendered once to populate the cache.\n\t\treturn this.behavior.isRenderableOptional();\n\t}\n\n\tsetRenderableOverride(renderableOverride) {\n\t\tthis.behavior.setRenderableOverride(renderableOverride);\n\t}\n\n\treset() {\n\t\tthis.renderCount = 0;\n\t\tthis.writeCount = 0;\n\t}\n\n\tresetCaches(types) {\n\t\ttypes = this.getResetTypes(types);\n\n\t\tsuper.resetCaches(types);\n\n\t\tif (types.data || types.read) {\n\t\t\tthis.#preprocessorCache = undefined;\n\t\t}\n\n\t\tif (types.data) {\n\t\t\tdelete this._dataCache;\n\t\t\t// delete this._usePermalinkRoot;\n\t\t\t// delete this.#stats;\n\t\t}\n\n\t\tif (types.render) {\n\t\t\tdelete this._cacheRenderedPromise;\n\t\t\tdelete this._cacheRenderedTransformsAndLayoutsPromise;\n\t\t}\n\t}\n\n\tsetOutputFormat(to) {\n\t\tthis.outputFormat = to;\n\t\tthis.behavior.setOutputFormat(to);\n\t}\n\n\tsetIsVerbose(isVerbose) {\n\t\tthis.isVerbose = isVerbose;\n\t\tthis.logger.isVerbose = isVerbose;\n\t}\n\n\tsetDryRunViaIncremental(isIncremental) {\n\t\tthis.isDryRun = isIncremental;\n\t\tthis.isIncremental = isIncremental;\n\t}\n\n\tsetDryRun(isDryRun) {\n\t\tthis.isDryRun = !!isDryRun;\n\t}\n\n\tsetExtraOutputSubdirectory(dir) {\n\t\tthis.extraOutputSubdirectory = dir + \"/\";\n\t}\n\n\tgetTemplateSubfolder() {\n\t\tlet dir = TemplatePath.absolutePath(this.parsed.dir);\n\t\tlet inputDir = TemplatePath.absolutePath(this.inputDir);\n\n\t\t// Browser virtual fs uses `/` root for absolute paths\n\t\t// Fixed in @11ty/eleventy-utils@2.0.8 or newer (can remove this later)\n\t\tif (inputDir === \"/\" && dir.startsWith(\"/\")) {\n\t\t\treturn dir;\n\t\t}\n\n\t\treturn TemplatePath.stripLeadingSubPath(dir, inputDir);\n\t}\n\n\ttemplateUsesLayouts(pageData) {\n\t\tif (this.hasTemplateRender()) {\n\t\t\treturn pageData?.[this.config.keys.layout] && this.templateRender.engine.useLayouts();\n\t\t}\n\n\t\t// If `layout` prop is set, default to true when engine is unknown\n\t\treturn Boolean(pageData?.[this.config.keys.layout]);\n\t}\n\n\tgetLayout(layoutKey) {\n\t\t// already cached downstream in TemplateLayout -> TemplateCache\n\t\ttry {\n\t\t\treturn TemplateLayout.getTemplate(layoutKey, this.eleventyConfig, this.extensionMap);\n\t\t} catch (e) {\n\t\t\tthrow new EleventyBaseError(\n\t\t\t\t`Problem creating an Eleventy Layout for the \"${this.inputPath}\" template file.`,\n\t\t\t\te,\n\t\t\t);\n\t\t}\n\t}\n\n\tget baseFile() {\n\t\treturn this.extensionMap.removeTemplateExtension(this.parsed.base);\n\t}\n\n\tasync _getLink(data) {\n\t\tif (!data) {\n\t\t\tthrow new Error(\"Internal error: data argument missing in Template->_getLink\");\n\t\t}\n\n\t\tlet permalink =\n\t\t\tdata[this.config.keys.permalink] ??\n\t\t\tdata?.[this.config.keys.computed]?.[this.config.keys.permalink];\n\t\tlet permalinkValue;\n\t\tlet isDynamicPermalinkEnabled =\n\t\t\tthis.config.dynamicPermalinks && data.dynamicPermalink !== false;\n\n\t\t// `permalink: false` means render but no file system write, e.g. use in collections only)\n\t\t// `permalink: true` throws an error\n\t\tif (typeof permalink === \"boolean\") {\n\t\t\tdebugDev(\"Using boolean permalink %o\", permalink);\n\t\t\tpermalinkValue = permalink;\n\t\t} else if (permalink && !isDynamicPermalinkEnabled) {\n\t\t\t// Issue #838\n\t\t\tdebugDev(\"Not using dynamic permalinks, using %o\", permalink);\n\t\t\tpermalinkValue = permalink;\n\t\t} else if (isPlainObject(permalink)) {\n\t\t\t// Empty permalink {} object should act as if no permalink was set at all\n\t\t\t// and inherit the default behavior\n\t\t\tlet isEmptyObject = Object.keys(permalink).length === 0;\n\t\t\tif (!isEmptyObject) {\n\t\t\t\tlet promises = [];\n\t\t\t\tlet keys = [];\n\t\t\t\tfor (let key in permalink) {\n\t\t\t\t\tkeys.push(key);\n\t\t\t\t\tif (key !== \"build\" && Array.isArray(permalink[key])) {\n\t\t\t\t\t\tpromises.push(\n\t\t\t\t\t\t\tPromise.all([...permalink[key]].map((entry) => super.renderPermalink(entry, data))),\n\t\t\t\t\t\t);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tpromises.push(super.renderPermalink(permalink[key], data));\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tlet results = await Promise.all(promises);\n\n\t\t\t\tpermalinkValue = {};\n\t\t\t\tfor (let j = 0, k = keys.length; j < k; j++) {\n\t\t\t\t\tlet key = keys[j];\n\t\t\t\t\tpermalinkValue[key] = results[j];\n\t\t\t\t\tdebug(\n\t\t\t\t\t\t\"Rendering permalink.%o for %o: %s becomes %o\",\n\t\t\t\t\t\tkey,\n\t\t\t\t\t\tthis.inputPath,\n\t\t\t\t\t\tpermalink[key],\n\t\t\t\t\t\tresults[j],\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (permalink) {\n\t\t\t// render variables inside permalink front matter, bypass markdown\n\t\t\tpermalinkValue = await super.renderPermalink(permalink, data);\n\t\t\tdebug(\"Rendering permalink for %o: %s becomes %o\", this.inputPath, permalink, permalinkValue);\n\t\t\tdebugDev(\"Permalink rendered with data: %o\", data);\n\t\t}\n\n\t\t// Override default permalink behavior. Only do this if permalink was _not_ in the data cascade\n\t\tif (!permalink && isDynamicPermalinkEnabled) {\n\t\t\tlet tr = await this.getTemplateRender();\n\t\t\tlet permalinkCompilation = tr.engine.permalinkNeedsCompilation(\"\");\n\t\t\tif (typeof permalinkCompilation === \"function\") {\n\t\t\t\tlet ret = await this._renderFunction(permalinkCompilation, permalinkValue, this.inputPath);\n\t\t\t\tif (ret !== undefined) {\n\t\t\t\t\tif (typeof ret === \"function\") {\n\t\t\t\t\t\t// function\n\t\t\t\t\t\tpermalinkValue = await this._renderFunction(ret, data);\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// scalar\n\t\t\t\t\t\tpermalinkValue = ret;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (permalinkValue !== undefined) {\n\t\t\tlet p = new TemplatePermalink(\n\t\t\t\tpermalinkValue,\n\t\t\t\tthis.extraOutputSubdirectory,\n\t\t\t\tisDynamicPermalinkEnabled,\n\t\t\t);\n\t\t\tp.setUrlTransforms(this.config.urlTransforms);\n\t\t\tthis.behavior.setFromPermalink(p);\n\t\t\treturn p;\n\t\t}\n\n\t\t// No `permalink` specified in data cascade, do the default\n\t\tlet p = TemplatePermalink.generate(\n\t\t\tthis.getTemplateSubfolder(),\n\t\t\tthis.baseFile,\n\t\t\tthis.extraOutputSubdirectory,\n\t\t\tthis.engine.defaultTemplateFileExtension,\n\t\t\tisDynamicPermalinkEnabled,\n\t\t);\n\t\tp.setUrlTransforms(this.config.urlTransforms);\n\t\treturn p;\n\t}\n\n\tasync usePermalinkRoot() {\n\t\t// @cachedproperty\n\t\tif (this._usePermalinkRoot === undefined) {\n\t\t\t// TODO this only works with immediate front matter and not data files\n\t\t\tlet { data } = await this.getFrontMatterData();\n\t\t\tthis._usePermalinkRoot = data[this.config.keys.permalinkRoot];\n\t\t}\n\n\t\treturn this._usePermalinkRoot;\n\t}\n\n\tasync getOutputLocations(data) {\n\t\tthis.bench.get(\"(count) getOutputLocations\").incrementCount();\n\t\tlet link = await this._getLink(data);\n\n\t\tlet path;\n\t\tif (await this.usePermalinkRoot()) {\n\t\t\tpath = link.toPathFromRoot();\n\t\t} else {\n\t\t\tpath = link.toPath(this.outputDir);\n\t\t}\n\n\t\tlet href = link.toHref();\n\t\treturn {\n\t\t\tlinkInstance: link,\n\t\t\trawPath: link.toOutputPath(), // includes output directory\n\t\t\thref,\n\t\t\tpath: path,\n\t\t\tdir: getDirectoryFromUrl(href), // for `page.dir`\n\t\t};\n\t}\n\n\t// This is likely now a test-only method\n\t// Preferred to use the singular `getOutputLocations` above.\n\tasync getRawOutputPath(data) {\n\t\tthis.bench.get(\"(count) getRawOutputPath\").incrementCount();\n\t\tlet link = await this._getLink(data);\n\t\treturn link.toOutputPath();\n\t}\n\n\t// Preferred to use the singular `getOutputLocations` above.\n\tasync getOutputHref(data) {\n\t\tthis.bench.get(\"(count) getOutputHref\").incrementCount();\n\t\tlet link = await this._getLink(data);\n\t\treturn link.toHref();\n\t}\n\n\t// Preferred to use the singular `getOutputLocations` above.\n\tasync getOutputPath(data) {\n\t\tthis.bench.get(\"(count) getOutputPath\").incrementCount();\n\t\tlet link = await this._getLink(data);\n\t\tif (await this.usePermalinkRoot()) {\n\t\t\treturn link.toPathFromRoot();\n\t\t}\n\t\treturn link.toPath(this.outputDir);\n\t}\n\n\tasync _testGetAllLayoutFrontMatterData() {\n\t\tlet { data: frontMatterData } = await this.getFrontMatterData();\n\n\t\tif (frontMatterData[this.config.keys.layout]) {\n\t\t\tlet layout = this.getLayout(frontMatterData[this.config.keys.layout]);\n\t\t\treturn await layout.getData();\n\t\t}\n\t\treturn {};\n\t}\n\n\tasync #getData() {\n\t\tdebugDev(\"%o getData\", this.inputPath);\n\t\tlet localData = {};\n\t\tlet globalData = {};\n\n\t\tif (this.templateData) {\n\t\t\tlocalData = await this.templateData.getTemplateDirectoryData(this.inputPath);\n\t\t\tglobalData = await this.templateData.getGlobalData();\n\t\t\tdebugDev(\"%o getData getTemplateDirectoryData and getGlobalData\", this.inputPath);\n\t\t}\n\n\t\tlet { data: frontMatterData } = await this.getFrontMatterData();\n\n\t\tlet mergedLayoutData = {};\n\t\tlet tr = await this.getTemplateRender();\n\t\tif (tr.engine.useLayouts()) {\n\t\t\tlet layoutKey =\n\t\t\t\tfrontMatterData[this.config.keys.layout] ||\n\t\t\t\tlocalData[this.config.keys.layout] ||\n\t\t\t\tglobalData[this.config.keys.layout];\n\n\t\t\t// Layout front matter data\n\t\t\tif (layoutKey) {\n\t\t\t\tlet layout = this.getLayout(layoutKey);\n\n\t\t\t\tmergedLayoutData = await layout.getData();\n\t\t\t\tdebugDev(\"%o getData merged layout chain front matter\", this.inputPath);\n\t\t\t}\n\t\t}\n\n\t\ttry {\n\t\t\tlet mergedData = Merge({}, globalData, mergedLayoutData, localData, frontMatterData);\n\n\t\t\tif (this.config.freezeReservedData) {\n\t\t\t\tReservedData.checkSubset(mergedData);\n\t\t\t}\n\n\t\t\tawait this.addPage(mergedData);\n\n\t\t\tdebugDev(\"%o getData mergedData\", this.inputPath);\n\n\t\t\treturn mergedData;\n\t\t} catch (e) {\n\t\t\t// if ReservedDataError, defer to that (from ReservedData.checkSubset above)\n\t\t\tif (!ReservedData.isReservedDataError(e) && ReservedData.isFrozenError(e)) {\n\t\t\t\tthrow ReservedData.getError({ cause: e });\n\t\t\t}\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\tasync getData() {\n\t\tif (!this._dataCache) {\n\t\t\t// @cachedproperty\n\t\t\tthis._dataCache = this.#getData();\n\t\t}\n\n\t\treturn this._dataCache;\n\t}\n\n\tasync addPage(data) {\n\t\tif (!(\"page\" in data)) {\n\t\t\tdata.page = {};\n\t\t}\n\n\t\t// Make sure to keep these keys synchronized in src/Util/ReservedData.js\n\t\tdata.page.inputPath = this.inputPath;\n\t\t// parsed dir never has the trailing slash\n\t\tdata.page.inputPathDir = PathNormalizer.getDirectoryFromFilePath(this.inputPath);\n\t\tdata.page.fileSlug = this.fileSlugStr;\n\t\tdata.page.filePathStem = this.filePathStem;\n\t\tdata.page.outputFileExtension = this.engine.defaultTemplateFileExtension;\n\t\tdata.page.templateSyntax = this.getEngineNames(data[this.config.keys.engineOverride]);\n\n\t\tlet newDate = await this.getMappedDate(data);\n\t\t// Skip date assignment if custom date is falsy.\n\t\tif (newDate) {\n\t\t\tdata.page.date = newDate;\n\t\t}\n\n\t\t// data.page.url\n\t\t// data.page.outputPath\n\t\t// data.page.excerpt from gray-matter and Front Matter\n\t\t// data.page.lang from I18nPlugin\n\t}\n\n\t// Tests only\n\tasync render() {\n\t\tthrow new Error(\"Internal error: `Template->render` was removed in Eleventy 3.0.\");\n\t}\n\n\t// Tests only\n\tasync renderLayout() {\n\t\tthrow new Error(\"Internal error: `Template->renderLayout` was removed in Eleventy 3.0.\");\n\t}\n\n\tasync renderDirect(str, data, bypassMarkdown) {\n\t\treturn super.render(str, data, bypassMarkdown);\n\t}\n\n\t// This is the primary render mechanism, called via TemplateMap->populateContentDataInMap\n\tasync renderPageEntryWithoutLayout(pageEntry) {\n\t\t// @cachedproperty\n\t\tif (!this._cacheRenderedPromise) {\n\t\t\tthis._cacheRenderedPromise = this.renderDirect(pageEntry.rawInput, pageEntry.data);\n\t\t\tthis.renderCount++;\n\t\t}\n\n\t\treturn this._cacheRenderedPromise;\n\t}\n\n\tsetLinters(linters) {\n\t\tif (!isPlainObject(linters)) {\n\t\t\tthrow new Error(\"Object expected in setLinters\");\n\t\t}\n\t\t// this acts as a reset\n\t\tthis.linters = [];\n\t\tfor (let linter of Object.values(linters).filter((l) => typeof l === \"function\")) {\n\t\t\tthis.addLinter(linter);\n\t\t}\n\t}\n\n\taddLinter(callback) {\n\t\tthis.linters.push(callback);\n\t}\n\n\tasync runLinters(str, page) {\n\t\tlet { inputPath, outputPath, url } = page;\n\t\tlet pageData = page.data.page;\n\n\t\tfor (let linter of this.linters) {\n\t\t\t// these can be asynchronous but no guarantee of order when they run\n\t\t\tlinter.call(\n\t\t\t\t{\n\t\t\t\t\tinputPath,\n\t\t\t\t\toutputPath,\n\t\t\t\t\turl,\n\t\t\t\t\tpage: pageData,\n\t\t\t\t},\n\t\t\t\tstr,\n\t\t\t\tinputPath,\n\t\t\t\toutputPath,\n\t\t\t);\n\t\t}\n\t}\n\n\tsetTransforms(transforms) {\n\t\tif (!isPlainObject(transforms)) {\n\t\t\tthrow new Error(\"Object expected in setTransforms\");\n\t\t}\n\t\tthis.transforms = transforms;\n\t}\n\n\tasync runTransforms(str, pageEntry) {\n\t\treturn TransformsUtil.runAll(str, pageEntry.data.page, this.transforms, {\n\t\t\tlogger: this.logger,\n\t\t});\n\t}\n\n\tasync #renderComputedUnit(entry, data) {\n\t\tif (typeof entry === \"string\") {\n\t\t\treturn this.renderComputedData(entry, data);\n\t\t}\n\n\t\tif (isPlainObject(entry)) {\n\t\t\tfor (let key in entry) {\n\t\t\t\tentry[key] = await this.#renderComputedUnit(entry[key], data);\n\t\t\t}\n\t\t}\n\n\t\tif (Array.isArray(entry)) {\n\t\t\tfor (let j = 0, k = entry.length; j < k; j++) {\n\t\t\t\tentry[j] = await this.#renderComputedUnit(entry[j], data);\n\t\t\t}\n\t\t}\n\n\t\treturn entry;\n\t}\n\n\t_addComputedEntry(computedData, obj, parentKey, declaredDependencies) {\n\t\t// this check must come before isPlainObject\n\t\tif (typeof obj === \"function\") {\n\t\t\tcomputedData.add(parentKey, obj, declaredDependencies);\n\t\t} else if (Array.isArray(obj) || typeof obj === \"string\") {\n\t\t\t// Arrays are treated as one entry in the dependency graph now, Issue #3728\n\t\t\tcomputedData.addTemplateString(\n\t\t\t\tparentKey,\n\t\t\t\tasync function (innerData) {\n\t\t\t\t\treturn this.tmpl.#renderComputedUnit(obj, innerData);\n\t\t\t\t},\n\t\t\t\tdeclaredDependencies,\n\t\t\t\tthis.getParseForSymbolsFunction(obj),\n\t\t\t\tthis,\n\t\t\t);\n\t\t} else if (isPlainObject(obj)) {\n\t\t\t// Arrays used to be computed here\n\t\t\tfor (let key in obj) {\n\t\t\t\tlet keys = [];\n\t\t\t\tif (parentKey) {\n\t\t\t\t\tkeys.push(parentKey);\n\t\t\t\t}\n\t\t\t\tkeys.push(key);\n\t\t\t\tthis._addComputedEntry(computedData, obj[key], keys.join(\".\"), declaredDependencies);\n\t\t\t}\n\t\t} else {\n\t\t\t// Numbers, booleans, etc\n\t\t\tcomputedData.add(parentKey, obj, declaredDependencies);\n\t\t}\n\t}\n\n\tasync addComputedData(data) {\n\t\tif (isPlainObject(data?.[this.config.keys.computed])) {\n\t\t\tthis.computedData = new ComputedData(this.config);\n\n\t\t\t// Note that `permalink` is only a thing that gets consumed—it does not go directly into generated data\n\t\t\t// this allows computed entries to use page.url or page.outputPath and they’ll be resolved properly\n\n\t\t\t// TODO Room for optimization here—we don’t need to recalculate `getOutputHref` and `getOutputPath`\n\t\t\t// TODO Why are these using addTemplateString instead of add\n\t\t\tthis.computedData.addTemplateString(\n\t\t\t\t\"page.url\",\n\t\t\t\tasync function (data) {\n\t\t\t\t\treturn this.tmpl.getOutputHref(data);\n\t\t\t\t},\n\t\t\t\tdata.permalink ? [\"permalink\"] : undefined,\n\t\t\t\tfalse, // skip symbol resolution\n\t\t\t\tthis,\n\t\t\t);\n\n\t\t\tthis.computedData.addTemplateString(\n\t\t\t\t\"page.outputPath\",\n\t\t\t\tasync function (data) {\n\t\t\t\t\treturn this.tmpl.getOutputPath(data);\n\t\t\t\t},\n\t\t\t\tdata.permalink ? [\"permalink\"] : undefined,\n\t\t\t\tfalse, // skip symbol resolution\n\t\t\t\tthis,\n\t\t\t);\n\n\t\t\t// Check for reserved properties in computed data\n\t\t\tif (this.config.freezeReservedData) {\n\t\t\t\tReservedData.checkSubset(data[this.config.keys.computed]);\n\t\t\t}\n\n\t\t\t// actually add the computed data\n\t\t\tthis._addComputedEntry(this.computedData, data[this.config.keys.computed]);\n\n\t\t\t// limited run of computed data—save the stuff that relies on collections for later.\n\t\t\tdebug(\"First round of computed data for %o\", this.inputPath);\n\t\t\tawait this.computedData.setupData(data, function (entry) {\n\t\t\t\treturn !this.isUsesStartsWith(entry, \"collections.\");\n\n\t\t\t\t// TODO possible improvement here is to only process page.url, page.outputPath, permalink\n\t\t\t\t// instead of only punting on things that rely on collections.\n\t\t\t\t// let firstPhaseComputedData = [\"page.url\", \"page.outputPath\", ...this.getOrderFor(\"page.url\"), ...this.getOrderFor(\"page.outputPath\")];\n\t\t\t\t// return firstPhaseComputedData.indexOf(entry) > -1;\n\t\t\t});\n\t\t} else {\n\t\t\tif (!(\"page\" in data)) {\n\t\t\t\tdata.page = {};\n\t\t\t}\n\n\t\t\t// pagination will already have these set via Pagination->getPageTemplates\n\t\t\tif (data.page.url && data.page.outputPath) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tlet { href, path, dir } = await this.getOutputLocations(data);\n\t\t\tdata.page.url = href;\n\t\t\tdata.page.outputPath = path;\n\t\t\tdata.page.dir = dir;\n\t\t}\n\t}\n\n\t// Computed data consuming collections!\n\tasync resolveRemainingComputedData(data) {\n\t\t// If it doesn’t exist, computed data is not used for this template\n\t\tif (this.computedData) {\n\t\t\tdebug(\"Second round of computed data for %o\", this.inputPath);\n\t\t\treturn this.computedData.processRemainingData(data);\n\t\t}\n\t}\n\n\tstatic augmentWithTemplateContentProperty(obj) {\n\t\treturn Object.defineProperties(obj, {\n\t\t\tneedsCheck: {\n\t\t\t\tenumerable: false,\n\t\t\t\twritable: true,\n\t\t\t\tvalue: true,\n\t\t\t},\n\t\t\t_templateContent: {\n\t\t\t\tenumerable: false,\n\t\t\t\twritable: true,\n\t\t\t\tvalue: undefined,\n\t\t\t},\n\t\t\ttemplateContent: {\n\t\t\t\tenumerable: true,\n\t\t\t\tset(content) {\n\t\t\t\t\tif (content === undefined) {\n\t\t\t\t\t\tthis.needsCheck = false;\n\t\t\t\t\t}\n\t\t\t\t\tthis._templateContent = content;\n\t\t\t\t},\n\t\t\t\tget() {\n\t\t\t\t\tif (this.needsCheck && this._templateContent === undefined) {\n\t\t\t\t\t\tif (this.template.isRenderable()) {\n\t\t\t\t\t\t\t// should at least warn here\n\t\t\t\t\t\t\tthrow new TemplateContentPrematureUseError(\n\t\t\t\t\t\t\t\t`Tried to use templateContent too early on ${this.inputPath}${\n\t\t\t\t\t\t\t\t\tthis.pageNumber ? ` (page ${this.pageNumber})` : \"\"\n\t\t\t\t\t\t\t\t}`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tthrow new TemplateContentUnrenderedTemplateError(\n\t\t\t\t\t\t\t\t`Tried to use templateContent on unrendered template: ${\n\t\t\t\t\t\t\t\t\tthis.inputPath\n\t\t\t\t\t\t\t\t}${this.pageNumber ? ` (page ${this.pageNumber})` : \"\"}`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn this._templateContent;\n\t\t\t\t},\n\t\t\t},\n\t\t\t// Alias for templateContent for consistency\n\t\t\tcontent: {\n\t\t\t\tenumerable: true,\n\t\t\t\tget() {\n\t\t\t\t\treturn this.templateContent;\n\t\t\t\t},\n\t\t\t\tset() {\n\t\t\t\t\tthrow new Error(\"Setter not available for `content`. Use `templateContent` instead.\");\n\t\t\t\t},\n\t\t\t},\n\t\t});\n\t}\n\n\tasync runPreprocessors(data) {\n\t\t// @cachedproperty\n\t\tif (!this.#preprocessorCache) {\n\t\t\tthis.#preprocessorCache = this.templatePreprocessor.runAll(this, data);\n\t\t}\n\n\t\treturn this.#preprocessorCache;\n\t}\n\n\tasync getTemplates(data) {\n\t\tlet { skippedVia: skippedViaPreprocessorName, content: rawInput } =\n\t\t\tawait this.runPreprocessors(data);\n\n\t\tif (skippedViaPreprocessorName) {\n\t\t\tdebug(\n\t\t\t\t\"Skipping %o, the %o preprocessor returned an explicit `false`\",\n\t\t\t\tthis.inputPath,\n\t\t\t\tskippedViaPreprocessorName,\n\t\t\t);\n\t\t\treturn [];\n\t\t}\n\n\t\t// Raw Input *includes* preprocessor modifications\n\t\t// https://github.com/11ty/eleventy/issues/1206\n\t\tdata.page.rawInput = rawInput;\n\n\t\tif (!Pagination.hasPagination(data)) {\n\t\t\tawait this.addComputedData(data);\n\n\t\t\tlet obj = {\n\t\t\t\ttemplate: this, // not on the docs but folks are relying on it\n\t\t\t\trawInput,\n\t\t\t\tgroupNumber: 0, // i18n plugin\n\t\t\t\tdata,\n\n\t\t\t\tpage: data.page,\n\t\t\t\tinputPath: this.inputPath,\n\t\t\t\tfileSlug: this.fileSlugStr,\n\t\t\t\tfilePathStem: this.filePathStem,\n\t\t\t\tdate: data.page.date,\n\t\t\t\toutputPath: data.page.outputPath,\n\t\t\t\turl: data.page.url,\n\t\t\t};\n\n\t\t\tobj = Template.augmentWithTemplateContentProperty(obj);\n\n\t\t\treturn [obj];\n\t\t} else {\n\t\t\t// needs collections for pagination items\n\t\t\t// but individual pagination entries won’t be part of a collection\n\t\t\tthis.paging = new Pagination(this, data, this.config);\n\n\t\t\tlet pageTemplates = await this.paging.getPageTemplates();\n\t\t\tlet objects = [];\n\n\t\t\tfor (let pageEntry of pageTemplates) {\n\t\t\t\tawait pageEntry.template.addComputedData(pageEntry.data);\n\n\t\t\t\tlet obj = {\n\t\t\t\t\ttemplate: pageEntry.template, // not on the docs but folks are relying on it\n\t\t\t\t\trawInput,\n\t\t\t\t\tpageNumber: pageEntry.pageNumber,\n\t\t\t\t\tgroupNumber: pageEntry.groupNumber || 0,\n\n\t\t\t\t\tdata: pageEntry.data,\n\n\t\t\t\t\tinputPath: this.inputPath,\n\t\t\t\t\tfileSlug: this.fileSlugStr,\n\t\t\t\t\tfilePathStem: this.filePathStem,\n\n\t\t\t\t\tpage: pageEntry.data.page,\n\t\t\t\t\tdate: pageEntry.data.page.date,\n\t\t\t\t\toutputPath: pageEntry.data.page.outputPath,\n\t\t\t\t\turl: pageEntry.data.page.url,\n\t\t\t\t};\n\n\t\t\t\tobj = Template.augmentWithTemplateContentProperty(obj);\n\n\t\t\t\tobjects.push(obj);\n\t\t\t}\n\n\t\t\treturn objects;\n\t\t}\n\t}\n\n\tasync _write({ url, outputPath, data, rawInput }, finalContent) {\n\t\tlet lang = {\n\t\t\tstart: \"Writing\",\n\t\t\tfinished: \"written\",\n\t\t};\n\n\t\tif (!this.isDryRun) {\n\t\t\tif (this.logger.isLoggingEnabled()) {\n\t\t\t\tlet isVirtual = this.isVirtualTemplate();\n\t\t\t\tlet tr = await this.getTemplateRender();\n\t\t\t\tlet engineList = tr.getReadableEnginesListDifferingFromFileExtension();\n\t\t\t\tlet suffix = `${isVirtual ? \" (virtual)\" : \"\"}${engineList ? ` (${engineList})` : \"\"}`;\n\t\t\t\tthis.logger.log(\n\t\t\t\t\t`${lang.start} ${outputPath} ${chalk.gray(`from ${this.inputPath}${suffix}`)}`,\n\t\t\t\t);\n\t\t\t}\n\t\t} else if (this.isDryRun) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet templateBenchmarkDir = this.bench.get(\"Template make parent directory\");\n\t\ttemplateBenchmarkDir.before();\n\n\t\tthis.fsManager.createDirectoryForFileSync(outputPath);\n\n\t\ttemplateBenchmarkDir.after();\n\n\t\tif (!Buffer.isBuffer(finalContent) && typeof finalContent !== \"string\") {\n\t\t\tthrow new Error(\n\t\t\t\t`The return value from the render function for the ${this.engine.name} template was not a String or Buffer. Received ${finalContent}`,\n\t\t\t);\n\t\t}\n\n\t\tlet templateBenchmark = this.bench.get(\"Template Write\");\n\t\ttemplateBenchmark.before();\n\n\t\tthis.fsManager.writeFileSync(outputPath, finalContent);\n\n\t\ttemplateBenchmark.after();\n\t\tthis.writeCount++;\n\t\tdebug(`${outputPath} ${lang.finished}.`);\n\n\t\tlet ret = {\n\t\t\tinputPath: this.inputPath,\n\t\t\toutputPath: outputPath,\n\t\t\turl,\n\t\t\tcontent: finalContent,\n\t\t\trawInput,\n\t\t};\n\n\t\tif (data && this.config.dataFilterSelectors?.size > 0) {\n\t\t\tret.data = this.retrieveDataForJsonOutput(data, this.config.dataFilterSelectors);\n\t\t}\n\n\t\treturn ret;\n\t}\n\n\tasync #renderPageEntryWithLayoutsAndTransforms(pageEntry) {\n\t\t// Don’t run linters/transforms/layouts if we didn’t render (via incremental)!\n\t\tif (pageEntry.template.isDryRun && pageEntry.template.isIncremental) {\n\t\t\treturn pageEntry.templateContent;\n\t\t}\n\n\t\tlet content;\n\t\tlet layoutKey = pageEntry.data[this.config.keys.layout];\n\t\tif (this.engine.useLayouts() && layoutKey) {\n\t\t\tlet layout = pageEntry.template.getLayout(layoutKey);\n\t\t\tcontent = await layout.renderPageEntry(pageEntry);\n\t\t} else {\n\t\t\tcontent = pageEntry.templateContent;\n\t\t}\n\n\t\tawait this.runLinters(content, pageEntry);\n\n\t\tcontent = await this.runTransforms(content, pageEntry);\n\t\treturn content;\n\t}\n\n\tasync renderPageEntry(pageEntry) {\n\t\t// @cachedproperty\n\t\tif (!pageEntry.template._cacheRenderedTransformsAndLayoutsPromise) {\n\t\t\tpageEntry.template._cacheRenderedTransformsAndLayoutsPromise =\n\t\t\t\tthis.#renderPageEntryWithLayoutsAndTransforms(pageEntry);\n\t\t}\n\n\t\treturn pageEntry.template._cacheRenderedTransformsAndLayoutsPromise;\n\t}\n\n\tretrieveDataForJsonOutput(data, selectors) {\n\t\t// if \"*\" is in the selectors, return all data unfiltered.\n\t\tif (selectors.has(\"*\")) {\n\t\t\treturn data;\n\t\t}\n\n\t\tlet filtered = {};\n\t\tfor (let selector of selectors) {\n\t\t\tlet value = lodashGet(data, selector);\n\t\t\tlodashSet(filtered, selector, value);\n\t\t}\n\t\treturn filtered;\n\t}\n\n\tasync generateMapEntry(mapEntry, to) {\n\t\tlet ret = [];\n\n\t\tfor (let page of mapEntry._pages) {\n\t\t\tlet content;\n\n\t\t\t// Note that behavior.render is overridden when using json output\n\t\t\tif (page.template.isRenderable()) {\n\t\t\t\t// this reuses page.templateContent, it doesn’t render it\n\t\t\t\tcontent = await page.template.renderPageEntry(page);\n\t\t\t}\n\n\t\t\tif (to === \"json\") {\n\t\t\t\tlet obj = {\n\t\t\t\t\turl: page.url,\n\t\t\t\t\tinputPath: page.inputPath,\n\t\t\t\t\toutputPath: page.outputPath,\n\t\t\t\t\trawInput: page.rawInput,\n\t\t\t\t\tcontent: content,\n\t\t\t\t};\n\n\t\t\t\tif (this.config.dataFilterSelectors?.size > 0) {\n\t\t\t\t\tobj.data = this.retrieveDataForJsonOutput(page.data, this.config.dataFilterSelectors);\n\t\t\t\t}\n\n\t\t\t\t// json\n\t\t\t\tret.push(obj);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (!page.template.isRenderable()) {\n\t\t\t\tdebug(\"Template not written %o from %o.\", page.outputPath, page.template.inputPath);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (!page.template.behavior.isWriteable()) {\n\t\t\t\tdebug(\n\t\t\t\t\t\"Template not written %o from %o (via permalink: false, permalink.build: false, or a permalink object without a build property).\",\n\t\t\t\t\tpage.outputPath,\n\t\t\t\t\tpage.template.inputPath,\n\t\t\t\t);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// compile returned undefined\n\t\t\tif (content !== undefined) {\n\t\t\t\tret.push(this._write(page, content));\n\t\t\t}\n\t\t}\n\n\t\treturn Promise.all(ret);\n\t}\n\n\tasync clone() {\n\t\t// TODO do we need to even run the constructor here or can we simplify it even more\n\t\tlet tmpl = new Template(\n\t\t\tthis.inputPath,\n\t\t\tthis.templateData,\n\t\t\tthis.extensionMap,\n\t\t\tthis.eleventyConfig,\n\t\t);\n\n\t\t// We use this cheap property setter below instead\n\t\t// await tmpl.getTemplateRender();\n\n\t\t// preserves caches too, e.g. _frontMatterDataCache\n\t\t// Does not yet include .computedData\n\t\tfor (let key in this) {\n\t\t\ttmpl[key] = this[key];\n\t\t}\n\n\t\treturn tmpl;\n\t}\n\n\tgetWriteCount() {\n\t\treturn this.writeCount;\n\t}\n\n\tgetRenderCount() {\n\t\treturn this.renderCount;\n\t}\n\n\tgetInputFileStat() {\n\t\t// @cachedproperty\n\t\tif (!this.#stats) {\n\t\t\tthis.#stats = statSync(this.inputPath);\n\t\t}\n\n\t\treturn this.#stats;\n\t}\n\n\tasync _getDateInstance(key = \"birthtimeMs\") {\n\t\tlet stat = this.getInputFileStat();\n\n\t\t// Issue 1823: https://github.com/11ty/eleventy/issues/1823\n\t\t// return current Date in a Lambda\n\t\t// otherwise ctime would be \"1980-01-01T00:00:00.000Z\"\n\t\t// otherwise birthtime would be \"1970-01-01T00:00:00.000Z\"\n\t\tif (stat.birthtimeMs === 0) {\n\t\t\treturn new Date();\n\t\t}\n\n\t\tlet newDate = new Date(stat[key]);\n\n\t\tdebug(\n\t\t\t\"Template date: using file’s %o for %o of %o (from %o)\",\n\t\t\tkey,\n\t\t\tthis.inputPath,\n\t\t\tnewDate,\n\t\t\tstat.birthtimeMs,\n\t\t);\n\n\t\treturn newDate;\n\t}\n\n\tasync getMappedDate(data) {\n\t\tlet dateValue = data?.date;\n\n\t\t// These can return a Date object, or a string.\n\t\t// Already type checked to be functions in UserConfig\n\t\tfor (let fn of this.config.customDateParsing) {\n\t\t\tlet ret = fn.call(\n\t\t\t\t{\n\t\t\t\t\tpage: data.page,\n\t\t\t\t},\n\t\t\t\tdateValue,\n\t\t\t);\n\n\t\t\tif (ret) {\n\t\t\t\tdebug(\"getMappedDate: date value override via `addDateParsing` callback to %o\", ret);\n\t\t\t\tdateValue = ret;\n\t\t\t}\n\t\t}\n\n\t\tif (dateValue) {\n\t\t\tdebug(\"getMappedDate: using a date in the data for %o of %o\", this.inputPath, data.date);\n\t\t\tif (dateValue?.constructor?.name === \"DateTime\") {\n\t\t\t\t// a luxon instance\n\t\t\t\tdebug(\"getMappedDate: found DateTime instance: %o\", dateValue);\n\t\t\t\treturn dateValue.toJSDate();\n\t\t\t}\n\n\t\t\tif (dateValue instanceof Date) {\n\t\t\t\t// YAML does its own date parsing\n\t\t\t\tdebug(\"getMappedDate: found Date instance (maybe from YAML): %o\", dateValue);\n\t\t\t\treturn dateValue;\n\t\t\t}\n\n\t\t\tif (typeof dateValue !== \"string\") {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Data cascade value for \\`date\\` (${dateValue}) is invalid for ${this.inputPath}. Expected a JavaScript Date instance, luxon DateTime instance, or String value.`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// special strings\n\t\t\tif (!this.isVirtualTemplate()) {\n\t\t\t\tif (dateValue.toLowerCase() === \"git last modified\") {\n\t\t\t\t\tlet timestamp = await getUpdatedTimestamp(this.inputPath);\n\t\t\t\t\tif (timestamp) {\n\t\t\t\t\t\tdebug(\n\t\t\t\t\t\t\t`getMappedDate: found git last modified timestamp for ${this.inputPath}: %o`,\n\t\t\t\t\t\t\ttimestamp,\n\t\t\t\t\t\t);\n\t\t\t\t\t\treturn new Date(timestamp);\n\t\t\t\t\t}\n\n\t\t\t\t\t// return now if this file is not yet available in `git`\n\t\t\t\t\treturn new Date();\n\t\t\t\t}\n\t\t\t\tif (dateValue.toLowerCase() === \"last modified\") {\n\t\t\t\t\treturn this._getDateInstance(\"ctimeMs\");\n\t\t\t\t}\n\t\t\t\tif (dateValue.toLowerCase() === \"git created\") {\n\t\t\t\t\tlet timestamp = await getCreatedTimestamp(this.inputPath);\n\t\t\t\t\tif (timestamp) {\n\t\t\t\t\t\tdebug(\n\t\t\t\t\t\t\t`getMappedDate: found git created timestamp for ${this.inputPath}: %o`,\n\t\t\t\t\t\t\ttimestamp,\n\t\t\t\t\t\t);\n\t\t\t\t\t\treturn new Date(timestamp);\n\t\t\t\t\t}\n\n\t\t\t\t\t// return now if this file is not yet available in `git`\n\t\t\t\t\treturn new Date();\n\t\t\t\t}\n\t\t\t\tif (dateValue.toLowerCase() === \"created\") {\n\t\t\t\t\treturn this._getDateInstance(\"birthtimeMs\");\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// try to parse with Luxon\n\t\t\treturn fromISOtoDateUTC(dateValue, this.inputPath);\n\t\t}\n\n\t\t// No Date supplied in the Data Cascade, try to find the date in the file name\n\t\tlet filepathRegex = this.inputPath.match(/(\\d{4}-\\d{2}-\\d{2})/);\n\t\tif (filepathRegex !== null) {\n\t\t\t// if multiple are found in the path, use the first one for the date\n\t\t\tlet dateObj = fromISOtoDateUTC(filepathRegex[1], this.inputPath);\n\t\t\tdebug(\n\t\t\t\t\"getMappedDate: using filename regex time for %o of %o: %o\",\n\t\t\t\tthis.inputPath,\n\t\t\t\tfilepathRegex[1],\n\t\t\t\tdateObj,\n\t\t\t);\n\t\t\treturn dateObj;\n\t\t}\n\n\t\t// No Date supplied in the Data Cascade\n\t\tif (this.isVirtualTemplate()) {\n\t\t\treturn new Date();\n\t\t}\n\n\t\treturn this._getDateInstance(\"birthtimeMs\");\n\t}\n\n\t// Important reminder: Template data is first generated in TemplateMap\n\tasync getTemplateMapEntries(data) {\n\t\tdebugDev(\"%o getMapped()\", this.inputPath);\n\n\t\tthis.behavior.setRenderViaDataCascade(data);\n\n\t\tlet entries = [];\n\t\t// does not return outputPath or url, we don’t want to render permalinks yet\n\t\tentries.push({\n\t\t\ttemplate: this,\n\t\t\tinputPath: this.inputPath,\n\t\t\tdata,\n\t\t});\n\n\t\treturn entries;\n\t}\n}\n\nexport default Template;\n"
  },
  {
    "path": "src/TemplateBehavior.js",
    "content": "import { isPlainObject } from \"@11ty/eleventy-utils\";\n\nclass TemplateBehavior {\n\t#isRenderOptional;\n\n\tconstructor(config) {\n\t\tthis.render = true;\n\t\tthis.write = true;\n\t\tthis.outputFormat = null;\n\n\t\tif (!config) {\n\t\t\tthrow new Error(\"Missing config argument in TemplateBehavior\");\n\t\t}\n\t\tthis.config = config;\n\t}\n\n\t// Render override set to false\n\tisRenderableDisabled() {\n\t\treturn this.renderableOverride === false;\n\t}\n\n\tisRenderableOptional() {\n\t\treturn this.#isRenderOptional;\n\t}\n\n\t// undefined (fallback), true, false\n\tsetRenderableOverride(renderableOverride) {\n\t\tif (renderableOverride === \"optional\") {\n\t\t\tthis.#isRenderOptional = true;\n\t\t\tthis.renderableOverride = undefined;\n\t\t} else {\n\t\t\tthis.#isRenderOptional = false;\n\t\t\tthis.renderableOverride = renderableOverride;\n\t\t}\n\t}\n\n\t// permalink *has* a build key or output is json\n\tisRenderable() {\n\t\treturn this.renderableOverride ?? (this.render || this.isRenderForced());\n\t}\n\n\tsetOutputFormat(format) {\n\t\tthis.outputFormat = format;\n\t}\n\n\tisRenderForced() {\n\t\treturn this.outputFormat === \"json\";\n\t}\n\n\tisWriteable() {\n\t\treturn this.write;\n\t}\n\n\t// Duplicate logic with TemplatePermalink constructor\n\tsetRenderViaDataCascade(data) {\n\t\t// render is false *only* if `build` key does not exist in permalink objects (both in data and eleventyComputed)\n\t\t// (note that permalink: false means it won’t write but will still render)\n\n\t\tlet keys = new Set();\n\t\tif (isPlainObject(data.permalink)) {\n\t\t\tfor (let key of Object.keys(data.permalink)) {\n\t\t\t\tkeys.add(key);\n\t\t\t}\n\t\t}\n\n\t\tlet computedKey = this.config.keys.computed;\n\t\tif (computedKey in data && isPlainObject(data[computedKey]?.permalink)) {\n\t\t\tfor (let key of Object.keys(data[computedKey].permalink)) {\n\t\t\t\tkeys.add(key);\n\t\t\t}\n\t\t}\n\n\t\tif (keys.size) {\n\t\t\tthis.render = keys.has(\"build\");\n\t\t}\n\t}\n\n\tsetFromPermalink(templatePermalink) {\n\t\t// this.render is duplicated between TemplatePermalink and `setRenderViaDataCascade` above\n\t\tthis.render = templatePermalink._isRendered;\n\n\t\tthis.write = templatePermalink._writeToFileSystem;\n\t}\n}\nexport default TemplateBehavior;\n"
  },
  {
    "path": "src/TemplateCollection.js",
    "content": "import { TemplatePath } from \"@11ty/eleventy-utils\";\n\nimport TemplateData from \"./Data/TemplateData.js\";\nimport Sortable from \"./Util/Objects/Sortable.js\";\nimport { isGlobMatch } from \"./Util/GlobMatcher.js\";\n\nclass TemplateCollection extends Sortable {\n\tconstructor() {\n\t\tsuper();\n\n\t\tthis._filteredByGlobsCache = new Map();\n\t}\n\n\tgetAll() {\n\t\treturn this.items.slice();\n\t}\n\n\tgetAllSorted() {\n\t\treturn this.sort(Sortable.sortFunctionDateInputPath);\n\t}\n\n\tgetSortedByDate() {\n\t\treturn this.sort(Sortable.sortFunctionDate);\n\t}\n\n\tgetGlobs(globs) {\n\t\tif (typeof globs === \"string\") {\n\t\t\tglobs = [globs];\n\t\t}\n\n\t\tglobs = globs.map((glob) => TemplatePath.addLeadingDotSlash(glob));\n\n\t\treturn globs;\n\t}\n\n\tgetFilteredByGlob(globs) {\n\t\tglobs = this.getGlobs(globs);\n\n\t\tlet key = globs.join(\"::\");\n\t\tif (!this._dirty) {\n\t\t\t// Try to find a pre-sorted list and clone it.\n\t\t\tif (this._filteredByGlobsCache.has(key)) {\n\t\t\t\treturn [...this._filteredByGlobsCache.get(key)];\n\t\t\t}\n\t\t} else if (this._filteredByGlobsCache.size) {\n\t\t\t// Blow away cache\n\t\t\tthis._filteredByGlobsCache = new Map();\n\t\t}\n\n\t\tlet filtered = this.getAllSorted().filter((item) => {\n\t\t\treturn isGlobMatch(item.inputPath, globs);\n\t\t});\n\t\tthis._dirty = false;\n\t\tthis._filteredByGlobsCache.set(key, [...filtered]);\n\t\treturn filtered;\n\t}\n\n\tgetFilteredByTag(tagName) {\n\t\treturn this.getAllSorted().filter((item) => {\n\t\t\tif (!tagName || TemplateData.getIncludedTagNames(item.data).includes(tagName)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\treturn false;\n\t\t});\n\t}\n\n\tgetFilteredByTags(...tags) {\n\t\treturn this.getAllSorted().filter((item) => {\n\t\t\tlet itemTags = new Set(TemplateData.getIncludedTagNames(item.data));\n\t\t\treturn tags.every((requiredTag) => {\n\t\t\t\treturn itemTags.has(requiredTag);\n\t\t\t});\n\t\t});\n\t}\n}\n\nexport default TemplateCollection;\n"
  },
  {
    "path": "src/TemplateConfig.js",
    "content": "import { Merge, TemplatePath, isPlainObject } from \"@11ty/eleventy-utils\";\nimport debugUtil from \"debug\";\n\nimport chalk from \"./Adapters/Packages/chalk.js\";\nimport getDefaultConfig from \"./Adapters/getDefaultConfig.js\";\nimport { EleventyImportRaw } from \"./Util/Require.js\";\nimport EleventyBaseError from \"./Errors/EleventyBaseError.js\";\nimport UserConfig from \"./UserConfig.js\";\nimport GlobalDependencyMap from \"./GlobalDependencyMap.js\";\nimport ExistsCache from \"./Util/ExistsCache.js\";\nimport eventBus from \"./EventBus.js\";\nimport ProjectTemplateFormats from \"./Util/ProjectTemplateFormats.js\";\nimport { isTypeScriptSupported } from \"./Util/FeatureTests.cjs\";\n\nconst debug = debugUtil(\"Eleventy:TemplateConfig\");\nconst debugDev = debugUtil(\"Dev:Eleventy:TemplateConfig\");\n\n/**\n * @module 11ty/eleventy/TemplateConfig\n */\n\n/**\n * Config as used by the template.\n * @typedef {object} module:11ty/eleventy/TemplateConfig~TemplateConfig~config\n * @property {String} [pathPrefix] - The path prefix.\n */\n\n/**\n * Errors in eleventy config.\n * @ignore\n */\nclass EleventyConfigError extends EleventyBaseError {}\n\n/**\n * Errors in eleventy plugins.\n * @ignore\n */\nclass EleventyPluginError extends EleventyBaseError {}\n\n/**\n * Config for a template.\n * @ignore\n * @param {{}} customRootConfig - tbd.\n * @param {String} projectConfigPath - Path to local project config.\n */\nclass TemplateConfig {\n\t#templateFormats;\n\t#runMode;\n\t#configManuallyDefined = false;\n\t/** @type {UserConfig} */\n\t#userConfig = new UserConfig();\n\t#existsCache = new ExistsCache();\n\t#usesGraph;\n\t#previousBuildModifiedFile;\n\t#activeConfigPath;\n\n\tconstructor(customRootConfig, projectConfigPath) {\n\t\t/** @type {object} */\n\t\tthis.overrides = {};\n\n\t\t/**\n\t\t * @type {String}\n\t\t * @description Path to local project config.\n\t\t * @default .eleventy.js\n\t\t */\n\t\tif (projectConfigPath !== undefined) {\n\t\t\tthis.#configManuallyDefined = true;\n\n\t\t\tif (!projectConfigPath) {\n\t\t\t\t// falsy skips config files\n\t\t\t\tthis.projectConfigPaths = [];\n\t\t\t} else {\n\t\t\t\tthis.projectConfigPaths = [projectConfigPath];\n\t\t\t}\n\t\t} else {\n\t\t\tthis.projectConfigPaths = [\n\t\t\t\t\".eleventy.js\",\n\t\t\t\t\"eleventy.config.js\",\n\t\t\t\t\"eleventy.config.mjs\",\n\t\t\t\t\"eleventy.config.cjs\",\n\t\t\t];\n\n\t\t\tif (isTypeScriptSupported()) {\n\t\t\t\tthis.projectConfigPaths.push(\"eleventy.config.ts\");\n\t\t\t\tthis.projectConfigPaths.push(\"eleventy.config.mts\");\n\t\t\t\tthis.projectConfigPaths.push(\"eleventy.config.cts\");\n\t\t\t}\n\t\t}\n\n\t\tif (customRootConfig) {\n\t\t\t/**\n\t\t\t * @type {object}\n\t\t\t * @description Custom root config.\n\t\t\t */\n\t\t\tthis.customRootConfig = customRootConfig;\n\t\t\tdebug(\"Warning: Using custom root config!\");\n\t\t} else {\n\t\t\tthis.customRootConfig = null;\n\t\t}\n\n\t\tthis.hasConfigMerged = false;\n\t\tthis.isEsm = false;\n\n\t\t// Wire up exists API to user config\n\t\tthis.userConfig.exists = (filePath) => {\n\t\t\treturn this.existsCache.exists(filePath);\n\t\t};\n\n\t\tthis.userConfig.events.on(\"eleventy#templateModified\", (inputPath, metadata = {}) => {\n\t\t\t// Might support multiple at some point\n\t\t\tthis.setPreviousBuildModifiedFile(inputPath, metadata);\n\n\t\t\t// Issue #3569, set that this file exists in the cache\n\t\t\tthis.#existsCache.set(inputPath, true);\n\t\t});\n\t}\n\n\tsetPreviousBuildModifiedFile(inputPath, metadata = {}) {\n\t\tthis.#previousBuildModifiedFile = inputPath;\n\t}\n\n\tgetPreviousBuildModifiedFile() {\n\t\treturn this.#previousBuildModifiedFile;\n\t}\n\n\tget userConfig() {\n\t\treturn this.#userConfig;\n\t}\n\n\tget aggregateBenchmark() {\n\t\treturn this.userConfig.benchmarks.aggregate;\n\t}\n\n\t/* Setter for Logger */\n\tsetLogger(logger) {\n\t\tthis.logger = logger;\n\t\tthis.userConfig.logger = this.logger;\n\t}\n\n\t/* Setter for Directories instance */\n\tsetDirectories(directories) {\n\t\tthis.directories = directories;\n\t\tthis.userConfig.directories = directories.getUserspaceInstance();\n\t}\n\n\t/* Setter for TemplateFormats instance */\n\tsetTemplateFormats(templateFormats) {\n\t\tthis.#templateFormats = templateFormats;\n\t}\n\n\tget templateFormats() {\n\t\tif (!this.#templateFormats) {\n\t\t\tthis.#templateFormats = new ProjectTemplateFormats();\n\t\t}\n\t\treturn this.#templateFormats;\n\t}\n\n\t/* Backwards compat */\n\tget inputDir() {\n\t\treturn this.directories.input;\n\t}\n\n\tsetRunMode(runMode) {\n\t\tthis.#runMode = runMode;\n\t}\n\n\tshouldSpiderJavaScriptDependencies() {\n\t\t// not for a standard build\n\t\treturn (\n\t\t\t(this.#runMode === \"watch\" || this.#runMode === \"serve\") &&\n\t\t\tthis.userConfig.watchJavaScriptDependencies\n\t\t);\n\t}\n\n\t/**\n\t * Normalises local project config file path.\n\t *\n\t * @method\n\t * @returns {String|undefined} - The normalised local project config file path.\n\t */\n\tgetLocalProjectConfigFile() {\n\t\tlet configFiles = this.getLocalProjectConfigFiles();\n\t\tlet configFile = configFiles.find((path) => path && this.existsCache.exists(path));\n\t\tif (configFile) {\n\t\t\treturn configFile;\n\t\t}\n\t}\n\n\tgetLocalProjectConfigFiles() {\n\t\tlet paths = this.projectConfigPaths;\n\t\tif (paths?.length > 0) {\n\t\t\treturn TemplatePath.addLeadingDotSlashArray(paths.filter((path) => Boolean(path)));\n\t\t}\n\t\treturn [];\n\t}\n\n\tgetActiveConfigPath() {\n\t\tif (!this.#activeConfigPath) {\n\t\t\tthis.#activeConfigPath = this.getLocalProjectConfigFile();\n\t\t}\n\t\treturn this.#activeConfigPath;\n\t}\n\n\tsetProjectUsingEsm(isEsmProject) {\n\t\tthis.isEsm = !!isEsmProject;\n\t\tthis.usesGraph.setIsEsm(isEsmProject);\n\t}\n\n\tgetIsProjectUsingEsm() {\n\t\treturn this.isEsm;\n\t}\n\n\t/**\n\t * Resets the configuration.\n\t */\n\tasync reset() {\n\t\tthis.#existsCache.reset();\n\n\t\tdebugDev(\"Resetting configuration: TemplateConfig and UserConfig.\");\n\t\tthis.userConfig.reset();\n\t\tthis.usesGraph.reset(); // needs to be before forceReloadConfig #3711\n\n\t\t// await this.initializeRootConfig();\n\t\tawait this.forceReloadConfig();\n\n\t\t// Clear the compile cache\n\t\teventBus.emit(\"eleventy.compileCacheReset\");\n\t}\n\n\t/**\n\t * Resets the configuration while in watch mode.\n\t *\n\t * @todo Add implementation.\n\t */\n\tresetOnWatch() {\n\t\t// nothing yet\n\t}\n\n\thasInitialized() {\n\t\treturn this.hasConfigMerged;\n\t}\n\n\t/**\n\t * Async-friendly init method\n\t */\n\tasync init(overrides) {\n\t\tthis.#activeConfigPath = undefined; // reset\n\n\t\tawait this.initializeRootConfig();\n\n\t\tif (overrides) {\n\t\t\tthis.appendToRootConfig(overrides);\n\t\t}\n\n\t\tthis.config = await this.mergeConfig();\n\t\tthis.hasConfigMerged = true;\n\t}\n\n\t/**\n\t * Force a reload of the configuration object.\n\t */\n\tasync forceReloadConfig() {\n\t\tthis.hasConfigMerged = false;\n\t\tawait this.init();\n\t}\n\n\t/**\n\t * Returns the config object.\n\t *\n\t * @returns {{}} - The config object.\n\t */\n\tgetConfig() {\n\t\tif (!this.hasConfigMerged) {\n\t\t\tthrow new Error(\"Invalid call to .getConfig(). Needs an .init() first.\");\n\t\t}\n\n\t\treturn this.config;\n\t}\n\n\t/**\n\t * Overwrites the config path.\n\t *\n\t * @param {String} path - The new config path.\n\t */\n\tasync setProjectConfigPath(path) {\n\t\tthis.#configManuallyDefined = true;\n\n\t\tif (path !== undefined) {\n\t\t\tthis.projectConfigPaths = [path];\n\t\t} else {\n\t\t\tthis.projectConfigPaths = [];\n\t\t}\n\n\t\tif (this.hasConfigMerged) {\n\t\t\t// merge it again\n\t\t\tdebugDev(\"Merging in getConfig again after setting the local project config path.\");\n\t\t\tawait this.forceReloadConfig();\n\t\t}\n\t}\n\n\t/**\n\t * Overwrites the path prefix.\n\t *\n\t * @param {String} pathPrefix - The new path prefix.\n\t */\n\tsetPathPrefix(pathPrefix) {\n\t\tif (pathPrefix && pathPrefix !== \"/\") {\n\t\t\tdebug(\"Setting pathPrefix to %o\", pathPrefix);\n\t\t\tthis.overrides.pathPrefix = pathPrefix;\n\t\t}\n\t}\n\n\t/**\n\t * Gets the current path prefix denoting the root folder the output will be deployed to\n\t *\n\t *  @returns {String} - The path prefix string\n\t */\n\tgetPathPrefix() {\n\t\tif (this.overrides.pathPrefix) {\n\t\t\treturn this.overrides.pathPrefix;\n\t\t}\n\n\t\tif (!this.hasConfigMerged) {\n\t\t\tthrow new Error(\"Config has not yet merged. Needs `init()`.\");\n\t\t}\n\n\t\treturn this.config?.pathPrefix;\n\t}\n\n\t/**\n\t * Bootstraps the config object.\n\t */\n\tasync initializeRootConfig() {\n\t\tthis.rootConfig = this.customRootConfig;\n\t\tif (!this.rootConfig) {\n\t\t\tthis.rootConfig = await getDefaultConfig();\n\t\t}\n\n\t\tif (typeof this.rootConfig === \"function\") {\n\t\t\t// Not yet using async in defaultConfig.js\n\t\t\tthis.rootConfig = this.rootConfig.call(this, this.userConfig);\n\t\t}\n\n\t\tdebug(\"Default Eleventy config %o\", this.rootConfig);\n\t}\n\n\t/*\n\t * Add additional overrides to the root config object, used for testing\n\t *\n\t * @param {object} - a subset of the return Object from the user’s config file.\n\t */\n\tappendToRootConfig(obj) {\n\t\tObject.assign(this.rootConfig, obj);\n\t}\n\n\t/*\n\t * Process the userland plugins from the Config\n\t *\n\t * @param {object} - the return Object from the user’s config file.\n\t */\n\tasync processPlugins({ dir, pathPrefix }) {\n\t\tthis.userConfig.dir = dir;\n\t\tthis.userConfig.pathPrefix = pathPrefix;\n\n\t\t// for Nested addPlugin calls, Issue #1925\n\t\tthis.userConfig._enablePluginExecution();\n\n\t\tlet storedActiveNamespace = this.userConfig.activeNamespace;\n\t\tfor (let { plugin, options, pluginNamespace } of this.userConfig.plugins) {\n\t\t\ttry {\n\t\t\t\tthis.userConfig.activeNamespace = pluginNamespace;\n\t\t\t\tawait this.userConfig._executePlugin(plugin, options);\n\t\t\t} catch (e) {\n\t\t\t\tlet name = this.userConfig._getPluginName(plugin);\n\t\t\t\tlet namespaces = [storedActiveNamespace, pluginNamespace].filter((entry) => !!entry);\n\n\t\t\t\tlet namespaceStr = \"\";\n\t\t\t\tif (namespaces.length) {\n\t\t\t\t\tnamespaceStr = ` (namespace: ${namespaces.join(\".\")})`;\n\t\t\t\t}\n\n\t\t\t\tthrow new EleventyPluginError(\n\t\t\t\t\t`Error processing ${name ? `the \\`${name}\\`` : \"a\"} plugin${namespaceStr}`,\n\t\t\t\t\te,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tthis.userConfig.activeNamespace = storedActiveNamespace;\n\n\t\tthis.userConfig._disablePluginExecution();\n\t}\n\n\t/**\n\t * Fetches and executes the local configuration file\n\t *\n\t * @returns {Promise<object>} merged - The merged config file object.\n\t */\n\tasync requireLocalConfigFile() {\n\t\tlet localConfig = {};\n\t\tlet exportedConfig = {};\n\n\t\tlet path = this.getActiveConfigPath();\n\n\t\tif (this.projectConfigPaths.length > 0 && this.#configManuallyDefined && !path) {\n\t\t\tthrow new EleventyConfigError(\n\t\t\t\t\"A configuration file was specified but not found: \" + this.projectConfigPaths.join(\", \"),\n\t\t\t);\n\t\t}\n\n\t\tdebug(`Merging default config with ${path}`);\n\t\tif (path) {\n\t\t\ttry {\n\t\t\t\tlet { default: configDefaultReturn, config: exportedConfigObject } =\n\t\t\t\t\tawait EleventyImportRaw(path, this.isEsm ? \"esm\" : \"cjs\");\n\n\t\t\t\texportedConfig = exportedConfigObject || {};\n\n\t\t\t\tif (this.directories && Object.keys(exportedConfigObject?.dir || {}).length > 0) {\n\t\t\t\t\tdebug(\n\t\t\t\t\t\t\"Setting directories via `config.dir` export from config file: %o\",\n\t\t\t\t\t\texportedConfigObject.dir,\n\t\t\t\t\t);\n\t\t\t\t\tthis.directories.setViaConfigObject(exportedConfigObject.dir);\n\t\t\t\t}\n\n\t\t\t\tif (typeof configDefaultReturn === \"function\") {\n\t\t\t\t\tlocalConfig = await configDefaultReturn(this.userConfig);\n\t\t\t\t} else {\n\t\t\t\t\tlocalConfig = configDefaultReturn;\n\t\t\t\t}\n\n\t\t\t\t// Removed a check for `filters` in 3.0.0-alpha.6 (now using addTransform instead) https://v3.11ty.dev/docs/config/#transforms\n\t\t\t} catch (err) {\n\t\t\t\tlet isModuleError =\n\t\t\t\t\terr instanceof Error && (err?.message || \"\").includes(\"Cannot find module\");\n\n\t\t\t\t// TODO the error message here is bad and I feel bad (needs more accurate info)\n\t\t\t\treturn Promise.reject(\n\t\t\t\t\tnew EleventyConfigError(\n\t\t\t\t\t\t`Error in your Eleventy config file '${path}'.` +\n\t\t\t\t\t\t\t(isModuleError ? chalk.cyan(\" You may need to run `npm install`.\") : \"\"),\n\t\t\t\t\t\terr,\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t}\n\t\t} else {\n\t\t\tdebug(\n\t\t\t\t\"Project config file not found (not an error—skipping). Looked in: %o\",\n\t\t\t\tthis.projectConfigPaths,\n\t\t\t);\n\t\t}\n\n\t\treturn {\n\t\t\tlocalConfig,\n\t\t\texportedConfig,\n\t\t};\n\t}\n\n\t/**\n\t * Merges different config files together.\n\t *\n\t * @returns {Promise<object>} merged - The merged config file.\n\t */\n\tasync mergeConfig() {\n\t\tlet { localConfig, exportedConfig } = await this.requireLocalConfigFile();\n\n\t\t// Merge `export const config = {}` with `return {}` in config callback\n\t\tif (isPlainObject(exportedConfig)) {\n\t\t\tlocalConfig = Merge(localConfig || {}, exportedConfig);\n\t\t}\n\n\t\tif (this.directories) {\n\t\t\tif (Object.keys(this.userConfig.directoryAssignments || {}).length > 0) {\n\t\t\t\tdebug(\n\t\t\t\t\t\"Setting directories via set*Directory configuration APIs %o\",\n\t\t\t\t\tthis.userConfig.directoryAssignments,\n\t\t\t\t);\n\t\t\t\tthis.directories.setViaConfigObject(this.userConfig.directoryAssignments);\n\t\t\t}\n\n\t\t\tif (localConfig && Object.keys(localConfig?.dir || {}).length > 0) {\n\t\t\t\tdebug(\n\t\t\t\t\t\"Setting directories via `dir` object return from configuration file: %o\",\n\t\t\t\t\tlocalConfig.dir,\n\t\t\t\t);\n\t\t\t\tthis.directories.setViaConfigObject(localConfig.dir);\n\t\t\t}\n\t\t}\n\n\t\t// `templateFormats` is an override via `setTemplateFormats`\n\t\tif (this.userConfig?.templateFormats) {\n\t\t\tthis.templateFormats.setViaConfig(this.userConfig.templateFormats);\n\t\t} else if (localConfig?.templateFormats || this.rootConfig?.templateFormats) {\n\t\t\t// Local project config or defaultConfig.js\n\t\t\tthis.templateFormats.setViaConfig(\n\t\t\t\tlocalConfig.templateFormats || this.rootConfig?.templateFormats,\n\t\t\t);\n\t\t}\n\n\t\t// `templateFormatsAdded` is additive via `addTemplateFormats`\n\t\tif (this.userConfig?.templateFormatsAdded) {\n\t\t\tthis.templateFormats.addViaConfig(this.userConfig.templateFormatsAdded);\n\t\t}\n\n\t\t// prefer Configuration API methods over return object\n\t\tif (this.userConfig?.htmlTemplateEngine !== undefined) {\n\t\t\tlocalConfig.htmlTemplateEngine = this.userConfig?.htmlTemplateEngine;\n\t\t}\n\n\t\t// prefer Configuration API methods over return object\n\t\tif (this.userConfig?.markdownTemplateEngine !== undefined) {\n\t\t\tlocalConfig.markdownTemplateEngine = this.userConfig?.markdownTemplateEngine;\n\t\t}\n\n\t\tlet mergedConfig = Merge({}, this.rootConfig, localConfig);\n\n\t\t// Setup a few properties for plugins:\n\n\t\t// Set frozen templateFormats\n\t\tmergedConfig.templateFormats = Object.freeze(this.templateFormats.getTemplateFormats());\n\n\t\t// Setup pathPrefix set via command line for plugin consumption\n\t\tif (this.overrides.pathPrefix) {\n\t\t\tmergedConfig.pathPrefix = this.overrides.pathPrefix;\n\t\t}\n\n\t\t// Returning a falsy value (e.g. \"\") from user config should reset to the default value.\n\t\tif (!mergedConfig.pathPrefix) {\n\t\t\tmergedConfig.pathPrefix = this.rootConfig.pathPrefix;\n\t\t}\n\n\t\t// This is not set in UserConfig.js so that getters aren’t converted to strings\n\t\t// We want to error if someone attempts to use a setter there.\n\t\tif (this.directories) {\n\t\t\tmergedConfig.directories = this.directories.getUserspaceInstance();\n\t\t}\n\n\t\t// Delay processing plugins until after the result of localConfig is returned\n\t\t// But BEFORE the rest of the config options are merged\n\t\t// this way we can pass directories and other template information to plugins\n\n\t\tawait this.userConfig.events.emit(\"eleventy.beforeConfig\", this.userConfig);\n\n\t\tlet pluginsBench = this.aggregateBenchmark.get(\"Processing plugins in config\");\n\t\tpluginsBench.before();\n\t\tawait this.processPlugins(mergedConfig);\n\t\tpluginsBench.after();\n\n\t\t// Template formats added via plugins\n\t\tif (this.userConfig?.templateFormatsAdded) {\n\t\t\tthis.templateFormats.addViaConfig(this.userConfig.templateFormatsAdded);\n\t\t\tmergedConfig.templateFormats = Object.freeze(this.templateFormats.getTemplateFormats());\n\t\t}\n\n\t\tlet eleventyConfigApiMergingObject = this.userConfig.getMergingConfigObject();\n\n\t\tif (\"templateFormats\" in eleventyConfigApiMergingObject) {\n\t\t\tthrow new Error(\n\t\t\t\t\"Internal error: templateFormats should not return from `getMergingConfigObject`\",\n\t\t\t);\n\t\t}\n\n\t\t// Overrides are only used by pathPrefix\n\t\tdebug(\"Configuration overrides: %o\", this.overrides);\n\t\tMerge(mergedConfig, eleventyConfigApiMergingObject, this.overrides);\n\n\t\tdebug(\"Current configuration: %o\", mergedConfig);\n\n\t\t// Add to the merged config too\n\t\tmergedConfig.uses = this.usesGraph;\n\n\t\treturn mergedConfig;\n\t}\n\n\t/**\n\t * @type {GlobalDependencyMap}\n\t */\n\tget usesGraph() {\n\t\tif (!this.#usesGraph) {\n\t\t\tthis.#usesGraph = new GlobalDependencyMap();\n\t\t\tthis.#usesGraph.setIsEsm(this.isEsm);\n\t\t\tthis.#usesGraph.setTemplateConfig(this);\n\t\t}\n\t\treturn this.#usesGraph;\n\t}\n\n\t/**\n\t * @type {GlobalDependencyMap}\n\t */\n\tget uses() {\n\t\tif (!this.usesGraph) {\n\t\t\tthrow new Error(\"The Eleventy Global Dependency Graph has not yet been initialized.\");\n\t\t}\n\t\treturn this.usesGraph;\n\t}\n\n\t/**\n\t * @type {ExistsCache}\n\t */\n\tget existsCache() {\n\t\treturn this.#existsCache;\n\t}\n}\n\nexport default TemplateConfig;\n"
  },
  {
    "path": "src/TemplateContent.js",
    "content": "import { readFileSync } from \"node:fs\";\nimport matter from \"@11ty/gray-matter\";\nimport lodash from \"@11ty/lodash-custom\";\nimport { DeepCopy, TemplatePath } from \"@11ty/eleventy-utils\";\nimport debugUtil from \"debug\";\n\nimport JavaScriptFrontMatter from \"./Engines/FrontMatter/JavaScript.js\";\nimport { EOL } from \"./Util/NewLineAdapter.js\";\nimport TemplateData from \"./Data/TemplateData.js\";\nimport TemplateRender from \"./TemplateRender.js\";\nimport EleventyBaseError from \"./Errors/EleventyBaseError.js\";\nimport EleventyErrorUtil from \"./Errors/EleventyErrorUtil.js\";\nimport eventBus from \"./EventBus.js\";\n\nimport { withResolvers } from \"./Util/PromiseUtil.js\";\n\nconst { set: lodashSet } = lodash;\nconst debug = debugUtil(\"Eleventy:TemplateContent\");\nconst debugDev = debugUtil(\"Dev:Eleventy:TemplateContent\");\n\nclass TemplateContentFrontMatterError extends EleventyBaseError {}\nclass TemplateContentCompileError extends EleventyBaseError {}\nclass TemplateContentRenderError extends EleventyBaseError {}\n\nclass TemplateContent {\n\t#initialized = false;\n\t#config;\n\t#templateRender;\n\t#renderPreprocessorEngine;\n\t#extensionMap;\n\t#configOptions;\n\t#frontMatterOptions;\n\n\tconstructor(inputPath, templateConfig) {\n\t\tif (!templateConfig || templateConfig.constructor.name !== \"TemplateConfig\") {\n\t\t\tthrow new Error(\"Missing or invalid `templateConfig` argument\");\n\t\t}\n\t\tthis.eleventyConfig = templateConfig;\n\t\tthis.inputPath = inputPath;\n\t}\n\n\tasync asyncTemplateInitialization() {\n\t\tif (!this.hasTemplateRender()) {\n\t\t\tawait this.getTemplateRender();\n\t\t}\n\n\t\tif (this.#initialized) {\n\t\t\treturn;\n\t\t}\n\t\tthis.#initialized = true;\n\n\t\tlet preprocessorEngineName = this.templateRender.getPreprocessorEngineName();\n\t\tif (preprocessorEngineName && this.templateRender.engine.getName() !== preprocessorEngineName) {\n\t\t\tlet engine = await this.templateRender.getEngineByName(preprocessorEngineName);\n\t\t\tthis.#renderPreprocessorEngine = engine;\n\t\t}\n\t}\n\n\tresetCachedTemplate({ eleventyConfig }) {\n\t\tthis.eleventyConfig = eleventyConfig;\n\t}\n\n\tget dirs() {\n\t\treturn this.eleventyConfig.directories;\n\t}\n\n\tget inputDir() {\n\t\treturn this.dirs.input;\n\t}\n\n\tget outputDir() {\n\t\treturn this.dirs.output;\n\t}\n\n\tgetResetTypes(types) {\n\t\tif (types) {\n\t\t\treturn Object.assign(\n\t\t\t\t{\n\t\t\t\t\tdata: false,\n\t\t\t\t\tread: false,\n\t\t\t\t\trender: false,\n\t\t\t\t},\n\t\t\t\ttypes,\n\t\t\t);\n\t\t}\n\n\t\treturn {\n\t\t\tdata: true,\n\t\t\tread: true,\n\t\t\trender: true,\n\t\t};\n\t}\n\n\t// Called during an incremental build when the template instance is cached but needs to be reset because it has changed\n\tresetCaches(types) {\n\t\ttypes = this.getResetTypes(types);\n\n\t\tif (types.read) {\n\t\t\tdelete this.readingPromise;\n\t\t\tdelete this.inputContent;\n\t\t\tdelete this._frontMatterDataCache;\n\t\t}\n\t\tif (types.render) {\n\t\t\tthis.#templateRender = undefined;\n\t\t}\n\t}\n\n\tget extensionMap() {\n\t\tif (!this.#extensionMap) {\n\t\t\tthrow new Error(\"Internal error: Missing `extensionMap` in TemplateContent.\");\n\t\t}\n\t\treturn this.#extensionMap;\n\t}\n\n\tset extensionMap(map) {\n\t\tthis.#extensionMap = map;\n\t}\n\n\tset eleventyConfig(config) {\n\t\tthis.#config = config;\n\n\t\tif (this.#config.constructor.name === \"TemplateConfig\") {\n\t\t\tthis.#configOptions = this.#config.getConfig();\n\t\t} else {\n\t\t\tthrow new Error(\"Tried to get an TemplateConfig but none was found.\");\n\t\t}\n\t}\n\n\tget eleventyConfig() {\n\t\tif (this.#config.constructor.name === \"TemplateConfig\") {\n\t\t\treturn this.#config;\n\t\t}\n\t\tthrow new Error(\"Tried to get an TemplateConfig but none was found.\");\n\t}\n\n\tget config() {\n\t\tif (this.#config.constructor.name === \"TemplateConfig\" && !this.#configOptions) {\n\t\t\tthis.#configOptions = this.#config.getConfig();\n\t\t}\n\n\t\treturn this.#configOptions;\n\t}\n\n\tget bench() {\n\t\treturn this.config.benchmarkManager.get(\"Aggregate\");\n\t}\n\n\tget engine() {\n\t\treturn this.templateRender.engine;\n\t}\n\n\tget templateRender() {\n\t\tif (!this.hasTemplateRender()) {\n\t\t\tthrow new Error(`\\`templateRender\\` has not yet initialized on ${this.inputPath}`);\n\t\t}\n\n\t\treturn this.#templateRender;\n\t}\n\n\thasTemplateRender() {\n\t\treturn !!this.#templateRender;\n\t}\n\n\tasync getTemplateRender() {\n\t\tif (!this.#templateRender) {\n\t\t\tthis.#templateRender = new TemplateRender(this.inputPath, this.eleventyConfig);\n\t\t\tthis.#templateRender.extensionMap = this.extensionMap;\n\n\t\t\treturn this.#templateRender.init().then(() => {\n\t\t\t\treturn this.#templateRender;\n\t\t\t});\n\t\t}\n\n\t\treturn this.#templateRender;\n\t}\n\n\t// For monkey patchers\n\tget frontMatter() {\n\t\tif (this.frontMatterOverride) {\n\t\t\treturn this.frontMatterOverride;\n\t\t} else {\n\t\t\tthrow new Error(\n\t\t\t\t\"Unfortunately you’re using code that monkey patched some Eleventy internals and it isn’t async-friendly. Change your code to use the async `read()` method on the template instead!\",\n\t\t\t);\n\t\t}\n\t}\n\n\t// For monkey patchers\n\tset frontMatter(contentOverride) {\n\t\tthis.frontMatterOverride = contentOverride;\n\t}\n\n\tgetInputPath() {\n\t\treturn this.inputPath;\n\t}\n\n\tgetInputDir() {\n\t\treturn this.inputDir;\n\t}\n\n\tisVirtualTemplate() {\n\t\tlet def = this.getVirtualTemplateDefinition();\n\t\treturn !!def;\n\t}\n\n\tgetVirtualTemplateDefinition() {\n\t\tlet inputDirRelativeInputPath =\n\t\t\tthis.eleventyConfig.directories.getInputPathRelativeToInputDirectory(this.inputPath);\n\t\treturn this.config.virtualTemplates[inputDirRelativeInputPath];\n\t}\n\n\tgetFrontMatterParsingOptions() {\n\t\tif (!this.#frontMatterOptions) {\n\t\t\tthis.#frontMatterOptions = DeepCopy(\n\t\t\t\t{\n\t\t\t\t\t// Set a project-wide default.\n\t\t\t\t\t// language: \"yaml\",\n\n\t\t\t\t\t// Supplementary engines\n\t\t\t\t\tengines: {\n\t\t\t\t\t\t// Moved to a fork of gray-matter to modernize to js-yaml@4 internally\n\t\t\t\t\t\t// yaml: yaml.load.bind(yaml),\n\n\t\t\t\t\t\t// Backwards compatible with `js` object front matter\n\t\t\t\t\t\t// https://github.com/11ty/eleventy/issues/2819\n\t\t\t\t\t\tjavascript: JavaScriptFrontMatter,\n\n\t\t\t\t\t\t// Upstream `js` was removed in @11ty/gray-matter@2\n\t\t\t\t\t\tjs: JavaScriptFrontMatter,\n\n\t\t\t\t\t\tnode: function () {\n\t\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t\t\"The `node` front matter type was a 3.0.0-alpha.x only feature, removed for stable release. Rename to `js` or `javascript` instead!\",\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tthis.config.frontMatterParsingOptions,\n\t\t\t);\n\t\t}\n\n\t\treturn this.#frontMatterOptions;\n\t}\n\n\tasync #read() {\n\t\tlet content = await this.inputContent;\n\n\t\tif (content || content === \"\") {\n\t\t\tlet tr = await this.getTemplateRender();\n\t\t\tif (tr.engine.useJavaScriptImport()) {\n\t\t\t\treturn {\n\t\t\t\t\tdata: {},\n\t\t\t\t\tcontent,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tlet options = this.getFrontMatterParsingOptions();\n\t\t\tlet fm;\n\t\t\ttry {\n\t\t\t\t// Added in 3.0, passed along to front matter engines\n\t\t\t\toptions.filePath = this.inputPath;\n\t\t\t\tfm = matter(content, options);\n\t\t\t} catch (e) {\n\t\t\t\tthrow new TemplateContentFrontMatterError(\n\t\t\t\t\t`Having trouble reading front matter from template ${this.inputPath}`,\n\t\t\t\t\te,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tif (typeof fm.data?.then === \"function\") {\n\t\t\t\tfm.data = await fm.data;\n\t\t\t}\n\n\t\t\tif (options.excerpt && fm.excerpt) {\n\t\t\t\tlet excerptString = fm.excerpt + (options.excerpt_separator || \"---\");\n\t\t\t\tif (fm.content.startsWith(excerptString + EOL)) {\n\t\t\t\t\t// with an os-specific newline after excerpt separator\n\t\t\t\t\tfm.content = fm.excerpt.trim() + \"\\n\" + fm.content.slice((excerptString + EOL).length);\n\t\t\t\t} else if (fm.content.startsWith(excerptString + \"\\n\")) {\n\t\t\t\t\t// with a newline (\\n) after excerpt separator\n\t\t\t\t\t// This is necessary for some git configurations on windows\n\t\t\t\t\tfm.content = fm.excerpt.trim() + \"\\n\" + fm.content.slice((excerptString + 1).length);\n\t\t\t\t} else if (fm.content.startsWith(excerptString)) {\n\t\t\t\t\t// no newline after excerpt separator\n\t\t\t\t\tfm.content = fm.excerpt + fm.content.slice(excerptString.length);\n\t\t\t\t}\n\n\t\t\t\t// alias, defaults to page.excerpt\n\t\t\t\tlet alias = options.excerpt_alias || \"page.excerpt\";\n\t\t\t\tlodashSet(fm.data, alias, fm.excerpt);\n\t\t\t}\n\n\t\t\t// For monkey patchers that used `frontMatter` 🤧\n\t\t\t// https://github.com/11ty/eleventy/issues/613#issuecomment-999637109\n\t\t\t// https://github.com/11ty/eleventy/issues/2710#issuecomment-1373854834\n\t\t\t// Removed this._frontMatter monkey patcher help in 3.0.0-alpha.7\n\n\t\t\treturn fm;\n\t\t} else {\n\t\t\treturn {\n\t\t\t\tdata: {},\n\t\t\t\tcontent: \"\",\n\t\t\t\texcerpt: \"\",\n\t\t\t};\n\t\t}\n\t}\n\n\tasync read() {\n\t\tif (!this.readingPromise) {\n\t\t\tif (!this.inputContent) {\n\t\t\t\t// @cachedproperty\n\t\t\t\tthis.inputContent = this.getInputContent();\n\t\t\t}\n\n\t\t\t// @cachedproperty\n\t\t\tthis.readingPromise = this.#read();\n\t\t}\n\n\t\treturn this.readingPromise;\n\t}\n\n\t/* Incremental builds cache the Template instances (in TemplateWriter) but\n\t * these template specific caches are important for Pagination */\n\tstatic cache(path, content) {\n\t\tthis._inputCache.set(TemplatePath.absolutePath(path), content);\n\t}\n\n\tstatic getCached(path) {\n\t\treturn this._inputCache.get(TemplatePath.absolutePath(path));\n\t}\n\n\tstatic deleteFromInputCache(path) {\n\t\tthis._inputCache.delete(TemplatePath.absolutePath(path));\n\t}\n\n\t// Used via clone\n\tsetInputContent(content) {\n\t\tthis.inputContent = content;\n\t}\n\n\tasync getInputContent() {\n\t\tlet tr = await this.getTemplateRender();\n\n\t\tlet virtualTemplateDefinition = this.getVirtualTemplateDefinition();\n\t\tif (virtualTemplateDefinition) {\n\t\t\tlet { content } = virtualTemplateDefinition;\n\t\t\treturn content;\n\t\t}\n\n\t\tif (\n\t\t\ttr.engine.useJavaScriptImport() &&\n\t\t\ttypeof tr.engine.getInstanceFromInputPath === \"function\"\n\t\t) {\n\t\t\treturn tr.engine.getInstanceFromInputPath(this.inputPath);\n\t\t}\n\n\t\tif (!tr.engine.needsToReadFileContents()) {\n\t\t\treturn \"\";\n\t\t}\n\n\t\tlet templateBenchmark = this.bench.get(\"Template Read\");\n\t\ttemplateBenchmark.before();\n\n\t\tlet content;\n\n\t\tif (this.config.useTemplateCache) {\n\t\t\tcontent = TemplateContent.getCached(this.inputPath);\n\t\t}\n\n\t\tif (!content && content !== \"\") {\n\t\t\tlet contentBuffer = readFileSync(this.inputPath);\n\n\t\t\tcontent = contentBuffer.toString(\"utf8\");\n\n\t\t\tif (this.config.useTemplateCache) {\n\t\t\t\tTemplateContent.cache(this.inputPath, content);\n\t\t\t}\n\t\t}\n\n\t\ttemplateBenchmark.after();\n\n\t\treturn content;\n\t}\n\n\tasync _testGetFrontMatter() {\n\t\tlet fm = this.frontMatterOverride ? this.frontMatterOverride : await this.read();\n\n\t\treturn fm;\n\t}\n\n\tasync getPreRender() {\n\t\tlet fm = this.frontMatterOverride ? this.frontMatterOverride : await this.read();\n\n\t\treturn fm.content;\n\t}\n\n\tasync #getFrontMatterData() {\n\t\tlet fm = await this.read();\n\n\t\t// gray-matter isn’t async-friendly but can return a promise from custom front matter\n\t\tif (fm.data instanceof Promise) {\n\t\t\tfm.data = await fm.data;\n\t\t}\n\n\t\tlet tr = await this.getTemplateRender();\n\t\tlet extraData = await tr.engine.getExtraDataFromFile(this.inputPath);\n\n\t\tlet virtualTemplateDefinition = this.getVirtualTemplateDefinition();\n\t\tlet virtualTemplateData;\n\t\tif (virtualTemplateDefinition) {\n\t\t\tvirtualTemplateData = virtualTemplateDefinition.data;\n\t\t}\n\n\t\tlet data = Object.assign({}, fm.data, extraData, virtualTemplateData);\n\n\t\tTemplateData.cleanupData(data, {\n\t\t\tfile: this.inputPath,\n\t\t\tisVirtualTemplate: Boolean(virtualTemplateData),\n\t\t});\n\n\t\treturn {\n\t\t\tdata,\n\t\t\texcerpt: fm.excerpt,\n\t\t};\n\t}\n\n\tasync getFrontMatterData() {\n\t\tif (!this._frontMatterDataCache) {\n\t\t\t// @cachedproperty\n\t\t\tthis._frontMatterDataCache = this.#getFrontMatterData();\n\t\t}\n\n\t\treturn this._frontMatterDataCache;\n\t}\n\n\tgetEngineNames(engineOverride) {\n\t\treturn this.templateRender.getEnginesList(engineOverride);\n\t}\n\n\tasync getEngineOverride() {\n\t\treturn this.getFrontMatterData().then((data) => {\n\t\t\treturn data[this.config.keys.engineOverride];\n\t\t});\n\t}\n\n\t// checks engines\n\tisTemplateCacheable() {\n\t\tif (this.#renderPreprocessorEngine) {\n\t\t\treturn this.#renderPreprocessorEngine.cacheable;\n\t\t}\n\t\treturn this.engine.cacheable;\n\t}\n\n\t_getCompileCache(str) {\n\t\t// Caches used to be bifurcated based on engine name, now they’re based on inputPath\n\t\t// TODO does `cacheable` need to help inform whether a cache is used here?\n\t\tlet inputPathMap = TemplateContent._compileCache.get(this.inputPath);\n\t\tif (!inputPathMap) {\n\t\t\tinputPathMap = new Map();\n\t\t\tTemplateContent._compileCache.set(this.inputPath, inputPathMap);\n\t\t}\n\n\t\tlet cacheable = this.isTemplateCacheable();\n\t\tlet { useCache, key } = this.engine.getCompileCacheKey(str, this.inputPath);\n\n\t\t// We also tie the compile cache key to the UserConfig instance, to alleviate issues with global template cache\n\t\t// Better to move the cache to the Eleventy instance instead, no?\n\t\t// (This specifically failed I18nPluginTest cases with filters being cached across tests and not having access to each plugin’s options)\n\t\tkey = this.eleventyConfig.userConfig._getUniqueId() + key;\n\n\t\treturn [cacheable, key, inputPathMap, useCache];\n\t}\n\n\tasync compile(str, options = {}) {\n\t\tlet { type, bypassMarkdown, engineOverride } = options;\n\n\t\t// Must happen before cacheable fetch below\n\t\t// Likely only necessary for Eleventy Layouts, see TemplateMap->initDependencyMap\n\t\tawait this.asyncTemplateInitialization();\n\n\t\t// this.templateRender is guaranteed here\n\t\tlet tr = await this.getTemplateRender();\n\t\tif (engineOverride !== undefined) {\n\t\t\tdebugDev(\"%o overriding template engine to use %o\", this.inputPath, engineOverride);\n\t\t\tawait tr.setEngineOverride(engineOverride, bypassMarkdown);\n\t\t} else {\n\t\t\ttr.setUseMarkdown(!bypassMarkdown);\n\t\t}\n\t\tif (bypassMarkdown && !this.engine.needsCompilation(str)) {\n\t\t\treturn function () {\n\t\t\t\treturn str;\n\t\t\t};\n\t\t}\n\n\t\tdebugDev(\"%o compile() using engine: %o\", this.inputPath, tr.engineName);\n\n\t\ttry {\n\t\t\tlet res;\n\t\t\tif (this.config.useTemplateCache) {\n\t\t\t\tlet [cacheable, key, cache, useCache] = this._getCompileCache(str);\n\t\t\t\tif (cacheable && key) {\n\t\t\t\t\tif (useCache && cache.has(key)) {\n\t\t\t\t\t\tthis.bench.get(\"(count) Template Compile Cache Hit\").incrementCount();\n\t\t\t\t\t\treturn cache.get(key);\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.bench.get(\"(count) Template Compile Cache Miss\").incrementCount();\n\n\t\t\t\t\t// Compile cache is cleared when the resource is modified (below)\n\n\t\t\t\t\t// Compilation is async, so we eagerly cache a Promise that eventually\n\t\t\t\t\t// resolves to the compiled function\n\t\t\t\t\tlet withRes = withResolvers();\n\t\t\t\t\tres = withRes.resolve;\n\n\t\t\t\t\tcache.set(key, withRes.promise);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tlet typeStr = type ? ` ${type}` : \"\";\n\t\t\tlet templateBenchmark = this.bench.get(`Template Compile${typeStr}`);\n\t\t\tlet inputPathBenchmark = this.bench.get(`> Compile${typeStr} > ${this.inputPath}`);\n\t\t\ttemplateBenchmark.before();\n\t\t\tinputPathBenchmark.before();\n\n\t\t\tlet fn = await tr.getCompiledTemplate(str);\n\t\t\tinputPathBenchmark.after();\n\t\t\ttemplateBenchmark.after();\n\t\t\tdebugDev(\"%o getCompiledTemplate function created\", this.inputPath);\n\t\t\tif (this.config.useTemplateCache && res) {\n\t\t\t\tres(fn);\n\t\t\t}\n\t\t\treturn fn;\n\t\t} catch (e) {\n\t\t\tlet [cacheable, key, cache] = this._getCompileCache(str);\n\t\t\tif (cacheable && key) {\n\t\t\t\tcache.delete(key);\n\t\t\t}\n\t\t\tdebug(`Having trouble compiling template ${this.inputPath}: %O`, str);\n\t\t\tthrow new TemplateContentCompileError(\n\t\t\t\t`Having trouble compiling template ${this.inputPath}`,\n\t\t\t\te,\n\t\t\t);\n\t\t}\n\t}\n\n\tgetParseForSymbolsFunction(str) {\n\t\tlet engine = this.engine;\n\n\t\t// Don’t use markdown as the engine to parse for symbols\n\t\t// TODO pass in engineOverride here\n\t\tif (this.#renderPreprocessorEngine) {\n\t\t\tengine = this.#renderPreprocessorEngine;\n\t\t}\n\n\t\tif (\"parseForSymbols\" in engine) {\n\t\t\treturn () => {\n\t\t\t\tif (Array.isArray(str)) {\n\t\t\t\t\treturn str\n\t\t\t\t\t\t.filter((entry) => typeof entry === \"string\")\n\t\t\t\t\t\t.map((entry) => engine.parseForSymbols(entry))\n\t\t\t\t\t\t.flat();\n\t\t\t\t}\n\t\t\t\tif (typeof str === \"string\") {\n\t\t\t\t\treturn engine.parseForSymbols(str);\n\t\t\t\t}\n\t\t\t\treturn [];\n\t\t\t};\n\t\t}\n\t}\n\n\t// used by computed data or for permalink functions\n\tasync _renderFunction(fn, ...args) {\n\t\tlet mixins = Object.assign({}, this.config.javascriptFunctions);\n\t\tlet result = await fn.call(mixins, ...args);\n\n\t\t// normalize Buffer away if returned from permalink\n\t\tif (Buffer.isBuffer(result)) {\n\t\t\treturn result.toString();\n\t\t}\n\n\t\treturn result;\n\t}\n\n\tasync renderComputedData(str, data) {\n\t\tif (typeof str === \"function\") {\n\t\t\treturn this._renderFunction(str, data);\n\t\t}\n\n\t\treturn this._render(str, data, {\n\t\t\ttype: \"Computed Data\",\n\t\t\tbypassMarkdown: true,\n\t\t});\n\t}\n\n\tasync renderPermalink(permalink, data) {\n\t\tlet tr = await this.getTemplateRender();\n\t\tlet permalinkCompilation = tr.engine.permalinkNeedsCompilation(permalink);\n\n\t\t// No string compilation:\n\t\t//    ({ compileOptions: { permalink: \"raw\" }})\n\t\t// These mean `permalink: false`, which is no file system writing:\n\t\t//    ({ compileOptions: { permalink: false }})\n\t\t//    ({ compileOptions: { permalink: () => false }})\n\t\t//    ({ compileOptions: { permalink: () => (() = > false) }})\n\t\tif (permalinkCompilation === false && typeof permalink !== \"function\") {\n\t\t\treturn permalink;\n\t\t}\n\n\t\t/* Custom `compile` function for permalinks, usage:\n\t\tpermalink: function(permalinkString, inputPath) {\n\t\t\treturn async function(data) {\n\t\t\t\treturn \"THIS IS MY RENDERED PERMALINK\";\n\t\t\t}\n\t\t}\n\t\t*/\n\t\tif (permalinkCompilation && typeof permalinkCompilation === \"function\") {\n\t\t\tpermalink = await this._renderFunction(permalinkCompilation, permalink, this.inputPath);\n\t\t}\n\n\t\t// Raw permalink function (in the app code data cascade)\n\t\tif (typeof permalink === \"function\") {\n\t\t\treturn this._renderFunction(permalink, data);\n\t\t}\n\n\t\treturn this._render(permalink, data, {\n\t\t\ttype: \"Permalink\",\n\t\t\tbypassMarkdown: true,\n\t\t});\n\t}\n\n\tasync render(str, data, bypassMarkdown) {\n\t\treturn this._render(str, data, {\n\t\t\ttype: \"Content\",\n\t\t\tbypassMarkdown,\n\t\t});\n\t}\n\n\t_getPaginationLogSuffix(data) {\n\t\tlet suffix = [];\n\t\tif (\"pagination\" in data) {\n\t\t\tsuffix.push(\" (\");\n\t\t\tif (data.pagination.pages) {\n\t\t\t\tsuffix.push(\n\t\t\t\t\t`${data.pagination.pages.length} page${data.pagination.pages.length !== 1 ? \"s\" : \"\"}`,\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tsuffix.push(\"Pagination\");\n\t\t\t}\n\t\t\tsuffix.push(\")\");\n\t\t}\n\t\treturn suffix.join(\"\");\n\t}\n\n\tasync _render(str, data, options = {}) {\n\t\tlet { bypassMarkdown, type } = options;\n\n\t\ttry {\n\t\t\tif (bypassMarkdown && !this.engine.needsCompilation(str)) {\n\t\t\t\treturn str;\n\t\t\t}\n\n\t\t\tlet fn = await this.compile(str, {\n\t\t\t\tbypassMarkdown,\n\t\t\t\tengineOverride: data[this.config.keys.engineOverride],\n\t\t\t\ttype,\n\t\t\t});\n\n\t\t\tif (fn === undefined) {\n\t\t\t\treturn;\n\t\t\t} else if (typeof fn !== \"function\") {\n\t\t\t\tthrow new Error(`The \\`compile\\` function did not return a function. Received ${fn}`);\n\t\t\t}\n\n\t\t\t// Benchmark\n\t\t\tlet templateBenchmark = this.bench.get(\"Render\");\n\t\t\tlet inputPathBenchmark = this.bench.get(\n\t\t\t\t`> Render${type ? ` ${type}` : \"\"} > ${this.inputPath}${this._getPaginationLogSuffix(data)}`,\n\t\t\t);\n\n\t\t\ttemplateBenchmark.before();\n\t\t\tif (inputPathBenchmark) {\n\t\t\t\tinputPathBenchmark.before();\n\t\t\t}\n\n\t\t\tlet rendered = await fn(data);\n\n\t\t\tif (inputPathBenchmark) {\n\t\t\t\tinputPathBenchmark.after();\n\t\t\t}\n\t\t\ttemplateBenchmark.after();\n\t\t\tdebugDev(\"%o getCompiledTemplate called, rendered content created\", this.inputPath);\n\t\t\treturn rendered;\n\t\t} catch (e) {\n\t\t\tif (EleventyErrorUtil.isPrematureTemplateContentError(e)) {\n\t\t\t\treturn Promise.reject(e);\n\t\t\t} else {\n\t\t\t\tlet tr = await this.getTemplateRender();\n\t\t\t\tlet engine = tr.getReadableEnginesList();\n\t\t\t\tdebug(`Having trouble rendering ${engine} template ${this.inputPath}: %O`, str);\n\t\t\t\treturn Promise.reject(\n\t\t\t\t\tnew TemplateContentRenderError(\n\t\t\t\t\t\t`Having trouble rendering ${engine} template ${this.inputPath}`,\n\t\t\t\t\t\te,\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\tgetExtensionEntries() {\n\t\treturn this.engine.extensionEntries;\n\t}\n\n\tisFileRelevantToThisTemplate(incrementalFile, metadata = {}) {\n\t\t// always relevant if incremental file not set (build everything)\n\t\tif (!incrementalFile) {\n\t\t\treturn true;\n\t\t}\n\n\t\tlet hasDependencies = this.engine.hasDependencies(incrementalFile);\n\n\t\tlet isRelevant = this.engine.isFileRelevantTo(this.inputPath, incrementalFile);\n\n\t\tdebug(\n\t\t\t\"Test dependencies to see if %o is relevant to %o: %o\",\n\t\t\tthis.inputPath,\n\t\t\tincrementalFile,\n\t\t\tisRelevant,\n\t\t);\n\n\t\tlet extensionEntries = this.getExtensionEntries().filter((entry) => !!entry.isIncrementalMatch);\n\t\tif (extensionEntries.length) {\n\t\t\tfor (let entry of extensionEntries) {\n\t\t\t\tif (\n\t\t\t\t\tentry.isIncrementalMatch.call(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tinputPath: this.inputPath,\n\t\t\t\t\t\t\tisFullTemplate: metadata.isFullTemplate,\n\t\t\t\t\t\t\tisFileRelevantToInputPath: isRelevant,\n\t\t\t\t\t\t\tdoesFileHaveDependencies: hasDependencies,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tincrementalFile,\n\t\t\t\t\t)\n\t\t\t\t) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn false;\n\t\t} else {\n\t\t\t// Not great way of building all templates if this is a layout, include, JS dependency.\n\t\t\t// TODO improve this for default template syntaxes\n\n\t\t\t// This is the fallback way of determining if something is incremental (no isIncrementalMatch available)\n\t\t\t// This will be true if the inputPath and incrementalFile are the same\n\t\t\tif (isRelevant) {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\t// only return true here if dependencies are not known\n\t\t\tif (!hasDependencies && !metadata.isFullTemplate) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n}\n\nTemplateContent._inputCache = new Map();\nTemplateContent._compileCache = new Map();\neventBus.on(\"eleventy.resourceModified\", (path) => {\n\t// delete from input cache\n\tTemplateContent.deleteFromInputCache(path);\n\n\t// delete from compile cache\n\tlet normalized = TemplatePath.addLeadingDotSlash(path);\n\tlet compileCache = TemplateContent._compileCache.get(normalized);\n\tif (compileCache) {\n\t\tcompileCache.clear();\n\t}\n});\n\n// Used when the configuration file reset https://github.com/11ty/eleventy/issues/2147\neventBus.on(\"eleventy.compileCacheReset\", () => {\n\tTemplateContent._compileCache = new Map();\n});\n\nexport default TemplateContent;\n"
  },
  {
    "path": "src/TemplateFileSlug.js",
    "content": "import path from \"node:path\";\nimport { TemplatePath } from \"@11ty/eleventy-utils\";\n\nclass TemplateFileSlug {\n\tconstructor(inputPath, extensionMap, eleventyConfig) {\n\t\tlet inputDir = eleventyConfig.directories.input;\n\t\tif (inputDir) {\n\t\t\tinputPath = TemplatePath.stripLeadingSubPath(inputPath, inputDir);\n\t\t}\n\n\t\tthis.inputPath = inputPath;\n\t\tthis.cleanInputPath = TemplatePath.stripLeadingDotSlash(inputPath);\n\n\t\tlet dirs = this.cleanInputPath.split(\"/\");\n\t\tthis.dirs = dirs;\n\t\tthis.dirs.pop();\n\n\t\tthis.parsed = path.parse(inputPath);\n\t\tthis.filenameNoExt = extensionMap.removeTemplateExtension(this.parsed.base);\n\t}\n\n\t// `page.filePathStem` see https://v3.11ty.dev/docs/data-eleventy-supplied/#page-variable\n\tgetFullPathWithoutExtension() {\n\t\treturn \"/\" + TemplatePath.join(...this.dirs, this._getRawSlug());\n\t}\n\n\t_getRawSlug() {\n\t\tlet slug = this.filenameNoExt;\n\t\treturn this._stripDateFromSlug(slug);\n\t}\n\n\t/** Removes dates in the format of YYYY-MM-DD from a given slug string candidate. */\n\t_stripDateFromSlug(slug) {\n\t\tlet reg = slug.match(/\\d{4}-\\d{2}-\\d{2}-(.*)/);\n\t\tif (reg) {\n\t\t\treturn reg[1];\n\t\t}\n\t\treturn slug;\n\t}\n\n\t// `page.fileSlug` see https://v3.11ty.dev/docs/data-eleventy-supplied/#page-variable\n\tgetSlug() {\n\t\tlet rawSlug = this._getRawSlug();\n\n\t\tif (rawSlug === \"index\") {\n\t\t\tif (!this.dirs.length) {\n\t\t\t\treturn \"\";\n\t\t\t}\n\t\t\tlet lastDir = this.dirs[this.dirs.length - 1];\n\t\t\treturn this._stripDateFromSlug(lastDir);\n\t\t}\n\n\t\treturn rawSlug;\n\t}\n}\n\nexport default TemplateFileSlug;\n"
  },
  {
    "path": "src/TemplateGlob.js",
    "content": "import { TemplatePath } from \"@11ty/eleventy-utils\";\n\nclass TemplateGlob {\n\tstatic normalizePath(...paths) {\n\t\tif (paths[0].charAt(0) === \"!\") {\n\t\t\tthrow new Error(\n\t\t\t\t`TemplateGlob.normalizePath does not accept ! glob paths like: ${paths.join(\"\")}`,\n\t\t\t);\n\t\t}\n\t\treturn TemplatePath.addLeadingDotSlash(TemplatePath.join(...paths));\n\t}\n\n\tstatic normalize(path) {\n\t\tpath = path.trim();\n\t\tif (path.charAt(0) === \"!\") {\n\t\t\treturn \"!\" + TemplateGlob.normalizePath(path.slice(1));\n\t\t} else {\n\t\t\treturn TemplateGlob.normalizePath(path);\n\t\t}\n\t}\n\n\tstatic map(files) {\n\t\tif (typeof files === \"string\") {\n\t\t\treturn TemplateGlob.normalize(files);\n\t\t} else if (Array.isArray(files)) {\n\t\t\treturn files.map(function (path) {\n\t\t\t\treturn TemplateGlob.normalize(path);\n\t\t\t});\n\t\t} else {\n\t\t\treturn files;\n\t\t}\n\t}\n}\n\nexport default TemplateGlob;\n"
  },
  {
    "path": "src/TemplateLayout.js",
    "content": "import { Merge, TemplatePath } from \"@11ty/eleventy-utils\";\nimport debugUtil from \"debug\";\n\nimport TemplateLayoutPathResolver from \"./TemplateLayoutPathResolver.js\";\nimport TemplateContent from \"./TemplateContent.js\";\nimport layoutCache from \"./LayoutCache.js\";\n\n// const debug = debugUtil(\"Eleventy:TemplateLayout\");\nconst debugDev = debugUtil(\"Dev:Eleventy:TemplateLayout\");\n\n// https://github.com/11ty/eleventy/issues/3954\nclass CdataWrapper {\n\tstatic PREFIX = \"<![CDATA[STARTRAW\";\n\tstatic POSTFIX = \"ENDRAW]]>\";\n\n\tconstructor(pageTemplateSyntax = \"\", layoutTemplateSyntax = \"\") {\n\t\tthis.isEligible = CdataWrapper.isEligible(pageTemplateSyntax, layoutTemplateSyntax);\n\t}\n\n\t// Markdown in Markdown layout only\n\tstatic isEligible(templateSyntax, layoutTemplateSyntax) {\n\t\treturn (\n\t\t\ttemplateSyntax.split(\",\").includes(\"md\") && layoutTemplateSyntax.split(\",\").includes(\"md\")\n\t\t);\n\t}\n\n\twrap(content) {\n\t\tif (this.isEligible) {\n\t\t\treturn CdataWrapper.PREFIX + content + CdataWrapper.POSTFIX;\n\t\t}\n\t\treturn content;\n\t}\n\n\tunwrap(content) {\n\t\tif (this.isEligible) {\n\t\t\treturn content.replaceAll(CdataWrapper.PREFIX, \"\").replaceAll(CdataWrapper.POSTFIX, \"\");\n\t\t}\n\n\t\treturn content;\n\t}\n}\n\nclass TemplateLayout extends TemplateContent {\n\tconstructor(key, extensionMap, eleventyConfig) {\n\t\tif (!eleventyConfig || eleventyConfig.constructor.name !== \"TemplateConfig\") {\n\t\t\tthrow new Error(\"Expected `eleventyConfig` in TemplateLayout constructor.\");\n\t\t}\n\n\t\tlet resolver = new TemplateLayoutPathResolver(key, extensionMap, eleventyConfig);\n\t\tlet resolvedPath = resolver.getFullPath();\n\n\t\tsuper(resolvedPath, eleventyConfig);\n\n\t\tif (!extensionMap) {\n\t\t\tthrow new Error(\"Expected `extensionMap` in TemplateLayout constructor.\");\n\t\t}\n\n\t\tthis.extensionMap = extensionMap;\n\t\tthis.key = resolver.getNormalizedLayoutKey();\n\t\tthis.dataKeyLayoutPath = key;\n\t\tthis.inputPath = resolvedPath;\n\t}\n\n\tgetKey() {\n\t\treturn this.key;\n\t}\n\n\tgetFullKey() {\n\t\treturn TemplateLayout.resolveFullKey(this.dataKeyLayoutPath, this.inputDir);\n\t}\n\n\tgetCacheKeys() {\n\t\treturn new Set([this.dataKeyLayoutPath, this.getFullKey(), this.key]);\n\t}\n\n\tstatic resolveFullKey(key, inputDir) {\n\t\treturn TemplatePath.join(inputDir, key);\n\t}\n\n\tstatic getTemplate(key, eleventyConfig, extensionMap) {\n\t\tlet config = eleventyConfig.getConfig();\n\t\tif (!config.useTemplateCache) {\n\t\t\treturn new TemplateLayout(key, extensionMap, eleventyConfig);\n\t\t}\n\n\t\tlet inputDir = eleventyConfig.directories.input;\n\t\tlet fullKey = TemplateLayout.resolveFullKey(key, inputDir);\n\t\tif (!layoutCache.has(fullKey)) {\n\t\t\tlet layout = new TemplateLayout(key, extensionMap, eleventyConfig);\n\n\t\t\tlayoutCache.add(layout);\n\t\t\tdebugDev(\"Added %o to LayoutCache\", key);\n\n\t\t\treturn layout;\n\t\t}\n\n\t\treturn layoutCache.get(fullKey);\n\t}\n\n\tasync getTemplateLayoutMapEntry() {\n\t\tlet { data: frontMatterData } = await this.getFrontMatterData();\n\t\treturn {\n\t\t\t// Used by `TemplateLayout.getTemplate()`\n\t\t\tkey: this.dataKeyLayoutPath,\n\n\t\t\t// used by `this.getData()`\n\t\t\tfrontMatterData,\n\t\t};\n\t}\n\n\tasync #getTemplateLayoutMap() {\n\t\t// For both the eleventy.layouts event and cyclical layout chain checking  (e.g., a => b => c => a)\n\t\tlet layoutChain = new Set();\n\t\tlayoutChain.add(this.inputPath);\n\n\t\tlet cfgKey = this.config.keys.layout;\n\t\tlet map = [];\n\t\tlet mapEntry = await this.getTemplateLayoutMapEntry();\n\n\t\tmap.push(mapEntry);\n\n\t\twhile (mapEntry.frontMatterData && cfgKey in mapEntry.frontMatterData) {\n\t\t\t// Layout of the current layout\n\t\t\tlet parentLayoutKey = mapEntry.frontMatterData[cfgKey];\n\n\t\t\tlet layout = TemplateLayout.getTemplate(\n\t\t\t\tparentLayoutKey,\n\t\t\t\tthis.eleventyConfig,\n\t\t\t\tthis.extensionMap,\n\t\t\t);\n\n\t\t\t// Abort if a circular layout chain is detected. Otherwise, we'll time out and run out of memory.\n\t\t\tif (layoutChain.has(layout.inputPath)) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Your layouts have a circular reference, starting at ${map[0].key}! The layout at ${layout.inputPath} was specified twice in this layout chain.`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// Keep track of this layout so we can detect duplicates in subsequent iterations\n\t\t\tlayoutChain.add(layout.inputPath);\n\n\t\t\t// reassign for next loop\n\t\t\tmapEntry = await layout.getTemplateLayoutMapEntry();\n\n\t\t\tmap.push(mapEntry);\n\t\t}\n\n\t\tthis.layoutChain = Array.from(layoutChain);\n\n\t\treturn map;\n\t}\n\n\tasync getTemplateLayoutMap() {\n\t\tif (!this.cachedLayoutMap) {\n\t\t\tthis.cachedLayoutMap = this.#getTemplateLayoutMap();\n\t\t}\n\n\t\treturn this.cachedLayoutMap;\n\t}\n\n\tasync getLayoutChain() {\n\t\tif (!Array.isArray(this.layoutChain)) {\n\t\t\tawait this.getTemplateLayoutMap();\n\t\t}\n\n\t\treturn this.layoutChain;\n\t}\n\n\tasync #getData() {\n\t\tlet map = await this.getTemplateLayoutMap();\n\t\tlet dataToMerge = [];\n\t\tfor (let j = map.length - 1; j >= 0; j--) {\n\t\t\tdataToMerge.push(map[j].frontMatterData);\n\t\t}\n\n\t\t// Deep merge of layout front matter\n\t\tlet data = Merge({}, ...dataToMerge);\n\t\tdelete data[this.config.keys.layout];\n\n\t\treturn data;\n\t}\n\n\tasync getData() {\n\t\tif (!this.dataCache) {\n\t\t\tthis.dataCache = this.#getData();\n\t\t}\n\n\t\treturn this.dataCache;\n\t}\n\n\tasync #getCachedCompiledLayoutFunction() {\n\t\tlet rawInput = await this.getPreRender();\n\t\treturn this.compile(rawInput);\n\t}\n\n\t// Do only cache this layout’s render function and delegate the rest to the other templates.\n\tasync getCachedCompiledLayoutFunction() {\n\t\tif (!this.cachedCompiledLayoutFunction) {\n\t\t\tthis.cachedCompiledLayoutFunction = this.#getCachedCompiledLayoutFunction();\n\t\t}\n\n\t\treturn this.cachedCompiledLayoutFunction;\n\t}\n\n\tasync getCompiledLayoutFunctions() {\n\t\tlet layoutMap = await this.getTemplateLayoutMap();\n\t\tlet fns = [];\n\n\t\ttry {\n\t\t\tfns.push({\n\t\t\t\tinputPath: this.inputPath,\n\t\t\t\ttemplate: this,\n\t\t\t\trender: await this.getCachedCompiledLayoutFunction(),\n\t\t\t});\n\n\t\t\tif (layoutMap.length > 1) {\n\t\t\t\tlet [, /*currentLayout*/ parentLayout] = layoutMap;\n\t\t\t\tlet { key } = parentLayout;\n\n\t\t\t\tlet layoutTemplate = TemplateLayout.getTemplate(\n\t\t\t\t\tkey,\n\t\t\t\t\tthis.eleventyConfig,\n\t\t\t\t\tthis.extensionMap,\n\t\t\t\t);\n\n\t\t\t\t// The parent already includes the rest of the layout chain\n\t\t\t\tlet upstreamFns = await layoutTemplate.getCompiledLayoutFunctions();\n\t\t\t\tfor (let j = 0, k = upstreamFns.length; j < k; j++) {\n\t\t\t\t\tfns.push(upstreamFns[j]);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn fns;\n\t\t} catch (e) {\n\t\t\tdebugDev(\"Clearing LayoutCache after error.\");\n\t\t\tlayoutCache.clear();\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\tasync render() {\n\t\tthrow new Error(\"Internal error: `render` was removed from TemplateLayout.js in Eleventy 3.0.\");\n\t}\n\n\t// Inefficient? We want to compile all the templatelayouts into a single reusable callback?\n\t// Trouble: layouts may need data variables present downstream/upstream\n\t// This is called from Template->renderPageEntry\n\tasync renderPageEntry(pageEntry) {\n\t\tlet pageTemplateSyntax = pageEntry.template?.getEngineNames(\n\t\t\tpageEntry.data[this.config.keys.engineOverride],\n\t\t);\n\t\tlet templateContent = pageEntry.templateContent;\n\t\tlet compiledFunctions = await this.getCompiledLayoutFunctions();\n\n\t\tfor (let { render, template } of compiledFunctions) {\n\t\t\tlet layoutTemplateSyntax = template.getEngineNames(); // templateEngineOverride not supported in layouts\n\t\t\tlet cdata = new CdataWrapper(pageTemplateSyntax, layoutTemplateSyntax);\n\n\t\t\tlet data = {\n\t\t\t\t...pageEntry.data,\n\t\t\t\t// This should come *after* data, so `content` have override `content` props set in data cascade\n\t\t\t\tcontent: cdata.wrap(templateContent),\n\t\t\t};\n\n\t\t\ttemplateContent = cdata.unwrap(await render(data));\n\t\t}\n\n\t\t// Don’t set `templateContent` on pageEntry because collection items should not have layout markup\n\t\treturn templateContent;\n\t}\n\n\tresetCaches(types) {\n\t\tsuper.resetCaches(types);\n\t\tdelete this.dataCache;\n\t\tdelete this.layoutChain;\n\t\tdelete this.cachedLayoutMap;\n\t\tdelete this.cachedCompiledLayoutFunction;\n\t}\n}\n\nexport default TemplateLayout;\n"
  },
  {
    "path": "src/TemplateLayoutPathResolver.js",
    "content": "import { TemplatePath } from \"@11ty/eleventy-utils\";\n// import debugUtil from \"debug\";\n// const debug = debugUtil(\"Eleventy:TemplateLayoutPathResolver\");\n\nclass TemplateLayoutPathResolver {\n\tconstructor(path, extensionMap, templateConfig) {\n\t\tif (!templateConfig) {\n\t\t\tthrow new Error(\"Expected `templateConfig` in TemplateLayoutPathResolver constructor\");\n\t\t}\n\n\t\tthis.templateConfig = templateConfig;\n\t\tthis.originalPath = path;\n\t\tthis.originalDisplayPath =\n\t\t\tTemplatePath.join(this.layoutsDir, this.originalPath) +\n\t\t\t` (via \\`layout: ${this.originalPath}\\`)`; // for error messaging\n\n\t\tthis.path = path;\n\t\tthis.aliases = {};\n\t\tthis.extensionMap = extensionMap;\n\t\tif (!extensionMap) {\n\t\t\tthrow new Error(\"Expected `extensionMap` in TemplateLayoutPathResolver constructor.\");\n\t\t}\n\n\t\tthis.init();\n\t}\n\n\tgetVirtualTemplate(layoutPath) {\n\t\tlet inputDirRelativePath =\n\t\t\tthis.templateConfig.directories.getLayoutPathRelativeToInputDirectory(layoutPath);\n\t\treturn this.config.virtualTemplates[inputDirRelativePath];\n\t}\n\n\tget dirs() {\n\t\treturn this.templateConfig.directories;\n\t}\n\n\tget inputDir() {\n\t\treturn this.dirs.input;\n\t}\n\n\tget layoutsDir() {\n\t\treturn this.dirs.layouts || this.dirs.includes;\n\t}\n\n\t/* Backwards compat */\n\tgetLayoutsDir() {\n\t\treturn this.layoutsDir;\n\t}\n\n\tsetAliases() {\n\t\tthis.aliases = Object.assign({}, this.config.layoutAliases, this.aliases);\n\t}\n\n\t// for testing\n\tset config(cfg) {\n\t\tthis._config = cfg;\n\t\tthis.init();\n\t}\n\n\tget config() {\n\t\tif (!this.templateConfig) {\n\t\t\tthrow new Error(\"Internal error: Missing this.templateConfig\");\n\t\t}\n\n\t\treturn this.templateConfig.getConfig();\n\t}\n\n\texists(layoutPath) {\n\t\tif (this.getVirtualTemplate(layoutPath)) {\n\t\t\treturn true;\n\t\t}\n\t\tlet fullPath = this.templateConfig.directories.getLayoutPath(layoutPath);\n\t\tlet existsCache = this.templateConfig.existsCache;\n\t\tif (existsCache.exists(fullPath) && !existsCache.isDirectory(fullPath)) {\n\t\t\t// #4191\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\n\tinit() {\n\t\t// we might be able to move this into the constructor?\n\t\tthis.aliases = Object.assign({}, this.config.layoutAliases, this.aliases);\n\n\t\tif (this.aliases[this.path]) {\n\t\t\tthis.path = this.aliases[this.path];\n\t\t}\n\n\t\tlet useLayoutResolution = this.config.layoutResolution;\n\n\t\tif (this.path.split(\".\").length > 0 && this.exists(this.path)) {\n\t\t\tthis.filename = this.path;\n\t\t\tthis.fullPath = this.templateConfig.directories.getLayoutPath(this.path);\n\t\t} else if (useLayoutResolution) {\n\t\t\tthis.filename = this.findFileName();\n\t\t\tthis.fullPath = this.templateConfig.directories.getLayoutPath(this.filename || \"\");\n\t\t}\n\t}\n\n\taddLayoutAlias(from, to) {\n\t\tthis.aliases[from] = to;\n\t}\n\n\tgetFileName() {\n\t\tif (!this.filename) {\n\t\t\tthrow new Error(\n\t\t\t\t`You’re trying to use a layout that does not exist: ${this.originalDisplayPath}`,\n\t\t\t);\n\t\t}\n\n\t\treturn this.filename;\n\t}\n\n\tgetFullPath() {\n\t\tif (!this.filename) {\n\t\t\tthrow new Error(\n\t\t\t\t`You’re trying to use a layout that does not exist: ${this.originalDisplayPath}`,\n\t\t\t);\n\t\t}\n\n\t\treturn this.fullPath;\n\t}\n\n\tfindFileName() {\n\t\tfor (let filename of this.extensionMap.getFileList(this.path)) {\n\t\t\tif (this.exists(filename)) {\n\t\t\t\treturn filename;\n\t\t\t}\n\t\t}\n\t}\n\n\tgetNormalizedLayoutKey() {\n\t\treturn TemplatePath.stripLeadingSubPath(this.fullPath, this.layoutsDir);\n\t}\n}\n\nexport default TemplateLayoutPathResolver;\n"
  },
  {
    "path": "src/TemplateMap.js",
    "content": "import { isPlainObject, TemplatePath } from \"@11ty/eleventy-utils\";\nimport debugUtil from \"debug\";\n\nimport TemplateCollection from \"./TemplateCollection.js\";\nimport EleventyErrorUtil from \"./Errors/EleventyErrorUtil.js\";\nimport UsingCircularTemplateContentReferenceError from \"./Errors/UsingCircularTemplateContentReferenceError.js\";\nimport DuplicatePermalinkOutputError from \"./Errors/DuplicatePermalinkOutputError.js\";\nimport TemplateData from \"./Data/TemplateData.js\";\nimport GlobalDependencyMap from \"./GlobalDependencyMap.js\";\n\nconst debug = debugUtil(\"Eleventy:TemplateMap\");\n\n// These template URL filenames are allowed to exclude file extensions\nconst EXTENSIONLESS_URL_ALLOWLIST = [\n\t\"/_redirects\", // Netlify specific\n\t\"/.htaccess\", // Apache\n\t\"/_headers\", // Cloudflare\n];\n\n// must match TemplateDepGraph\nconst SPECIAL_COLLECTION_NAMES = {\n\tkeys: \"[keys]\",\n\tall: \"all\",\n};\n\nclass TemplateMap {\n\t#dependencyMapInitialized = false;\n\n\tconstructor(eleventyConfig) {\n\t\tif (!eleventyConfig || eleventyConfig.constructor.name !== \"TemplateConfig\") {\n\t\t\tthrow new Error(\"Missing or invalid `eleventyConfig` argument.\");\n\t\t}\n\t\tthis.eleventyConfig = eleventyConfig;\n\t\tthis.map = [];\n\t\tthis.inputPathMap = new Map(); // NEW: O(1) lookup Map for performance\n\t\tthis.collectionsData = null;\n\t\tthis.cached = false;\n\t\tthis.verboseOutput = true;\n\t\tthis.collection = new TemplateCollection();\n\t}\n\n\tset userConfig(config) {\n\t\tthis._userConfig = config;\n\t}\n\n\tget userConfig() {\n\t\tif (!this._userConfig) {\n\t\t\t// TODO use this.config for this, need to add collections to mergeable props in userconfig\n\t\t\tthis._userConfig = this.eleventyConfig.userConfig;\n\t\t}\n\n\t\treturn this._userConfig;\n\t}\n\n\tget config() {\n\t\tif (!this._config) {\n\t\t\tthis._config = this.eleventyConfig.getConfig();\n\t\t}\n\t\treturn this._config;\n\t}\n\n\tasync add(template) {\n\t\tif (!template) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet data = await template.getData();\n\t\tlet entries = await template.getTemplateMapEntries(data);\n\t\tlet { skippedVia } = await template.runPreprocessors(data);\n\n\t\tif (skippedVia) {\n\t\t\treturn;\n\t\t}\n\n\t\tfor (let map of entries) {\n\t\t\tthis.map.push(map);\n\t\t\tthis._addToInputPathMap(map); // NEW: Add to lookup Map for O(1) access\n\t\t}\n\t}\n\n\tgetMap() {\n\t\treturn this.map;\n\t}\n\n\t_addToInputPathMap(mapEntry) {\n\t\t// Store under absolute path\n\t\tlet absoluteInputPath = TemplatePath.absolutePath(mapEntry.inputPath);\n\t\tthis.inputPathMap.set(absoluteInputPath, mapEntry);\n\t}\n\n\tgetTagTarget(str) {\n\t\tif (str === \"collections\") {\n\t\t\t// special, means targeting `collections` specifically\n\t\t\treturn SPECIAL_COLLECTION_NAMES.keys;\n\t\t}\n\n\t\tif (str.startsWith(\"collections.\")) {\n\t\t\treturn str.slice(\"collections.\".length);\n\t\t}\n\n\t\t// Fixes #2851\n\t\tif (str.startsWith(\"collections['\") || str.startsWith('collections[\"')) {\n\t\t\treturn str.slice(\"collections['\".length, -2);\n\t\t}\n\t}\n\n\tgetPaginationTagTarget(entry) {\n\t\tif (entry.data.pagination?.data) {\n\t\t\treturn this.getTagTarget(entry.data.pagination.data);\n\t\t}\n\t}\n\n\t#addEntryToGlobalDependencyGraph(entry) {\n\t\tlet consumes = [];\n\t\tconsumes.push(this.getPaginationTagTarget(entry));\n\n\t\tif (Array.isArray(entry.data.eleventyImport?.collections)) {\n\t\t\tfor (let tag of entry.data.eleventyImport.collections) {\n\t\t\t\tconsumes.push(tag);\n\t\t\t}\n\t\t}\n\n\t\t// Important: consumers must come before publishers\n\n\t\t// TODO it’d be nice to set the dependency relationship for addCollection here\n\t\t// But collections are not yet populated (they populate after template order)\n\t\tlet publishes = TemplateData.getIncludedCollectionNames(entry.data);\n\n\t\tthis.config.uses.addNewNodeRelationships(entry.inputPath, consumes, publishes);\n\t}\n\n\taddAllToGlobalDependencyGraph() {\n\t\tthis.#dependencyMapInitialized = true;\n\n\t\t// Should come before individual entry additions\n\t\tthis.config.uses.initializeUserConfigurationApiCollections();\n\n\t\tfor (let entry of this.map) {\n\t\t\tthis.#addEntryToGlobalDependencyGraph(entry);\n\t\t}\n\t}\n\n\tasync setCollectionByTagName(tagName) {\n\t\tif (this.isUserConfigCollectionName(tagName)) {\n\t\t\t// async\n\t\t\tthis.collectionsData[tagName] = await this.getUserConfigCollection(tagName);\n\t\t} else {\n\t\t\tthis.collectionsData[tagName] = this.getTaggedCollection(tagName);\n\t\t}\n\n\t\tlet precompiled = this.config.precompiledCollections;\n\t\tif (precompiled?.[tagName]) {\n\t\t\tif (\n\t\t\t\ttagName === \"all\" ||\n\t\t\t\t!Array.isArray(this.collectionsData[tagName]) ||\n\t\t\t\tthis.collectionsData[tagName].length === 0\n\t\t\t) {\n\t\t\t\tthis.collectionsData[tagName] = precompiled[tagName];\n\t\t\t}\n\t\t}\n\t}\n\n\t// TODO(slightlyoff): major bottleneck\n\tasync initDependencyMap(fullTemplateOrder) {\n\t\t// Temporary workaround for async constructor work in templates\n\t\t// Issue #3170 #3870\n\t\tlet inputPathSet = new Set(fullTemplateOrder);\n\t\tawait Promise.all(\n\t\t\tthis.map\n\t\t\t\t.filter(({ inputPath }) => {\n\t\t\t\t\treturn inputPathSet.has(inputPath);\n\t\t\t\t})\n\t\t\t\t.map(({ template }) => {\n\t\t\t\t\t// This also happens for layouts in TemplateContent->compile\n\t\t\t\t\treturn template.asyncTemplateInitialization();\n\t\t\t\t}),\n\t\t);\n\n\t\tfor (let depEntry of fullTemplateOrder) {\n\t\t\tif (GlobalDependencyMap.isCollection(depEntry)) {\n\t\t\t\tlet tagName = GlobalDependencyMap.getTagName(depEntry);\n\t\t\t\t// [keys] should initialize `all`\n\t\t\t\tif (tagName === SPECIAL_COLLECTION_NAMES.keys) {\n\t\t\t\t\tawait this.setCollectionByTagName(\"all\");\n\t\t\t\t\t// [NAME] is special and implied (e.g. [keys])\n\t\t\t\t} else if (!tagName.startsWith(\"[\") && !tagName.endsWith(\"]\")) {\n\t\t\t\t\t// is a tag (collection) entry\n\t\t\t\t\tawait this.setCollectionByTagName(tagName);\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// is a template entry\n\t\t\tlet map = this.getMapEntryForInputPath(depEntry);\n\t\t\tawait this.#initDependencyMapEntry(map);\n\t\t}\n\t}\n\n\tasync #initDependencyMapEntry(map) {\n\t\ttry {\n\t\t\tmap._pages = await map.template.getTemplates(map.data);\n\t\t} catch (e) {\n\t\t\tthrow new Error(\"Error generating template page(s) for \" + map.inputPath + \".\", { cause: e });\n\t\t}\n\n\t\tif (map._pages.length === 0) {\n\t\t\t// Reminder: a serverless code path was removed here.\n\t\t} else {\n\t\t\tlet counter = 0;\n\t\t\tfor (let page of map._pages) {\n\t\t\t\t// Copy outputPath to map entry\n\t\t\t\t// This is no longer used internally, just for backwards compatibility\n\t\t\t\t// Error added in v3 for https://github.com/11ty/eleventy/issues/3183\n\t\t\t\tif (map.data.pagination) {\n\t\t\t\t\tif (!Object.prototype.hasOwnProperty.call(map, \"outputPath\")) {\n\t\t\t\t\t\tObject.defineProperty(map, \"outputPath\", {\n\t\t\t\t\t\t\tget() {\n\t\t\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t\t\t\"Internal error: `.outputPath` on a paginated map entry is not consistent. Use `_pages[…].outputPath` instead.\",\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t} else if (!map.outputPath) {\n\t\t\t\t\tmap.outputPath = page.outputPath;\n\t\t\t\t}\n\n\t\t\t\tif (counter === 0 || map.data.pagination?.addAllPagesToCollections) {\n\t\t\t\t\tif (map.data.eleventyExcludeFromCollections !== true) {\n\t\t\t\t\t\t// is in *some* collections\n\t\t\t\t\t\tthis.collection.add(page);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tcounter++;\n\t\t\t}\n\t\t}\n\t}\n\n\tgetTemplateOrder() {\n\t\t// 1. Templates that don’t use Pagination\n\t\t// 2. Pagination templates that consume config API collections\n\t\t// 3. Pagination templates consuming `collections`\n\t\t// 4. Pagination templates consuming `collections.all`\n\t\tlet fullTemplateOrder = this.config.uses.getTemplateOrder();\n\n\t\treturn fullTemplateOrder\n\t\t\t.map((entry) => {\n\t\t\t\tif (GlobalDependencyMap.isCollection(entry)) {\n\t\t\t\t\treturn entry;\n\t\t\t\t}\n\n\t\t\t\tlet inputPath = TemplatePath.addLeadingDotSlash(entry);\n\t\t\t\tif (!this.hasMapEntryForInputPath(inputPath)) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\treturn inputPath;\n\t\t\t})\n\t\t\t.filter(Boolean);\n\t}\n\n\tasync cache() {\n\t\tif (!this.#dependencyMapInitialized) {\n\t\t\tthis.addAllToGlobalDependencyGraph();\n\t\t}\n\n\t\tthis.collectionsData = {};\n\n\t\tfor (let entry of this.map) {\n\t\t\tentry.data.collections = this.collectionsData;\n\t\t}\n\n\t\tlet fullTemplateOrder = this.getTemplateOrder();\n\t\tdebug(\n\t\t\t\"Rendering templates in order (%o concurrency): %O\",\n\t\t\tthis.userConfig.getConcurrency(),\n\t\t\tfullTemplateOrder,\n\t\t);\n\n\t\tawait this.initDependencyMap(fullTemplateOrder);\n\t\tawait this.resolveRemainingComputedData();\n\n\t\tlet orderedPaths = this.#removeTagsFromTemplateOrder(fullTemplateOrder);\n\n\t\tlet orderedMap = orderedPaths.map((inputPath) => {\n\t\t\treturn this.getMapEntryForInputPath(inputPath);\n\t\t});\n\n\t\tawait this.config.events.emitLazy(\"eleventy.contentMap\", () => {\n\t\t\treturn {\n\t\t\t\tinputPathToUrl: this.generateInputUrlContentMap(orderedMap),\n\t\t\t\turlToInputPath: this.generateUrlMap(orderedMap),\n\t\t\t};\n\t\t});\n\n\t\tawait this.runDataSchemas(orderedMap);\n\t\tawait this.populateContentDataInMap(orderedMap);\n\n\t\tthis.populateCollectionsWithContent();\n\t\tthis.cached = true;\n\n\t\tthis.checkForDuplicatePermalinks();\n\t\tthis.checkForMissingFileExtensions();\n\n\t\tawait this.config.events.emitLazy(\"eleventy.layouts\", () => this.generateLayoutsMap());\n\t}\n\n\tgenerateInputUrlContentMap(orderedMap) {\n\t\tlet entries = {};\n\t\tfor (let entry of orderedMap) {\n\t\t\tentries[entry.inputPath] = entry._pages.map((entry) => entry.url);\n\t\t}\n\t\treturn entries;\n\t}\n\n\tgenerateUrlMap(orderedMap) {\n\t\tlet entries = {};\n\t\tfor (let entry of orderedMap) {\n\t\t\tfor (let page of entry._pages) {\n\t\t\t\t// duplicate urls throw an error, so we can return non array here\n\t\t\t\tentries[page.url] = {\n\t\t\t\t\tinputPath: entry.inputPath,\n\t\t\t\t\tgroupNumber: page.groupNumber,\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\t\treturn entries;\n\t}\n\n\thasMapEntryForInputPath(inputPath) {\n\t\treturn Boolean(this.getMapEntryForInputPath(inputPath));\n\t}\n\n\tgetMapEntryForInputPath(inputPath) {\n\t\tlet absoluteInputPath = TemplatePath.absolutePath(inputPath);\n\t\treturn this.inputPathMap.get(absoluteInputPath);\n\t}\n\n\t#removeTagsFromTemplateOrder(maps) {\n\t\treturn maps.filter((dep) => !GlobalDependencyMap.isCollection(dep));\n\t}\n\n\tasync runDataSchemas(orderedMap) {\n\t\tfor (let map of orderedMap) {\n\t\t\tif (!map._pages) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tfor (let pageEntry of map._pages) {\n\t\t\t\t// Data Schema callback #879\n\t\t\t\tif (typeof pageEntry.data[this.config.keys.dataSchema] === \"function\") {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tawait pageEntry.data[this.config.keys.dataSchema](pageEntry.data);\n\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t`Error in the data schema for: ${map.inputPath} (via \\`eleventyDataSchema\\`)`,\n\t\t\t\t\t\t\t{ cause: e },\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tasync populateContentDataInMap(orderedMap) {\n\t\tlet usedTemplateContentTooEarlyMap = [];\n\n\t\t// Note that empty pagination templates will be skipped here as not renderable\n\t\tlet filteredMap = orderedMap.filter((entry) => entry.template.isRenderable());\n\n\t\t// Get concurrency level from user config\n\t\tconst concurrency = this.userConfig.getConcurrency();\n\n\t\t// Process the templates in chunks to limit concurrency\n\t\t// This replaces the functionality of p-map's concurrency option\n\t\tfor (let i = 0; i < filteredMap.length; i += concurrency) {\n\t\t\t// Create a chunk of tasks that will run in parallel\n\t\t\tconst chunk = filteredMap.slice(i, i + concurrency);\n\n\t\t\t// Run the chunk of tasks in parallel\n\t\t\tawait Promise.all(\n\t\t\t\tchunk.map(async (map) => {\n\t\t\t\t\tif (!map._pages) {\n\t\t\t\t\t\tthrow new Error(`Internal error: _pages not found for ${map.inputPath}`);\n\t\t\t\t\t}\n\n\t\t\t\t\t// IMPORTANT: this is where template content is rendered\n\t\t\t\t\ttry {\n\t\t\t\t\t\tfor (let pageEntry of map._pages) {\n\t\t\t\t\t\t\tpageEntry.templateContent =\n\t\t\t\t\t\t\t\tawait pageEntry.template.renderPageEntryWithoutLayout(pageEntry);\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\tif (EleventyErrorUtil.isPrematureTemplateContentError(e)) {\n\t\t\t\t\t\t\t// Add to list of templates that need to be processed again\n\t\t\t\t\t\t\tusedTemplateContentTooEarlyMap.push(map);\n\n\t\t\t\t\t\t\t// Reset cached render promise\n\t\t\t\t\t\t\tfor (let pageEntry of map._pages) {\n\t\t\t\t\t\t\t\tpageEntry.template.resetCaches({ render: true });\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tthrow e;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\n\t\t// Process templates that had premature template content errors\n\t\t// This is the second pass for templates that couldn't be rendered in the first pass\n\t\tfor (let map of usedTemplateContentTooEarlyMap) {\n\t\t\ttry {\n\t\t\t\tfor (let pageEntry of map._pages) {\n\t\t\t\t\tpageEntry.templateContent =\n\t\t\t\t\t\tawait pageEntry.template.renderPageEntryWithoutLayout(pageEntry);\n\t\t\t\t}\n\t\t\t} catch (e) {\n\t\t\t\tif (EleventyErrorUtil.isPrematureTemplateContentError(e)) {\n\t\t\t\t\t// If we still have template content errors after the second pass,\n\t\t\t\t\t// it's likely a circular reference\n\t\t\t\t\tthrow new UsingCircularTemplateContentReferenceError(\n\t\t\t\t\t\t`${map.inputPath} contains a circular reference (using collections) to its own templateContent.`,\n\t\t\t\t\t);\n\t\t\t\t} else {\n\t\t\t\t\t// rethrow?\n\t\t\t\t\tthrow e;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tgetTaggedCollection(tag) {\n\t\tlet result;\n\t\tif (!tag || tag === \"all\") {\n\t\t\tresult = this.collection.getAllSorted();\n\t\t} else {\n\t\t\tresult = this.collection.getFilteredByTag(tag);\n\t\t}\n\n\t\t// May not return an array (can be anything)\n\t\t// https://www.11ty.dev/docs/collections-api/#return-values\n\t\tdebug(`Collection: collections.${tag || \"all\"} size: ${result?.length}`);\n\n\t\treturn result;\n\t}\n\n\t/* 3.0.0-alpha.1: setUserConfigCollections method removed (was only used for testing) */\n\tisUserConfigCollectionName(name) {\n\t\tlet collections = this.userConfig.getCollections();\n\t\treturn name && !!collections[name];\n\t}\n\n\tgetUserConfigCollectionNames() {\n\t\treturn Object.keys(this.userConfig.getCollections());\n\t}\n\n\tasync getUserConfigCollection(name) {\n\t\tlet configCollections = this.userConfig.getCollections();\n\n\t\t// This works with async now\n\t\tlet result = await configCollections[name](this.collection);\n\n\t\t// May not return an array (can be anything)\n\t\t// https://www.11ty.dev/docs/collections-api/#return-values\n\t\tdebug(`Collection: collections.${name} size: ${result?.length}`);\n\t\treturn result;\n\t}\n\n\tpopulateCollectionsWithContent() {\n\t\tfor (let collectionName in this.collectionsData) {\n\t\t\t// skip custom collections set in configuration files that have arbitrary types\n\t\t\tif (!Array.isArray(this.collectionsData[collectionName])) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tfor (let item of this.collectionsData[collectionName]) {\n\t\t\t\t// skip custom collections set in configuration files that have arbitrary types\n\t\t\t\tif (!isPlainObject(item) || !(\"inputPath\" in item)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tlet entry = this.getMapEntryForInputPath(item.inputPath);\n\t\t\t\t// This check skips precompiled collections\n\t\t\t\tif (entry) {\n\t\t\t\t\tlet index = item.pageNumber || 0;\n\t\t\t\t\tlet content = entry._pages[index]._templateContent;\n\t\t\t\t\tif (content !== undefined) {\n\t\t\t\t\t\titem.templateContent = content;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tasync resolveRemainingComputedData() {\n\t\tlet promises = [];\n\t\tfor (let entry of this.map) {\n\t\t\tfor (let pageEntry of entry._pages) {\n\t\t\t\tif (this.config.keys.computed in pageEntry.data) {\n\t\t\t\t\tpromises.push(pageEntry.template.resolveRemainingComputedData(pageEntry.data));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn Promise.all(promises);\n\t}\n\n\tasync generateLayoutsMap() {\n\t\tlet layouts = {};\n\n\t\tfor (let entry of this.map) {\n\t\t\tfor (let page of entry._pages) {\n\t\t\t\tlet tmpl = page.template;\n\t\t\t\tif (tmpl.templateUsesLayouts(page.data)) {\n\t\t\t\t\tlet layoutKey = page.data[this.config.keys.layout];\n\t\t\t\t\tlet layout = tmpl.getLayout(layoutKey);\n\t\t\t\t\tlet layoutChain = await layout.getLayoutChain();\n\t\t\t\t\tlet priors = [];\n\t\t\t\t\tfor (let filepath of layoutChain) {\n\t\t\t\t\t\tif (!layouts[filepath]) {\n\t\t\t\t\t\t\tlayouts[filepath] = new Set();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tlayouts[filepath].add(page.inputPath);\n\t\t\t\t\t\tfor (let prior of priors) {\n\t\t\t\t\t\t\tlayouts[filepath].add(prior);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tpriors.push(filepath);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tfor (let key in layouts) {\n\t\t\tlayouts[key] = Array.from(layouts[key]);\n\t\t}\n\n\t\treturn layouts;\n\t}\n\n\t#onEachPage(callback) {\n\t\tfor (let template of this.map) {\n\t\t\tfor (let page of template._pages) {\n\t\t\t\tcallback(page, template);\n\t\t\t}\n\t\t}\n\t}\n\n\tcheckForDuplicatePermalinks() {\n\t\tlet inputs = {};\n\t\tlet outputPaths = {};\n\t\tlet warnings = {};\n\t\tthis.#onEachPage((page, template) => {\n\t\t\tif (page.outputPath === false || page.url === false) {\n\t\t\t\t// do nothing (also serverless)\n\t\t\t} else {\n\t\t\t\t// Make sure output doesn’t overwrite input (e.g. --input=. --output=.)\n\t\t\t\t// Related to https://github.com/11ty/eleventy/issues/3327\n\t\t\t\tif (page.outputPath === page.inputPath) {\n\t\t\t\t\tthrow new DuplicatePermalinkOutputError(\n\t\t\t\t\t\t`The template at \"${page.inputPath}\" attempted to overwrite itself.`,\n\t\t\t\t\t);\n\t\t\t\t} else if (inputs[page.outputPath]) {\n\t\t\t\t\tthrow new DuplicatePermalinkOutputError(\n\t\t\t\t\t\t`The template at \"${page.inputPath}\" attempted to overwrite an existing template at \"${page.outputPath}\".`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tinputs[page.inputPath] = true;\n\n\t\t\t\tif (!outputPaths[page.outputPath]) {\n\t\t\t\t\toutputPaths[page.outputPath] = [template.inputPath];\n\t\t\t\t} else {\n\t\t\t\t\twarnings[page.outputPath] = `Output conflict: multiple input files are writing to \\`${\n\t\t\t\t\t\tpage.outputPath\n\t\t\t\t\t}\\`. Use distinct \\`permalink\\` values to resolve this conflict.\n  1. ${template.inputPath}\n${outputPaths[page.outputPath]\n\t.map(function (inputPath, index) {\n\t\treturn `  ${index + 2}. ${inputPath}\\n`;\n\t})\n\t.join(\"\")}\n`;\n\t\t\t\t\toutputPaths[page.outputPath].push(template.inputPath);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\n\t\tlet warningList = Object.values(warnings);\n\t\tif (warningList.length) {\n\t\t\t// throw one at a time\n\t\t\tthrow new DuplicatePermalinkOutputError(warningList[0]);\n\t\t}\n\t}\n\n\tcheckForMissingFileExtensions() {\n\t\t// disabled in config\n\t\tif (this.userConfig?.errorReporting?.allowMissingExtensions === true) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.#onEachPage((page) => {\n\t\t\tif (\n\t\t\t\tpage.outputPath === false ||\n\t\t\t\tpage.url === false ||\n\t\t\t\tpage.data.eleventyAllowMissingExtension ||\n\t\t\t\tEXTENSIONLESS_URL_ALLOWLIST.some((url) => page.url.endsWith(url))\n\t\t\t) {\n\t\t\t\t// do nothing (also serverless)\n\t\t\t} else {\n\t\t\t\tif (TemplatePath.getExtension(page.outputPath) === \"\") {\n\t\t\t\t\tlet e =\n\t\t\t\t\t\tnew Error(`The template at '${page.inputPath}' attempted to write to '${page.outputPath}'${page.data.permalink ? ` (via \\`permalink\\` value: '${page.data.permalink}')` : \"\"}, which is a target on the file system that does not include a file extension.\n\nYou *probably* want to add a file extension to your permalink so that hosts will know how to correctly serve this file to web browsers. Without a file extension, this file may not be reliably deployed without additional hosting configuration (it won’t have a mime type) and may also cause local development issues if you later attempt to write to a subdirectory of the same name.\n\nLearn more: https://v3.11ty.dev/docs/permalinks/#trailing-slashes\n\nThis is usually but not *always* an error so if you’d like to disable this error message, add \\`eleventyAllowMissingExtension: true\\` somewhere in the data cascade for this template or use \\`eleventyConfig.configureErrorReporting({ allowMissingExtensions: true });\\` to disable this feature globally.`);\n\t\t\t\t\te.skipOriginalStack = true;\n\t\t\t\t\tthrow e;\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t}\n\n\t// TODO move these into TemplateMapTest.js\n\t_testGetAllTags() {\n\t\tlet allTags = {};\n\t\tfor (let map of this.map) {\n\t\t\tlet tags = map.data.tags;\n\t\t\tif (Array.isArray(tags)) {\n\t\t\t\tfor (let tag of tags) {\n\t\t\t\t\tallTags[tag] = true;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn Object.keys(allTags);\n\t}\n\n\tasync _testGetUserConfigCollectionsData() {\n\t\tlet collections = {};\n\t\tlet configCollections = this.userConfig.getCollections();\n\n\t\tfor (let name in configCollections) {\n\t\t\tcollections[name] = configCollections[name](this.collection);\n\n\t\t\tdebug(`Collection: collections.${name} size: ${collections[name].length}`);\n\t\t}\n\n\t\treturn collections;\n\t}\n\n\tasync _testGetTaggedCollectionsData() {\n\t\tlet collections = {};\n\t\tcollections.all = this.collection.getAllSorted();\n\t\tdebug(`Collection: collections.all size: ${collections.all.length}`);\n\n\t\tlet tags = this._testGetAllTags();\n\t\tfor (let tag of tags) {\n\t\t\tcollections[tag] = this.collection.getFilteredByTag(tag);\n\t\t\tdebug(`Collection: collections.${tag} size: ${collections[tag].length}`);\n\t\t}\n\t\treturn collections;\n\t}\n\n\tasync _testGetAllCollectionsData() {\n\t\tlet collections = {};\n\t\tlet taggedCollections = await this._testGetTaggedCollectionsData();\n\t\tObject.assign(collections, taggedCollections);\n\n\t\tlet userConfigCollections = await this._testGetUserConfigCollectionsData();\n\t\tObject.assign(collections, userConfigCollections);\n\n\t\treturn collections;\n\t}\n\n\tasync _testGetCollectionsData() {\n\t\tif (!this.cached) {\n\t\t\tawait this.cache();\n\t\t}\n\n\t\treturn this.collectionsData;\n\t}\n}\n\nexport default TemplateMap;\n"
  },
  {
    "path": "src/TemplatePassthrough.js",
    "content": "import path from \"node:path\";\n\nimport copy from \"@11ty/recursive-copy\";\nimport { TemplatePath } from \"@11ty/eleventy-utils\";\nimport debugUtil from \"debug\";\n\nimport { readableFileSize } from \"./Util/FileSize.js\";\nimport { isDynamicPattern } from \"./Util/GlobMatcher.js\";\nimport EleventyBaseError from \"./Errors/EleventyBaseError.js\";\nimport checkPassthroughCopyBehavior from \"./Util/PassthroughCopyBehaviorCheck.js\";\nimport ProjectDirectories from \"./Util/ProjectDirectories.js\";\n\nconst debug = debugUtil(\"Eleventy:TemplatePassthrough\");\n\nclass TemplatePassthroughError extends EleventyBaseError {}\n\nclass TemplatePassthrough {\n\tisDryRun = false;\n\t#isInputPathGlob;\n\t#benchmarks;\n\t#isAlreadyNormalized = false;\n\t#projectDirCheck = false;\n\n\t// paths already guaranteed from the autocopy plugin\n\tstatic factory(inputPath, outputPath, opts = {}) {\n\t\tlet p = new TemplatePassthrough(\n\t\t\t{\n\t\t\t\tinputPath,\n\t\t\t\toutputPath,\n\t\t\t\tcopyOptions: opts.copyOptions,\n\t\t\t},\n\t\t\topts.templateConfig,\n\t\t);\n\n\t\treturn p;\n\t}\n\n\tconstructor(path, templateConfig) {\n\t\tif (!templateConfig || templateConfig.constructor.name !== \"TemplateConfig\") {\n\t\t\tthrow new Error(\n\t\t\t\t\"Internal error: Missing `templateConfig` or was not an instance of `TemplateConfig`.\",\n\t\t\t);\n\t\t}\n\t\tthis.templateConfig = templateConfig;\n\n\t\tthis.rawPath = path;\n\n\t\t// inputPath is relative to the root of your project and not your Eleventy input directory.\n\t\t// TODO normalize these with forward slashes\n\t\tthis.inputPath = this.normalizeIfDirectory(path.inputPath);\n\t\tthis.#isInputPathGlob = isDynamicPattern(this.inputPath);\n\n\t\tthis.outputPath = path.outputPath;\n\t\tthis.copyOptions = path.copyOptions; // custom options for recursive-copy\n\t}\n\n\tget benchmarks() {\n\t\tif (!this.#benchmarks) {\n\t\t\tthis.#benchmarks = {\n\t\t\t\taggregate: this.config.benchmarkManager.get(\"Aggregate\"),\n\t\t\t};\n\t\t}\n\n\t\treturn this.#benchmarks;\n\t}\n\n\tget config() {\n\t\treturn this.templateConfig.getConfig();\n\t}\n\n\tget directories() {\n\t\treturn this.templateConfig.directories;\n\t}\n\n\t// inputDir is used when stripping from output path in `getOutputPath`\n\tget inputDir() {\n\t\treturn this.templateConfig.directories.input;\n\t}\n\n\tget outputDir() {\n\t\treturn this.templateConfig.directories.output;\n\t}\n\n\t// Skips `getFiles()` normalization\n\tsetIsAlreadyNormalized(isNormalized) {\n\t\tthis.#isAlreadyNormalized = Boolean(isNormalized);\n\t}\n\n\tsetCheckSourceDirectory(check) {\n\t\tthis.#projectDirCheck = Boolean(check);\n\t}\n\n\t/* { inputPath, outputPath } though outputPath is *not* the full path: just the output directory */\n\tgetPath() {\n\t\treturn this.rawPath;\n\t}\n\n\tasync getOutputPath(inputFileFromGlob) {\n\t\tlet { inputDir, outputDir, outputPath, inputPath } = this;\n\n\t\tif (outputPath === true) {\n\t\t\t// no explicit target, implied target\n\t\t\tif (this.isDirectory(inputPath)) {\n\t\t\t\tlet inputRelativePath = TemplatePath.stripLeadingSubPath(\n\t\t\t\t\tinputFileFromGlob || inputPath,\n\t\t\t\t\tinputDir,\n\t\t\t\t);\n\t\t\t\treturn ProjectDirectories.normalizeDirectory(\n\t\t\t\t\tTemplatePath.join(outputDir, inputRelativePath),\n\t\t\t\t);\n\t\t\t}\n\n\t\t\treturn TemplatePath.normalize(\n\t\t\t\tTemplatePath.join(\n\t\t\t\t\toutputDir,\n\t\t\t\t\tTemplatePath.stripLeadingSubPath(inputFileFromGlob || inputPath, inputDir),\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\n\t\tif (inputFileFromGlob) {\n\t\t\treturn this.getOutputPathForGlobFile(inputFileFromGlob);\n\t\t}\n\n\t\t// Has explicit target\n\n\t\t// Bug when copying incremental file overwriting output directory (and making it a file)\n\t\t// e.g. public/test.css -> _site\n\t\t// https://github.com/11ty/eleventy/issues/2278\n\t\tlet fullOutputPath = TemplatePath.normalize(TemplatePath.join(outputDir, outputPath));\n\t\tif (outputPath === \"\" || this.isDirectory(inputPath)) {\n\t\t\tfullOutputPath = ProjectDirectories.normalizeDirectory(fullOutputPath);\n\t\t}\n\n\t\t// TODO room for improvement here:\n\t\tif (\n\t\t\t!this.#isInputPathGlob &&\n\t\t\tthis.isExists(inputPath) &&\n\t\t\t!this.isDirectory(inputPath) &&\n\t\t\tthis.isDirectory(fullOutputPath)\n\t\t) {\n\t\t\tlet filename = path.parse(inputPath).base;\n\t\t\treturn TemplatePath.normalize(TemplatePath.join(fullOutputPath, filename));\n\t\t}\n\n\t\treturn fullOutputPath;\n\t}\n\n\tasync getOutputPathForGlobFile(inputFileFromGlob) {\n\t\treturn TemplatePath.join(\n\t\t\tawait this.getOutputPath(),\n\t\t\tTemplatePath.getLastPathSegment(inputFileFromGlob),\n\t\t);\n\t}\n\n\tsetDryRun(isDryRun) {\n\t\tthis.isDryRun = Boolean(isDryRun);\n\t}\n\n\tsetRunMode(runMode) {\n\t\tthis.runMode = runMode;\n\t}\n\n\tsetFileSystemSearch(fileSystemSearch) {\n\t\tthis.fileSystemSearch = fileSystemSearch;\n\t}\n\n\tasync getFiles(glob) {\n\t\tdebug(\"Searching for: %o\", glob);\n\t\tlet b = this.benchmarks.aggregate.get(\"Searching the file system (passthrough)\");\n\t\tb.before();\n\n\t\tif (!this.fileSystemSearch) {\n\t\t\tthrow new Error(\"Internal error: Missing `fileSystemSearch` property.\");\n\t\t}\n\n\t\t// TODO perf this globs once per addPassthroughCopy entry\n\t\tlet files = TemplatePath.addLeadingDotSlashArray(\n\t\t\tawait this.fileSystemSearch.search(\"passthrough\", glob, {\n\t\t\t\tignore: [\n\t\t\t\t\t// *only* ignores output dir (not node_modules!)\n\t\t\t\t\tthis.outputDir,\n\t\t\t\t],\n\t\t\t}),\n\t\t);\n\t\tb.after();\n\t\treturn files;\n\t}\n\n\tisExists(filePath) {\n\t\treturn this.templateConfig.existsCache.exists(filePath);\n\t}\n\n\tisDirectory(filePath) {\n\t\treturn this.templateConfig.existsCache.isDirectory(filePath);\n\t}\n\n\t// dir is guaranteed to exist by context\n\t// dir may not be a directory\n\tnormalizeIfDirectory(input) {\n\t\tif (typeof input === \"string\") {\n\t\t\tif (input.endsWith(path.sep) || input.endsWith(\"/\")) {\n\t\t\t\treturn input;\n\t\t\t}\n\n\t\t\t// When inputPath is a directory, make sure it has a slash for passthrough copy aliasing\n\t\t\t// https://github.com/11ty/eleventy/issues/2709\n\t\t\tif (this.isDirectory(input)) {\n\t\t\t\treturn `${input}/`;\n\t\t\t}\n\t\t}\n\n\t\treturn input;\n\t}\n\n\t// maps input paths to output paths\n\tasync getFileMap() {\n\t\tif (this.#isAlreadyNormalized) {\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\tinputPath: this.inputPath,\n\t\t\t\t\toutputPath: this.outputPath,\n\t\t\t\t},\n\t\t\t];\n\t\t}\n\n\t\t// TODO VirtualFileSystem candidate\n\t\tif (!isDynamicPattern(this.inputPath) && this.isExists(this.inputPath)) {\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\tinputPath: this.inputPath,\n\t\t\t\t\toutputPath: await this.getOutputPath(),\n\t\t\t\t},\n\t\t\t];\n\t\t}\n\n\t\tlet paths = [];\n\t\t// If not directory or file, attempt to get globs\n\t\tlet files = await this.getFiles(this.inputPath);\n\t\tfor (let filePathFromGlob of files) {\n\t\t\tpaths.push({\n\t\t\t\tinputPath: filePathFromGlob,\n\t\t\t\toutputPath: await this.getOutputPath(filePathFromGlob),\n\t\t\t});\n\t\t}\n\n\t\treturn paths;\n\t}\n\n\t/* Types:\n\t * 1. via glob, individual files found\n\t * 2. directory, triggers an event for each file\n\t * 3. individual file\n\t */\n\tasync copy(src, dest, copyOptions) {\n\t\tif (this.#projectDirCheck && !this.directories.isFileInProjectFolder(src)) {\n\t\t\treturn Promise.reject(\n\t\t\t\tnew TemplatePassthroughError(\n\t\t\t\t\t\"Source file is not in the project directory. Check your passthrough paths.\",\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\n\t\tif (!this.directories.isFileInOutputFolder(dest)) {\n\t\t\treturn Promise.reject(\n\t\t\t\tnew TemplatePassthroughError(\n\t\t\t\t\t\"Destination is not in the site output directory. Check your passthrough paths.\",\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\n\t\tlet fileCopyCount = 0;\n\t\tlet fileSizeCount = 0;\n\t\tlet map = {};\n\t\tlet b = this.benchmarks.aggregate.get(\"Passthrough Copy File\");\n\n\t\t// returns a promise\n\t\treturn copy(src, dest, copyOptions)\n\t\t\t.on(copy.events.COPY_FILE_START, (copyOp) => {\n\t\t\t\t// Access to individual files at `copyOp.src`\n\t\t\t\tmap[copyOp.src] = copyOp.dest;\n\t\t\t\tb.before();\n\t\t\t})\n\t\t\t.on(copy.events.COPY_FILE_COMPLETE, (copyOp) => {\n\t\t\t\tfileCopyCount++;\n\t\t\t\tfileSizeCount += copyOp.stats.size;\n\t\t\t\tif (copyOp.stats.size > 5000000) {\n\t\t\t\t\tdebug(\n\t\t\t\t\t\t`Copied %o (⚠️ large) file from %o`,\n\t\t\t\t\t\treadableFileSize(copyOp.stats.size),\n\t\t\t\t\t\tcopyOp.src,\n\t\t\t\t\t);\n\t\t\t\t} else {\n\t\t\t\t\tdebug(`Copied %o file from %o`, readableFileSize(copyOp.stats.size), copyOp.src);\n\t\t\t\t}\n\t\t\t\tb.after();\n\t\t\t})\n\t\t\t.then(\n\t\t\t\t() => {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tcount: fileCopyCount,\n\t\t\t\t\t\tsize: fileSizeCount,\n\t\t\t\t\t\tmap,\n\t\t\t\t\t};\n\t\t\t\t},\n\t\t\t\t(error) => {\n\t\t\t\t\tif (copyOptions.overwrite === false && error.code === \"EEXIST\") {\n\t\t\t\t\t\t// just ignore if the output already exists and overwrite: false\n\t\t\t\t\t\tdebug(\"Overwrite error ignored: %O\", error);\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tcount: 0,\n\t\t\t\t\t\t\tsize: 0,\n\t\t\t\t\t\t\tmap,\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\n\t\t\t\t\treturn Promise.reject(error);\n\t\t\t\t},\n\t\t\t);\n\t}\n\n\tasync write() {\n\t\tif (this.isDryRun) {\n\t\t\treturn Promise.resolve({\n\t\t\t\tcount: 0,\n\t\t\t\tmap: {},\n\t\t\t});\n\t\t}\n\n\t\tdebug(\"Copying %o\", this.inputPath);\n\t\tlet fileMap = await this.getFileMap();\n\n\t\t// default options for recursive-copy\n\t\t// see https://www.npmjs.com/package/recursive-copy#arguments\n\t\tlet copyOptionsDefault = {\n\t\t\toverwrite: true, // overwrite output. fails when input is directory (mkdir) and output is file\n\t\t\tdot: true, // copy dotfiles\n\t\t\tjunk: false, // copy cache files like Thumbs.db\n\t\t\tresults: false,\n\t\t\texpand: false, // follow symlinks (matches recursive-copy default)\n\t\t\tdebug: false, // (matches recursive-copy default)\n\n\t\t\t// Note: `filter` callback function only passes in a relative path, which is unreliable\n\t\t\t// See https://github.com/timkendrick/recursive-copy/blob/4c9a8b8a4bf573285e9c4a649a30a2b59ccf441c/lib/copy.js#L59\n\t\t\t// e.g. `{ filePaths: [ './img/coolkid.jpg' ], relativePaths: [ '' ] }`\n\t\t};\n\n\t\tlet copyOptions = Object.assign(copyOptionsDefault, this.copyOptions);\n\n\t\tlet promises = fileMap.map((entry) => {\n\t\t\t// For-free passthrough copy\n\t\t\tif (checkPassthroughCopyBehavior(this.config, this.runMode)) {\n\t\t\t\tlet aliasMap = {};\n\t\t\t\taliasMap[entry.inputPath] = entry.outputPath;\n\n\t\t\t\treturn Promise.resolve({\n\t\t\t\t\tcount: 0,\n\t\t\t\t\tmap: aliasMap,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// Copy the files (only in build mode)\n\t\t\treturn this.copy(entry.inputPath, entry.outputPath, copyOptions);\n\t\t});\n\n\t\t// IMPORTANT: this returns an array of promises, does not await for promise to finish\n\t\treturn Promise.all(promises).then(\n\t\t\t(results) => {\n\t\t\t\t// collate the count and input/output map results from the array.\n\t\t\t\tlet count = 0;\n\t\t\t\tlet size = 0;\n\t\t\t\tlet map = {};\n\n\t\t\t\tfor (let result of results) {\n\t\t\t\t\tcount += result.count;\n\t\t\t\t\tsize += result.size;\n\t\t\t\t\tObject.assign(map, result.map);\n\t\t\t\t}\n\n\t\t\t\treturn {\n\t\t\t\t\tcount,\n\t\t\t\t\tsize,\n\t\t\t\t\tmap,\n\t\t\t\t};\n\t\t\t},\n\t\t\t(err) => {\n\t\t\t\tthrow new TemplatePassthroughError(`Error copying passthrough files: ${err.message}`, err);\n\t\t\t},\n\t\t);\n\t}\n}\n\nexport default TemplatePassthrough;\n"
  },
  {
    "path": "src/TemplatePassthroughManager.js",
    "content": "import { TemplatePath } from \"@11ty/eleventy-utils\";\nimport debugUtil from \"debug\";\n\nimport EleventyBaseError from \"./Errors/EleventyBaseError.js\";\nimport TemplatePassthrough from \"./TemplatePassthrough.js\";\nimport checkPassthroughCopyBehavior from \"./Util/PassthroughCopyBehaviorCheck.js\";\nimport { isGlobMatch, isDynamicPattern } from \"./Util/GlobMatcher.js\";\nimport { withResolvers } from \"./Util/PromiseUtil.js\";\n\nconst debug = debugUtil(\"Eleventy:TemplatePassthroughManager\");\n\nclass TemplatePassthroughManagerCopyError extends EleventyBaseError {}\n\nclass TemplatePassthroughManager {\n\t#isDryRun = false;\n\t#afterBuild;\n\t#queue = new Map();\n\t#extensionMap;\n\n\tconstructor(templateConfig) {\n\t\tif (!templateConfig || templateConfig.constructor.name !== \"TemplateConfig\") {\n\t\t\tthrow new Error(\"Internal error: Missing or invalid `templateConfig` argument.\");\n\t\t}\n\n\t\tthis.templateConfig = templateConfig;\n\t\tthis.config = templateConfig.getConfig();\n\n\t\t// eleventy# event listeners are removed on each build\n\t\tthis.config.events.on(\"eleventy#copy\", ({ source, target, options }) => {\n\t\t\tthis.enqueueCopy(source, target, options);\n\t\t});\n\n\t\tthis.config.events.on(\"eleventy#beforerender\", () => {\n\t\t\tthis.#afterBuild = withResolvers();\n\t\t});\n\n\t\tthis.config.events.on(\"eleventy#render\", () => {\n\t\t\tlet { resolve } = this.#afterBuild;\n\t\t\tresolve();\n\t\t});\n\n\t\tthis.reset();\n\t}\n\n\treset() {\n\t\tthis.count = 0;\n\t\tthis.size = 0;\n\t\tthis.conflictMap = {};\n\t\tthis.incrementalFile;\n\n\t\tthis.#queue = new Map();\n\t}\n\n\tset extensionMap(extensionMap) {\n\t\tthis.#extensionMap = extensionMap;\n\t}\n\n\tget extensionMap() {\n\t\tif (!this.#extensionMap) {\n\t\t\tthrow new Error(\"Internal error: missing `extensionMap` in TemplatePassthroughManager.\");\n\t\t}\n\t\treturn this.#extensionMap;\n\t}\n\n\tget inputDir() {\n\t\treturn this.templateConfig.directories.input;\n\t}\n\n\tget outputDir() {\n\t\treturn this.templateConfig.directories.output;\n\t}\n\n\tsetDryRun(isDryRun) {\n\t\tthis.#isDryRun = Boolean(isDryRun);\n\t}\n\n\tsetRunMode(runMode) {\n\t\tthis.runMode = runMode;\n\t}\n\n\tsetIncrementalFile(path) {\n\t\tif (path) {\n\t\t\tthis.incrementalFile = path;\n\t\t}\n\t}\n\n\tresetIncrementalFile() {\n\t\tthis.incrementalFile = undefined;\n\t}\n\n\t_normalizePaths(path, outputPath, copyOptions = {}) {\n\t\treturn {\n\t\t\tinputPath: TemplatePath.addLeadingDotSlash(path),\n\t\t\toutputPath: outputPath ? TemplatePath.stripLeadingDotSlash(outputPath) : true,\n\t\t\tcopyOptions,\n\t\t};\n\t}\n\n\tgetConfigPaths() {\n\t\tlet paths = [];\n\t\tlet pathsRaw = this.config.passthroughCopies || {};\n\t\tdebug(\"`addPassthroughCopy` config API paths: %o\", pathsRaw);\n\t\tfor (let [inputPath, { outputPath, copyOptions }] of Object.entries(pathsRaw)) {\n\t\t\tpaths.push(this._normalizePaths(inputPath, outputPath, copyOptions));\n\t\t}\n\t\tdebug(\"`addPassthroughCopy` config API normalized paths: %o\", paths);\n\t\treturn paths;\n\t}\n\n\tgetConfigPathGlobs() {\n\t\treturn this.getConfigPaths().map((path) => {\n\t\t\treturn TemplatePath.convertToRecursiveGlobSync(path.inputPath);\n\t\t});\n\t}\n\n\tgetNonTemplatePaths(paths) {\n\t\tlet matches = [];\n\t\tfor (let path of paths) {\n\t\t\tif (!this.extensionMap.hasEngine(path)) {\n\t\t\t\tmatches.push(path);\n\t\t\t}\n\t\t}\n\n\t\treturn matches;\n\t}\n\n\tgetCopyCount() {\n\t\treturn this.count;\n\t}\n\n\tgetCopySize() {\n\t\treturn this.size;\n\t}\n\n\tgetMetadata() {\n\t\treturn {\n\t\t\tcopyCount: this.getCopyCount(),\n\t\t\tcopySize: this.getCopySize(),\n\t\t};\n\t}\n\n\tsetFileSystemSearch(fileSystemSearch) {\n\t\tthis.fileSystemSearch = fileSystemSearch;\n\t}\n\n\tgetTemplatePassthroughForPath(path) {\n\t\tlet inst = new TemplatePassthrough(path, this.templateConfig);\n\n\t\tinst.setFileSystemSearch(this.fileSystemSearch);\n\t\tinst.setDryRun(this.#isDryRun);\n\t\tinst.setRunMode(this.runMode);\n\n\t\treturn inst;\n\t}\n\n\tasync copyPassthrough(pass) {\n\t\tif (!(pass instanceof TemplatePassthrough)) {\n\t\t\tthrow new TemplatePassthroughManagerCopyError(\n\t\t\t\t\"copyPassthrough expects an instance of TemplatePassthrough\",\n\t\t\t);\n\t\t}\n\n\t\tlet { inputPath } = pass.getPath();\n\n\t\t// TODO https://github.com/11ty/eleventy/issues/2452\n\t\t// De-dupe both the input and output paired together to avoid the case\n\t\t// where an input/output pair has been added via multiple passthrough methods (glob, file suffix, etc)\n\t\t// Probably start with the `filter` callback in recursive-copy but it only passes relative paths\n\t\t// See the note in TemplatePassthrough.js->write()\n\n\t\t// Also note that `recursive-copy` handles repeated overwrite copy to the same destination just fine.\n\t\t// e.g. `for(let j=0, k=1000; j<k; j++) { copy(\"coolkid.jpg\", \"_site/coolkid.jpg\"); }`\n\n\t\t// Eventually we’ll want to move all of this to use Node’s fs.cp, which is experimental and only on Node 16+\n\n\t\treturn pass.write().then(\n\t\t\t({ size, count, map }) => {\n\t\t\t\tfor (let src in map) {\n\t\t\t\t\tlet dest = map[src];\n\t\t\t\t\tif (this.conflictMap[dest]) {\n\t\t\t\t\t\tif (src !== this.conflictMap[dest]) {\n\t\t\t\t\t\t\tlet paths = [src, this.conflictMap[dest]].sort();\n\t\t\t\t\t\t\tthrow new TemplatePassthroughManagerCopyError(\n\t\t\t\t\t\t\t\t`Multiple passthrough copy files are trying to write to the same output file (${TemplatePath.standardizeFilePath(dest)}). ${paths.map((p) => TemplatePath.standardizeFilePath(p)).join(\" and \")}`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// Multiple entries from the same source\n\t\t\t\t\t\t\tdebug(\n\t\t\t\t\t\t\t\t\"A passthrough copy entry (%o) caused the same file (%o) to be copied more than once to the output (%o). This is atomically safe but a waste of build resources.\",\n\t\t\t\t\t\t\t\tinputPath,\n\t\t\t\t\t\t\t\tsrc,\n\t\t\t\t\t\t\t\tdest,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.conflictMap[dest] = src;\n\t\t\t\t}\n\n\t\t\t\tif (pass.isDryRun) {\n\t\t\t\t\t// We don’t count the skipped files as we need to iterate over them\n\t\t\t\t\tdebug(\n\t\t\t\t\t\t\"Skipped %o (either from --dryrun or --incremental or for-free passthrough copy)\",\n\t\t\t\t\t\tinputPath,\n\t\t\t\t\t);\n\t\t\t\t} else {\n\t\t\t\t\tif (count) {\n\t\t\t\t\t\tthis.count += count;\n\t\t\t\t\t\tthis.size += size;\n\t\t\t\t\t\tdebug(\"Copied %o (%d files, %d size)\", inputPath, count || 0, size || 0);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdebug(\"Skipped copying %o (emulated passthrough copy)\", inputPath);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn {\n\t\t\t\t\tcount,\n\t\t\t\t\tmap,\n\t\t\t\t};\n\t\t\t},\n\t\t\tfunction (e) {\n\t\t\t\treturn Promise.reject(\n\t\t\t\t\tnew TemplatePassthroughManagerCopyError(`Having trouble copying '${inputPath}'`, e),\n\t\t\t\t);\n\t\t\t},\n\t\t);\n\t}\n\n\tisPassthroughCopyFile(paths, changedFile) {\n\t\tif (!changedFile) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// passthrough copy by non-matching engine extension (via templateFormats)\n\t\tfor (let path of paths) {\n\t\t\tif (path === changedFile && !this.extensionMap.hasEngine(path)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\tfor (let path of this.getConfigPaths()) {\n\t\t\tif (TemplatePath.startsWithSubPath(changedFile, path.inputPath)) {\n\t\t\t\treturn path;\n\t\t\t}\n\t\t\tif (\n\t\t\t\tchangedFile &&\n\t\t\t\tisDynamicPattern(path.inputPath) &&\n\t\t\t\tisGlobMatch(changedFile, [path.inputPath])\n\t\t\t) {\n\t\t\t\treturn path;\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n\n\tgetAllNormalizedPaths(paths = []) {\n\t\tif (this.incrementalFile) {\n\t\t\tlet isPassthrough = this.isPassthroughCopyFile(paths, this.incrementalFile);\n\n\t\t\tif (isPassthrough) {\n\t\t\t\tif (isPassthrough.outputPath) {\n\t\t\t\t\treturn [isPassthrough];\n\t\t\t\t}\n\n\t\t\t\treturn [this._normalizePaths(this.incrementalFile)];\n\t\t\t}\n\n\t\t\t// Fixes https://github.com/11ty/eleventy/issues/2491\n\t\t\tif (!checkPassthroughCopyBehavior(this.config, this.runMode)) {\n\t\t\t\treturn [];\n\t\t\t}\n\t\t}\n\n\t\tlet normalizedPaths = this.getConfigPaths();\n\t\tif (debug.enabled) {\n\t\t\tfor (let path of normalizedPaths) {\n\t\t\t\tdebug(\"TemplatePassthrough copying from config: %o\", path);\n\t\t\t}\n\t\t}\n\n\t\tif (paths?.length) {\n\t\t\tlet passthroughPaths = this.getNonTemplatePaths(paths);\n\t\t\tfor (let path of passthroughPaths) {\n\t\t\t\tlet normalizedPath = this._normalizePaths(path);\n\n\t\t\t\tdebug(\n\t\t\t\t\t`TemplatePassthrough copying from non-matching file extension: ${normalizedPath.inputPath}`,\n\t\t\t\t);\n\n\t\t\t\tnormalizedPaths.push(normalizedPath);\n\t\t\t}\n\t\t}\n\n\t\treturn normalizedPaths;\n\t}\n\n\t// keys: output\n\t// values: input\n\tgetAliasesFromPassthroughResults(result) {\n\t\tlet entries = {};\n\t\tfor (let entry of result) {\n\t\t\tfor (let src in entry.map) {\n\t\t\t\tlet dest = TemplatePath.stripLeadingSubPath(entry.map[src], this.outputDir);\n\t\t\t\tentries[\"/\" + encodeURI(dest)] = src;\n\t\t\t}\n\t\t}\n\t\treturn entries;\n\t}\n\n\tasync #waitForTemplatesRendered() {\n\t\tif (!this.#afterBuild) {\n\t\t\treturn Promise.resolve(); // immediately resolve\n\t\t}\n\n\t\tlet { promise } = this.#afterBuild;\n\t\treturn promise;\n\t}\n\n\tenqueueCopy(source, target, copyOptions) {\n\t\tlet key = `${source}=>${target}`;\n\n\t\t// light de-dupe the same source/target combo (might be in the same file, might be viaTransforms)\n\t\tif (this.#queue.has(key)) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet passthrough = TemplatePassthrough.factory(source, target, {\n\t\t\ttemplateConfig: this.templateConfig,\n\t\t\tcopyOptions,\n\t\t});\n\n\t\tpassthrough.setCheckSourceDirectory(true);\n\t\tpassthrough.setIsAlreadyNormalized(true);\n\t\tpassthrough.setRunMode(this.runMode);\n\t\tpassthrough.setDryRun(this.#isDryRun);\n\n\t\tthis.#queue.set(key, this.copyPassthrough(passthrough));\n\t}\n\n\tasync copyAll(templateExtensionPaths) {\n\t\tdebug(\"TemplatePassthrough copy started.\");\n\t\tlet normalizedPaths = this.getAllNormalizedPaths(templateExtensionPaths);\n\n\t\tlet passthroughs = normalizedPaths.map((path) => this.getTemplatePassthroughForPath(path));\n\n\t\tlet promises = passthroughs.map((pass) => this.copyPassthrough(pass));\n\n\t\tawait this.#waitForTemplatesRendered();\n\n\t\tfor (let [key, afterBuildCopyPromises] of this.#queue) {\n\t\t\tpromises.push(afterBuildCopyPromises);\n\t\t}\n\n\t\treturn Promise.all(promises).then(async (results) => {\n\t\t\tlet aliases = this.getAliasesFromPassthroughResults(results);\n\t\t\tawait this.config.events.emit(\"eleventy.passthrough\", {\n\t\t\t\tmap: aliases,\n\t\t\t});\n\n\t\t\tdebug(`TemplatePassthrough copy finished. Current count: ${this.count} (size: ${this.size})`);\n\t\t\treturn results;\n\t\t});\n\t}\n}\n\nexport default TemplatePassthroughManager;\n"
  },
  {
    "path": "src/TemplatePermalink.js",
    "content": "import path from \"node:path\";\nimport { TemplatePath, isPlainObject } from \"@11ty/eleventy-utils\";\n\nclass TemplatePermalink {\n\t#dynamicPermalinkEnabled;\n\n\t// `link` with template syntax should have already been rendered in Template.js\n\tconstructor(link, extraSubdir, isDynamicPermalinkEnabled = true) {\n\t\tlet isLinkAnObject = isPlainObject(link);\n\n\t\tthis._isRendered = true;\n\t\tthis._writeToFileSystem = true;\n\t\tthis.#dynamicPermalinkEnabled = isDynamicPermalinkEnabled;\n\n\t\tlet buildLink;\n\n\t\tif (isLinkAnObject) {\n\t\t\tif (\"build\" in link) {\n\t\t\t\tbuildLink = link.build;\n\t\t\t}\n\n\t\t\t// find the first string key\n\t\t\tfor (let key in link) {\n\t\t\t\tif (typeof key !== \"string\") {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t} else {\n\t\t\tbuildLink = link;\n\t\t}\n\n\t\t// permalink: false and permalink: build: false\n\t\tif (typeof buildLink === \"boolean\") {\n\t\t\tif (buildLink === false) {\n\t\t\t\tthis._writeToFileSystem = false;\n\t\t\t} else {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`\\`permalink: ${\n\t\t\t\t\t\tisLinkAnObject ? \"build: \" : \"\"\n\t\t\t\t\t}true\\` is not a supported feature in Eleventy. Did you mean \\`permalink: ${\n\t\t\t\t\t\tisLinkAnObject ? \"build: \" : \"\"\n\t\t\t\t\t}false\\`?`,\n\t\t\t\t);\n\t\t\t}\n\t\t} else if (buildLink) {\n\t\t\tif (typeof buildLink !== \"string\") {\n\t\t\t\tlet stringToString = \"toString\" in buildLink ? `:\\n\\n${buildLink.toString()}` : \"\";\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Expected permalink value to be a string. Received \\`${typeof buildLink}\\` (dynamicPermalink: ${this.#dynamicPermalinkEnabled})${stringToString}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tthis.buildLink = buildLink;\n\t\t}\n\n\t\tif (isLinkAnObject) {\n\t\t\t// default if permalink is an Object but does not have a `build` prop\n\t\t\tif (!(\"build\" in link)) {\n\t\t\t\tthis._writeToFileSystem = false;\n\t\t\t\tthis._isRendered = false;\n\t\t\t}\n\t\t}\n\n\t\tthis.extraPaginationSubdir = extraSubdir || \"\";\n\t}\n\n\tsetUrlTransforms(transforms) {\n\t\tthis._urlTransforms = transforms;\n\t}\n\n\tget urlTransforms() {\n\t\treturn this._urlTransforms || [];\n\t}\n\n\t_addDefaultLinkFilename(link) {\n\t\treturn link + (link.slice(-1) === \"/\" ? \"index.html\" : \"\");\n\t}\n\n\ttoOutputPath() {\n\t\tif (!this.buildLink) {\n\t\t\t// empty or false\n\t\t\treturn false;\n\t\t}\n\t\tlet cleanLink = this._addDefaultLinkFilename(this.buildLink);\n\t\tlet parsed = path.parse(cleanLink);\n\n\t\treturn TemplatePath.join(parsed.dir, this.extraPaginationSubdir, parsed.base);\n\t}\n\n\t// Used in url transforms feature\n\tstatic getUrlStem(original) {\n\t\tlet subject = original;\n\t\tif (original.endsWith(\".html\")) {\n\t\t\tsubject = original.slice(0, -1 * \".html\".length);\n\t\t}\n\t\treturn TemplatePermalink.normalizePathToUrl(subject);\n\t}\n\n\tstatic normalizePathToUrl(original) {\n\t\tlet compare = original || \"\";\n\n\t\tlet needleHtml = \"/index.html\";\n\t\tlet needleBareTrailingSlash = \"/index/\";\n\t\tlet needleBare = \"/index\";\n\t\tif (compare.endsWith(needleHtml)) {\n\t\t\treturn compare.slice(0, compare.length - needleHtml.length) + \"/\";\n\t\t} else if (compare.endsWith(needleBareTrailingSlash)) {\n\t\t\treturn compare.slice(0, compare.length - needleBareTrailingSlash.length) + \"/\";\n\t\t} else if (compare.endsWith(needleBare)) {\n\t\t\treturn compare.slice(0, compare.length - needleBare.length) + \"/\";\n\t\t}\n\n\t\treturn original;\n\t}\n\n\t// This method is used to generate the `page.url` variable.\n\n\t// remove all index.html’s from links\n\t// index.html becomes /\n\t// test/index.html becomes test/\n\ttoHref() {\n\t\tif (!this.buildLink) {\n\t\t\t// empty or false\n\t\t\treturn false;\n\t\t}\n\n\t\tlet transformedLink = this.toOutputPath();\n\t\tlet original = (transformedLink.charAt(0) !== \"/\" ? \"/\" : \"\") + transformedLink;\n\n\t\tlet normalized = TemplatePermalink.normalizePathToUrl(original) || \"\";\n\t\tfor (let transform of this.urlTransforms) {\n\t\t\toriginal =\n\t\t\t\ttransform({\n\t\t\t\t\turl: normalized,\n\t\t\t\t\turlStem: TemplatePermalink.getUrlStem(original),\n\t\t\t\t}) ?? original;\n\t\t}\n\n\t\treturn TemplatePermalink.normalizePathToUrl(original);\n\t}\n\n\ttoPath(outputDir) {\n\t\tif (!this.buildLink) {\n\t\t\treturn false;\n\t\t}\n\n\t\tlet uri = this.toOutputPath();\n\n\t\tif (uri === false) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn TemplatePath.addLeadingDotSlash(TemplatePath.normalize(outputDir + \"/\" + uri));\n\t}\n\n\ttoPathFromRoot() {\n\t\tif (!this.buildLink) {\n\t\t\treturn false;\n\t\t}\n\n\t\tlet uri = this.toOutputPath();\n\n\t\tif (uri === false) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn TemplatePath.addLeadingDotSlash(TemplatePath.normalize(uri));\n\t}\n\n\tstatic _hasDuplicateFolder(dir, base) {\n\t\tlet folders = dir.split(\"/\");\n\t\tif (!folders[folders.length - 1]) {\n\t\t\tfolders.pop();\n\t\t}\n\t\treturn folders[folders.length - 1] === base;\n\t}\n\n\tstatic generate(\n\t\tdir,\n\t\tfilenameNoExt,\n\t\textraSubdir,\n\t\tfileExtension = \"html\",\n\t\tisDynamicPermalinkEnabled,\n\t) {\n\t\tlet path;\n\t\tif (fileExtension === \"html\") {\n\t\t\tlet hasDupeFolder = TemplatePermalink._hasDuplicateFolder(dir, filenameNoExt);\n\n\t\t\tpath =\n\t\t\t\t(dir ? dir + \"/\" : \"\") +\n\t\t\t\t(filenameNoExt !== \"index\" && !hasDupeFolder ? filenameNoExt + \"/\" : \"\") +\n\t\t\t\t\"index.html\";\n\t\t} else {\n\t\t\tpath = (dir ? dir + \"/\" : \"\") + filenameNoExt + \".\" + fileExtension;\n\t\t}\n\n\t\treturn new TemplatePermalink(path, extraSubdir, isDynamicPermalinkEnabled);\n\t}\n}\n\nexport default TemplatePermalink;\n"
  },
  {
    "path": "src/TemplatePreprocessors.js",
    "content": "export class TemplatePreprocessors {\n\tconstructor(preprocessors) {\n\t\tthis.preprocessors = preprocessors || [];\n\t}\n\n\tasync runAll(template, data) {\n\t\tlet { inputPath } = template;\n\t\tlet content = await template.getPreRender();\n\n\t\tlet skippedVia = false;\n\t\tfor (let [name, preprocessor] of Object.entries(this.preprocessors)) {\n\t\t\tlet { filter, callback } = preprocessor;\n\n\t\t\tlet filters;\n\t\t\tif (Array.isArray(filter)) {\n\t\t\t\tfilters = filter;\n\t\t\t} else if (typeof filter === \"string\") {\n\t\t\t\tfilters = filter.split(\",\");\n\t\t\t} else {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Expected file extensions passed to \"${name}\" content preprocessor to be a string or array. Received: ${filter}`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tfilters = filters.map((extension) => {\n\t\t\t\tif (extension.startsWith(\".\") || extension === \"*\") {\n\t\t\t\t\treturn extension;\n\t\t\t\t}\n\n\t\t\t\treturn `.${extension}`;\n\t\t\t});\n\n\t\t\tif (!filters.some((extension) => extension === \"*\" || inputPath.endsWith(extension))) {\n\t\t\t\t// skip\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tlet ret = await callback.call(\n\t\t\t\t\t{\n\t\t\t\t\t\tinputPath,\n\t\t\t\t\t},\n\t\t\t\t\tdata,\n\t\t\t\t\tcontent,\n\t\t\t\t);\n\n\t\t\t\t// Returning explicit false is the same as ignoring the template\n\t\t\t\tif (ret === false) {\n\t\t\t\t\tskippedVia = name;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Different from transforms: returning falsy (not false) here does nothing (skips the preprocessor)\n\t\t\t\tif (ret) {\n\t\t\t\t\tcontent = ret;\n\t\t\t\t}\n\t\t\t} catch (e) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Preprocessor \\`${name}\\` encountered an error when transforming ${inputPath}.`,\n\t\t\t\t\t{ cause: e },\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tskippedVia,\n\t\t\tcontent,\n\t\t};\n\t}\n}\n"
  },
  {
    "path": "src/TemplateRender.js",
    "content": "import debugUtil from \"debug\";\nimport EleventyBaseError from \"./Errors/EleventyBaseError.js\";\nimport TemplateEngineManager from \"./Engines/TemplateEngineManager.js\";\n\nconst debugConfiguration = debugUtil(\"Eleventy:UserConfig\");\n\nclass TemplateRenderUnknownEngineError extends EleventyBaseError {}\n\n// works with full path names or short engine name\nexport default class TemplateRender {\n\t#extensionMap;\n\t#config;\n\n\tconstructor(tmplPath, config) {\n\t\tif (!tmplPath) {\n\t\t\tthrow new Error(`TemplateRender requires a tmplPath argument, instead of ${tmplPath}`);\n\t\t}\n\t\tthis.#setConfig(config);\n\n\t\tthis.engineNameOrPath = tmplPath;\n\t\tthis.parseMarkdownWith = this.config.markdownTemplateEngine;\n\t\tif (this.parseMarkdownWith === \"md\") {\n\t\t\tthis.parseMarkdownWith = false;\n\t\t\tdebugConfiguration(\n\t\t\t\t\"Misconfiguration warning: the preprocessing template syntax for Markdown files cannot be Markdown, we’re assuming you meant `false` to skip preprocessing altogether (via the `markdownTemplateEngine` configuration property or the `setMarkdownTemplateEngine` configuration method). Read more: https://www.11ty.dev/docs/config/#default-template-engine-for-markdown-files\",\n\t\t\t);\n\t\t}\n\t\tthis.parseHtmlWith = this.config.htmlTemplateEngine;\n\t}\n\n\t#setConfig(config) {\n\t\tif (config?.constructor?.name !== \"TemplateConfig\") {\n\t\t\tthrow new Error(\"TemplateRender must receive a TemplateConfig instance.\");\n\t\t}\n\n\t\tthis.eleventyConfig = config;\n\t\tthis.config = config.getConfig();\n\t}\n\n\tget dirs() {\n\t\treturn this.eleventyConfig.directories;\n\t}\n\n\tget inputDir() {\n\t\treturn this.dirs.input;\n\t}\n\n\tget includesDir() {\n\t\treturn this.dirs.includes;\n\t}\n\n\t/* Backwards compat */\n\tgetIncludesDir() {\n\t\treturn this.includesDir;\n\t}\n\n\tget config() {\n\t\treturn this.#config;\n\t}\n\n\tset config(config) {\n\t\tthis.#config = config;\n\t}\n\n\tset extensionMap(extensionMap) {\n\t\tthis.#extensionMap = extensionMap;\n\t}\n\n\tget extensionMap() {\n\t\tif (!this.#extensionMap) {\n\t\t\tthrow new Error(\"Internal error: missing `extensionMap` in TemplateRender.\");\n\t\t}\n\t\treturn this.#extensionMap;\n\t}\n\n\tasync getEngineByName(name) {\n\t\t// WARNING: eleventyConfig assignment removed here\n\t\treturn this.extensionMap.engineManager.getEngine(name, this.extensionMap);\n\t}\n\n\t// Runs once per template\n\tasync init(engineNameOrPath) {\n\t\tlet name = engineNameOrPath || this.engineNameOrPath;\n\t\tthis.extensionMap.setTemplateConfig(this.eleventyConfig);\n\n\t\tlet extensionEntry = this.extensionMap.getExtensionEntry(name);\n\t\tlet engineName = extensionEntry?.aliasKey || extensionEntry?.key;\n\t\tif (TemplateEngineManager.isSimpleAlias(extensionEntry)) {\n\t\t\tengineName = extensionEntry?.key;\n\t\t}\n\t\tthis._engineName = engineName;\n\n\t\tif (!extensionEntry || !this._engineName) {\n\t\t\tthrow new TemplateRenderUnknownEngineError(\n\t\t\t\t`Unknown engine for ${name} (supported extensions: ${this.extensionMap.getReadableFileExtensions()})`,\n\t\t\t);\n\t\t}\n\n\t\tthis._engine = await this.getEngineByName(this._engineName);\n\n\t\tif (this.useMarkdown === undefined) {\n\t\t\tthis.setUseMarkdown(this._engineName === \"md\");\n\t\t}\n\t}\n\n\tget engineName() {\n\t\tif (!this._engineName) {\n\t\t\tthrow new Error(\"TemplateRender needs a call to the init() method.\");\n\t\t}\n\t\treturn this._engineName;\n\t}\n\n\tget engine() {\n\t\tif (!this._engine) {\n\t\t\tthrow new Error(\"TemplateRender needs a call to the init() method.\");\n\t\t}\n\t\treturn this._engine;\n\t}\n\n\tstatic parseEngineOverrides(engineName) {\n\t\tif (typeof (engineName || \"\") !== \"string\") {\n\t\t\tthrow new Error(\"Expected String passed to parseEngineOverrides. Received: \" + engineName);\n\t\t}\n\n\t\tlet overlappingEngineWarningCount = 0;\n\t\tlet engines = [];\n\t\tlet uniqueLookup = {};\n\t\tlet usingMarkdown = false;\n\t\t(engineName || \"\")\n\t\t\t.split(\",\")\n\t\t\t.map((name) => {\n\t\t\t\treturn name.toLowerCase().trim();\n\t\t\t})\n\t\t\t.forEach((name) => {\n\t\t\t\t// html is assumed (treated as plaintext by the system)\n\t\t\t\tif (!name || name === \"html\") {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (name === \"md\") {\n\t\t\t\t\tusingMarkdown = true;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (!uniqueLookup[name]) {\n\t\t\t\t\tengines.push(name);\n\t\t\t\t\tuniqueLookup[name] = true;\n\n\t\t\t\t\t// we already short circuit md and html types above\n\t\t\t\t\toverlappingEngineWarningCount++;\n\t\t\t\t}\n\t\t\t});\n\n\t\tif (overlappingEngineWarningCount > 1) {\n\t\t\tthrow new Error(\n\t\t\t\t`Don’t mix multiple templating engines in your front matter overrides (exceptions for HTML and Markdown). You used: ${engineName}`,\n\t\t\t);\n\t\t}\n\n\t\t// markdown should always be first\n\t\tif (usingMarkdown) {\n\t\t\tengines.unshift(\"md\");\n\t\t}\n\n\t\treturn engines;\n\t}\n\n\t// used for error logging and console output.\n\tgetReadableEnginesList() {\n\t\treturn this.getReadableEnginesListDifferingFromFileExtension() || this.engineName;\n\t}\n\n\tgetReadableEnginesListDifferingFromFileExtension() {\n\t\tlet keyFromFilename = this.extensionMap.getKey(this.engineNameOrPath);\n\t\tif (this.engine?.constructor?.name === \"CustomEngine\") {\n\t\t\tif (\n\t\t\t\tthis.engine.entry &&\n\t\t\t\tthis.engine.entry.name &&\n\t\t\t\tkeyFromFilename !== this.engine.entry.name\n\t\t\t) {\n\t\t\t\treturn this.engine.entry.name;\n\t\t\t} else {\n\t\t\t\t// We don’t have a name for it so we return nothing so we don’t misreport (per #2386)\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\tif (this.engineName === \"md\" && this.useMarkdown && this.parseMarkdownWith) {\n\t\t\treturn this.parseMarkdownWith;\n\t\t}\n\t\tif (this.engineName === \"html\" && this.parseHtmlWith) {\n\t\t\treturn this.parseHtmlWith;\n\t\t}\n\n\t\t// templateEngineOverride in play and template language differs from file extension\n\t\tif (keyFromFilename !== this.engineName) {\n\t\t\treturn this.engineName;\n\t\t}\n\t}\n\n\t// TODO templateEngineOverride\n\tgetPreprocessorEngineName() {\n\t\tif (this.engineName === \"md\" && this.parseMarkdownWith) {\n\t\t\treturn this.parseMarkdownWith;\n\t\t}\n\t\tif (this.engineName === \"html\" && this.parseHtmlWith) {\n\t\t\treturn this.parseHtmlWith;\n\t\t}\n\t\t// TODO do we need this?\n\t\treturn this.extensionMap.getKey(this.engineNameOrPath);\n\t}\n\n\t// We pass in templateEngineOverride here because it isn’t yet applied to templateRender\n\tgetEnginesList(engineOverride) {\n\t\tif (engineOverride) {\n\t\t\tlet engines = TemplateRender.parseEngineOverrides(engineOverride).reverse();\n\t\t\treturn engines.join(\",\");\n\t\t}\n\n\t\tif (this.engineName === \"md\" && this.useMarkdown && this.parseMarkdownWith) {\n\t\t\treturn `${this.parseMarkdownWith},md`;\n\t\t}\n\t\tif (this.engineName === \"html\" && this.parseHtmlWith) {\n\t\t\treturn this.parseHtmlWith;\n\t\t}\n\n\t\t// templateEngineOverride in play\n\t\treturn this.extensionMap.getKey(this.engineNameOrPath);\n\t}\n\n\tasync setEngineOverride(engineName, bypassMarkdown) {\n\t\tlet engines = TemplateRender.parseEngineOverrides(engineName);\n\n\t\t// when overriding, Template Engines with HTML will instead use the Template Engine as primary and output HTML\n\t\t// So any HTML engine usage here will never use a preprocessor templating engine.\n\t\tthis.setHtmlEngine(false);\n\n\t\tif (!engines.length) {\n\t\t\tawait this.init(\"html\");\n\t\t\treturn;\n\t\t}\n\n\t\tawait this.init(engines[0]);\n\n\t\tlet usingMarkdown = engines[0] === \"md\" && !bypassMarkdown;\n\n\t\tthis.setUseMarkdown(usingMarkdown);\n\n\t\tif (usingMarkdown) {\n\t\t\t// false means only parse markdown and not with a preprocessor template engine\n\t\t\tthis.setMarkdownEngine(engines.length > 1 ? engines[1] : false);\n\t\t}\n\t}\n\n\tgetEngineName() {\n\t\treturn this.engineName;\n\t}\n\n\tisEngine(engine) {\n\t\treturn this.engineName === engine;\n\t}\n\n\tsetUseMarkdown(useMarkdown) {\n\t\tthis.useMarkdown = !!useMarkdown;\n\t}\n\n\t// this is only called for templateEngineOverride\n\tsetMarkdownEngine(markdownEngine) {\n\t\tthis.parseMarkdownWith = markdownEngine;\n\t}\n\n\t// this is only called for templateEngineOverride\n\tsetHtmlEngine(htmlEngineName) {\n\t\tthis.parseHtmlWith = htmlEngineName;\n\t}\n\n\tasync _testRender(str, data) {\n\t\treturn this.engine._testRender(str, data);\n\t}\n\n\tasync getCompiledTemplate(str) {\n\t\t// TODO refactor better, move into TemplateEngine logic\n\t\tif (this.engineName === \"md\") {\n\t\t\treturn this.engine.compile(\n\t\t\t\tstr,\n\t\t\t\tthis.engineNameOrPath,\n\t\t\t\tthis.parseMarkdownWith,\n\t\t\t\t!this.useMarkdown,\n\t\t\t);\n\t\t} else if (this.engineName === \"html\") {\n\t\t\treturn this.engine.compile(str, this.engineNameOrPath, this.parseHtmlWith);\n\t\t} else {\n\t\t\treturn this.engine.compile(str, this.engineNameOrPath);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/TemplateWriter.js",
    "content": "import { TemplatePath, isPlainObject } from \"@11ty/eleventy-utils\";\nimport debugUtil from \"debug\";\n\nimport Template from \"./Template.js\";\nimport TemplateMap from \"./TemplateMap.js\";\nimport EleventyBaseError from \"./Errors/EleventyBaseError.js\";\nimport { EleventyErrorHandler } from \"./Errors/EleventyErrorHandler.js\";\nimport EleventyErrorUtil from \"./Errors/EleventyErrorUtil.js\";\nimport ConsoleLogger from \"./Util/ConsoleLogger.js\";\n\nconst debug = debugUtil(\"Eleventy:TemplateWriter\");\n\nclass TemplateWriterMissingConfigArgError extends EleventyBaseError {}\nclass EleventyPassthroughCopyError extends EleventyBaseError {}\nclass EleventyTemplateError extends EleventyBaseError {}\n\nclass TemplateWriter {\n\t#eleventyFiles;\n\t#passthroughManager;\n\t#errorHandler;\n\t#extensionMap;\n\n\tconstructor(\n\t\ttemplateFormats, // TODO remove this in favor of this.#eleventyFiles\n\t\ttemplateData,\n\t\ttemplateConfig,\n\t) {\n\t\tif (!templateConfig) {\n\t\t\tthrow new TemplateWriterMissingConfigArgError(\"Missing config argument.\");\n\t\t}\n\t\tthis.templateConfig = templateConfig;\n\t\tthis.config = templateConfig.getConfig();\n\t\tthis.userConfig = templateConfig.userConfig;\n\n\t\tthis.templateFormats = templateFormats;\n\n\t\tthis.templateData = templateData;\n\t\tthis.isVerbose = true;\n\t\tthis.isDryRun = false;\n\t\tthis.writeCount = 0;\n\t\tthis.renderCount = 0;\n\t\tthis.skippedCount = 0;\n\t\tthis.isRunInitialBuild = true;\n\n\t\tthis._templatePathCache = new Map();\n\t}\n\n\tget dirs() {\n\t\treturn this.templateConfig.directories;\n\t}\n\n\tget inputDir() {\n\t\treturn this.dirs.input;\n\t}\n\n\tget outputDir() {\n\t\treturn this.dirs.output;\n\t}\n\n\tget templateFormats() {\n\t\treturn this._templateFormats;\n\t}\n\n\tset templateFormats(value) {\n\t\tthis._templateFormats = value;\n\t}\n\n\t/* Getter for error handler */\n\tget errorHandler() {\n\t\tif (!this.#errorHandler) {\n\t\t\tthis.#errorHandler = new EleventyErrorHandler();\n\t\t\tthis.#errorHandler.isVerbose = this.verboseMode;\n\t\t\tthis.#errorHandler.logger = this.logger;\n\t\t}\n\n\t\treturn this.#errorHandler;\n\t}\n\n\t/* Getter for Logger */\n\tget logger() {\n\t\tif (!this._logger) {\n\t\t\tthis._logger = new ConsoleLogger();\n\t\t\tthis._logger.isVerbose = this.verboseMode;\n\t\t}\n\n\t\treturn this._logger;\n\t}\n\n\t/* Setter for Logger */\n\tset logger(logger) {\n\t\tthis._logger = logger;\n\t}\n\n\t/* For testing */\n\toverrideConfig(config) {\n\t\tthis.config = config;\n\t}\n\n\trestart() {\n\t\tthis.writeCount = 0;\n\t\tthis.renderCount = 0;\n\t\tthis.skippedCount = 0;\n\t}\n\n\tset extensionMap(extensionMap) {\n\t\tthis.#extensionMap = extensionMap;\n\t}\n\n\tget extensionMap() {\n\t\tif (!this.#extensionMap) {\n\t\t\tthrow new Error(\"Internal error: missing `extensionMap` in TemplateWriter.\");\n\t\t}\n\t\treturn this.#extensionMap;\n\t}\n\n\tsetPassthroughManager(mgr) {\n\t\tthis.#passthroughManager = mgr;\n\t}\n\n\tsetEleventyFiles(eleventyFiles) {\n\t\tthis.#eleventyFiles = eleventyFiles;\n\t}\n\n\t// Tests\n\tgetPassthroughGlobs() {\n\t\treturn this.#eleventyFiles?.passthroughGlobs;\n\t}\n\n\tgetPathsWithVirtualTemplates(paths) {\n\t\t// Support for virtual templates added in 3.0\n\t\tif (this.config.virtualTemplates && isPlainObject(this.config.virtualTemplates)) {\n\t\t\tlet virtualTemplates = Object.keys(this.config.virtualTemplates)\n\t\t\t\t.filter((path) => {\n\t\t\t\t\t// Filter out includes/layouts\n\t\t\t\t\treturn this.dirs.isTemplateFile(path);\n\t\t\t\t})\n\t\t\t\t.map((path) => {\n\t\t\t\t\tlet fullVirtualPath = this.dirs.getInputPath(path);\n\t\t\t\t\tif (!this.extensionMap.getKey(fullVirtualPath)) {\n\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t`The virtual template at ${fullVirtualPath} is using a template format that’s not valid for your project. Your project is using: \"${this.formats}\". Read more about formats: https://v3.11ty.dev/docs/config/#template-formats`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\treturn fullVirtualPath;\n\t\t\t\t});\n\n\t\t\tpaths = paths.concat(virtualTemplates);\n\n\t\t\t// Virtual templates can not live at the same place as files on the file system!\n\t\t\tif (paths.length !== new Set(paths).size) {\n\t\t\t\tlet conflicts = {};\n\t\t\t\tfor (let path of paths) {\n\t\t\t\t\tif (conflicts[path]) {\n\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t`A virtual template had the same path as a file on the file system: \"${path}\"`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tconflicts[path] = true;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn paths;\n\t}\n\n\tasync _getAllPaths() {\n\t\tif (!this.#eleventyFiles) {\n\t\t\treturn this.getPathsWithVirtualTemplates([]);\n\t\t}\n\n\t\t// this is now cached upstream by FileSystemSearch\n\t\tlet paths = await this.#eleventyFiles.getFiles();\n\t\tpaths = this.getPathsWithVirtualTemplates(paths);\n\t\treturn paths;\n\t}\n\n\t_createTemplate(path, to = \"fs\") {\n\t\tlet tmpl = this._templatePathCache.get(path);\n\t\tlet wasCached = false;\n\n\t\tif (tmpl) {\n\t\t\twasCached = true;\n\t\t\t// Update config for https://github.com/11ty/eleventy/issues/3468\n\t\t\t// TODO reset other constructor things here like inputDir/outputDir\n\t\t\ttmpl.resetCachedTemplate({\n\t\t\t\ttemplateData: this.templateData,\n\t\t\t\textensionMap: this.extensionMap,\n\t\t\t\televentyConfig: this.templateConfig,\n\t\t\t});\n\t\t} else {\n\t\t\ttmpl = new Template(path, this.templateData, this.extensionMap, this.templateConfig);\n\t\t\ttmpl.setOutputFormat(to);\n\t\t\ttmpl.logger = this.logger;\n\t\t\tthis._templatePathCache.set(path, tmpl);\n\t\t}\n\n\t\ttmpl.setTransforms(this.config.transforms);\n\t\ttmpl.setLinters(this.config.linters);\n\t\ttmpl.setDryRun(this.isDryRun);\n\t\ttmpl.setIsVerbose(this.isVerbose);\n\t\ttmpl.reset();\n\n\t\treturn {\n\t\t\ttemplate: tmpl,\n\t\t\twasCached,\n\t\t};\n\t}\n\n\t// incrementalFileShape is `template` or `copy` (for passthrough file copy)\n\tasync _addToTemplateMapIncrementalBuild(incrementalFileShape, paths, to = \"fs\") {\n\t\t// Render overrides are only used when `--ignore-initial` is in play and an initial build is not run\n\t\tlet ignoreInitialBuild = !this.isRunInitialBuild;\n\t\tlet secondOrderRelevantLookup = {};\n\t\tlet templates = [];\n\n\t\tlet promises = [];\n\t\tfor (let path of paths) {\n\t\t\tlet { template: tmpl } = this._createTemplate(path, to);\n\n\t\t\t// Note: removed a fix here to fetch missing templateRender instances\n\t\t\t// that was tested as no longer needed (Issue #3170)\n\t\t\t// Related: #3870, improved configuration reset\n\n\t\t\ttemplates.push(tmpl);\n\n\t\t\t// required for tmpl.isFileRelevantToThisTemplate below\n\t\t\tawait tmpl.asyncTemplateInitialization();\n\n\t\t\t// This must happen before data is generated for the incremental file only\n\t\t\tif (incrementalFileShape === \"template\" && tmpl.inputPath === this.incrementalFile) {\n\t\t\t\ttmpl.resetCaches();\n\t\t\t} else if (\n\t\t\t\t// Issue #3824 #3870\n\t\t\t\ttmpl.isFileRelevantToThisTemplate(this.incrementalFile, {\n\t\t\t\t\tisFullTemplate: incrementalFileShape === \"template\",\n\t\t\t\t})\n\t\t\t) {\n\t\t\t\ttmpl.resetCaches();\n\t\t\t}\n\n\t\t\t// IMPORTANT: This is where the data is first generated for the template\n\t\t\tpromises.push(this.templateMap.add(tmpl));\n\t\t}\n\n\t\t// Important to set up template dependency relationships first\n\t\tawait Promise.all(promises);\n\n\t\t// Delete incremental file from the dependency graph so we get fresh entries!\n\t\t// This _must_ happen before any additions, the other ones are in Custom.js and GlobalDependencyMap.js (from the eleventy.layouts Event)\n\t\tthis.config.uses.resetNode(this.incrementalFile);\n\n\t\t// write new template relationships to the global dependency graph for next time\n\t\tthis.templateMap.addAllToGlobalDependencyGraph();\n\n\t\t// Always disable render for --ignore-initial\n\t\tif (ignoreInitialBuild) {\n\t\t\tfor (let tmpl of templates) {\n\t\t\t\ttmpl.setRenderableOverride(false); // disable render\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tfor (let tmpl of templates) {\n\t\t\tif (incrementalFileShape === \"template\" && tmpl.inputPath === this.incrementalFile) {\n\t\t\t\ttmpl.setRenderableOverride(undefined); // unset, probably render\n\t\t\t} else if (\n\t\t\t\ttmpl.isFileRelevantToThisTemplate(this.incrementalFile, {\n\t\t\t\t\tisFullTemplate: incrementalFileShape === \"template\",\n\t\t\t\t})\n\t\t\t) {\n\t\t\t\t// changed file is used by template\n\t\t\t\t// template uses the changed file\n\t\t\t\ttmpl.setRenderableOverride(undefined); // unset, probably render\n\t\t\t\tsecondOrderRelevantLookup[tmpl.inputPath] = true;\n\t\t\t} else if (this.config.uses.isFileUsedBy(this.incrementalFile, tmpl.inputPath)) {\n\t\t\t\t// changed file uses this template\n\t\t\t\ttmpl.setRenderableOverride(\"optional\");\n\t\t\t} else {\n\t\t\t\t// For incremental, always disable render on irrelevant templates\n\t\t\t\ttmpl.setRenderableOverride(false); // disable render\n\t\t\t}\n\t\t}\n\n\t\tlet secondOrderRelevantArray = this.config.uses\n\t\t\t.getTemplatesRelevantToTemplateList(Object.keys(secondOrderRelevantLookup))\n\t\t\t.map((entry) => TemplatePath.addLeadingDotSlash(entry));\n\t\tlet secondOrderTemplates = Object.fromEntries(\n\t\t\tObject.entries(secondOrderRelevantArray).map(([index, value]) => [value, true]),\n\t\t);\n\n\t\tfor (let tmpl of templates) {\n\t\t\t// second order templates must also be rendered if not yet already rendered at least once and available in cache.\n\t\t\tif (secondOrderTemplates[tmpl.inputPath]) {\n\t\t\t\tif (tmpl.isRenderableDisabled()) {\n\t\t\t\t\ttmpl.setRenderableOverride(\"optional\");\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Order of templates does not matter here, they’re reordered later based on dependencies in TemplateMap.js\n\t\tfor (let tmpl of templates) {\n\t\t\tif (incrementalFileShape === \"template\" && tmpl.inputPath === this.incrementalFile) {\n\t\t\t\t// Cache is reset above (to invalidate data cache at the right time)\n\t\t\t\ttmpl.setDryRunViaIncremental(false);\n\t\t\t} else if (!tmpl.isRenderableDisabled() && !tmpl.isRenderableOptional()) {\n\t\t\t\t// Related to the template but not the template (reset the render cache, not the read cache)\n\t\t\t\ttmpl.resetCaches({\n\t\t\t\t\tdata: true,\n\t\t\t\t\trender: true,\n\t\t\t\t});\n\n\t\t\t\ttmpl.setDryRunViaIncremental(false);\n\t\t\t} else {\n\t\t\t\t// During incremental we only reset the data cache for non-matching templates, see https://github.com/11ty/eleventy/issues/2710\n\t\t\t\t// Keep caches for read/render\n\t\t\t\ttmpl.resetCaches({\n\t\t\t\t\tdata: true,\n\t\t\t\t});\n\n\t\t\t\ttmpl.setDryRunViaIncremental(true);\n\n\t\t\t\tthis.skippedCount++;\n\t\t\t}\n\t\t}\n\t}\n\n\tasync _addToTemplateMapFullBuild(paths, to = \"fs\") {\n\t\tif (this.incrementalFile) {\n\t\t\treturn [];\n\t\t}\n\n\t\tlet ignoreInitialBuild = !this.isRunInitialBuild;\n\t\tlet promises = [];\n\t\tfor (let path of paths) {\n\t\t\tlet { template: tmpl, wasCached } = this._createTemplate(path, to);\n\t\t\t// Render overrides are only used when `--ignore-initial` is in play and an initial build is not run\n\t\t\tif (ignoreInitialBuild) {\n\t\t\t\ttmpl.setRenderableOverride(false); // disable render\n\t\t\t} else {\n\t\t\t\ttmpl.setRenderableOverride(undefined); // unset, render\n\t\t\t}\n\n\t\t\tif (wasCached) {\n\t\t\t\ttmpl.resetCaches();\n\t\t\t}\n\n\t\t\t// IMPORTANT: This is where the data is first generated for the template\n\t\t\tpromises.push(this.templateMap.add(tmpl));\n\t\t}\n\n\t\treturn Promise.all(promises);\n\t}\n\n\tgetFileShape(paths, incrementalFile) {\n\t\t// WARNING: This is leaky—if Core is being used instead of Eleventy we are assuming everything is a template (not passthrough copy)\n\t\tif (!this.#eleventyFiles) {\n\t\t\treturn \"template\";\n\t\t}\n\n\t\treturn this.#eleventyFiles.getFileShape(paths, incrementalFile);\n\t}\n\n\tasync _addToTemplateMap(paths, to = \"fs\") {\n\t\tlet incrementalFileShape = this.getFileShape(paths, this.incrementalFile);\n\n\t\t// Filter out passthrough copy files\n\t\tpaths = paths.filter((path) => {\n\t\t\tif (!this.extensionMap.hasEngine(path)) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tif (incrementalFileShape === \"copy\") {\n\t\t\t\tthis.skippedCount++;\n\t\t\t\t// Filters out templates if the incremental file is a passthrough copy file\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\treturn true;\n\t\t});\n\n\t\tif (this.incrementalFile) {\n\t\t\t// Top level async to get at the promises returned.\n\t\t\treturn await this._addToTemplateMapIncrementalBuild(incrementalFileShape, paths, to);\n\t\t}\n\n\t\t// Full Build\n\t\tlet ret = await this._addToTemplateMapFullBuild(paths, to);\n\n\t\t// write new template relationships to the global dependency graph for next time\n\t\tthis.templateMap.addAllToGlobalDependencyGraph();\n\n\t\treturn ret;\n\t}\n\n\tasync _createTemplateMap(paths, to) {\n\t\tthis.templateMap = new TemplateMap(this.templateConfig);\n\n\t\tawait this._addToTemplateMap(paths, to);\n\t\tawait this.templateMap.cache();\n\n\t\t// Return is used by tests\n\t\treturn this.templateMap;\n\t}\n\n\tasync _generateTemplate(mapEntry, to) {\n\t\tlet tmpl = mapEntry.template;\n\n\t\treturn tmpl.generateMapEntry(mapEntry, to).then((pages) => {\n\t\t\tthis.renderCount += tmpl.getRenderCount();\n\t\t\tthis.writeCount += tmpl.getWriteCount();\n\t\t\treturn pages;\n\t\t});\n\t}\n\n\tasync writePassthroughCopy(templateExtensionPaths) {\n\t\tif (!this.#passthroughManager) {\n\t\t\tthrow new Error(\"Internal error: Missing `passthroughManager` instance.\");\n\t\t}\n\n\t\treturn this.#passthroughManager.copyAll(templateExtensionPaths).catch((e) => {\n\t\t\tthis.errorHandler.warn(e, \"Error with passthrough copy\");\n\t\t\treturn Promise.reject(new EleventyPassthroughCopyError(\"Having trouble copying\", e));\n\t\t});\n\t}\n\n\tasync generateTemplates(paths, to = \"fs\") {\n\t\tlet promises = [];\n\t\t// TODO optimize await here\n\t\tawait this._createTemplateMap(paths, to);\n\t\tdebug(\"Template map created.\");\n\n\t\tlet usedTemplateContentTooEarlyMap = [];\n\t\tfor (let mapEntry of this.templateMap.getMap()) {\n\t\t\tpromises.push(\n\t\t\t\tthis._generateTemplate(mapEntry, to).catch(function (e) {\n\t\t\t\t\t// Premature templateContent in layout render, this also happens in\n\t\t\t\t\t// TemplateMap.populateContentDataInMap for non-layout content\n\t\t\t\t\tif (EleventyErrorUtil.isPrematureTemplateContentError(e)) {\n\t\t\t\t\t\tusedTemplateContentTooEarlyMap.push(mapEntry);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tlet outputPaths = `\"${mapEntry._pages.map((page) => page.outputPath).join(`\", \"`)}\"`;\n\t\t\t\t\t\treturn Promise.reject(\n\t\t\t\t\t\t\tnew EleventyTemplateError(\n\t\t\t\t\t\t\t\t`Having trouble writing to ${outputPaths} from \"${mapEntry.inputPath}\"`,\n\t\t\t\t\t\t\t\te,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\n\t\tfor (let mapEntry of usedTemplateContentTooEarlyMap) {\n\t\t\tpromises.push(\n\t\t\t\tthis._generateTemplate(mapEntry, to).catch(function (e) {\n\t\t\t\t\treturn Promise.reject(\n\t\t\t\t\t\tnew EleventyTemplateError(\n\t\t\t\t\t\t\t`Having trouble writing to (second pass) \"${mapEntry.outputPath}\" from \"${mapEntry.inputPath}\"`,\n\t\t\t\t\t\t\te,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\n\t\treturn promises;\n\t}\n\n\t// Similiar to `write()` but skips passthrough copy\n\tasync writeTemplates() {\n\t\tlet paths = await this._getAllPaths();\n\n\t\treturn Promise.all(await this.generateTemplates(paths)).then(\n\t\t\t(templateResults) => {\n\t\t\t\treturn {\n\t\t\t\t\t// New in 3.0: flatten and filter out falsy templates\n\t\t\t\t\ttemplates: templateResults.flat().filter(Boolean),\n\t\t\t\t};\n\t\t\t},\n\t\t\t(e) => {\n\t\t\t\treturn Promise.reject(e);\n\t\t\t},\n\t\t);\n\t}\n\n\tasync write() {\n\t\tlet paths = await this._getAllPaths();\n\n\t\t// This must happen before writePassthroughCopy\n\t\tthis.templateConfig.userConfig.emit(\"eleventy#beforerender\");\n\n\t\tlet aggregatePassthroughCopyPromise = this.writePassthroughCopy(paths);\n\n\t\tlet templatesPromise = Promise.all(await this.generateTemplates(paths)).then((results) => {\n\t\t\tthis.templateConfig.userConfig.emit(\"eleventy#render\");\n\n\t\t\treturn results;\n\t\t});\n\n\t\treturn Promise.all([aggregatePassthroughCopyPromise, templatesPromise]).then(\n\t\t\tasync ([passthroughCopyResults, templateResults]) => {\n\t\t\t\treturn {\n\t\t\t\t\tpassthroughCopy: passthroughCopyResults,\n\t\t\t\t\t// New in 3.0: flatten and filter out falsy templates\n\t\t\t\t\ttemplates: templateResults.flat().filter(Boolean),\n\t\t\t\t};\n\t\t\t},\n\t\t\t(e) => {\n\t\t\t\treturn Promise.reject(e);\n\t\t\t},\n\t\t);\n\t}\n\n\t// Passthrough copy not supported in JSON output.\n\t// --incremental not supported in JSON output.\n\tasync getJSON(to = \"json\") {\n\t\tlet paths = await this._getAllPaths();\n\t\tlet promises = await this.generateTemplates(paths, to);\n\n\t\treturn Promise.all(promises).then(\n\t\t\t(templateResults) => {\n\t\t\t\treturn {\n\t\t\t\t\t// New in 3.0: flatten and filter out falsy templates\n\t\t\t\t\ttemplates: templateResults.flat().filter(Boolean),\n\t\t\t\t};\n\t\t\t},\n\t\t\t(e) => {\n\t\t\t\treturn Promise.reject(e);\n\t\t\t},\n\t\t);\n\t}\n\n\tsetVerboseOutput(isVerbose) {\n\t\tthis.isVerbose = isVerbose;\n\t\tthis.errorHandler.isVerbose = isVerbose;\n\t}\n\n\tsetDryRun(isDryRun) {\n\t\tthis.isDryRun = Boolean(isDryRun);\n\t}\n\n\tsetRunInitialBuild(runInitialBuild) {\n\t\tthis.isRunInitialBuild = runInitialBuild;\n\t}\n\tsetIncrementalBuild(isIncremental) {\n\t\tthis.isIncremental = isIncremental;\n\t}\n\tsetIncrementalFile(incrementalFile) {\n\t\tthis.incrementalFile = incrementalFile;\n\t\tthis.#passthroughManager?.setIncrementalFile(incrementalFile);\n\t}\n\tresetIncrementalFile() {\n\t\tthis.incrementalFile = null;\n\t\tthis.#passthroughManager?.resetIncrementalFile();\n\t}\n\n\tgetMetadata() {\n\t\treturn {\n\t\t\t// copyCount, copySize\n\t\t\t...(this.#passthroughManager?.getMetadata() || {}),\n\t\t\tskipCount: this.skippedCount,\n\t\t\twriteCount: this.writeCount,\n\t\t\trenderCount: this.renderCount,\n\t\t};\n\t}\n\n\tget caches() {\n\t\treturn [\"_templatePathCache\"];\n\t}\n}\n\nexport default TemplateWriter;\n"
  },
  {
    "path": "src/UserConfig.js",
    "content": "import debugUtil from \"debug\";\n\nimport { DeepCopy, TemplatePath, isPlainObject } from \"@11ty/eleventy-utils\";\n\nimport chalk from \"./Adapters/Packages/chalk.js\";\nimport { resolvePlugin } from \"./Util/ResolvePlugin.js\";\nimport isAsyncFunction from \"./Util/IsAsyncFunction.js\";\nimport objectFilter from \"./Util/Objects/ObjectFilter.js\";\nimport EventEmitter from \"./Util/AsyncEventEmitter.js\";\nimport EleventyCompatibility from \"./Util/Compatibility.js\";\nimport EleventyBaseError from \"./Errors/EleventyBaseError.js\";\nimport BenchmarkManager from \"./Benchmark/BenchmarkManager.js\";\nimport { augmentFunction } from \"./Engines/Util/ContextAugmenter.js\";\n\nconst debug = debugUtil(\"Eleventy:UserConfig\");\n\nclass UserConfigError extends EleventyBaseError {}\n\n/**\n * Eleventy’s user-land Configuration API\n * @module 11ty/eleventy/UserConfig\n */\nclass UserConfig {\n\t/** @type {boolean} */\n\t#pluginExecution = false;\n\t/** @type {boolean} */\n\t#quietModeLocked = false;\n\t/** @type {number|undefined} */\n\t#uniqueId;\n\t/** @type {number} */\n\t#concurrency = 1;\n\t// Before using os.availableParallelism(); see https://github.com/11ty/eleventy/issues/3596\n\n\tconstructor() {\n\t\t// These are completely unnecessary lines to satisfy TypeScript\n\t\tthis.plugins = [];\n\t\tthis.templateFormatsAdded = [];\n\t\tthis.additionalWatchTargets = [];\n\t\tthis.watchTargetsConfigReset = new Set();\n\t\tthis.extensionMap = new Set();\n\t\tthis.extensionMapClasses = {};\n\t\tthis.dataExtensions = new Map();\n\t\tthis.urlTransforms = [];\n\t\tthis.customDateParsingCallbacks = new Set();\n\t\tthis.ignores = new Set();\n\t\tthis.events = new EventEmitter();\n\n\t\t/** @type {object} */\n\t\tthis.directories = {};\n\t\t/** @type {undefined} */\n\t\tthis.logger;\n\t\t/** @type {string} */\n\t\tthis.dir;\n\t\t/** @type {string} */\n\t\tthis.pathPrefix;\n\t\t/** @type {object} */\n\t\tthis.errorReporting = {};\n\n\t\tthis.reset();\n\t\tthis.#uniqueId = Math.random();\n\t}\n\n\t// Internally used in TemplateContent for cache keys\n\t_getUniqueId() {\n\t\treturn this.#uniqueId;\n\t}\n\n\treset() {\n\t\tdebug(\"Resetting EleventyConfig to initial values.\");\n\n\t\t/** @type {EventEmitter} */\n\t\tthis.events = new EventEmitter();\n\t\tthis.events.setMaxListeners(25); // defaults to 10\n\n\t\t/** @type {BenchmarkManager} */\n\t\tthis.benchmarkManager = new BenchmarkManager();\n\n\t\t/** @type {object} */\n\t\tthis.benchmarks = {\n\t\t\t/** @type {import('./Benchmark/BenchmarkGroup.js')} */\n\t\t\tconfig: this.benchmarkManager.get(\"Configuration\"),\n\t\t\t/** @type {import('./Benchmark/BenchmarkGroup.js')} */\n\t\t\taggregate: this.benchmarkManager.get(\"Aggregate\"),\n\t\t};\n\n\t\t/** @type {object} */\n\t\tthis.directoryAssignments = {};\n\t\t/** @type {object} */\n\t\tthis.collections = {};\n\t\t/** @type {object} */\n\t\tthis.precompiledCollections = {};\n\t\tthis.templateFormats = undefined;\n\t\tthis.templateFormatsAdded = [];\n\n\t\t/** @type {object} */\n\t\tthis.universal = {\n\t\t\tfilters: {},\n\t\t\tshortcodes: {},\n\t\t\tpairedShortcodes: {},\n\t\t};\n\n\t\t/** @type {object} */\n\t\tthis.liquid = {\n\t\t\toptions: {},\n\t\t\ttags: {},\n\t\t\tfilters: {},\n\t\t\tshortcodes: {},\n\t\t\tpairedShortcodes: {},\n\t\t\tparameterParsing: \"builtin\", // or legacy (Breaking: default swapped in v4.0.0)\n\t\t};\n\n\t\t/** @type {object} */\n\t\tthis.nunjucks = {\n\t\t\t// `dev: true` gives us better error messaging\n\t\t\tenvironmentOptions: { dev: true },\n\t\t\tprecompiledTemplates: {},\n\t\t\tloaders: [],\n\t\t\tfilters: {},\n\t\t\tasyncFilters: {},\n\t\t\ttags: {},\n\t\t\tglobals: {},\n\t\t\tshortcodes: {},\n\t\t\tpairedShortcodes: {},\n\t\t\tasyncShortcodes: {},\n\t\t\tasyncPairedShortcodes: {},\n\t\t};\n\n\t\t/** @type {object} */\n\t\tthis.javascript = {\n\t\t\tfunctions: {},\n\t\t\tfilters: {},\n\t\t\tshortcodes: {},\n\t\t\tpairedShortcodes: {},\n\t\t};\n\n\t\tthis.markdownHighlighter = null;\n\n\t\t/** @type {object} */\n\t\tthis.libraryOverrides = {};\n\n\t\t/** @type {object} */\n\t\tthis.passthroughCopies = {};\n\t\tthis.passthroughCopiesHtmlRelative = new Set();\n\n\t\t/** @type {object} */\n\t\tthis.layoutAliases = {};\n\t\tthis.layoutResolution = true; // extension-less layout files\n\n\t\t/** @type {object} */\n\t\tthis.linters = {};\n\t\t/** @type {object} */\n\t\tthis.transforms = {};\n\t\t/** @type {object} */\n\t\tthis.preprocessors = {};\n\n\t\tthis.activeNamespace = \"\";\n\t\tthis.dynamicPermalinks = true;\n\n\t\tthis.useGitIgnore = true;\n\n\t\tlet defaultIgnores = new Set();\n\t\tdefaultIgnores.add(\"**/node_modules/**\");\n\t\tdefaultIgnores.add(\".git/**\"); // TODO `**/.git/**`\n\t\tthis.ignores = new Set(defaultIgnores);\n\t\tthis.watchIgnores = new Set(defaultIgnores);\n\n\t\tthis.extensionMap = new Set();\n\t\tthis.extensionMapClasses = {};\n\t\t/** @type {object} */\n\t\tthis.extensionConflictMap = {};\n\t\tthis.watchJavaScriptDependencies = true;\n\t\tthis.additionalWatchTargets = [];\n\t\tthis.watchTargetsConfigReset = new Set();\n\t\t/** @type {object} */\n\t\tthis.serverOptions = {};\n\t\t/** @type {object} */\n\t\tthis.globalData = {};\n\t\t/** @type {object} */\n\t\tthis.chokidarConfig = {};\n\t\tthis.watchThrottleWaitTime = 0; //ms\n\n\t\t// using Map to preserve insertion order\n\t\tthis.dataExtensions = new Map();\n\n\t\tthis.quietMode = false;\n\n\t\tthis.plugins = [];\n\n\t\tthis.useTemplateCache = true;\n\t\tthis.dataFilterSelectors = new Set();\n\n\t\t/** @type {object} */\n\t\tthis.libraryAmendments = {};\n\t\tthis.serverPassthroughCopyBehavior = \"copy\"; // or \"passthrough\"\n\t\tthis.urlTransforms = [];\n\n\t\t// Defaults in `defaultConfig.js`\n\t\tthis.dataFileSuffixesOverride = false;\n\t\tthis.dataFileDirBaseNameOverride = false;\n\n\t\t/** @type {object} */\n\t\t// Moved into TemplateContent->getFrontMatterParsingOptions\n\t\tthis.frontMatterParsingOptions = {};\n\n\t\t/** @type {object} */\n\t\tthis.virtualTemplates = {};\n\t\tthis.freezeReservedData = true;\n\t\tthis.customDateParsingCallbacks = new Set();\n\n\t\t/** @type {object} */\n\t\tthis.errorReporting = {};\n\n\t\t// Before using os.availableParallelism(); see https://github.com/11ty/eleventy/issues/3596\n\t\tthis.#concurrency = 1;\n\t}\n\n\t// compatibleRange is optional in 2.0.0-beta.2\n\tversionCheck(compatibleRange) {\n\t\tlet compat = new EleventyCompatibility(compatibleRange);\n\n\t\tif (!compat.isCompatible()) {\n\t\t\tthrow new UserConfigError(compat.getErrorMessage());\n\t\t}\n\t}\n\n\t/*\n\t * Events\n\t */\n\n\t// Duplicate event bindings are avoided with the `reset` method above.\n\t// A new EventEmitter instance is created when the config is reset.\n\ton(eventName, callback) {\n\t\treturn this.events.on(eventName, callback);\n\t}\n\n\tonce(eventName, callback) {\n\t\treturn this.events.once(eventName, callback);\n\t}\n\n\temit(eventName, ...args) {\n\t\treturn this.events.emit(eventName, ...args);\n\t}\n\n\tsetEventEmitterMode(mode) {\n\t\tthis.events.setHandlerMode(mode);\n\t}\n\n\t/*\n\t * Universal getters\n\t */\n\tgetFilter(name) {\n\t\t// JavaScript functions are included here for backwards compatibility https://github.com/11ty/eleventy/issues/3365\n\t\treturn this.universal.filters[name] || this.javascript.functions[name];\n\t}\n\n\tgetFilters(options = {}) {\n\t\tif (options.type) {\n\t\t\treturn objectFilter(\n\t\t\t\tthis.universal.filters,\n\t\t\t\t(entry) => entry.__eleventyInternal?.type === options.type,\n\t\t\t);\n\t\t}\n\n\t\treturn this.universal.filters;\n\t}\n\n\tgetShortcode(name) {\n\t\treturn this.universal.shortcodes[name];\n\t}\n\n\tgetShortcodes(options = {}) {\n\t\tif (options.type) {\n\t\t\treturn objectFilter(\n\t\t\t\tthis.universal.shortcodes,\n\t\t\t\t(entry) => entry.__eleventyInternal?.type === options.type,\n\t\t\t);\n\t\t}\n\n\t\treturn this.universal.shortcodes;\n\t}\n\n\tgetPairedShortcode(name) {\n\t\treturn this.universal.pairedShortcodes[name];\n\t}\n\n\tgetPairedShortcodes(options = {}) {\n\t\tif (options.type) {\n\t\t\treturn objectFilter(\n\t\t\t\tthis.universal.pairedShortcodes,\n\t\t\t\t(entry) => entry.__eleventyInternal?.type === options.type,\n\t\t\t);\n\t\t}\n\t\treturn this.universal.pairedShortcodes;\n\t}\n\n\t/*\n\t * Private utilities\n\t */\n\t#add(target, originalName, callback, options) {\n\t\tlet { description, functionName } = options;\n\n\t\tif (typeof callback !== \"function\") {\n\t\t\tthrow new Error(`Invalid definition for \"${originalName}\" ${description}.`);\n\t\t}\n\n\t\tlet name = this.getNamespacedName(originalName);\n\n\t\tif (target[name]) {\n\t\t\tdebug(\n\t\t\t\tchalk.yellow(`Warning, overwriting previous ${description} \"%o\" via \\`%o(%o)\\``),\n\t\t\t\tname,\n\t\t\t\tfunctionName,\n\t\t\t\toriginalName,\n\t\t\t);\n\t\t} else {\n\t\t\tdebug(`Adding new ${description} \"%o\" via \\`%o(%o)\\``, name, functionName, originalName);\n\t\t}\n\n\t\ttarget[name] = this.#decorateCallback(`\"${name}\" ${description}`, callback);\n\t}\n\n\t#decorateCallback(type, callback) {\n\t\treturn this.benchmarks.config.add(type, callback);\n\t}\n\n\t/*\n\t * Markdown\n\t */\n\n\t// Don’t use this, projects should use `amendLibrary` as documented here:\n\t// https://www.11ty.dev/docs/languages/markdown/#optional-amend-the-library-instance\n\t// Warning: this is in use by the Syntax Highlighting plugin (as of v5.0.2)\n\taddMarkdownHighlighter(highlightFn) {\n\t\tthis.markdownHighlighter = highlightFn;\n\t}\n\n\tsetMarkdownTemplateEngine(engineName) {\n\t\tthis.markdownTemplateEngine = engineName;\n\t}\n\n\tsetHtmlTemplateEngine(engineName) {\n\t\tthis.htmlTemplateEngine = engineName;\n\t}\n\n\t/*\n\t * Filters\n\t */\n\n\taddLiquidFilter(name, callback) {\n\t\tthis.#add(this.liquid.filters, name, callback, {\n\t\t\tdescription: \"Liquid Filter\",\n\t\t\tfunctionName: \"addLiquidFilter\",\n\t\t});\n\t}\n\n\taddNunjucksAsyncFilter(name, callback) {\n\t\tthis.#add(this.nunjucks.asyncFilters, name, callback, {\n\t\t\tdescription: \"Nunjucks Filter\",\n\t\t\tfunctionName: \"addNunjucksAsyncFilter\",\n\t\t});\n\t}\n\n\t// Support the nunjucks style syntax for asynchronous filter add\n\taddNunjucksFilter(name, callback, isAsync = false) {\n\t\tif (isAsync) {\n\t\t\t// namespacing happens downstream\n\t\t\tthis.addNunjucksAsyncFilter(name, callback);\n\t\t} else {\n\t\t\tthis.#add(this.nunjucks.filters, name, callback, {\n\t\t\t\tdescription: \"Nunjucks Filter\",\n\t\t\t\tfunctionName: \"addNunjucksFilter\",\n\t\t\t});\n\t\t}\n\t}\n\n\taddJavaScriptFilter(name, callback) {\n\t\tthis.#add(this.javascript.filters, name, callback, {\n\t\t\tdescription: \"JavaScript Filter\",\n\t\t\tfunctionName: \"addJavaScriptFilter\",\n\t\t});\n\n\t\t// Backwards compat for a time before `addJavaScriptFilter` existed.\n\t\tthis.addJavaScriptFunction(name, callback);\n\t}\n\n\taddFilter(name, callback) {\n\t\t// This method *requires* `async function` and will not work with `function` that returns a promise\n\t\tif (isAsyncFunction(callback)) {\n\t\t\tthis.addAsyncFilter(name, callback);\n\t\t\treturn;\n\t\t}\n\n\t\t// namespacing happens downstream\n\t\tthis.#add(this.universal.filters, name, callback, {\n\t\t\tdescription: \"Universal Filter\",\n\t\t\tfunctionName: \"addFilter\",\n\t\t});\n\n\t\tthis.addLiquidFilter(name, callback);\n\t\tthis.addJavaScriptFilter(name, callback);\n\t\tthis.addNunjucksFilter(\n\t\t\tname,\n\t\t\t/** @this {any} */\n\t\t\tfunction (...args) {\n\t\t\t\t// Note that `callback` is already a function as the `#add` method throws an error if not.\n\t\t\t\tlet ret = callback.call(this, ...args);\n\t\t\t\tif (ret instanceof Promise) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Nunjucks *is* async-friendly with \\`addFilter(\"${name}\", async function() {})\\` but you need to supply an \\`async function\\`. You returned a promise from \\`addFilter(\"${name}\", function() {})\\`. Alternatively, use the \\`addAsyncFilter(\"${name}\")\\` configuration API method.`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\treturn ret;\n\t\t\t},\n\t\t);\n\t}\n\n\t// Liquid, Nunjucks, and JS only\n\taddAsyncFilter(name, callback) {\n\t\t// namespacing happens downstream\n\t\tthis.#add(this.universal.filters, name, callback, {\n\t\t\tdescription: \"Universal Filter\",\n\t\t\tfunctionName: \"addAsyncFilter\",\n\t\t});\n\n\t\tthis.addLiquidFilter(name, callback);\n\t\tthis.addJavaScriptFilter(name, callback);\n\t\tthis.addNunjucksAsyncFilter(\n\t\t\tname,\n\t\t\t/** @this {any} */\n\t\t\tasync function (...args) {\n\t\t\t\tlet cb = args.pop();\n\t\t\t\t// Note that `callback` is already a function as the `#add` method throws an error if not.\n\t\t\t\tlet ret = await callback.call(this, ...args);\n\t\t\t\tcb(null, ret);\n\t\t\t},\n\t\t);\n\t}\n\n\t/*\n\t * Shortcodes\n\t */\n\n\taddShortcode(name, callback) {\n\t\t// This method *requires* `async function` and will not work with `function` that returns a promise\n\t\tif (isAsyncFunction(callback)) {\n\t\t\tthis.addAsyncShortcode(name, callback);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.#add(this.universal.shortcodes, name, callback, {\n\t\t\tdescription: \"Universal Shortcode\",\n\t\t\tfunctionName: \"addShortcode\",\n\t\t});\n\n\t\tthis.addLiquidShortcode(name, callback);\n\t\tthis.addJavaScriptShortcode(name, callback);\n\t\tthis.addNunjucksShortcode(name, callback);\n\t}\n\n\taddAsyncShortcode(name, callback) {\n\t\tthis.#add(this.universal.shortcodes, name, callback, {\n\t\t\tdescription: \"Universal Shortcode\",\n\t\t\tfunctionName: \"addAsyncShortcode\",\n\t\t});\n\n\t\t// Related: #498\n\t\tthis.addNunjucksAsyncShortcode(name, callback);\n\t\tthis.addLiquidShortcode(name, callback);\n\t\tthis.addJavaScriptShortcode(name, callback);\n\t}\n\n\taddNunjucksAsyncShortcode(name, callback) {\n\t\tthis.#add(this.nunjucks.asyncShortcodes, name, callback, {\n\t\t\tdescription: \"Nunjucks Async Shortcode\",\n\t\t\tfunctionName: \"addNunjucksAsyncShortcode\",\n\t\t});\n\t}\n\n\taddNunjucksShortcode(name, callback, isAsync = false) {\n\t\tif (isAsync) {\n\t\t\tthis.addNunjucksAsyncShortcode(name, callback);\n\t\t} else {\n\t\t\tthis.#add(this.nunjucks.shortcodes, name, callback, {\n\t\t\t\tdescription: \"Nunjucks Shortcode\",\n\t\t\t\tfunctionName: \"addNunjucksShortcode\",\n\t\t\t});\n\t\t}\n\t}\n\n\taddLiquidShortcode(name, callback) {\n\t\tthis.#add(this.liquid.shortcodes, name, callback, {\n\t\t\tdescription: \"Liquid Shortcode\",\n\t\t\tfunctionName: \"addLiquidShortcode\",\n\t\t});\n\t}\n\n\taddPairedShortcode(name, callback) {\n\t\t// This method *requires* `async function` and will not work with `function` that returns a promise\n\t\tif (isAsyncFunction(callback)) {\n\t\t\tthis.addPairedAsyncShortcode(name, callback);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.#add(this.universal.pairedShortcodes, name, callback, {\n\t\t\tdescription: \"Universal Paired Shortcode\",\n\t\t\tfunctionName: \"addPairedShortcode\",\n\t\t});\n\n\t\tthis.addPairedNunjucksShortcode(name, callback);\n\t\tthis.addPairedLiquidShortcode(name, callback);\n\t\tthis.addPairedJavaScriptShortcode(name, callback);\n\t}\n\n\t// Related: #498\n\taddPairedAsyncShortcode(name, callback) {\n\t\tthis.#add(this.universal.pairedShortcodes, name, callback, {\n\t\t\tdescription: \"Universal Paired Async Shortcode\",\n\t\t\tfunctionName: \"addPairedAsyncShortcode\",\n\t\t});\n\n\t\tthis.addPairedNunjucksAsyncShortcode(name, callback);\n\t\tthis.addPairedLiquidShortcode(name, callback);\n\t\tthis.addPairedJavaScriptShortcode(name, callback);\n\t}\n\n\taddPairedNunjucksAsyncShortcode(name, callback) {\n\t\tthis.#add(this.nunjucks.asyncPairedShortcodes, name, callback, {\n\t\t\tdescription: \"Nunjucks Async Paired Shortcode\",\n\t\t\tfunctionName: \"addPairedNunjucksAsyncShortcode\",\n\t\t});\n\t}\n\n\taddPairedNunjucksShortcode(name, callback, isAsync = false) {\n\t\tif (isAsync) {\n\t\t\tthis.addPairedNunjucksAsyncShortcode(name, callback);\n\t\t} else {\n\t\t\tthis.#add(this.nunjucks.pairedShortcodes, name, callback, {\n\t\t\t\tdescription: \"Nunjucks Paired Shortcode\",\n\t\t\t\tfunctionName: \"addPairedNunjucksShortcode\",\n\t\t\t});\n\t\t}\n\t}\n\n\taddPairedLiquidShortcode(name, callback) {\n\t\tthis.#add(this.liquid.pairedShortcodes, name, callback, {\n\t\t\tdescription: \"Liquid Paired Shortcode\",\n\t\t\tfunctionName: \"addPairedLiquidShortcode\",\n\t\t});\n\t}\n\n\taddJavaScriptShortcode(name, callback) {\n\t\tthis.#add(this.javascript.shortcodes, name, callback, {\n\t\t\tdescription: \"JavaScript Shortcode\",\n\t\t\tfunctionName: \"addJavaScriptShortcode\",\n\t\t});\n\n\t\t// Backwards compat for a time before `addJavaScriptShortcode` existed.\n\t\tthis.addJavaScriptFunction(name, callback);\n\t}\n\n\taddPairedJavaScriptShortcode(name, callback) {\n\t\tthis.#add(this.javascript.pairedShortcodes, name, callback, {\n\t\t\tdescription: \"JavaScript Paired Shortcode\",\n\t\t\tfunctionName: \"addPairedJavaScriptShortcode\",\n\t\t});\n\n\t\t// Backwards compat for a time before `addJavaScriptShortcode` existed.\n\t\tthis.addJavaScriptFunction(name, callback);\n\t}\n\n\t// Both Filters and shortcodes feed into this\n\taddJavaScriptFunction(name, callback) {\n\t\tthis.#add(this.javascript.functions, name, callback, {\n\t\t\tdescription: \"JavaScript Function\",\n\t\t\tfunctionName: \"addJavaScriptFunction\",\n\t\t});\n\t}\n\n\t/*\n\t * Custom Tags\n\t */\n\n\t// tagCallback: function(liquidEngine) { return { parse: …, render: … }} };\n\taddLiquidTag(name, tagFn) {\n\t\tif (typeof tagFn !== \"function\") {\n\t\t\tthrow new UserConfigError(\n\t\t\t\t`EleventyConfig.addLiquidTag expects a callback function to be passed in for ${name}: addLiquidTag(name, function(liquidEngine) { return { parse: …, render: … } })`,\n\t\t\t);\n\t\t}\n\n\t\tthis.#add(this.liquid.tags, name, tagFn, {\n\t\t\tdescription: \"Liquid Custom Tag\",\n\t\t\tfunctionName: \"addLiquidTag\",\n\t\t});\n\t}\n\n\taddNunjucksTag(name, tagFn) {\n\t\tif (typeof tagFn !== \"function\") {\n\t\t\tthrow new UserConfigError(\n\t\t\t\t`EleventyConfig.addNunjucksTag expects a callback function to be passed in for ${name}: addNunjucksTag(name, function(nunjucksEngine) {})`,\n\t\t\t);\n\t\t}\n\n\t\tthis.#add(this.nunjucks.tags, name, tagFn, {\n\t\t\tdescription: \"Nunjucks Custom Tag\",\n\t\t\tfunctionName: \"addNunjucksTag\",\n\t\t});\n\t}\n\n\t/*\n\t * Plugins\n\t */\n\n\t// Internal method\n\t_enablePluginExecution() {\n\t\tthis.#pluginExecution = true;\n\t}\n\n\t// Internal method\n\t_disablePluginExecution() {\n\t\tthis.#pluginExecution = false;\n\t}\n\n\t/* Config is executed in two stages and plugins are the second stage—are we in the plugins stage? */\n\tisPluginExecution() {\n\t\treturn this.#pluginExecution;\n\t}\n\n\t/**\n\t * @typedef {function|Promise<function>|object} PluginDefinition\n\t * @property {Function} [configFunction]\n\t * @property {string} [eleventyPackage]\n\t * @property {object} [eleventyPluginOptions={}]\n\t * @property {boolean} [eleventyPluginOptions.unique]\n\t */\n\n\t/**\n\t * addPlugin: async friendly in 3.0\n\t *\n\t * @param {PluginDefinition} plugin\n\t */\n\taddPlugin(plugin, options = {}) {\n\t\t// First addPlugin of a unique plugin wins\n\t\tif (plugin?.eleventyPluginOptions?.unique && this.hasPlugin(plugin)) {\n\t\t\tdebug(\"Skipping duplicate unique addPlugin for %o\", this._getPluginName(plugin));\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.isPluginExecution() || options?.immediate) {\n\t\t\t// this might return a promise\n\t\t\treturn this._executePlugin(plugin, options);\n\t\t} else {\n\t\t\tthis.plugins.push({\n\t\t\t\tplugin,\n\t\t\t\toptions,\n\t\t\t\tpluginNamespace: this.activeNamespace,\n\t\t\t});\n\t\t}\n\t}\n\n\t/** @param {string} name */\n\tresolvePlugin(name) {\n\t\treturn resolvePlugin(name);\n\t}\n\n\t/** @param {string|PluginDefinition} plugin */\n\thasPlugin(plugin) {\n\t\tlet pluginName;\n\t\tif (typeof plugin === \"string\") {\n\t\t\tpluginName = plugin;\n\t\t} else {\n\t\t\tpluginName = this._getPluginName(plugin);\n\t\t}\n\n\t\treturn this.plugins.some((entry) => this._getPluginName(entry.plugin) === pluginName);\n\t}\n\n\taddNunjucksLoader(options) {\n\t\tif (!isPlainObject(options)) {\n\t\t\tthrow new Error(\"addNunjucksLoader expects an object literal argument.\");\n\t\t}\n\t\tthis.nunjucks.loaders.push(options);\n\t}\n\n\t// Using Function.name https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/name#examples\n\t/** @param {PluginDefinition} plugin */\n\t_getPluginName(plugin) {\n\t\tif (plugin?.eleventyPackage) {\n\t\t\treturn plugin.eleventyPackage;\n\t\t}\n\t\tif (typeof plugin === \"function\") {\n\t\t\treturn plugin.name;\n\t\t}\n\t\tif (plugin?.configFunction && typeof plugin.configFunction === \"function\") {\n\t\t\treturn plugin.configFunction.name;\n\t\t}\n\t}\n\n\t// Starting in 3.0 the plugin callback might be asynchronous!\n\t_executePlugin(plugin, options) {\n\t\tlet name = this._getPluginName(plugin);\n\t\tlet ret;\n\t\tdebug(`Adding %o plugin`, name || \"anonymous\");\n\t\tlet pluginBenchmark = this.benchmarks.aggregate.get(\"Configuration addPlugin\");\n\n\t\tif (typeof plugin === \"function\") {\n\t\t\tpluginBenchmark.before();\n\t\t\tthis.benchmarks.config;\n\t\t\tlet configFunction = plugin;\n\t\t\tret = configFunction(this, options);\n\t\t\tpluginBenchmark.after();\n\t\t} else if (plugin?.configFunction) {\n\t\t\tpluginBenchmark.before();\n\n\t\t\tif (options && typeof options.init === \"function\") {\n\t\t\t\t// init is not yet async-friendly but it’s also barely used\n\t\t\t\toptions.init.call(this, plugin.initArguments || {});\n\t\t\t}\n\n\t\t\tret = plugin.configFunction(this, options);\n\t\t\tpluginBenchmark.after();\n\t\t} else {\n\t\t\tthrow new UserConfigError(\n\t\t\t\t\"Invalid eleventyConfig.addPlugin() signature. Should be a function or a valid Eleventy plugin object.\",\n\t\t\t);\n\t\t}\n\t\treturn ret;\n\t}\n\n\t/** @param {string} name */\n\tgetNamespacedName(name) {\n\t\treturn this.activeNamespace + name;\n\t}\n\n\tasync namespace(pluginNamespace, callback) {\n\t\tlet validNamespace = pluginNamespace && typeof pluginNamespace === \"string\";\n\t\tif (validNamespace) {\n\t\t\tthis.activeNamespace = pluginNamespace || \"\";\n\t\t}\n\n\t\tawait callback(this);\n\n\t\tif (validNamespace) {\n\t\t\tthis.activeNamespace = \"\";\n\t\t}\n\t}\n\n\t/**\n\t * Adds a path to a file or directory to the list of pass-through copies\n\t * which are copied as-is to the output.\n\t *\n\t * @param {string|object} fileOrDir The path to the file or directory that should\n\t * be copied. OR an object where the key is the input glob and the property is the output directory\n\t * @param {object} copyOptions options for recursive-copy.\n\t * see https://www.npmjs.com/package/recursive-copy#arguments\n\t * default options are defined in TemplatePassthrough copyOptionsDefault\n\t * @returns {any} a reference to the `EleventyConfig` object.\n\t */\n\taddPassthroughCopy(fileOrDir, copyOptions = {}) {\n\t\tif (copyOptions.mode) {\n\t\t\tif (copyOptions.mode !== \"html-relative\") {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t\"Invalid `mode` option for `addPassthroughCopy`. Received: '\" + copyOptions.mode + \"'\",\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (isPlainObject(fileOrDir)) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t\"mode: 'html-relative' does not yet support passthrough copy objects (input -> output mapping). Use a string glob or an Array of string globs.\",\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tthis.passthroughCopiesHtmlRelative?.add({\n\t\t\t\tmatch: fileOrDir,\n\t\t\t\t...copyOptions,\n\t\t\t});\n\t\t} else if (typeof fileOrDir === \"string\") {\n\t\t\tthis.passthroughCopies[fileOrDir] = { outputPath: true, copyOptions };\n\t\t} else {\n\t\t\tfor (let [inputPath, outputPath] of Object.entries(fileOrDir)) {\n\t\t\t\tthis.passthroughCopies[inputPath] = { outputPath, copyOptions };\n\t\t\t}\n\t\t}\n\n\t\treturn this;\n\t}\n\n\t/*\n\t * Template Formats\n\t */\n\tsetTemplateFormats(templateFormats) {\n\t\tthis.templateFormats = templateFormats;\n\t}\n\n\t// additive, usually for plugins\n\taddTemplateFormats(templateFormats) {\n\t\tthis.templateFormatsAdded.push(templateFormats);\n\t}\n\n\t/*\n\t * Library Overrides and Options\n\t */\n\tsetLibrary(engineName, libraryInstance) {\n\t\tif (engineName === \"liquid\" && Object.keys(this.liquid.options).length) {\n\t\t\tdebug(\n\t\t\t\t\"WARNING: using `eleventyConfig.setLibrary` will override any configuration set using `.setLiquidOptions` via the config API. You’ll need to pass these options to the library yourself.\",\n\t\t\t);\n\t\t} else if (engineName === \"njk\" && Object.keys(this.nunjucks.environmentOptions).length) {\n\t\t\tdebug(\n\t\t\t\t\"WARNING: using `eleventyConfig.setLibrary` will override any configuration set using `.setNunjucksEnvironmentOptions` via the config API. You’ll need to pass these options to the library yourself.\",\n\t\t\t);\n\t\t}\n\n\t\tthis.libraryOverrides[engineName.toLowerCase()] = libraryInstance;\n\t}\n\n\t/* These callbacks run on both libraryOverrides and default library instances */\n\tamendLibrary(engineName, callback) {\n\t\tlet name = engineName.toLowerCase();\n\t\tif (!this.libraryAmendments[name]) {\n\t\t\tthis.libraryAmendments[name] = [];\n\t\t}\n\n\t\tthis.libraryAmendments[name].push(callback);\n\t}\n\n\tsetLiquidOptions(options) {\n\t\tthis.liquid.options = options;\n\t}\n\n\tsetLiquidParameterParsing(behavior) {\n\t\tif (behavior !== \"legacy\" && behavior !== \"builtin\") {\n\t\t\tthrow new Error(\n\t\t\t\t`Invalid argument passed to \\`setLiquidParameterParsing\\`. Expected one of \"legacy\" or \"builtin\".`,\n\t\t\t);\n\t\t}\n\t\tthis.liquid.parameterParsing = behavior;\n\t}\n\n\tsetNunjucksEnvironmentOptions(options) {\n\t\tthis.nunjucks.environmentOptions = options;\n\t}\n\n\tsetNunjucksPrecompiledTemplates(templates) {\n\t\tthis.nunjucks.precompiledTemplates = templates;\n\t}\n\n\tsetDynamicPermalinks(enabled) {\n\t\tthis.dynamicPermalinks = !!enabled;\n\t}\n\n\tsetUseGitIgnore(enabled) {\n\t\tthis.useGitIgnore = !!enabled;\n\t}\n\n\taddWatchTarget(additionalWatchTargets, options = {}) {\n\t\t// Reset the config when the target path changes\n\t\tif (options.resetConfig) {\n\t\t\tthis.watchTargetsConfigReset.add(additionalWatchTargets);\n\t\t}\n\n\t\tthis.additionalWatchTargets.push(additionalWatchTargets);\n\t}\n\n\tsetWatchJavaScriptDependencies(watchEnabled) {\n\t\tthis.watchJavaScriptDependencies = !!watchEnabled;\n\t}\n\n\tsetServerOptions(options = {}, override = false) {\n\t\tif (override) {\n\t\t\tthis.serverOptions = options;\n\t\t} else {\n\t\t\tthis.serverOptions = DeepCopy(this.serverOptions, options);\n\t\t}\n\t}\n\n\tsetChokidarConfig(options = {}) {\n\t\tthis.chokidarConfig = options;\n\t}\n\n\tsetWatchThrottleWaitTime(time = 0) {\n\t\tthis.watchThrottleWaitTime = time;\n\t}\n\n\t// 3.0 change: this does a top level merge instead of reset.\n\tsetFrontMatterParsingOptions(options = {}) {\n\t\tDeepCopy(this.frontMatterParsingOptions, options);\n\t}\n\n\t/* Internal method for CLI --quiet */\n\t_setQuietModeOverride(quietMode) {\n\t\tthis.setQuietMode(quietMode);\n\t\tthis.#quietModeLocked = true;\n\t}\n\n\tsetQuietMode(quietMode) {\n\t\tif (this.#quietModeLocked) {\n\t\t\tdebug(\n\t\t\t\t\"Attempt to `setQuietMode(%o)` ignored, --quiet command line argument override in place.\",\n\t\t\t\t!!quietMode,\n\t\t\t);\n\t\t\t// override via CLI takes precedence\n\t\t\treturn;\n\t\t}\n\n\t\tthis.quietMode = !!quietMode;\n\t}\n\n\taddEngine(fileExtension, classInstance) {\n\t\tif (Object.getPrototypeOf(classInstance).name !== \"TemplateEngine\") {\n\t\t\tthrow new Error(\n\t\t\t\t`Instance of TemplateEngine expected. Received: ${Object.getPrototypeOf(classInstance).name} If you’re trying to create a custom template engine, please use the eleventyConfig.addExtension API.`,\n\t\t\t);\n\t\t}\n\n\t\t// TODO check for conflicts\n\t\tthis.extensionMapClasses[fileExtension] = classInstance;\n\t}\n\n\taddExtension(fileExtension, options = {}) {\n\t\tlet extensions;\n\n\t\t// Array support 2.0.0-canary.19+\n\t\tif (Array.isArray(fileExtension)) {\n\t\t\textensions = fileExtension;\n\t\t} else {\n\t\t\t// Comma separated support 4.0.0-alpha.7+\n\t\t\textensions = (fileExtension || \"\").split(\",\");\n\t\t}\n\n\t\tfor (let extension of extensions) {\n\t\t\tif (this.extensionConflictMap[extension]) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`An attempt was made to override the \"${extension}\" template syntax twice (via the \\`addExtension\\` configuration API). A maximum of one override is currently supported.`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tthis.extensionConflictMap[extension] = true;\n\n\t\t\t/** @type {object} */\n\t\t\tlet extensionOptions = Object.assign(\n\t\t\t\t{\n\t\t\t\t\t// Might be overridden for aliasing in options.key\n\t\t\t\t\tkey: extension,\n\t\t\t\t\textension: extension,\n\t\t\t\t},\n\t\t\t\toptions,\n\t\t\t);\n\n\t\t\tif (extensionOptions.key !== extensionOptions.extension) {\n\t\t\t\textensionOptions.aliasKey = extensionOptions.extension;\n\t\t\t}\n\n\t\t\tthis.extensionMap.add(extensionOptions);\n\t\t}\n\t}\n\n\taddDataExtension(extensionList, parser) {\n\t\tlet options = {};\n\t\t// second argument is an object with a `parser` callback\n\t\tif (typeof parser !== \"function\") {\n\t\t\tif (!(\"parser\" in parser)) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t\"Expected `parser` property in second argument object to `eleventyConfig.addDataExtension`\",\n\t\t\t\t);\n\t\t\t}\n\n\t\t\toptions = parser;\n\t\t\tparser = options.parser;\n\t\t}\n\n\t\tlet extensions = extensionList.split(\",\").map((s) => s.trim());\n\t\tfor (let extension of extensions) {\n\t\t\tthis.dataExtensions.set(extension, {\n\t\t\t\textension,\n\t\t\t\tparser,\n\t\t\t\toptions,\n\t\t\t});\n\t\t}\n\t}\n\n\tsetUseTemplateCache(bypass) {\n\t\tthis.useTemplateCache = !!bypass;\n\t}\n\n\tsetPrecompiledCollections(collections) {\n\t\tthis.precompiledCollections = collections;\n\t}\n\n\t// \"passthrough\" is the default, no other value is explicitly required in code\n\t// but opt-out via \"copy\" is suggested\n\tsetServerPassthroughCopyBehavior(behavior) {\n\t\tthis.serverPassthroughCopyBehavior = behavior;\n\t}\n\n\t// Url transforms change page.url and work good with server side content-negotiation (e.g. i18n plugin)\n\taddUrlTransform(callback) {\n\t\tthis.urlTransforms.push(callback);\n\t}\n\n\tsetDataFileSuffixes(suffixArray) {\n\t\tthis.dataFileSuffixesOverride = suffixArray;\n\t}\n\n\tsetDataFileBaseName(baseName) {\n\t\tthis.dataFileDirBaseNameOverride = baseName;\n\t}\n\n\taddTemplate(virtualInputPath, content, data) {\n\t\t// Lookups keys must be normalized\n\t\tvirtualInputPath = TemplatePath.stripLeadingDotSlash(\n\t\t\tTemplatePath.standardizeFilePath(virtualInputPath),\n\t\t);\n\t\tif (this.virtualTemplates[virtualInputPath]) {\n\t\t\tthrow new Error(\n\t\t\t\t\"Virtual template conflict: you can’t add multiple virtual templates that have the same inputPath: \" +\n\t\t\t\t\tvirtualInputPath,\n\t\t\t);\n\t\t}\n\n\t\tthis.virtualTemplates[virtualInputPath] = {\n\t\t\tinputPath: virtualInputPath,\n\t\t\tdata,\n\t\t\tcontent,\n\t\t};\n\t}\n\n\tisVirtualTemplate(virtualInputPath) {\n\t\treturn Boolean(this.virtualTemplates[virtualInputPath]);\n\t}\n\n\t#setDirectory(key, dir) {\n\t\tif (this.isPluginExecution()) {\n\t\t\tthrow new Error(\n\t\t\t\t\"The `set*Directory` configuration API methods are not yet allowed in plugins.\",\n\t\t\t);\n\t\t}\n\t\tthis.directoryAssignments[key] = dir;\n\t}\n\n\tsetInputDirectory(dir) {\n\t\tthis.#setDirectory(\"input\", dir);\n\t}\n\n\tsetOutputDirectory(dir) {\n\t\tthis.#setDirectory(\"output\", dir);\n\t}\n\n\tsetDataDirectory(dir) {\n\t\tthis.#setDirectory(\"data\", dir);\n\t}\n\n\tsetIncludesDirectory(dir) {\n\t\tthis.#setDirectory(\"includes\", dir);\n\t}\n\n\tsetLayoutsDirectory(dir) {\n\t\tthis.#setDirectory(\"layouts\", dir);\n\t}\n\n\t// Some data keywords in Eleventy are reserved, throw an error if an application tries to set these.\n\tsetFreezeReservedData(bool) {\n\t\tthis.freezeReservedData = !!bool;\n\t}\n\n\taddDateParsing(callback) {\n\t\tif (typeof callback === \"function\") {\n\t\t\tthis.customDateParsingCallbacks.add(callback);\n\t\t} else {\n\t\t\tthrow new Error(\"addDateParsing expects a function argument.\");\n\t\t}\n\t}\n\n\t// 3.0.0-alpha.18 started merging conflicts here (when possible), issue #3389\n\taddGlobalData(name, data) {\n\t\tname = this.getNamespacedName(name);\n\t\tif (this.globalData[name]) {\n\t\t\tif (isPlainObject(this.globalData[name]) && isPlainObject(data)) {\n\t\t\t\tDeepCopy(this.globalData[name], data);\n\t\t\t} else {\n\t\t\t\tdebug(\"Warning: overwriting a previous value set with addGlobalData(%o)\", name);\n\t\t\t\tthis.globalData[name] = data;\n\t\t\t}\n\t\t} else {\n\t\t\tthis.globalData[name] = data;\n\t\t}\n\t\treturn this;\n\t}\n\n\taddNunjucksGlobal(name, globalType) {\n\t\tname = this.getNamespacedName(name);\n\n\t\tif (this.nunjucks.globals[name]) {\n\t\t\tdebug(\n\t\t\t\tchalk.yellow(\"Warning, overwriting a Nunjucks global with `addNunjucksGlobal(%o)`\"),\n\t\t\t\tname,\n\t\t\t);\n\t\t}\n\n\t\tif (typeof globalType === \"function\") {\n\t\t\tthis.nunjucks.globals[name] = this.#decorateCallback(`\"${name}\" Nunjucks Global`, globalType);\n\t\t} else {\n\t\t\tthis.nunjucks.globals[name] = globalType;\n\t\t}\n\t}\n\n\taddTransform(name, callback) {\n\t\tname = this.getNamespacedName(name);\n\n\t\tthis.transforms[name] = this.#decorateCallback(`\"${name}\" Transform`, callback);\n\t}\n\n\taddPreprocessor(name, fileExtensions, callback) {\n\t\tname = this.getNamespacedName(name);\n\n\t\tthis.preprocessors[name] = {\n\t\t\tfilter: fileExtensions,\n\t\t\tcallback: this.#decorateCallback(`\"${name}\" Preprocessor`, callback),\n\t\t};\n\t}\n\n\taddLinter(name, callback) {\n\t\tname = this.getNamespacedName(name);\n\n\t\tthis.linters[name] = this.#decorateCallback(`\"${name}\" Linter`, callback);\n\t}\n\n\taddLayoutAlias(from, to) {\n\t\tthis.layoutAliases[from] = to;\n\t}\n\n\tsetLayoutResolution(resolution) {\n\t\tthis.layoutResolution = !!resolution;\n\t}\n\n\t// compat\n\tenableLayoutResolution() {\n\t\tthis.layoutResolution = true;\n\t}\n\n\tconfigureErrorReporting(options = {}) {\n\t\t// allowMissingExtensions: true\n\t\tObject.assign(this.errorReporting, options);\n\t}\n\n\t/*\n\t * Collections\n\t */\n\n\t// get config defined collections\n\tgetCollections() {\n\t\treturn this.collections;\n\t}\n\n\taddCollection(name, callback) {\n\t\tname = this.getNamespacedName(name);\n\n\t\tif (this.collections[name]) {\n\t\t\tthrow new UserConfigError(\n\t\t\t\t`config.addCollection(${name}) already exists. Try a different name for your collection.`,\n\t\t\t);\n\t\t}\n\n\t\tthis.collections[name] = callback;\n\t}\n\n\taugmentFunctionContext(fn, options) {\n\t\tlet t = typeof fn;\n\t\tif (t !== \"function\") {\n\t\t\tthrow new UserConfigError(\n\t\t\t\t\"Invalid type passed to `augmentFunctionContext`—function was expected and received: \" + t,\n\t\t\t);\n\t\t}\n\n\t\treturn augmentFunction(fn, options);\n\t}\n\n\tsetConcurrency(number) {\n\t\tif (typeof number !== \"number\") {\n\t\t\tthrow new UserConfigError(\"Argument passed to `setConcurrency` must be a number.\");\n\t\t}\n\n\t\tthis.#concurrency = number;\n\t}\n\n\tgetConcurrency() {\n\t\treturn this.#concurrency;\n\t}\n\n\tgetMergingConfigObject() {\n\t\tlet obj = {\n\t\t\t// filters removed in 1.0 (use addTransform instead)\n\t\t\ttransforms: this.transforms,\n\t\t\tlinters: this.linters,\n\t\t\tpreprocessors: this.preprocessors,\n\t\t\tglobalData: this.globalData,\n\t\t\tlayoutAliases: this.layoutAliases,\n\t\t\tlayoutResolution: this.layoutResolution,\n\t\t\tpassthroughCopiesHtmlRelative: this.passthroughCopiesHtmlRelative,\n\t\t\tpassthroughCopies: this.passthroughCopies,\n\n\t\t\t// Liquid\n\t\t\tliquidOptions: this.liquid.options,\n\t\t\tliquidTags: this.liquid.tags,\n\t\t\tliquidFilters: this.liquid.filters,\n\t\t\tliquidShortcodes: this.liquid.shortcodes,\n\t\t\tliquidPairedShortcodes: this.liquid.pairedShortcodes,\n\t\t\tliquidParameterParsing: this.liquid.parameterParsing,\n\n\t\t\t// Nunjucks\n\t\t\tnunjucksEnvironmentOptions: this.nunjucks.environmentOptions,\n\t\t\tnunjucksLoaders: this.nunjucks.loaders,\n\t\t\tnunjucksPrecompiledTemplates: this.nunjucks.precompiledTemplates,\n\t\t\tnunjucksFilters: this.nunjucks.filters,\n\t\t\tnunjucksAsyncFilters: this.nunjucks.asyncFilters,\n\t\t\tnunjucksTags: this.nunjucks.tags,\n\t\t\tnunjucksGlobals: this.nunjucks.globals,\n\t\t\tnunjucksAsyncShortcodes: this.nunjucks.asyncShortcodes,\n\t\t\tnunjucksShortcodes: this.nunjucks.shortcodes,\n\t\t\tnunjucksAsyncPairedShortcodes: this.nunjucks.asyncPairedShortcodes,\n\t\t\tnunjucksPairedShortcodes: this.nunjucks.pairedShortcodes,\n\n\t\t\t// 11ty.js\n\t\t\tjavascriptFunctions: this.javascript.functions, // filters and shortcodes, combined\n\t\t\tjavascriptShortcodes: this.javascript.shortcodes,\n\t\t\tjavascriptPairedShortcodes: this.javascript.pairedShortcodes,\n\t\t\tjavascriptFilters: this.javascript.filters,\n\n\t\t\t// Markdown\n\t\t\tmarkdownHighlighter: this.markdownHighlighter,\n\n\t\t\tlibraryOverrides: this.libraryOverrides,\n\t\t\tdynamicPermalinks: this.dynamicPermalinks,\n\t\t\tuseGitIgnore: this.useGitIgnore,\n\t\t\tignores: this.ignores,\n\t\t\twatchIgnores: this.watchIgnores,\n\t\t\twatchJavaScriptDependencies: this.watchJavaScriptDependencies,\n\t\t\tadditionalWatchTargets: this.additionalWatchTargets,\n\t\t\twatchTargetsConfigReset: this.watchTargetsConfigReset,\n\t\t\tserverOptions: this.serverOptions,\n\t\t\tchokidarConfig: this.chokidarConfig,\n\t\t\twatchThrottleWaitTime: this.watchThrottleWaitTime,\n\t\t\tfrontMatterParsingOptions: this.frontMatterParsingOptions,\n\t\t\tdataExtensions: this.dataExtensions,\n\t\t\textensionMap: this.extensionMap,\n\t\t\textensionMapClasses: this.extensionMapClasses,\n\t\t\tquietMode: this.quietMode,\n\t\t\tevents: this.events,\n\t\t\tbenchmarkManager: this.benchmarkManager,\n\t\t\tplugins: this.plugins,\n\t\t\tuseTemplateCache: this.useTemplateCache,\n\t\t\tprecompiledCollections: this.precompiledCollections,\n\t\t\tdataFilterSelectors: this.dataFilterSelectors,\n\t\t\tlibraryAmendments: this.libraryAmendments,\n\t\t\tserverPassthroughCopyBehavior: this.serverPassthroughCopyBehavior,\n\t\t\turlTransforms: this.urlTransforms,\n\t\t\tvirtualTemplates: this.virtualTemplates,\n\t\t\t// `directories` and `directoryAssignments` are merged manually prior to plugin processing\n\t\t\tfreezeReservedData: this.freezeReservedData,\n\t\t\tcustomDateParsing: this.customDateParsingCallbacks,\n\t\t\terrorReporting: this.errorReporting,\n\t\t};\n\n\t\tif (Array.isArray(this.dataFileSuffixesOverride)) {\n\t\t\t// no upstream merging of this array, so we add the override: prefix\n\t\t\tobj[\"override:dataFileSuffixes\"] = this.dataFileSuffixesOverride;\n\t\t}\n\n\t\tif (this.dataFileDirBaseNameOverride) {\n\t\t\tobj.dataFileDirBaseNameOverride = this.dataFileDirBaseNameOverride;\n\t\t}\n\n\t\t// htmlTemplateEngine and markdownTemplateEngine are merged manually in TemplateConfig for config() ordering\n\n\t\treturn obj;\n\t}\n\n\t// Removed features\n\tget DateTime() {\n\t\tthrow new Error(\n\t\t\t'Luxon’s DateTime property in configuration was removed in Eleventy v4. Please `import { DateTime } from \"luxon\"` directly.',\n\t\t);\n\t}\n\n\t_normalizeTemplateFormats() {\n\t\tthrow new Error(\"The internal _normalizeTemplateFormats() method was removed in Eleventy v3\");\n\t}\n\n\tsetBrowserSyncConfig() {\n\t\tthis._attemptedBrowserSyncUse = true;\n\t\tdebug(\n\t\t\t\"The `setBrowserSyncConfig` method was removed in Eleventy v2. Use `setServerOptions` with the new Eleventy development server or the `@11ty/eleventy-browser-sync` plugin moving forward.\",\n\t\t);\n\t}\n\n\tconfigureTemplateHandling(options = {}) {\n\t\t// Was used for sync/async swapping on file write operations\n\t\tthrow new Error(\"Internal configuration API method `configureTemplateHandling` was removed.\");\n\t}\n\n\tsetDataDeepMerge(deepMerge) {\n\t\tif (Boolean(deepMerge) === false) {\n\t\t\tthrow new Error(\n\t\t\t\t\"The `setDataDeepMerge(false)` Configuration API feature was removed in Eleventy v4. Read more at https://github.com/11ty/eleventy/issues/3937\",\n\t\t\t);\n\t\t}\n\t}\n\n\t// No-op functions for backwards compat\n\taddHandlebarsHelper() {}\n\tsetPugOptions() {}\n\tsetEjsOptions() {}\n\taddHandlebarsShortcode() {}\n\taddPairedHandlebarsShortcode() {}\n\n\t// Used by the Upgrade Helper Plugin v1 (no longer relevant)\n\t// https://github.com/11ty/eleventy-upgrade-help/blob/v1.x/src/data-deep-merge.js#L5-L9\n\tisDataDeepMergeModified() {}\n}\n\nexport default UserConfig;\n"
  },
  {
    "path": "src/Util/ArrayUtil.js",
    "content": "export function arrayDelete(arr, match) {\n\tif (!Array.isArray(arr)) {\n\t\treturn [];\n\t}\n\n\tif (!match) {\n\t\treturn arr;\n\t}\n\n\t// only mutates if found\n\tif (typeof match === \"function\") {\n\t\tif (arr.find(match)) {\n\t\t\treturn arr.filter((entry) => {\n\t\t\t\treturn !match(entry);\n\t\t\t});\n\t\t}\n\t} else if (arr.includes(match)) {\n\t\treturn arr.filter((entry) => {\n\t\t\treturn entry !== match;\n\t\t});\n\t}\n\n\treturn arr;\n}\n"
  },
  {
    "path": "src/Util/AsyncEventEmitter.js",
    "content": "import { EventEmitter } from \"node:events\";\n\n/**\n * This class emits events asynchronously.\n *\n * Note that Eleventy has two separate event emitter instances it uses:\n * 1. a userland one (UserConfig.js)\n * 2. a global one for internals (EventBus.js)\n */\nclass AsyncEventEmitter extends EventEmitter {\n\t#handlerMode = \"parallel\";\n\n\t// TypeScript slop\n\tconstructor(...args) {\n\t\tsuper(...args);\n\t}\n\n\treset() {\n\t\t// `eleventy#` event type listeners are removed at the start of each build (singletons)\n\t\tfor (let type of this.eventNames()) {\n\t\t\tif (typeof type === \"string\" && type.startsWith(\"eleventy#\")) {\n\t\t\t\tthis.removeAllListeners(type);\n\t\t\t}\n\t\t}\n\n\t}\n\n\t/**\n\t * @param {string} type - The event name to emit.\n\t * @param {...*} args - Additional arguments that get passed to listeners.\n\t * @returns {Promise} - Promise resolves once all listeners were invoked\n\t */\n\t/** @ts-expect-error */\n\tasync emit(type, ...args) {\n\t\tlet listeners = this.listeners(type);\n\t\tif (listeners.length === 0) {\n\t\t\treturn [];\n\t\t}\n\n\t\tif (this.#handlerMode == \"sequential\") {\n\t\t\tconst result = [];\n\t\t\tfor (const listener of listeners) {\n\t\t\t\tconst returnValue = await listener.apply(this, args);\n\t\t\t\tresult.push(returnValue);\n\t\t\t}\n\t\t\treturn result;\n\t\t} else {\n\t\t\treturn Promise.all(\n\t\t\t\tlisteners.map((listener) => {\n\t\t\t\t\treturn listener.apply(this, args);\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * @param {string} type - The event name to emit.\n\t * @param {...*} args - Additional lazy-executed function arguments that get passed to listeners.\n\t * @returns {Promise} - Promise resolves once all listeners were invoked\n\t */\n\tasync emitLazy(type, ...args) {\n\t\tlet listeners = this.listeners(type);\n\t\tif (listeners.length === 0) {\n\t\t\treturn [];\n\t\t}\n\n\t\tlet argsMap = [];\n\t\tfor (let arg of args) {\n\t\t\tif (typeof arg === \"function\") {\n\t\t\t\tlet r = arg();\n\t\t\t\tif (r instanceof Promise) {\n\t\t\t\t\tr = await r;\n\t\t\t\t}\n\t\t\t\targsMap.push(r);\n\t\t\t} else {\n\t\t\t\targsMap.push(arg);\n\t\t\t}\n\t\t}\n\n\t\treturn this.emit.call(this, type, ...argsMap);\n\t}\n\n\tsetHandlerMode(mode) {\n\t\tthis.#handlerMode = mode;\n\t}\n}\n\nexport default AsyncEventEmitter;\n"
  },
  {
    "path": "src/Util/Compatibility.js",
    "content": "import { satisfies } from \"../Adapters/Packages/semver.js\";\nimport { getEleventyPackageJson, getWorkingProjectPackageJson } from \"./ImportJsonSync.js\";\n\nconst pkg = getEleventyPackageJson();\n\n// Used in user config versionCheck method.\nclass Compatibility {\n\tstatic NORMALIZE_PRERELEASE_REGEX = /-canary\\b/g;\n\n\tstatic #projectPackageJson;\n\n\tconstructor(compatibleRange) {\n\t\tthis.compatibleRange = Compatibility.getCompatibilityValue(compatibleRange);\n\t}\n\n\tstatic get projectPackageJson() {\n\t\tif (!this.#projectPackageJson) {\n\t\t\tthis.#projectPackageJson = getWorkingProjectPackageJson();\n\t\t}\n\n\t\treturn this.#projectPackageJson;\n\t}\n\n\tstatic normalizeIdentifier(identifier) {\n\t\treturn identifier.replace(Compatibility.NORMALIZE_PRERELEASE_REGEX, \"-alpha\");\n\t}\n\n\tstatic getCompatibilityValue(compatibleRange) {\n\t\tif (compatibleRange) {\n\t\t\treturn compatibleRange;\n\t\t}\n\n\t\t// fetch from project’s package.json\n\t\tif (this.projectPackageJson?.[\"11ty\"]?.compatibility) {\n\t\t\treturn this.projectPackageJson[\"11ty\"].compatibility;\n\t\t}\n\t}\n\n\tisCompatible() {\n\t\treturn Compatibility.satisfies(pkg.version, this.compatibleRange);\n\t}\n\n\tstatic satisfies(version, compatibleRange) {\n\t\treturn satisfies(\n\t\t\tCompatibility.normalizeIdentifier(version),\n\t\t\tCompatibility.normalizeIdentifier(compatibleRange),\n\t\t\t{\n\t\t\t\tincludePrerelease: true,\n\t\t\t},\n\t\t);\n\t}\n\n\tgetErrorMessage() {\n\t\treturn `We found Eleventy version '${pkg.version}' which does not meet the required version range: '${this.compatibleRange}'. Use \\`npm install @11ty/eleventy\\` to upgrade your local project to the latest Eleventy version (or \\`npm install @11ty/eleventy -g\\` to upgrade the globally installed version).`;\n\t}\n}\n\nexport default Compatibility;\n"
  },
  {
    "path": "src/Util/ConsoleLogger.js",
    "content": "import debugUtil from \"debug\";\nimport chalk from \"../Adapters/Packages/chalk.js\";\n\nconst debug = debugUtil(\"Eleventy:Logger\");\n\n/**\n * Logger implementation that logs to STDOUT.\n * @typedef {'error'|'log'|'warn'|'info'} LogType\n */\nclass ConsoleLogger {\n\t/** @type {boolean} */\n\t#isVerbose = true;\n\t/** @type {boolean} */\n\t#isChalkEnabled = true;\n\t/** @type {object|boolean|undefined} */\n\t#logger;\n\n\tconstructor() {}\n\n\tisLoggingEnabled() {\n\t\tif (!this.isVerbose || process.env.DEBUG) {\n\t\t\treturn true;\n\t\t}\n\t\treturn this.#logger !== false;\n\t}\n\n\tget isVerbose() {\n\t\treturn this.#isVerbose;\n\t}\n\n\tset isVerbose(verbose) {\n\t\tthis.#isVerbose = !!verbose;\n\t}\n\n\tget isChalkEnabled() {\n\t\treturn this.#isChalkEnabled;\n\t}\n\n\tset isChalkEnabled(enabled) {\n\t\tthis.#isChalkEnabled = !!enabled;\n\t}\n\n\toverrideLogger(logger) {\n\t\tthis.#logger = logger;\n\t}\n\n\tget logger() {\n\t\treturn this.#logger || console;\n\t}\n\n\t/** @param {string} msg */\n\tlog(msg) {\n\t\tthis.message(msg);\n\t}\n\n\t/**\n\t * @typedef LogOptions\n\t * @property {string} message\n\t * @property {string=} prefix\n\t * @property {LogType=} type\n\t * @property {string=} color\n\t * @property {boolean=} force\n\t * @param {LogOptions} options\n\t */\n\tlogWithOptions({ message, type, prefix, color, force }) {\n\t\tthis.message(message, type, color, force, prefix);\n\t}\n\n\t/** @param {string} msg */\n\tforceLog(msg) {\n\t\tthis.message(msg, undefined, undefined, true);\n\t}\n\n\t/** @param {string} msg */\n\tinfo(msg) {\n\t\tthis.message(msg, \"log\", \"blue\");\n\t}\n\n\t/** @param {string} msg */\n\twarn(msg) {\n\t\tthis.message(msg, \"warn\", \"yellow\");\n\t}\n\n\t/** @param {string} msg */\n\terror(msg) {\n\t\tthis.message(msg, \"error\", \"red\");\n\t}\n\n\t/**\n\t * Formats the message to log.\n\t *\n\t * @param {string} message - The raw message to log.\n\t * @param {LogType} [type='log'] - The error level to log.\n\t * @param {string|undefined} [chalkColor=undefined] - Color name or falsy to disable\n\t * @param {boolean} [forceToConsole=false] - Enforce a log on console instead of specified target.\n\t */\n\tmessage(\n\t\tmessage,\n\t\ttype = \"log\",\n\t\tchalkColor = undefined,\n\t\tforceToConsole = false,\n\t\tprefix = \"[11ty]\",\n\t) {\n\t\tif (!forceToConsole && (!this.isVerbose || process.env.DEBUG)) {\n\t\t\tdebug(message);\n\t\t} else if (this.#logger !== false) {\n\t\t\tmessage = `${chalk.gray(prefix)} ${message.split(\"\\n\").join(`\\n${chalk.gray(prefix)} `)}`;\n\n\t\t\tif (chalkColor && this.isChalkEnabled) {\n\t\t\t\tthis.logger[type](chalk[chalkColor](message));\n\t\t\t} else {\n\t\t\t\tthis.logger[type](message);\n\t\t\t}\n\t\t}\n\t}\n}\n\nexport default ConsoleLogger;\n"
  },
  {
    "path": "src/Util/DateParse.js",
    "content": "import debugUtil from \"debug\";\nimport { IsoDate } from \"@11ty/parse-date-strings\";\n\nconst debug = debugUtil(\"Eleventy:DateTime\");\n\nexport function fromISOtoDateUTC(dateValue, inputPath) {\n\t// This has had a UTC default since the beginnning:\n\t// https://github.com/11ty/eleventy/commit/4272311dab203d2b217ebd4f6b597eb0e816006b\n\ttry {\n\t\tlet date = IsoDate.parse(dateValue);\n\t\tdebug(\"@11ty/parse-date-strings parsed %o as %o\", dateValue, date);\n\n\t\treturn date;\n\t} catch (e) {\n\t\tthrow new Error(\n\t\t\t`Data cascade value for \\`date\\` (${dateValue}) is invalid${inputPath ? ` for ${inputPath}` : \"\"}`,\n\t\t\t{ cause: e },\n\t\t);\n\t}\n}\n"
  },
  {
    "path": "src/Util/DirContains.js",
    "content": "import path from \"node:path\";\n\n// Returns true if subfolder is in parent (accepts absolute or relative paths for both)\nexport default function (parentFolder, subFolder) {\n\t// path.resolve returns an absolute path\n\tif (path.resolve(subFolder).startsWith(path.resolve(parentFolder))) {\n\t\treturn true;\n\t}\n\treturn false;\n}\n"
  },
  {
    "path": "src/Util/EsmResolver.js",
    "content": "import debugUtil from \"debug\";\nimport { fileURLToPath } from \"../Adapters/Packages/url.js\";\nimport PathNormalizer from \"./PathNormalizer.js\";\n\nconst debug = debugUtil(\"Eleventy:EsmResolver\");\n\nlet lastModifiedPaths = new Map();\nexport async function initialize({ port }) {\n\t// From `eleventy.importCacheReset` event in Require.js\n\tport.on(\"message\", ({ path, newDate }) => {\n\t\tlastModifiedPaths.set(path, newDate);\n\t});\n}\n\n// Fixes issue https://github.com/11ty/eleventy/issues/3270\n// Docs: https://nodejs.org/docs/latest/api/module.html#resolvespecifier-context-nextresolve\nexport async function resolve(specifier, context, nextResolve) {\n\ttry {\n\t\t// Not a relative import and not a file import\n\t\t// Or from node_modules (perhaps better to check if the specifier is in the project directory instead)\n\t\tif (\n\t\t\t(!specifier.startsWith(\"../\") &&\n\t\t\t\t!specifier.startsWith(\"./\") &&\n\t\t\t\t!specifier.startsWith(\"file:\")) ||\n\t\t\tcontext.parentURL.includes(\"/node_modules/\")\n\t\t) {\n\t\t\treturn nextResolve(specifier);\n\t\t}\n\n\t\tlet fileUrl = new URL(specifier, context.parentURL);\n\t\tif (fileUrl.searchParams.has(\"_cache_bust\")) {\n\t\t\t// already is cache busted outside resolver (wider compat, url was changed prior to import, probably in Require.js)\n\t\t\treturn nextResolve(specifier);\n\t\t}\n\n\t\tlet absolutePath = PathNormalizer.normalizeSeperator(fileURLToPath(fileUrl));\n\t\t// Bust the import cache if this is a recently modified file\n\t\tif (lastModifiedPaths.has(absolutePath)) {\n\t\t\tfileUrl.search = \"\"; // delete existing searchparams\n\t\t\tfileUrl.searchParams.set(\"_cache_bust\", lastModifiedPaths.get(absolutePath));\n\t\t\tdebug(\"Cache busting %o to %o\", specifier, fileUrl.toString());\n\n\t\t\treturn nextResolve(fileUrl.toString());\n\t\t}\n\t} catch (e) {\n\t\tdebug(\"EsmResolver Error parsing specifier (%o): %o\", specifier, e);\n\t}\n\n\treturn nextResolve(specifier);\n}\n\n// export async function load(url, context, nextLoad) {\n// }\n"
  },
  {
    "path": "src/Util/EsmResolverPortAdapter.core.js",
    "content": "// This is optional (and featured tested upstream)\nexport const port1 = undefined;\n"
  },
  {
    "path": "src/Util/EsmResolverPortAdapter.js",
    "content": "import module from \"node:module\";\nimport { MessageChannel } from \"node:worker_threads\";\n\nconst { port1, port2 } = new MessageChannel();\n\n// ESM Cache Buster is an enhancement that works in Node 18.19+\n// https://nodejs.org/docs/latest/api/module.html#moduleregisterspecifier-parenturl-options\n// Fixes https://github.com/11ty/eleventy/issues/3270\n// ENV variable for https://github.com/11ty/eleventy/issues/3371\nif (\"register\" in module && !process?.env?.ELEVENTY_SKIP_ESM_RESOLVER) {\n\tmodule.register(\"./EsmResolver.js\", import.meta.url, {\n\t\tparentURL: import.meta.url,\n\t\tdata: {\n\t\t\tport: port2,\n\t\t},\n\t\ttransferList: [port2],\n\t});\n}\n\nexport { port1 };\n"
  },
  {
    "path": "src/Util/EventBusUtil.js",
    "content": "import eventBus from \"../EventBus.js\";\nimport debugUtil from \"debug\";\n\nconst debug = debugUtil(\"Eleventy:EventBus\");\n\nclass EventBusUtil {\n\tstatic debugCurrentListenerCounts() {\n\t\tfor (let name of eventBus.eventNames()) {\n\t\t\tdebug(\"Listeners for %o: %o\", name, eventBus.listenerCount(name));\n\t\t}\n\t}\n}\n\nexport default EventBusUtil;\n"
  },
  {
    "path": "src/Util/ExistsCache.js",
    "content": "import { existsSync, statSync } from \"node:fs\";\nimport { TemplatePath } from \"@11ty/eleventy-utils\";\n\n// Checks both files and directories\nclass ExistsCache {\n\t#exists = new Map();\n\t#dirs = new Map();\n\n\tconstructor() {\n\t\tthis.lookupCount = 0;\n\t}\n\n\treset() {\n\t\tthis.#exists = new Map();\n\t\tthis.#dirs = new Map();\n\t}\n\n\tget size() {\n\t\treturn this.#exists.size;\n\t}\n\n\thas(path) {\n\t\treturn this.#exists.has(path);\n\t}\n\n\tset(path, isExist) {\n\t\tthis.#exists.set(TemplatePath.addLeadingDotSlash(path), Boolean(isExist));\n\t}\n\n\t// Not yet needed\n\t// setDirectory(path, isExist) {}\n\n\t// Relative paths (to root directory) expected (but not enforced due to perf costs)\n\texists(path) {\n\t\tif (!this.#exists.has(path)) {\n\t\t\tlet exists = existsSync(path);\n\t\t\tthis.lookupCount++;\n\n\t\t\t// mark for next time\n\t\t\tthis.#exists.set(path, Boolean(exists));\n\n\t\t\treturn exists;\n\t\t}\n\n\t\treturn this.#exists.get(path);\n\t}\n\n\tisDirectory(path) {\n\t\tif (!this.exists(path)) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif (!this.#dirs.has(path)) {\n\t\t\tlet isDir = statSync(path).isDirectory();\n\t\t\tthis.lookupCount++;\n\n\t\t\t// mark for next time\n\t\t\tthis.#dirs.set(path, isDir);\n\n\t\t\treturn isDir;\n\t\t}\n\n\t\treturn this.#dirs.get(path);\n\t}\n}\n\nexport default ExistsCache;\n"
  },
  {
    "path": "src/Util/FeatureTests.cjs",
    "content": "function canRequireTypeScript() {\n\ttry {\n\t\tlet res = require(\"./TypeScript/TypeScriptSample.cts\");\n\t\treturn typeof res === \"function\";\n\t} catch(e) {\n\t\t// Not supported in node_modules, but we know it is supported!\n\t\tif(e.code === \"ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING\") {\n      return true;\n    }\n\t\treturn false;\n\t}\n}\n\nconst TYPESCRIPT_ENABLED = canRequireTypeScript();\n\nmodule.exports.isTypeScriptSupported = function() {\n\treturn TYPESCRIPT_ENABLED;\n}\n"
  },
  {
    "path": "src/Util/FeatureTests.core.cjs",
    "content": "module.exports.isTypeScriptSupported = function() {\n\treturn false;\n}\n"
  },
  {
    "path": "src/Util/FilePathUtil.js",
    "content": "class FilePathUtil {\n\tstatic isMatchingExtension(filepath, fileExtension) {\n\t\tif (!fileExtension) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif (!(fileExtension || \"\").startsWith(\".\")) {\n\t\t\tfileExtension = \".\" + fileExtension;\n\t\t}\n\n\t\treturn filepath.endsWith(fileExtension);\n\t}\n\n\tstatic getFileExtension(filepath) {\n\t\treturn (filepath || \"\").split(\".\").pop();\n\t}\n}\n\nexport { FilePathUtil };\n"
  },
  {
    "path": "src/Util/FileSize.js",
    "content": "export function readableFileSize(bytes) {\n\t// Uses kilobytes and not kibibytes\n\tlet entries = [\n\t\t[1e6, \"mB\"],\n\t\t[1e3, \"kB\"],\n\t];\n\tfor (let [compare, suffix] of entries) {\n\t\tif (Math.abs(bytes) >= compare) {\n\t\t\treturn Math.round(bytes / compare) + suffix;\n\t\t}\n\t}\n\treturn bytes + \" bytes\";\n}\n"
  },
  {
    "path": "src/Util/FileSystemManager.js",
    "content": "import path from \"node:path\";\nimport { mkdirSync, writeFileSync } from \"node:fs\";\n\nclass FileSystemManager {\n\tconstructor(templateConfig) {\n\t\tif (!templateConfig || templateConfig.constructor.name !== \"TemplateConfig\") {\n\t\t\tthrow new Error(\n\t\t\t\t\"Internal error: Missing `templateConfig` or was not an instance of `TemplateConfig`.\",\n\t\t\t);\n\t\t}\n\t\tthis.templateConfig = templateConfig;\n\t}\n\n\texists(pathname) {\n\t\treturn this.templateConfig.existsCache.exists(pathname);\n\t}\n\n\tcreateDirectoryForFileSync(filePath) {\n\t\tlet dir = path.parse(filePath).dir;\n\t\tif (!dir || this.exists(dir)) {\n\t\t\treturn;\n\t\t}\n\n\t\tmkdirSync(dir, { recursive: true });\n\t}\n\n\twriteFileSync(filePath, content) {\n\t\t// Note: This deliberately uses the synchronous version to avoid\n\t\t// unbounded concurrency: https://github.com/11ty/eleventy/issues/3271\n\t\twriteFileSync(filePath, content);\n\t}\n}\n\nexport { FileSystemManager };\n"
  },
  {
    "path": "src/Util/GetJavaScriptData.js",
    "content": "import EleventyBaseError from \"../Errors/EleventyBaseError.js\";\n\nclass JavaScriptInvalidDataFormatError extends EleventyBaseError {}\n\nexport default async function (inst, inputPath, key = \"data\", options = {}) {\n\tlet { mixins, isObjectRequired } = Object.assign(\n\t\t{\n\t\t\tmixins: {},\n\t\t\tisObjectRequired: true,\n\t\t},\n\t\toptions,\n\t);\n\n\tif (inst && key in inst) {\n\t\t// get extra data from `data` method,\n\t\t// either as a function or getter or object literal\n\t\tlet result = await (typeof inst[key] === \"function\"\n\t\t\t? Object.keys(mixins).length > 0\n\t\t\t\t? inst[key].call(mixins)\n\t\t\t\t: inst[key]()\n\t\t\t: inst[key]);\n\n\t\tif (isObjectRequired && typeof result !== \"object\") {\n\t\t\tthrow new JavaScriptInvalidDataFormatError(\n\t\t\t\t`Invalid data format returned from ${inputPath}: typeof ${typeof result}`,\n\t\t\t);\n\t\t}\n\t\treturn result;\n\t}\n}\n"
  },
  {
    "path": "src/Util/Git.js",
    "content": "import { spawnAsync } from \"./spawn.js\";\nimport memoize from \"./MemoizeFunction.js\";\n\nconst getCreatedTimestamp = memoize(async function (filePath) {\n\ttry {\n\t\tlet timestamp = await spawnAsync(\n\t\t\t\"git\",\n\t\t\t// Formats https://www.git-scm.com/docs/git-log#_pretty_formats\n\t\t\t// %at author date, UNIX timestamp\n\t\t\t[\"log\", \"--diff-filter=A\", \"--follow\", \"-1\", \"--format=%at\", filePath],\n\t\t);\n\t\t// parseInt removes trailing \\n\n\t\treturn parseInt(timestamp, 10) * 1000;\n\t} catch (e) {\n\t\t// do nothing\n\t}\n});\n\nconst getUpdatedTimestamp = memoize(async function (filePath) {\n\ttry {\n\t\tlet timestamp = await spawnAsync(\n\t\t\t\"git\",\n\t\t\t// Formats https://www.git-scm.com/docs/git-log#_pretty_formats\n\t\t\t// %at author date, UNIX timestamp\n\t\t\t[\"log\", \"-1\", \"--format=%at\", filePath],\n\t\t);\n\t\treturn parseInt(timestamp, 10) * 1000;\n\t} catch (e) {\n\t\t// do nothing\n\t}\n});\n\nexport { getCreatedTimestamp, getUpdatedTimestamp };\n"
  },
  {
    "path": "src/Util/GlobMatcher.client.js",
    "content": "export function isGlobMatch() {\n\tthrow new Error(\n\t\t\"Glob matching (e.g. getFilteredByGlob collection API method) is not supported in the `@11ty/client` bundle. Use the `@11ty/client/eleventy` bundle instead.\",\n\t);\n}\n\n// When using a glob as an input (see ProjectDirectories)\nexport function isDynamicPattern(pattern) {\n\treturn false;\n}\n"
  },
  {
    "path": "src/Util/GlobMatcher.js",
    "content": "// picomatch costs ~50KB minified\nimport picomatch from \"picomatch\";\nimport { TemplatePath } from \"@11ty/eleventy-utils\";\n\nexport function isGlobMatch(filepath, globs = [], options = undefined) {\n\tif (!filepath || !Array.isArray(globs) || globs.length === 0) {\n\t\treturn false;\n\t}\n\n\tlet inputPath = TemplatePath.stripLeadingDotSlash(filepath);\n\tlet opts = Object.assign(\n\t\t{\n\t\t\tdot: true,\n\t\t\tnocase: true, // insensitive\n\t\t},\n\t\toptions,\n\t);\n\n\t// globs: string or array of strings\n\treturn picomatch.isMatch(inputPath, globs, opts);\n}\n\n// via tinyglobby\nexport function isDynamicPattern(pattern) {\n\tconst s = picomatch.scan(pattern);\n\treturn s.isGlob || s.negated;\n}\n"
  },
  {
    "path": "src/Util/GlobRemap.js",
    "content": "import path from \"node:path\";\nimport ProjectDirectories from \"./ProjectDirectories.js\";\nimport PathNormalizer from \"./PathNormalizer.js\";\n\n// even on Windows (in cmd.exe) these paths are normalized to forward slashes\n// tinyglobby expects forward slashes on Windows\nconst SEP = \"/\";\n\nclass GlobRemap {\n\tconstructor(paths = []) {\n\t\tthis.paths = paths;\n\t\tthis.cwd = GlobRemap.getCwd(paths);\n\t}\n\n\tgetCwd() {\n\t\treturn this.cwd;\n\t}\n\n\tgetRemapped(paths) {\n\t\treturn paths.map((entry) => GlobRemap.remapInput(entry, this.cwd));\n\t}\n\n\tgetInput() {\n\t\treturn this.getRemapped(this.paths);\n\t}\n\n\tgetOutput(paths = []) {\n\t\treturn paths.map((entry) => GlobRemap.remapOutput(entry, this.cwd));\n\t}\n\n\tstatic getParentDirPrefix(filePath = \"\") {\n\t\tlet count = [];\n\t\tfor (let p of filePath.split(SEP)) {\n\t\t\tif (p === \"..\") {\n\t\t\t\tcount.push(\"..\");\n\t\t\t} else {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif (count.length > 0) {\n\t\t\t// trailing slash\n\t\t\treturn count.join(SEP) + SEP;\n\t\t}\n\t\treturn \"\";\n\t}\n\n\tstatic getLongestParentDirPrefix(filePaths) {\n\t\tlet longest = \"\";\n\t\tfilePaths\n\t\t\t.map((entry) => {\n\t\t\t\treturn this.getParentDirPrefix(entry);\n\t\t\t})\n\t\t\t.filter((entry) => Boolean(entry))\n\t\t\t.forEach((prefix) => {\n\t\t\t\tif (!longest || prefix.length > longest.length) {\n\t\t\t\t\tlongest = prefix;\n\t\t\t\t}\n\t\t\t});\n\t\treturn longest;\n\t}\n\n\t// alias\n\tstatic getCwd(filePaths) {\n\t\treturn this.getLongestParentDirPrefix(filePaths);\n\t}\n\n\tstatic remapInput(entry, cwd) {\n\t\tif (cwd) {\n\t\t\tif (!entry.startsWith(\"**\" + SEP) && !entry.startsWith(`.git${SEP}**`)) {\n\t\t\t\treturn PathNormalizer.normalizeSeperator(ProjectDirectories.getRelativeTo(entry, cwd));\n\t\t\t}\n\t\t}\n\t\treturn entry;\n\t}\n\n\tstatic remapOutput(entry, cwd) {\n\t\tif (cwd) {\n\t\t\treturn PathNormalizer.normalizeSeperator(path.join(cwd, entry));\n\t\t}\n\t\treturn entry;\n\t}\n}\n\nexport default GlobRemap;\n"
  },
  {
    "path": "src/Util/GlobStripper.js",
    "content": "// import { TemplatePath } from \"@11ty/eleventy-utils\";\nimport { isDynamicPattern } from \"./GlobMatcher.js\";\n\nexport class GlobStripper {\n\tstatic SEP = \"/\";\n\tstatic DOUBLE = \"**\";\n\tstatic SINGLE = \"*\";\n\n\tstatic parse(pattern = \"\") {\n\t\tlet parts = pattern.split(this.SEP);\n\t\tlet c = 0;\n\t\tfor (let p of parts) {\n\t\t\tif (p === \"?\") {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (\n\t\t\t\tp === this.DOUBLE ||\n\t\t\t\t(p.includes(this.SINGLE) && !p.includes(this.DOUBLE)) ||\n\t\t\t\tisDynamicPattern(p)\n\t\t\t) {\n\t\t\t\treturn {\n\t\t\t\t\tpath: parts.slice(0, c).join(this.SEP) || \".\",\n\t\t\t\t\tglob: parts.slice(c).join(this.SEP) || undefined,\n\t\t\t\t};\n\t\t\t}\n\t\t\tc++;\n\t\t}\n\n\t\tif (isDynamicPattern(pattern)) {\n\t\t\tthrow new Error(\n\t\t\t\t`Could not automatically determine top-most folder from glob pattern: ${pattern}`,\n\t\t\t);\n\t\t}\n\n\t\treturn {\n\t\t\tpath: parts.join(this.SEP) || \".\",\n\t\t};\n\t}\n}\n"
  },
  {
    "path": "src/Util/HtmlRelativeCopy.js",
    "content": "import path from \"node:path\";\nimport { TemplatePath } from \"@11ty/eleventy-utils\";\nimport { isValidUrl } from \"./UrlUtil.js\";\nimport { isGlobMatch } from \"./GlobMatcher.js\";\n\n// https://github.com/11ty/eleventy/pull/3573\n\nclass HtmlRelativeCopy {\n\t#userConfig;\n\t#matchingGlobs = new Set();\n\t#matchingGlobsArray;\n\t#dirty = false;\n\t#paths = new Set();\n\t#failOnError = true;\n\t#copyOptions = {\n\t\tdot: false, // differs from standard passthrough copy\n\t};\n\n\tisEnabled() {\n\t\treturn this.#matchingGlobs.size > 0;\n\t}\n\n\tsetFailOnError(failOnError) {\n\t\tthis.#failOnError = Boolean(failOnError);\n\t}\n\n\tsetCopyOptions(opts) {\n\t\tif (opts) {\n\t\t\tObject.assign(this.#copyOptions, opts);\n\t\t}\n\t}\n\n\tsetUserConfig(userConfig) {\n\t\tif (!userConfig || userConfig.constructor.name !== \"UserConfig\") {\n\t\t\tthrow new Error(\n\t\t\t\t\"Internal error: Missing `userConfig` or was not an instance of `UserConfig`.\",\n\t\t\t);\n\t\t}\n\t\tthis.#userConfig = userConfig;\n\t}\n\n\taddPaths(paths = []) {\n\t\tfor (let path of paths) {\n\t\t\tthis.#paths.add(TemplatePath.getDir(path));\n\t\t}\n\t}\n\n\tget matchingGlobs() {\n\t\tif (this.#dirty || !this.#matchingGlobsArray) {\n\t\t\tthis.#matchingGlobsArray = Array.from(this.#matchingGlobs);\n\t\t\tthis.#dirty = false;\n\t\t}\n\n\t\treturn this.#matchingGlobsArray;\n\t}\n\n\taddMatchingGlob(glob) {\n\t\tif (glob) {\n\t\t\tif (Array.isArray(glob)) {\n\t\t\t\tfor (let g of glob) {\n\t\t\t\t\tthis.#matchingGlobs.add(g);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tthis.#matchingGlobs.add(glob);\n\t\t\t}\n\t\t\tthis.#dirty = true;\n\t\t}\n\t}\n\n\tisSkippableHref(rawRef) {\n\t\tif (\n\t\t\tthis.#matchingGlobs.size === 0 ||\n\t\t\t!rawRef ||\n\t\t\tpath.isAbsolute(rawRef) ||\n\t\t\tisValidUrl(rawRef)\n\t\t) {\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\n\tisCopyableTarget(target) {\n\t\tif (!isGlobMatch(target, this.matchingGlobs)) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn true;\n\t}\n\n\texists(filePath) {\n\t\treturn this.#userConfig.exists(filePath);\n\t}\n\n\tgetAliasedPath(ref) {\n\t\tfor (let dir of this.#paths) {\n\t\t\tlet found = TemplatePath.join(dir, ref);\n\t\t\tif (this.isCopyableTarget(found) && this.exists(found)) {\n\t\t\t\treturn found;\n\t\t\t}\n\t\t}\n\t}\n\n\tgetFilePathRelativeToProjectRoot(ref, contextFilePath) {\n\t\tlet dir = TemplatePath.getDirFromFilePath(contextFilePath);\n\t\treturn TemplatePath.join(dir, ref);\n\t}\n\n\tcopy(fileRef, tmplInputPath, tmplOutputPath) {\n\t\t// original ref is a full URL or no globs exist\n\t\tif (this.isSkippableHref(fileRef)) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Relative to source file’s input path\n\t\tlet source = this.getFilePathRelativeToProjectRoot(fileRef, tmplInputPath);\n\t\tif (!this.isCopyableTarget(source)) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (!this.exists(source)) {\n\t\t\t// Try to alias using `options.paths`\n\t\t\tlet alias = this.getAliasedPath(fileRef);\n\t\t\tif (!alias) {\n\t\t\t\tif (this.#failOnError) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\"Missing input file for `html-relative` Passthrough Copy file: \" +\n\t\t\t\t\t\t\tTemplatePath.absolutePath(source),\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\t// don’t fail on error\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tsource = alias;\n\t\t}\n\n\t\tlet target = this.getFilePathRelativeToProjectRoot(fileRef, tmplOutputPath);\n\n\t\t// We use a Set here to allow passthrough copy manager to properly error on conflicts upstream\n\t\t// Only errors when different inputs write to the same output\n\t\t// Also errors if attempts to write outside the output folder.\n\t\tthis.#userConfig.emit(\"eleventy#copy\", {\n\t\t\tsource,\n\t\t\ttarget,\n\t\t\toptions: this.#copyOptions,\n\t\t});\n\t}\n}\n\nexport { HtmlRelativeCopy };\n"
  },
  {
    "path": "src/Util/HtmlTransformer.js",
    "content": "import posthtml from \"posthtml\";\nimport urls from \"@11ty/posthtml-urls\";\nimport { FilePathUtil } from \"./FilePathUtil.js\";\n\nimport { arrayDelete } from \"./ArrayUtil.js\";\n\nexport class HtmlTransformer {\n\t// feature test for Eleventy Bundle Plugin\n\tstatic SUPPORTS_PLUGINS_ENABLED_CALLBACK = true;\n\n\tstatic TYPES = [\"callbacks\", \"plugins\"];\n\n\tconstructor() {\n\t\t// execution order is important (not order of addition/object key order)\n\t\tthis.callbacks = {};\n\t\tthis.posthtmlProcessOptions = {\n\t\t\trecognizeNoValueAttribute: true,\n\t\t\tclosingSingleTag: \"closeAs\",\n\t\t};\n\t\tthis.plugins = {};\n\t}\n\n\tget aggregateBench() {\n\t\tif (!this.userConfig) {\n\t\t\tthrow new Error(\"Internal error: Missing `userConfig` in HtmlTransformer.\");\n\t\t}\n\t\treturn this.userConfig.benchmarkManager.get(\"Aggregate\");\n\t}\n\n\tsetUserConfig(config) {\n\t\tthis.userConfig = config;\n\t}\n\n\tstatic prioritySort(a, b) {\n\t\tif (b.priority > a.priority) {\n\t\t\treturn 1;\n\t\t}\n\t\tif (a.priority > b.priority) {\n\t\t\treturn -1;\n\t\t}\n\t\treturn 0;\n\t}\n\n\t// context is important as it is used in html base plugin for page specific URL\n\tstatic _getPosthtmlInstance(callbacks = [], plugins = [], context = {}) {\n\t\tlet inst = posthtml();\n\n\t\t// already sorted by priority when added\n\t\tfor (let { fn: plugin, options } of plugins) {\n\t\t\tinst.use(plugin(Object.assign({}, context, options)));\n\t\t}\n\n\t\t// Run the built-ins last\n\t\tif (callbacks.length > 0) {\n\t\t\tinst.use(\n\t\t\t\turls({\n\t\t\t\t\teachURL: (url, attrName, tagName) => {\n\t\t\t\t\t\tfor (let { fn: callback } of callbacks) {\n\t\t\t\t\t\t\t// already sorted by priority when added\n\t\t\t\t\t\t\turl = callback.call(context, url, { attribute: attrName, tag: tagName });\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn url;\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\n\t\treturn inst;\n\t}\n\n\t_add(extensions, addType, value, options = {}) {\n\t\toptions = Object.assign(\n\t\t\t{\n\t\t\t\tpriority: 0,\n\t\t\t},\n\t\t\toptions,\n\t\t);\n\n\t\tlet extensionsArray = (extensions || \"\").split(\",\");\n\t\tfor (let ext of extensionsArray) {\n\t\t\tlet target = this[addType];\n\t\t\tif (!target[ext]) {\n\t\t\t\ttarget[ext] = [];\n\t\t\t}\n\n\t\t\ttarget[ext].push({\n\t\t\t\t// *could* fallback to function name, `value.name`\n\t\t\t\tname: options.name, // for `remove` and debugging\n\t\t\t\tfn: value, // callback or plugin\n\t\t\t\tpriority: options.priority, // sorted in descending order\n\t\t\t\tenabled: options.enabled || (() => true),\n\t\t\t\toptions: options.pluginOptions,\n\t\t\t});\n\n\t\t\ttarget[ext].sort(HtmlTransformer.prioritySort);\n\t\t}\n\t}\n\n\taddPosthtmlPlugin(extensions, plugin, options = {}) {\n\t\tthis._add(extensions, \"plugins\", plugin, options);\n\t}\n\n\t// match can be a plugin function or a filter callback(plugin => true);\n\tremove(extensions, match) {\n\t\tfor (let removeType of HtmlTransformer.TYPES) {\n\t\t\tfor (let ext of (extensions || \"\").split(\",\")) {\n\t\t\t\tthis[removeType][ext] = arrayDelete(this[removeType][ext], match);\n\t\t\t}\n\t\t}\n\t}\n\n\taddUrlTransform(extensions, callback, options = {}) {\n\t\tthis._add(extensions, \"callbacks\", callback, options);\n\t}\n\n\tsetPosthtmlProcessOptions(options) {\n\t\tObject.assign(this.posthtmlProcessOptions, options);\n\t}\n\n\tisTransformable(extension, context) {\n\t\treturn (\n\t\t\tthis.getCallbacks(extension, context).length > 0 || this.getPlugins(extension).length > 0\n\t\t);\n\t}\n\n\tgetCallbacks(extension, context) {\n\t\tlet callbacks = this.callbacks[extension] || [];\n\t\treturn callbacks.filter(({ enabled }) => {\n\t\t\tif (!enabled || typeof enabled !== \"function\") {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\treturn enabled(context);\n\t\t});\n\t}\n\n\tgetPlugins(extension) {\n\t\tlet plugins = this.plugins[extension] || [];\n\t\treturn plugins.filter(({ enabled }) => {\n\t\t\tif (!enabled || typeof enabled !== \"function\") {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\treturn enabled();\n\t\t});\n\t}\n\n\tstatic async transformStandalone(content, callback, posthtmlProcessOptions = {}) {\n\t\tlet posthtmlInstance = this._getPosthtmlInstance([\n\t\t\t{\n\t\t\t\tfn: callback,\n\t\t\t\tenabled: () => true,\n\t\t\t},\n\t\t]);\n\t\tlet result = await posthtmlInstance.process(content, posthtmlProcessOptions);\n\t\treturn result.html;\n\t}\n\n\tasync transformContent(outputPath, content, context) {\n\t\tlet extension = FilePathUtil.getFileExtension(outputPath);\n\t\tif (!this.isTransformable(extension, context)) {\n\t\t\treturn content;\n\t\t}\n\n\t\tlet bench = this.aggregateBench.get(`Transforming \\`${extension}\\` with posthtml`);\n\t\tbench.before();\n\t\tlet callbacks = this.getCallbacks(extension, context);\n\t\tlet plugins = this.getPlugins(extension);\n\t\tlet posthtmlInstance = HtmlTransformer._getPosthtmlInstance(callbacks, plugins, context);\n\t\tlet result = await posthtmlInstance.process(content, this.posthtmlProcessOptions);\n\t\tbench.after();\n\t\treturn result.html;\n\t}\n}\n"
  },
  {
    "path": "src/Util/ImportJsonSync.js",
    "content": "import { existsSync } from \"node:fs\";\nimport debugUtil from \"debug\";\nimport { TemplatePath } from \"@11ty/eleventy-utils\";\n\nimport { importJsonSync, eleventyPackageJson } from \"./RequireUtils.js\";\n\nconst debug = debugUtil(\"Eleventy:ImportJsonSync\");\n\nfunction findFilePathInParentDirs(dir, filename) {\n\t// `package.json` searches look in parent dirs:\n\t// https://docs.npmjs.com/cli/v7/configuring-npm/folders#more-information\n\t// Fixes issue #3178, limited to working dir paths only\n\tlet workingDir = TemplatePath.getWorkingDir();\n\t// TODO use DirContains\n\tlet allDirs = TemplatePath.getAllDirs(dir).filter((entry) => entry.startsWith(workingDir));\n\n\tfor (let dir of allDirs) {\n\t\tlet newPath = TemplatePath.join(dir, filename);\n\t\tif (existsSync(newPath)) {\n\t\t\tdebug(\"Found %o searching parent directories at: %o\", filename, dir);\n\t\t\treturn newPath;\n\t\t}\n\t}\n}\n\nfunction getEleventyPackageJson() {\n\treturn eleventyPackageJson;\n}\n\n// Used by EleventyServe.js for custom servers only\nfunction getModulePackageJson(dir) {\n\tlet filePath = findFilePathInParentDirs(TemplatePath.absolutePath(dir), \"package.json\");\n\n\t// Fails nicely\n\tif (!filePath) {\n\t\treturn {};\n\t}\n\n\treturn importJsonSync(filePath);\n}\n\n// This will *not* find a package.json in a parent directory above root\nfunction getWorkingProjectPackageJsonPath() {\n\tlet dir = TemplatePath.absolutePath(TemplatePath.getWorkingDir());\n\treturn findFilePathInParentDirs(dir, \"package.json\");\n}\n\nfunction getWorkingProjectPackageJson() {\n\tlet filePath = getWorkingProjectPackageJsonPath();\n\n\t// Fails nicely\n\tif (!filePath) {\n\t\treturn {};\n\t}\n\n\treturn importJsonSync(filePath);\n}\n\nexport {\n\timportJsonSync,\n\tgetEleventyPackageJson,\n\tgetModulePackageJson,\n\tgetWorkingProjectPackageJson,\n\tfindFilePathInParentDirs,\n\tgetWorkingProjectPackageJsonPath,\n};\n"
  },
  {
    "path": "src/Util/IsAsyncFunction.js",
    "content": "const ComparisonAsyncFunction = (async () => {}).constructor;\n\nexport default function isAsyncFunction(fn) {\n\treturn fn instanceof ComparisonAsyncFunction;\n}\n"
  },
  {
    "path": "src/Util/JavaScriptDependencies.core.js",
    "content": "class JavaScriptDependencies {\n\tstatic async getDependencies() {\n\t\tthrow new Error(\"This feature is not supported in `@11ty/client` bundles.\");\n\t}\n}\n\nexport default JavaScriptDependencies;\n"
  },
  {
    "path": "src/Util/JavaScriptDependencies.js",
    "content": "import dependencyTree from \"@11ty/dependency-tree\";\nimport { find, findGraph, mergeGraphs } from \"@11ty/dependency-tree-esm\";\nimport {\n\tfind as findTypeScript,\n\tfindGraph as findTypeScriptGraph,\n} from \"@11ty/dependency-tree-typescript\";\nimport { TemplatePath } from \"@11ty/eleventy-utils\";\nimport { DepGraph } from \"dependency-graph\";\n\nimport { union } from \"./SetUtil.js\";\nimport EleventyBaseError from \"../Errors/EleventyBaseError.js\";\n\nclass JavaScriptDependencies {\n\tstatic getErrorMessage(file, type) {\n\t\treturn `A problem was encountered looking for JavaScript dependencies in ${type} file: ${file}. This only affects --watch and --serve behavior and does not affect your build.`;\n\t}\n\n\tstatic getFlavor(filePath, isProjectUsingEsm) {\n\t\tif (\n\t\t\t(isProjectUsingEsm && (filePath.endsWith(\".js\") || filePath.endsWith(\".ts\"))) ||\n\t\t\tfilePath.endsWith(\".mjs\") ||\n\t\t\tfilePath.endsWith(\".mts\")\n\t\t) {\n\t\t\treturn \"esm\";\n\t\t}\n\t\tif (\n\t\t\t(!isProjectUsingEsm && (filePath.endsWith(\".js\") || filePath.endsWith(\".ts\"))) ||\n\t\t\tfilePath.endsWith(\".cjs\") ||\n\t\t\tfilePath.endsWith(\".cts\")\n\t\t) {\n\t\t\treturn \"cjs\";\n\t\t}\n\t}\n\n\tstatic isTypeScript(filePath) {\n\t\treturn filePath.endsWith(\".ts\") || filePath.endsWith(\".cts\") || filePath.endsWith(\".mts\");\n\t}\n\n\tstatic async getCommonJsDependencies(inputFiles, isProjectUsingEsm) {\n\t\tlet depSet = new Set();\n\n\t\t// TODO does this need to work with aliasing? what other JS extensions will have deps?\n\t\tlet commonJsFiles = inputFiles.filter(\n\t\t\t(file) => this.getFlavor(file, isProjectUsingEsm) === \"cjs\",\n\t\t);\n\n\t\t// TODO require(esm) means some files *could* be wrong here\n\t\tfor (let file of commonJsFiles) {\n\t\t\ttry {\n\t\t\t\tlet modules = dependencyTree(file, {\n\t\t\t\t\tnodeModuleNames: \"exclude\",\n\t\t\t\t\tallowNotFound: true,\n\t\t\t\t}).map((dependency) => {\n\t\t\t\t\treturn TemplatePath.addLeadingDotSlash(TemplatePath.relativePath(dependency));\n\t\t\t\t});\n\n\t\t\t\tfor (let dep of modules) {\n\t\t\t\t\tdepSet.add(dep);\n\t\t\t\t}\n\t\t\t} catch (e) {\n\t\t\t\tthrow new EleventyBaseError(this.getErrorMessage(file, \"CommonJS\"), e);\n\t\t\t}\n\t\t}\n\n\t\treturn depSet;\n\t}\n\n\tstatic async getEsmDependencies(inputFiles, isProjectUsingEsm) {\n\t\tlet depSet = new Set();\n\n\t\tlet esmFiles = inputFiles.filter((file) => this.getFlavor(file, isProjectUsingEsm) === \"esm\");\n\t\tfor (let file of esmFiles) {\n\t\t\ttry {\n\t\t\t\t// TODO feature test for node:module->stripTypeScriptTypes and use with find(file, { preprocess })\n\t\t\t\tlet modules = await (this.isTypeScript(file) ? findTypeScript : find)(file);\n\t\t\t\tfor (let dep of modules) {\n\t\t\t\t\tdepSet.add(dep);\n\t\t\t\t}\n\t\t\t} catch (e) {\n\t\t\t\tthrow new EleventyBaseError(this.getErrorMessage(file, \"ESM\"), e);\n\t\t\t}\n\t\t}\n\n\t\treturn depSet;\n\t}\n\n\tstatic async getDependencies(inputFiles, isProjectUsingEsm) {\n\t\tlet cjs = await this.getCommonJsDependencies(inputFiles, isProjectUsingEsm);\n\t\tlet esm = await this.getEsmDependencies(inputFiles, isProjectUsingEsm);\n\t\treturn Array.from(union(cjs, esm));\n\t}\n\n\tstatic async getEsmGraph(inputFiles, isProjectUsingEsm) {\n\t\tlet rootGraph = new DepGraph();\n\t\tlet esmFiles = inputFiles.filter((file) => this.getFlavor(file, isProjectUsingEsm) === \"esm\");\n\t\tfor (let file of esmFiles) {\n\t\t\ttry {\n\t\t\t\t// TODO feature test for node:module->stripTypeScriptTypes and use with find(file, { preprocess })\n\t\t\t\tlet graph = await (this.isTypeScript(file) ? findTypeScriptGraph : findGraph)(file);\n\n\t\t\t\tmergeGraphs(rootGraph, graph);\n\t\t\t} catch (e) {\n\t\t\t\tthrow new EleventyBaseError(this.getErrorMessage(file, \"ESM\"), e);\n\t\t\t}\n\t\t}\n\n\t\treturn rootGraph;\n\t}\n}\n\nexport default JavaScriptDependencies;\n"
  },
  {
    "path": "src/Util/MemoizeFunction.js",
    "content": "export default function (callback, options = {}) {\n\tlet { bench, name } = options;\n\tlet cache = new Map();\n\n\treturn (...args) => {\n\t\t// Only supports single-arg functions for now.\n\t\tif (args.filter(Boolean).length > 1) {\n\t\t\tbench?.get(`(count) ${name} Not valid for memoize`).incrementCount();\n\t\t\treturn callback(...args);\n\t\t}\n\n\t\tlet [cacheKey] = args;\n\n\t\tif (!cache.has(cacheKey)) {\n\t\t\tcache.set(cacheKey, callback(...args));\n\n\t\t\tbench?.get(`(count) ${name} memoize miss`).incrementCount();\n\n\t\t\treturn cache.get(cacheKey);\n\t\t}\n\n\t\tbench?.get(`(count) ${name} memoize hit`).incrementCount();\n\n\t\treturn cache.get(cacheKey);\n\t};\n}\n"
  },
  {
    "path": "src/Util/NewLineAdapter.core.js",
    "content": "export const EOL = \"\\n\";\n"
  },
  {
    "path": "src/Util/NewLineAdapter.js",
    "content": "export { EOL } from \"node:os\";\n"
  },
  {
    "path": "src/Util/Objects/DeepFreeze.js",
    "content": "import { isPlainObject } from \"@11ty/eleventy-utils\";\n\n// via https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze\n\nfunction DeepFreeze(obj, topLevelExceptions) {\n\tfor (let name of Reflect.ownKeys(obj)) {\n\t\tif ((topLevelExceptions || []).find((key) => key === name)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst value = obj[name];\n\t\tif (isPlainObject(value)) {\n\t\t\tDeepFreeze(value);\n\t\t}\n\t}\n\n\treturn Object.freeze(obj);\n}\n\nexport { DeepFreeze };\n"
  },
  {
    "path": "src/Util/Objects/ObjectFilter.js",
    "content": "export default function objectFilter(obj, callback) {\n\tlet newObject = {};\n\tfor (let [key, value] of Object.entries(obj || {})) {\n\t\tif (callback(value, key)) {\n\t\t\tnewObject[key] = value;\n\t\t}\n\t}\n\treturn newObject;\n}\n"
  },
  {
    "path": "src/Util/Objects/ProxyWrap.js",
    "content": "import debugUtil from \"debug\";\nimport { isPlainObject } from \"@11ty/eleventy-utils\";\n\nconst debug = debugUtil(\"Dev:Eleventy:Proxy\");\n\nconst ProxySymbol = Symbol.for(\"11ty.ProxySymbol\");\n\nfunction wrapObject(target, fallback) {\n\tif (Object.isFrozen(target)) {\n\t\treturn target;\n\t}\n\n\treturn new Proxy(target, {\n\t\tgetOwnPropertyDescriptor(target, prop) {\n\t\t\tlet ret;\n\n\t\t\tif (Reflect.has(target, prop)) {\n\t\t\t\tret = Reflect.getOwnPropertyDescriptor(target, prop);\n\t\t\t} else if (Reflect.has(fallback, prop)) {\n\t\t\t\tret = Reflect.getOwnPropertyDescriptor(fallback, prop);\n\t\t\t}\n\n\t\t\treturn ret;\n\t\t},\n\t\thas(target, prop) {\n\t\t\tif (Reflect.has(target, prop)) {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\treturn Reflect.has(fallback, prop);\n\t\t},\n\t\townKeys(target) {\n\t\t\tlet s = new Set();\n\t\t\t// The fallback keys need to come first to preserve proper key order\n\t\t\t// https://github.com/11ty/eleventy/issues/3849\n\t\t\tif (isPlainObject(fallback)) {\n\t\t\t\tfor (let k of Reflect.ownKeys(fallback)) {\n\t\t\t\t\ts.add(k);\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (let k of Reflect.ownKeys(target)) {\n\t\t\t\tif (!s.has(k)) {\n\t\t\t\t\ts.add(k);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn Array.from(s);\n\t\t},\n\t\tget(target, prop) {\n\t\t\tdebug(\"handler:get\", prop);\n\t\t\tif (prop === ProxySymbol) {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tlet value = Reflect.get(target, prop);\n\n\t\t\tif (Reflect.has(target, prop)) {\n\t\t\t\t// Careful: swapped from node:util/types->isProxy test here\n\t\t\t\tif (Reflect.get(target, ProxySymbol)) {\n\t\t\t\t\treturn value;\n\t\t\t\t}\n\n\t\t\t\tif (isPlainObject(value) && Reflect.has(fallback, prop)) {\n\t\t\t\t\tif (Object.isFrozen(value)) {\n\t\t\t\t\t\treturn value;\n\t\t\t\t\t}\n\n\t\t\t\t\tlet ret = wrapObject(value, Reflect.get(fallback, prop));\n\t\t\t\t\tdebug(\"handler:get (primary, object)\", prop);\n\t\t\t\t\treturn ret;\n\t\t\t\t}\n\n\t\t\t\tdebug(\"handler:get (primary)\", prop);\n\t\t\t\treturn value;\n\t\t\t}\n\n\t\t\t// Does not exist in primary\n\t\t\tif (\n\t\t\t\t(typeof fallback === \"object\" || typeof fallback === \"function\") &&\n\t\t\t\tReflect.has(fallback, prop)\n\t\t\t) {\n\t\t\t\t// fallback has prop\n\t\t\t\tlet fallbackValue = Reflect.get(fallback, prop);\n\n\t\t\t\tif (isPlainObject(fallbackValue)) {\n\t\t\t\t\tif (Object.isFrozen(fallbackValue)) {\n\t\t\t\t\t\treturn fallbackValue;\n\t\t\t\t\t}\n\n\t\t\t\t\tdebug(\"handler:get (fallback, object)\", prop);\n\t\t\t\t\t// set empty object on primary\n\t\t\t\t\tlet emptyObject = {};\n\t\t\t\t\tReflect.set(target, prop, emptyObject);\n\n\t\t\t\t\treturn wrapObject(emptyObject, fallbackValue);\n\t\t\t\t}\n\n\t\t\t\tdebug(\"handler:get (fallback)\", prop);\n\t\t\t\treturn fallbackValue;\n\t\t\t}\n\n\t\t\t// primary *and* fallback do _not_ have prop\n\t\t\tdebug(\"handler:get (not on primary or fallback)\", prop);\n\n\t\t\treturn value;\n\t\t},\n\t\tset(target, prop, value) {\n\t\t\tdebug(\"handler:set\", prop);\n\n\t\t\treturn Reflect.set(target, prop, value);\n\t\t},\n\t});\n}\n\nfunction ProxyWrap(target, fallback) {\n\tif (!isPlainObject(target) || !isPlainObject(fallback)) {\n\t\tthrow new Error(\"ProxyWrap expects objects for both the target and fallback\");\n\t}\n\n\treturn wrapObject(target, fallback);\n}\n\nexport { ProxyWrap };\n"
  },
  {
    "path": "src/Util/Objects/SampleModule.mjs",
    "content": "export default {};\n"
  },
  {
    "path": "src/Util/Objects/Sortable.js",
    "content": "class Sortable {\n\tconstructor() {\n\t\tthis.isSortAscending = true;\n\t\tthis.isSortNumeric = false;\n\t\tthis.items = [];\n\t\tthis._dirty = true;\n\n\t\tthis.sortFunctionStringMap = {\n\t\t\t\"A-Z\": \"sortFunctionAscending\",\n\t\t\t\"Z-A\": \"sortFunctionDescending\",\n\t\t\t\"0-9\": \"sortFunctionNumericAscending\",\n\t\t\t\"9-0\": \"sortFunctionNumericDescending\",\n\t\t};\n\t}\n\n\tget length() {\n\t\treturn this.items.length;\n\t}\n\n\tadd(item) {\n\t\tthis._dirty = true;\n\t\tthis.items.push(item);\n\t}\n\n\tsort(sortFunction) {\n\t\tif (!sortFunction) {\n\t\t\tsortFunction = this.getSortFunction();\n\t\t} else if (typeof sortFunction === \"string\") {\n\t\t\tlet key = sortFunction;\n\t\t\tlet name;\n\t\t\tif (key in this.sortFunctionStringMap) {\n\t\t\t\tname = this.sortFunctionStringMap[key];\n\t\t\t}\n\t\t\tif (Sortable[name]) {\n\t\t\t\tsortFunction = Sortable[name];\n\t\t\t} else {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Invalid String argument for sort(). Received \\`${key}\\`. Valid values: ${Object.keys(\n\t\t\t\t\t\tthis.sortFunctionStringMap,\n\t\t\t\t\t)}`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\treturn this.items.slice().sort(sortFunction);\n\t}\n\n\tsortAscending() {\n\t\treturn this.sort(this.getSortFunctionAscending());\n\t}\n\n\tsortDescending() {\n\t\treturn this.sort(this.getSortFunctionDescending());\n\t}\n\n\tsetSortDescending(isDescending = true) {\n\t\tthis.isSortAscending = !isDescending;\n\t}\n\n\tsetSortAscending(isAscending = true) {\n\t\tthis.isSortAscending = isAscending;\n\t}\n\n\tsetSortNumeric(isNumeric) {\n\t\tthis.isSortNumeric = isNumeric;\n\t}\n\n\t/* Sort functions */\n\tstatic sortFunctionNumericAscending(a, b) {\n\t\treturn a - b;\n\t}\n\n\tstatic sortFunctionNumericDescending(a, b) {\n\t\treturn b - a;\n\t}\n\n\tstatic sortFunctionAscending(a, b) {\n\t\tif (a > b) {\n\t\t\treturn 1;\n\t\t} else if (a < b) {\n\t\t\treturn -1;\n\t\t}\n\t\treturn 0;\n\t}\n\n\tstatic sortFunctionDescending(a, b) {\n\t\treturn Sortable.sortFunctionAscending(b, a);\n\t}\n\n\tstatic sortFunctionAlphabeticAscending(a, b) {\n\t\treturn Sortable.sortFunctionAscending(a, b);\n\t}\n\n\tstatic sortFunctionAlphabeticDescending(a, b) {\n\t\treturn Sortable.sortFunctionAscending(b, a);\n\t}\n\n\tstatic sortFunctionDate(mapA, mapB) {\n\t\treturn Sortable.sortFunctionNumericAscending(mapA.date.getTime(), mapB.date.getTime());\n\t}\n\n\tstatic sortFunctionDateInputPath(mapA, mapB) {\n\t\tlet sortDate = Sortable.sortFunctionNumericAscending(mapA.date.getTime(), mapB.date.getTime());\n\t\tif (sortDate === 0) {\n\t\t\treturn Sortable.sortFunctionAlphabeticAscending(mapA.inputPath, mapB.inputPath);\n\t\t}\n\t\treturn sortDate;\n\t}\n\t/* End sort functions */\n\n\tgetSortFunction() {\n\t\tif (this.isSortAscending) {\n\t\t\treturn this.getSortFunctionAscending();\n\t\t} else {\n\t\t\treturn this.getSortFunctionDescending();\n\t\t}\n\t}\n\n\tgetSortFunctionAscending() {\n\t\tif (this.isSortNumeric) {\n\t\t\treturn Sortable.sortFunctionNumericAscending;\n\t\t} else {\n\t\t\treturn Sortable.sortFunctionAlphabeticAscending;\n\t\t}\n\t}\n\n\tgetSortFunctionDescending() {\n\t\tif (this.isSortNumeric) {\n\t\t\treturn Sortable.sortFunctionNumericDescending;\n\t\t} else {\n\t\t\treturn Sortable.sortFunctionAlphabeticDescending;\n\t\t}\n\t}\n}\n\nexport default Sortable;\n"
  },
  {
    "path": "src/Util/Objects/Unique.js",
    "content": "export default function Unique(arr) {\n\treturn Array.from(new Set(arr));\n}\n"
  },
  {
    "path": "src/Util/PassthroughCopyBehaviorCheck.js",
    "content": "function isUsingEleventyDevServer(config) {\n\treturn (\n\t\t!config.serverOptions.module || config.serverOptions.module === \"@11ty/eleventy-dev-server\"\n\t);\n}\n\n// Config opt-in via serverPassthroughCopyBehavior\n// False when other server is used\n// False when runMode is \"build\" or \"watch\"\nexport default function (config, runMode) {\n\treturn (\n\t\tconfig.serverPassthroughCopyBehavior === \"passthrough\" &&\n\t\tisUsingEleventyDevServer(config) &&\n\t\trunMode === \"serve\"\n\t);\n}\n"
  },
  {
    "path": "src/Util/PathNormalizer.js",
    "content": "import { parse, sep } from \"node:path\";\nimport { TemplatePath } from \"@11ty/eleventy-utils\";\nimport { fileURLToPath } from \"../Adapters/Packages/url.js\";\n\nexport default class PathNormalizer {\n\tstatic getParts(inputPath) {\n\t\tif (!inputPath) {\n\t\t\treturn [];\n\t\t}\n\n\t\tlet separator = \"/\";\n\t\tif (inputPath.includes(sep)) {\n\t\t\tseparator = sep;\n\t\t}\n\n\t\treturn inputPath.split(separator).filter((entry) => entry !== \".\");\n\t}\n\n\t// order is important here: the top-most directory returns first\n\t// array of file and all parent directories\n\tstatic getAllPaths(inputPath) {\n\t\tlet parts = this.getParts(inputPath);\n\t\tlet allPaths = [];\n\n\t\tlet fullpath = \"\";\n\t\tfor (let part of parts) {\n\t\t\tfullpath += (fullpath.length > 0 ? \"/\" : \"\") + part;\n\t\t\tallPaths.push(fullpath);\n\t\t}\n\n\t\treturn allPaths;\n\t}\n\n\tstatic normalizeSeperator(inputPath) {\n\t\tif (!inputPath) {\n\t\t\treturn inputPath;\n\t\t}\n\t\treturn inputPath.split(sep).join(\"/\");\n\t}\n\n\tstatic addTrailingSlashToDirectory(dir) {\n\t\tif (dir.endsWith(\"/\")) {\n\t\t\treturn dir;\n\t\t}\n\n\t\treturn dir + \"/\";\n\t}\n\n\t// returns a path\n\tstatic getDirectoryFromFilePath(filePath) {\n\t\tlet parsed = parse(filePath);\n\t\treturn this.addTrailingSlashToDirectory(parsed.dir);\n\t}\n\n\tstatic fullNormalization(inputPath) {\n\t\tif (typeof inputPath !== \"string\") {\n\t\t\treturn inputPath;\n\t\t}\n\n\t\t// Fix file:///Users/ or file:///C:/ paths passed in\n\t\tif (inputPath.startsWith(\"file://\")) {\n\t\t\tinputPath = fileURLToPath(inputPath);\n\t\t}\n\n\t\t// Paths should not be absolute (we convert absolute paths to relative)\n\t\t// Paths should not have a leading dot slash\n\t\t// Paths should always be `/` independent of OS path separator\n\t\treturn TemplatePath.stripLeadingDotSlash(\n\t\t\tthis.normalizeSeperator(TemplatePath.relativePath(inputPath)),\n\t\t);\n\t}\n}\n"
  },
  {
    "path": "src/Util/PathPrefixer.js",
    "content": "import path from \"node:path\";\n\nimport PathNormalizer from \"./PathNormalizer.js\";\n\nclass PathPrefixer {\n\tstatic normalizePathPrefix(pathPrefix) {\n\t\tif (pathPrefix) {\n\t\t\t// add leading / (for browsersync), see #1454\n\t\t\t// path.join uses \\\\ for Windows so we split and rejoin\n\t\t\treturn PathPrefixer.joinUrlParts(\"/\", pathPrefix);\n\t\t}\n\n\t\treturn \"/\";\n\t}\n\n\tstatic joinUrlParts(...parts) {\n\t\treturn PathNormalizer.normalizeSeperator(path.join(...parts));\n\t}\n}\n\nexport default PathPrefixer;\n"
  },
  {
    "path": "src/Util/Pluralize.js",
    "content": "export default function (count, singleWord, pluralWord) {\n\treturn count === 1 ? singleWord : pluralWord;\n}\n"
  },
  {
    "path": "src/Util/ProjectDirectories.js",
    "content": "import { existsSync, statSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { TemplatePath } from \"@11ty/eleventy-utils\";\nimport { isDynamicPattern } from \"../Util/GlobMatcher.js\";\n\nimport DirContains from \"./DirContains.js\";\n\n/* Directories internally should always use *nix forward slashes */\nclass ProjectDirectories {\n\tstatic defaults = {\n\t\tinput: \"./\",\n\t\tdata: \"./_data/\", // Relative to input directory\n\t\tincludes: \"./_includes/\", // Relative to input directory\n\t\tlayouts: \"./_layouts/\", // Relative to input directory\n\t\toutput: \"./_site/\",\n\t};\n\n\t// no updates allowed, input/output set via CLI\n\t#frozen = false;\n\n\t#raw = {};\n\n\t#dirs = {};\n\n\tinputFile = undefined;\n\tinputGlob = undefined;\n\n\t// Add leading dot slash\n\t// Use forward slashes\n\tstatic normalizePath(fileOrDir) {\n\t\treturn TemplatePath.standardizeFilePath(fileOrDir);\n\t}\n\n\t// Must be a directory\n\t// Always include a trailing slash\n\tstatic normalizeDirectory(dir) {\n\t\treturn this.addTrailingSlash(this.normalizePath(dir));\n\t}\n\n\tnormalizeDirectoryPathRelativeToInputDirectory(filePath) {\n\t\treturn ProjectDirectories.normalizeDirectory(path.join(this.input, filePath));\n\t}\n\n\tstatic addTrailingSlash(path) {\n\t\tif (path.slice(-1) === \"/\") {\n\t\t\treturn path;\n\t\t}\n\t\treturn path + \"/\";\n\t}\n\n\t// If input/output are set via CLI, they take precedence over all other configuration values.\n\tfreeze() {\n\t\tthis.#frozen = true;\n\t}\n\n\tsetViaConfigObject(configDirs = {}) {\n\t\t// input must come last\n\t\tlet inputChanged = false;\n\t\tif (\n\t\t\tconfigDirs.input &&\n\t\t\tProjectDirectories.normalizeDirectory(configDirs.input) !== this.input\n\t\t) {\n\t\t\tthis.#setInputRaw(configDirs.input);\n\t\t\tinputChanged = true;\n\t\t}\n\n\t\t// If falsy or an empty string, the current directory is used.\n\t\tif (configDirs.output !== undefined) {\n\t\t\tif (ProjectDirectories.normalizeDirectory(configDirs.output) !== this.output) {\n\t\t\t\tthis.setOutput(configDirs.output);\n\t\t\t}\n\t\t}\n\n\t\t// Input relative directory, if falsy or an empty string, inputDir is used!\n\t\t// Always set if input changed, e.g. input is `src` and data is `../_data` (resulting in `./_data`) we still want to set data to this new value\n\t\tif (configDirs.data !== undefined) {\n\t\t\tif (\n\t\t\t\tinputChanged ||\n\t\t\t\tthis.normalizeDirectoryPathRelativeToInputDirectory(configDirs.data || \"\") !== this.data\n\t\t\t) {\n\t\t\t\tthis.setData(configDirs.data);\n\t\t\t}\n\t\t}\n\n\t\t// Input relative directory, if falsy or an empty string, inputDir is used!\n\t\tif (configDirs.includes !== undefined) {\n\t\t\tif (\n\t\t\t\tinputChanged ||\n\t\t\t\tthis.normalizeDirectoryPathRelativeToInputDirectory(configDirs.includes || \"\") !==\n\t\t\t\t\tthis.includes\n\t\t\t) {\n\t\t\t\tthis.setIncludes(configDirs.includes);\n\t\t\t}\n\t\t}\n\n\t\t// Input relative directory, if falsy or an empty string, inputDir is used!\n\t\tif (configDirs.layouts !== undefined) {\n\t\t\tif (\n\t\t\t\tinputChanged ||\n\t\t\t\tthis.normalizeDirectoryPathRelativeToInputDirectory(configDirs.layouts || \"\") !==\n\t\t\t\t\tthis.layouts\n\t\t\t) {\n\t\t\t\tthis.setLayouts(configDirs.layouts);\n\t\t\t}\n\t\t}\n\n\t\tif (inputChanged) {\n\t\t\tthis.updateInputDependencies();\n\t\t}\n\t}\n\n\tupdateInputDependencies() {\n\t\t// raw first, fall back to Eleventy defaults if not yet set\n\t\tthis.setData(this.#raw.data ?? ProjectDirectories.defaults.data);\n\t\tthis.setIncludes(this.#raw.includes ?? ProjectDirectories.defaults.includes);\n\n\t\t// Should not include this if not explicitly opted-in\n\t\tif (this.#raw.layouts !== undefined) {\n\t\t\tthis.setLayouts(this.#raw.layouts ?? ProjectDirectories.defaults.layouts);\n\t\t}\n\t}\n\n\t/* Relative to project root, must exist */\n\t#setInputRaw(dirOrFile, inputDir = undefined) {\n\t\t// is frozen and was defined previously\n\t\tif (this.#frozen && this.#raw.input !== undefined) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.#raw.input = dirOrFile;\n\n\t\tif (!dirOrFile) {\n\t\t\t// input must exist if inputDir is not set.\n\t\t\treturn;\n\t\t}\n\n\t\t// Normalize absolute paths to relative, #3805 #3896\n\t\tif (path.isAbsolute(dirOrFile)) {\n\t\t\tdirOrFile = path.relative(\".\", dirOrFile);\n\t\t}\n\n\t\t// Input has to exist (assumed glob if it does not exist)\n\t\tlet inputExists = existsSync(dirOrFile);\n\t\tlet inputExistsAndIsDirectory = inputExists && statSync(dirOrFile).isDirectory();\n\n\t\tif (inputExistsAndIsDirectory) {\n\t\t\t// is not a file or glob\n\t\t\tthis.#dirs.input = ProjectDirectories.normalizeDirectory(dirOrFile);\n\t\t} else {\n\t\t\tif (inputExists) {\n\t\t\t\tthis.inputFile = ProjectDirectories.normalizePath(dirOrFile);\n\t\t\t} else {\n\t\t\t\tif (!isDynamicPattern(dirOrFile)) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`The \"${dirOrFile}\" \\`input\\` parameter (directory or file path) must exist on the file system (unless detected as a glob by the \\`tinyglobby\\` package)`,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tthis.inputGlob = dirOrFile;\n\t\t\t}\n\n\t\t\t// Explicit Eleventy option for inputDir\n\t\t\tif (inputDir) {\n\t\t\t\t// Changed in 3.0: must exist\n\t\t\t\tif (!existsSync(inputDir)) {\n\t\t\t\t\tthrow new Error(\"Directory must exist (via inputDir option to Eleventy constructor).\");\n\t\t\t\t}\n\n\t\t\t\tthis.#dirs.input = ProjectDirectories.normalizeDirectory(inputDir);\n\t\t\t} else {\n\t\t\t\t// the input directory is implied to be the parent directory of the\n\t\t\t\t// file, unless inputDir is explicitly specified (via Eleventy constructor `options`)\n\t\t\t\tthis.#dirs.input = ProjectDirectories.normalizeDirectory(\n\t\t\t\t\tTemplatePath.getDirFromFilePath(dirOrFile), // works with globs\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\tsetInput(dirOrFile, inputDir = undefined) {\n\t\tthis.#setInputRaw(dirOrFile, inputDir); // does not update\n\t\tthis.updateInputDependencies();\n\t}\n\n\t/* Relative to input dir */\n\tsetIncludes(dir) {\n\t\tif (dir !== undefined) {\n\t\t\t// falsy or an empty string is valid (falls back to input dir)\n\t\t\tthis.#raw.includes = dir;\n\t\t\tthis.#dirs.includes = ProjectDirectories.normalizeDirectory(\n\t\t\t\tTemplatePath.join(this.input, dir || \"\"),\n\t\t\t);\n\t\t}\n\t}\n\n\t/* Relative to input dir */\n\t/* Optional */\n\tsetLayouts(dir) {\n\t\tif (dir !== undefined) {\n\t\t\t// falsy or an empty string is valid (falls back to input dir)\n\t\t\tthis.#raw.layouts = dir;\n\t\t\tthis.#dirs.layouts = ProjectDirectories.normalizeDirectory(\n\t\t\t\tTemplatePath.join(this.input, dir || \"\"),\n\t\t\t);\n\t\t}\n\t}\n\n\t/* Relative to input dir */\n\tsetData(dir) {\n\t\tif (dir !== undefined) {\n\t\t\t// falsy or an empty string is valid (falls back to input dir)\n\t\t\t// TODO must exist if specified\n\t\t\tthis.#raw.data = dir;\n\t\t\tthis.#dirs.data = ProjectDirectories.normalizeDirectory(\n\t\t\t\tTemplatePath.join(this.input, dir || \"\"),\n\t\t\t);\n\t\t}\n\t}\n\n\t/* Relative to project root */\n\tsetOutput(dir) {\n\t\t// is frozen and was defined previously\n\t\tif (this.#frozen && this.#raw.output !== undefined) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (dir !== undefined) {\n\t\t\tthis.#raw.output = dir;\n\n\t\t\t// Normalize absolute paths to relative, #3805 #3896\n\t\t\tif (path.isAbsolute(dir)) {\n\t\t\t\tdir = path.relative(\".\", dir);\n\t\t\t}\n\n\t\t\tthis.#dirs.output = ProjectDirectories.normalizeDirectory(dir || \"\");\n\t\t}\n\t}\n\n\tget input() {\n\t\treturn this.#dirs.input || ProjectDirectories.defaults.input;\n\t}\n\n\tget data() {\n\t\treturn this.#dirs.data || ProjectDirectories.defaults.data;\n\t}\n\n\tget includes() {\n\t\treturn this.#dirs.includes || ProjectDirectories.defaults.includes;\n\t}\n\n\tget layouts() {\n\t\t// explicit opt-in, no fallback.\n\t\treturn this.#dirs.layouts;\n\t}\n\n\tget output() {\n\t\treturn this.#dirs.output || ProjectDirectories.defaults.output;\n\t}\n\n\tisTemplateFile(filePath) {\n\t\tlet inputPath = this.getInputPath(filePath);\n\t\t// TODO use DirContains\n\t\tif (this.layouts && inputPath.startsWith(this.layouts)) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// if this.includes is \"\" (and thus is the same directory as this.input)\n\t\t// we don’t actually know if this is a template file, so defer\n\t\tif (this.includes && this.includes !== this.input) {\n\t\t\tif (inputPath.startsWith(this.includes)) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\t// TODO use DirContains\n\t\treturn inputPath.startsWith(this.input);\n\t}\n\n\t// for a hypothetical template file\n\tgetInputPath(filePathRelativeToInputDir) {\n\t\t// TODO change ~/ to project root dir\n\t\treturn TemplatePath.addLeadingDotSlash(\n\t\t\tTemplatePath.join(this.input, TemplatePath.standardizeFilePath(filePathRelativeToInputDir)),\n\t\t);\n\t}\n\n\t// Inverse of getInputPath\n\t// Removes input dir from path\n\tgetInputPathRelativeToInputDirectory(filePathRelativeToInputDir) {\n\t\tlet inputDir = TemplatePath.addLeadingDotSlash(TemplatePath.join(this.input));\n\n\t\t// No leading dot slash\n\t\treturn TemplatePath.stripLeadingSubPath(filePathRelativeToInputDir, inputDir);\n\t}\n\n\t// for a hypothetical Eleventy layout file\n\tgetLayoutPath(filePathRelativeToLayoutDir) {\n\t\treturn TemplatePath.addLeadingDotSlash(\n\t\t\tTemplatePath.join(\n\t\t\t\tthis.layouts || this.includes,\n\t\t\t\tTemplatePath.standardizeFilePath(filePathRelativeToLayoutDir),\n\t\t\t),\n\t\t);\n\t}\n\n\t// Removes layout dir from path\n\tgetLayoutPathRelativeToInputDirectory(filePathRelativeToLayoutDir) {\n\t\tlet layoutPath = this.getLayoutPath(filePathRelativeToLayoutDir);\n\t\tlet inputDir = TemplatePath.addLeadingDotSlash(TemplatePath.join(this.input));\n\n\t\t// No leading dot slash\n\t\treturn TemplatePath.stripLeadingSubPath(layoutPath, inputDir);\n\t}\n\n\tgetProjectPath(filePath) {\n\t\treturn TemplatePath.addLeadingDotSlash(\n\t\t\tTemplatePath.join(\".\", TemplatePath.standardizeFilePath(filePath)),\n\t\t);\n\t}\n\n\tisFileInProjectFolder(filePath) {\n\t\treturn DirContains(TemplatePath.getWorkingDir(), filePath);\n\t}\n\n\tisFileInOutputFolder(filePath) {\n\t\treturn DirContains(this.output, filePath);\n\t}\n\n\tstatic getRelativeTo(targetPath, cwd) {\n\t\treturn path.relative(cwd, path.join(path.resolve(\".\"), targetPath));\n\t}\n\n\t// Access the data without being able to set the data.\n\tgetUserspaceInstance() {\n\t\tlet d = this;\n\n\t\treturn {\n\t\t\tget input() {\n\t\t\t\treturn d.input;\n\t\t\t},\n\t\t\tget inputFile() {\n\t\t\t\treturn d.inputFile;\n\t\t\t},\n\t\t\tget inputGlob() {\n\t\t\t\treturn d.inputGlob;\n\t\t\t},\n\t\t\tget data() {\n\t\t\t\treturn d.data;\n\t\t\t},\n\t\t\tget includes() {\n\t\t\t\treturn d.includes;\n\t\t\t},\n\t\t\tget layouts() {\n\t\t\t\treturn d.layouts;\n\t\t\t},\n\t\t\tget output() {\n\t\t\t\treturn d.output;\n\t\t\t},\n\t\t};\n\t}\n\n\ttoString() {\n\t\treturn {\n\t\t\tinput: this.input,\n\t\t\tinputFile: this.inputFile,\n\t\t\tinputGlob: this.inputGlob,\n\t\t\tdata: this.data,\n\t\t\tincludes: this.includes,\n\t\t\tlayouts: this.layouts,\n\t\t\toutput: this.output,\n\t\t};\n\t}\n}\n\nexport default ProjectDirectories;\n"
  },
  {
    "path": "src/Util/ProjectTemplateFormats.js",
    "content": "import debugUtil from \"debug\";\nconst debug = debugUtil(\"Eleventy:Util:ProjectTemplateFormats\");\n\nclass ProjectTemplateFormats {\n\t#useAll = {};\n\t#raw = {};\n\n\t#values = {}; // Set objects\n\n\tstatic union(...sets) {\n\t\tlet s = new Set();\n\n\t\tfor (let set of sets) {\n\t\t\tif (!set || typeof set[Symbol.iterator] !== \"function\") {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tfor (let v of set) {\n\t\t\t\ts.add(v);\n\t\t\t}\n\t\t}\n\n\t\treturn s;\n\t}\n\n\t#normalize(formats) {\n\t\tif (Array.isArray(formats)) {\n\t\t\tformats = \"\" + formats.join(\",\");\n\t\t}\n\n\t\tif (typeof formats !== \"string\") {\n\t\t\tthrow new Error(\n\t\t\t\t`Invalid formats (expect String, Array) passed to ProjectTemplateFormats->normalize: ${formats}`,\n\t\t\t);\n\t\t}\n\n\t\tlet final = new Set();\n\t\tfor (let format of formats.split(\",\")) {\n\t\t\tformat = format.trim();\n\t\t\tif (format && format !== \"*\") {\n\t\t\t\tfinal.add(format);\n\t\t\t}\n\t\t}\n\n\t\treturn final;\n\t}\n\n\tisWildcard() {\n\t\treturn this.#useAll.cli || this.#useAll.config || false;\n\t}\n\n\t/** @returns {boolean} */\n\t#isUseAll(rawFormats) {\n\t\tif (rawFormats === \"\") {\n\t\t\treturn false;\n\t\t}\n\n\t\tif (typeof rawFormats === \"string\") {\n\t\t\trawFormats = rawFormats.split(\",\");\n\t\t}\n\n\t\tif (Array.isArray(rawFormats)) {\n\t\t\treturn rawFormats.find((entry) => entry === \"*\") !== undefined;\n\t\t}\n\n\t\treturn false;\n\t}\n\n\t// 3.x Breaking: \"\" now means no formats. In 2.x and prior it meant \"*\"\n\tsetViaCommandLine(formats) {\n\t\tif (formats === undefined) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.#useAll.cli = this.#isUseAll(formats);\n\t\tthis.#raw.cli = formats;\n\t\tthis.#values.cli = this.#normalize(formats);\n\t}\n\n\t// 3.x Breaking: \"\" now means no formats—in 2.x and prior it meant \"*\"\n\t// 3.x Adds support for comma separated string—in 2.x this required an Array\n\tsetViaConfig(formats) {\n\t\tif (formats === undefined) {\n\t\t\treturn;\n\t\t}\n\n\t\t// \"*\" is supported\n\t\tthis.#useAll.config = this.#isUseAll(formats);\n\t\tthis.#raw.config = formats;\n\t\tthis.#values.config = this.#normalize(formats);\n\t}\n\n\taddViaConfig(formats) {\n\t\tif (!formats) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.#isUseAll(formats)) {\n\t\t\tthrow new Error(\n\t\t\t\t`\\`addTemplateFormats(\"*\")\\` is not supported for project template syntaxes.`,\n\t\t\t);\n\t\t}\n\n\t\t// \"*\" not supported here\n\t\tthis.#raw.configAdd = formats;\n\t\tthis.#values.configAdd = this.#normalize(formats);\n\t}\n\n\tgetAllTemplateFormats() {\n\t\treturn Array.from(ProjectTemplateFormats.union(this.#values.config, this.#values.configAdd));\n\t}\n\n\tgetTemplateFormats() {\n\t\tif (this.#useAll.cli) {\n\t\t\tlet v = this.getAllTemplateFormats();\n\t\t\tdebug(\"Using CLI --formats='*': %o\", v);\n\t\t\treturn v;\n\t\t}\n\n\t\tif (this.#raw.cli !== undefined) {\n\t\t\tlet v = Array.from(this.#values.cli);\n\t\t\tdebug(\"Using CLI --formats: %o\", v);\n\t\t\treturn v;\n\t\t}\n\n\t\tlet v = this.getAllTemplateFormats();\n\t\tdebug(\n\t\t\t\"Using configuration `templateFormats`, `setTemplateFormats()`, `addTemplateFormats()`: %o\",\n\t\t\tv,\n\t\t);\n\t\treturn v;\n\t}\n}\n\nexport default ProjectTemplateFormats;\n"
  },
  {
    "path": "src/Util/PromiseUtil.js",
    "content": "export function withResolvers() {\n\tif (\"withResolvers\" in Promise) {\n\t\treturn Promise.withResolvers();\n\t}\n\n\tlet resolve;\n\tlet reject;\n\tlet promise = new Promise((res, rej) => {\n\t\tresolve = res;\n\t\treject = rej;\n\t});\n\treturn { promise, resolve, reject };\n}\n"
  },
  {
    "path": "src/Util/Require.js",
    "content": "import { readFileSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { TemplatePath } from \"@11ty/eleventy-utils\";\n\nimport importer from \"./importer.js\";\nimport { clearRequireCache, requireCommonJsTypeScript } from \"../Util/RequireUtils.js\";\nimport { port1 } from \"./EsmResolverPortAdapter.js\";\nimport EleventyBaseError from \"../Errors/EleventyBaseError.js\";\nimport eventBus from \"../EventBus.js\";\n\nclass EleventyImportError extends EleventyBaseError {}\n\nconst requestPromiseCache = new Map();\n\nfunction isCommonJSTypeScript(filePath, type) {\n\treturn (type === \"cjs\" && filePath.endsWith(\".ts\")) || filePath.endsWith(\".cts\"); // no .mts here\n}\n\nfunction getImportErrorMessage(filePath, type) {\n\treturn `There was a problem importing '${path.relative(\".\", filePath)}' via ${type}`;\n}\n\n// Used for JSON imports, suffering from Node warning that import assertions experimental but also\n// throwing an error if you try to import() a JSON file without an import assertion.\n/**\n *\n * @returns {string|undefined}\n */\nfunction loadContents(path, options = {}) {\n\tlet rawInput;\n\t/** @type {string} */\n\tlet encoding = \"utf8\"; // JSON is utf8\n\tif (options?.encoding || options?.encoding === null) {\n\t\tencoding = options.encoding;\n\t}\n\n\ttry {\n\t\t// @ts-expect-error This is an error in the upstream types\n\t\trawInput = readFileSync(path, encoding);\n\t} catch (error) {\n\t\t// @ts-expect-error Temporary\n\t\tif (error?.code === \"ENOENT\") {\n\t\t\t// if file does not exist, return nothing\n\t\t\treturn;\n\t\t}\n\n\t\tthrow error;\n\t}\n\n\t// Can return a buffer, string, etc\n\tif (typeof rawInput === \"string\") {\n\t\trawInput = rawInput.trim();\n\t}\n\n\treturn rawInput;\n}\n\nlet lastModifiedPaths = new Map();\neventBus.on(\"eleventy.importCacheReset\", (fileQueue) => {\n\tfor (let filePath of fileQueue) {\n\t\tlet absolutePath = TemplatePath.absolutePath(filePath);\n\t\tlet newDate = Date.now();\n\t\tlastModifiedPaths.set(absolutePath, newDate);\n\n\t\t// post to EsmResolver worker thread\n\t\tif (port1) {\n\t\t\tport1.postMessage({ path: absolutePath, newDate });\n\t\t}\n\n\t\tclearRequireCache(absolutePath);\n\t}\n});\n\n// raw means we don’t normalize away the `default` export\nasync function dynamicImportAbsolutePath(absolutePath, options = {}) {\n\tlet { type, returnRaw, cacheBust } = Object.assign(\n\t\t{\n\t\t\ttype: undefined,\n\t\t\treturnRaw: false,\n\t\t\tcacheBust: false, // force cache bust\n\t\t},\n\t\toptions,\n\t);\n\n\t// Short circuit for JSON files (that are optional and can be empty)\n\tif (absolutePath.endsWith(\".json\") || type === \"json\") {\n\t\ttry {\n\t\t\t// https://v8.dev/features/import-assertions#dynamic-import() is still experimental in Node 20\n\t\t\tlet rawInput = loadContents(absolutePath);\n\t\t\tif (!rawInput) {\n\t\t\t\t// should not error when file exists but is _empty_\n\t\t\t\treturn;\n\t\t\t}\n\t\t\treturn JSON.parse(rawInput);\n\t\t} catch (e) {\n\t\t\treturn Promise.reject(\n\t\t\t\tnew EleventyImportError(getImportErrorMessage(absolutePath, \"fs.readFile(json)\"), e),\n\t\t\t);\n\t\t}\n\t}\n\n\t// Removed a `require` short circuit from this piece originally added\n\t// in https://github.com/11ty/eleventy/pull/3493 Was a bit faster but\n\t// error messaging was worse for require(esm)\n\n\t// Workaround for Node issue https://github.com/nodejs/node/issues/61385\n\t// Remove this when fixed upstream!\n\tif (isCommonJSTypeScript(absolutePath, type)) {\n\t\treturn requireCommonJsTypeScript(absolutePath);\n\t}\n\n\tlet urlPath;\n\ttry {\n\t\tlet u = new URL(`file:${absolutePath}`);\n\n\t\t// Bust the import cache if this is the last modified file (or cache busting is forced)\n\t\tif (cacheBust) {\n\t\t\tlastModifiedPaths.set(absolutePath, Date.now());\n\t\t}\n\n\t\tif (cacheBust || lastModifiedPaths.has(absolutePath)) {\n\t\t\tu.searchParams.set(\"_cache_bust\", lastModifiedPaths.get(absolutePath));\n\t\t}\n\n\t\turlPath = u.toString();\n\t} catch (e) {\n\t\turlPath = absolutePath;\n\t}\n\n\tlet promise;\n\tif (requestPromiseCache.has(urlPath)) {\n\t\tpromise = requestPromiseCache.get(urlPath);\n\t} else {\n\t\tpromise = importer(urlPath);\n\t\trequestPromiseCache.set(urlPath, promise);\n\t}\n\n\treturn promise.then(\n\t\t(target) => {\n\t\t\tif (returnRaw) {\n\t\t\t\treturn target;\n\t\t\t}\n\n\t\t\t// If the only export is `default`, elevate to top (for ESM and CJS)\n\t\t\tif (Object.keys(target).length === 1 && \"default\" in target) {\n\t\t\t\treturn target.default;\n\t\t\t}\n\n\t\t\t// When using import() on a CommonJS file that exports an object sometimes it\n\t\t\t// returns duplicated values in `default` key, e.g. `{ default: {key: value}, key: value }`\n\n\t\t\t// A few examples:\n\t\t\t// module.exports = { key: false };\n\t\t\t//    returns `{ default: {key: false}, key: false }` as not expected.\n\t\t\t// module.exports = { key: true };\n\t\t\t// module.exports = { key: null };\n\t\t\t// module.exports = { key: undefined };\n\t\t\t// module.exports = { key: class {} };\n\n\t\t\t// A few examples where it does not duplicate:\n\t\t\t// module.exports = { key: 1 };\n\t\t\t//    returns `{ default: {key: 1} }` as expected.\n\t\t\t// module.exports = { key: \"value\" };\n\t\t\t// module.exports = { key: {} };\n\t\t\t// module.exports = { key: [] };\n\n\t\t\tif (type === \"cjs\" && \"default\" in target) {\n\t\t\t\tlet match = true;\n\t\t\t\tfor (let key in target) {\n\t\t\t\t\tif (key === \"default\") {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tif (key === \"module.exports\") {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tif (target[key] !== target.default[key]) {\n\t\t\t\t\t\tmatch = false;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (match) {\n\t\t\t\t\treturn target.default;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Otherwise return { default: value, named: value }\n\t\t\t// Object.assign here so we can add things to it in JavaScript.js\n\t\t\treturn Object.assign({}, target);\n\t\t},\n\t\t(error) => {\n\t\t\treturn Promise.reject(\n\t\t\t\tnew EleventyImportError(getImportErrorMessage(absolutePath, `import(${type})`), error),\n\t\t\t);\n\t\t},\n\t);\n}\n\nasync function dynamicImport(localPath, type, options = {}) {\n\tlet absolutePath = TemplatePath.absolutePath(localPath);\n\toptions.type = type;\n\n\t// Returns promise\n\treturn dynamicImportAbsolutePath(absolutePath, options);\n}\n\n/* Used to import app configuration files, raw means we don’t normalize away the `default` export */\nasync function dynamicImportRaw(localPath, type) {\n\tlet absolutePath = TemplatePath.absolutePath(localPath);\n\n\t// Returns promise\n\treturn dynamicImportAbsolutePath(absolutePath, { type, returnRaw: true });\n}\n\nexport {\n\tloadContents as EleventyLoadContent,\n\tdynamicImport as EleventyImport,\n\tdynamicImportRaw as EleventyImportRaw,\n};\n"
  },
  {
    "path": "src/Util/RequireUtils.core.js",
    "content": "import { EleventyLoadContent } from \"./Require.js\";\n// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import/with#browser_compatibility\nimport eleventyPackageJson from \"../../package.json\" with { type: \"json\" };\n\n// We *could* prune everything but `name`, `version`, and `type` here but esbuild will still bundle the entire package.json\nexport { eleventyPackageJson };\n\n// noop\nexport function clearRequireCache() {}\n\n// Stub for workaround for https://github.com/nodejs/node/issues/61385\nexport function requireCommonJsTypeScript() {\n\tthrow new Error(\"TypeScript is not supported in this bundle of Eleventy.\");\n}\n\nexport function importJsonSync(path) {\n\t// should not be a no-op\n\tlet rawInput = EleventyLoadContent(path);\n\tif (!rawInput) {\n\t\t// should not error when file exists but is _empty_\n\t\treturn;\n\t}\n\treturn JSON.parse(rawInput);\n}\n"
  },
  {
    "path": "src/Util/RequireUtils.js",
    "content": "import { createRequire } from \"node:module\";\n\n// important to clear the require.cache in CJS projects\nconst require = createRequire(import.meta.url);\n\nexport const eleventyPackageJson = require(\"../../package.json\");\n\nexport function clearRequireCache(absolutePath) {\n\t// ESM Eleventy when using `import()` on a CJS project file still adds to require.cache\n\tif (absolutePath in (require?.cache || {})) {\n\t\tdelete require.cache[absolutePath];\n\t}\n}\n\nexport function importJsonSync(filePath) {\n\tif (!filePath || !filePath.endsWith(\".json\")) {\n\t\tthrow new Error(`importJsonSync expects a .json file extension (received: ${filePath})`);\n\t}\n\n\treturn require(filePath);\n}\n\nexport function requireCommonJsTypeScript(filePath) {\n\treturn require(filePath);\n}\n"
  },
  {
    "path": "src/Util/ReservedData.js",
    "content": "class EleventyReservedDataError extends TypeError {}\n\nclass ReservedData {\n\tstatic fullProperties = [\n\t\t\"pkg\", // Object.freeze’d upstream\n\t\t\"eleventy\", // Object.freeze’d upstream\n\t\t// \"page\" is only frozen for specific subproperties below\n\t\t\"content\",\n\t\t\"collections\",\n\t];\n\n\tstatic properties = [\n\t\t// \"page\" is only frozen for specific subproperties below\n\t\t\"content\",\n\t\t\"collections\",\n\t];\n\n\tstatic pageProperties = [\n\t\t\"date\",\n\t\t\"inputPath\",\n\t\t\"fileSlug\",\n\t\t\"filePathStem\",\n\t\t\"outputFileExtension\",\n\t\t\"templateSyntax\",\n\t\t\"url\",\n\t\t\"outputPath\",\n\t\t// not yet `excerpt` or `lang` set via front matter and computed data\n\t];\n\n\t// Check in the data cascade for reserved data properties.\n\tstatic getReservedKeys(data, globalProperties = this.fullProperties) {\n\t\tif (!data) {\n\t\t\treturn [];\n\t\t}\n\n\t\tlet keys = globalProperties.filter((key) => {\n\t\t\treturn key in data;\n\t\t});\n\n\t\tif (\"page\" in data) {\n\t\t\tif (typeof data.page === \"object\") {\n\t\t\t\tfor (let key of this.pageProperties) {\n\t\t\t\t\tif (key in data.page) {\n\t\t\t\t\t\tkeys.push(`page.${key}`);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// fail `page` when set to non-object values.\n\t\t\t\tkeys.push(\"page\");\n\t\t\t}\n\t\t}\n\t\treturn keys;\n\t}\n\n\tstatic #check(data, sourceLocation, propertiesList) {\n\t\tlet reservedNames = ReservedData.getReservedKeys(data, propertiesList);\n\t\tif (reservedNames.length === 0) {\n\t\t\treturn;\n\t\t}\n\t\tthrow this.getError({\n\t\t\treservedNames,\n\t\t\tsourceLocation,\n\t\t});\n\t}\n\n\t// check for frozen objects too\n\tstatic check(data, sourceLocation) {\n\t\tthis.#check(data, sourceLocation, this.fullProperties);\n\t}\n\n\tstatic checkSubset(data, sourceLocation) {\n\t\tthis.#check(data, sourceLocation, this.properties);\n\t}\n\n\tstatic getError(options = {}) {\n\t\tlet { reservedNames, cause, sourceLocation } = options || {};\n\n\t\tif (cause) {\n\t\t\treservedNames ??= cause.reservedNames;\n\t\t}\n\n\t\tlet e = new EleventyReservedDataError(\n\t\t\t`You attempted to set one of Eleventy’s reserved data property names${reservedNames ? `: ${reservedNames.join(\", \")}` : \"\"}${sourceLocation ? ` (source: ${sourceLocation})` : \"\"}. You can opt-out of this behavior with \\`eleventyConfig.setFreezeReservedData(false)\\` or rename/remove the property in your data cascade that conflicts with Eleventy’s reserved property names (e.g. \\`eleventy\\`, \\`pkg\\`, and others). Learn more: https://v3.11ty.dev/docs/data-eleventy-supplied/`,\n\t\t\t{ cause },\n\t\t);\n\n\t\tif (reservedNames) {\n\t\t\te.reservedNames = reservedNames;\n\t\t}\n\t\treturn e;\n\t}\n\n\tstatic isFrozenError(e) {\n\t\treturn (\n\t\t\te instanceof TypeError &&\n\t\t\te.message.startsWith(\"Cannot add property\") &&\n\t\t\te.message.endsWith(\"not extensible\")\n\t\t);\n\t}\n\n\tstatic isReservedDataError(e) {\n\t\treturn e instanceof EleventyReservedDataError;\n\t}\n}\n\nexport default ReservedData;\n"
  },
  {
    "path": "src/Util/ResolvePlugin.client.js",
    "content": "export function resolvePlugin() {\n\tthrow new Error(\n\t\t\"eleventyConfig.resolvePlugin() is not supported in the `@11ty/client` bundle. You can switch to use the larger `@11ty/client/eleventy` bundle or `import` plugins directly.\",\n\t);\n}\n"
  },
  {
    "path": "src/Util/ResolvePlugin.js",
    "content": "import HtmlBasePlugin from \"../Plugins/HtmlBasePlugin.js\";\nimport InputPathToUrlPlugin from \"../Plugins/InputPathToUrl.js\";\n\nexport function resolvePlugin(name) {\n\tlet filenameLookup = {\n\t\t// Sync, https://github.com/11ty/eleventy-plugin-rss/issues/52\n\t\t\"@11ty/eleventy/html-base-plugin\": HtmlBasePlugin,\n\t\t\"@11ty/eleventy/inputpath-to-url-plugin\": InputPathToUrlPlugin,\n\n\t\t// Async plugins:\n\t\t// v4 moved RenderPlugin async for bundle size (Liquid import)\n\t\t\"@11ty/eleventy/render-plugin\": \"./Plugins/RenderPlugin.js\", // Liquid is ~73KB min\n\t\t\"@11ty/eleventy/i18n-plugin\": \"./Plugins/I18nPlugin.js\", // bcp-47-normalize is ~180KB min\n\t};\n\n\tif (!filenameLookup[name]) {\n\t\tthrow new Error(\n\t\t\t`Invalid name \"${name}\" passed to resolvePlugin. Valid options: ${Object.keys(filenameLookup).join(\", \")}`,\n\t\t);\n\t}\n\n\t// Future improvement: add support for any npm package name?\n\tif (typeof filenameLookup[name] === \"string\") {\n\t\t// returns promise\n\t\treturn import(/* @vite-ignore */ filenameLookup[name]).then((plugin) => plugin.default);\n\t}\n\n\t// return reference\n\treturn filenameLookup[name];\n}\n"
  },
  {
    "path": "src/Util/RetrieveGlobals.client.js",
    "content": "export async function RetrieveGlobals(code, filePath) {\n\tlet target = `data:text/javascript;charset=utf-8,${encodeURIComponent(code)}`;\n\treturn import(/* @vite-ignore */ target).then((result) => {\n\t\tif (Object.keys(result).length === 0) {\n\t\t\tconsole.warn(\n\t\t\t\t`Arbitrary JavaScript front matter expects the use of \\`export\\` when used with the \\`@11ty/client\\` bundle (${filePath}). Add export or swap to use the \\`@11ty/client/eleventy\\` bundle instead.`,\n\t\t\t);\n\t\t}\n\t\treturn result;\n\t});\n}\n"
  },
  {
    "path": "src/Util/RetrieveGlobals.core.js",
    "content": "import { importFromString } from \"import-module-string\";\n\nexport async function RetrieveGlobals(code, filePath) {\n\tlet data = {\n\t\tpage: {\n\t\t\t// Theoretically fileSlug and filePathStem could be added here but require extensionMap\n\t\t\tinputPath: filePath,\n\t\t},\n\t};\n\n\t// Do *not* error when imports are found because they might be mapped via an Import Map.\n\treturn importFromString(code, { data, filePath });\n}\n"
  },
  {
    "path": "src/Util/RetrieveGlobals.js",
    "content": "import { RetrieveGlobals as NodeRetrieveGlobals } from \"node-retrieve-globals\";\nimport { parseCode, walkCode, importFromString } from \"import-module-string\";\nimport { isBuiltin } from \"node:module\";\n\nexport async function RetrieveGlobals(code, filePath, options = {}) {\n\tlet { isJavaScriptFrontMatterCompat } = Object.assign(\n\t\t{ isJavaScriptFrontMatterCompat: false },\n\t\toptions,\n\t);\n\tlet data = {\n\t\tpage: {\n\t\t\t// Theoretically fileSlug and filePathStem could be added here but require extensionMap\n\t\t\tinputPath: filePath,\n\t\t},\n\t};\n\n\tlet ast = parseCode(code);\n\tlet { imports } = walkCode(ast);\n\n\tlet nonBuiltinImports = Array.from(imports).filter((name) => !isBuiltin(name));\n\tif (nonBuiltinImports.length === 0) {\n\t\tlet implicitExports = isJavaScriptFrontMatterCompat ? false : true;\n\t\treturn importFromString(code, { ast, data, filePath, implicitExports });\n\t}\n\n\t// TODO re-use already parsed AST from `import-module-string` in `node-retrieve-globals`\n\tlet vm = new NodeRetrieveGlobals(code, {\n\t\tfilePath,\n\t\t// ignored if vm.Module is stable (or --experimental-vm-modules)\n\t\ttransformEsmImports: true,\n\t});\n\n\t// Future warning until vm.Module is stable:\n\t// If the frontMatterCode uses `import` this uses the `experimentalModuleApi`\n\t// option in node-retrieve-globals to workaround https://github.com/zachleat/node-retrieve-globals/issues/2\n\n\t// this is async, but it’s handled in Eleventy upstream.\n\treturn vm.getGlobalContext(data, {\n\t\treuseGlobal: true,\n\t\tdynamicImport: true,\n\t\t// addRequire: true,\n\t});\n}\n"
  },
  {
    "path": "src/Util/SemverCoerce.js",
    "content": "// This replaces use of semver/functions/coerce.js (which had more than we needed, we’re only targeting our local package.json for eleventy supplied global data)\n\nexport function coerce(version) {\n\tlet s = String(version);\n\tif (s.startsWith(\"v\")) {\n\t\ts = s.slice(1);\n\t}\n\t// Remove pre-release identifier\n\treturn s.split(\"-\")[0];\n}\n"
  },
  {
    "path": "src/Util/SetUtil.js",
    "content": "export function union(...sets) {\n\tlet root = new Set();\n\tfor (let set of sets) {\n\t\tfor (let entry of set) {\n\t\t\troot.add(entry);\n\t\t}\n\t}\n\treturn root;\n}\n"
  },
  {
    "path": "src/Util/TemplateDepGraph.js",
    "content": "import { DepGraph as DependencyGraph } from \"dependency-graph\";\nimport debugUtil from \"debug\";\n\nconst debug = debugUtil(\"Eleventy:TemplateDepGraph\");\n\nconst COLLECTION_PREFIX = \"__collection:\";\n\nexport class TemplateDepGraph extends DependencyGraph {\n\tstatic STAGES = [\"[basic]\", \"[userconfig]\", \"[keys]\", \"all\"];\n\n\t#configCollectionNames = new Set();\n\n\tconstructor() {\n\t\t// BREAKING TODO move this back to non-circular with errors\n\t\tsuper({ circular: true });\n\n\t\tlet previous;\n\t\t// establish stage relationships, all uses keys, keys uses userconfig, userconfig uses tags\n\t\tfor (let stageName of TemplateDepGraph.STAGES.filter(Boolean).reverse()) {\n\t\t\tlet stageKey = `${COLLECTION_PREFIX}${stageName}`;\n\t\t\tif (previous) {\n\t\t\t\tthis.uses(previous, stageKey);\n\t\t\t}\n\t\t\tprevious = stageKey;\n\t\t}\n\t}\n\n\tuses(from, to) {\n\t\tthis.addDependency(from, to);\n\t}\n\n\taddTag(tagName, type) {\n\t\tif (\n\t\t\ttagName === \"all\" ||\n\t\t\t(tagName.startsWith(\"[\") && tagName.endsWith(\"]\")) ||\n\t\t\tthis.#configCollectionNames.has(tagName)\n\t\t) {\n\t\t\treturn;\n\t\t}\n\t\tif (!type) {\n\t\t\tthrow new Error(\n\t\t\t\t`Missing tag type for addTag. Expecting one of ${TemplateDepGraph.STAGES.map((entry) => entry.slice(1, -1)).join(\" or \")}. Received: ${type}`,\n\t\t\t);\n\t\t}\n\n\t\tdebug(\"collection type %o uses tag %o\", tagName, type);\n\n\t\tthis.uses(`${COLLECTION_PREFIX}[${type}]`, `${COLLECTION_PREFIX}${tagName}`);\n\t}\n\n\taddConfigCollectionName(collectionName) {\n\t\tif (collectionName === \"all\") {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.#configCollectionNames.add(collectionName);\n\t\t// Collection relationships to `[userconfig]` are added last, in unfilteredOrder()\n\t}\n\n\tcleanupCollectionNames(collectionNames = []) {\n\t\tlet s = new Set(collectionNames);\n\t\tif (s.has(\"[userconfig]\")) {\n\t\t\treturn collectionNames;\n\t\t}\n\n\t\tlet hasAnyConfigCollections = collectionNames.find((name) => {\n\t\t\tif (this.#configCollectionNames.has(name)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\treturn false;\n\t\t});\n\n\t\tif (hasAnyConfigCollections) {\n\t\t\ts.add(\"[userconfig]\");\n\t\t}\n\n\t\treturn Array.from(s);\n\t}\n\n\taddTemplate(filePath, consumes = [], publishesTo = []) {\n\t\t// Move to the beginning if it doesn’t consume anything\n\t\tif (consumes.length === 0) {\n\t\t\tthis.uses(`${COLLECTION_PREFIX}[basic]`, filePath);\n\t\t}\n\n\t\tconsumes = this.cleanupCollectionNames(consumes);\n\t\tpublishesTo = this.cleanupCollectionNames(publishesTo);\n\t\t// Can’t consume AND publish to `all` simultaneously\n\t\tlet consumesAll = consumes.includes(\"all\");\n\t\tif (consumesAll) {\n\t\t\tpublishesTo = publishesTo.filter((entry) => entry !== \"all\");\n\t\t}\n\n\t\tdebug(\"%o consumes %o and publishes to %o\", filePath, consumes, publishesTo);\n\n\t\tfor (let collectionName of publishesTo) {\n\t\t\tif (!consumesAll) {\n\t\t\t\tlet tagType = \"basic\";\n\n\t\t\t\tlet consumesUserConfigCollection = consumes.includes(\"[userconfig]\");\n\t\t\t\tif (consumesUserConfigCollection) {\n\t\t\t\t\t// must finish before [keys]\n\t\t\t\t\ttagType = \"keys\";\n\t\t\t\t}\n\n\t\t\t\tthis.addTag(collectionName, tagType);\n\t\t\t}\n\n\t\t\tthis.uses(`${COLLECTION_PREFIX}${collectionName}`, filePath);\n\t\t}\n\n\t\tfor (let collectionName of consumes) {\n\t\t\tthis.uses(filePath, `${COLLECTION_PREFIX}${collectionName}`);\n\n\t\t\tlet stageIndex = TemplateDepGraph.STAGES.indexOf(collectionName);\n\t\t\tlet nextStage = stageIndex > 0 ? TemplateDepGraph.STAGES[stageIndex + 1] : undefined;\n\t\t\tif (nextStage) {\n\t\t\t\tthis.uses(`${COLLECTION_PREFIX}${nextStage}`, filePath);\n\t\t\t}\n\t\t}\n\t}\n\n\taddDependency(from, to) {\n\t\tif (!this.hasNode(from)) {\n\t\t\tthis.addNode(from);\n\t\t}\n\t\tif (!this.hasNode(to)) {\n\t\t\tthis.addNode(to);\n\t\t}\n\t\tsuper.addDependency(from, to);\n\t}\n\n\tunfilteredOrder() {\n\t\t// these need to be added last, after the template map has been added (see addConfigCollectionName)\n\t\tfor (let collectionName of this.#configCollectionNames) {\n\t\t\tthis.uses(`${COLLECTION_PREFIX}[keys]`, `${COLLECTION_PREFIX}${collectionName}`);\n\t\t}\n\n\t\treturn super.overallOrder();\n\t}\n\n\toverallOrder() {\n\t\tlet unfiltered = this.unfilteredOrder();\n\n\t\tlet filtered = unfiltered.filter((entry) => {\n\t\t\tif (entry === `${COLLECTION_PREFIX}[keys]`) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\treturn !entry.startsWith(`${COLLECTION_PREFIX}[`) && !entry.endsWith(\"]\");\n\t\t});\n\n\t\tlet allKey = `${COLLECTION_PREFIX}all`;\n\t\t// Add another collections.all entry to the end (if not already the last one)\n\t\tif (filtered[filtered.length - 1] !== allKey) {\n\t\t\tfiltered.push(allKey);\n\t\t}\n\n\t\treturn filtered;\n\t}\n}\n"
  },
  {
    "path": "src/Util/TransformsUtil.js",
    "content": "import EleventyBaseError from \"../Errors/EleventyBaseError.js\";\nimport { isPlainObject } from \"@11ty/eleventy-utils\";\nimport debugUtil from \"debug\";\n\nconst debug = debugUtil(\"Eleventy:Transforms\");\n\nclass EleventyTransformError extends EleventyBaseError {}\n\nclass TransformsUtil {\n\tstatic changeTransformsToArray(transformsObj) {\n\t\tlet transforms = [];\n\t\tfor (let name in transformsObj) {\n\t\t\ttransforms.push({\n\t\t\t\tname: name,\n\t\t\t\tcallback: transformsObj[name],\n\t\t\t});\n\t\t}\n\t\treturn transforms;\n\t}\n\n\tstatic async runAll(content, pageData, transforms = {}, options = {}) {\n\t\tlet { baseHrefOverride, logger } = options;\n\t\tlet { inputPath, outputPath, url } = pageData;\n\n\t\tif (!isPlainObject(transforms)) {\n\t\t\tthrow new Error(\"Object of transforms expected.\");\n\t\t}\n\n\t\tlet transformsArray = this.changeTransformsToArray(transforms);\n\n\t\tfor (let { callback, name } of transformsArray) {\n\t\t\tdebug(\"Running %o transform on %o: %o\", name, inputPath, outputPath);\n\n\t\t\ttry {\n\t\t\t\tlet hadContentBefore = !!content;\n\n\t\t\t\tcontent = await callback.call(\n\t\t\t\t\t{\n\t\t\t\t\t\tinputPath,\n\t\t\t\t\t\toutputPath,\n\t\t\t\t\t\turl,\n\t\t\t\t\t\tpage: pageData,\n\t\t\t\t\t\tbaseHref: baseHrefOverride,\n\t\t\t\t\t},\n\t\t\t\t\tcontent,\n\t\t\t\t\toutputPath,\n\t\t\t\t);\n\n\t\t\t\tif (hadContentBefore && !content) {\n\t\t\t\t\tif (!logger || !logger.warn) {\n\t\t\t\t\t\tthrow new Error(\"Internal error: missing `logger` instance.\");\n\t\t\t\t\t}\n\n\t\t\t\t\tlogger.warn(\n\t\t\t\t\t\t`Warning: Transform \\`${name}\\` returned empty when writing ${outputPath} from ${inputPath}.`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t} catch (e) {\n\t\t\t\tthrow new EleventyTransformError(\n\t\t\t\t\t`Transform \\`${name}\\` encountered an error when transforming ${inputPath}.`,\n\t\t\t\t\te,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\treturn content;\n\t}\n}\n\nexport default TransformsUtil;\n"
  },
  {
    "path": "src/Util/TypeScript/TypeScriptSample.cts",
    "content": "module.exports = function(b: boolean, s: string) {}\n"
  },
  {
    "path": "src/Util/UrlUtil.js",
    "content": "export function isValidUrl(url) {\n\ttry {\n\t\tnew URL(url);\n\t\treturn true;\n\t} catch (e) {\n\t\t// invalid url OR local path\n\t\treturn false;\n\t}\n}\n\nexport function getDirectoryFromUrl(url) {\n\tif (url === false) {\n\t\treturn false;\n\t}\n\n\t// returns a url\n\tif (url.endsWith(\"/\")) {\n\t\treturn url;\n\t}\n\n\tlet parts = url.split(\"/\");\n\tparts.pop();\n\treturn parts.join(\"/\") + \"/\";\n}\n"
  },
  {
    "path": "src/Util/importer.client.js",
    "content": "export default function importer(relPath) {\n\t// TODO we could probably use a super streamlined version of import-module-string here that doesn’t support imports!\n\tthrow new Error(\n\t\t\"Dynamic import() is not supported in the `@11ty/client` bundle. Use the `@11ty/client/eleventy` bundle instead.\",\n\t);\n}\n"
  },
  {
    "path": "src/Util/importer.core.js",
    "content": "import { existsSync, readFileSync } from \"node:fs\";\nimport { importFromString } from \"import-module-string\";\n\nimport { fileURLToPath } from \"../Adapters/Packages/url.js\";\nimport { EleventyLoadContent } from \"./Require.js\";\n\nexport default function importer(relPath) {\n\tlet filePath = fileURLToPath(relPath);\n\n\t// `import-module-string` can now `import()` so we avoid needing to esbuild these\n\tlet code = EleventyLoadContent(filePath);\n\treturn importFromString(code, {\n\t\timplicitExports: false,\n\t\tfilePath,\n\t\tresolveImportContent: function (modInfo = {}) {\n\t\t\tif (modInfo.mode !== \"relative\") {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (!existsSync(modInfo.path)) {\n\t\t\t\tthrow new Error(\"Could not find content for module: \" + JSON.stringify(modInfo));\n\t\t\t}\n\n\t\t\treturn readFileSync(modInfo.path, \"utf8\");\n\t\t},\n\t});\n\n\t// import { parseCode, walkCode, importFromString } from \"import-module-string\";\n\t// Alternative approach saved for posterity (and could be used to warn about modules needing to be Import Mapped):\n\t// \tlet ast = parseCode(code);\n\t// \tlet { imports } = walkCode(ast);\n\t// \tif(imports.size === 0) {\n\t// \t\treturn importFromString(code, { ast, filePath });\n\t// \t}\n\t// \t// This file needs to be esbuild-ed\n\t// \treturn import(filePath);\n\t// }\n}\n"
  },
  {
    "path": "src/Util/importer.js",
    "content": "export default function importer(relPath) {\n\treturn import(relPath);\n}\n"
  },
  {
    "path": "src/Util/spawn.core.js",
    "content": "export function spawnAsync() {\n\tthrow new Error(\"This feature is not supported in `@11ty/client` bundles.\");\n}\n"
  },
  {
    "path": "src/Util/spawn.js",
    "content": "import { spawn } from \"node:child_process\";\nimport { withResolvers } from \"./PromiseUtil.js\";\n\nexport function spawnAsync(command, args, options) {\n\tlet { promise, resolve, reject } = withResolvers();\n\n\tconst cmd = spawn(command, args, options);\n\tlet res = [];\n\tcmd.stdout.on(\"data\", (data) => {\n\t\tres.push(data.toString(\"utf8\"));\n\t});\n\n\tlet err = [];\n\tcmd.stderr.on(\"data\", (data) => {\n\t\terr.push(data.toString(\"utf8\"));\n\t});\n\n\tcmd.on(\"close\", (code) => {\n\t\tif (err.length > 0) {\n\t\t\treject(err.join(\"\\n\"));\n\t\t} else if (code === 1) {\n\t\t\treject(\"Internal error: process closed with error exit code.\");\n\t\t} else {\n\t\t\tresolve(res.join(\"\\n\"));\n\t\t}\n\t});\n\n\treturn promise;\n}\n"
  },
  {
    "path": "src/Watch.js",
    "content": "import debugUtil from \"debug\";\nimport { TemplatePath } from \"@11ty/eleventy-utils\";\nimport chokidar from \"chokidar\";\n\nimport { isGlobMatch } from \"./Util/GlobMatcher.js\";\nimport { GlobStripper } from \"./Util/GlobStripper.js\";\n\nconst debug = debugUtil(\"Eleventy:Watch\");\n\nexport class Watch {\n\t/** @type {module:chokidar} */\n\t#chokidar;\n\t/** @type {Set} */\n\t#watchedGlobs = [];\n\t/** @type {Set} */\n\t#ignoredGlobs = [];\n\n\tconstructor(config) {\n\t\tif (!config || config.constructor.name !== \"TemplateConfig\") {\n\t\t\tthrow new Error(\"Internal error: Missing or invalid `config` argument.\");\n\t\t}\n\t\tthis.templateConfig = config;\n\t}\n\n\tgetChokidarConfig() {\n\t\tlet options = Object.assign(\n\t\t\t{\n\t\t\t\tignoreInitial: true,\n\t\t\t\tawaitWriteFinish: {\n\t\t\t\t\tstabilityThreshold: 150,\n\t\t\t\t\tpollInterval: 25,\n\t\t\t\t},\n\t\t\t},\n\t\t\tthis.templateConfig.userConfig.chokidarConfig,\n\t\t);\n\n\t\t// unsupported: using your own `ignored`\n\t\tif (options.ignored) {\n\t\t\tdelete options.ignored;\n\t\t}\n\n\t\treturn options;\n\t}\n\n\t// alias for watchTargets() (backwards compat)\n\tadd(targets = []) {\n\t\tthis.watchTargets(targets);\n\t}\n\n\twatchTargets(targets = []) {\n\t\tlet uniqueSet = new Set();\n\t\tfor (let target of targets) {\n\t\t\tthis.#watchedGlobs.push(TemplatePath.stripLeadingDotSlash(target));\n\n\t\t\t// strip globs off of target, chokidar@4\n\t\t\tlet { path } = GlobStripper.parse(target);\n\t\t\tif (path) {\n\t\t\t\tuniqueSet.add(path);\n\t\t\t}\n\t\t}\n\n\t\tthis.#chokidar?.add(Array.from(uniqueSet));\n\t}\n\n\taddIgnores(ignores) {\n\t\tfor (let target of ignores) {\n\t\t\tthis.#ignoredGlobs.push(target);\n\t\t}\n\t}\n\n\t#isDirectory(path) {\n\t\treturn this.templateConfig.existsCache.isDirectory(path);\n\t}\n\n\tasync start() {\n\t\tlet options = this.getChokidarConfig();\n\n\t\toptions.ignored = (filepath) => {\n\t\t\t// don’t ignore root (if specified)\n\t\t\tif (filepath === \".\") {\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tif (this.#ignoredGlobs.length > 0 && isGlobMatch(filepath, this.#ignoredGlobs)) {\n\t\t\t\tdebug(\"Ignore file (ignore globs)\", filepath);\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\t// don’t ignore directories that are not in ignores\n\t\t\tif (this.#isDirectory(filepath)) {\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\t// make sure this matches at least one of the original globs\n\t\t\tif (this.#watchedGlobs.length === 0 || !isGlobMatch(filepath, this.#watchedGlobs)) {\n\t\t\t\tdebug(\"Ignore file (no glob match)\", filepath, this.#watchedGlobs);\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\treturn false;\n\t\t};\n\n\t\t// strip globs off of target, chokidar@4\n\t\tlet targets = this.#watchedGlobs\n\t\t\t.map((target) => {\n\t\t\t\tlet { path } = GlobStripper.parse(target);\n\t\t\t\treturn path;\n\t\t\t})\n\t\t\t.filter(Boolean);\n\n\t\tthis.#chokidar = chokidar.watch(targets, options);\n\n\t\t// Note: if there are no watch targets the `ready` event doesn’t fire so skip it\n\t\tif (targets.length > 0) {\n\t\t\tawait new Promise((resolve) => {\n\t\t\t\tthis.#chokidar.on(\"ready\", () => resolve());\n\t\t\t});\n\t\t}\n\t}\n\n\ton(event, callback) {\n\t\tthis.#chokidar.on(event, callback);\n\t}\n\n\tasync close() {\n\t\treturn this.#chokidar?.close();\n\t}\n}\n"
  },
  {
    "path": "src/WatchQueue.js",
    "content": "import { TemplatePath } from \"@11ty/eleventy-utils\";\n\nimport PathNormalizer from \"./Util/PathNormalizer.js\";\n\n/* Decides when to watch and in what mode to watch\n * Incremental builds don’t batch changes, they queue.\n * Nonincremental builds batch.\n */\n\nclass WatchQueue {\n\tconstructor() {\n\t\tthis.incremental = false;\n\t\tthis.isActive = false;\n\t\tthis.activeQueue = [];\n\t}\n\n\tisBuildRunning() {\n\t\treturn this.isActive;\n\t}\n\n\tsetBuildRunning() {\n\t\tthis.isActive = true;\n\n\t\t// pop waiting queue into the active queue\n\t\tthis.activeQueue = this.popNextActiveQueue();\n\t}\n\n\tsetBuildFinished() {\n\t\tthis.isActive = false;\n\t\tthis.activeQueue = [];\n\t}\n\n\tgetIncrementalFile() {\n\t\tif (this.incremental) {\n\t\t\treturn this.activeQueue.length ? this.activeQueue[0] : false;\n\t\t}\n\n\t\treturn false;\n\t}\n\n\t/* Returns the changed files currently being operated on in the current `watch` build\n\t * Works with or without incremental (though in incremental only one file per time will be processed)\n\t */\n\tgetActiveQueue() {\n\t\tif (!this.isActive) {\n\t\t\treturn [];\n\t\t} else if (this.incremental && this.activeQueue.length === 0) {\n\t\t\treturn [];\n\t\t} else if (this.incremental) {\n\t\t\treturn [this.activeQueue[0]];\n\t\t}\n\n\t\treturn this.activeQueue;\n\t}\n\n\t_queueMatches(file) {\n\t\tlet filterCallback;\n\t\tif (typeof file === \"function\") {\n\t\t\tfilterCallback = file;\n\t\t} else {\n\t\t\tfilterCallback = (path) => path === file;\n\t\t}\n\n\t\treturn this.activeQueue.filter(filterCallback);\n\t}\n\n\thasAllQueueFiles(file) {\n\t\treturn (\n\t\t\tthis.activeQueue.length > 0 && this.activeQueue.length === this._queueMatches(file).length\n\t\t);\n\t}\n\n\thasQueuedFile(file) {\n\t\tif (file) {\n\t\t\treturn this._queueMatches(file).length > 0;\n\t\t}\n\t\treturn false;\n\t}\n\n\thasQueuedFiles(files) {\n\t\tfor (const file of files) {\n\t\t\tif (this.hasQueuedFile(file)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n\n\tget pendingQueue() {\n\t\tif (!this._queue) {\n\t\t\tthis._queue = [];\n\t\t}\n\t\treturn this._queue;\n\t}\n\n\tset pendingQueue(value) {\n\t\tthis._queue = value;\n\t}\n\n\taddToPendingQueue(path) {\n\t\tif (path) {\n\t\t\tpath = PathNormalizer.normalizeSeperator(TemplatePath.addLeadingDotSlash(path));\n\t\t\tthis.pendingQueue.push(path);\n\t\t}\n\t}\n\n\tgetPendingQueueSize() {\n\t\treturn this.pendingQueue.length;\n\t}\n\n\tgetPendingQueue() {\n\t\treturn this.pendingQueue;\n\t}\n\n\tgetActiveQueueSize() {\n\t\treturn this.activeQueue.length;\n\t}\n\n\t// returns array\n\tpopNextActiveQueue() {\n\t\tif (this.incremental) {\n\t\t\treturn this.pendingQueue.length ? [this.pendingQueue.shift()] : [];\n\t\t}\n\n\t\tlet ret = this.pendingQueue.slice();\n\t\tthis.pendingQueue = [];\n\t\treturn ret;\n\t}\n}\n\nexport default WatchQueue;\n"
  },
  {
    "path": "src/WatchTargets.js",
    "content": "import { TemplatePath } from \"@11ty/eleventy-utils\";\nimport { DepGraph } from \"dependency-graph\";\nimport { mergeGraphs } from \"@11ty/dependency-tree-esm\";\n\nimport JavaScriptDependencies from \"./Util/JavaScriptDependencies.js\";\nimport eventBus from \"./EventBus.js\";\n\nexport default class WatchTargets {\n\t#templateConfig;\n\n\tconstructor(templateConfig) {\n\t\tthis.targets = new Set();\n\t\tthis.dependencies = new Set();\n\t\tthis.newTargets = new Set();\n\t\tthis.isEsm = false;\n\n\t\tthis.graph = new DepGraph();\n\t\tthis.#templateConfig = templateConfig;\n\t}\n\n\tsetProjectUsingEsm(isEsmProject) {\n\t\tthis.isEsm = !!isEsmProject;\n\t}\n\n\tisJavaScriptDependency(path) {\n\t\treturn this.dependencies.has(path);\n\t}\n\n\treset() {\n\t\tthis.newTargets = new Set();\n\t}\n\n\taddToDependencyGraph(parent, deps) {\n\t\tif (!this.graph.hasNode(parent)) {\n\t\t\tthis.graph.addNode(parent);\n\t\t}\n\t\tfor (let dep of deps) {\n\t\t\tif (!this.graph.hasNode(dep)) {\n\t\t\t\tthis.graph.addNode(dep);\n\t\t\t}\n\t\t\tthis.graph.addDependency(parent, dep);\n\t\t}\n\t}\n\n\tuses(parent, dep) {\n\t\treturn this.getDependenciesOf(parent).includes(dep);\n\t}\n\n\tgetDependenciesOf(parent) {\n\t\tif (!this.graph.hasNode(parent)) {\n\t\t\treturn [];\n\t\t}\n\t\treturn this.graph.dependenciesOf(parent);\n\t}\n\n\tgetDependantsOf(child) {\n\t\tif (!this.graph.hasNode(child)) {\n\t\t\treturn [];\n\t\t}\n\t\treturn this.graph.dependantsOf(child);\n\t}\n\n\taddRaw(targets, isDependency) {\n\t\tfor (let target of targets) {\n\t\t\tlet path = TemplatePath.addLeadingDotSlash(target);\n\t\t\tif (!this.targets.has(target)) {\n\t\t\t\tthis.newTargets.add(path);\n\t\t\t}\n\n\t\t\tthis.targets.add(path);\n\n\t\t\tif (isDependency) {\n\t\t\t\tthis.dependencies.add(path);\n\t\t\t}\n\t\t}\n\t}\n\n\tstatic toArray(targets) {\n\t\tif (!targets) {\n\t\t\treturn [];\n\t\t} else if (Array.isArray(targets)) {\n\t\t\treturn targets;\n\t\t}\n\n\t\treturn [targets];\n\t}\n\n\t// add only a target\n\tadd(targets) {\n\t\tthis.addRaw(WatchTargets.toArray(targets));\n\t}\n\n\tstatic normalizeToGlobs(targets) {\n\t\treturn WatchTargets.toArray(targets).map((entry) =>\n\t\t\tTemplatePath.convertToRecursiveGlobSync(entry),\n\t\t);\n\t}\n\n\t// add only a target’s dependencies\n\tasync addDependencies(targets, filterCallback) {\n\t\tif (this.#templateConfig && !this.#templateConfig.shouldSpiderJavaScriptDependencies()) {\n\t\t\treturn;\n\t\t}\n\n\t\ttargets = WatchTargets.toArray(targets);\n\t\tlet cjsDeps = Array.from(\n\t\t\tawait JavaScriptDependencies.getCommonJsDependencies(targets, this.isEsm),\n\t\t);\n\t\tif (filterCallback) {\n\t\t\tcjsDeps = cjsDeps.filter(filterCallback);\n\t\t}\n\t\tfor (let target of targets) {\n\t\t\tthis.addToDependencyGraph(target, cjsDeps);\n\t\t}\n\t\tthis.addRaw(cjsDeps, true);\n\n\t\t// https://github.com/11ty/eleventy/issues/3899\n\t\t// Note that this fix is ESM-only, dependency-tree CJS doesn’t support returning graphs (yet?)\n\t\tlet esmGraph = await JavaScriptDependencies.getEsmGraph(targets, this.isEsm);\n\t\tif (filterCallback) {\n\t\t\tfor (let node of esmGraph.overallOrder()) {\n\t\t\t\tif (!filterCallback(node)) {\n\t\t\t\t\tesmGraph.removeNode(node);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tmergeGraphs(this.graph, esmGraph);\n\n\t\t// ESM graph includes original targets, which we do not want for addRaw so we’ll remove them before adding\n\t\tlet rawEsmGraph = esmGraph.clone();\n\t\tfor (let t of targets) {\n\t\t\trawEsmGraph.removeNode(t);\n\t\t}\n\t\tthis.addRaw(rawEsmGraph.overallOrder(), true);\n\t}\n\n\tsetWriter(templateWriter) {\n\t\tthis.writer = templateWriter;\n\t}\n\n\tclearImportCacheFor(filePathArray) {\n\t\tlet paths = new Set();\n\t\tfor (const filePath of filePathArray) {\n\t\t\tpaths.add(filePath);\n\n\t\t\t// Delete from require cache so that updates to the module are re-required\n\t\t\tlet importsTheChangedFile = this.getDependantsOf(filePath);\n\t\t\tfor (let dep of importsTheChangedFile) {\n\t\t\t\tpaths.add(dep);\n\t\t\t}\n\n\t\t\tlet isImportedInTheChangedFile = this.getDependenciesOf(filePath);\n\t\t\tfor (let dep of isImportedInTheChangedFile) {\n\t\t\t\tpaths.add(dep);\n\t\t\t}\n\n\t\t\t// Use GlobalDependencyMap\n\t\t\tlet dependantsMapped = this.#templateConfig?.usesGraph.getDependantsFor(filePath) || [];\n\t\t\tfor (let dep of dependantsMapped) {\n\t\t\t\tpaths.add(dep);\n\t\t\t}\n\t\t}\n\n\t\teventBus.emit(\"eleventy.importCacheReset\", paths);\n\t}\n\n\tgetNewTargetsSinceLastReset() {\n\t\treturn Array.from(this.newTargets);\n\t}\n\n\tgetTargets() {\n\t\treturn Array.from(this.targets);\n\t}\n}\n"
  },
  {
    "path": "src/defaultConfig.js",
    "content": "import fullBundleDefaultConfig from \"./defaultConfigExtended.js\";\nimport TransformsUtil from \"./Util/TransformsUtil.js\";\n\n/**\n * @module 11ty/eleventy/defaultConfig\n */\n\n/**\n * @callback addFilter - Register a global filter.\n * @param {string} name - Register a template filter by this name.\n * @param {function} callback - The filter logic.\n */\n\n/**\n * @typedef {object} config\n * @property {addFilter} addFilter - Register a new global filter.\n * @property {addPlugin} addPlugin - Execute or defer a plugin’s execution.\n * @property {addTransform} addTransform - Add an Eleventy transform to postprocess template output\n */\n\n/**\n * @typedef {object} defaultConfig\n * @property {Array<string>} templateFormats - An array of accepted template formats.\n * @property {Array<string>} dataFileSuffixes - Array of file suffixes for data files in the Data Cascade.\n * @property {boolean} [dataFileDirBaseNameOverride=false] - Use index.* instead of dirname.* for Directory Data File names\n * @property {string} [pathPrefix='/'] - The directory under which all output files should be written to.\n * @property {string} [markdownTemplateEngine='liquid'] - Template engine to process markdown files with.\n * @property {string} [htmlTemplateEngine='liquid'] - Template engine to process html files with.\n * @property {boolean} [dataTemplateEngine=false] - Changed in v1.0\n * @property {string} [jsDataFileSuffix='.11tydata'] - File suffix for jsData files.\n * @property {object} keys\n * @property {string} [keys.package='pkg'] - Global data property for package.json data\n * @property {string} [keys.layout='layout']\n * @property {string} [keys.permalink='permalink']\n * @property {string} [keys.permalinkRoot='permalinkBypassOutputDir']\n * @property {string} [keys.engineOverride='templateEngineOverride']\n * @property {string} [keys.computed='eleventyComputed']\n * @property {string} [keys.dataSchema='eleventyDataSchema']\n * @property {object} dir\n * @property {string} [dir.input='.']\n * @property {string} [dir.includes='_includes']\n * @property {string} [dir.data='_data']\n * @property {string} [dir.output='_site']\n * @deprecated handlebarsHelpers\n * @deprecated nunjucksFilters\n */\n\n/**\n * Default configuration object factory.\n *\n * @param {config} config - Eleventy configuration object.\n * @returns {defaultConfig}\n */\nexport default function (config) {\n\t// add extra config (not available in `@11ty/client` bundle)\n\tfullBundleDefaultConfig.call(this, config);\n\n\tconfig.addFilter(\"log\", (input, ...messages) => {\n\t\tconsole.log(input, ...messages);\n\t\treturn input;\n\t});\n\n\t// Process arbitrary content with transforms\n\tconfig.addFilter(\n\t\t\"renderTransforms\",\n\t\tasync function transformsFilter(content, pageEntryOverride, baseHrefOverride) {\n\t\t\treturn TransformsUtil.runAll(content, pageEntryOverride || this.page, config.transforms, {\n\t\t\t\tbaseHrefOverride,\n\t\t\t\tlogger: config.logger,\n\t\t\t});\n\t\t},\n\t);\n\n\treturn {\n\t\ttemplateFormats: [\"liquid\", \"md\", \"njk\", \"html\", \"11ty.js\"],\n\t\t// to add a parent directory structure to URLs (not reflected on the file system), change this\n\t\tpathPrefix: \"/\",\n\t\tmarkdownTemplateEngine: \"liquid\",\n\t\thtmlTemplateEngine: \"liquid\",\n\n\t\t// Renamed from `jsDataFileSuffix` in 2.0 (and swapped to an Array)\n\t\t// If you remove \"\" we won’t look for dir/dir.json or file.json\n\t\tdataFileSuffixes: [\".11tydata\", \"\"],\n\n\t\t// \"index\" will look for `directory/index.*` directory data files instead of `directory/directory.*`\n\t\tdataFileDirBaseNameOverride: false,\n\n\t\tkeys: {\n\t\t\t// TODO breaking: use `false` by default\n\t\t\tpackage: \"pkg\", // supports `false`\n\t\t\tlayout: \"layout\",\n\t\t\tpermalink: \"permalink\",\n\t\t\tpermalinkRoot: \"permalinkBypassOutputDir\",\n\t\t\tengineOverride: \"templateEngineOverride\",\n\t\t\tcomputed: \"eleventyComputed\",\n\t\t\tdataSchema: \"eleventyDataSchema\",\n\t\t},\n\n\t\t// Deprecated, define using `export const directories = {}` instead.\n\t\t// Reference values using `eleventyConfig.directories` instead.\n\t\tdir: {\n\t\t\t// These values here aren’t used internally either (except by a few tests), instead we’re using `ProjectDirectories.defaults`.\n\t\t\t// These are kept in place for backwards compat with `eleventyConfig.dir` references in project config code and plugins.\n\t\t\tinput: \".\",\n\t\t\tincludes: \"_includes\",\n\t\t\tdata: \"_data\",\n\t\t\toutput: \"_site\",\n\t\t},\n\n\t\t// deprecated, use config.addNunjucksFilter\n\t\tnunjucksFilters: {},\n\t};\n}\n"
  },
  {
    "path": "src/defaultConfigExtended.client.js",
    "content": "export default function (config) {\n\tconfig.addFilter(\"url\", () => {\n\t\tthrow new Error(\n\t\t\t\"The `url` filter is not included with the `@11ty/client` bundle. Use the `@11ty/client/eleventy` bundle.\",\n\t\t);\n\t});\n\n\tconfig.addFilter(\"inputPathToUrl\", () => {\n\t\tthrow new Error(\n\t\t\t\"The `inputPathToUrl` filter is not included with the `@11ty/client` bundle. Use the larger `@11ty/client/eleventy` bundle.\",\n\t\t);\n\t});\n\n\t// Saves ~26KB (minified)\n\t// Differences from main bundle: async and not memoized\n\tconfig.addAsyncFilter(\"slugify\", async function (str, options = {}) {\n\t\treturn import(\"@sindresorhus/slugify\")\n\t\t\t.then((mod) => mod.default)\n\t\t\t.then((slugify) => {\n\t\t\t\toptions.decamelize ??= false;\n\t\t\t\treturn slugify(\"\" + str, options);\n\t\t\t});\n\t});\n}\n"
  },
  {
    "path": "src/defaultConfigExtended.js",
    "content": "import bundlePlugin from \"@11ty/eleventy-plugin-bundle\";\nimport slugify from \"@sindresorhus/slugify\";\n\nimport { HtmlTransformer } from \"./Util/HtmlTransformer.js\";\nimport { HtmlRelativeCopyPlugin } from \"./Plugins/HtmlRelativeCopyPlugin.js\";\nimport MemoizeUtil from \"./Util/MemoizeFunction.js\";\n\nimport urlFilter from \"./Filters/Url.js\";\nimport getLocaleCollectionItem from \"./Filters/GetLocaleCollectionItem.js\";\nimport getCollectionItemIndex from \"./Filters/GetCollectionItemIndex.js\";\nimport { FilterPlugin as InputPathToUrlFilterPlugin } from \"./Plugins/InputPathToUrl.js\";\n\n/**\n * @typedef {object} config\n * @property {addPlugin} addPlugin - Execute or defer a plugin’s execution.\n * @property {addTransform} addTransform - Add an Eleventy transform to postprocess template output\n * @property {htmlTransformer} htmlTransformer - HTML modification API\n */\n\n/**\n * Extended default configuration object factory.\n *\n * @param {config} config - Eleventy configuration object.\n * @returns {defaultConfig}\n */\nexport default function (config) {\n\t// Used for the HTML <base>, InputPathToUrl, Image transform plugins\n\tlet htmlTransformer = new HtmlTransformer();\n\thtmlTransformer.setUserConfig(config);\n\n\t// This needs to be assigned before bundlePlugin is added below.\n\tconfig.htmlTransformer = htmlTransformer;\n\n\t// Remember: the transform added here runs before the `htmlTransformer` transform\n\tconfig.addPlugin(bundlePlugin, {\n\t\tbundles: false, // no default bundles included—must be opt-in.\n\t\timmediate: true,\n\t});\n\n\t// Run the `htmlTransformer` transform\n\tconfig.addTransform(\"@11ty/eleventy/html-transformer\", async function (content) {\n\t\t// Runs **AFTER** the bundle plugin transform (except: delayed bundles)\n\t\treturn htmlTransformer.transformContent(this.outputPath, content, this);\n\t});\n\n\t// Requires user configuration, so must run as second-stage\n\tconfig.addPlugin(HtmlRelativeCopyPlugin);\n\n\t// Filter: Maps an input path to output URL\n\tconfig.addPlugin(InputPathToUrlFilterPlugin, {\n\t\timmediate: true,\n\t});\n\n\t// slug Filter (removed, errors)\n\tconfig.addFilter(\"slug\", function () {\n\t\tthrow new Error(\n\t\t\t\"The `slug` filter (deprecated since v1) has been removed in Eleventy v4. You can add it manually to your configuration file for backwards compatibility, read more at GitHub Issue #3893: https://github.com/11ty/eleventy/issues/3893 Alternatively (more risky), you can swap to use the `slugify` filter instead (outputs may be different and production URLs may break!)\",\n\t\t);\n\t});\n\n\t// slugify Filter\n\tconfig.addFilter(\n\t\t\"slugify\",\n\t\tMemoizeUtil(\n\t\t\tfunction (str, options = {}) {\n\t\t\t\toptions.decamelize ??= false;\n\n\t\t\t\treturn slugify(\"\" + str, options);\n\t\t\t},\n\t\t\t{ name: \"slugify\", bench: config.benchmarkManager.get(\"Configuration\") },\n\t\t),\n\t);\n\n\t// Collection Filters\n\tconfig.addFilter(\"getCollectionItemIndex\", function (collection, pageOverride) {\n\t\treturn getCollectionItemIndex.call(this, collection, pageOverride);\n\t});\n\tconfig.addFilter(\"getCollectionItem\", function (collection, pageOverride, langCode) {\n\t\treturn getLocaleCollectionItem.call(this, config, collection, pageOverride, langCode, 0);\n\t});\n\tconfig.addFilter(\"getPreviousCollectionItem\", function (collection, pageOverride, langCode) {\n\t\treturn getLocaleCollectionItem.call(this, config, collection, pageOverride, langCode, -1);\n\t});\n\tconfig.addFilter(\"getNextCollectionItem\", function (collection, pageOverride, langCode) {\n\t\treturn getLocaleCollectionItem.call(this, config, collection, pageOverride, langCode, 1);\n\t});\n\n\t// Deprecated, use HtmlBasePlugin instead.\n\t// Adds a pathPrefix manually to a URL string\n\tlet templateConfig = this;\n\tconfig.addFilter(\"url\", function addPathPrefixFilter(url, pathPrefixOverride) {\n\t\tlet pathPrefix;\n\t\tif (pathPrefixOverride && typeof pathPrefixOverride === \"string\") {\n\t\t\tpathPrefix = pathPrefixOverride;\n\t\t} else {\n\t\t\tpathPrefix = templateConfig.getPathPrefix();\n\t\t}\n\n\t\treturn urlFilter.call(this, url, pathPrefix);\n\t});\n}\n"
  },
  {
    "path": "test/ArrayUtilTest.js",
    "content": "import test from \"ava\";\nimport {arrayDelete} from \"../src/Util/ArrayUtil.js\";\n\ntest(\"ArrayUtil.arrayDelete empties\", async (t) => {\n  t.deepEqual(arrayDelete(), []);\n  t.deepEqual(arrayDelete(undefined, 1), []);\n\n  t.deepEqual(arrayDelete(null), []);\n  t.deepEqual(arrayDelete(1), []);\n  t.deepEqual(arrayDelete(true), []);\n  t.deepEqual(arrayDelete(false), []);\n});\n\ntest(\"ArrayUtil.arrayDelete if array does not have value, it does not mutate\", async (t) => {\n  let empty = [];\n  t.is(arrayDelete(empty), empty);\n  t.is(arrayDelete(empty, 1), empty);\n  t.is(arrayDelete(empty, true), empty);\n  t.is(arrayDelete(empty, undefined), empty);\n});\n\ntest(\"ArrayUtil.arrayDelete if array does not have function matched value, it does not mutate\", async (t) => {\n  let empty = [];\n  t.is(arrayDelete(empty, () => false), empty);\n});\n\n\ntest(\"ArrayUtil.arrayDelete mutates when array contains match\", async (t) => {\n  let a = [1, 2];\n  t.not(arrayDelete(a, 1), [2]);\n  t.deepEqual(arrayDelete(a, 1), [2]);\n});\n\ntest(\"ArrayUtil.arrayDelete mutates when array contains function matched value\", async (t) => {\n  let a = [1, 2];\n  t.not(arrayDelete(a, entry => entry === 1), [2]);\n  t.deepEqual(arrayDelete(a, entry => entry === 1), [2]);\n});\n\ntest(\"ArrayUtil.arrayDelete complex delete\", async (t) => {\n  let a = [1,2,3,4,5,6,7,8];\n  t.deepEqual(arrayDelete(a, 4), [1,2,3,5,6,7,8]);\n});\n\ntest(\"ArrayUtil.arrayDelete function matched delete\", async (t) => {\n  let a = [1,2,3,4,5,6,7,8];\n  t.deepEqual(arrayDelete(a, entry => entry === 4), [1,2,3,5,6,7,8]);\n});\n\ntest(\"ArrayUtil.arrayDelete double delete\", async (t) => {\n  let a = [1,2,3,4,5,6,7,8];\n  t.deepEqual(arrayDelete(arrayDelete(a, 4), 6), [1,2,3,5,7,8]);\n});\n"
  },
  {
    "path": "test/BenchmarkTest.js",
    "content": "import test from \"ava\";\nimport Benchmark from \"../src/Benchmark/Benchmark.js\";\n\ntest(\"Standard Benchmark\", async (t) => {\n  await new Promise((resolve) => {\n    let b = new Benchmark();\n    b.before();\n    setTimeout(function () {\n      b.after();\n      t.truthy(b.getTotal() >= 0);\n      resolve();\n    }, 100);\n  });\n});\n\ntest(\"Nested Benchmark (nested calls are ignored while a parent is measuring)\", async (t) => {\n  await new Promise((resolve) => {\n    let b = new Benchmark();\n    b.before();\n\n    setTimeout(function () {\n      b.before();\n      b.after();\n      t.truthy(b.getTotal() <= 0.1);\n\n      b.after();\n      t.truthy(b.getTotal() >= 10);\n      resolve();\n    }, 100);\n  });\n});\n\ntest(\"Reset Benchmark\", async (t) => {\n  await new Promise((resolve) => {\n    let b = new Benchmark();\n    b.before();\n    b.reset();\n\n    setTimeout(function () {\n      b.before();\n      b.after();\n\n      t.throws(function () {\n        // throws because we reset\n        b.after();\n      });\n\n      resolve();\n    }, 100);\n  });\n});\n"
  },
  {
    "path": "test/BundlePluginTest.js",
    "content": "import test from \"ava\";\nimport Eleventy from \"../src/Eleventy.js\";\n\ntest(\"addBundle\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: eleventyConfig => {\n      eleventyConfig.addPlugin(() => {\n        eleventyConfig.addBundle(\"css\")\n      });\n      eleventyConfig.addTemplate(\"index.njk\", \"{% css %}/* Hi */{% endcss %}<style>{% getBundle 'css' %}</style>\");\n    }\n  });\n\n  let results = await elev.toJSON();\n  t.is(results[0].content, `<style>/* Hi */</style>`);\n});\n\ntest(\"addBundle (empty css)\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: eleventyConfig => {\n      eleventyConfig.addPlugin(() => {\n        eleventyConfig.addBundle(\"css\");\n      });\n\n      eleventyConfig.addTemplate(\"index.njk\", \"Hi<style>{% getBundle 'css' %}</style>\");\n    }\n  });\n\n  let results = await elev.toJSON();\n  t.is(results[0].content, `Hi`);\n});\n\ntest(\"addBundle (empty js)\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: eleventyConfig => {\n      eleventyConfig.addPlugin(() => {\n        eleventyConfig.addBundle(\"js\");\n      });\n\n      eleventyConfig.addTemplate(\"index.njk\", \"Hi<script>{% getBundle 'js' %}</script>\");\n    }\n  });\n\n  let results = await elev.toJSON();\n  t.is(results[0].content, `Hi`);\n});\n\ntest(\"Empty script node is removed (not using bundle)\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: eleventyConfig => {\n      eleventyConfig.addPlugin(() => {\n        eleventyConfig.addBundle(\"js\");\n      });\n\n      eleventyConfig.addTemplate(\"index.njk\", \"Hi<script></script>\");\n    }\n  });\n\n  let results = await elev.toJSON();\n  t.is(results[0].content, `Hi`);\n});\n\n\ntest(\"Empty style node is removed (not using bundle)\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: eleventyConfig => {\n      eleventyConfig.addPlugin(() => {\n        eleventyConfig.addBundle(\"css\");\n      });\n\n      eleventyConfig.addTemplate(\"index.njk\", \"Hi<style></style>\");\n    }\n  });\n\n  let results = await elev.toJSON();\n  t.is(results[0].content, `Hi`);\n});\n\ntest(\"Empty link node is removed (not using bundle)\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: eleventyConfig => {\n      eleventyConfig.addPlugin(() => {\n        eleventyConfig.addBundle(\"css\");\n      });\n\n      eleventyConfig.addTemplate(\"index.njk\", \"Hi<link rel='stylesheet' href=''>\");\n    }\n  });\n\n  let results = await elev.toJSON();\n  t.is(results[0].content, `Hi`);\n});\n\ntest(\"Empty link node is removed (no href attribute at all, not using bundle)\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: eleventyConfig => {\n      eleventyConfig.addPlugin(() => {\n        eleventyConfig.addBundle(\"css\");\n      });\n\n      eleventyConfig.addTemplate(\"index.njk\", \"Hi<link rel='stylesheet'>\");\n    }\n  });\n\n  let results = await elev.toJSON();\n  t.is(results[0].content, `Hi`);\n});\n\ntest(\"Empty link node is kept (no rel attribute, not using bundle)\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: eleventyConfig => {\n      eleventyConfig.addPlugin(() => {\n        eleventyConfig.addBundle(\"css\");\n      });\n\n      eleventyConfig.addTemplate(\"index.njk\", \"Hi<link>\");\n    }\n  });\n\n  let results = await elev.toJSON();\n  t.is(results[0].content, `Hi<link>`);\n});\n"
  },
  {
    "path": "test/CompatibilityTest.js",
    "content": "import test from \"ava\";\nimport EleventyCompatibility from \"../src/Util/Compatibility.js\";\n\ntest(\".canary- to .alpha- normalization (because pre-releases are alphabetic comparisons 😭)\", (t) => {\n  t.is(EleventyCompatibility.normalizeIdentifier(\"2.0.0\"), \"2.0.0\");\n  t.is(EleventyCompatibility.normalizeIdentifier(\"2.0.0-beta.1\"), \"2.0.0-beta.1\");\n  t.is(EleventyCompatibility.normalizeIdentifier(\"2.0.0-canary.1\"), \"2.0.0-alpha.1\");\n  t.is(EleventyCompatibility.normalizeIdentifier(\"2.0.0-alpha.1\"), \"2.0.0-alpha.1\");\n  t.is(EleventyCompatibility.normalizeIdentifier(\">=2.0.0-beta.1\"), \">=2.0.0-beta.1\");\n  t.is(\n    EleventyCompatibility.normalizeIdentifier(\">=2.0.0-beta.1 || >=2.0.0-canary.1\"),\n    \">=2.0.0-beta.1 || >=2.0.0-alpha.1\"\n  );\n});\n\ntest(\"Version checking for plugin compatibility >=0.5.4\", (t) => {\n  let range = \">=0.5.4\";\n  t.true(EleventyCompatibility.satisfies(\"1.0.0-beta.1\", range));\n  t.true(EleventyCompatibility.satisfies(\"1.0.2\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.0-canary.3\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.0-canary.18\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.0-canary.19\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.0-beta.1\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.0\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.1\", range));\n  t.true(EleventyCompatibility.satisfies(\"3.0.0-canary.1\", range));\n  t.true(EleventyCompatibility.satisfies(\"3.0.0-beta.1\", range));\n  t.true(EleventyCompatibility.satisfies(\"3.0.0\", range));\n});\n\ntest(\"Version checking for plugin compatibility >=0.6\", (t) => {\n  let range = \">=0.6\";\n  t.true(EleventyCompatibility.satisfies(\"1.0.0-beta.1\", range));\n  t.true(EleventyCompatibility.satisfies(\"1.0.2\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.0-canary.3\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.0-canary.18\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.0-canary.19\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.0-beta.1\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.0\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.1\", range));\n  t.true(EleventyCompatibility.satisfies(\"3.0.0-canary.1\", range));\n  t.true(EleventyCompatibility.satisfies(\"3.0.0-beta.1\", range));\n  t.true(EleventyCompatibility.satisfies(\"3.0.0\", range));\n});\n\ntest(\"Version checking for plugin compatibility >=1\", (t) => {\n  let range = \">=1\"; // **not** the same as >=1.0.0\n  t.true(EleventyCompatibility.satisfies(\"1.0.0-beta.1\", range));\n  t.true(EleventyCompatibility.satisfies(\"1.0.2\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.0-canary\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.0-canary.18\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.0-canary.19\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.0-beta.1\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.0\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.1\", range));\n  t.true(EleventyCompatibility.satisfies(\"3.0.0-canary.1\", range));\n  t.true(EleventyCompatibility.satisfies(\"3.0.0-beta.1\", range));\n  t.true(EleventyCompatibility.satisfies(\"3.0.0\", range));\n});\n\ntest(\"Version checking for plugin compatibility >=1.0.0\", (t) => {\n  let range = \">=1.0.0\"; // **not** the same as >=1\n  t.false(EleventyCompatibility.satisfies(\"1.0.0-beta.1\", range));\n  t.true(EleventyCompatibility.satisfies(\"1.0.2\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.0-canary\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.0-canary.18\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.0-canary.19\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.0-beta.1\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.0\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.1\", range));\n  t.true(EleventyCompatibility.satisfies(\"3.0.0-canary.1\", range));\n  t.true(EleventyCompatibility.satisfies(\"3.0.0-beta.1\", range));\n  t.true(EleventyCompatibility.satisfies(\"3.0.0\", range));\n});\n\ntest(\"Version checking for plugin compatibility >=1.0.0 || >=1.0.0-beta || >=1.0.0-canary\", (t) => {\n  // could be simplified to >=1\n  // noting that pre-1.0 versions of Eleventy did not match prereleases—which doesn’t matter because 0.12 would return false here anyway.\n  let range = \">=1.0.0 || >=1.0.0-beta || >=1.0.0-canary\";\n  t.true(EleventyCompatibility.satisfies(\"1.0.0-beta.1\", range));\n  t.true(EleventyCompatibility.satisfies(\"1.0.2\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.0-canary\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.0-canary.18\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.0-canary.19\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.0-beta.1\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.0\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.1\", range));\n  t.true(EleventyCompatibility.satisfies(\"3.0.0-canary.1\", range));\n  t.true(EleventyCompatibility.satisfies(\"3.0.0-beta.1\", range));\n  t.true(EleventyCompatibility.satisfies(\"3.0.0\", range));\n});\n\ntest(\"Version checking for plugin compatibility >=2\", (t) => {\n  let range = \">=2\";\n  t.false(EleventyCompatibility.satisfies(\"1.0.0-beta.1\", range));\n  t.false(EleventyCompatibility.satisfies(\"1.0.2\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.0-canary.3\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.0-canary.18\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.0-canary.19\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.0-beta.1\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.0\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.1\", range));\n  t.true(EleventyCompatibility.satisfies(\"3.0.0-canary.1\", range));\n  t.true(EleventyCompatibility.satisfies(\"3.0.0-beta.1\", range));\n  t.true(EleventyCompatibility.satisfies(\"3.0.0\", range));\n});\n\ntest(\"Version checking for plugin compatibility >=2.0.0\", (t) => {\n  // Recommend to use >=2 instead of this\n  let range = \">=2.0.0\";\n  t.false(EleventyCompatibility.satisfies(\"1.0.0-beta.1\", range));\n  t.false(EleventyCompatibility.satisfies(\"1.0.2\", range));\n  t.false(EleventyCompatibility.satisfies(\"2.0.0-canary.3\", range)); // Contentious! I wish this were true\n  t.false(EleventyCompatibility.satisfies(\"2.0.0-canary.18\", range)); // Contentious! I wish this were true\n  t.false(EleventyCompatibility.satisfies(\"2.0.0-canary.19\", range)); // Contentious! I wish this were true\n  t.false(EleventyCompatibility.satisfies(\"2.0.0-beta.1\", range)); // Contentious! I wish this were true\n  t.true(EleventyCompatibility.satisfies(\"2.0.0\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.1\", range));\n  t.true(EleventyCompatibility.satisfies(\"3.0.0-canary.1\", range));\n  t.true(EleventyCompatibility.satisfies(\"3.0.0-beta.1\", range));\n  t.true(EleventyCompatibility.satisfies(\"3.0.0\", range));\n});\n\ntest(\"Version checking for plugin compatibility >=2.0.0-canary\", (t) => {\n  // Recommend to use >=2 instead of this\n  let range = \">=2.0.0-canary\";\n  t.false(EleventyCompatibility.satisfies(\"1.0.0-beta.1\", range));\n  t.false(EleventyCompatibility.satisfies(\"1.0.2\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.0-canary.3\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.0-canary.18\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.0-canary.19\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.0-beta.1\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.0\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.1\", range));\n  t.true(EleventyCompatibility.satisfies(\"3.0.0-canary.1\", range));\n  t.true(EleventyCompatibility.satisfies(\"3.0.0-beta.1\", range));\n  t.true(EleventyCompatibility.satisfies(\"3.0.0\", range));\n});\n\ntest(\"Version checking for plugin compatibility >=2.0.0-canary.1\", (t) => {\n  // Recommend to use >=2 instead of this\n  let range = \">=2.0.0-canary.1\";\n  t.false(EleventyCompatibility.satisfies(\"1.0.0-beta.1\", range));\n  t.false(EleventyCompatibility.satisfies(\"1.0.2\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.0-canary.3\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.0-canary.18\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.0-canary.19\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.0-beta.1\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.0\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.1\", range));\n  t.true(EleventyCompatibility.satisfies(\"3.0.0-canary.1\", range));\n  t.true(EleventyCompatibility.satisfies(\"3.0.0-beta.1\", range));\n  t.true(EleventyCompatibility.satisfies(\"3.0.0\", range));\n});\n\ntest(\"Version checking for plugin compatibility >=2.0.0-beta\", (t) => {\n  let range = \">=2.0.0-beta\";\n  t.false(EleventyCompatibility.satisfies(\"1.0.0-beta.1\", range));\n  t.false(EleventyCompatibility.satisfies(\"1.0.2\", range));\n  t.false(EleventyCompatibility.satisfies(\"2.0.0-canary\", range));\n  t.false(EleventyCompatibility.satisfies(\"2.0.0-canary.18\", range));\n  t.false(EleventyCompatibility.satisfies(\"2.0.0-canary.19\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.0-beta.1\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.0\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.1\", range));\n  t.true(EleventyCompatibility.satisfies(\"3.0.0-canary.1\", range));\n  t.true(EleventyCompatibility.satisfies(\"3.0.0-beta.1\", range));\n  t.true(EleventyCompatibility.satisfies(\"3.0.0\", range));\n});\n\ntest(\"Version checking for plugin compatibility >=2.0.0-beta.1\", (t) => {\n  let range = \">=2.0.0-beta.1\";\n  t.false(EleventyCompatibility.satisfies(\"1.0.0-beta.1\", range));\n  t.false(EleventyCompatibility.satisfies(\"1.0.2\", range));\n  t.false(EleventyCompatibility.satisfies(\"2.0.0-canary\", range));\n  t.false(EleventyCompatibility.satisfies(\"2.0.0-canary.18\", range));\n  t.false(EleventyCompatibility.satisfies(\"2.0.0-canary.19\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.0-beta.1\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.0-beta.2\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.0\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.1\", range));\n  t.true(EleventyCompatibility.satisfies(\"3.0.0-canary.1\", range));\n  t.true(EleventyCompatibility.satisfies(\"3.0.0-beta.1\", range));\n  t.true(EleventyCompatibility.satisfies(\"3.0.0\", range));\n});\n\ntest(\"Version checking for plugin compatibility >=2.0.0-beta.2\", (t) => {\n  let range = \">=2.0.0-beta.2\";\n  t.false(EleventyCompatibility.satisfies(\"1.0.0-beta.1\", range));\n  t.false(EleventyCompatibility.satisfies(\"1.0.2\", range));\n  t.false(EleventyCompatibility.satisfies(\"2.0.0-canary\", range));\n  t.false(EleventyCompatibility.satisfies(\"2.0.0-canary.18\", range));\n  t.false(EleventyCompatibility.satisfies(\"2.0.0-canary.19\", range));\n  t.false(EleventyCompatibility.satisfies(\"2.0.0-beta.1\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.0-beta.2\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.0\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.1\", range));\n  t.true(EleventyCompatibility.satisfies(\"3.0.0-canary.1\", range));\n  t.true(EleventyCompatibility.satisfies(\"3.0.0-beta.1\", range));\n  t.true(EleventyCompatibility.satisfies(\"3.0.0\", range));\n});\n\n// TODO eleventy-upgrade-help\ntest(\"Version checking for plugin compatibility >=2 <3\", (t) => {\n  let range = \">=2 <3\";\n  t.false(EleventyCompatibility.satisfies(\"1.0.0-beta.1\", range));\n  t.false(EleventyCompatibility.satisfies(\"1.0.2\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.0-canary\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.0-canary.18\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.0-canary.19\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.0-beta.1\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.0\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.1\", range));\n  t.false(EleventyCompatibility.satisfies(\"3.0.0-canary.1\", range));\n  t.false(EleventyCompatibility.satisfies(\"3.0.0-beta.1\", range));\n  t.false(EleventyCompatibility.satisfies(\"3.0.0\", range));\n});\n\ntest(\"Version checking for plugin compatibility >=2.0.0-canary.19\", (t) => {\n  let range = \">=2.0.0-canary.19\";\n  t.false(EleventyCompatibility.satisfies(\"1.0.0-beta.1\", range));\n  t.false(EleventyCompatibility.satisfies(\"1.0.2\", range));\n  t.false(EleventyCompatibility.satisfies(\"2.0.0-canary\", range));\n  t.false(EleventyCompatibility.satisfies(\"2.0.0-canary.18\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.0-canary.19\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.0-beta.1\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.0\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.1\", range));\n  t.true(EleventyCompatibility.satisfies(\"3.0.0-canary.1\", range));\n  t.true(EleventyCompatibility.satisfies(\"3.0.0-beta.1\", range));\n  t.true(EleventyCompatibility.satisfies(\"3.0.0\", range));\n});\n\ntest(\"Version checking for plugin compatibility >=2.0.0-canary.19 || >=2.0.0-beta.1\", (t) => {\n  let range = \">=2.0.0-canary.19 || >=2.0.0-beta.1\"; // can be simplified to >=2.0.0-canary.19\n  t.false(EleventyCompatibility.satisfies(\"1.0.0-beta.1\", range));\n  t.false(EleventyCompatibility.satisfies(\"1.0.2\", range));\n  t.false(EleventyCompatibility.satisfies(\"2.0.0-canary\", range));\n  t.false(EleventyCompatibility.satisfies(\"2.0.0-canary.18\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.0-canary.19\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.0-beta.1\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.0\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.1\", range));\n  t.true(EleventyCompatibility.satisfies(\"3.0.0-canary.1\", range));\n  t.true(EleventyCompatibility.satisfies(\"3.0.0-beta.1\", range));\n  t.true(EleventyCompatibility.satisfies(\"3.0.0\", range));\n});\n\ntest(\"Version checking for plugin compatibility >=2.0.0-canary.19 || >=2.0.0-beta.2\", (t) => {\n  let range = \">=2.0.0-canary.19 || >=2.0.0-beta.2\"; // same as \">=2.0.0-canary.19\"\n  t.false(EleventyCompatibility.satisfies(\"1.0.0-beta.1\", range));\n  t.false(EleventyCompatibility.satisfies(\"1.0.2\", range));\n  t.false(EleventyCompatibility.satisfies(\"2.0.0-canary\", range));\n  t.false(EleventyCompatibility.satisfies(\"2.0.0-canary.18\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.0-canary.19\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.0-beta.1\", range)); // warning: this matches because of >=2.0.0-canary.19\n  t.true(EleventyCompatibility.satisfies(\"2.0.0-beta.2\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.0\", range));\n  t.true(EleventyCompatibility.satisfies(\"2.0.1\", range));\n  t.true(EleventyCompatibility.satisfies(\"3.0.0-canary.1\", range));\n  t.true(EleventyCompatibility.satisfies(\"3.0.0-beta.1\", range));\n  t.true(EleventyCompatibility.satisfies(\"3.0.0\", range));\n});\n"
  },
  {
    "path": "test/ComputedDataProxyTest.js",
    "content": "import test from \"ava\";\nimport ComputedDataProxy from \"../src/Data/ComputedDataProxy.js\";\n\ntest(\"Get vars used by function\", async (t) => {\n  let cd = new ComputedDataProxy([\"key1\"]);\n  let key1Fn = () => {};\n  let key2Fn = (data) => {\n    return `${data.key1}`;\n  };\n\n  t.deepEqual(await cd.findVarsUsed(key1Fn), []);\n  t.deepEqual(await cd.findVarsUsed(key2Fn), [\"key1\"]);\n});\n\ntest(\"Get vars used by function (not a computed key)\", async (t) => {\n  let cd = new ComputedDataProxy([\"page.url\"]);\n  let key1Fn = (data) => {\n    return `${data.page.url}`;\n  };\n\n  t.deepEqual(\n    await cd.findVarsUsed(key1Fn, {\n      page: { url: \"\" },\n    }),\n    [\"page.url\"]\n  );\n});\n\ntest(\"Get vars used by function (multiple functions—not computed keys)\", async (t) => {\n  let cd = new ComputedDataProxy([\n    \"page.url\",\n    \"key1\",\n    \"very.deep.reference\",\n    \"very.other.deep.reference\",\n  ]);\n\n  // this would be real\n  let sampleData = {\n    key1: \"\",\n    page: {\n      url: \"\",\n    },\n    very: {\n      deep: {\n        reference: \"\",\n      },\n      other: {\n        deep: {\n          reference: \"\",\n        },\n      },\n    },\n  };\n\n  let key1Fn = (data) => {\n    return `${data.page.url}`;\n  };\n  let key2Fn = (data) => {\n    return `${data.key1}${data.very.deep.reference}${data.very.other.deep.reference}`;\n  };\n\n  t.deepEqual(await cd.findVarsUsed(key1Fn, sampleData), [\"page.url\"]);\n  t.deepEqual(await cd.findVarsUsed(key2Fn, sampleData), [\n    \"key1\",\n    \"very.deep.reference\",\n    \"very.other.deep.reference\",\n  ]);\n});\n\ntest(\"Proxy shouldn’t always return {}\", async (t) => {\n  let cd = new ComputedDataProxy([\"page.fileSlug\"]);\n  let proxy = cd.getProxyData(\n    {\n      page: {\n        fileSlug: \"\",\n      },\n    },\n    new Set()\n  );\n\n  t.notDeepEqual(proxy.page.fileSlug, {});\n  t.is(proxy.page.fileSlug, \"\");\n});\n\ntest(\"isArrayOrPlainObject\", async (t) => {\n  let cd = new ComputedDataProxy();\n\n  t.is(cd.isArrayOrPlainObject(true), false);\n  t.is(cd.isArrayOrPlainObject(false), false);\n  t.is(cd.isArrayOrPlainObject(1), false);\n  t.is(\n    cd.isArrayOrPlainObject(() => {}),\n    false\n  );\n  t.is(\n    cd.isArrayOrPlainObject(function () {}),\n    false\n  );\n  t.is(cd.isArrayOrPlainObject(new Date()), false);\n  t.is(cd.isArrayOrPlainObject({}), true);\n  t.is(cd.isArrayOrPlainObject([]), true);\n});\n\ntest(\"findVarsUsed empty\", async (t) => {\n  let cdg = new ComputedDataProxy();\n  t.deepEqual(await cdg.findVarsUsed(() => {}), []);\n  t.deepEqual(await cdg.findVarsUsed(({}) => {}), []);\n\n  let data = { key: \"value\" };\n  t.deepEqual(await cdg.findVarsUsed((data) => {}), []);\n  t.deepEqual(await cdg.findVarsUsed((data) => data.key), [\"key\"]);\n});\n\ntest(\"findVarsUsed with a computed key (target a string)\", async (t) => {\n  let cdg = new ComputedDataProxy();\n  let data = {\n    key: \"value\",\n    computed: {\n      key: function (data) {\n        return data.key;\n      },\n    },\n  };\n\n  t.deepEqual(await cdg.findVarsUsed(data.computed.key, data), [\"key\"]);\n});\n\ntest(\"findVarsUsed with a computed key (target an array)\", async (t) => {\n  let cdg = new ComputedDataProxy();\n  let data = {\n    arr: [0, 1, 2],\n    computed: {\n      key: function (data) {\n        return data.arr[1];\n      },\n    },\n  };\n\n  t.deepEqual(await cdg.findVarsUsed(data.computed.key, data), [\"arr[1]\"]);\n});\n\ntest(\"findVarsUsed with a computed key (target an object)\", async (t) => {\n  let cdg = new ComputedDataProxy();\n  let data = {\n    obj: {\n      b: 1,\n    },\n    computed: {\n      key: function (data) {\n        return data.obj.b;\n      },\n    },\n  };\n\n  t.deepEqual(await cdg.findVarsUsed(data.computed.key, data), [\"obj.b\"]);\n});\n\ntest(\"findVarsUsed with a computed key (target an object in an array)\", async (t) => {\n  let cdg = new ComputedDataProxy();\n  let data = {\n    obj: [{ b: 1 }, { a: 2 }],\n    computed: {\n      key: function (data) {\n        return data.obj[1].a;\n      },\n    },\n  };\n\n  t.deepEqual(await cdg.findVarsUsed(data.computed.key, data), [\"obj[1].a\"]);\n});\n\ntest(\"findVarsUsed with a computed key (target a string not used in the output)\", async (t) => {\n  let cdg = new ComputedDataProxy();\n  let data = {\n    key1: \"value1\",\n    key2: \"value2\",\n    computed: {\n      key: function (data) {\n        let b = data.key2;\n        return data.key1;\n      },\n    },\n  };\n\n  t.deepEqual(await cdg.findVarsUsed(data.computed.key, data), [\"key2\", \"key1\"]);\n});\n\ntest(\"findVarsUsed with a deep computed reference that doesn’t exist in parent data\", async (t) => {\n  let cdg = new ComputedDataProxy([\"deep.deep1\", \"deep.deep2\"]);\n  let data = {\n    key1: \"value1\",\n    key2: \"value2\",\n    computed: {\n      deep: {\n        deep1: function (data) {\n          return data.key2;\n        },\n        deep2: function (data) {\n          return data.deep.deep1;\n        },\n      },\n    },\n  };\n\n  t.deepEqual(await cdg.findVarsUsed(data.computed.deep.deep2, data), [\"deep.deep1\"]);\n  t.deepEqual(await cdg.findVarsUsed(data.computed.deep.deep1, data), [\"key2\"]);\n});\n\ntest(\"findVarsUsed with a array should filter out array methods\", async (t) => {\n  let cdg = new ComputedDataProxy();\n  let data = {\n    arr: [0, 1, 2],\n    computed: {\n      key: function (data) {\n        return data.arr.filter((entry) => entry === 2);\n      },\n    },\n  };\n\n  t.deepEqual(await cdg.findVarsUsed(data.computed.key, data), [\"arr\"]);\n});\n\ntest(\"findVarsUsed with a array can still reference length\", async (t) => {\n  let cdg = new ComputedDataProxy();\n  let data = {\n    arr: [0, 1, 2],\n    computed: {\n      key: function (data) {\n        return data.arr.length;\n      },\n    },\n  };\n\n  t.deepEqual(await cdg.findVarsUsed(data.computed.key, data), [\"arr\"]);\n});\n\ntest(\"findVarsUsed can work with empty arrays\", async (t) => {\n  let cdg = new ComputedDataProxy();\n  let data = {\n    arr: [],\n    computed: {\n      key: function (data) {\n        return data.arr;\n      },\n    },\n  };\n\n  t.deepEqual(await cdg.findVarsUsed(data.computed.key, data), [\"arr\"]);\n});\n"
  },
  {
    "path": "test/ComputedDataQueueTest.js",
    "content": "import test from \"ava\";\nimport ComputedDataQueue from \"../src/Data/ComputedDataQueue.js\";\n\ntest(\"Standard uses\", (t) => {\n  let queue = new ComputedDataQueue();\n  queue.uses(\"permalink\", [\"var1\", \"var2\"]);\n  queue.uses(\"collections.all\", [\"var2\", \"var3\"]);\n  t.deepEqual(queue.getOrder(), [\"var1\", \"var2\", \"permalink\", \"var3\", \"collections.all\"]);\n});\n\ntest(\"What does permalink use\", (t) => {\n  let queue = new ComputedDataQueue();\n  queue.uses(\"permalink\", [\"var1\", \"var2\"]);\n  queue.uses(\"collections.all\", [\"var2\", \"var3\"]);\n\n  let varsUsedByPermalink = queue.getOrderFor(\"permalink\");\n  t.deepEqual(varsUsedByPermalink, [\"var1\", \"var2\"]);\n\n  // After we process these\n  queue.markComputed([\"permalink\", ...varsUsedByPermalink]);\n  t.deepEqual(queue.getOrder(), [\"var3\", \"collections.all\"]);\n});\n\ntest(\"What does page.url and page.outputPath use\", (t) => {\n  let queue = new ComputedDataQueue();\n  queue.uses(\"page.url\", [\"permalink\"]);\n  queue.uses(\"page.url\", [\"var1\", \"var2\"]);\n  queue.uses(\"page.outputPath\", [\"permalink\"]);\n  queue.uses(\"page.outputPath\", [\"var2\", \"var3\"]);\n\n  let varsUsedByPageUrl = queue.getOrderFor(\"page.url\");\n  t.deepEqual(varsUsedByPageUrl, [\"permalink\", \"var1\", \"var2\"]);\n  queue.markComputed([...varsUsedByPageUrl, \"page.url\"]);\n  t.deepEqual(queue.getOrder(), [\"var3\", \"page.outputPath\"]);\n\n  let varsUsedByPageOutput = queue.getOrderFor(\"page.outputPath\");\n  // even though page.outputPath used permalink and var2,\n  // they were already computed above by page.url\n  t.deepEqual(varsUsedByPageOutput, [\"var3\"]);\n  queue.markComputed([...varsUsedByPageOutput, \"page.outputPath\"]);\n  t.deepEqual(queue.getOrder(), []);\n});\n\ntest(\"Permalink uses a collection (not yet supported in Eleventy)\", (t) => {\n  let queue = new ComputedDataQueue();\n  queue.uses(\"permalink\", [\"collections.dog\", \"var2\"]);\n  queue.uses(\"collections.all\", [\"var2\", \"var3\"]);\n  queue.uses(\"collections.dog\", [\"hi\"]);\n  queue.uses(\"unrelated\", [\"test\"]);\n\n  t.deepEqual(queue.getDependsOn(\"collections.dog\"), [\"permalink\"]);\n  t.deepEqual(queue.getDependsOn(\"var2\"), [\"permalink\", \"collections.all\"]);\n  t.deepEqual(queue.getDependsOn(\"collections.all\"), []);\n  t.deepEqual(queue.getDependsOn(\"hi\"), [\"permalink\", \"collections.dog\"]);\n  t.is(queue.isUsesStartsWith(\"collections.dog\", \"hi\"), true);\n  t.is(queue.isUsesStartsWith(\"permalink\", \"collections.\"), true);\n  t.is(queue.isUsesStartsWith(\"unrelated\", \"collections.\"), false);\n\n  t.deepEqual(queue.getOrderFor(\"unrelated\"), [\"test\"]);\n\n  let varsUsedByPermalink = queue.getOrderFor(\"permalink\");\n  t.deepEqual(varsUsedByPermalink, [\"hi\", \"collections.dog\", \"var2\"]);\n\n  // After we process these\n  queue.markComputed([\"permalink\", ...varsUsedByPermalink]);\n  t.deepEqual(queue.getOrder(), [\"var3\", \"collections.all\", \"test\", \"unrelated\"]);\n});\n"
  },
  {
    "path": "test/ComputedDataTemplateStringTest.js",
    "content": "import test from \"ava\";\nimport ComputedDataTemplateString from \"../src/Data/ComputedDataTemplateString.js\";\n\ntest(\"Get fake proxy data\", (t) => {\n  let cd = new ComputedDataTemplateString([\"key1\", \"key2\"]);\n  t.deepEqual(cd.getProxyData(), {\n    key1: `${cd.prefix}key1${cd.suffix}`,\n    key2: `${cd.prefix}key2${cd.suffix}`,\n  });\n});\n\ntest(\"Get nested fake proxy data\", (t) => {\n  let cd = new ComputedDataTemplateString([\"key1.nested\", \"key2\"]);\n  t.deepEqual(cd.getProxyData(), {\n    key1: {\n      nested: `${cd.prefix}key1.nested${cd.suffix}`,\n    },\n    key2: `${cd.prefix}key2${cd.suffix}`,\n  });\n});\n\ntest(\"Get vars from output\", (t) => {\n  let cd = new ComputedDataTemplateString();\n  t.deepEqual(cd.findVarsInOutput(\"\"), []);\n  t.deepEqual(cd.findVarsInOutput(\"slkdjfkljdsf\"), []);\n  t.deepEqual(cd.findVarsInOutput(`slkdjfkljdsf${cd.prefix}${cd.suffix}sldkjflkds`), []);\n  t.deepEqual(cd.findVarsInOutput(`slkdjfkljdsf${cd.prefix}firstVar${cd.suffix}sldkjflkds`), [\n    \"firstVar\",\n  ]);\n  t.deepEqual(\n    cd.findVarsInOutput(\n      `slkdjfkljdsf${cd.prefix}firstVar${cd.suffix}test${cd.prefix}firstVar${cd.suffix}sldkjflkds`\n    ),\n    [\"firstVar\"]\n  );\n  t.deepEqual(\n    cd.findVarsInOutput(\n      `slkdjfkljdsf${cd.prefix}firstVar${cd.suffix}test${cd.prefix}secondVar${cd.suffix}sldkjflkds`\n    ),\n    [\"firstVar\", \"secondVar\"]\n  );\n});\n"
  },
  {
    "path": "test/ComputedDataTest.js",
    "content": "import test from \"ava\";\nimport ComputedData from \"../src/Data/ComputedData.js\";\nimport TemplateConfig from \"../src/TemplateConfig.js\";\n\ntest(\"Basic get/set\", async (t) => {\n  let cd = new ComputedData();\n\n  cd.add(\"keystr\", \"this is a str\");\n  cd.add(\"key1\", (data) => {\n    return `this is a test ${data.key2}${data.keystr}`;\n  });\n\n  let data = {\n    key2: \"inject me\",\n  };\n  await cd.setupData(data);\n\n  t.is(data.key1, \"this is a test inject methis is a str\");\n  t.is(data.key2, \"inject me\");\n  t.is(data.keystr, \"this is a str\");\n});\n\ntest(\"Basic get/set (reverse order of adds)\", async (t) => {\n  let cd = new ComputedData();\n\n  cd.add(\"key1\", (data) => {\n    return `this is a test ${data.key2}${data.keystr}`;\n  });\n  cd.add(\"keystr\", \"this is a str\");\n\n  let data = {\n    key2: \"inject me\",\n  };\n  await cd.setupData(data);\n\n  t.is(data.key1, \"this is a test inject methis is a str\");\n  t.is(data.key2, \"inject me\");\n  t.is(data.keystr, \"this is a str\");\n});\n\ntest(\"Basic get/set (reverse order of adds) nested two deep\", async (t) => {\n  let cd = new ComputedData();\n\n  cd.add(\"key1.key3\", (data) => {\n    return `this is a test ${data.key2}${data.keystr}`;\n  });\n  cd.add(\"key1.key4\", (data) => {\n    return `this is a test ${data.key1.key3}`;\n  });\n  cd.add(\"keystr\", \"this is a str\");\n\n  let data = {\n    key2: \"inject me\",\n  };\n  await cd.setupData(data);\n\n  t.is(data.key1.key3, \"this is a test inject methis is a str\");\n  t.is(data.key1.key4, \"this is a test this is a test inject methis is a str\");\n  t.is(data.key2, \"inject me\");\n  t.is(data.keystr, \"this is a str\");\n});\n\ntest(\"use a computed value in another computed\", async (t) => {\n  let cd = new ComputedData();\n  cd.add(\"keyComputed\", (data) => {\n    return `this is a test ${data.keyOriginal}`;\n  });\n  cd.add(\"keyComputed2nd\", (data) => {\n    return `using computed ${data.keyComputed}`;\n  });\n\n  let data = {\n    keyOriginal: \"inject me\",\n  };\n  await cd.setupData(data);\n\n  t.is(data.keyComputed2nd, \"using computed this is a test inject me\");\n});\n\ntest(\"use a computed value in another computed (out of order)\", async (t) => {\n  let cd = new ComputedData();\n  cd.add(\"keyComputed2nd\", (data) => {\n    return `using computed ${data.keyComputed}`;\n  });\n  cd.add(\"keyComputed\", (data) => {\n    return `this is a test ${data.keyOriginal}`;\n  });\n\n  let data = {\n    keyOriginal: \"inject me\",\n  };\n  await cd.setupData(data);\n\n  t.is(data.keyComputed2nd, \"using computed this is a test inject me\");\n});\n\ntest(\"use a computed value in another computed (out of order), async callbacks\", async (t) => {\n  let cd = new ComputedData();\n  cd.add(\"keyComputed2nd\", async (data) => {\n    // await in data.keyComputed is optional 👀\n    return `using computed ${data.keyComputed}`;\n  });\n  cd.add(\"keyComputed\", async (data) => {\n    // await in data.keyOriginal is optional 👀\n    return `this is a test ${await data.keyOriginal}`;\n  });\n\n  let data = {\n    keyOriginal: \"inject me\",\n  };\n  await cd.setupData(data);\n\n  t.is(data.keyComputed2nd, \"using computed this is a test inject me\");\n});\n\ntest(\"Basic get/set nested\", async (t) => {\n  let cd = new ComputedData();\n\n  cd.add(\"key1.nested\", (data) => {\n    return `${data.key2}`;\n  });\n  cd.add(\"key2\", (data) => \"hi\");\n\n  let data = {\n    key2: \"inject me\",\n  };\n  await cd.setupData(data);\n\n  t.deepEqual(data.key1, { nested: \"hi\" });\n  t.is(data.key1.nested, \"hi\");\n  t.is(data.key2, \"hi\");\n});\n\ntest(\"Basic get/set nested deeper\", async (t) => {\n  let cd = new ComputedData();\n\n  cd.add(\"key1.nested.deeperA\", (data) => {\n    return `${data.key2}`;\n  });\n  cd.add(\"key1.nested.deeperB\", (data) => {\n    return `${data.key2}`;\n  });\n  cd.add(\"key1.nested.deeperC.wow\", (data) => {\n    return `${data.key2}`;\n  });\n  cd.add(\"key2\", (data) => \"hi\");\n\n  let data = {\n    key1: {\n      nonComputed: \"hi\",\n    },\n    key2: \"inject me\",\n  };\n  await cd.setupData(data);\n\n  t.deepEqual(data.key1, {\n    nonComputed: \"hi\",\n    nested: {\n      deeperA: \"hi\",\n      deeperB: \"hi\",\n      deeperC: {\n        wow: \"hi\",\n      },\n    },\n  });\n\n  t.is(data.key1.nested.deeperA, \"hi\");\n  t.is(data.key1.nested.deeperB, \"hi\");\n  t.is(data.key1.nested.deeperC.wow, \"hi\");\n  t.is(data.key1.nonComputed, \"hi\");\n  t.is(data.key2, \"hi\");\n});\n\ntest(\"template string versus function types\", async (t) => {\n  let cd = new ComputedData();\n\n  cd.add(\"key1.nested.deeperA\", (data) => {\n    return `${data.key2}`;\n  });\n  cd.add(\"key2\", () => \"hi\");\n\n  let data = {\n    key1: {\n      nonComputed: \"hi\",\n    },\n    key2: \"inject me\",\n  };\n  await cd.setupData(data);\n\n  t.deepEqual(data.key1, {\n    nonComputed: \"hi\",\n    nested: {\n      deeperA: \"hi\",\n    },\n  });\n});\n\ntest(\"Basic get/set with template string\", async (t) => {\n  let cd = new ComputedData();\n\n  cd.addTemplateString(\"keystr\", \"this is a str\");\n  cd.addTemplateString(\"key1\", (data) => {\n    return `this is a test ${data.key2}${data.keystr}`;\n  });\n\n  let data = {\n    key2: \"inject me\",\n  };\n  await cd.setupData(data);\n\n  t.is(data.key1, \"this is a test inject methis is a str\");\n  t.is(data.key2, \"inject me\");\n  t.is(data.keystr, \"this is a str\");\n});\n\ntest(\"Basic get/set using array data\", async (t) => {\n  t.plan(5);\n  let cd = new ComputedData();\n\n  cd.add(\"keystr\", \"this is a str\");\n  cd.add(\"key1\", (data) => {\n    t.is(Array.isArray(data.arr), true);\n    return `this is a test ${data.arr[0]}${data.keystr}`;\n  });\n\n  let data = {\n    arr: [\"inject me\"],\n    collections: {\n      first: [],\n      second: [],\n    },\n  };\n  await cd.setupData(data);\n\n  t.is(data.key1, \"this is a test inject methis is a str\");\n  t.is(data.arr[0], \"inject me\");\n  t.is(data.keystr, \"this is a str\");\n});\n\ntest(\"Computed returns deep object\", async (t) => {\n  let cd = new ComputedData();\n\n  cd.add(\"returnobj\", (data) => {\n    return {\n      key1: \"value1\",\n      nest: {\n        key2: \"value2\",\n      },\n    };\n  });\n\n  let data = {\n    returnobj: {\n      key1: \"bad1\",\n      nest: {\n        key2: \"bad2\",\n      },\n    },\n  };\n  await cd.setupData(data);\n\n  t.is(data.returnobj.key1, \"value1\");\n  t.is(data.returnobj.nest.key2, \"value2\");\n});\n\ntest(\"Boolean computed value Issue #1114\", async (t) => {\n  let cd = new ComputedData();\n\n  cd.add(\"bool1\", true);\n\n  let data = {\n    key2: \"inject me\",\n  };\n  await cd.setupData(data);\n\n  t.is(data.bool1, true);\n  t.is(data.key2, \"inject me\");\n});\n\ntest(\"Expect even missing collections to be arrays in data callback #1114\", async (t) => {\n  t.plan(2);\n  let cd = new ComputedData();\n\n  cd.add(\"key1\", (data) => {\n    t.is(Array.isArray(data.collections.first), true);\n    t.is(Array.isArray(data.collections.second), true);\n    return ``;\n  });\n\n  let data = {\n    collections: {},\n  };\n  await cd.resolveVarOrder(data);\n});\n\ntest(\"Expect collections to be arrays in data callback #1114\", async (t) => {\n  t.plan(2);\n  let cd = new ComputedData();\n\n  cd.add(\"key1\", (data) => {\n    if (data.collections.first.length) {\n      t.is(data.collections.first[0], 1);\n      t.is(data.collections.second[0], 2);\n    }\n    return ``;\n  });\n\n  let data = {\n    collections: {\n      first: [1],\n      second: [2],\n    },\n  };\n  await cd.setupData(data);\n});\n\ntest(\"Get var order\", async (t) => {\n  let cd = new ComputedData();\n\n  cd.add(\"key1\", (data) => data.collections.all);\n  cd.add(\"key2\", (data) => data.collections.dog);\n  cd.add(\"key0\", (data) => \"\");\n\n  let data = {\n    key2: \"inject me\",\n    collections: {\n      all: [1],\n      dog: [2],\n    },\n  };\n\n  await cd.resolveVarOrder(data);\n  t.deepEqual(cd.queue.getOrder(), [\"collections.all\", \"key1\", \"collections.dog\", \"key2\", \"key0\"]);\n});\n\ntest(\"Get var order and process it in two stages\", async (t) => {\n  let cd = new ComputedData();\n\n  cd.add(\"page.url\", (data) => data.key2);\n  cd.add(\"page.outputPath\", (data) => data.key2);\n  cd.add(\"key0\", (data) => \"hi\");\n  cd.add(\"key1\", (data) => data.collections.dog[0]);\n  cd.add(\"collections.processed\", (data) => \"hi\");\n\n  let data = {\n    key2: \"/my-path/\",\n    collections: {\n      dog: [2],\n    },\n  };\n\n  // set page.url, page.outputPath, key2, collections.dog[0]\n  await cd.setupData(data, function (entry) {\n    // TODO see note in Template.js about changing the two pass computed data\n    return !this.isUsesStartsWith(entry, \"collections.\");\n  });\n\n  t.deepEqual(data, {\n    collections: {\n      dog: [2],\n      processed: \"\",\n    },\n    key0: \"hi\",\n    key1: \"\",\n    key2: \"/my-path/\",\n    page: {\n      url: \"/my-path/\",\n      outputPath: \"/my-path/\",\n    },\n  });\n\n  // set collections.processed\n  await cd.setupData(data);\n\n  t.deepEqual(data, {\n    collections: {\n      dog: [2],\n      processed: \"hi\",\n    },\n    key0: \"hi\",\n    key1: 2,\n    key2: \"/my-path/\",\n    page: {\n      url: \"/my-path/\",\n      outputPath: \"/my-path/\",\n    },\n  });\n\n  // t.deepEqual(cd.queue.getOrder(), [\"collections.all\", \"key1\", \"collections.dog\", \"key2\", \"key0\"]);\n});\n\ntest(\"Use JavaScript functions (filters) in computed data functions\", async (t) => {\n  let eleventyCfg = new TemplateConfig();\n  await eleventyCfg.init();\n\n  let cfg = eleventyCfg.getConfig();\n  cfg.javascriptFunctions.alwaysBlue = function (str) {\n    return str + \" is blue\";\n  };\n  let cd = new ComputedData(cfg);\n\n  cd.add(\"key1\", function (data) {\n    return this.alwaysBlue(\"this is a test\");\n  });\n\n  let data = {};\n  await cd.setupData(data);\n\n  t.is(data.key1, \"this is a test is blue\");\n});\n"
  },
  {
    "path": "test/ConsoleLoggerTest.js",
    "content": "import test from \"ava\";\nimport ConsoleLogger from \"../src/Util/ConsoleLogger.js\";\n\ntest(\"Disable chalk\", (t) => {\n  let cl = new ConsoleLogger();\n  cl.isChalkEnabled = false;\n  t.is(cl.isChalkEnabled, false);\n});\n\ntest(\"Re-enable chalk\", (t) => {\n  let cl = new ConsoleLogger();\n  cl.isChalkEnabled = false;\n  cl.isChalkEnabled = true;\n  t.is(cl.isChalkEnabled, true);\n});\n\ntest(\"Message styles\", (t) => {\n  let cl = new ConsoleLogger();\n  let logged;\n  cl.message = (msg, type, color, forceToConsole) =>\n    (logged = { msg, type, color, forceToConsole });\n\n  cl.log(\"test\");\n  t.deepEqual(logged, {\n    msg: \"test\",\n    type: undefined,\n    color: undefined,\n    forceToConsole: undefined,\n  });\n\n  cl.forceLog(\"test\");\n  t.deepEqual(logged, {\n    msg: \"test\",\n    type: undefined,\n    color: undefined,\n    forceToConsole: true,\n  });\n\n  cl.info(\"test\");\n  t.deepEqual(logged, {\n    msg: \"test\",\n    type: \"log\",\n    color: \"blue\",\n    forceToConsole: undefined,\n  });\n\n  cl.warn(\"test\");\n  t.deepEqual(logged, {\n    msg: \"test\",\n    type: \"warn\",\n    color: \"yellow\",\n    forceToConsole: undefined,\n  });\n\n  cl.error(\"test\");\n  t.deepEqual(logged, {\n    msg: \"test\",\n    type: \"error\",\n    color: \"red\",\n    forceToConsole: undefined,\n  });\n});\n"
  },
  {
    "path": "test/DependencyGraphTest.js",
    "content": "import test from \"ava\";\nimport { DepGraph as DependencyGraph } from \"dependency-graph\";\n\ntest(\"Dependency graph nodes don’t require dependencies\", async (t) => {\n  let graph = new DependencyGraph();\n\n  graph.addNode(\"all\");\n  graph.addNode(\"template-a\");\n  graph.addNode(\"template-b\");\n  graph.addNode(\"template-c\");\n\n  let order = graph.overallOrder();\n  t.true(order.includes(\"all\"));\n  t.true(order.includes(\"template-a\"));\n  t.true(order.includes(\"template-b\"));\n  t.true(order.includes(\"template-c\"));\n\n  // in order of addNode\n  t.deepEqual(graph.overallOrder(), [\"all\", \"template-a\", \"template-b\", \"template-c\"]);\n});\n\ntest(\"Dependency graph relationships\", async (t) => {\n  let graph = new DependencyGraph();\n\n  graph.addNode(\"all\");\n  graph.addNode(\"template-a\");\n  graph.addNode(\"template-b\");\n  graph.addNode(\"template-c\");\n  graph.addNode(\"userCollection\");\n\n  graph.addDependency(\"all\", \"template-a\");\n  graph.addDependency(\"all\", \"template-b\");\n  graph.addDependency(\"all\", \"template-c\");\n  graph.addDependency(\"userCollection\", \"all\");\n\n  t.deepEqual(graph.overallOrder(), [\n    \"template-a\",\n    \"template-b\",\n    \"template-c\",\n    \"all\",\n    \"userCollection\",\n  ]);\n});\n\ntest(\"Do dependencies (edges) get removed when nodes are deleted? (yes)\", async (t) => {\n  let graph = new DependencyGraph();\n\n  graph.addNode(\"template-a\");\n  graph.addNode(\"template-b\");\n  graph.addDependency(\"template-a\", \"template-b\");\n  t.deepEqual(graph.overallOrder(), [\"template-b\", \"template-a\"]);\n\n  t.deepEqual(graph.dependenciesOf(\"template-a\"), [\"template-b\"]);\n\n  graph.removeNode(\"template-b\");\n  t.deepEqual(graph.dependenciesOf(\"template-a\"), []);\n\n  graph.addNode(\"template-b\");\n  t.deepEqual(graph.dependenciesOf(\"template-a\"), []);\n\n  t.deepEqual(graph.overallOrder(), [\"template-a\", \"template-b\"]);\n});\n"
  },
  {
    "path": "test/DirContainsTest.js",
    "content": "import test from \"ava\";\nimport DirContains from \"../src/Util/DirContains.js\";\n\ntest(\"Compare to current dir\", (t) => {\n  t.true(DirContains(\".\", \".\"));\n  t.false(DirContains(\".\", \"..\"));\n  t.true(DirContains(\".\", \"test\"));\n  t.true(DirContains(\".\", \"./test\"));\n  t.false(DirContains(\".\", \"../test\"));\n});\n\ntest(\"Compare to parent dir\", (t) => {\n  t.true(DirContains(\"..\", \".\"));\n  t.true(DirContains(\"..\", \"..\"));\n  t.false(DirContains(\"..\", \"../..\"));\n  t.true(DirContains(\"..\", \"test\"));\n  t.true(DirContains(\"..\", \"./test\"));\n  t.true(DirContains(\"..\", \"../test\"));\n});\n\ntest(\"Compare to subfolder\", (t) => {\n  t.false(DirContains(\"test\", \".\"));\n  t.false(DirContains(\"test\", \"..\"));\n  t.false(DirContains(\"test\", \"../..\"));\n  t.true(DirContains(\"test\", \"test\"));\n  t.true(DirContains(\"test\", \"./test\"));\n  t.false(DirContains(\"test\", \"../test\"));\n});\n\ntest(\"Compare to subfolder dot slash\", (t) => {\n  t.false(DirContains(\"./test\", \".\"));\n  t.false(DirContains(\"./test\", \"..\"));\n  t.false(DirContains(\"./test\", \"../..\"));\n  t.true(DirContains(\"./test\", \"test\"));\n  t.true(DirContains(\"./test\", \"./test\"));\n  t.false(DirContains(\"./test\", \"../test\"));\n});\n\ntest(\"Compare to sibling folder\", (t) => {\n  t.false(DirContains(\"../test\", \".\"));\n  t.false(DirContains(\"../test\", \"..\"));\n  t.false(DirContains(\"../test\", \"../..\"));\n  t.false(DirContains(\"../test\", \"test\"));\n  t.false(DirContains(\"../test\", \"./test\"));\n  t.true(DirContains(\"../test\", \"../test\"));\n  t.true(DirContains(\"../test\", \"../test/sub1\"));\n});\n"
  },
  {
    "path": "test/EleventyAddGlobalDataTest.js",
    "content": "import test from \"ava\";\nimport Eleventy from \"../src/Eleventy.js\";\n\ntest(\"Eleventy addGlobalData should run once\", async (t) => {\n  let count = 0;\n  let elev = new Eleventy(\"./test/stubs-addglobaldata/\", \"./test/stubs-addglobaldata/_site\", {\n    config: function (eleventyConfig) {\n      eleventyConfig.addGlobalData(\"count\", () => {\n        count++;\n        return count;\n      });\n    },\n  });\n\n  let results = await elev.toJSON();\n  t.is(count, 1);\n});\n\ntest(\"Eleventy addGlobalData shouldn’t run if no input templates match!\", async (t) => {\n  let count = 0;\n  let elev = new Eleventy(\n    \"./test/stubs-addglobaldata-noop/\",\n    \"./test/stubs-addglobaldata-noop/_site\",\n    {\n      config: function (eleventyConfig) {\n        eleventyConfig.addGlobalData(\"count\", () => {\n          count++;\n          return count;\n        });\n      },\n    }\n  );\n\n  let results = await elev.toJSON();\n  t.is(count, 0);\n});\n\ntest(\"Eleventy addGlobalData can feed layouts to populate data cascade with layout data, issue #1245\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-2145/\", \"./test/stubs-2145/_site\", {\n    config: function (eleventyConfig) {\n      eleventyConfig.addGlobalData(\"layout\", () => \"layout.njk\");\n      eleventyConfig.dataFilterSelectors.add(\"LayoutData\");\n    },\n  });\n\n  let [result] = await elev.toJSON();\n  t.deepEqual(result.data, { LayoutData: 123 });\n  t.is(result.content.trim(), \"FromLayoutlayout.njk\");\n});\n\ntest(\"Eleventy addGlobalData merge data #3389\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: function (eleventyConfig) {\n      eleventyConfig.addGlobalData(\"eleventyComputed\", {\n        testing(data) {\n          return `testing:${data.page.url}`;\n        }\n      });\n\n      eleventyConfig.addGlobalData(\"eleventyComputed\", {\n        other(data) {\n          return `other:${data.page.url}`;\n        }\n      });\n\n      eleventyConfig.addTemplate(\"computed.njk\", \"{{ testing }}|{{ other }}\", {})\n    },\n  });\n\n  let results = await elev.toJSON();\n  t.is(results.length, 1);\n  t.is(results[0].content, \"testing:/computed/|other:/computed/\");\n});\n\ntest(\"Eleventy addGlobalData merge data #3389 lodash set\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: function (eleventyConfig) {\n      eleventyConfig.addGlobalData(\"eleventyComputed.testing\", () => {\n        return (data) => {\n          return `testing:${data.page.url}`;\n        }\n      });\n\n      eleventyConfig.addGlobalData(\"eleventyComputed.other\", () => {\n        return (data) => {\n          return `other:${data.page.url}`;\n        }\n      });\n\n      eleventyConfig.addTemplate(\"computed.njk\", \"{{ testing }}|{{ other }}\", {})\n    },\n  });\n\n  let results = await elev.toJSON();\n  t.is(results.length, 1);\n  t.is(results[0].content, \"testing:/computed/|other:/computed/\");\n});\n\ntest.skip(\"Eleventy addGlobalData merge data #3389 no nested function\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: function (eleventyConfig) {\n      eleventyConfig.addGlobalData(\"eleventyComputed.testing\", (data) => {\n        return `testing:${data.page.url}`;\n      });\n\n      eleventyConfig.addGlobalData(\"eleventyComputed.other\", (data) => {\n        return `other:${data.page.url}`;\n      });\n\n      eleventyConfig.addTemplate(\"computed.njk\", \"{{ testing }}|{{ other }}\", {})\n    },\n  });\n\n  let results = await elev.toJSON();\n  t.is(results.length, 1);\n  t.is(results[0].content, \"testing:/computed/|other:/computed/\");\n});\n\n"
  },
  {
    "path": "test/EleventyErrorHandlerTest.js",
    "content": "import test from \"ava\";\nimport { EleventyErrorHandler } from \"../src/Errors/EleventyErrorHandler.js\";\n\ntest(\"Log a warning, warning\", (t) => {\n  let errorHandler = new EleventyErrorHandler();\n  let output = [];\n  errorHandler.logger = {\n    log: function (str) {\n      output.push(str);\n    },\n    warn: function (str) {\n      output.push(str);\n    },\n    error: function (str) {\n      output.push(str);\n    },\n    message: function (str) {\n      output.push(str);\n    },\n  };\n  errorHandler.warn(new Error(\"Test warning\"), \"Hello\");\n\n  let expected = \"Hello:\";\n  t.is(output.join(\"\\n\").slice(0, expected.length), expected);\n});\n\ntest(\"Log a warning, error\", (t) => {\n  let errorHandler = new EleventyErrorHandler();\n\n  let output = [];\n  errorHandler.logger = {\n    log: function (str) {\n      output.push(str);\n    },\n    warn: function (str) {\n      output.push(str);\n    },\n    error: function (str) {\n      output.push(str);\n    },\n    message: function (str) {\n      output.push(str);\n    },\n  };\n\n  errorHandler.error(new Error(\"Test error\"), \"It’s me\");\n\n  let expected = `It’s me:\nTest error\n\nOriginal error stack trace: Error: Test error`;\n  t.is(output.join(\"\\n\").slice(0, expected.length), expected);\n});\n"
  },
  {
    "path": "test/EleventyErrorUtilTest.js",
    "content": "import test from \"ava\";\nimport EleventyErrorUtil from \"../src/Errors/EleventyErrorUtil.js\";\n\nconst SAMPLE_ERROR = new Error(\"Nothing to see here\");\n\nconst { cleanMessage, hasEmbeddedError, convertErrorToString, deconvertErrorToObject } =\n  EleventyErrorUtil;\n\ntest(\"hasEmbeddedError()\", (t) => {\n  t.false(hasEmbeddedError(\"\"));\n  t.true(hasEmbeddedError(convertErrorToString(SAMPLE_ERROR)));\n});\n\ntest(\"cleanMessage()\", (t) => {\n  t.is(cleanMessage(null), \"\");\n  t.is(cleanMessage(undefined), \"\");\n  t.is(cleanMessage(false), \"\");\n  t.is(cleanMessage(\"\"), \"\");\n\n  const text = \"I am the very model of a sample text input\";\n  t.is(cleanMessage(text), text);\n  t.is(cleanMessage(text + convertErrorToString(SAMPLE_ERROR)), text);\n});\n\ntest(\"deconvertErrorToObject() should throw on invalid inputs\", (t) => {\n  t.throws(() => deconvertErrorToObject(undefined), {\n    message: \"Could not convert error object from: undefined\",\n  });\n  t.throws(() => deconvertErrorToObject(\"\"), {\n    message: \"Could not convert error object from: \",\n  });\n  t.throws(() => deconvertErrorToObject(\"Not an error\"), {\n    message: \"Could not convert error object from: Not an error\",\n  });\n});\n\ntest(\"deconvertErrorToObject() should return its argument if it does not contain another error\", (t) => {\n  t.is(deconvertErrorToObject(SAMPLE_ERROR), SAMPLE_ERROR);\n});\n\ntest(\"deconvertErrorToObject() should get message and stack from convertErrorToString()\", (t) => {\n  const nestingError = new Error(\n    \"This error contains a sample error: \" + convertErrorToString(SAMPLE_ERROR)\n  );\n  const result = deconvertErrorToObject(nestingError);\n  t.is(result.name, nestingError.name);\n  t.is(result.message, SAMPLE_ERROR.message);\n  t.is(result.stack, SAMPLE_ERROR.stack);\n});\n"
  },
  {
    "path": "test/EleventyExtensionMapTest.js",
    "content": "import test from \"ava\";\nimport EleventyExtensionMap from \"../src/EleventyExtensionMap.js\";\nimport TemplateEngineManager from \"../src/Engines/TemplateEngineManager.js\";\nimport TemplateConfig from \"../src/TemplateConfig.js\";\n\nasync function getExtensionMap(formats, config = new TemplateConfig()) {\n  if (config) {\n    await config.init();\n  }\n  let map = new EleventyExtensionMap(config);\n  map.setFormats(formats);\n  map.engineManager = new TemplateEngineManager(config);\n  return map;\n}\n\ntest(\"Empty formats\", async (t) => {\n  let map = await getExtensionMap([]);\n  t.deepEqual(map.getGlobs(\".\"), []);\n});\ntest(\"Single format\", async (t) => {\n  let map = await getExtensionMap([\"liquid\"]);\n  t.deepEqual(map.getGlobs(\".\"), [\"./**/*.liquid\"]);\n  t.deepEqual(map.getGlobs(\"src\"), [\"./src/**/*.liquid\"]);\n});\ntest(\"Multiple formats\", async (t) => {\n  let map = await getExtensionMap([\"njk\", \"liquid\"]);\n  t.deepEqual(map.getGlobs(\".\"), [\"./**/*.{njk,liquid}\"]);\n  t.deepEqual(map.getGlobs(\"src\"), [\"./src/**/*.{njk,liquid}\"]);\n});\n\ntest(\"Invalid keys are filtered (using passthrough copy)\", async (t) => {\n  let map = await getExtensionMap([\"lksdjfjlsk\"]);\n  t.deepEqual(map.getGlobs(\".\"), [\"./**/*.lksdjfjlsk\"]);\n});\n\ntest(\"Keys are mapped to lower case\", async (t) => {\n  let map = await getExtensionMap([\"LIQUID\", \"PUG\", \"NJK\"]);\n  t.deepEqual(map.getGlobs(\".\"), [\"./**/*.{liquid,pug,njk}\"]);\n});\n\ntest(\"Pruned globs\", async (t) => {\n  let map = await getExtensionMap([\"liquid\", \"njk\", \"png\"]);\n  t.deepEqual(map.getPassthroughCopyGlobs(\".\"), [\"./**/*.png\"]);\n});\n\ntest(\"Empty path for fileList\", async (t) => {\n  let map = await getExtensionMap([\"njk\", \"liquid\"]);\n  t.deepEqual(map.getFileList(), []);\n});\n\ntest(\"fileList\", async (t) => {\n  let map = await getExtensionMap([\"njk\", \"liquid\"]);\n  t.deepEqual(map.getFileList(\"filename\"), [\"filename.njk\", \"filename.liquid\"]);\n});\n\ntest(\"fileList with dir\", async (t) => {\n  let map = await getExtensionMap([\"njk\", \"liquid\"]);\n  t.deepEqual(map.getFileList(\"filename\", \"_includes\"), [\n    \"_includes/filename.njk\",\n    \"_includes/filename.liquid\",\n  ]);\n});\n\ntest(\"fileList with dir in path\", async (t) => {\n  let map = await getExtensionMap([\"njk\", \"liquid\"]);\n  t.deepEqual(map.getFileList(\"layouts/filename\"), [\n    \"layouts/filename.njk\",\n    \"layouts/filename.liquid\",\n  ]);\n});\n\ntest(\"fileList with dir in path and dir\", async (t) => {\n  let map = await getExtensionMap([\"njk\", \"liquid\", \"pug\"]);\n  t.deepEqual(map.getFileList(\"layouts/filename\", \"_includes\"), [\n    \"_includes/layouts/filename.njk\",\n    \"_includes/layouts/filename.liquid\",\n  ]);\n});\n\ntest(\"removeTemplateExtension\", async (t) => {\n  let map = await getExtensionMap([\"njk\", \"11ty.js\"]);\n  t.is(map.removeTemplateExtension(\"component.njk\"), \"component\");\n  t.is(map.removeTemplateExtension(\"component.11ty.js\"), \"component\");\n\n  t.is(map.removeTemplateExtension(\"\"), \"\");\n  t.is(map.removeTemplateExtension(\"component\"), \"component\");\n  t.is(map.removeTemplateExtension(\"component.js\"), \"component.js\");\n});\n\ntest(\"hasEngine\", async (t) => {\n  let map = await getExtensionMap([\"liquid\", \"njk\", \"11ty.js\"]);\n  t.true(map.hasEngine(\"default.liquid\"));\n  t.is(map.getKey(\"default.liquid\"), \"liquid\");\n  t.falsy(map.getKey());\n  t.is(map.getKey(\"LiQuid\"), \"liquid\");\n  t.true(map.hasEngine(\"LiqUiD\"));\n  t.true(map.hasEngine(\"liquid\"));\n  t.falsy(map.getKey(\"sldkjfkldsj\"));\n  t.false(map.hasEngine(\"sldkjfkldsj\"));\n\n  t.is(map.getKey(\"11ty.js\"), \"11ty.js\");\n  t.true(map.hasEngine(\"11ty.js\"));\n\n  t.falsy(map.getKey(\"md\"));\n  t.false(map.hasEngine(\"md\"));\n});\n\ntest(\"hasEngine no formats passed in\", async (t) => {\n  let map = await getExtensionMap([]);\n  t.false(map.hasEngine(\"default.liquid\"));\n  t.falsy(map.getKey(\"default.liquid\"));\n  t.falsy(map.getKey());\n  t.falsy(map.getKey(\"LiQuid\"));\n  t.false(map.hasEngine(\"LiqUiD\"));\n  t.false(map.hasEngine(\"liquid\"));\n  t.falsy(map.getKey(\"sldkjfkldsj\"));\n  t.false(map.hasEngine(\"sldkjfkldsj\"));\n  t.falsy(map.getKey(\"11ty.js\"));\n  t.false(map.hasEngine(\"11ty.js\"));\n  t.falsy(map.getKey(\"md\"));\n  t.false(map.hasEngine(\"md\"));\n});\n\ntest(\"getKey\", async (t) => {\n  let map = await getExtensionMap([\"njk\", \"11ty.js\", \"md\"]);\n  t.is(map.getKey(\"component.njk\"), \"njk\");\n  t.is(map.getKey(\"component.11ty.js\"), \"11ty.js\");\n  t.is(map.getKey(\"11ty.js\"), \"11ty.js\");\n  t.is(map.getKey(\".11ty.js\"), \"11ty.js\");\n\n  t.is(map.getKey(\"sample.md\"), \"md\");\n\n  t.is(map.getKey(\"\"), undefined);\n  t.is(map.getKey(\"js\"), undefined);\n  t.is(map.getKey(\"component\"), undefined);\n  t.is(map.getKey(\"component.js\"), undefined);\n});\n\ntest(\"isFullTemplateFilePath (not a passthrough copy extension)\", async (t) => {\n  let map = await getExtensionMap([\"liquid\", \"njk\", \"11ty.js\", \"js\", \"css\"]);\n  t.true(map.isFullTemplateFilePath(\"template.liquid\"));\n  t.true(map.isFullTemplateFilePath(\"template.njk\"));\n  t.true(map.isFullTemplateFilePath(\"template.11ty.js\"));\n  t.false(map.isFullTemplateFilePath(\"template.ejs\"));\n  t.false(map.isFullTemplateFilePath(\"template.pug\"));\n  t.false(map.isFullTemplateFilePath(\"passthrough.js\"));\n  t.false(map.isFullTemplateFilePath(\"passthrough.css\"));\n});\n\ntest(\"getValidExtensionsForPath\", async (t) => {\n  let cfg = new TemplateConfig();\n  cfg.userConfig.extensionMap.add({\n    key: \"js\",\n    extension: \"js\",\n  });\n  await cfg.init();\n\n  let map = await getExtensionMap([\"liquid\", \"njk\", \"11ty.js\", \"js\"], cfg);\n\n  t.deepEqual(map.getValidExtensionsForPath(\"template.liquid\"), [\"liquid\"]);\n  t.deepEqual(map.getValidExtensionsForPath(\"template.11ty.js\"), [\"11ty.js\", \"js\"]);\n  t.deepEqual(map.getValidExtensionsForPath(\"template.pug\"), []);\n  t.deepEqual(map.getValidExtensionsForPath(\"template.liquid.js\"), [\"js\"]);\n  t.deepEqual(map.getValidExtensionsForPath(\"njk.liquid.11ty.js\"), [\"11ty.js\", \"js\"]);\n});\n\ntest(\"shouldSpiderJavaScriptDependencies\", async (t) => {\n  let cfg = new TemplateConfig();\n  cfg.userConfig.extensionMap.add({\n    key: \"js\",\n    extension: \"js\",\n  });\n  await cfg.init();\n\n  let map = await getExtensionMap([\"liquid\", \"njk\", \"11ty.js\", \"js\"], cfg);\n\n  t.deepEqual(await map.shouldSpiderJavaScriptDependencies(\"template.liquid\"), false);\n  t.deepEqual(await map.shouldSpiderJavaScriptDependencies(\"template.njk\"), false);\n  t.deepEqual(await map.shouldSpiderJavaScriptDependencies(\"template.css\"), false);\n  t.deepEqual(await map.shouldSpiderJavaScriptDependencies(\"template.11ty.js\"), true);\n  t.deepEqual(await map.shouldSpiderJavaScriptDependencies(\"template.js\"), false);\n});\n"
  },
  {
    "path": "test/EleventyFilesGitIgnoreEleventyIgnoreTest.js",
    "content": "import test from \"ava\";\nimport { TemplatePath } from \"@11ty/eleventy-utils\";\n\nimport { getTemplateConfigInstance, getTemplateConfigInstanceCustomCallback, getEleventyFilesInstance } from \"./_testHelpers.js\";\n\n/* .eleventyignore and .gitignore combos */\n\ntest(\"Get ignores (no .eleventyignore no .gitignore)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs/ignore1\",\n      output: \"test/stubs/ignore1/_site\"\n    }\n  });\n\n  let { eleventyFiles: evf } = getEleventyFilesInstance([], eleventyConfig);\n  evf._setLocalPathRoot(\"./test/stubs/ignorelocalroot\");\n\n  t.deepEqual(evf.getIgnores(), [\n    \"./test/stubs/ignorelocalroot/test.md\",\n    \"./test/stubs/ignore1/_site/**\",\n  ]);\n\n  t.deepEqual(evf.getIgnoreGlobs().slice(-2), [\n    \"**/node_modules/**\",\n    \"./test/stubs/ignorelocalroot/.git/**\",\n  ]);\n});\n\ntest(\"Get ignores (no .eleventyignore)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs/ignore2\",\n      output: \"test/stubs/ignore2/_site\"\n    }\n  });\n\n  let { eleventyFiles: evf } = getEleventyFilesInstance([], eleventyConfig);\n  evf._setLocalPathRoot(\"./test/stubs/ignorelocalrootgitignore\");\n\n  t.deepEqual(evf.getIgnores(), [\n    \"./test/stubs/ignorelocalrootgitignore/thisshouldnotexist12345\",\n    \"./test/stubs/ignorelocalrootgitignore/test.md\",\n    \"./test/stubs/ignore2/_site/**\",\n  ]);\n\n  t.deepEqual(evf.getIgnoreGlobs().slice(-2), [\n    \"**/node_modules/**\",\n    \"./test/stubs/ignorelocalrootgitignore/.git/**\",\n  ]);\n});\n\ntest(\"Get ignores (no .eleventyignore, using setUseGitIgnore(false))\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback({\n    input: \"test/stubs/ignore2\",\n    output: \"test/stubs/ignore2/_site\",\n  }, function(eleventyConfig) {\n    eleventyConfig.setUseGitIgnore(false);\n  });\n\n  let { eleventyFiles: evf } = getEleventyFilesInstance([], eleventyConfig);\n  evf._setLocalPathRoot(\"./test/stubs/ignorelocalroot\");\n\n  t.deepEqual(evf.getIgnores(), [\n    \"./test/stubs/ignorelocalroot/test.md\",\n    \"./test/stubs/ignore2/_site/**\",\n  ]);\n\n  t.deepEqual(evf.getIgnoreGlobs().slice(-2), [\n\t\t\"**/node_modules/**\",\n\t\t\"./test/stubs/ignorelocalroot/.git/**\",\n\t]);\n});\n\ntest(\"Get ignores (no .gitignore)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs/ignore3\",\n      output: \"test/stubs/ignore3/_site\"\n    }\n  });\n\n  let { eleventyFiles: evf } = getEleventyFilesInstance([], eleventyConfig);\n  evf._setLocalPathRoot(\"./test/stubs/ignorelocalroot\");\n\n  t.deepEqual(evf.getIgnores(), [\n    \"./test/stubs/ignorelocalroot/test.md\",\n    \"./test/stubs/ignore3/ignoredFolder/**\",\n    \"./test/stubs/ignore3/ignoredFolder/ignored.md\",\n    \"./test/stubs/ignore3/_site/**\",\n  ]);\n\n  t.deepEqual(evf.getIgnoreGlobs().slice(-2), [\n    \"**/node_modules/**\",\n    \"./test/stubs/ignorelocalroot/.git/**\",\n  ]);\n});\n\ntest(\"Get ignores (project .eleventyignore and root .gitignore)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs/ignore4\",\n      output: \"test/stubs/ignore4/_site\"\n    }\n  });\n\n  let { eleventyFiles: evf } = getEleventyFilesInstance([], eleventyConfig);\n  evf._setLocalPathRoot(\"./test/stubs/ignorelocalrootgitignore\");\n\n  t.deepEqual(evf.getIgnores(), [\n    \"./test/stubs/ignorelocalrootgitignore/thisshouldnotexist12345\",\n    \"./test/stubs/ignorelocalrootgitignore/test.md\",\n    \"./test/stubs/ignore4/ignoredFolder/**\",\n    \"./test/stubs/ignore4/ignoredFolder/ignored.md\",\n    \"./test/stubs/ignore4/_site/**\",\n  ]);\n\n  t.deepEqual(evf.getIgnoreGlobs().slice(-2), [\n    \"**/node_modules/**\",\n    \"./test/stubs/ignorelocalrootgitignore/.git/**\",\n  ]);\n});\n\ntest(\"Get ignores (project .eleventyignore and root .gitignore, using setUseGitIgnore(false))\", async (t) => {\n\tlet eleventyConfig = await getTemplateConfigInstanceCustomCallback({\n\t\tinput: \"test/stubs/ignore4\",\n\t\toutput: \"test/stubs/ignore4/_site\",\n  }, function(eleventyConfig) {\n    eleventyConfig.setUseGitIgnore(false);\n  });\n\n  let { eleventyFiles: evf } = getEleventyFilesInstance([], eleventyConfig);\n\n  evf._setLocalPathRoot(\"./test/stubs/ignorelocalrootgitignore\");\n\n  t.deepEqual(evf.getIgnores(), [\n    \"./test/stubs/ignorelocalrootgitignore/test.md\",\n    \"./test/stubs/ignore4/ignoredFolder/**\",\n    \"./test/stubs/ignore4/ignoredFolder/ignored.md\",\n    \"./test/stubs/ignore4/_site/**\",\n  ]);\n\n  t.deepEqual(evf.getIgnoreGlobs().slice(-2), [\n    \"**/node_modules/**\",\n    \"./test/stubs/ignorelocalrootgitignore/.git/**\",\n  ]);\n});\n\ntest(\"Get ignores (no .eleventyignore  .gitignore exists but empty)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs/ignore5\",\n      output: \"test/stubs/ignore5/_site\"\n    }\n  });\n\n  let { eleventyFiles: evf } = getEleventyFilesInstance([], eleventyConfig);\n\n  evf._setLocalPathRoot(\"./test/stubs/ignorelocalroot\");\n\n  t.deepEqual(evf.getIgnores(), [\n    \"./test/stubs/ignorelocalroot/test.md\",\n    \"./test/stubs/ignore5/_site/**\",\n  ]);\n\n  t.deepEqual(evf.getIgnoreGlobs().slice(-2), [\n    \"**/node_modules/**\",\n    \"./test/stubs/ignorelocalroot/.git/**\",\n  ]);\n});\n\ntest(\"Get ignores (both .eleventyignore and .gitignore exists, but .gitignore is empty)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs/ignore6\",\n      output: \"test/stubs/ignore6/_site\"\n    }\n  });\n\n  let { eleventyFiles: evf } = getEleventyFilesInstance([], eleventyConfig);\n  evf._setLocalPathRoot(\"./test/stubs/ignorelocalroot\");\n\n  t.deepEqual(evf.getIgnores(), [\n    \"./test/stubs/ignorelocalroot/test.md\",\n    \"./test/stubs/ignore6/ignoredFolder/**\",\n    \"./test/stubs/ignore6/ignoredFolder/ignored.md\",\n    \"./test/stubs/ignore6/_site/**\",\n  ]);\n\n  t.deepEqual(evf.getIgnoreGlobs().slice(-2), [\n    \"**/node_modules/**\",\n    \"./test/stubs/ignorelocalroot/.git/**\",\n  ]);\n});\n\ntest(\"Bad expected output, this indicates a bug upstream in a dependency (update, was fixed in fast-glob@3.3.3).  Input to 'src' and empty includes dir (issue #403, full paths in eleventyignore)\", async (t) => {\n\tlet eleventyConfig = await getTemplateConfigInstanceCustomCallback({\n\t\tinput: \"test/stubs-403\",\n\t\toutput: \"_site\",\n\t\tincludes: \"\",\n\t\tdata: false,\n  }, function(eleventyConfig) {\n    eleventyConfig.setUseGitIgnore(false);\n  });\n\n  let { eleventyFiles: evf } = getEleventyFilesInstance([\"liquid\"], eleventyConfig);\n  evf._setEleventyIgnoreContent(TemplatePath.absolutePath(\"test/stubs-403/_includes\") + \"/**\");\n  evf.init(); // duplicate init\n\n  t.deepEqual(await evf.getFiles(), [\n    \"./test/stubs-403/template.liquid\",\n    // UPDATE: this was fixed in fast-glob@3.3.3\n    // This should be excluded from this list but is not because the ignore content used an absolutePath above.\n    // \"./test/stubs-403/_includes/include.liquid\",\n  ]);\n});\n\ntest(\"Workaround for Bad expected output, this indicates a bug upstream in a dependency.  Input to 'src' and empty includes dir (issue #403, full paths in eleventyignore)\", async (t) => {\n\tlet eleventyConfig = await getTemplateConfigInstanceCustomCallback({\n\t\tinput: \"test/stubs-403\",\n\t\toutput: \"_site\",\n\t\tincludes: \"\",\n\t\tdata: false,\n  }, function(eleventyConfig) {\n    eleventyConfig.setUseGitIgnore(false);\n  });\n\n  let { eleventyFiles: evf } = getEleventyFilesInstance([\"liquid\"], eleventyConfig);\n  evf._setEleventyIgnoreContent(\"./test/stubs-403/_includes/**\");\n  evf.init(); // duplicate init\n\n  t.deepEqual(await evf.getFiles(), [\"./test/stubs-403/template.liquid\"]);\n});\n\ntest(\"Issue #403: all .eleventyignores should be relative paths not absolute paths\", async (t) => {\n\tlet eleventyConfig = await getTemplateConfigInstanceCustomCallback({\n\t\tinput: \"test/stubs-403\",\n\t\toutput: \"_site\",\n\t\tincludes: \"\",\n\t\tdata: false,\n  }, function(eleventyConfig) {\n    eleventyConfig.setUseGitIgnore(false);\n  });\n\n  let { eleventyFiles: evf } = getEleventyFilesInstance([\"liquid\"], eleventyConfig);\n\n  let globs = await evf.getFileGlobs();\n  t.is(\n    globs.filter((glob) => {\n      return glob.indexOf(TemplatePath.absolutePath()) > -1;\n    }).length,\n    0\n  );\n});\n\ntest(\"Same input and output directories, issues #186 and #1129\", async (t) => {\n\tlet eleventyConfig = await getTemplateConfigInstanceCustomCallback({\n\t\tinput: \"test/stubs\",\n\t\toutput: \"\",\n  }, function(eleventyConfig) {\n    eleventyConfig.setUseGitIgnore(false);\n  });\n\n  let { eleventyFiles: evf } = getEleventyFilesInstance([], eleventyConfig);\n\n  t.deepEqual(\n    evf.getIgnores().filter((entry) => entry.indexOf(\"_site\") > -1),\n    []\n  );\n});\n\ntest(\"Single input file is in the output directory, issues #186\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback({\n    input: \"test/stubs\",\n    output: \"\",\n    includes: \"\",\n  }, function(eleventyConfig) {\n    eleventyConfig.setUseGitIgnore(false);\n  });\n\n  let { eleventyFiles: evf } = getEleventyFilesInstance([\"njk\"], eleventyConfig);\n\n  t.deepEqual(\n    evf.getIgnores().filter((entry) => entry.indexOf(\"_site\") > -1),\n    []\n  );\n});\n\ntest(\"De-duplicated ignores\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs/ignore-dedupe\",\n      output: \"test/stubs/ignore-dedupe/_site\"\n    }\n  });\n\n  let { eleventyFiles: evf } = getEleventyFilesInstance([], eleventyConfig);\n\n  evf._setLocalPathRoot(\"./test/stubs/ignore-dedupe\");\n\n  t.deepEqual(evf.getGlobWatcherFiles(), [\n    \"./test/stubs/ignore-dedupe/_includes/**\",\n    \"./test/stubs/ignore-dedupe/_data/**\",\n  ]);\n\n  t.deepEqual(evf.getIgnores(), [\n    \"./test/stubs/ignore-dedupe/ignoredFolder\",\n    \"./test/stubs/ignore-dedupe/_site/**\",\n  ]);\n\n  t.deepEqual(evf.getIgnoreGlobs().slice(-2), [\n    \"**/node_modules/**\",\n    \"./test/stubs/ignore-dedupe/.git/**\",\n  ]);\n});\n"
  },
  {
    "path": "test/EleventyFilesTest.js",
    "content": "import test from \"ava\";\nimport { glob } from \"tinyglobby\";\n\nimport EleventyFiles from \"../src/EleventyFiles.js\";\nimport TemplateConfig from \"../src/TemplateConfig.js\";\nimport TemplatePassthroughManager from \"../src/TemplatePassthroughManager.js\";\nimport ProjectDirectories from \"../src/Util/ProjectDirectories.js\";\nimport { isTypeScriptSupported } from \"../src/Util/FeatureTests.cjs\";\n\nimport { getTemplateConfigInstance, getTemplateConfigInstanceCustomCallback, getEleventyFilesInstance } from \"./_testHelpers.js\";\n\ntest(\"Dirs paths\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"src\",\n      includes: \"includes\",\n      data: \"data\",\n      output: \"dist\",\n    }\n  });\n\n  let { eleventyFiles: evf } = getEleventyFilesInstance([], eleventyConfig);\n\n  t.deepEqual(evf.inputDir, \"./src/\");\n  t.deepEqual(evf.includesDir, \"./src/includes/\");\n  t.deepEqual(evf.getDataDir(), \"./src/data/\");\n  t.deepEqual(evf.outputDir, \"./dist/\");\n});\n\ntest(\"Dirs paths (relative)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"src\",\n      includes: \"../includes\",\n      data: \"../data\",\n      output: \"dist\",\n    },\n  });\n\n  let { eleventyFiles: evf } = getEleventyFilesInstance([], eleventyConfig);\n\n  t.deepEqual(evf.inputDir, \"./src/\");\n  t.deepEqual(evf.includesDir, \"./includes/\");\n  t.deepEqual(evf.getDataDir(), \"./data/\");\n  t.deepEqual(evf.outputDir, \"./dist/\");\n});\n\ntest(\"getFiles\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"./test/stubs/writeTest\",\n    }\n  });\n\n  let { eleventyFiles: evf } = getEleventyFilesInstance([\"liquid\", \"md\"], eleventyConfig);\n\n  t.deepEqual(await evf.getFiles(), [\"./test/stubs/writeTest/test.md\"]);\n});\n\ntest(\"getFiles (without 11ty.js)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"./test/stubs/writeTestJS\"\n    }\n  });\n\n  let { eleventyFiles: evf } = getEleventyFilesInstance([\"liquid\", \"md\"], eleventyConfig);\n\n  t.deepEqual(await evf.getFiles(), []);\n});\n\ntest(\"getFiles (with 11ty.js)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"./test/stubs/writeTestJS\",\n    }\n  });\n\n  let { eleventyFiles: evf } = getEleventyFilesInstance([\"liquid\", \"md\", \"11ty.js\"], eleventyConfig);\n\n  t.deepEqual(await evf.getFiles(), [\"./test/stubs/writeTestJS/test.11ty.cjs\"]);\n});\n\ntest(\"getFiles (with js, treated as passthrough copy)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"./test/stubs/writeTestJS-passthrough\",\n    }\n  });\n\n  let { eleventyFiles: evf } = getEleventyFilesInstance([\"liquid\", \"md\", \"js\", \"11ty.js\"], eleventyConfig);\n\n  const files = await evf.getFiles();\n  t.deepEqual(\n    files.sort(),\n    [\n      \"./test/stubs/writeTestJS-passthrough/sample.js\",\n      \"./test/stubs/writeTestJS-passthrough/test.11ty.js\",\n    ].sort()\n  );\n\n  t.false(evf.extensionMap.hasEngine(\"./test/stubs/writeTestJS-passthrough/sample.js\"));\n  t.true(evf.extensionMap.hasEngine(\"./test/stubs/writeTestJS-passthrough/test.11ty.js\"));\n});\n\ntest(\"getFiles (with case insensitivity)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"./test/stubs/writeTestJS-casesensitive\",\n    }\n  });\n\n  let { eleventyFiles: evf } = getEleventyFilesInstance([\"11ty.js\", \"JS\"], eleventyConfig);\n\n  t.deepEqual(\n    (await evf.getFiles()).sort(),\n    [\n      \"./test/stubs/writeTestJS-casesensitive/sample.Js\",\n      \"./test/stubs/writeTestJS-casesensitive/test.11Ty.js\",\n    ].sort()\n  );\n  t.false(evf.extensionMap.hasEngine(\"./test/stubs/writeTestJS-casesensitive/sample.Js\"));\n  t.true(evf.extensionMap.hasEngine(\"./test/stubs/writeTestJS-casesensitive/test.11Ty.js\"));\n});\n\ntest(\"Mutually exclusive Input and Output dirs\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"./test/stubs/writeTest\",\n    }\n  });\n\n  let { eleventyFiles: evf } = getEleventyFilesInstance([\"liquid\", \"md\"], eleventyConfig);\n\n  let files = await glob(evf.getFileGlobs());\n  t.deepEqual(evf.getRawFiles(), [\"./test/stubs/writeTest/**/*.{liquid,md}\"]);\n  t.true(files.length > 0);\n  t.is(files[0], \"test/stubs/writeTest/test.md\");\n});\n\ntest(\"Single File Input (deep path)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"./test/stubs/index.html\",\n    }\n  });\n\n  let { eleventyFiles: evf } = getEleventyFilesInstance([\"liquid\", \"md\"], eleventyConfig);\n\n  let files = await glob(evf.getFileGlobs());\n  t.is(evf.getRawFiles().length, 1);\n  t.is(files.length, 1);\n  t.is(files[0], \"test/stubs/index.html\");\n});\n\ntest(\"Single File Input (shallow path)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"README.md\",\n    }\n  });\n\n  let { eleventyFiles: evf } = getEleventyFilesInstance([\"md\"], eleventyConfig);\n\n  let globs = evf.getFileGlobs(); //.filter((path) => path !== \"./README.md\");\n  let files = await glob(globs, {\n    ignore: evf.getIgnoreGlobs(),\n  });\n  t.is(evf.getRawFiles().length, 1);\n  t.is(files.length, 1);\n  t.is(files[0], \"README.md\");\n});\n\ntest(\"Glob Input\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"./test/stubs/glob-pages/!(contact.md)\",\n    }\n  });\n  let { eleventyFiles: evf } = getEleventyFilesInstance([\"md\"], eleventyConfig);\n\n  let globs = evf.getFileGlobs();\n  let files = await glob(globs);\n\n  t.is(files.length, 2);\n  t.is(files[0], \"test/stubs/glob-pages/about.md\");\n  t.is(files[1], \"test/stubs/glob-pages/home.md\");\n});\n\ntest(\".eleventyignore parsing\", (t) => {\n  let ignores = EleventyFiles.getFileIgnores(\"./test/stubs/.eleventyignore\");\n  t.is(ignores.length, 2);\n  t.is(ignores[0], \"./test/stubs/ignoredFolder/**\");\n  t.is(ignores[1], \"./test/stubs/ignoredFolder/ignored.md\");\n});\n\ntest(\"Parse multiple .eleventyignores\", (t) => {\n  let ignores = EleventyFiles.getFileIgnores([\n    \"./test/stubs/multiple-ignores/.eleventyignore\",\n    \"./test/stubs/multiple-ignores/subfolder/.eleventyignore\",\n  ]);\n  t.is(ignores.length, 4);\n  // Note these folders must exist!\n  t.is(ignores[0], \"./test/stubs/multiple-ignores/ignoredFolder/**\");\n  t.is(ignores[1], \"./test/stubs/multiple-ignores/ignoredFolder/ignored.md\");\n  t.is(ignores[2], \"./test/stubs/multiple-ignores/subfolder/ignoredFolder2/**\");\n  t.is(ignores[3], \"./test/stubs/multiple-ignores/subfolder/ignoredFolder2/ignored2.md\");\n});\n\ntest(\"Passed file name does not exist\", (t) => {\n  let ignores = EleventyFiles.getFileIgnores(\".thisfiledoesnotexist\");\n  t.deepEqual(ignores, []);\n});\n\ntest(\".eleventyignore files\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs\"\n    }\n  });\n  let { eleventyFiles: evf } = getEleventyFilesInstance([\"liquid\", \"md\"], eleventyConfig);\n\n  let ignoredFiles = await glob(\"test/stubs/ignoredFolder/*.md\");\n  t.is(ignoredFiles.length, 1);\n\n  let files = await glob(evf.getFileGlobs(), {\n    ignore: evf.getIgnoreGlobs(),\n  });\n\n  t.true(files.length > 0);\n\n  t.is(\n    files.filter((file) => {\n      return file.indexOf(\"./test/stubs/ignoredFolder\") > -1;\n    }).length,\n    0\n  );\n});\n\ntest(\"getTemplateData caching\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs\"\n    }\n  });\n\n  let { eleventyFiles: evf } = getEleventyFilesInstance([], eleventyConfig);\n  evf.init();\n  let templateDataFirstCall = evf.templateData;\n  let templateDataSecondCall = evf.templateData;\n  t.is(templateDataFirstCall, templateDataSecondCall);\n});\n\ntest(\"getDataDir\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \".\"\n    }\n  });\n\n  let { eleventyFiles: evf } = getEleventyFilesInstance([], eleventyConfig);\n  evf.init();\n  t.is(evf.getDataDir(), \"./_data/\");\n});\n\ntest(\"getDataDir subdir\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs\"\n    }\n  });\n\n  let { eleventyFiles: evf } = getEleventyFilesInstance([], eleventyConfig);\n  evf.init();\n  t.is(evf.getDataDir(), \"./test/stubs/_data/\");\n});\n\ntest(\"Include and Data Dirs\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs\"\n    }\n  });\n  let { eleventyFiles: evf } = getEleventyFilesInstance([], eleventyConfig);\n  evf.init();\n\n  t.deepEqual(evf.getIncludesAndDataDirs(), [\n    \"./test/stubs/_includes/**\",\n    \"./test/stubs/_data/**\",\n  ]);\n});\n\ntest(\"Input to 'src' and empty includes dir (issue #403)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"src\"\n    }\n  });\n  let { eleventyFiles: evf } = getEleventyFilesInstance([\"md\", \"liquid\", \"html\"], eleventyConfig);\n  evf._setEleventyIgnoreContent(\"!./src/_includes/**\");\n  evf._setConfig({\n    useGitIgnore: false,\n    dir: {\n      input: \".\",\n      output: \"_site\",\n      includes: \"\",\n      data: \"_data\",\n    },\n  });\n  evf.init(); // duplicate init\n\n  t.deepEqual(evf.getFileGlobs(), [\n    \"./src/**/*.{md,liquid,html}\",\n    // \"!./src/_includes/**\",\n    // \"!./src/_site/**\",\n    // \"!./src/_data/**\",\n  ]);\n});\n\ntest(\"Glob Watcher Files\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs\"\n    }\n  });\n\n  let { eleventyFiles: evf } = getEleventyFilesInstance([\"njk\"], eleventyConfig);\n\n  t.deepEqual(evf.getGlobWatcherFiles(), [\n    \"./test/stubs/**/*.njk\",\n    \"./test/stubs/_includes/**\",\n    \"./test/stubs/_data/**\",\n  ]);\n});\n\ntest(\"Glob Watcher Files with File Extension Passthroughs\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs\"\n    }\n  });\n  let { eleventyFiles: evf } = getEleventyFilesInstance([\"njk\", \"png\"], eleventyConfig);\n\n  t.deepEqual(evf.getGlobWatcherFiles(), [\n    \"./test/stubs/**/*.njk\",\n    \"./test/stubs/**/*.png\",\n    \"./test/stubs/_includes/**\",\n    \"./test/stubs/_data/**\",\n  ]);\n});\n\ntest(\"Glob Watcher Files with File Extension Passthroughs with Dev Server (for free passthrough copy #2456)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs\"\n    }\n  });\n\n  eleventyConfig.userConfig.setServerPassthroughCopyBehavior(\"passthrough\");\n  eleventyConfig.config.serverPassthroughCopyBehavior = \"passthrough\";\n\n  let { eleventyFiles: evf } = getEleventyFilesInstance([\"njk\", \"png\"], eleventyConfig);\n  evf.setRunMode(\"serve\");\n  evf.init(); // duplicate init\n\n  t.deepEqual(evf.getGlobWatcherFiles(), [\n    \"./test/stubs/**/*.njk\",\n    \"./test/stubs/_includes/**\",\n    \"./test/stubs/_data/**\",\n  ]);\n\n  t.deepEqual(evf.getGlobWatcherFilesForPassthroughCopy(), [\"./test/stubs/**/*.png\"]);\n});\n\ntest(\"Glob Watcher Files with Config Passthroughs (one template format)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback({\n    input: \"test/stubs\",\n    output: \"test/stubs/_site\"\n  }, function(cfg) {\n\t\tcfg.passthroughCopies = {\n\t\t\t\"test/stubs/img/\": { outputPath: true },\n\t\t};\n\t});\n\n\n  let { eleventyFiles: evf } = getEleventyFilesInstance([\"njk\"], eleventyConfig);\n\n  t.deepEqual(evf.getGlobWatcherFiles(), [\n    \"./test/stubs/**/*.njk\",\n    \"./test/stubs/img/**\",\n    \"./test/stubs/_includes/**\",\n    \"./test/stubs/_data/**\",\n  ]);\n});\n\ntest(\"Glob Watcher Files with Config Passthroughs (one template format) with Dev Server (for free passthrough copy #2456)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback({\n    input: \"test/stubs\"\n  }, function(cfg) {\n\t\tcfg.setServerPassthroughCopyBehavior(\"passthrough\");\n\n\t\tcfg.passthroughCopies = {\n\t\t\t\"test/stubs/img/\": { outputPath: true },\n\t\t};\n\t});\n\n  let { eleventyFiles: evf } = getEleventyFilesInstance([\"njk\"], eleventyConfig);\n  evf.setRunMode(\"serve\");\n  evf.init(); // duplicate init\n\n  let mgr = new TemplatePassthroughManager(eleventyConfig);\n  evf.setPassthroughManager(mgr);\n\n  t.deepEqual(evf.getGlobWatcherFiles(), [\n    \"./test/stubs/**/*.njk\",\n    \"./test/stubs/_includes/**\",\n    \"./test/stubs/_data/**\",\n  ]);\n\n  t.deepEqual(evf.getGlobWatcherFilesForPassthroughCopy(), [\"./test/stubs/img/**\"]);\n});\n\ntest(\"Glob Watcher Files with Config Passthroughs (no template formats)\", async (t) => {\n  let templateConfig = new TemplateConfig();\n  let projectDirs = new ProjectDirectories();\n  projectDirs.setViaConfigObject({\n    input: \"test/stubs\"\n  });\n  let eleventyConfig = await getTemplateConfigInstance(templateConfig, projectDirs);\n\n  let { eleventyFiles: evf } = getEleventyFilesInstance([], eleventyConfig);\n  evf.init();\n\n  t.deepEqual(await evf.getGlobWatcherTemplateDataFiles(), [\n    `./test/stubs/**/*.{json,11tydata.mjs,11tydata.cjs,11tydata.js${isTypeScriptSupported() ? \",11tydata.mts,11tydata.cts,11tydata.ts\" : \"\"}}`,\n  ]);\n});\n\ntest(\"Test that negations are ignored (for now) PR#709, will change when #693 is implemented\", async (t) => {\n  t.deepEqual(\n    EleventyFiles.normalizeIgnoreContent(\n      \"./\",\n      `hello\n!testing`\n    ),\n    [\"./hello\"]\n  );\n});\n"
  },
  {
    "path": "test/EleventyImgTransformTest.js",
    "content": "import test from \"ava\";\nimport Eleventy from \"../src/Eleventy.js\";\nimport { eleventyImageTransformPlugin } from \"@11ty/eleventy-img\";\nimport { normalizeNewLines } from \"./Util/normalizeNewLines.js\";\n\ntest(\"Default image transform with a single image\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-img-transform/single.md\", \"./test/stubs-img-transform/_site\", {\n    config: eleventyConfig => {\n      eleventyConfig.addPlugin(eleventyImageTransformPlugin, {\n\t\t\t\textensions: \"html\",\n\t\t\t\tdryRun: true,\n\t\t\t\tformats: [\"auto\"],\n\t\t\t\tdefaultAttributes: {\n\t\t\t\t\tloading: \"lazy\",\n\t\t\t\t\tdecoding: \"async\"\n\t\t\t\t}\n\t\t\t});\n    }\n  });\n\n  let [result] = await elev.toJSON();\n\tt.deepEqual(normalizeNewLines(result.content), `<img src=\"/single/IdthKOzqFA-350.png\" alt=\"it’s a possum\" loading=\"eager\" decoding=\"async\" width=\"350\" height=\"685\">`);\n});\n\ntest(\"Default image transform with multiple images\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-img-transform/multiple.md\", \"./test/stubs-img-transform/_site\", {\n    config: eleventyConfig => {\n      eleventyConfig.addPlugin(eleventyImageTransformPlugin, {\n\t\t\t\textensions: \"html\",\n\t\t\t\tdryRun: true,\n\t\t\t\tformats: [\"auto\"],\n\t\t\t\tdefaultAttributes: {\n\t\t\t\t\tloading: \"lazy\",\n\t\t\t\t\tdecoding: \"async\"\n\t\t\t\t}\n\t\t\t});\n    }\n  });\n\n  let [result] = await elev.toJSON();\n\tt.deepEqual(normalizeNewLines(result.content), `<img src=\"/multiple/IdthKOzqFA-350.png\" alt=\"it’s a possum\" loading=\"eager\" decoding=\"async\" width=\"350\" height=\"685\">\n<img src=\"/multiple/IdthKOzqFA-350.png\" alt=\"it’s a possum\" loading=\"lazy\" decoding=\"async\" width=\"350\" height=\"685\">`);\n});\n\ntest(\"Default image transform with an ignored image\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-img-transform/ignored.md\", \"./test/stubs-img-transform/_site\", {\n    config: eleventyConfig => {\n      eleventyConfig.addPlugin(eleventyImageTransformPlugin, {\n\t\t\t\textensions: \"html\",\n\t\t\t\tdryRun: true,\n\t\t\t\tformats: [\"auto\"],\n\t\t\t\tdefaultAttributes: {\n\t\t\t\t\tloading: \"lazy\",\n\t\t\t\t\tdecoding: \"async\"\n\t\t\t\t}\n\t\t\t});\n    }\n  });\n\n  let [result] = await elev.toJSON();\n\tt.deepEqual(normalizeNewLines(result.content), `<img src=\"./possum.png\" alt=\"it’s a possum\" loading=\"eager\">`);\n});\n\ntest(\"Missing alt\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-img-transform/missing-alt.md\", \"./test/stubs-img-transform/_site\", {\n    config: eleventyConfig => {\n      eleventyConfig.addPlugin(eleventyImageTransformPlugin, {\n\t\t\t\textensions: \"html\",\n\t\t\t\tdryRun: true,\n\t\t\t\tformats: [\"auto\"],\n\t\t\t});\n    }\n  });\n\telev.setIsVerbose(false);\n  elev.disableLogger();\n\n\tawait t.throwsAsync(async () => {\n\t\tawait elev.toJSON();\n\t});\n});\n"
  },
  {
    "path": "test/EleventyMarkdownTest.js",
    "content": "import test from \"ava\";\nimport Eleventy from \"../src/Eleventy.js\";\n\ntest(\"Markdown in markdown #3954\", async (t) => {\n  let elev = new Eleventy({\n    input: \"./test/stubs-virtual/\",\n    config: eleventyConfig => {\n      eleventyConfig.addTemplate(\"_includes/layout.md\", `{{ content }}`);\n      eleventyConfig.addTemplate(\"index.md\", `---\nlayout: layout.md\n---\n#  Heading\n\n\\`\\`\\`\n# This is code\n\n# This is another code\n\\`\\`\\``);\n    }\n  });\n\n  let results = await elev.toJSON();\n  t.is(results.length, 1);\n  t.is(results[0].content.trim(), `<h1>Heading</h1>\n<pre><code># This is code\n\n# This is another code\n</code></pre>`);\n});\n\n\ntest(\"Preprocess Markdown with markdown #3925\", async (t) => {\n  let elev = new Eleventy({\n    input: \"./test/stubs-virtual/\",\n    config: eleventyConfig => {\n      eleventyConfig.setMarkdownTemplateEngine(\"md\");\n      eleventyConfig.addTemplate(\"index.md\", `#  Heading\n\n\\`\\`\\`\n# This is code\n\n# This is another code\n\\`\\`\\``);\n    }\n  });\n\n  let results = await elev.toJSON();\n  t.is(results.length, 1);\n  t.is(results[0].content.trim(), `<h1>Heading</h1>\n<pre><code># This is code\n\n# This is another code\n</code></pre>`);\n});\n"
  },
  {
    "path": "test/EleventyNunjucksTest.js",
    "content": "import test from \"ava\";\nimport Eleventy from \"../src/Eleventy.js\";\n\ntest(\"Paired shortcodes in macros #2261 #1749\", async (t) => {\n\tlet elev = new Eleventy({\n\t\tinput: \"./test/stubs-2261/\",\n\t\tconfigPath: \"./test/stubs-2261/eleventy.config.js\",\n\t});\n\n\tlet results = await elev.toJSON();\n\tt.is(results.length, 1);\n\tt.is(results[0].content.trim(), `<div>HelloHello Manuel</div>`);\n});\n"
  },
  {
    "path": "test/EleventyServeTest.js",
    "content": "import test from \"ava\";\n\nimport EleventyServe from \"../src/EleventyServe.js\";\nimport TemplateConfig from \"../src/TemplateConfig.js\";\n\nasync function getServerInstance(eleventyConfig) {\n  let es = new EleventyServe();\n  if (!eleventyConfig) {\n    eleventyConfig = new TemplateConfig();\n    await eleventyConfig.init();\n  }\n\n  es.eleventyConfig = eleventyConfig;\n\n  await es.init();\n\n  delete es.options.logger;\n  delete es.options.module;\n\n  return es;\n}\n\ntest(\"Constructor\", async (t) => {\n  let es = await getServerInstance();\n  t.is(es.options.pathPrefix, \"/\");\n});\n\ntest(\"Get Options\", async (t) => {\n  let es = await getServerInstance();\n  es.setOutputDir(\"_site\");\n\n  t.deepEqual(es.options, {\n    pathPrefix: \"/\",\n    port: 8080,\n  });\n});\n\ntest(\"Get Options (with a pathPrefix)\", async (t) => {\n  let eleventyConfig = new TemplateConfig();\n  await eleventyConfig.init({ pathPrefix: \"/web/\" });\n\n  let es = await getServerInstance(eleventyConfig);\n  es.setOutputDir(\"_site\");\n\n  t.deepEqual(es.options, {\n    pathPrefix: \"/web/\",\n    port: 8080,\n  });\n});\n\ntest(\"Get Options (override in config)\", async (t) => {\n  let es = await getServerInstance();\n  es.setOutputDir(\"_site\");\n\n  t.deepEqual(es.options, {\n    pathPrefix: \"/\",\n    port: 8080,\n  });\n});\n\ntest(\"Sanity test that default output is set correctly\", async (t) => {\n  let es = await getServerInstance();\n  es.setOutputDir(\"_site\");\n  await es.initServerInstance();\n\n  t.is(es.server.dir, \"_site\");\n});\n\n// This assert should work once updating the output dir of the server works.\ntest(\"Custom output dir is set correctly\", async (t) => {\n  let es = await getServerInstance();\n  es.setOutputDir(\"x\");\n  await es.initServerInstance();\n\n  t.is(es.outputDir, \"x\");\n\n  t.is(es.server.dir, \"x\");\n});\n"
  },
  {
    "path": "test/EleventyTest-CustomDateParsing.js",
    "content": "import { createRequire } from \"node:module\";\nimport test from \"ava\";\nimport { DateTime } from \"luxon\";\nimport Eleventy from \"../src/Eleventy.js\";\n\nconst require = createRequire(import.meta.url);\n\ntest(\"Custom date parsing callback (return string), Issue #867\", async (t) => {\n  t.plan(3);\n\n  let elev = new Eleventy(\"./test/stubs-virtual/\", \"./test/stubs-virtual/\", {\n    config: eleventyConfig => {\n      eleventyConfig.addTemplate(\"test.html\", `# Markdown`);\n      eleventyConfig.dataFilterSelectors.add(\"page.date\");\n\n      eleventyConfig.addDateParsing(function(dateValue) {\n        t.truthy(this.page.inputPath);\n        t.is(dateValue, undefined);\n        return \"2001-01-01T12:00:00Z\";\n      });\n    }\n  });\n  elev.disableLogger();\n\n  let [result] = await elev.toJSON();\n  t.deepEqual(result.data.page.date, new Date(Date.UTC(2001,0,1,12)));\n});\n\ntest(\"Custom date parsing callback (input a non-YAML date format), Issue #867\", async (t) => {\n  t.plan(2);\n\n  let elev = new Eleventy(\"./test/stubs-virtual/\", \"./test/stubs-virtual/\", {\n    config: eleventyConfig => {\n      eleventyConfig.addTemplate(\"test.html\", `---\ndate: 2019-08-31 23:59:56 America/New_York\n---\n# Markdown`);\n      eleventyConfig.dataFilterSelectors.add(\"page.date\");\n\n      eleventyConfig.addDateParsing(function(dateValue) {\n        t.is(dateValue, \"2019-08-31 23:59:56 America/New_York\");\n        // returns DateTime instance from Luxon\n        return DateTime.fromFormat(dateValue, \"yyyy-MM-dd hh:mm:ss z\");\n      });\n    }\n  });\n  elev.disableLogger();\n\n  let [result] = await elev.toJSON();\n  t.deepEqual(result.data.page.date, new Date(Date.UTC(2019,8,1,3,59,56)));\n});\n\ntest(\"Custom date parsing callback (return Date), Issue #867\", async (t) => {\n  t.plan(3);\n\n  let elev = new Eleventy(\"./test/stubs-virtual/\", \"./test/stubs-virtual/\", {\n    config: eleventyConfig => {\n      eleventyConfig.addTemplate(\"test.html\", `# Markdown`);\n      eleventyConfig.dataFilterSelectors.add(\"page.date\");\n\n      eleventyConfig.addDateParsing(function(dateValue) {\n        t.truthy(this.page.inputPath);\n        t.is(dateValue, undefined);\n        return new Date(Date.UTC(2001,0,1,12));\n      });\n    }\n  });\n  elev.disableLogger();\n\n  let [result] = await elev.toJSON();\n  t.deepEqual(result.data.page.date, new Date(Date.UTC(2001,0,1,12)));\n});\n\ntest(\"Custom date parsing callback (using date from data cascade, return string), Issue #867\", async (t) => {\n  t.plan(3);\n\n  let elev = new Eleventy(\"./test/stubs-virtual/\", \"./test/stubs-virtual/\", {\n    config: eleventyConfig => {\n      eleventyConfig.addTemplate(\"test.html\", `# Markdown`, {\n        date: new Date(Date.UTC(2002, 0, 1, 12))\n      });\n      eleventyConfig.dataFilterSelectors.add(\"page.date\");\n\n      eleventyConfig.addDateParsing(function (dateValue) {\n        t.truthy(this.page.inputPath);\n        t.true(dateValue instanceof Date);\n        return \"2001-01-01T12:00:00Z\";\n      });\n    }\n  });\n  elev.disableLogger();\n\n  let [result] = await elev.toJSON();\n  t.deepEqual(result.data.page.date, new Date(Date.UTC(2001,0,1,12)));\n});\n\ntest(\"Custom date parsing callback (two, return undefined/falsy), Issue #867\", async (t) => {\n  t.plan(5);\n\n  let elev = new Eleventy(\"./test/stubs-virtual/\", \"./test/stubs-virtual/\", {\n    config: eleventyConfig => {\n      eleventyConfig.addTemplate(\"test.html\", `# Markdown`, {\n        date: new Date(Date.UTC(2003, 0, 1, 12))\n      });\n      eleventyConfig.dataFilterSelectors.add(\"page.date\");\n\n      eleventyConfig.addDateParsing(function (dateValue) {\n        t.truthy(this.page.inputPath);\n        t.deepEqual(dateValue, new Date(Date.UTC(2003,0,1,12)));\n        // return nothing\n      });\n\n      eleventyConfig.addDateParsing(function (dateValue) {\n        t.truthy(this.page.inputPath);\n        t.deepEqual(dateValue, new Date(Date.UTC(2003,0,1,12)));\n        // return nothing\n      });\n    }\n  });\n  elev.disableLogger();\n\n  let [result] = await elev.toJSON();\n  t.deepEqual(result.data.page.date, new Date(Date.UTC(2003,0,1,12)));\n});\n\ntest(\"Custom date parsing callback (return explicit false), Issue #867\", async (t) => {\n  t.plan(2);\n\n  let elev = new Eleventy(\"./test/stubs-virtual/\", \"./test/stubs-virtual/\", {\n    config: eleventyConfig => {\n      eleventyConfig.addTemplate(\"test.html\", `# Markdown`, {\n        date: new Date(Date.UTC(2003, 0, 1, 12))\n      });\n      eleventyConfig.dataFilterSelectors.add(\"page.date\");\n\n      eleventyConfig.addDateParsing(function (dateValue) {\n        t.truthy(this.page.inputPath);\n        return false;\n      });\n    }\n  });\n\n  elev.disableLogger();\n\n  let [result] = await elev.toJSON();\n  t.deepEqual(result.data.page.date, new Date(Date.UTC(2003,0,1,12)));\n});\n\ntest(\"Custom date parsing callbacks (two, last wins, return string), Issue #867\", async (t) => {\n  t.plan(5);\n\n  let elev = new Eleventy(\"./test/stubs-virtual/\", \"./test/stubs-virtual/\", {\n    config: eleventyConfig => {\n      eleventyConfig.addTemplate(\"test.html\", `# Markdown`);\n      eleventyConfig.dataFilterSelectors.add(\"page.date\");\n\n      eleventyConfig.addDateParsing(function (dateValue) {\n        t.truthy(this.page.inputPath);\n        t.is(dateValue, undefined);\n        return \"2010-01-01T12:00:00Z\";\n      });\n\n      eleventyConfig.addDateParsing(function (dateValue) {\n        t.truthy(this.page.inputPath);\n        t.is(dateValue, \"2010-01-01T12:00:00Z\");\n        return \"2001-01-01T12:00:00Z\";\n      });\n    }\n  });\n  elev.disableLogger();\n\n  let [result] = await elev.toJSON();\n  t.deepEqual(result.data.page.date, new Date(Date.UTC(2001,0,1,12)));\n});\n\n// https://github.com/11ty/eleventy/issues/3674\ntest(\"instanceof DateTime issue, Issue #3674\", async (t) => {\n  const { DateTime } = require(\"luxon\");\n\n  // this test *requires* a non-virtual template to repro\n  let elev = new Eleventy(\"./test/stubs/index.html\", undefined, {\n    config: eleventyConfig => {\n      eleventyConfig.addTemplate(\"test.html\", `# Markdown`);\n      eleventyConfig.dataFilterSelectors.add(\"page.date\");\n\n      eleventyConfig.addDateParsing(function (dateValue) {\n        return DateTime.fromISO(\"2001-01-01T12:00:00Z\");\n      });\n    }\n  });\n  elev.disableLogger();\n\n  let [result] = await elev.toJSON();\n  t.deepEqual(result.data.page.date, new Date(Date.UTC(2001,0,1,12)));\n});\n"
  },
  {
    "path": "test/EleventyTest-PageData.js",
    "content": "import test from \"ava\";\nimport Eleventy from \"../src/Eleventy.js\";\n\ntest(\"#3794: page.inputPathDir and page.dir\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: function(eleventyConfig) {\n      eleventyConfig.addTemplate(\"test.njk\", `{{ page.inputPathDir }} and {{ page.dir }}`, {});\n    }\n  });\n\n  let [result] = await elev.toJSON();\n  t.is(result.content, \"./test/stubs-virtual/ and /test/\");\n});\n\ntest(\"#3794: page.inputPathDir and page.dir (index file)\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: function(eleventyConfig) {\n      eleventyConfig.addTemplate(\"index.njk\", `{{ page.inputPathDir }} and {{ page.dir }}`, {});\n    }\n  });\n\n  let [result] = await elev.toJSON();\n  t.is(result.content, \"./test/stubs-virtual/ and /\");\n});\n\ntest(\"#3794: page.inputPathDir and page.dir (paginated)\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: function(eleventyConfig) {\n      eleventyConfig.addTemplate(\"index.njk\", `{{ page.inputPathDir }} and {{ page.dir }}`, {\n        data: [1,2,3],\n        pagination: {\n          data: \"data\",\n          size: 1,\n        }\n      });\n    }\n  });\n\n  let results = await elev.toJSON();\n  t.is(results.length, 3);\n\n  let [page1, page2, page3]  = results;\n  t.is(page1.content, \"./test/stubs-virtual/ and /\");\n  t.is(page2.content, \"./test/stubs-virtual/ and /1/\");\n  t.is(page3.content, \"./test/stubs-virtual/ and /2/\");\n});\n\ntest(\"#3794: page.inputPathDir and page.dir (with file slug and index)\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: function(eleventyConfig) {\n      eleventyConfig.addTemplate(\"yawn/index.njk\", `{{ page.inputPathDir }} and {{ page.dir }}`, {\n        permalink: \"{{ page.filePathStem }}.{{ page.outputFileExtension }}\"\n      });\n    }\n  });\n\n  let results = await elev.toJSON();\n  t.is(results.length, 1);\n\n  let [page1]  = results;\n  t.is(page1.content, \"./test/stubs-virtual/yawn/ and /yawn/\");\n});\n\ntest(\"#3794: page.inputPathDir and page.dir (with file slug and not-index)\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: function(eleventyConfig) {\n      eleventyConfig.addTemplate(\"yawn/test.njk\", `{{ page.inputPathDir }} and {{ page.dir }}`, {\n        permalink: \"{{ page.filePathStem }}.{{ page.outputFileExtension }}\"\n      });\n    }\n  });\n\n  let results = await elev.toJSON();\n  t.is(results.length, 1);\n\n  let [page1]  = results;\n  t.is(page1.content, \"./test/stubs-virtual/yawn/ and /yawn/\");\n});\n\ntest(\"#3794: page.inputPathDir and page.dir (paginated with file slug and not-index)\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: function(eleventyConfig) {\n      eleventyConfig.addTemplate(\"yawn/test.njk\", `{{ page.inputPathDir }} and {{ page.dir }}`, {\n        data: [1,2,3],\n        pagination: {\n          data: \"data\",\n          size: 1,\n        },\n        permalink: \"/{{ pagination.pageNumber }}{{ page.filePathStem }}.{{ page.outputFileExtension }}\"\n      });\n    }\n  });\n\n  let results = await elev.toJSON();\n  t.is(results.length, 3);\n\n  let [page1, page2, page3]  = results;\n  t.is(page1.content, \"./test/stubs-virtual/yawn/ and /0/yawn/\");\n  t.is(page2.content, \"./test/stubs-virtual/yawn/ and /1/yawn/\");\n  t.is(page3.content, \"./test/stubs-virtual/yawn/ and /2/yawn/\");\n});\n\ntest(\"#3794: page.inputPathDir and page.dir (permalink: false)\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: function(eleventyConfig) {\n      eleventyConfig.addTemplate(\"index.njk\", `{{ page.inputPathDir }} and {{ page.dir }}`, { permalink: false });\n    }\n  });\n\n  let [result] = await elev.toJSON();\n  t.is(result.content, \"./test/stubs-virtual/ and false\");\n});\n"
  },
  {
    "path": "test/EleventyTest-Preprocessors.js",
    "content": "import test from \"ava\";\nimport Eleventy from \"../src/Eleventy.js\";\n\ntest(\"#188: Content preprocessing (dot in file extension)\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: eleventyConfig => {\n      eleventyConfig.addPreprocessor(\"drafts\", \".njk\", (data, content) => {\n        if(data.draft) {\n          return false;\n        }\n        return `Hello ${content}`;\n      });\n\n      eleventyConfig.addTemplate(\"index.njk\", \"Before\");\n      eleventyConfig.addTemplate(\"draft.njk\", \"Before\", { draft: true });\n    }\n  });\n\n  let results = await elev.toJSON();\n  t.is(results.length, 1);\n  t.is(results[0].content, `Hello Before`);\n});\n\ntest(\"#188: Content preprocessing (no dot in file extension)\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: eleventyConfig => {\n      eleventyConfig.addPreprocessor(\"drafts\", \"njk\", (data, content) => {\n        if(data.draft) {\n          return false;\n        }\n        return `Hello ${content}`;\n      });\n\n      eleventyConfig.addTemplate(\"index.njk\", \"Before\");\n      eleventyConfig.addTemplate(\"draft.njk\", \"Before\", { draft: true });\n    }\n  });\n\n  let results = await elev.toJSON();\n  t.is(results.length, 1);\n  t.is(results[0].content, `Hello Before`);\n});\n\n\ntest(\"#188: Content preprocessing (array, no dot in file extension)\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: eleventyConfig => {\n      eleventyConfig.addPreprocessor(\"drafts\", [\"njk\"], (data, content) => {\n        if(data.draft) {\n          return false;\n        }\n        return `Hello ${content}`;\n      });\n\n      eleventyConfig.addTemplate(\"index.njk\", \"Before\");\n      eleventyConfig.addTemplate(\"draft.njk\", \"Before\", { draft: true });\n    }\n  });\n\n  let results = await elev.toJSON();\n  t.is(results.length, 1);\n  t.is(results[0].content, `Hello Before`);\n});\n\ntest(\"#188: Content preprocessing (array, dot in file extension)\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: eleventyConfig => {\n      eleventyConfig.addPreprocessor(\"drafts\", [\".njk\"], (data, content) => {\n        if(data.draft) {\n          return false;\n        }\n        return `Hello ${content}`;\n      });\n\n      eleventyConfig.addTemplate(\"index.njk\", \"Before\");\n      eleventyConfig.addTemplate(\"draft.njk\", \"Before\", { draft: true });\n    }\n  });\n\n  let results = await elev.toJSON();\n  t.is(results.length, 1);\n  t.is(results[0].content, `Hello Before`);\n});\n\ntest(\"#188: Content preprocessing (wildcard)\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: eleventyConfig => {\n      eleventyConfig.addPreprocessor(\"drafts\", \"*\", (data, content) => {\n        if(data.draft) {\n          return false;\n        }\n        return `Hello ${content}`;\n      });\n\n      eleventyConfig.addTemplate(\"index.njk\", \"Before\");\n      eleventyConfig.addTemplate(\"draft.njk\", \"Before\", { draft: true });\n    }\n  });\n\n  let results = await elev.toJSON();\n  t.is(results.length, 1);\n  t.is(results[0].content, `Hello Before`);\n});\n\n\ntest(\"addPreprocessor with 11ty.js, Issue #3433\", async (t) => {\n  t.plan(5);\n\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: eleventyConfig => {\n      eleventyConfig.addPreprocessor(\"testing\", \"11ty.js\", (data, content) => {\n        t.is( typeof content, \"function\" );\n        t.is(content(), \"Hello!\");\n\n        return {\n          render: function() {\n            return \"naw\";\n          }\n        };\n      });\n\n      eleventyConfig.addTemplate(\"template.11ty.js\", function() {\n        return \"Hello!\"\n      });\n    }\n  });\n\n  let results = await elev.toJSON();\n  t.is(results.length, 1);\n  t.is(results[0].url, `/template/`);\n  t.is(results[0].content.trim(), `naw`);\n});\n\n\ntest(\"addPreprocessor and addExtension, Issue #3433\", async (t) => {\n  t.plan(5);\n\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: eleventyConfig => {\n      eleventyConfig.addTemplateFormats(\"11ty.test\");\n      eleventyConfig.addExtension(\"11ty.test\", {\n        key: \"11ty.js\",\n      });\n\n      eleventyConfig.addPreprocessor(\"testing\", \"11ty.test\", (data, content) => {\n        t.is( typeof content, \"function\" );\n        t.is(content(), \"Hello!\");\n\n        return {\n          render: function() {\n            return \"naw\";\n          }\n        };\n      });\n\n      eleventyConfig.addTemplate(\"template.11ty.test\", function() {\n        return \"Hello!\"\n      });\n    }\n  });\n\n  let results = await elev.toJSON();\n  t.is(results.length, 1);\n  t.is(results[0].url, `/template/`);\n  t.is(results[0].content.trim(), `naw`);\n});\n\ntest(\"addPreprocessor and addExtension with custom `compile` (defaultRenderer), Issue #3433\", async (t) => {\n  t.plan(5);\n\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: eleventyConfig => {\n      eleventyConfig.addTemplateFormats(\"11ty.test\");\n      eleventyConfig.addExtension(\"11ty.test\", {\n        key: \"11ty.js\",\n        compile: function() {\n          return this.defaultRenderer;\n        }\n      });\n\n      eleventyConfig.addPreprocessor(\"testing\", \"11ty.test\", (data, content) => {\n        t.is( typeof content, \"function\" );\n        t.is(content(), \"Hello!\");\n\n        return {\n          render: function() {\n            return \"naw\";\n          }\n        };\n      });\n\n      eleventyConfig.addTemplate(\"template.11ty.test\", function() {\n        return \"Hello!\"\n      });\n    }\n  });\n\n  let results = await elev.toJSON();\n  t.is(results.length, 1);\n  t.is(results[0].url, `/template/`);\n  t.is(results[0].content.trim(), `naw`);\n});\n\ntest(\"addPreprocessor and addExtension with custom `compile` (re-use render function directly), Issue #3433\", async (t) => {\n  t.plan(5);\n\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: eleventyConfig => {\n      eleventyConfig.addTemplateFormats(\"11ty.test\");\n      eleventyConfig.addExtension(\"11ty.test\", {\n        key: \"11ty.js\",\n        compile: function(content) {\n          return function() {\n            return content.render();\n          }\n        }\n      });\n\n      eleventyConfig.addPreprocessor(\"testing\", \"11ty.test\", (data, content) => {\n        t.is( typeof content, \"function\" );\n        t.is(content(), \"Hello!\");\n\n        return {\n          render: function() {\n            return \"naw\";\n          }\n        };\n      });\n\n      eleventyConfig.addTemplate(\"template.11ty.test\", function() {\n        return \"Hello!\"\n      });\n    }\n  });\n\n  let results = await elev.toJSON();\n  t.is(results.length, 1);\n  t.is(results[0].url, `/template/`);\n  t.is(results[0].content.trim(), `naw`);\n});\n\ntest(\"addPreprocessor and addExtension with custom `compile` (new render function), Issue #3433\", async (t) => {\n  t.plan(7);\n\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: eleventyConfig => {\n      eleventyConfig.addTemplateFormats(\"11ty.test\");\n      eleventyConfig.addExtension(\"11ty.test\", {\n        key: \"11ty.js\",\n        compile: function(content) {\n          // check preprocessor override\n          t.is( typeof content.render, \"function\" );\n          t.is(content.render(), \"Preprocessor override\");\n\n          return function() {\n            return \"Compiled content\";\n          }\n        }\n      });\n\n      eleventyConfig.addPreprocessor(\"testing\", \"11ty.test\", (data, content) => {\n        // check template content directly\n        t.is( typeof content, \"function\" );\n        t.is(content(), \"Original template content\");\n\n        return {\n          render: function() {\n            return \"Preprocessor override\";\n          }\n        };\n      });\n\n      eleventyConfig.addTemplate(\"template.11ty.test\", function() {\n        return \"Original template content\"\n      });\n    }\n  });\n\n  let results = await elev.toJSON();\n  t.is(results.length, 1);\n  t.is(results[0].url, `/template/`);\n  t.is(results[0].content.trim(), `Compiled content`);\n});\n\n// #3933\ntest(\"Tags in pages excluded with preprocessing should not populate collections props\", async (t) => {\n  let preprocessorRuns = 0;\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: eleventyConfig => {\n      eleventyConfig.addPreprocessor(\"drafts\", \"njk\", (data, content) => {\n        preprocessorRuns++;\n        if(data.draft) {\n          return false;\n        }\n        return `Hello ${content}`;\n      });\n\n      eleventyConfig.addTemplate(\"paged.njk\", \"{{ tag }}\", {\n        pagination: {\n          data: \"collections\",\n          size: 1,\n          alias: \"tag\",\n          filter: [\"all\"],\n        },\n        permalink: \"/{{ tag }}/\"\n      });\n      eleventyConfig.addTemplate(\"source.njk\", \"Before\", { tags: [\"yep\"] });\n      eleventyConfig.addTemplate(\"source-draft.njk\", \"Before\", { draft: true, tags: [\"nope\"] });\n    }\n  });\n\n  let results = await elev.toJSON();\n  t.is(preprocessorRuns, 3);\n  t.is(results.length, 2);\n  t.truthy(results.find(entry => entry.inputPath.endsWith(\"source.njk\")));\n  t.falsy(results.find(entry => entry.inputPath.endsWith(\"source-draft.njk\")));\n\n  let pages = results.filter(entry => entry.inputPath.endsWith(\"paged.njk\"));\n  t.is(pages.length, 1);\n  t.is(pages[0].content, \"Hello yep\");\n});\n"
  },
  {
    "path": "test/EleventyTest-Shortcodes.js",
    "content": "import test from \"ava\";\nimport Eleventy from \"../src/Eleventy.js\";\n\ntest.skip(\"#3400: Both a paired and unpaired shortcode.\", async (t) => {\n  let count = 0;\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: function(eleventyConfig) {\n      eleventyConfig.addShortcode(\"single\", function() {\n        count++;\n      });\n      eleventyConfig.addPairedShortcode(\"single\", function() {\n        count++;\n      });\n\n      eleventyConfig.addTemplate(\"test.njk\", `{% single %}\n{% single %}{% endsingle %}`, {});\n    }\n  });\n\n  let results = await elev.toJSON();\n  t.is(count, 2);\n});\n"
  },
  {
    "path": "test/EleventyTest.js",
    "content": "import test from \"ava\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport lodash from \"@11ty/lodash-custom\";\nimport { z } from \"zod\";\nimport { fromZodError } from \"zod-validation-error\";\nimport { marked } from \"marked\";\nimport nunjucks from \"@11ty/nunjucks\";\nimport * as sass from \"sass\";\n\nimport Eleventy, { HtmlBasePlugin } from \"../src/Eleventy.js\";\nimport TemplateContent from \"../src/TemplateContent.js\";\nimport TemplateMap from \"../src/TemplateMap.js\";\nimport TemplateConfig from \"../src/TemplateConfig.js\";\nimport { getCreatedTimestamp, getUpdatedTimestamp } from \"../src/Util/Git.js\";\nimport PathNormalizer from \"../src/Util/PathNormalizer.js\";\nimport { normalizeNewLines, localizeNewLines } from \"./Util/normalizeNewLines.js\";\nimport { isTypeScriptSupported } from \"../src/Util/FeatureTests.cjs\";\nimport { deleteDirectory } from \"./_testHelpers.js\";\n\nconst lodashGet = lodash.get;\n\ntest(\"Eleventy, defaults inherit from config\", async (t) => {\n  let elev = new Eleventy();\n\n  let eleventyConfig = new TemplateConfig();\n  await eleventyConfig.init();\n\n  await elev.initializeConfig();\n  let config = eleventyConfig.getConfig();\n\n  t.truthy(elev.input);\n  t.truthy(elev.outputDir);\n  t.is(config.dir.input, \".\");\n  t.is(elev.input, \"./\");\n  t.is(config.dir.output, \"_site\");\n  t.is(elev.outputDir, \"./_site/\");\n});\n\ntest(\"Eleventy, null output directory should default to _site\", async (t) => {\n  let elev = new Eleventy(\".\", null);\n\n  let eleventyConfig = new TemplateConfig();\n  await eleventyConfig.init();\n\n  await elev.initializeConfig();\n  let config = eleventyConfig.getConfig();\n\n  t.is(config.dir.input, \".\");\n  t.is(elev.input, \"./\");\n  t.is(config.dir.output, \"_site\");\n  t.is(elev.outputDir, \"./_site/\");\n});\n\ntest(\"Eleventy, get version\", (t) => {\n  let elev = new Eleventy();\n\n  t.truthy(elev.getVersion());\n});\n\ntest(\"Eleventy, get help\", (t) => {\n  let elev = new Eleventy();\n\n  t.truthy(elev.getHelp());\n});\n\ntest(\"Eleventy, set is verbose (before config init)\", async (t) => {\n  let elev = new Eleventy();\n  elev.setIsVerbose(true);\n\n  await elev.initializeConfig();\n\n  t.true(elev.verboseMode);\n});\n\ntest(\"Eleventy, set is verbose (after config init)\", async (t) => {\n  let elev = new Eleventy();\n\n  await elev.initializeConfig();\n  elev.setIsVerbose(true);\n\n  t.true(elev.verboseMode);\n});\n\ntest(\"Eleventy set input/output\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs\", \"./test/stubs/_site\");\n\n  t.is(elev.input, \"./test/stubs/\");\n  t.is(elev.outputDir, \"./test/stubs/_site/\");\n\n  await elev.init();\n  t.truthy(elev.templateData);\n  t.truthy(elev.writer);\n});\n\ntest(\"Eleventy process.ENV\", async (t) => {\n  delete process.env.ELEVENTY_ROOT;\n  t.falsy(process.env.ELEVENTY_ROOT);\n\n  let elev = new Eleventy(\"./test/stubs\", \"./test/stubs/_site\");\n  await elev.init();\n  t.truthy(process.env.ELEVENTY_ROOT);\n\n  // all ELEVENTY_ env variables are also available on eleventy.env\n  let globals = await elev.templateData.getInitialGlobalData();\n  t.truthy(globals.eleventy.env.root);\n});\n\ntest(\"Eleventy file watching\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs\", \"./test/stubs/_site\", {\n    runMode: \"watch\" // required to spider deps\n  });\n  elev.setFormats(\"njk\");\n  elev.disableLogger();\n\n  await elev.init();\n  let globalData = await elev.templateData.getGlobalData();\n\n  await elev.eleventyFiles.getFiles();\n  await elev.startWatch();\n\n  let { targets, ignores } = await elev.getWatchedTargets();\n  await elev.stopWatch();\n\n  t.deepEqual(targets, [\n    \"./package.json\",\n    \"./test/stubs/**/*.njk\",\n    \"./test/stubs/_includes/**\",\n    \"./test/stubs/_data/**\",\n    \"./.gitignore\",\n    \"./.eleventyignore\",\n    \"./test/stubs/.eleventyignore\",\n    `./test/stubs/**/*.{json,11tydata.mjs,11tydata.cjs,11tydata.js${isTypeScriptSupported() ? \",11tydata.mts,11tydata.cts,11tydata.ts\" : \"\"}}`,\n    \"./test/stubs/deps/dep1.cjs\",\n    \"./test/stubs/deps/dep2.cjs\",\n  ]);\n\n  t.true(ignores.includes(\"node_modules/**\"));\n  t.true(ignores.includes(\"**/node_modules/**\"));\n  t.true(ignores.includes(\"test/stubs/_site/**\"));\n  t.true(ignores.includes(\"./.git/**\"));\n  t.true(ignores.includes(\".cache\"));\n});\n\ntest(\"Eleventy file watching (don’t watch deps of passthrough copy .js files)\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-1325\", \"./test/stubs-1325/_site\");\n  elev.setFormats(\"11ty.js,js\");\n  elev.disableLogger();\n\n  await elev.init();\n  await elev.eleventyFiles.getFiles();\n  await elev.startWatch();\n\n  let paths = await elev.eleventyFiles.getWatchPathCache();\n  await elev.stopWatch();\n\n  t.deepEqual(paths, [\"./test/stubs-1325/test.11ty.js\"]);\n});\n\ntest(\"Eleventy file watching (no JS dependencies)\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs\", \"./test/stubs/_site\", {\n    config: eleventyConfig => {\n      eleventyConfig.setWatchJavaScriptDependencies(false);\n    }\n  });\n  elev.setFormats(\"njk\");\n  elev.disableLogger();\n\n  await elev.init();\n  await elev.startWatch();\n\n  let { targets, ignores } = await elev.getWatchedTargets();\n\n  await elev.stopWatch();\n\n  t.deepEqual(targets, [\n    \"./package.json\",\n    \"./test/stubs/**/*.njk\",\n    \"./test/stubs/_includes/**\",\n    \"./test/stubs/_data/**\",\n    \"./.gitignore\",\n    \"./.eleventyignore\",\n    \"./test/stubs/.eleventyignore\",\n    `./test/stubs/**/*.{json,11tydata.mjs,11tydata.cjs,11tydata.js${isTypeScriptSupported() ? \",11tydata.mts,11tydata.cts,11tydata.ts\" : \"\"}}`,\n  ]);\n\n  t.true(ignores.includes(\"node_modules/**\"));\n  t.true(ignores.includes(\"**/node_modules/**\"));\n  t.true(ignores.includes(\"test/stubs/_site/**\"));\n  t.true(ignores.includes(\"./.git/**\"));\n  t.true(ignores.includes(\".cache\"));\n});\n\ntest(\"Eleventy set input/output, one file input\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs/index.html\", \"./test/stubs/_site\");\n\n  t.is(elev.input, \"./test/stubs/index.html\");\n  t.is(elev.inputFile, \"./test/stubs/index.html\");\n  t.is(elev.inputDir, \"./test/stubs/\");\n  t.is(elev.outputDir, \"./test/stubs/_site/\");\n});\n\ntest(\"Eleventy set input/output, one file input, deeper subdirectory\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs/subdir/index.html\", \"./test/stubs/_site\", {\n\t\tinputDir: \"./test/stubs\"\n\t});\n\n  t.is(elev.input, \"./test/stubs/subdir/index.html\");\n  t.is(elev.inputFile, \"./test/stubs/subdir/index.html\");\n  t.is(elev.inputDir, \"./test/stubs/\");\n  t.is(elev.outputDir, \"./test/stubs/_site/\");\n});\n\ntest(\"Eleventy set input/output, one file input root dir\", async (t) => {\n  let elev = new Eleventy(\"./README.md\", \"./test/stubs/_site\");\n\n  t.is(elev.input, \"./README.md\");\n  t.is(elev.inputFile, \"./README.md\");\n  t.is(elev.inputDir, \"./\");\n  t.is(elev.outputDir, \"./test/stubs/_site/\");\n});\n\ntest(\"Eleventy set input/output, one file input root dir without leading dot/slash\", async (t) => {\n  let elev = new Eleventy(\"README.md\", \"./test/stubs/_site\");\n\n  t.is(elev.input, \"./README.md\");\n  t.is(elev.inputDir, \"./\");\n  t.is(elev.outputDir, \"./test/stubs/_site/\");\n});\n\ntest(\"Eleventy set input/output, one file input exitCode (script)\", async (t) => {\n  let previousExitCode = process.exitCode;\n  let elev = new Eleventy(\"./test/stubs/exitCode/failure.njk\", \"./test/stubs/exitCode/_site\", {\n    source: \"script\",\n  });\n  elev.disableLogger();\n\n  await t.throwsAsync(async () => {\n    await elev.write();\n  });\n\n  // no change to the exit code when running script\n  t.is(process.exitCode, previousExitCode);\n});\n\ntest(\"Eleventy set input/output, one file input exitCode (cli)\", async (t) => {\n  let previousExitCode = process.exitCode;\n  let elev = new Eleventy(\"./test/stubs/exitCode/failure.njk\", \"./test/stubs/exitCode/_site\", {\n    source: \"cli\",\n  });\n  elev.disableLogger();\n\n  let e = await t.throwsAsync(() => elev.write());\n\n  t.is(e.message, \"Having trouble rendering njk template ./test/stubs/exitCode/failure.njk\");\n\n  t.is(process.exitCode, 1);\n\n  process.exitCode = previousExitCode;\n});\n\ntest(\"Eleventy to json\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs--to/\");\n  elev.setIsVerbose(false);\n\n  let result = await elev.toJSON();\n\n  t.deepEqual(\n    result.filter((entry) => entry.url === \"/test/\"),\n    [\n      {\n        url: \"/test/\",\n        inputPath: \"./test/stubs--to/test.md\",\n        outputPath: \"./_site/test/index.html\",\n        rawInput: localizeNewLines(\"# hi\\n\"),\n        content: \"<h1>hi</h1>\\n\",\n      },\n    ]\n  );\n  t.deepEqual(\n    result.filter((entry) => entry.url === \"/test2/\"),\n    [\n      {\n        url: \"/test2/\",\n        inputPath: \"./test/stubs--to/test2.liquid\",\n        outputPath: \"./_site/test2/index.html\",\n        rawInput: \"{{ hi }}\",\n        content: \"hello\",\n      },\n    ]\n  );\n});\n\ntest(\"Two Eleventies, two configs!!! (config used to be a global)\", async (t) => {\n  let elev1 = new Eleventy();\n  await elev1.initializeConfig();\n\n  t.is(elev1.eleventyConfig, elev1.eleventyConfig);\n  t.is(elev1.config, elev1.config);\n  delete elev1.config.uses;\n  t.is(JSON.stringify(elev1.config), JSON.stringify(elev1.config));\n\n  let elev2 = new Eleventy();\n  await elev2.initializeConfig();\n  t.not(elev1.eleventyConfig, elev2.eleventyConfig);\n  elev1.config.benchmarkManager = null;\n  elev2.config.benchmarkManager = null;\n  delete elev2.config.uses;\n  t.is(JSON.stringify(elev1.config), JSON.stringify(elev2.config));\n});\n\ntest(\"Config propagates to other instances correctly\", async (t) => {\n  let elev = new Eleventy();\n  await elev.init();\n\n  t.is(elev.eleventyServe.config, elev.config);\n\n  t.is(elev.extensionMap.templateConfig, elev.eleventyConfig);\n  t.is(elev.passthroughManager.templateConfig, elev.eleventyConfig);\n  t.is(elev.eleventyFiles.templateConfig, elev.eleventyConfig);\n  t.is(elev.templateData.templateConfig, elev.eleventyConfig);\n  t.is(elev.writer.templateConfig, elev.eleventyConfig);\n});\n\ntest(\"Eleventy programmatic API without init\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs--to/\");\n  elev.setIsVerbose(false);\n\n  let result = await elev.toJSON();\n\n  t.deepEqual(\n    result.filter((entry) => entry.url === \"/test/\"),\n    [\n      {\n        url: \"/test/\",\n        inputPath: \"./test/stubs--to/test.md\",\n        outputPath: \"./_site/test/index.html\",\n        rawInput: localizeNewLines(\"# hi\\n\"),\n        content: \"<h1>hi</h1>\\n\",\n      },\n    ]\n  );\n  t.deepEqual(\n    result.filter((entry) => entry.url === \"/test2/\"),\n    [\n      {\n        url: \"/test2/\",\n        inputPath: \"./test/stubs--to/test2.liquid\",\n        outputPath: \"./_site/test2/index.html\",\n        rawInput: `{{ hi }}`,\n        content: \"hello\",\n      },\n    ]\n  );\n});\n\ntest(\"Can Eleventy run two executeBuilds in parallel?\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs--to/\");\n  elev.setIsVerbose(false);\n\n  let p1 = elev.toJSON();\n  let p2 = elev.toJSON();\n  let [result1, result2] = await Promise.all([p1, p2]);\n\n  let test1Result = [\n    {\n      url: \"/test/\",\n      inputPath: \"./test/stubs--to/test.md\",\n      outputPath: \"./_site/test/index.html\",\n      rawInput: localizeNewLines(\"# hi\\n\"),\n      content: \"<h1>hi</h1>\\n\",\n    },\n  ];\n\n  let test2Result = [\n    {\n      url: \"/test2/\",\n      inputPath: \"./test/stubs--to/test2.liquid\",\n      outputPath: \"./_site/test2/index.html\",\n      rawInput: \"{{ hi }}\",\n      content: \"hello\",\n    },\n  ];\n\n  t.deepEqual(\n    result1.filter((entry) => entry.url === \"/test/\"),\n    test1Result\n  );\n  t.deepEqual(\n    result1.filter((entry) => entry.url === \"/test2/\"),\n    test2Result\n  );\n\n  t.deepEqual(\n    result2.filter((entry) => entry.url === \"/test/\"),\n    test1Result\n  );\n  t.deepEqual(\n    result2.filter((entry) => entry.url === \"/test2/\"),\n    test2Result\n  );\n});\n\ntest(\"Unicode in front matter `tags`, issue #670\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-670/\", \"./test/stubs-670/_site\");\n\n  let results = await elev.toJSON();\n  results.sort((a, b) => {\n    if (a.inputPath > b.inputPath) {\n      return -1;\n    }\n    return 1;\n  });\n\n  t.is(results[0].content.trim(), \"2,Cañon City,all,\");\n});\n\ntest(\"#142: date 'git Last Modified' populates page.date\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-142/\", \"./test/stubs-142/_site\");\n\n  let results = await elev.toJSON();\n  let [result] = results;\n\n  // Warning: this doesn’t test the validity of the function, only that it populates page.date.\n  let timestamp = await getUpdatedTimestamp(\"./test/stubs-142/index.njk\");\n  t.truthy(result.content.trim());\n  t.truthy(timestamp);\n  t.is(result.content.trim(), \"\" + timestamp);\n});\n\ntest(\"git getUpdatedTimestamp returns undefined on nonexistent path\", async (t) => {\n  t.is(await getUpdatedTimestamp(\"./test/invalid.invalid\"), undefined);\n});\n\ntest(\"#2167: Pagination with permalink: false\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-2167/\", \"./test/stubs-2167/_site\");\n  elev.disableLogger();\n  elev.setDryRun(true);\n\n  let [,pages] = await elev.write();\n  t.is(pages.length, 0);\n\n  let results = await elev.toJSON();\n  t.is(results.length, 5);\n});\n\ntest(\"Pagination over collection using eleventyComputed (liquid)\", async (t) => {\n  t.plan(5);\n  let elev = new Eleventy(\n    \"./test/stubs-pagination-computed-quotes/\",\n    \"./test/stubs-pagination-computed-quotes/_site\",\n    {\n      config: function (eleventyConfig) {\n        eleventyConfig.addFilter(\"selectRandomFromArray\", (arr) => {\n          t.true(Array.isArray(arr));\n          t.deepEqual(arr, [\"The person that shared this is awesome\"]);\n          return arr[0];\n        });\n      },\n    }\n  );\n\n  let results = await elev.toJSON();\n  t.is(results.length, 2);\n  let content = results.map((entry) => entry.content).sort();\n  t.is(content[0], \"No\");\n  t.is(content[1], \"The person that shared this is awesome\");\n});\n\ntest(\"Pagination over collection using eleventyComputed (njk)\", async (t) => {\n  t.plan(5);\n  let elev = new Eleventy(\n    \"./test/stubs-pagination-computed-quotes-njk/\",\n    \"./test/stubs-pagination-computed-quotes-njk/_site\",\n    {\n      config: function (eleventyConfig) {\n        eleventyConfig.addFilter(\"selectRandomFromArray\", (arr) => {\n          t.true(Array.isArray(arr));\n          t.deepEqual(arr, [\"The person that shared this is awesome\"]);\n          return arr[0];\n        });\n      },\n    }\n  );\n\n  let results = await elev.toJSON();\n  t.is(results.length, 2);\n  let content = results.map((entry) => entry.content).sort();\n  t.is(content[0], \"No\");\n  t.is(content[1], \"The person that shared this is awesome\");\n});\n\ntest(\"Paginated template uses proxy and global data\", async (t) => {\n  let elev = new Eleventy(\n    \"./test/proxy-pagination-globaldata/\",\n    \"./test/proxy-pagination-globaldata/_site\",\n    {\n      config: function (eleventyConfig) {},\n    }\n  );\n\n  let results = await elev.toJSON();\n  let allContentMatches = results.filter((entry) => {\n    return entry.content.trim() === \"BANNER TEXT\";\n  });\n  t.is(results.length, allContentMatches.length);\n});\n\ntest(\"Liquid shortcode with multiple arguments(issue #2348)\", async (t) => {\n  // NOTE issue #2348 was only active when you were processing multiple templates at the same time.\n\n  let elev = new Eleventy(\"./test/stubs-2367/\", \"./test/stubs-2367/_site\", {\n    config: function (eleventyConfig) {\n      eleventyConfig.addShortcode(\"simplelink\", function (...args) {\n        return JSON.stringify(args);\n      });\n    },\n  });\n\n  let arr = [\"layout\", \"/mylayout\", \"layout\", \"/mylayout\", \"layout\", \"/mylayout\"];\n  let str = normalizeNewLines(`${JSON.stringify(arr)}\n${JSON.stringify(arr)}`);\n  let results = await elev.toJSON();\n  t.is(results.length, 2);\n  let content = results.map((entry) => entry.content).sort();\n  t.is(normalizeNewLines(content[0]), str);\n  t.is(normalizeNewLines(content[1]), str);\n});\n\ntest(\"#2224: date 'git created' populates page.date\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-2224/\", \"./test/stubs-2224/_site\");\n\n  let results = await elev.toJSON();\n  let [result] = results;\n\n  // This doesn’t test the validity of the function, only that it populates page.date.\n  let timestamp = await getCreatedTimestamp(\"./test/stubs-2224/index.njk\");\n  t.truthy(result.content.trim());\n  t.truthy(timestamp);\n  t.is(result.content.trim(), \"\" + timestamp);\n});\n\ntest(\"git getCreatedTimestamp returns undefined on nonexistent path\", async (t) => {\n  t.is(await getCreatedTimestamp(\"./test/invalid.invalid\"), undefined);\n});\n\ntest(\"Does pathPrefix affect page URLs\", async (t) => {\n  let elev = new Eleventy(\"./README.md\", \"./_site\", {\n    config: function (eleventyConfig) {\n      return {\n        pathPrefix: \"/testdirectory/\",\n      };\n    },\n  });\n\n  let results = await elev.toJSON();\n  let [result] = results;\n  t.is(result.url, \"/README/\");\n});\n\ntest(\"Improvements to custom template syntax APIs (includes a layout file) #2258\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-2258/\", \"./test/stubs-2258/_site\", {\n    configPath: \"./test/stubs-2258/eleventy.config.cjs\",\n  });\n\n  // Restore previous contents\n  let includeFilePath = \"./test/stubs-2258/_includes/_code.scss\";\n  let previousContents = `code {\n  padding: 0.25em;\n  line-height: 0;\n}`;\n  let newContents = `/* New content */`;\n\n  fs.writeFileSync(includeFilePath, previousContents, \"utf8\");\n\n  let sizes = [TemplateContent._inputCache.size, TemplateContent._compileCache.size];\n\n  let results = await elev.toJSON();\n\n  t.is(results.length, 1);\n  t.is(\n    normalizeNewLines(results[0].content),\n    `/* Banner */\n${previousContents}\n\n/* Comment */`\n  );\n\n  // Cache sizes are now one bigger\n  t.is(sizes[0] + 1, 1);\n  t.is(sizes[1] + 1, 1);\n\n  let results2 = await elev.toJSON();\n  t.is(\n    normalizeNewLines(results2[0].content),\n    `/* Banner */\n${previousContents}\n\n/* Comment */`\n  );\n\n  // Cache sizes are unchanged from last build\n  t.is(sizes[0] + 1, 1);\n  t.is(sizes[1] + 1, 1);\n\n  fs.writeFileSync(includeFilePath, newContents, \"utf8\");\n\n  // This also triggers that the file has changed in the event bus via setPreviousBuildModifiedFile\n  elev.setIncrementalFile(includeFilePath);\n\n  let results3 = await elev.toJSON();\n  t.is(\n    normalizeNewLines(results3[0].content),\n    `/* Banner */\n${newContents}\n/* Comment */`\n  );\n\n  fs.writeFileSync(includeFilePath, previousContents, \"utf8\");\n});\n\n\ntest(\"`useLayouts: false` custom engine property #2830\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-2258-2830-skip-layouts/\", \"./test/stubs-2258-2830-skip-layouts/_site\", {\n    configPath: \"./test/stubs-2258-2830-skip-layouts/eleventy.config.cjs\",\n  });\n\n  let results = await elev.toJSON();\n\n  t.is(results.length, 1);\n  t.is(\n    normalizeNewLines(results[0].content),\n    `code {\n  padding: 0.25em;\n  line-height: 0;\n}`\n  );\n});\n\ntest(\"Lodash get (for pagination data target) object key with spaces, issue #2851\", (t) => {\n  let data = {\n    collections: {\n      \"tag with spaces\": 2,\n    },\n  };\n  t.is(2, lodashGet(data, \"collections['tag with spaces']\"));\n\n  // wow, this works huh?\n  t.is(2, lodashGet(data, \"collections.tag with spaces\"));\n\n  let tm = new TemplateMap(new TemplateConfig());\n  t.is(tm.getTagTarget(\"collections.tag with spaces\"), \"tag with spaces\");\n  t.is(tm.getTagTarget(\"collections['tag with spaces']\"), \"tag with spaces\");\n  t.is(tm.getTagTarget('collections[\"tag with spaces\"]'), \"tag with spaces\");\n});\n\ntest(\"Eleventy tag collection with spaces in the tag name, issue #2851\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-2851\", \"./test/stubs-2851/_site\", {\n    config: function (eleventyConfig) {\n      eleventyConfig.dataFilterSelectors.add(\"collections\");\n    },\n  });\n  elev.setIsVerbose(false);\n\n  let result = await elev.toJSON();\n  t.deepEqual(result.length, 2);\n  t.deepEqual(result.length, result[0].data.collections.all.length);\n  t.deepEqual(result[0].data.collections[\"tag with spaces\"].length, 1);\n});\n\ntest(\"this.eleventy on JavaScript template functions, issue #2790\", async (t) => {\n  t.plan(3);\n\n  let elev = new Eleventy(\"./test/stubs-2790\", \"./test/stubs-2790/_site\", {\n    config: function (eleventyConfig) {\n      eleventyConfig.addJavaScriptFunction(\"jsfunction\", function () {\n        t.truthy(this.eleventy);\n        return this.eleventy.generator.split(\" \")[0];\n      });\n    },\n  });\n  let result = await elev.toJSON();\n  t.deepEqual(result.length, 1);\n  t.deepEqual(result[0].content, `<p>Eleventy</p>`);\n});\n\ntest(\"Global data JS files should only execute once, issue #2753\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-2753\", \"./test/stubs-2753/_site\", {\n    config: function (eleventyConfig) {},\n  });\n  let result = await elev.toJSON();\n  t.deepEqual(result.length, 2);\n  t.deepEqual(result[0].content, `1`);\n  t.deepEqual(result[0].content, `1`);\n});\n\nfunction sortResultsBy(results, key = \"content\") {\n  results.sort((a, b) => {\n    if(a[key] < b[key]) {\n      return -1;\n    }\n    if(b[key] < a[key]) {\n      return 1;\n    }\n    return 0;\n  });\n}\n\ntest(\"Access to raw input of file (toJSON), issue #1206\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-1206\", \"./test/stubs-1206/_site\", {\n    config: function (eleventyConfig) {},\n  });\n  let results = await elev.toJSON();\n  sortResultsBy(results, \"content\");\n\n  t.deepEqual(results.length, 2);\n  t.deepEqual(results[0].content, `This is the first template.This is the first template.{{ page.rawInput }}`);\n  t.deepEqual(results[0].rawInput, `This is the first template.{{ page.rawInput }}`);\n  t.deepEqual(results[1].content, `This is the second template.This is the first template.{{ page.rawInput }}`);\n  t.deepEqual(results[1].rawInput, `This is the second template.{{ collections.tag1[0].rawInput }}`);\n});\n\n// Warning: this test writes to the file system\ntest(\"Access to raw input of file (dryRun), issue #1206\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-1206\", \"./test/stubs-1206/_site\", {\n    config: function (eleventyConfig) {},\n  });\n  elev.disableLogger();\n\n  let [,results] = await elev.write();\n  sortResultsBy(results, \"content\");\n\n  t.deepEqual(results.length, 2);\n  t.deepEqual(results[0].content, `This is the first template.This is the first template.{{ page.rawInput }}`);\n  t.deepEqual(results[0].rawInput, `This is the first template.{{ page.rawInput }}`);\n  t.deepEqual(results[1].content, `This is the second template.This is the first template.{{ page.rawInput }}`);\n  t.deepEqual(results[1].rawInput, `This is the second template.{{ collections.tag1[0].rawInput }}`);\n\n\tdeleteDirectory(\"./test/stubs-1206/_site/\");\n});\n\ntest(\"eleventy.before and eleventy.after Event Arguments, directories\", async (t) => {\n  t.plan(6);\n  let elev = new Eleventy(\"./test/noop/\", \"./test/noop/_site\", {\n    config: function (eleventyConfig) {\n      eleventyConfig.on(\"eleventy.before\", arg => {\n        t.is(arg.inputDir, \"./test/noop/\");\n        t.is(arg.directories.input, \"./test/noop/\");\n        t.is(arg.directories.includes, \"./test/noop/_includes/\");\n      })\n      eleventyConfig.on(\"eleventy.after\", arg => {\n        t.is(arg.inputDir, \"./test/noop/\");\n        t.is(arg.directories.input, \"./test/noop/\");\n        t.is(arg.directories.includes, \"./test/noop/_includes/\");\n      })\n    },\n  });\n\n  let results = await elev.toJSON();\n});\n\ntest(\"eleventy.after fires sequentially setting eventEmitterMode 'sequential'\", async (t) => {\n  let reachFirst;\n  const firstReached = new Promise(resolve => reachFirst = resolve)\n  let next;\n  const firstResult = new Promise(resolve => next = resolve)\n  let secondCalled = false;\n  let elev = new Eleventy(\"./test/noop/\", \"./test/noop/_site\", {\n    config: function (eleventyConfig) {\n      eleventyConfig.setEventEmitterMode('sequential')\n      eleventyConfig.on(\"eleventy.after\", arg => {\n        reachFirst()\n        return firstResult;\n      })\n      eleventyConfig.on(\"eleventy.after\", arg => {\n        secondCalled = true;\n      })\n    },\n  });\n  const resultPromise = elev.toJSON();\n  await firstReached;\n  t.is(secondCalled, false)\n  next()\n  await 'microtask'\n  t.is(secondCalled, true)\n  await resultPromise;\n})\n\ntest(\"setInputDirectory config method #1503\", async (t) => {\n  t.plan(5);\n  let elev = new Eleventy(\"./test/noop/\", \"./test/noop/_site\", {\n    config: function (eleventyConfig) {\n      eleventyConfig.setInputDirectory(\"./test/noop2/\");\n\n      eleventyConfig.on(\"eleventy.before\", arg => {\n        t.is(arg.directories.input, \"./test/noop2/\");\n        t.is(arg.directories.includes, \"./test/noop2/_includes/\");\n        t.is(arg.directories.data, \"./test/noop2/_data/\");\n        t.is(arg.directories.layouts, undefined);\n        t.is(arg.directories.output, \"./test/noop/_site/\");\n      })\n    },\n  });\n\n  let results = await elev.toJSON();\n});\n\ntest(\"setIncludesDirectory config method #1503\", async (t) => {\n  t.plan(5);\n  let elev = new Eleventy(\"./test/noop/\", \"./test/noop/_site\", {\n    config: function (eleventyConfig) {\n      eleventyConfig.setIncludesDirectory(\"myincludes\");\n\n      eleventyConfig.on(\"eleventy.before\", arg => {\n        t.is(arg.directories.input, \"./test/noop/\");\n        t.is(arg.directories.includes, \"./test/noop/myincludes/\");\n        t.is(arg.directories.data, \"./test/noop/_data/\");\n        t.is(arg.directories.layouts, undefined);\n        t.is(arg.directories.output, \"./test/noop/_site/\");\n      })\n    },\n  });\n\n  let results = await elev.toJSON();\n});\n\ntest(\"setDataDirectory config method #1503\", async (t) => {\n  t.plan(5);\n  let elev = new Eleventy(\"./test/noop/\", \"./test/noop/_site\", {\n    config: function (eleventyConfig) {\n      eleventyConfig.setDataDirectory(\"data\");\n\n      eleventyConfig.on(\"eleventy.before\", arg => {\n        t.is(arg.directories.input, \"./test/noop/\");\n        t.is(arg.directories.includes, \"./test/noop/_includes/\");\n        t.is(arg.directories.data, \"./test/noop/data/\");\n        t.is(arg.directories.layouts, undefined);\n        t.is(arg.directories.output, \"./test/noop/_site/\");\n      })\n    },\n  });\n\n  let results = await elev.toJSON();\n});\n\ntest(\"setLayoutsDirectory config method #1503\", async (t) => {\n  t.plan(5);\n  let elev = new Eleventy(\"./test/noop/\", \"./test/noop/_site\", {\n    config: function (eleventyConfig) {\n      eleventyConfig.setLayoutsDirectory(\"layouts\");\n\n      eleventyConfig.on(\"eleventy.before\", arg => {\n        t.is(arg.directories.input, \"./test/noop/\");\n        t.is(arg.directories.includes, \"./test/noop/_includes/\");\n        t.is(arg.directories.data, \"./test/noop/_data/\");\n        t.is(arg.directories.layouts, \"./test/noop/layouts/\");\n        t.is(arg.directories.output, \"./test/noop/_site/\");\n      })\n    },\n  });\n\n  let results = await elev.toJSON();\n});\n\ntest(\"setInputDirectory config method #1503 in a plugin throws error\", async (t) => {\n  let elev = new Eleventy(\"./test/noop/\", \"./test/noop/_site\", {\n    config: function (eleventyConfig) {\n      eleventyConfig.addPlugin(() => {\n        eleventyConfig.setInputDirectory(\"./test/noop2/\");\n      });\n    },\n  });\n\n  await t.throwsAsync(() => elev.toJSON(), {\n    // The `set*Directory` configuration API methods are not yet allowed in plugins.\n    message: \"Error processing a plugin\",\n  });\n});\n\ntest(\"Accepts absolute paths for input and output\", async (t) => {\n  let input = path.resolve(\"./test/noop/\");\n  let output = path.resolve(\"./test/noop/_site\");\n\n  let elev = new Eleventy(input, output);\n\n  let results = await elev.toJSON();\n\n  // trailing slashes are expected\n  t.is(PathNormalizer.normalizeSeperator(elev.directories.input), PathNormalizer.normalizeSeperator(\"./test/noop/\"));\n  t.is(PathNormalizer.normalizeSeperator(elev.directories.includes), PathNormalizer.normalizeSeperator(\"./test/noop/_includes/\"));\n  t.is(PathNormalizer.normalizeSeperator(elev.directories.data), PathNormalizer.normalizeSeperator(\"./test/noop/_data/\"));\n  t.is(elev.directories.layouts, undefined);\n  t.is(PathNormalizer.normalizeSeperator(elev.directories.output), PathNormalizer.normalizeSeperator(\"./test/noop/_site/\"));\n});\n\ntest(\"Accepts absolute paths urls for input and output, results output #3805\", async (t) => {\n  let input = path.resolve(\"./test/stubs-absolute/test.md\");\n  let output = path.resolve(\"./test/stubs-absolute/_site\");\n  let elev = new Eleventy(input, output);\n\n  let results = await elev.toJSON();\n  t.is(results.length, 1);\n});\n\ntest(\"Accepts absolute paths urls for input and output and a virtual template, results output #3805\", async (t) => {\n  let input = path.resolve(\"./test/noop/\");\n  let output = path.resolve(\"./test/noop/_site\");\n  let elev = new Eleventy(input, output, {\n    config: eleventyConfig => {\n      eleventyConfig.addTemplate(\"index.md\", `# Title`)\n    }\n  });\n\n  let results = await elev.toJSON();\n  t.is(results.length, 1);\n});\n\ntest(\"Eleventy config export (ESM)\", async (t) => {\n  t.plan(5);\n  let elev = new Eleventy(\"test/stubs/cfg-directories-export\", null, {\n    configPath: \"./test/stubs/cfg-directories-export/eleventy.config.js\",\n    config: function (eleventyConfig) {\n      eleventyConfig.on(\"eleventy.after\", arg => {\n        t.is(arg.directories.input, \"./src/\");\n        t.is(arg.directories.includes, \"./src/myincludes/\");\n        t.is(arg.directories.data, \"./src/mydata/\");\n        t.is(arg.directories.layouts, undefined);\n        t.is(arg.directories.output, \"./dist/\");\n      })\n    },\n  });\n\n  let result = await elev.toJSON();\n});\n\ntest(\"Eleventy config export (CommonJS)\", async (t) => {\n  t.plan(5);\n  let elev = new Eleventy(\"test/stubs/cfg-directories-export-cjs\", null, {\n    configPath: \"./test/stubs/cfg-directories-export-cjs/eleventy.config.cjs\",\n    config: function (eleventyConfig) {\n      eleventyConfig.on(\"eleventy.after\", arg => {\n        t.is(arg.directories.input, \"./src/\");\n        t.is(arg.directories.includes, \"./src/myincludes2/\");\n        t.is(arg.directories.data, \"./src/mydata2/\");\n        t.is(arg.directories.layouts, undefined);\n        t.is(arg.directories.output, \"./dist2/\");\n      })\n    },\n  });\n\n  let result = await elev.toJSON();\n});\n\ntest(\"Eleventy setting reserved data throws error (eleventy)\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: eleventyConfig => {\n      eleventyConfig.addTemplate(\"index.html\", `---\neleventy:\n  key1: NOOOOO\n---`);\n    }\n  });\n  elev.disableLogger();\n\n  let e = await t.throwsAsync(() => elev.toJSON(), {\n    message: 'You attempted to set one of Eleventy’s reserved data property names. You can opt-out of this behavior with `eleventyConfig.setFreezeReservedData(false)` or rename/remove the property in your data cascade that conflicts with Eleventy’s reserved property names (e.g. `eleventy`, `pkg`, and others). Learn more: https://v3.11ty.dev/docs/data-eleventy-supplied/'\n  });\n\n  t.is(e.cause.toString(), \"TypeError: Cannot add property key1, object is not extensible\");\n});\n\ntest(\"Eleventy setting reserved data throws error (pkg)\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: eleventyConfig => {\n      eleventyConfig.addTemplate(\"index.html\", `---\npkg:\n  myOwn: OVERRIDE\n---`);\n    }\n  });\n  elev.disableLogger();\n\n  let e = await t.throwsAsync(() => elev.toJSON(), {\n    message: 'You attempted to set one of Eleventy’s reserved data property names. You can opt-out of this behavior with `eleventyConfig.setFreezeReservedData(false)` or rename/remove the property in your data cascade that conflicts with Eleventy’s reserved property names (e.g. `eleventy`, `pkg`, and others). Learn more: https://v3.11ty.dev/docs/data-eleventy-supplied/'\n  });\n\n  t.is(e.cause.toString(), \"TypeError: Cannot add property myOwn, object is not extensible\");\n});\n\ntest(\"Eleventy pagination works okay with reserved data throws (eleventy) Issue #3262\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: eleventyConfig => {\n      eleventyConfig.addTemplate(\"index.html\", `---\npagination:\n  data: \"test\"\n  size: 1\ntest:\n  - a\n  - b\n  - c\n---\n{{ eleventy.generator }}`);\n    }\n  });\n  elev.disableLogger();\n\n  let result = await elev.toJSON();\n  t.is(result.length, 3);\n});\n\ntest(\"Eleventy setting reserved data throws error (page)\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: eleventyConfig => {\n      eleventyConfig.addTemplate(\"index.html\", `---\npage: \"My page value\"\n---`)\n    }\n  });\n  elev.disableLogger();\n\n  let e = await t.throwsAsync(() => elev.toJSON(), {\n    message: 'You attempted to set one of Eleventy’s reserved data property names: page. You can opt-out of this behavior with `eleventyConfig.setFreezeReservedData(false)` or rename/remove the property in your data cascade that conflicts with Eleventy’s reserved property names (e.g. `eleventy`, `pkg`, and others). Learn more: https://v3.11ty.dev/docs/data-eleventy-supplied/'\n  });\n});\n\ntest(\"Eleventy setting reserved data throws error (content)\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: eleventyConfig => {\n      eleventyConfig.addTemplate(\"index.html\", `---\ncontent: \"My page value\"\n---`)\n    }\n  });\n  elev.disableLogger();\n\n  await t.throwsAsync(() => elev.toJSON(), {\n    message: 'You attempted to set one of Eleventy’s reserved data property names: content. You can opt-out of this behavior with `eleventyConfig.setFreezeReservedData(false)` or rename/remove the property in your data cascade that conflicts with Eleventy’s reserved property names (e.g. `eleventy`, `pkg`, and others). Learn more: https://v3.11ty.dev/docs/data-eleventy-supplied/'\n  });\n});\n\ntest(\"Eleventy setting reserved data throws error (collections)\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: eleventyConfig => {\n      eleventyConfig.addTemplate(\"index.html\", `---\ncollections: []\n---`)\n    }\n  });\n  elev.disableLogger();\n\n  await t.throwsAsync(() => elev.toJSON(), {\n    message: 'You attempted to set one of Eleventy’s reserved data property names: collections. You can opt-out of this behavior with `eleventyConfig.setFreezeReservedData(false)` or rename/remove the property in your data cascade that conflicts with Eleventy’s reserved property names (e.g. `eleventy`, `pkg`, and others). Learn more: https://v3.11ty.dev/docs/data-eleventy-supplied/'\n  });\n});\n\ntest(\"Eleventy setting pkg data is okay when pkg is remapped to parkour\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: eleventyConfig => {\n      eleventyConfig.addTemplate(\"index.html\", `---\npkg:\n  myOwn: OVERRIDE\n---`);\n    }\n  });\n  elev.disableLogger();\n\n  await elev.initializeConfig({\n    keys: {\n      package: \"parkour\"\n    }\n  });\n\n  // Remap successful\n  t.is(elev.eleventyConfig.config.keys.package, \"parkour\");\n\n  let [result] = await elev.toJSON();\n  t.deepEqual(result, {\n    content: \"\",\n    inputPath: \"./test/stubs-virtual/index.html\",\n    outputPath: \"./_site/index.html\",\n    rawInput: \"\",\n    url: \"/\"\n  });\n});\n\ntest(\"Eleventy setting pkg data is okay when keys.package is false\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: eleventyConfig => {\n      eleventyConfig.addTemplate(\"index.html\", `---\npkg:\n  myOwn: OVERRIDE\n---\n{{ pkg.myOwn }}`);\n    }\n  });\n  elev.disableLogger();\n\n  await elev.initializeConfig({\n    keys: {\n      package: false\n    }\n  });\n\n  // Remap successful\n  t.is(elev.eleventyConfig.config.keys.package, false);\n\n  let [result] = await elev.toJSON();\n  t.deepEqual(result, {\n    content: \"OVERRIDE\",\n    inputPath: \"./test/stubs-virtual/index.html\",\n    outputPath: \"./_site/index.html\",\n    rawInput: \"{{ pkg.myOwn }}\",\n    url: \"/\"\n  });\n});\n\ntest(\"Eleventy setting reserved data throws error (pkg remapped to parkour)\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: eleventyConfig => {\n      eleventyConfig.addTemplate(\"index.html\", `---\nparkour:\n  myOwn: OVERRIDE\n---`);\n    }\n  });\n  elev.disableLogger();\n\n  await elev.initializeConfig({\n    keys: {\n      package: \"parkour\"\n    }\n  });\n\n  // Remap successful\n  t.is(elev.eleventyConfig.config.keys.package, \"parkour\");\n\n  let e = await t.throwsAsync(() => elev.toJSON(), {\n    message: 'You attempted to set one of Eleventy’s reserved data property names. You can opt-out of this behavior with `eleventyConfig.setFreezeReservedData(false)` or rename/remove the property in your data cascade that conflicts with Eleventy’s reserved property names (e.g. `eleventy`, `pkg`, and others). Learn more: https://v3.11ty.dev/docs/data-eleventy-supplied/'\n  });\n\n  t.is(e.cause.toString(), \"TypeError: Cannot add property myOwn, object is not extensible\");\n});\n\ntest(\"Eleventy data schema (success) #879\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: eleventyConfig => {\n      eleventyConfig.addTemplate(\"index1.html\", \"\", {\n        draft: true,\n        eleventyDataSchema: function(data) {\n          if(typeof data.draft !== \"boolean\") {\n            throw new Error(\"Invalid data type for draft.\");\n          }\n        }\n      });\n\n      eleventyConfig.addTemplate(\"index2.html\", \"\", {\n        draft: true,\n        eleventyDataSchema: function(data) {\n          if(typeof data.draft !== \"boolean\") {\n            throw new Error(\"Invalid data type for draft.\");\n          }\n        }\n      });\n    }\n  });\n  elev.disableLogger();\n\n  let results = await elev.toJSON();\n  t.is(results.length, 2);\n});\n\ntest(\"Eleventy data schema (fails) #879\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: eleventyConfig => {\n      eleventyConfig.addTemplate(\"index1.html\", \"\", {\n        draft: 1,\n        eleventyDataSchema: function(data) {\n          if(typeof data.draft !== \"boolean\") {\n            throw new Error(\"Invalid data type for draft.\");\n          }\n        }\n      });\n    }\n  });\n  elev.disableLogger();\n\n  let e = await t.throwsAsync(() => elev.toJSON(), {\n    message: 'Error in the data schema for: ./test/stubs-virtual/index1.html (via `eleventyDataSchema`)'\n  });\n\n  t.is(e.cause.toString(), \"Error: Invalid data type for draft.\");\n});\n\ntest(\"Eleventy data schema (fails, using zod) #879\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: eleventyConfig => {\n      eleventyConfig.addTemplate(\"index1.html\", \"\", {\n        draft: 1,\n        eleventyDataSchema: function(data) {\n          let result = z.object({\n            draft: z.boolean().or(z.undefined()),\n          }).safeParse(data);\n\n          if(result.error) {\n            throw fromZodError(result.error);\n          }\n        }\n      });\n    }\n  });\n  elev.disableLogger();\n\n  let e = await t.throwsAsync(() => elev.toJSON(), {\n    message: 'Error in the data schema for: ./test/stubs-virtual/index1.html (via `eleventyDataSchema`)'\n  });\n\n  t.is(e.cause.toString(), 'Validation error: Invalid input: expected boolean, received number at \"draft\" or Invalid input: expected undefined, received number at \"draft\"');\n});\n\ntest(\"Eleventy data schema has access to custom collections created via API #879\", async (t) => {\n  t.plan(2);\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: eleventyConfig => {\n      eleventyConfig.addCollection(\"userCollection\", function (collection) {\n        return collection.getAll();\n      });\n\n      eleventyConfig.addTemplate(\"index1.html\", \"\", {\n        eleventyDataSchema: function(data) {\n          t.is(data.collections.userCollection.length, 1);\n        }\n      });\n    }\n  });\n  elev.disableLogger();\n\n  let results = await elev.toJSON();\n  t.is(results.length, 1);\n});\n\ntest(\"Eleventy transforms filter (using collections and page override data)\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    pathPrefix: \"hi\",\n    config: eleventyConfig => {\n      eleventyConfig.addPlugin(HtmlBasePlugin);\n\n      eleventyConfig.addTemplate(\"index.html\", `<img src=\"/test.png\" alt=\"abc\">`, { tags: \"posts\" });\n      eleventyConfig.addTemplate(\"feed.njk\", `{% for post in collections.posts %}{{ post.content | renderTransforms(post.page) | safe }}{% endfor %}`, {\n        permalink: \"feed.xml\"\n      });\n    }\n  });\n  elev.disableLogger();\n\n  let results = await elev.toJSON();\n  t.is(results.length, 2);\n\n  t.is(results.filter(({inputPath}) => inputPath.endsWith(\"index.html\"))[0].content, `<img src=\"/hi/test.png\" alt=\"abc\">`);\n  t.is(results.filter(({inputPath}) => inputPath.endsWith(\"feed.njk\"))[0].content, `<img src=\"/hi/test.png\" alt=\"abc\">`);\n});\n\ntest(\"Custom Markdown Render with permalink, Issue #2780\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: eleventyConfig => {\n      eleventyConfig.addExtension(\"md\", {\n        compile: str => {\n          return data => marked.parse(str);\n        }\n      });\n\n      eleventyConfig.addTemplate(\"template.md\", `# Markdown?`, { permalink: \"/permalink.html\" });\n    }\n  });\n\n  let results = await elev.toJSON();\n  t.is(results.length, 1);\n  t.is(results[0].url, `/permalink.html`);\n  t.is(results[0].content.trim(), `<h1>Markdown?</h1>`);\n});\n\ntest(\"Custom Markdown Render with permalink, Issue #2780 #3339\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: eleventyConfig => {\n      eleventyConfig.addTemplateFormats(\"markdown\");\n      eleventyConfig.addExtension(\"markdown\", {\n        key: \"md\"\n      });\n\n      eleventyConfig.addTemplate(\"filename-hi.markdown\", `# Markdown?`, { permalink: \"/{{ page.fileSlug }}.html\" });\n    }\n  });\n\n  let results = await elev.toJSON();\n  t.is(results.length, 1);\n  t.is(results[0].url, `/filename-hi.html`);\n  t.is(results[0].content.trim(), `<h1>Markdown?</h1>`);\n});\n\ntest(\"Test input/output conflicts (input overwrites output), Issue #3327\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", \"./test/stubs-virtual/\", {\n    config: eleventyConfig => {\n      eleventyConfig.addTemplate(\"test.html\", `# Markdown`, { permalink: \"test.html\" });\n    }\n  });\n  elev.disableLogger();\n\n  let e = await t.throwsAsync(async () => {\n    await elev.toJSON();\n  });\n  t.true(e.toString().startsWith(\"DuplicatePermalinkOutputError:\"));\n});\n\ntest(\"Test input/output conflicts (output overwrites another input), Issue #3327\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", \"./test/stubs-virtual/\", {\n    config: eleventyConfig => {\n      eleventyConfig.addTemplate(\"test.html\", `# Markdown`);\n      eleventyConfig.addTemplate(\"index.html\", `# Markdown`, { permalink: \"test.html\" });\n    }\n  });\n  elev.disableLogger();\n\n  let e = await t.throwsAsync(async () => {\n    await elev.toJSON();\n  });\n  t.true(e.toString().startsWith(\"DuplicatePermalinkOutputError:\"));\n});\n\ntest(\"Eleventy data schema has access to custom collections created via API #613 #3345\", async (t) => {\n\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: eleventyConfig => {\n      eleventyConfig.addCollection(\"userCollection\", async function (collection) {\n        let c = collection.getFilteredByTag(\"posts\");\n        for(let item of c) {\n          const frontMatter = await item.template.read();\n          frontMatter.content = `lol\\n${frontMatter.content}`;\n          item.template.frontMatter = frontMatter;\n        }\n        return c;\n      });\n\n      eleventyConfig.addTemplate(\"home.html\", \"{% for post in collections.userCollection %}{{ post.content }}{% endfor %}\");\n      eleventyConfig.addTemplate(\"post.html\", \"test\", { tags: \"posts\" });\n    }\n  });\n  elev.disableLogger();\n\n  let results = await elev.toJSON();\n  let [home] = results.filter(item => item.url.endsWith(\"/home/\"));\n  t.truthy(home);\n  t.is(home.content, \"test\");\n});\n\ntest(\"Custom Nunjucks syntax has shortcode with access to `this`, Issue #3310\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: eleventyConfig => {\n      eleventyConfig.addShortcode(\"customized\", function(argString) {\n        return `${this.page.url}:${argString}:Custom Shortcode`;\n      });\n\n      let njkEnv = new nunjucks.Environment();\n\n      function CustomExtension() {\n        this.tags = ['customized'];\n\n        this.parse = function(parser, nodes, lexer) {\n          let args;\n          let tok = parser.nextToken();\n          args = parser.parseSignature(true, true);\n          parser.advanceAfterBlockEnd(tok.value);\n          return new nodes.CallExtension(this, \"run\", args);\n        };\n\n        this.run = function(context, argString) {\n          let fn = eleventyConfig.augmentFunctionContext(\n            eleventyConfig.getShortcode(\"customized\"),\n            {\n              source: context.ctx,\n              // lazy: false,\n              // getter: (key, context) => context?.[key];\n              // overwrite: true,\n            }\n          );\n\n          return fn(argString);\n        };\n      }\n\n      njkEnv.addExtension('CustomExtension', new CustomExtension());\n\n      eleventyConfig.addTemplateFormats(\"njknew\");\n\n      eleventyConfig.addExtension(\"njknew\", {\n        compile: (str, inputPath) => {\n          let tmpl = new nunjucks.Template(str, njkEnv, inputPath, false);\n          return function(data) {\n            return new Promise(function (resolve, reject) {\n              tmpl.render(data, function (err, res) {\n                if (err) {\n                  reject(err);\n                } else {\n                  resolve(res);\n                }\n              });\n            });\n          }\n        }\n      });\n\n      eleventyConfig.addTemplate(\"template.njknew\", `<h1>{{ hello }}:{% customized \"passed in\" %}</h1>`, {\n        hello: \"goodbye\"\n      });\n    }\n  });\n\n  let results = await elev.toJSON();\n  t.is(results.length, 1);\n  t.is(results[0].content.trim(), `<h1>goodbye:/template/:passed in:Custom Shortcode</h1>`);\n});\n\ntest(\"Related to issue 3206: Does Nunjucks throwOnUndefined variables require normalizeContext to be a lazy get\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: eleventyConfig => {\n      eleventyConfig.addShortcode(\"customized\", function(argString) {\n        return `${this.page.url}:Custom Shortcode`;\n      });\n\n      eleventyConfig.setNunjucksEnvironmentOptions({\n        throwOnUndefined: true,\n      });\n\n      eleventyConfig.addTemplate(\"index.html\", `HELLO{% customized %}:{{ page.url }}`);\n    }\n  });\n\n  let results = await elev.toJSON();\n  t.is(results.length, 1);\n  t.is(results[0].content.trim(), `HELLO/:Custom Shortcode:/`);\n});\n\ntest(\"#727: Error messaging when trying to use a missing layout\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: eleventyConfig => {\n      eleventyConfig.addTemplate(\"index.html\", `HELLO {{ page.url }}`, {\n        layout: \"does-not-exist.html\",\n      });\n    }\n  });\n  elev.disableLogger();\n\n  await t.throwsAsync(() => elev.toJSON(), {\n    message: `Problem creating an Eleventy Layout for the \"./test/stubs-virtual/index.html\" template file.`\n  });\n});\n\ntest(\"#1419: Shortcode in a permalink\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: eleventyConfig => {\n      eleventyConfig.addShortcode(\"shortcode\", () => \"url-slug\");\n      eleventyConfig.addTemplate(\"index.njk\", \"\", {\n        permalink: \"/{% shortcode %}/\",\n      });\n    }\n  });\n\n  let results = await elev.toJSON();\n  t.is(results.length, 1);\n  t.is(results[0].url, `/url-slug/`);\n});\n\ntest(\"#3373: Throw an error when explicit config path is not found.\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    configPath: \"this-file-is-not-found.js\"\n  });\n\n  let e = await t.throwsAsync(() => elev.toJSON());\n  t.is(e.message, \"A configuration file was specified but not found: this-file-is-not-found.js\");\n});\n\ntest(\"Eleventy loader can force ESM mode\", async (t) => {\n  let elev = new Eleventy(\"./README.md\", \"./_site\", {\n    loader: \"esm\",\n  });\n\n  t.is(elev.isEsm, true);\n});\n\ntest(\"Eleventy loader can force CommonJS mode\", async (t) => {\n  let elev = new Eleventy(\"./README.md\", \"./_site\", {\n    loader: \"cjs\",\n  });\n\n  t.is(elev.isEsm, false);\n});\n\ntest(\"Truthy outputPath without a file extension now throws an error, issue #3399\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: function (eleventyConfig) {\n      // eleventyConfig.configureErrorReporting({ allowMissingExtensions: true });\n      eleventyConfig.addTemplate(\"index.html\", \"\", { permalink: \"foo\" })\n    },\n  });\n  elev.disableLogger();\n\n  await t.throwsAsync(() => elev.toJSON(), {\n    // The `set*Directory` configuration API methods are not yet allowed in plugins.\n    message: `The template at './test/stubs-virtual/index.html' attempted to write to './_site/foo' (via \\`permalink\\` value: 'foo'), which is a target on the file system that does not include a file extension.\n\nYou *probably* want to add a file extension to your permalink so that hosts will know how to correctly serve this file to web browsers. Without a file extension, this file may not be reliably deployed without additional hosting configuration (it won’t have a mime type) and may also cause local development issues if you later attempt to write to a subdirectory of the same name.\n\nLearn more: https://v3.11ty.dev/docs/permalinks/#trailing-slashes\n\nThis is usually but not *always* an error so if you’d like to disable this error message, add \\`eleventyAllowMissingExtension: true\\` somewhere in the data cascade for this template or use \\`eleventyConfig.configureErrorReporting({ allowMissingExtensions: true });\\` to disable this feature globally.`\n  });\n});\n\ntest(\"Truthy outputPath without a file extension can be ignored, issue #3399\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: function (eleventyConfig) {\n      // eleventyConfig.configureErrorReporting({ allowMissingExtensions: true });\n      eleventyConfig.addTemplate(\"index.html\", \"\", { permalink: \"foo\", eleventyAllowMissingExtension: true })\n    },\n  });\n  elev.disableLogger();\n\n  let results = await elev.toJSON();\n  t.is(results.length, 1);\n  t.is(results[0].url, \"/foo\");\n});\n\n\ntest(\"Allow list for some file types without a file extension, issue #3399\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: function (eleventyConfig) {\n      eleventyConfig.addTemplate(\"index.html\", \"\", { permalink: \"/test/_redirects\" })\n    },\n  });\n  elev.disableLogger();\n\n  let results = await elev.toJSON();\n  t.is(results.length, 1);\n  t.is(results[0].url, \"/test/_redirects\");\n});\n\ntest(\"Truthy outputPath without a file extension error message is disabled, issue #3399\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: function (eleventyConfig) {\n      eleventyConfig.configureErrorReporting({ allowMissingExtensions: true });\n      eleventyConfig.addTemplate(\"index.html\", \"\", { permalink: \"foo\" })\n    },\n  });\n  elev.disableLogger();\n\n  let results = await elev.toJSON();\n  t.is(results.length, 1);\n});\n\ntest(\"permalink: false outputPath new error message won’t throw an error, issue #3399\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: function (eleventyConfig) {\n      eleventyConfig.addTemplate(\"index.html\", \"\", { permalink: false })\n    },\n  });\n  elev.disableLogger();\n\n  let results = await elev.toJSON();\n  t.is(results.length, 1);\n});\n\ntest(\"permalink on custom template lang, issue #3619\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: function (eleventyConfig) {\n      eleventyConfig.addGlobalData(\"permalink\", () => {\n        return (data) =>\n          `/rewrite/${data.page.filePathStem}.${data.page.outputFileExtension}`;\n      });\n\n      eleventyConfig.addTemplateFormats(\"scss\");\n\n      eleventyConfig.addExtension(\"scss\", {\n        outputFileExtension: \"css\",\n        compileOptions: {\n    \t\t\tpermalink(inputContent, inputPath) {\n    \t\t\t\treturn (data) => {\n    \t\t\t\t\treturn `/testing/${data.permalink(data)}`;\n    \t\t\t\t}\n    \t\t\t}\n    \t\t},\n        compile: function (str, inputPath) {\n          // TODO declare data variables as SASS variables?\n          return async function (data) {\n            return new Promise(function (resolve, reject) {\n              sass.render(\n                {\n                  data: str,\n                  outFile: \"test_this_is_to_not_write_a_file.css\",\n                },\n                function (error, result) {\n                  if (error) {\n                    reject(error);\n                  } else {\n                    resolve(result.css.toString(\"utf8\"));\n                  }\n                },\n              );\n            });\n          };\n        },\n      });\n\n      eleventyConfig.addTemplate(\"index.scss\", `html {\n  color: red;\n}`)\n    },\n  });\n  elev.disableLogger();\n\n  let results = await elev.toJSON();\n  t.is(results[0].url, \"/testing/rewrite/index.css\");\n  t.is(results[0].content, `html {\n  color: red;\n}`);\n});\n\ntest(\"Template data throws error when tags is not an Array or String #1791\", async (t) => {\n  let elev = new Eleventy(\"./test/noop/\", \"./test/noop/_site\", {\n    config: function (eleventyConfig) {\n      eleventyConfig.addTemplate(\"index.html\", \"\", {\n        tags: {\"one\": 1, \"two\": 2}\n      });\n    },\n  });\n  elev.disableLogger();\n\n  await t.throwsAsync(() => elev.toJSON(), {\n    // The `set*Directory` configuration API methods are not yet allowed in plugins.\n    message: \"String or Array expected for `tags` in virtual template: ./test/noop/index.html. Received: { one: 1, two: 2 }\",\n  });\n});\n\ntest(\"sass docs on 11ty.dev, issue #408\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-408-sass/\", undefined, {\n    config: function (eleventyConfig) {\n      eleventyConfig.addTemplateFormats(\"scss\");\n\n      eleventyConfig.addExtension(\"scss\", {\n        outputFileExtension: \"css\",\n\n        // opt-out of Eleventy Layouts\n        useLayouts: false,\n\n        compile: async function (inputContent, inputPath) {\n          let parsed = path.parse(inputPath);\n          if(parsed.name.startsWith(\"_\")) {\n            return;\n          }\n\n          let result = sass.compileString(inputContent, {\n            loadPaths: [\n              parsed.dir || \".\",\n              this.config.dir.includes\n            ]\n          });\n\n          this.addDependencies(inputPath, result.loadedUrls);\n\n          return async (data) => {\n            return result.css;\n          };\n        },\n      });\n    },\n  });\n  elev.disableLogger();\n\n   let results = await elev.toJSON();\n   t.is(results.length, 2);\n\n   let code = results.filter(entry => entry.inputPath.endsWith(\"_code.scss\"))[0];\n   t.is(code.url, \"/_code.css\");\n   t.is(code.content, undefined);\n\n   let main = results.filter(entry => entry.inputPath.endsWith(\"style.scss\"))[0];\n   t.is(main.url, \"/style.css\");\n   t.is(\n    normalizeNewLines(main.content),\n    `code {\n  padding: 0.25em;\n  line-height: 0;\n}\n\n/* Comment */`);\n});\n\ntest(\"Use a date object for `date`, issue #3022\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: function (eleventyConfig) {\n      eleventyConfig.dataFilterSelectors.add(\"page.date\");\n      eleventyConfig.addTemplate(\"index.html\", \"\", { date: new Date() })\n    },\n  });\n  elev.disableLogger();\n\n let results = await elev.toJSON();\n t.is(results.length, 1);\n t.truthy(results[0].data.page.date instanceof Date);\n});\n\ntest(\"Use a date object for `date` (js object front matter), issue #3022\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: function (eleventyConfig) {\n      eleventyConfig.dataFilterSelectors.add(\"page.date\");\n      eleventyConfig.addTemplate(\"index.html\", `---js\n{\n  date: new Date(),\n}\n---`);\n    },\n  });\n  elev.disableLogger();\n\n  let results = await elev.toJSON();\n  t.is(results.length, 1);\n  t.truthy(results[0].data.page.date instanceof Date);\n});\n\ntest(\"Use a date object for `date` (js front matter), issue #3022\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: function (eleventyConfig) {\n      eleventyConfig.dataFilterSelectors.add(\"page.date\");\n      eleventyConfig.addTemplate(\"index.html\", `---js\nlet date = new Date();\n---`);\n    },\n  });\n  elev.disableLogger();\n\n  let results = await elev.toJSON();\n  t.is(results.length, 1);\n  t.truthy(results[0].data.page.date instanceof Date);\n});\n\ntest(\"Cleaner constructor args #3880\", async (t) => {\n  let elev = new Eleventy({\n    input: \"./test/stubs-virtual/\",\n    config: eleventyConfig => {\n      eleventyConfig.addTemplate(\"index.md\", `# Title`)\n    }\n  });\n\n  let results = await elev.toJSON();\n  t.is(results.length, 1);\n  t.is(results[0].content.trim(), `<h1>Title</h1>`);\n});\n"
  },
  {
    "path": "test/EleventyVirtualTemplatesTest.js",
    "content": "import test from \"ava\";\nimport fs from \"fs\";\nimport { feedPlugin } from \"@11ty/eleventy-plugin-rss\";\n\nimport Eleventy from \"../src/Eleventy.js\";\nimport DuplicatePermalinkOutputError from \"../src/Errors/DuplicatePermalinkOutputError.js\";\n\nimport { deleteDirectory } from \"./_testHelpers.js\";\n\ntest(\"Virtual templates, issue #1612\", async (t) => {\n\tlet elev = new Eleventy(\"./test/stubs-virtual-nowrite\", \"./test/stubs-virtual-nowrite/_site\", {\n\t\tconfig: function (eleventyConfig) {\n\t\t\televentyConfig.addTemplate(\"virtual.md\", `# Hello`)\n\t\t},\n\t});\n\n\tlet results = await elev.toJSON();\n\n\tt.deepEqual(results.length, 1);\n\tt.deepEqual(results[0].content.trim(), `<h1>Hello</h1>`);\n\tt.deepEqual(results[0].rawInput, `# Hello`);\n});\n\ntest(\"Virtual templates with front matter, issue #1612\", async (t) => {\n\tlet elev = new Eleventy(\"./test/stubs-virtual-nowrite\", \"./test/stubs-virtual-nowrite/_site\", {\n\t\tconfig: function (eleventyConfig) {\n\t\t\televentyConfig.addTemplate(\"./virtual.md\", `---\nmyKey: myValue\n---\n# {{ myKey }}`)\n\t\t},\n\t});\n\n\tlet results = await elev.toJSON();\n\n\tt.deepEqual(results.length, 1);\n\tt.deepEqual(results[0].content.trim(), `<h1>myValue</h1>`);\n\tt.deepEqual(results[0].rawInput, `# {{ myKey }}`);\n});\n\ntest(\"Virtual templates with supplemental data, issue #1612\", async (t) => {\n\tlet elev = new Eleventy(\"./test/stubs-virtual-nowrite\", \"./test/stubs-virtual-nowrite/_site\", {\n\t\tconfig: function (eleventyConfig) {\n\t\t\televentyConfig.addTemplate(\"virtual.md\", `# {{ myKey }}`, { myKey: \"myValue\" })\n\t\t},\n\t});\n\n\tlet results = await elev.toJSON();\n\n\tt.deepEqual(results.length, 1);\n\tt.deepEqual(results[0].content.trim(), `<h1>myValue</h1>`);\n\tt.deepEqual(results[0].rawInput, `# {{ myKey }}`);\n});\n\n// Supplemental data overrides front matter.\ntest(\"Virtual templates with front matter and supplemental data, issue #1612\", async (t) => {\n\tlet elev = new Eleventy(\"./test/stubs-virtual-nowrite\", \"./test/stubs-virtual-nowrite/_site\", {\n\t\tconfig: function (eleventyConfig) {\n\t\t\televentyConfig.addTemplate(\"virtual.md\", `---\nmyKey1: myValue1\nmyKey3: myValueFm\n---\n# {{ myKey1 }}{{ myKey2 }}{{ myKey3 }}`, { myKey2: \"myValue2\", myKey3: \"myValueData\" })\n\t\t},\n\t});\n\n\tlet results = await elev.toJSON();\n\n\tt.deepEqual(results.length, 1);\n\tt.deepEqual(results[0].content.trim(), `<h1>myValue1myValue2myValueData</h1>`);\n\tt.deepEqual(results[0].rawInput, `# {{ myKey1 }}{{ myKey2 }}{{ myKey3 }}`);\n});\n\ntest(\"Virtual template conflicts with file on file system, issue #1612\", async (t) => {\n\tlet elev = new Eleventy(\"./test/stubs/stubs-virtual-conflict\", \"./test/stubs/stubs-virtual-conflict/_site\", {\n\t\tconfig: function (eleventyConfig) {\n\t\t\televentyConfig.addTemplate(\"virtual.md\", `# Virtual template`)\n\t\t},\n\t});\n\telev.disableLogger();\n\n\tawait t.throwsAsync(elev.toJSON(), {\n\t\tmessage: `A virtual template had the same path as a file on the file system: \"./test/stubs/stubs-virtual-conflict/virtual.md\"`\n\t});\n});\n\ntest(\"Virtual templates try to output to the same file, issue #1612\", async (t) => {\n\tlet elev = new Eleventy(\"./test/stubs-virtual-nowrite\", \"./test/stubs-virtual-nowrite/_site\", {\n\t\tconfig: function (eleventyConfig) {\n\t\t\televentyConfig.addTemplate(\"virtual-one.md\", \"\", {\n\t\t\t\tpermalink: \"/output.html\"\n\t\t\t})\n\t\t\televentyConfig.addTemplate(\"virtual-two.md\", \"\", {\n\t\t\t\tpermalink: \"/output.html\"\n\t\t\t})\n\t\t},\n\t});\n\telev.disableLogger();\n\n\tawait t.throwsAsync(elev.toJSON(), {\n\t\tinstanceOf: DuplicatePermalinkOutputError,\n\t});\n});\n\n// Warning: this test writes to the file system\ntest(\"Virtual template writes to file system, issue #1612\", async (t) => {\n\tlet elev = new Eleventy(\"./test/stubs-virtual\", \"./test/stubs-virtual/_site\", {\n\t\tconfig: function (eleventyConfig) {\n\t\t\televentyConfig.addTemplate(\"virtual.md\", `# Hello`)\n\t\t},\n\t});\n\telev.disableLogger();\n\n\tlet [,results] = await elev.write();\n\n\tt.deepEqual(results.length, 1);\n\tt.deepEqual(results[0].content.trim(), `<h1>Hello</h1>`);\n\tt.deepEqual(results[0].rawInput, `# Hello`);\n\tt.true(fs.existsSync(\"./test/stubs-virtual/_site/virtual/index.html\"));\n\n\tdeleteDirectory(\"./test/stubs-virtual/_site/\");\n});\n\ntest(\"Virtual templates conflict\", async (t) => {\n\tlet elev = new Eleventy(\"./test/stubs-virtual-nowrite\", \"./test/stubs-virtual-nowrite/_site\", {\n\t\tconfig: function (eleventyConfig) {\n\t\t\televentyConfig.addTemplate(\"virtual.md\", `# Hello`);\n\t\t\televentyConfig.addTemplate(\"virtual.md\", `# Hello`);\n\t\t},\n\t});\n\n\tlet e = await t.throwsAsync(async () => {\n\t\tawait elev.toJSON();\n\t});\n\n\tt.is(e.message, \"Virtual template conflict: you can’t add multiple virtual templates that have the same inputPath: virtual.md\");\n});\n\n// https://github.com/11ty/eleventy-plugin-rss/issues/50\ntest(\"RSS virtual templates plugin\", async (t) => {\n\tlet elev = new Eleventy(\"./test/stubs-virtual-nowrite\", \"./test/stubs-virtual-nowrite/_site\", {\n\t\tconfig: function (eleventyConfig) {\n\t\t\televentyConfig.addTemplate(\"virtual.md\", `# Hello`, { tag: \"posts\" })\n\n\t\t\televentyConfig.addPlugin(feedPlugin, {\n\t\t\t\ttype: \"atom\", // or \"rss\", \"json\"\n\t\t\t\toutputPath: \"/feed.xml\",\n\t\t\t\tcollection: {\n\t\t\t\t\tname: \"posts\", // iterate over `collections.posts`\n\t\t\t\t\tlimit: 10,     // 0 means no limit\n\t\t\t\t},\n\t\t\t});\n\t\t},\n\t});\n\n\tlet results = await elev.toJSON();\n\n\tt.deepEqual(results.length, 2);\n\tlet [ feed ] = results.filter(entry => entry.outputPath.endsWith(\".xml\"));\n\tt.truthy(feed.content.startsWith(`<?xml version=\"1.0\" encoding=\"utf-8\"?>`));\n});\n\ntest(\"Virtual templates as layouts, issue #2307\", async (t) => {\n\tlet elev = new Eleventy(\"./test/stubs-virtual-nowrite\", \"./test/stubs-virtual-nowrite/_site\", {\n\t\tconfig: function (eleventyConfig) {\n\t\t\televentyConfig.addTemplate(\"virtual.md\", `# Hello`, {\n\t\t\t\tlayout: \"virtual.html\"\n\t\t\t});\n\n\t\t\tlet layoutPath = eleventyConfig.directories.getLayoutPathRelativeToInputDirectory(\"virtual.html\");\n\t\t\televentyConfig.addTemplate(layoutPath, `<!-- Layout -->{{ content }}`);\n\t\t},\n\t});\n\n\tlet results = await elev.toJSON();\n\n\tt.deepEqual(results.length, 1);\n\tt.deepEqual(results[0].content.trim(), `<!-- Layout --><h1>Hello</h1>`);\n\tt.deepEqual(results[0].rawInput, `# Hello`);\n});\n\ntest(\"11ty.js Virtual Templates (object), issue #3347\", async (t) => {\n  let templateDefinition = {\n    data: () => {\n      return { var: 2 };\n    },\n    render: function(data) {\n      return `this is a test ${data.var}.`;\n    }\n  };\n\n\tlet elev = new Eleventy(\"./test/stubs-virtual-nowrite\", \"./test/stubs-virtual-nowrite/_site\", {\n\t\tconfig: function (eleventyConfig) {\n\t\t\televentyConfig.addTemplate(\"virtual.11ty.js\", templateDefinition);\n    }\n\t});\n\n\tlet results = await elev.toJSON();\n\n\tt.deepEqual(results.length, 1);\n\tt.deepEqual(results[0].content.trim(), `this is a test 2.`);\n\t// TODO support rawInput on 11ty.js? Issue #3348\n\t// t.deepEqual(results[0].rawInput.data, templateDefinition.data);\n\t// t.deepEqual(results[0].rawInput.render, templateDefinition.render);\n});\n\ntest(\"11ty.js Virtual Templates (function), issue #3347\", async (t) => {\n  let templateDefinition = function(data) {\n    return `this is a test ${data.page.url}.`;\n  };\n\n\tlet elev = new Eleventy(\"./test/stubs-virtual-nowrite\", \"./test/stubs-virtual-nowrite/_site\", {\n\t\tconfig: function (eleventyConfig) {\n\t\t\televentyConfig.addTemplate(\"virtual.11ty.js\", templateDefinition);\n    }\n\t});\n\n\tlet results = await elev.toJSON();\n\n\tt.deepEqual(results.length, 1);\n\tt.deepEqual(results[0].content.trim(), `this is a test /virtual/.`);\n  // TODO support rawInput on 11ty.js?\n\t// t.deepEqual(results[0].rawInput, templateDefinition);\n});\n\n\ntest(\"11ty.js class templates with invalid signature, issue #1645\", async (t) => {\n\tlet elev = new Eleventy(\"./test/stubs-virtual-nowrite\", \"./test/stubs-virtual-nowrite/_site\", {\n\t\tconfig: function (eleventyConfig) {\n\t\t\televentyConfig.addTemplate(\"virtual.11ty.js\", class {});\n    }\n\t});\n  elev.disableLogger();\n\n  await t.throwsAsync(elev.toJSON(), {\n\t\tmessage: `Invalid class signature for an 11ty.js template: needs a render or data instance property.`\n\t});\n});\n\ntest(\"11ty.js class templates with instance properties (data), issue #1645\", async (t) => {\n\tlet elev = new Eleventy(\"./test/stubs-virtual-nowrite\", \"./test/stubs-virtual-nowrite/_site\", {\n\t\tconfig: function (eleventyConfig) {\n\t\t\televentyConfig.addTemplate(\"virtual.11ty.js\", class { data() { return {} } });\n    }\n\t});\n\n\tlet results = await elev.toJSON();\n\n\tt.deepEqual(results.length, 1);\n\tt.deepEqual(results[0].content.trim(), ``);\n});\n\ntest(\"11ty.js class templates with instance properties (render), issue #1645\", async (t) => {\n\tlet elev = new Eleventy(\"./test/stubs-virtual-nowrite\", \"./test/stubs-virtual-nowrite/_site\", {\n\t\tconfig: function (eleventyConfig) {\n\t\t\televentyConfig.addTemplate(\"virtual.11ty.js\", class { render() { return \"Hello!\" } });\n    }\n\t});\n\n\tlet results = await elev.toJSON();\n\n\tt.deepEqual(results.length, 1);\n\tt.deepEqual(results[0].content.trim(), `Hello!`);\n});\n\ntest(\"11ty.js class templates with instance properties (data and render), issue #1645\", async (t) => {\n\tlet elev = new Eleventy(\"./test/stubs-virtual-nowrite\", \"./test/stubs-virtual-nowrite/_site\", {\n\t\tconfig: function (eleventyConfig) {\n\t\t\televentyConfig.addTemplate(\"virtual.11ty.js\", class {\n        data() { return { key: \"world\" }; }\n        render(data) { return `Hello ${data.key}!` }\n      });\n    }\n\t});\n\n\tlet results = await elev.toJSON();\n\n\tt.deepEqual(results.length, 1);\n\tt.deepEqual(results[0].content.trim(), `Hello world!`);\n});\n\ntest(\"11ty.js class templates with instance properties (data and render arrows), issue #1645\", async (t) => {\n\tlet elev = new Eleventy(\"./test/stubs-virtual-nowrite\", \"./test/stubs-virtual-nowrite/_site\", {\n\t\tconfig: function (eleventyConfig) {\n\t\t\televentyConfig.addTemplate(\"virtual.11ty.js\", class {\n        data = () => { return { key: \"world\" }; }\n        render = (data) => { return `Hello ${data.key}!` }\n      });\n    }\n\t});\n\n\tlet results = await elev.toJSON();\n\n\tt.deepEqual(results.length, 1);\n\tt.deepEqual(results[0].content.trim(), `Hello world!`);\n});\n\ntest(\"11ty.js class templates with instance properties (data and render arrows, new), issue #1645\", async (t) => {\n\tlet elev = new Eleventy(\"./test/stubs-virtual-nowrite\", \"./test/stubs-virtual-nowrite/_site\", {\n\t\tconfig: function (eleventyConfig) {\n\t\t\televentyConfig.addTemplate(\"virtual.11ty.js\", new class {\n        data = () => { return { key: \"world\" }; }\n        render = (data) => { return `Hello ${data.key}!` }\n      });\n    }\n\t});\n\n\tlet results = await elev.toJSON();\n\n\tt.deepEqual(results.length, 1);\n\tt.deepEqual(results[0].content.trim(), `Hello world!`);\n});\n"
  },
  {
    "path": "test/ExistsCacheTest.js",
    "content": "import test from \"ava\";\n\nimport ExistsCache from \"../src/Util/ExistsCache.js\";\n\ntest(\"Simple check (with directory checking)\", async t => {\n  let cache = new ExistsCache();\n\n  t.is(cache.exists(\"test\"), true);\n  t.is(cache.size, 1);\n  t.is(cache.lookupCount, 1);\n  t.is(cache.exists(\"test\"), true);\n  t.is(cache.size, 1);\n  t.is(cache.lookupCount, 1);\n  t.is(cache.exists(\"test/stubs\"), true);\n  t.is(cache.size, 2);\n  t.is(cache.lookupCount, 2);\n  t.is(cache.exists(\"test/stubs/does-not-exist-ever-hslkadjflk\"), false);\n  t.is(cache.size, 3);\n  t.is(cache.lookupCount, 3);\n});\n\ntest(\"Simple check (parent directory already invalidated)\", async t => {\n  let cache = new ExistsCache();\n\n  t.is(cache.exists(\"test/folder-does-not-exist-askdfjkladjs\"), false);\n  t.is(cache.size, 1);\n  t.is(cache.lookupCount, 1);\n\n\t// we already know this *doesn’t* exist.\n  t.is(cache.exists(\"test/folder-does-not-exist-askdfjkladjs/file-we-already-know-does-not-exist.liquid\"), false);\n\tt.is(cache.size, 2);\n  t.is(cache.lookupCount, 2);\n});\n"
  },
  {
    "path": "test/FileSystemSearchTest.js",
    "content": "import test from \"ava\";\nimport FileSystemSearch from \"../src/FileSystemSearch.js\";\n\ntest(\"Base\", async (t) => {\n  let fs = new FileSystemSearch();\n  t.is(fs.count, 0);\n\n  t.deepEqual(await fs.search(\"key\", \"./test/file-system-search/*.txt\"), [\n    \"./test/file-system-search/file.txt\",\n  ]);\n  t.is(fs.count, 1);\n\n  fs.add(\"./test/file-system-search/virtual-file.txt\");\n  t.deepEqual(await fs.search(\"key\", \"./test/file-system-search/*.txt\"), [\n    \"./test/file-system-search/file.txt\",\n    \"./test/file-system-search/virtual-file.txt\",\n  ]);\n  t.is(fs.count, 1);\n\n  fs.add(\"./test/file-system-search/another-file.txt\");\n  t.deepEqual(await fs.search(\"key\", \"./test/file-system-search/*.txt\"), [\n    \"./test/file-system-search/file.txt\",\n    \"./test/file-system-search/virtual-file.txt\",\n    \"./test/file-system-search/another-file.txt\",\n  ]);\n  t.is(fs.count, 1);\n\n  // Delete\n  fs.delete(\"./test/file-system-search/file.txt\");\n  t.deepEqual(await fs.search(\"key\", \"./test/file-system-search/*.txt\"), [\n    \"./test/file-system-search/virtual-file.txt\",\n    \"./test/file-system-search/another-file.txt\",\n  ]);\n  t.is(fs.count, 1);\n\n  fs.delete(\"./test/file-system-search/another-file.txt\");\n  t.deepEqual(await fs.search(\"key\", \"./test/file-system-search/*.txt\"), [\n    \"./test/file-system-search/virtual-file.txt\",\n  ]);\n  t.is(fs.count, 1);\n\n  fs.delete(\"./test/file-system-search/virtual-file.txt\");\n  t.deepEqual(await fs.search(\"key\", \"./test/file-system-search/*.txt\"), []);\n  t.is(fs.count, 1);\n});\n"
  },
  {
    "path": "test/GetCollectionItemIndexTest.js",
    "content": "import test from \"ava\";\nimport getCollectionItemIndex from \"../src/Filters/GetCollectionItemIndex.js\";\n\ntest(\"getCollectionItemIndex\", (t) => {\n  let first = {\n    inputPath: \"hello.md\",\n    outputPath: \"/hello/\",\n  };\n  let second = {\n    inputPath: \"hello2.md\",\n    outputPath: \"/hello2/\",\n  };\n  let third = {\n    inputPath: \"hello3.md\",\n    outputPath: \"/hello3/\",\n  };\n  let collections = [first, second, third];\n\n  t.deepEqual(getCollectionItemIndex(collections, first), 0);\n  t.deepEqual(getCollectionItemIndex(collections, second), 1);\n  t.deepEqual(getCollectionItemIndex(collections, third), 2);\n\n  t.deepEqual(\n    getCollectionItemIndex(collections, {\n      inputPath: \"unknown.md\",\n      outputPath: \"/unknown/\",\n    }),\n    undefined\n  );\n});\n"
  },
  {
    "path": "test/GetCollectionItemTest.js",
    "content": "import test from \"ava\";\n\nimport getCollectionItem from \"../src/Filters/GetCollectionItem.js\";\n\ntest(\"getCollectionItem\", (t) => {\n  let first = {\n    inputPath: \"hello.md\",\n    outputPath: \"/hello/\",\n  };\n  let second = {\n    inputPath: \"hello2.md\",\n    outputPath: \"/hello2/\",\n  };\n  let third = {\n    inputPath: \"hello3.md\",\n    outputPath: \"/hello3/\",\n  };\n  let collections = [first, second, third];\n\n  t.deepEqual(getCollectionItem(collections, first), first);\n  t.deepEqual(getCollectionItem(collections, second), second);\n  t.deepEqual(getCollectionItem(collections, third), third);\n\n  t.deepEqual(getCollectionItem(collections, first, -1), undefined);\n  t.deepEqual(getCollectionItem(collections, second, -1), first);\n  t.deepEqual(getCollectionItem(collections, third, -1), second);\n\n  t.deepEqual(getCollectionItem(collections, first, 1), second);\n  t.deepEqual(getCollectionItem(collections, second, 1), third);\n  t.deepEqual(getCollectionItem(collections, third, 1), undefined);\n});\n"
  },
  {
    "path": "test/GlobRemapTest.js",
    "content": "import test from \"ava\";\nimport path from \"node:path\";\nimport GlobRemap from \"../src/Util/GlobRemap.js\";\nimport { normalizeSeparatorString } from \"./Util/normalizeSeparators.js\";\n\ntest(\"getParentDirPrefix\", (t) => {\n  t.is(GlobRemap.getParentDirPrefix(\"\"), \"\");\n  t.is(GlobRemap.getParentDirPrefix(\"./test/\"), \"\");\n  t.is(GlobRemap.getParentDirPrefix(\"../test/\"), \"../\");\n  t.is(GlobRemap.getParentDirPrefix(\"../test/../\"), \"../\");\n  t.is(GlobRemap.getParentDirPrefix(\"../../test/\"), \"../../\");\n});\n\ntest(\"getCwd\", (t) => {\n  t.is(GlobRemap.getCwd([]), \"\");\n  t.is(GlobRemap.getCwd([\"test.njk\"]), \"\");\n  t.is(GlobRemap.getCwd([\"./test.njk\"]), \"\");\n  t.is(GlobRemap.getCwd([\"../test.njk\"]), \"../\");\n  t.is(GlobRemap.getCwd([\"../test.njk\", \"../../2.njk\"]), \"../../\");\n});\n\ntest(\"Constructor (control)\", t => {\n  let m = new GlobRemap([\n    '**/*.{liquid,md,njk,html,11ty.js,11ty.cjs,11ty.mjs}',\n    '**/*.txt', // passthrough copy\n    '**/*.png',\n    '_includes/**',\n    '_data/**',\n    '.gitignore',\n    '.eleventyignore',\n    'eleventy.config.js',\n  ])\n\n  t.deepEqual(m.getInput(), [\n    '**/*.{liquid,md,njk,html,11ty.js,11ty.cjs,11ty.mjs}',\n    '**/*.txt', // passthrough copy\n    '**/*.png',\n    '_includes/**',\n    '_data/**',\n    '.gitignore',\n    '.eleventyignore',\n    'eleventy.config.js',\n  ])\n});\n\ntest(\"Constructor (control with ./)\", t => {\n  let m = new GlobRemap([\n    './**/*.{liquid,md,njk,html,11ty.js,11ty.cjs,11ty.mjs}',\n    './**/*.txt', // passthrough copy\n    './**/*.png',\n    './_includes/**',\n    './_data/**',\n    './.gitignore',\n    './.eleventyignore',\n    './eleventy.config.js',\n  ])\n\n  t.deepEqual(m.getInput(), [\n    './**/*.{liquid,md,njk,html,11ty.js,11ty.cjs,11ty.mjs}',\n    './**/*.txt', // passthrough copy\n    './**/*.png',\n    './_includes/**',\n    './_data/**',\n    './.gitignore',\n    './.eleventyignore',\n    './eleventy.config.js',\n  ])\n});\n\ntest(\"Constructor (up one dir)\", t => {\n  let m = new GlobRemap([\n    '../**/*.{liquid,md,njk,html,11ty.js,11ty.cjs,11ty.mjs}',\n    '../**/*.txt', // passthrough copy\n    '../**/*.png',\n    '../_includes/**',\n    '../_data/**',\n    './.gitignore',\n    './.eleventyignore',\n    '../.eleventyignore',\n    './eleventy.config.js',\n  ])\n\n  let parentDir = normalizeSeparatorString(path.resolve(\"./\").split(path.sep).slice(-1).join(path.sep));\n\n  t.deepEqual(m.getInput(), [\n    '**/*.{liquid,md,njk,html,11ty.js,11ty.cjs,11ty.mjs}',\n    '**/*.txt', // passthrough copy\n    '**/*.png',\n    '_includes/**',\n    '_data/**',\n    `${parentDir}/.gitignore`,\n    `${parentDir}/.eleventyignore`,\n    '.eleventyignore',\n    `${parentDir}/eleventy.config.js`,\n  ])\n});\n\ntest(\"Constructor (up two dirs)\", t => {\n  let m = new GlobRemap([\n    '../../**/*.{liquid,md,njk,html,11ty.js,11ty.cjs,11ty.mjs}',\n    '../**/*.txt', // passthrough copy\n    '../**/*.png',\n    '../_includes/**',\n    '../_data/**',\n    './.gitignore',\n    './.eleventyignore',\n    '../.eleventyignore',\n    './eleventy.config.js',\n  ])\n\n  let childDir = normalizeSeparatorString(path.resolve(\"./\").split(path.sep).slice(-2).join(path.sep));\n  let parentDir = normalizeSeparatorString(path.resolve(\"./\").split(path.sep).slice(-2, -1).join(path.sep));\n\n  t.deepEqual(m.getInput(), [\n    '**/*.{liquid,md,njk,html,11ty.js,11ty.cjs,11ty.mjs}',\n    `${parentDir}/**/*.txt`, // passthrough copy\n    `${parentDir}/**/*.png`,\n    `${parentDir}/_includes/**`,\n    `${parentDir}/_data/**`,\n    `${childDir}/.gitignore`,\n    `${childDir}/.eleventyignore`,\n    `${parentDir}/.eleventyignore`,\n    `${childDir}/eleventy.config.js`,\n  ])\n});\n"
  },
  {
    "path": "test/GlobStripperTest.js",
    "content": "import test from \"ava\";\nimport { GlobStripper } from \"../src/Util/GlobStripper.js\";\n\ntest(\"Separate globs from directories\", (t) => {\n  t.deepEqual(GlobStripper.parse(\"\"), { path: \".\" });\n  t.deepEqual(GlobStripper.parse(\"dir\"), { path: \"dir\" });\n  t.deepEqual(GlobStripper.parse(\"./*\"), { path: \".\", glob: \"*\" });\n  t.deepEqual(GlobStripper.parse(\"*\"), { path: \".\", glob: \"*\" });\n  t.deepEqual(GlobStripper.parse(\"**\"), { path: \".\", glob: \"**\" });\n  t.deepEqual(GlobStripper.parse(\"**/*\"), { path: \".\", glob: \"**/*\" });\n  t.deepEqual(GlobStripper.parse(\"*/*\"), { path: \".\", glob: \"*/*\" });\n  t.deepEqual(GlobStripper.parse(\"**/**/*\"), { path: \".\", glob: \"**/**/*\" });\n  t.deepEqual(GlobStripper.parse(\".dot/**\"), { path: \".dot\", glob: \"**\" });\n  t.deepEqual(GlobStripper.parse(\"dir/**\"), { path: \"dir\", glob: \"**\" });\n  t.deepEqual(GlobStripper.parse(\"/dir/**\"), { path: \"/dir\", glob: \"**\" });\n  t.deepEqual(GlobStripper.parse(\"dir/**/*\"), { path: \"dir\", glob: \"**/*\" });\n  t.deepEqual(GlobStripper.parse(\"dir/**/*.{jpg,png}\"), { path: \"dir\", glob: \"**/*.{jpg,png}\" });\n});\n\ntest(\"Star not at start of filename\", (t) => {\n  t.deepEqual(GlobStripper.parse(\"a*.c*\"), { path: \".\", glob: \"a*.c*\" });\n  t.deepEqual(GlobStripper.parse(\"a/b-*/**/z.js\"), { path: \"a\", glob: \"b-*/**/z.js\" });\n});\n\ntest(\"Expected failures\", (t) => {\n  t.throws(() => GlobStripper.parse(\"?/?\"), { message: \"Could not automatically determine top-most folder from glob pattern: ?/?\"});\n});\n\ntest(\"Issue #3910\", (t) => {\n  t.deepEqual(GlobStripper.parse(\"./node_modules/artificial-chart/artificial-chart.css\"), { path: \"./node_modules/artificial-chart/artificial-chart.css\" });\n  t.deepEqual(GlobStripper.parse(\"./node_modules/artificial-chart/artificial-chart.{css,js}\"), { path: \"./node_modules/artificial-chart\", glob: \"artificial-chart.{css,js}\" });\n  t.deepEqual(GlobStripper.parse(\"./node_modules/artificial-chart/artificial-chart.(css|js)\"), { path: \"./node_modules/artificial-chart\", glob: \"artificial-chart.(css|js)\" });\n});\n"
  },
  {
    "path": "test/GlobalDependencyMapTest.js",
    "content": "import test from \"ava\";\nimport GlobalDependencyMap from \"../src/GlobalDependencyMap.js\";\n\ntest(\"Test map\", (t) => {\n  let map = new GlobalDependencyMap();\n  map.addDependency(\"test.njk\", [\"_includes/include.njk\"]);\n  t.true(map.hasDependency(\"test.njk\", \"_includes/include.njk\"));\n  t.false(map.hasDependency(\"test.njk\", \"_includes/other.njk\"));\n\n  t.false(map.isFileRelevantTo(\"test.njk\", null));\n  t.true(map.isFileRelevantTo(\"test.njk\", \"test.njk\"));\n\n  // if _includes/include.njk changes, we want to recompile test.njk\n  t.true(map.isFileRelevantTo(\"test.njk\", \"_includes/include.njk\"));\n  t.false(map.isFileRelevantTo(\"_includes/include.njk\", \"test.njk\"));\n});\n\ntest(\"Normalize nodes (remove leading dot slash)\", (t) => {\n  let map = new GlobalDependencyMap();\n  map.addDependency(\"./test.njk\", [\"./_includes/include.njk\"]);\n  t.true(map.hasDependency(\"./test.njk\", \"./_includes/include.njk\"));\n  t.false(map.hasDependency(\"./test.njk\", \"./_includes/other.njk\"));\n\n  t.true(map.isFileRelevantTo(\"./test.njk\", \"./_includes/include.njk\"));\n  t.false(map.isFileRelevantTo(\"./_includes/include.njk\", \"./test.njk\"));\n});\n\ntest(\"Layouts\", (t) => {\n  let map = new GlobalDependencyMap();\n  map.addDependency(\"test.njk\", [\"_includes/include.njk\"]);\n  map.addLayoutsToMap({\n    \"./_includes/layout.njk\": [\"./test.njk\"],\n  });\n\n  // if _layout/layout.njk changes, we want to write test.njk\n  t.true(map.isFileRelevantTo(\"test.njk\", \"_includes/layout.njk\"));\n  t.false(map.isFileRelevantTo(\"_includes/layout.njk\", \"test.njk\"));\n\n  t.false(map.isFileRelevantTo(\"_includes/layout.njk\", \"_includes/include.njk\"));\n  t.false(map.isFileRelevantTo(\"_includes/include.njk\", \"_includes/layout.njk\"));\n\n  // if _layout/layout.njk changes, we don’t care about recompiling test.njk (ignore layouts)\n  // though we do want to re-write test.njk so we want incremental to match\n  t.false(map.isFileRelevantTo(\"test.njk\", \"_includes/layout.njk\", false));\n  t.false(map.isFileRelevantTo(\"_includes/layout.njk\", \"test.njk\", false));\n\n  t.false(map.isFileRelevantTo(\"_includes/layout.njk\", \"_includes/include.njk\", false));\n  t.false(map.isFileRelevantTo(\"_includes/include.njk\", \"_includes/layout.njk\", false));\n});\n\ntest(\"Stringify/restore\", (t) => {\n  let origin = new GlobalDependencyMap();\n  origin.addDependency(\"test.njk\", [\"_includes/include.njk\"]);\n\n  t.is(\n    origin.stringify(),\n    `{\"nodes\":{\"__collection:all\":\"__collection:all\",\"__collection:[keys]\":\"__collection:[keys]\",\"__collection:[userconfig]\":\"__collection:[userconfig]\",\"__collection:[basic]\":\"__collection:[basic]\",\"test.njk\":\"test.njk\",\"_includes/include.njk\":\"_includes/include.njk\"},\"outgoingEdges\":{\"__collection:all\":[\"__collection:[keys]\"],\"__collection:[keys]\":[\"__collection:[userconfig]\"],\"__collection:[userconfig]\":[\"__collection:[basic]\"],\"__collection:[basic]\":[],\"test.njk\":[\"_includes/include.njk\"],\"_includes/include.njk\":[]},\"incomingEdges\":{\"__collection:all\":[],\"__collection:[keys]\":[\"__collection:all\"],\"__collection:[userconfig]\":[\"__collection:[keys]\"],\"__collection:[basic]\":[\"__collection:[userconfig]\"],\"test.njk\":[],\"_includes/include.njk\":[\"test.njk\"]},\"circular\":true}`\n  );\n\n  let map = new GlobalDependencyMap();\n  map.restore(origin.stringify());\n\n  t.true(map.hasDependency(\"test.njk\", \"_includes/include.njk\"));\n  t.false(map.hasDependency(\"test.njk\", \"_includes/other.njk\"));\n\n  t.false(map.isFileRelevantTo(\"test.njk\", null));\n  t.true(map.isFileRelevantTo(\"test.njk\", \"test.njk\"));\n\n  // if _includes/include.njk changes, we want to recompile test.njk\n  t.true(map.isFileRelevantTo(\"test.njk\", \"_includes/include.njk\"));\n  t.false(map.isFileRelevantTo(\"_includes/include.njk\", \"test.njk\"));\n});\n\ntest(\"Collection API\", (t) => {\n  let map = new GlobalDependencyMap();\n\n  map.setCollectionApiNames([\"articles\"]);\n  map.addNewNodeRelationships(\"test.njk\", [], [\"all\"])\n  map.addNewNodeRelationships(\"feed.njk\", [\"articles\"], [\"all\"])\n\n  t.deepEqual(map.getTemplateOrder(), [\n    \"test.njk\",\n    \"__collection:[keys]\",\n    \"__collection:articles\",\n    \"feed.njk\",\n    \"__collection:all\",\n  ]);\n});\n"
  },
  {
    "path": "test/HtmlBasePluginTest.js",
    "content": "import test from \"ava\";\n\nimport { default as HtmlBasePlugin, applyBaseToUrl } from \"../src/Plugins/HtmlBasePlugin.js\";\nimport Eleventy from \"../src/Eleventy.js\";\nimport { normalizeNewLines } from \"./Util/normalizeNewLines.js\";\n\nfunction getContentFor(results, filename) {\n  let content = results.filter((entry) => entry.outputPath.endsWith(filename))[0].content;\n  return normalizeNewLines(content.trim());\n}\n\ntest(\"Using the filter directly\", async (t) => {\n  // url, base, pathprefix\n\n  // default pathprefix\n  t.is(applyBaseToUrl(\"/\", \"/\"), \"/\");\n  t.is(applyBaseToUrl(\"/test/\", \"/\"), \"/test/\");\n  t.is(applyBaseToUrl(\"subdir/\", \"/\"), \"subdir/\");\n  t.is(applyBaseToUrl(\"../subdir/\", \"/\"), \"../subdir/\");\n  t.is(applyBaseToUrl(\"./subdir/\", \"/\"), \"subdir/\");\n  t.is(applyBaseToUrl(\"http://example.com/\", \"/\"), \"http://example.com/\");\n  t.is(applyBaseToUrl(\"http://example.com/test/\", \"/\"), \"http://example.com/test/\");\n\n  // relative url pathprefix is ignored\n  t.is(applyBaseToUrl(\"/\", \"../\"), \"/\");\n  t.is(applyBaseToUrl(\"/test/\", \"../\"), \"/test/\");\n  t.is(applyBaseToUrl(\"subdir/\", \"../\"), \"subdir/\");\n  t.is(applyBaseToUrl(\"../subdir/\", \"../\"), \"../subdir/\");\n  t.is(applyBaseToUrl(\"./subdir/\", \"../\"), \"subdir/\");\n  t.is(applyBaseToUrl(\"http://example.com/\", \"../\"), \"http://example.com/\");\n  t.is(applyBaseToUrl(\"http://example.com/test/\", \"../\"), \"http://example.com/test/\");\n\n  // with a pathprefix\n  t.is(applyBaseToUrl(\"/\", \"/pathprefix/\"), \"/pathprefix/\");\n  t.is(applyBaseToUrl(\"/test/\", \"/pathprefix/\"), \"/pathprefix/test/\");\n  t.is(applyBaseToUrl(\"subdir/\", \"/pathprefix/\"), \"subdir/\");\n  t.is(applyBaseToUrl(\"../subdir/\", \"/pathprefix/\"), \"../subdir/\");\n  t.is(applyBaseToUrl(\"./subdir/\", \"/pathprefix/\"), \"subdir/\");\n  t.is(applyBaseToUrl(\"#anchor\", \"/pathprefix/\"), \"#anchor\");\n  t.is(applyBaseToUrl(\"/test/#anchor\", \"/pathprefix/\"), \"/pathprefix/test/#anchor\");\n  t.is(applyBaseToUrl(\"/test/?param=value\", \"/pathprefix/\"), \"/pathprefix/test/?param=value\");\n  t.is(applyBaseToUrl(\"http://url.com/\", \"/pathprefix/\"), \"http://url.com/\");\n  t.is(applyBaseToUrl(\"http://url.com/test/\", \"/pathprefix/\"), \"http://url.com/test/\");\n\n  // with a URL base\n  t.is(applyBaseToUrl(\"/\", \"http://example.com/\"), \"http://example.com/\");\n  t.is(applyBaseToUrl(\"/test/\", \"http://example.com/\"), \"http://example.com/test/\");\n  t.is(applyBaseToUrl(\"subdir/\", \"http://example.com/\"), \"http://example.com/subdir/\");\n  t.is(applyBaseToUrl(\"../subdir/\", \"http://example.com/\"), \"http://example.com/subdir/\");\n  t.is(applyBaseToUrl(\"./subdir/\", \"http://example.com/\"), \"http://example.com/subdir/\");\n  t.is(applyBaseToUrl(\"http://url.com/\", \"http://example.com/\"), \"http://url.com/\");\n  t.is(applyBaseToUrl(\"http://url.com/test/\", \"http://example.com/\"), \"http://url.com/test/\");\n  t.is(applyBaseToUrl(\"#anchor\", \"http://example.com/\"), \"http://example.com/#anchor\");\n\tt.is(applyBaseToUrl(\"/test/#anchor\", \"http://example.com/\"), \"http://example.com/test/#anchor\");\n\tt.is(applyBaseToUrl(\"/test/?param=value#anchor\", \"http://example.com/\"), \"http://example.com/test/?param=value#anchor\");\n\n  // with a URL base with extra subdirectory\n  t.is(applyBaseToUrl(\"/\", \"http://example.com/ignored/\"), \"http://example.com/\");\n  t.is(applyBaseToUrl(\"/test/\", \"http://example.com/ignored/\"), \"http://example.com/test/\");\n  t.is(applyBaseToUrl(\"subdir/\", \"http://example.com/deep/\"), \"http://example.com/deep/subdir/\");\n  t.is(applyBaseToUrl(\"../subdir/\", \"http://example.com/deep/\"), \"http://example.com/subdir/\");\n  t.is(applyBaseToUrl(\"./subdir/\", \"http://example.com/deep/\"), \"http://example.com/deep/subdir/\");\n  t.is(applyBaseToUrl(\"http://url.com/\", \"http://example.com/ignored/\"), \"http://url.com/\");\n  t.is(\n    applyBaseToUrl(\"http://url.com/test/\", \"http://example.com/ignored/\"),\n    \"http://url.com/test/\"\n  );\n\n  // with a URL base and root pathprefix\n  t.is(applyBaseToUrl(\"/\", \"http://example.com/\", { pathPrefix: \"/\" }), \"http://example.com/\");\n  t.is(\n    applyBaseToUrl(\"/test/\", \"http://example.com/\", { pathPrefix: \"/\" }),\n    \"http://example.com/test/\"\n  );\n  t.is(\n    applyBaseToUrl(\"subdir/\", \"http://example.com/\", { pathPrefix: \"/\" }),\n    \"http://example.com/subdir/\"\n  );\n  t.is(\n    applyBaseToUrl(\"../subdir/\", \"http://example.com/\", { pathPrefix: \"/\" }),\n    \"http://example.com/subdir/\"\n  );\n  t.is(\n    applyBaseToUrl(\"./subdir/\", \"http://example.com/\", { pathPrefix: \"/\" }),\n    \"http://example.com/subdir/\"\n  );\n  t.is(\n    applyBaseToUrl(\"http://url.com/\", \"http://example.com/\", {\n      pathPrefix: \"/\",\n    }),\n    \"http://url.com/\"\n  );\n  t.is(\n    applyBaseToUrl(\"http://url.com/test/\", \"http://example.com/\", {\n      pathPrefix: \"/\",\n    }),\n    \"http://url.com/test/\"\n  );\n\n  // with a base and pathprefix\n  t.is(\n    applyBaseToUrl(\"/\", \"http://example.com/\", { pathPrefix: \"/pathprefix/\" }),\n    \"http://example.com/pathprefix/\"\n  );\n  t.is(\n    applyBaseToUrl(\"/test/\", \"http://example.com/\", {\n      pathPrefix: \"/pathprefix/\",\n    }),\n    \"http://example.com/pathprefix/test/\"\n  );\n  t.is(\n    applyBaseToUrl(\"subdir/\", \"http://example.com/\", {\n      pathPrefix: \"/pathprefix/\",\n    }),\n    \"http://example.com/pathprefix/subdir/\"\n  );\n  t.is(\n    applyBaseToUrl(\"../subdir/\", \"http://example.com/\", {\n      pathPrefix: \"/pathprefix/\",\n    }),\n    \"http://example.com/pathprefix/subdir/\"\n  );\n  t.is(\n    applyBaseToUrl(\"./subdir/\", \"http://example.com/\", {\n      pathPrefix: \"/pathprefix/\",\n    }),\n    \"http://example.com/pathprefix/subdir/\"\n  );\n  t.is(\n    applyBaseToUrl(\"http://url.com/\", \"http://example.com/\", {\n      pathPrefix: \"/pathprefix/\",\n    }),\n    \"http://url.com/\"\n  );\n  t.is(\n    applyBaseToUrl(\"http://url.com/test/\", \"http://example.com/\", {\n      pathPrefix: \"/pathprefix/\",\n    }),\n    \"http://url.com/test/\"\n  );\n\n  // with a base and pathprefix and page url (for relative path urls)\n  t.is(\n    applyBaseToUrl(\"/\", \"http://example.com/\", {\n      pathPrefix: \"/pathprefix/\",\n      pageUrl: \"/deep/\",\n    }),\n    \"http://example.com/pathprefix/\"\n  );\n  t.is(\n    applyBaseToUrl(\"/test/\", \"http://example.com/\", {\n      pathPrefix: \"/pathprefix/\",\n      pageUrl: \"/deep/\",\n    }),\n    \"http://example.com/pathprefix/test/\"\n  );\n  t.is(\n    applyBaseToUrl(\"subdir/\", \"http://example.com/\", {\n      pathPrefix: \"/pathprefix/\",\n      pageUrl: \"/deep/\",\n    }),\n    \"http://example.com/pathprefix/deep/subdir/\"\n  );\n  t.is(\n    applyBaseToUrl(\"../subdir/\", \"http://example.com/\", {\n      pathPrefix: \"/pathprefix/\",\n      pageUrl: \"/deep/\",\n    }),\n    \"http://example.com/pathprefix/subdir/\"\n  );\n  t.is(\n    applyBaseToUrl(\"./subdir/\", \"http://example.com/\", {\n      pathPrefix: \"/pathprefix/\",\n      pageUrl: \"/deep/\",\n    }),\n    \"http://example.com/pathprefix/deep/subdir/\"\n  );\n  t.is(\n    applyBaseToUrl(\"http://url.com/\", \"http://example.com/\", {\n      pathPrefix: \"/pathprefix/\",\n      pageUrl: \"/deep/\",\n    }),\n    \"http://url.com/\"\n  );\n  t.is(\n    applyBaseToUrl(\"http://url.com/test/\", \"http://example.com/\", {\n      pathPrefix: \"/pathprefix/\",\n      pageUrl: \"/deep/\",\n    }),\n    \"http://url.com/test/\"\n  );\n\n  // with a base (with extra subdir) and pathprefix and page url (for relative path urls)\n  // Note: Extra subdir is ignored when pageUrl is in play\n  t.is(\n    applyBaseToUrl(\"/\", \"http://example.com/ignored/\", {\n      pathPrefix: \"/pathprefix/\",\n      pageUrl: \"/deep/\",\n    }),\n    \"http://example.com/pathprefix/\"\n  );\n  t.is(\n    applyBaseToUrl(\"/test/\", \"http://example.com/ignored/\", {\n      pathPrefix: \"/pathprefix/\",\n      pageUrl: \"/deep/\",\n    }),\n    \"http://example.com/pathprefix/test/\"\n  );\n  t.is(\n    applyBaseToUrl(\"subdir/\", \"http://example.com/ignored/\", {\n      pathPrefix: \"/pathprefix/\",\n      pageUrl: \"/deep/\",\n    }),\n    \"http://example.com/pathprefix/deep/subdir/\"\n  );\n  t.is(\n    applyBaseToUrl(\"../subdir/\", \"http://example.com/ignored/\", {\n      pathPrefix: \"/pathprefix/\",\n      pageUrl: \"/deep/\",\n    }),\n    \"http://example.com/pathprefix/subdir/\"\n  );\n  t.is(\n    applyBaseToUrl(\"./subdir/\", \"http://example.com/ignored/\", {\n      pathPrefix: \"/pathprefix/\",\n      pageUrl: \"/deep/\",\n    }),\n    \"http://example.com/pathprefix/deep/subdir/\"\n  );\n  t.is(\n    applyBaseToUrl(\"http://url.com/\", \"http://example.com/ignored/\", {\n      pathPrefix: \"/pathprefix/\",\n      pageUrl: \"/deep/\",\n    }),\n    \"http://url.com/\"\n  );\n  t.is(\n    applyBaseToUrl(\"http://url.com/test/\", \"http://example.com/ignored/\", {\n      pathPrefix: \"/pathprefix/\",\n      pageUrl: \"/deep/\",\n    }),\n    \"http://url.com/test/\"\n  );\n});\n\ntest(\"Using the HTML base plugin (default values)\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-base/\", \"./test/stubs-base/_site\", {\n    configPath: false,\n    config: function (eleventyConfig) {\n      eleventyConfig.setUseTemplateCache(false);\n      eleventyConfig.addPlugin(HtmlBasePlugin);\n    },\n  });\n  await elev.initializeConfig();\n\n  elev.setIsVerbose(false);\n  elev.disableLogger();\n\n  let results = await elev.toJSON();\n  t.is(\n    getContentFor(results, \"/deep/index.html\"),\n    `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<meta name=\"description\" content=\"\">\n<title></title>\n<style>div { background-image: url(test.jpg); }</style>\n<style>div { background-image: url(/test.jpg); }</style>\n<link rel=\"stylesheet\" href=\"/test.css\">\n<script src=\"/test.js\"></script>\n</head>\n<body>\n<a href=\"/\">Home</a>\n<a href=\"subdir/\">Test</a>\n<a href=\"./subdir/\">Test</a>\n<a href=\"../subdir/\">Test</a>\n</body>\n</html>`\n  );\n});\n\ntest(\"Using the HTML base plugin with pathPrefix: /test/\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-base/\", \"./test/stubs-base/_site\", {\n    pathPrefix: \"/test/\",\n\n    configPath: false,\n    config: function (eleventyConfig) {\n      eleventyConfig.setUseTemplateCache(false);\n      eleventyConfig.addPlugin(HtmlBasePlugin);\n    },\n  });\n\n  await elev.initializeConfig();\n\n  elev.setIsVerbose(false);\n  elev.disableLogger();\n\n  let results = await elev.toJSON();\n  t.is(\n    getContentFor(results, \"/deep/index.html\"),\n    `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<meta name=\"description\" content=\"\">\n<title></title>\n<style>div { background-image: url(test.jpg); }</style>\n<style>div { background-image: url(/test/test.jpg); }</style>\n<link rel=\"stylesheet\" href=\"/test/test.css\">\n<script src=\"/test/test.js\"></script>\n</head>\n<body>\n<a href=\"/test/\">Home</a>\n<a href=\"subdir/\">Test</a>\n<a href=\"subdir/\">Test</a>\n<a href=\"../subdir/\">Test</a>\n</body>\n</html>`\n  );\n});\n\ntest(\"Using the HTML base plugin with pathPrefix: /test/ and base: http://example.com/\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-base/\", \"./test/stubs-base/_site\", {\n    pathPrefix: \"/test/\",\n\n    configPath: false,\n    config: function (eleventyConfig) {\n      eleventyConfig.setUseTemplateCache(false);\n      eleventyConfig.addPlugin(HtmlBasePlugin, {\n        baseHref: \"http://example.com/\",\n      });\n    },\n  });\n\n  await elev.initializeConfig();\n\n  elev.setIsVerbose(false);\n  elev.disableLogger();\n\n  let results = await elev.toJSON();\n  t.is(\n    getContentFor(results, \"/deep/index.html\"),\n    `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<meta name=\"description\" content=\"\">\n<title></title>\n<style>div { background-image: url(test.jpg); }</style>\n<style>div { background-image: url(http://example.com/test/test.jpg); }</style>\n<link rel=\"stylesheet\" href=\"http://example.com/test/test.css\">\n<script src=\"http://example.com/test/test.js\"></script>\n</head>\n<body>\n<a href=\"http://example.com/test/\">Home</a>\n<a href=\"http://example.com/test/deep/subdir/\">Test</a>\n<a href=\"http://example.com/test/deep/subdir/\">Test</a>\n<a href=\"http://example.com/test/subdir/\">Test</a>\n</body>\n</html>`\n  );\n});\n\ntest(\"Using the HTML base plugin strips extra path in full URL base (default pathPrefix)\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-base/\", \"./test/stubs-base/_site\", {\n    configPath: false,\n    config: function (eleventyConfig) {\n      eleventyConfig.setUseTemplateCache(false);\n      eleventyConfig.addPlugin(HtmlBasePlugin, {\n        baseHref: \"http://example.com/hello/\", // extra path will be stripped\n      });\n    },\n  });\n\n  await elev.initializeConfig();\n\n  elev.setIsVerbose(false);\n  elev.disableLogger();\n\n  let results = await elev.toJSON();\n  t.is(\n    getContentFor(results, \"/deep/index.html\"),\n    `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<meta name=\"description\" content=\"\">\n<title></title>\n<style>div { background-image: url(test.jpg); }</style>\n<style>div { background-image: url(http://example.com/test.jpg); }</style>\n<link rel=\"stylesheet\" href=\"http://example.com/test.css\">\n<script src=\"http://example.com/test.js\"></script>\n</head>\n<body>\n<a href=\"http://example.com/\">Home</a>\n<a href=\"http://example.com/deep/subdir/\">Test</a>\n<a href=\"http://example.com/deep/subdir/\">Test</a>\n<a href=\"http://example.com/subdir/\">Test</a>\n</body>\n</html>`\n  );\n});\n\ntest(\"Using the HTML base plugin strips extra path in full URL base (pathPrefix: /test/)\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-base/\", \"./test/stubs-base/_site\", {\n    pathPrefix: \"/test/\",\n\n    configPath: false,\n    config: function (eleventyConfig) {\n      eleventyConfig.setUseTemplateCache(false);\n      eleventyConfig.addPlugin(HtmlBasePlugin, {\n        baseHref: \"http://example.com/hello/\", // extra path will be stripped\n      });\n    },\n  });\n\n  await elev.initializeConfig();\n\n  elev.setIsVerbose(false);\n  elev.disableLogger();\n\n  let results = await elev.toJSON();\n  t.is(\n    getContentFor(results, \"/deep/index.html\"),\n    `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<meta name=\"description\" content=\"\">\n<title></title>\n<style>div { background-image: url(test.jpg); }</style>\n<style>div { background-image: url(http://example.com/test/test.jpg); }</style>\n<link rel=\"stylesheet\" href=\"http://example.com/test/test.css\">\n<script src=\"http://example.com/test/test.js\"></script>\n</head>\n<body>\n<a href=\"http://example.com/test/\">Home</a>\n<a href=\"http://example.com/test/deep/subdir/\">Test</a>\n<a href=\"http://example.com/test/deep/subdir/\">Test</a>\n<a href=\"http://example.com/test/subdir/\">Test</a>\n</body>\n</html>`\n  );\n});\n\ntest(\"Opt out of the transform with falsy extensions list\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-base/\", \"./test/stubs-base/_site\", {\n    pathPrefix: \"/test/\",\n\n    configPath: false,\n    config: function (eleventyConfig) {\n      eleventyConfig.setUseTemplateCache(false);\n      eleventyConfig.addPlugin(HtmlBasePlugin, {\n        extensions: false,\n      });\n    },\n  });\n\n  await elev.initializeConfig();\n\n  elev.setIsVerbose(false);\n  elev.disableLogger();\n\n  let results = await elev.toJSON();\n  t.is(\n    getContentFor(results, \"/deep/index.html\"),\n    `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<meta name=\"description\" content=\"\">\n<title></title>\n<style>div { background-image: url(test.jpg); }</style>\n<style>div { background-image: url(/test/test.jpg); }</style>\n<link rel=\"stylesheet\" href=\"/test.css\">\n<script src=\"/test.js\"></script>\n</head>\n<body>\n<a href=\"/\">Home</a>\n<a href=\"subdir/\">Test</a>\n<a href=\"./subdir/\">Test</a>\n<a href=\"../subdir/\">Test</a>\n</body>\n</html>`\n  );\n});\n\ntest(\"Base plugin with permalink: false, #2602\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-2602/\", \"./test/stubs-2602/_site\", {\n    pathPrefix: \"/test/\",\n\n    configPath: false,\n    config: function (eleventyConfig) {\n      eleventyConfig.setUseTemplateCache(false);\n      eleventyConfig.addPlugin(HtmlBasePlugin);\n    },\n  });\n\n  await elev.initializeConfig();\n\n  elev.setIsVerbose(false);\n  elev.disableLogger();\n\n  let results = await elev.toJSON();\n  t.is(\n    getContentFor(results, \"/deep/index.html\"),\n    `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<meta name=\"description\" content=\"\">\n<title></title>\n<style>div { background-image: url(test.jpg); }</style>\n<style>div { background-image: url(/test/test.jpg); }</style>\n<link rel=\"stylesheet\" href=\"/test/test.css\">\n<script src=\"/test/test.js\"></script>\n</head>\n<body>\n<a href=\"/test/\">Home</a>\n<a href=\"subdir/\">Test</a>\n<a href=\"../subdir/\">Test</a>\n</body>\n</html>`\n  );\n});\n\ntest(\"Using the HTML base plugin with pathPrefix: /test/ and transformed attributes are *not* case sensitive\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-base-case-sens/\", \"./test/stubs-base-case-sens/_site\", {\n    pathPrefix: \"/test/\",\n\n    configPath: false,\n    config: function (eleventyConfig) {\n      eleventyConfig.setUseTemplateCache(false);\n      eleventyConfig.addPlugin(HtmlBasePlugin);\n    },\n  });\n\n  await elev.initializeConfig();\n\n  elev.setIsVerbose(false);\n  elev.disableLogger();\n\n  let results = await elev.toJSON();\n  t.is(\n    getContentFor(results, \"/deep/index.html\"),\n    `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<meta name=\"description\" content=\"\">\n<title></title>\n<style>div { background-image: url(test.jpg); }</style>\n<style>div { background-image: url(/test/test.jpg); }</style>\n<link rel=\"stylesheet\" href=\"/test/test.css\">\n<script SrC=\"/test.js\"></script>\n</head>\n<body>\n<a hreF=\"/\">Home</a>\n<a HrEf=\"subdir/\">Test</a>\n<a href=\"../subdir/\">Test</a>\n</body>\n</html>`\n  );\n});\n\ntest(\"HTML base plugin only adds once (unique)\", async (t) => {\n  t.plan(2);\n  let elev = new Eleventy(\"./test/stubs-base/\", \"./test/stubs-base/_site\", {\n    configPath: false,\n    config: function (eleventyConfig) {\n      // Runs before defaultConfig.js\n      t.is(eleventyConfig.plugins.length, 0);\n      eleventyConfig.addPlugin(HtmlBasePlugin);\n      eleventyConfig.addPlugin(HtmlBasePlugin);\n      eleventyConfig.addPlugin(HtmlBasePlugin);\n      eleventyConfig.addPlugin(HtmlBasePlugin);\n      t.is(eleventyConfig.plugins.length, 1);\n    },\n  });\n  await elev.init();\n});\n\ntest(\"HTML base plugin can resolve by name\", async (t) => {\n  t.plan(2);\n  let elev = new Eleventy(\"./test/stubs-base/\", \"./test/stubs-base/_site\", {\n    configPath: false,\n    config: async function (eleventyConfig) {\n      // Runs before defaultConfig.js\n      t.is(eleventyConfig.plugins.length, 0);\n\n      let plugin = await eleventyConfig.resolvePlugin(\"@11ty/eleventy/html-base-plugin\");\n      eleventyConfig.addPlugin(plugin);\n\n      // does not add duplicate\n      eleventyConfig.addPlugin(plugin);\n\n      // does not add duplicate even with a different reference\n      eleventyConfig.addPlugin(HtmlBasePlugin);\n      eleventyConfig.addPlugin(HtmlBasePlugin);\n\n      t.is(eleventyConfig.plugins.length, 1);\n    },\n  });\n  await elev.init();\n});\n\ntest(\"Using recognizeNoValueAttribute for boolean attributes without quotes #2766\", async (t) => {\n  let elev = new Eleventy({\n    input: \"./test/stubs-virtual/\",\n    pathPrefix: \"/prefixed/\",\n    configPath: false,\n    config: function (eleventyConfig) {\n      eleventyConfig.setUseTemplateCache(false);\n      eleventyConfig.addTemplate(\"index.njk\", `---\npermalink: /deep/\n---\n<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"description\" content=\"\">\n<link rel=\"stylesheet\" crossorigin>\n<link rel=\"stylesheet\" href=\"/test.css\">\n</head>\n</html>`)\n      eleventyConfig.addPlugin(HtmlBasePlugin);\n    },\n  });\n\n  await elev.initializeConfig();\n\n  elev.setIsVerbose(false);\n  elev.disableLogger();\n\n  let results = await elev.toJSON();\n  t.is(\n    getContentFor(results, \"/deep/index.html\"),\n    `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"description\" content=\"\">\n<link rel=\"stylesheet\" crossorigin>\n<link rel=\"stylesheet\" href=\"/prefixed/test.css\">\n</head>\n</html>`\n  );\n});\n"
  },
  {
    "path": "test/HtmlRelativeCopyTest.js",
    "content": "import test from \"ava\";\nimport fs from \"node:fs\";\nimport { TemplatePath } from \"@11ty/eleventy-utils\";\nimport { globSync } from \"tinyglobby\";\n\nimport { TransformPlugin as InputPathToUrlTransformPlugin } from \"../src/Plugins/InputPathToUrl.js\";\nimport { default as HtmlBasePlugin } from \"../src/Plugins/HtmlBasePlugin.js\";\nimport Eleventy from \"../src/Eleventy.js\";\nimport { deleteDirectory } from \"./_testHelpers.js\";\n\ntest.after.always(\"Directory cleanup\", () => {\n  let dirs = globSync(\"./test/stubs-autocopy/_site*\", {\n    onlyDirectories: true,\n    expandDirectories: false,\n  });\n\n\tfor(let dir of dirs) {\n\t\tdeleteDirectory(dir);\n\t}\n})\n\ntest(\"Basic usage\", async (t) => {\n\tlet elev = new Eleventy(\"./test/stubs-autocopy/\", \"./test/stubs-autocopy/_site-basica\", {\n\t\tconfigPath: false,\n\t\tconfig: function (eleventyConfig) {\n\t\t\t// Node 24: workaround for re-using input directory (and not ignoring all output directories by default)\n\t\t\televentyConfig.ignores.add(\"./test/stubs-autocopy/_site*/**\");\n\n\t\t\televentyConfig.addPassthroughCopy(\"**/*.png\", {\n\t\t\t\tmode: \"html-relative\"\n\t\t\t})\n\n\t\t\televentyConfig.on(\"eleventy.passthrough\", copyMap => {\n\t\t\t\tt.deepEqual(copyMap, {\n\t\t\t\t\tmap: {\n\t\t\t\t\t\t\"/test/possum.png\": TemplatePath.normalizeOperatingSystemFilePath(\"test/stubs-autocopy/possum.png\")\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t});\n\n\t\t\televentyConfig.addTemplate(\"test.njk\", `<img src=\"possum.png\">`)\n\t\t},\n\t});\n\n\telev.disableLogger();\n\n\tlet [copy, templates] = await elev.write();\n\n\tt.is(copy.length, 1);\n\tt.is(templates.length, 1);\n\n\tt.deepEqual(templates[0], {\n\t\tinputPath: './test/stubs-autocopy/test.njk',\n\t\toutputPath: './test/stubs-autocopy/_site-basica/test/index.html',\n\t\turl: '/test/',\n\t\tcontent: '<img src=\"possum.png\">',\n\t\trawInput: '<img src=\"possum.png\">'\n\t});\n\n\tt.deepEqual(copy[0], {\n\t\tcount: 1,\n\t\tmap: {\n\t\t\t[TemplatePath.normalizeOperatingSystemFilePath(\"test/stubs-autocopy/possum.png\")]: TemplatePath.normalizeOperatingSystemFilePath(\"test/stubs-autocopy/_site-basica/test/possum.png\"),\n\t\t}\n\t});\n\n\tt.is(fs.existsSync(\"test/stubs-autocopy/_site-basica/test/possum.png\"), true);\n\tt.is(fs.existsSync(\"test/stubs-autocopy/_site-basica/test/index.html\"), true);\n});\n\ntest(\"More complex image path (parent dir)\", async (t) => {\n\tlet elev = new Eleventy(\"./test/stubs-autocopy/\", \"./test/stubs-autocopy/_site-basicb\", {\n\t\tconfigPath: false,\n\t\tconfig: function (eleventyConfig) {\n\t\t\t// Node 24: workaround for re-using input directory (and not ignoring all output directories by default)\n\t\t\televentyConfig.ignores.add(\"./test/stubs-autocopy/_site*/**\");\n\n\t\t\televentyConfig.addPassthroughCopy(\"**/*.png\", {\n\t\t\t\tmode: \"html-relative\"\n\t\t\t})\n\n\t\t\televentyConfig.on(\"eleventy.passthrough\", copyMap => {\n\t\t\t\tt.deepEqual(copyMap, {\n\t\t\t\t\tmap: {\n\t\t\t\t\t\t\"/stubs-img-transform/possum.png\": TemplatePath.normalizeOperatingSystemFilePath(\"test/stubs-img-transform/possum.png\")\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t});\n\n\t\t\televentyConfig.addTemplate(\"test.njk\", `<img src=\"../stubs-img-transform/possum.png\">`)\n\t\t},\n\t});\n\n\telev.disableLogger();\n\n\tlet [copy, templates] = await elev.write();\n\n\tt.is(copy.length, 1);\n\tt.is(templates.length, 1);\n\n\tt.deepEqual(templates[0], {\n\t\tinputPath: './test/stubs-autocopy/test.njk',\n\t\toutputPath: './test/stubs-autocopy/_site-basicb/test/index.html',\n\t\turl: '/test/',\n\t\tcontent: '<img src=\"../stubs-img-transform/possum.png\">',\n\t\trawInput: '<img src=\"../stubs-img-transform/possum.png\">'\n\t});\n\n\tt.deepEqual(copy[0], {\n\t\tcount: 1,\n\t\tmap: {\n\t\t\t// test/stubs-autocopy/test.njk => \"../stubs-img-transform/possum.png\"\n\t\t\t[TemplatePath.normalizeOperatingSystemFilePath(\"test/stubs-img-transform/possum.png\")]: TemplatePath.normalizeOperatingSystemFilePath(\"test/stubs-autocopy/_site-basicb/stubs-img-transform/possum.png\"),\n\t\t}\n\t});\n\n\tt.is(fs.existsSync(\"test/stubs-autocopy/_site-basicb/stubs-img-transform/possum.png\"), true);\n\tt.is(fs.existsSync(\"test/stubs-autocopy/_site-basicb/test/index.html\"), true);\n});\n\ntest(\"No matches\", async (t) => {\n\tlet elev = new Eleventy(\"./test/stubs-autocopy/\", \"./test/stubs-autocopy/_site2\", {\n\t\tconfigPath: false,\n\t\tconfig: function (eleventyConfig) {\n\t\t\t// Node 24: workaround for re-using input directory (and not ignoring all output directories by default)\n\t\t\televentyConfig.ignores.add(\"./test/stubs-autocopy/_site*/**\");\n\n\t\t\televentyConfig.addPassthroughCopy(\"**/*.jpeg\", {\n\t\t\t\tmode: \"html-relative\"\n\t\t\t})\n\n\t\t\televentyConfig.on(\"eleventy.passthrough\", copyMap => {\n\t\t\t\tt.deepEqual(copyMap, { map: {} })\n\t\t\t});\n\n\t\t\televentyConfig.addTemplate(\"test.njk\", `<img src=\"lol.lol\">`)\n\t\t},\n\t});\n\n\telev.disableLogger();\n\n\tlet [copy, templates] = await elev.write();\n\n\tt.is(copy.length, 0);\n\tt.is(templates.length, 1);\n\n\tt.is(fs.existsSync(\"test/stubs-autocopy/_site2/test/lol.lol\"), false);\n\tt.is(fs.existsSync(\"test/stubs-autocopy/_site2/test/index.html\"), true);\n});\n\ntest(\"Match but does not exist (throws error)\", async (t) => {\n\tlet elev = new Eleventy(\"./test/stubs-autocopy/\", \"./test/stubs-autocopy/_site3\", {\n\t\tconfigPath: false,\n\t\tconfig: function (eleventyConfig) {\n\t\t\televentyConfig.addPassthroughCopy(\"**/*.png\", {\n\t\t\t\tmode: \"html-relative\"\n\t\t\t});\n\n\t\t\televentyConfig.on(\"eleventy.passthrough\", copyMap => {\n\t\t\t\tt.deepEqual(copyMap, { map: {} })\n\t\t\t});\n\n\t\t\televentyConfig.addTemplate(\"test.njk\", `<img src=\"missing.png\">`)\n\t\t},\n\t});\n\n\telev.disableLogger();\n\n\tawait t.throwsAsync(async () => {\n\t\tawait elev.write();\n\t}, {\n\t\tmessage: `Having trouble writing to \"./test/stubs-autocopy/_site3/test/index.html\" from \"./test/stubs-autocopy/test.njk\"`\n\t});\n\n\tt.is(fs.existsSync(\"test/stubs-autocopy/_site3/test/index.html\"), false);\n});\n\ntest(\"Match but does not exist (no error, using `failOnError: false`)\", async (t) => {\n\tlet elev = new Eleventy(\"./test/stubs-autocopy/\", \"./test/stubs-autocopy/_site4\", {\n\t\tconfigPath: false,\n\t\tconfig: function (eleventyConfig) {\n\t\t\t// Node 24: workaround for re-using input directory (and not ignoring all output directories by default)\n\t\t\televentyConfig.ignores.add(\"./test/stubs-autocopy/_site*/**\");\n\n\t\t\televentyConfig.addPassthroughCopy(\"**/*.png\", {\n\t\t\t\tmode: \"html-relative\",\n\t\t\t\tfailOnError: false,\n\t\t\t})\n\n\t\t\televentyConfig.on(\"eleventy.passthrough\", copyMap => {\n\t\t\t\tt.deepEqual(copyMap, { map: {} })\n\t\t\t});\n\n\t\t\televentyConfig.addTemplate(\"test.njk\", `<img src=\"missing.png\">`)\n\t\t},\n\t});\n\n\telev.disableLogger();\n\n\tlet [copy, templates] = await elev.write();\n\n\tt.is(copy.length, 0);\n\tt.is(templates.length, 1);\n\n\tt.is(fs.existsSync(\"test/stubs-autocopy/_site4/test/missing.png\"), false);\n\tt.is(fs.existsSync(\"test/stubs-autocopy/_site4/test/index.html\"), true);\n});\n\ntest(\"Copying dotfiles are not allowed\", async (t) => {\n\tlet elev = new Eleventy(\"./test/stubs-autocopy/\", \"./test/stubs-autocopy/_site5\", {\n\t\tconfigPath: false,\n\t\tconfig: function (eleventyConfig) {\n\t\t\t// Node 24: workaround for re-using input directory (and not ignoring all output directories by default)\n\t\t\televentyConfig.ignores.add(\"./test/stubs-autocopy/_site*/**\");\n\n\t\t\t// WARNING: don’t do this\n\t\t\televentyConfig.addPassthroughCopy(\"**/*\", {\n\t\t\t\tmode: \"html-relative\",\n\t\t\t\tcopyOptions: {\n\t\t\t\t\t// debug: true,\n\t\t\t\t}\n\t\t\t});\n\n\t\t\televentyConfig.on(\"eleventy.passthrough\", copyMap => {\n\t\t\t\tt.deepEqual(copyMap, { map: {} })\n\t\t\t});\n\n\t\t\televentyConfig.addTemplate(\"test.njk\", `<img src=\".gitkeep\">`)\n\t\t},\n\t});\n\n\telev.disableLogger();\n\n\tlet [copy, templates] = await elev.write();\n\n\tt.is(copy.length, 1);\n\tt.is(copy[0].count, 0);\n\tt.is(templates.length, 1);\n\n\tt.is(fs.existsSync(\"test/stubs-autocopy/_site5/.gitkeep\"), false);\n\tt.is(fs.existsSync(\"test/stubs-autocopy/_site5/test/.gitkeep\"), false);\n\tt.is(fs.existsSync(\"test/stubs-autocopy/_site5/test/index.html\"), true);\n});\n\ntest(\"Using with InputPathToUrl plugin\", async (t) => {\n\tlet elev = new Eleventy(\"./test/stubs-autocopy/\", \"./test/stubs-autocopy/_site6\", {\n\t\tconfigPath: false,\n\t\tconfig: function (eleventyConfig) {\n\t\t\t// Node 24: workaround for re-using input directory (and not ignoring all output directories by default)\n\t\t\televentyConfig.ignores.add(\"./test/stubs-autocopy/_site*/**\");\n\n\t\t\t// order of addPlugin shouldn’t matter here\n\t\t\televentyConfig.addPassthroughCopy(\"**/*.{html,njk}\", {\n\t\t\t\tmode: \"html-relative\"\n\t\t\t});\n\n\t\t\televentyConfig.addPlugin(InputPathToUrlTransformPlugin);\n\n\t\t\televentyConfig.on(\"eleventy.passthrough\", copyMap => {\n\t\t\t\tt.deepEqual(copyMap, { map: {} })\n\t\t\t});\n\n\t\t\televentyConfig.addTemplate(\"test1.njk\", `Test 1`)\n\t\t\televentyConfig.addTemplate(\"test2.njk\", `<a href=\"test1.njk\">Test 2</a>`)\n\t\t},\n\t});\n\n\telev.disableLogger();\n\n\tlet [copy, templates] = await elev.write();\n\n\tt.is(copy.length, 0);\n\tt.is(templates.length, 2);\n\n\tt.is(templates.filter(entry => entry.url.endsWith(\"/test2/\"))[0].content, `<a href=\"/test1/\">Test 2</a>`);\n\n\tt.is(fs.existsSync(\"test/stubs-autocopy/_site6/test2/test1.njk\"), false);\n\tt.is(fs.existsSync(\"test/stubs-autocopy/_site6/test2/index.html\"), true);\n});\n\ntest(\"Using with InputPathToUrl plugin (reverse addPlugin order)\", async (t) => {\n\tlet elev = new Eleventy(\"./test/stubs-autocopy/\", \"./test/stubs-autocopy/_site7\", {\n\t\tconfigPath: false,\n\t\tconfig: function (eleventyConfig) {\n\t\t\t// Node 24: workaround for re-using input directory (and not ignoring all output directories by default)\n\t\t\televentyConfig.ignores.add(\"./test/stubs-autocopy/_site*/**\");\n\n\t\t\t// order of addPlugin shouldn’t matter here\n\t\t\televentyConfig.addPlugin(InputPathToUrlTransformPlugin);\n\n\t\t\televentyConfig.addPassthroughCopy(\"**/*.{html,njk}\", {\n\t\t\t\tmode: \"html-relative\"\n\t\t\t});\n\n\t\t\televentyConfig.on(\"eleventy.passthrough\", copyMap => {\n\t\t\t\tt.deepEqual(copyMap, { map: {} })\n\t\t\t});\n\n\t\t\televentyConfig.addTemplate(\"test1.njk\", `Test 1`)\n\t\t\televentyConfig.addTemplate(\"test2.njk\", `<a href=\"test1.njk\">Test 2</a>`)\n\t\t},\n\t});\n\n\telev.disableLogger();\n\n\tlet [copy, templates] = await elev.write();\n\n\tt.is(copy.length, 0);\n\tt.is(templates.length, 2);\n\tt.is(templates.filter(entry => entry.url.endsWith(\"/test2/\"))[0].content, `<a href=\"/test1/\">Test 2</a>`);\n\n\tt.is(fs.existsSync(\"test/stubs-autocopy/_site7/test2/test1.njk\"), false);\n\tt.is(fs.existsSync(\"test/stubs-autocopy/_site7/test2/index.html\"), true);\n});\n\ntest(\"Use with HtmlBasePlugin usage\", async (t) => {\n\tlet elev = new Eleventy(\"./test/stubs-autocopy/\", \"./test/stubs-autocopy/_site8a\", {\n\t\tconfigPath: false,\n\t\tpathPrefix: \"yolo\",\n\t\tconfig: function (eleventyConfig) {\n\t\t\t// Node 24: workaround for re-using input directory (and not ignoring all output directories by default)\n\t\t\televentyConfig.ignores.add(\"./test/stubs-autocopy/_site*/**\");\n\n\t\t\televentyConfig.addPlugin(HtmlBasePlugin);\n\t\t\televentyConfig.addPassthroughCopy(\"**/*.png\", {\n\t\t\t\tmode: \"html-relative\"\n\t\t\t});\n\n\t\t\televentyConfig.on(\"eleventy.passthrough\", copyMap => {\n\t\t\t\tt.deepEqual(copyMap, {\n\t\t\t\t\tmap: {\n\t\t\t\t\t\t\"/test/possum.png\": TemplatePath.normalizeOperatingSystemFilePath(\"test/stubs-autocopy/possum.png\")\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t});\n\n\t\t\televentyConfig.addTemplate(\"test.njk\", `<img src=\"possum.png\"><img src=\"/test/possum.png\">`)\n\t\t},\n\t});\n\n\telev.disableLogger();\n\n\tlet [copy, templates] = await elev.write();\n\n\tt.is(copy.length, 1);\n\tt.is(templates.length, 1);\n\n\tt.deepEqual(templates[0], {\n\t\tinputPath: './test/stubs-autocopy/test.njk',\n\t\toutputPath: './test/stubs-autocopy/_site8a/test/index.html',\n\t\turl: '/test/',\n\t\tcontent: '<img src=\"possum.png\"><img src=\"/yolo/test/possum.png\">',\n\t\trawInput: '<img src=\"possum.png\"><img src=\"/test/possum.png\">'\n\t});\n\n\tt.deepEqual(copy[0], {\n\t\tcount: 1,\n\t\tmap: {\n\t\t\t[TemplatePath.normalizeOperatingSystemFilePath(\"test/stubs-autocopy/possum.png\")]: TemplatePath.normalizeOperatingSystemFilePath(\"test/stubs-autocopy/_site8a/test/possum.png\"),\n\t\t}\n\t});\n\n\tt.is(fs.existsSync(\"test/stubs-autocopy/_site8a/test/possum.png\"), true);\n\tt.is(fs.existsSync(\"test/stubs-autocopy/_site8a/test/index.html\"), true);\n});\n\ntest(\"Using with InputPathToUrl plugin and HtmlBasePlugin\", async (t) => {\n\tlet elev = new Eleventy(\"./test/stubs-autocopy/\", \"./test/stubs-autocopy/_site8b\", {\n\t\tconfigPath: false,\n\t\tpathPrefix: \"yolo\",\n\t\tconfig: function (eleventyConfig) {\n\t\t\t// Node 24: workaround for re-using input directory (and not ignoring all output directories by default)\n\t\t\televentyConfig.ignores.add(\"./test/stubs-autocopy/_site*/**\");\n\n\t\t\t// order of addPlugin shouldn’t matter here\n\t\t\televentyConfig.addPassthroughCopy(\"**/*.{html,njk}\", {\n\t\t\t\tmode: \"html-relative\"\n\t\t\t});\n\n\t\t\televentyConfig.addPlugin(InputPathToUrlTransformPlugin);\n\t\t\televentyConfig.addPlugin(HtmlBasePlugin);\n\n\t\t\televentyConfig.on(\"eleventy.passthrough\", copyMap => {\n\t\t\t\tt.deepEqual(copyMap, { map: {} })\n\t\t\t});\n\n\t\t\televentyConfig.addTemplate(\"test1.njk\", `Test 1`)\n\t\t\televentyConfig.addTemplate(\"test2.njk\", `<a href=\"test1.njk\">Test 2</a>`)\n\t\t},\n\t});\n\n\telev.disableLogger();\n\n\tlet [copy, templates] = await elev.write();\n\n\tt.is(copy.length, 0);\n\tt.is(templates.length, 2);\n\n\tt.is(templates.filter(entry => entry.url.endsWith(\"/test2/\"))[0].content, `<a href=\"/yolo/test1/\">Test 2</a>`);\n\n\tt.is(fs.existsSync(\"test/stubs-autocopy/_site8b/test2/test1.njk\"), false);\n\tt.is(fs.existsSync(\"test/stubs-autocopy/_site8b/test2/index.html\"), true);\n});\n\ntest(\"Multiple addPlugin calls (use both globs)\", async (t) => {\n\tlet elev = new Eleventy(\"./test/stubs-autocopy/\", \"./test/stubs-autocopy/_site9\", {\n\t\tconfigPath: false,\n\t\tconfig: function (eleventyConfig) {\n\t\t\t// Node 24: workaround for re-using input directory (and not ignoring all output directories by default)\n\t\t\televentyConfig.ignores.add(\"./test/stubs-autocopy/_site*/**\");\n\n\t\t\televentyConfig.addPassthroughCopy(\"**/*.jpg\", {\n\t\t\t\tmode: \"html-relative\"\n\t\t\t});\n\t\t\televentyConfig.addPassthroughCopy(\"**/*.png\", {\n\t\t\t\tmode: \"html-relative\"\n\t\t\t});\n\n\t\t\televentyConfig.on(\"eleventy.passthrough\", copyMap => {\n\t\t\t\tt.deepEqual(copyMap, {\n\t\t\t\t\tmap: {\n\t\t\t\t\t\t\"/test/possum.jpg\": TemplatePath.normalizeOperatingSystemFilePath(\"test/stubs-autocopy/possum.jpg\"),\n\t\t\t\t\t\t\"/test/possum.png\": TemplatePath.normalizeOperatingSystemFilePath(\"test/stubs-autocopy/possum.png\"),\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t});\n\n\t\t\televentyConfig.addTemplate(\"test.njk\", `<img src=\"possum.png\"><img src=\"possum.jpg\">`)\n\t\t},\n\t});\n\n\telev.disableLogger();\n\n\tlet [copy, templates] = await elev.write();\n\n\tt.is(copy.length, 2);\n\tt.is(templates.length, 1);\n\n\tt.deepEqual(templates[0], {\n\t\tinputPath: './test/stubs-autocopy/test.njk',\n\t\toutputPath: './test/stubs-autocopy/_site9/test/index.html',\n\t\turl: '/test/',\n\t\tcontent: '<img src=\"possum.png\"><img src=\"possum.jpg\">',\n\t\trawInput: '<img src=\"possum.png\"><img src=\"possum.jpg\">'\n\t});\n\n\tt.deepEqual(copy[0], {\n\t\tcount: 1,\n\t\tmap: {\n\t\t\t[TemplatePath.normalizeOperatingSystemFilePath(\"test/stubs-autocopy/possum.png\")]: TemplatePath.normalizeOperatingSystemFilePath(\"test/stubs-autocopy/_site9/test/possum.png\"),\n\t\t}\n\t});\n\tt.deepEqual(copy[1], {\n\t\tcount: 1,\n\t\tmap: {\n\t\t\t[TemplatePath.normalizeOperatingSystemFilePath(\"test/stubs-autocopy/possum.jpg\")]: TemplatePath.normalizeOperatingSystemFilePath(\"test/stubs-autocopy/_site9/test/possum.jpg\"),\n\t\t}\n\t});\n\n\tt.is(fs.existsSync(\"test/stubs-autocopy/_site9/test/possum.jpg\"), true);\n\tt.is(fs.existsSync(\"test/stubs-autocopy/_site9/test/possum.png\"), true);\n\tt.is(fs.existsSync(\"test/stubs-autocopy/_site9/test/index.html\"), true);\n});\n\ntest(\"Array of globs\", async (t) => {\n\tlet elev = new Eleventy(\"./test/stubs-autocopy/\", \"./test/stubs-autocopy/_site10\", {\n\t\tconfigPath: false,\n\t\tconfig: function (eleventyConfig) {\n\t\t\t// Node 24: workaround for re-using input directory (and not ignoring all output directories by default)\n\t\t\televentyConfig.ignores.add(\"./test/stubs-autocopy/_site*/**\");\n\n\t\t\televentyConfig.addPassthroughCopy([\"**/*.jpg\", \"**/*.png\"], {\n\t\t\t\tmode: \"html-relative\"\n\t\t\t});\n\n\t\t\televentyConfig.on(\"eleventy.passthrough\", copyMap => {\n\t\t\t\tt.deepEqual(copyMap, {\n\t\t\t\t\tmap: {\n\t\t\t\t\t\t\"/test/possum.jpg\": TemplatePath.normalizeOperatingSystemFilePath(\"test/stubs-autocopy/possum.jpg\"),\n\t\t\t\t\t\t\"/test/possum.png\": TemplatePath.normalizeOperatingSystemFilePath(\"test/stubs-autocopy/possum.png\"),\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t});\n\n\t\t\televentyConfig.addTemplate(\"test.njk\", `<img src=\"possum.png\"><img src=\"possum.jpg\">`)\n\t\t},\n\t});\n\n\telev.disableLogger();\n\n\tlet [copy, templates] = await elev.write();\n\n\tt.is(copy.length, 2);\n\tt.is(templates.length, 1);\n\n\tt.deepEqual(templates[0], {\n\t\tinputPath: './test/stubs-autocopy/test.njk',\n\t\toutputPath: './test/stubs-autocopy/_site10/test/index.html',\n\t\turl: '/test/',\n\t\tcontent: '<img src=\"possum.png\"><img src=\"possum.jpg\">',\n\t\trawInput: '<img src=\"possum.png\"><img src=\"possum.jpg\">'\n\t});\n\n\tt.deepEqual(copy[0], {\n\t\tcount: 1,\n\t\tmap: {\n\t\t\t[TemplatePath.normalizeOperatingSystemFilePath(\"test/stubs-autocopy/possum.png\")]: TemplatePath.normalizeOperatingSystemFilePath(\"test/stubs-autocopy/_site10/test/possum.png\"),\n\t\t}\n\t});\n\tt.deepEqual(copy[1], {\n\t\tcount: 1,\n\t\tmap: {\n\t\t\t[TemplatePath.normalizeOperatingSystemFilePath(\"test/stubs-autocopy/possum.jpg\")]: TemplatePath.normalizeOperatingSystemFilePath(\"test/stubs-autocopy/_site10/test/possum.jpg\"),\n\t\t}\n\t});\n\n\tt.is(fs.existsSync(\"test/stubs-autocopy/_site10/test/possum.jpg\"), true);\n\tt.is(fs.existsSync(\"test/stubs-autocopy/_site10/test/possum.png\"), true);\n\tt.is(fs.existsSync(\"test/stubs-autocopy/_site10/test/index.html\"), true);\n});\n\ntest(\"overwrite: false\", async (t) => {\n\tfs.mkdirSync(\"./test/stubs-autocopy/_site11/test/\", { recursive: true })\n\tfs.copyFileSync(\"./test/stubs-autocopy/possum.png\", \"./test/stubs-autocopy/_site11/test/possum.png\");\n\n\tlet elev = new Eleventy(\"./test/stubs-autocopy/\", \"./test/stubs-autocopy/_site11\", {\n\t\tconfigPath: false,\n\t\tconfig: function (eleventyConfig) {\n\t\t\t// Node 24: workaround for re-using input directory (and not ignoring all output directories by default)\n\t\t\televentyConfig.ignores.add(\"./test/stubs-autocopy/_site*/**\");\n\n\t\t\televentyConfig.addPassthroughCopy(\"**/*.png\", {\n\t\t\t\tmode: \"html-relative\",\n\t\t\t\tcopyOptions: {\n\t\t\t\t\toverwrite: false,\n\t\t\t\t}\n\t\t\t});\n\n\t\t\televentyConfig.on(\"eleventy.passthrough\", copyMap => {\n\t\t\t\tt.deepEqual(copyMap, {\n\t\t\t\t\tmap: {}\n\t\t\t\t})\n\t\t\t});\n\n\t\t\televentyConfig.addTemplate(\"test.njk\", `<img src=\"possum.png\">`)\n\t\t},\n\t});\n\n\telev.disableLogger();\n\n\tlet [copy, templates] = await elev.write();\n\n\tt.is(copy.length, 1);\n\tt.is(templates.length, 1);\n\n\tt.deepEqual(templates[0], {\n\t\tinputPath: './test/stubs-autocopy/test.njk',\n\t\toutputPath: './test/stubs-autocopy/_site11/test/index.html',\n\t\turl: '/test/',\n\t\tcontent: '<img src=\"possum.png\">',\n\t\trawInput: '<img src=\"possum.png\">'\n\t});\n\n\tt.deepEqual(copy[0], {\n\t\tcount: 0,\n\t\tmap: {}\n\t});\n\n\tt.is(fs.existsSync(\"test/stubs-autocopy/_site11/test/possum.png\"), true);\n\tt.is(fs.existsSync(\"test/stubs-autocopy/_site11/test/index.html\"), true);\n});\n\ntest(\"Input -> output remapping not yet supported (throws error)\", async (t) => {\n\tlet elev = new Eleventy(\"./test/stubs-autocopy/\", \"./test/stubs-autocopy/_site12\", {\n\t\tconfigPath: false,\n\t\tconfig: function (eleventyConfig) {\n\t\t\t// Node 24: workaround for re-using input directory (and not ignoring all output directories by default)\n\t\t\televentyConfig.ignores.add(\"./test/stubs-autocopy/_site*/**\");\n\n\t\t\t// not yet supported\n\t\t\televentyConfig.addPassthroughCopy({\"**/*.png\": \"yo\"}, {\n\t\t\t\tmode: \"html-relative\"\n\t\t\t});\n\n\t\t\televentyConfig.on(\"eleventy.passthrough\", copyMap => {\n\t\t\t\tt.deepEqual(copyMap, { map: {} })\n\t\t\t});\n\n\t\t\televentyConfig.addTemplate(\"test.njk\", `<img src=\"missing.png\">`)\n\t\t},\n\t});\n\n\telev.disableLogger();\n\n\tawait t.throwsAsync(async () => {\n\t\tawait elev.write();\n\t}, {\n\t\tmessage: `mode: 'html-relative' does not yet support passthrough copy objects (input -> output mapping). Use a string glob or an Array of string globs.`\n\t});\n\n\tt.is(fs.existsSync(\"test/stubs-autocopy/_site12/test/index.html\"), false);\n});\n\ntest(\"Invalid copy mode throws error\", async (t) => {\n\tlet elev = new Eleventy(\"./test/stubs-autocopy/\", \"./test/stubs-autocopy/_site13\", {\n\t\tconfigPath: false,\n\t\tconfig: function (eleventyConfig) {\n\t\t\t// Node 24: workaround for re-using input directory (and not ignoring all output directories by default)\n\t\t\televentyConfig.ignores.add(\"./test/stubs-autocopy/_site*/**\");\n\n\t\t\t// not yet supported\n\t\t\televentyConfig.addPassthroughCopy({\"**/*.png\": \"yo\"}, {\n\t\t\t\tmode: \"throw-an-error\"\n\t\t\t});\n\t\t},\n\t});\n\n\telev.disableLogger();\n\n\tawait t.throwsAsync(async () => {\n\t\tawait elev.write();\n\t}, {\n\t\tmessage: `Invalid \\`mode\\` option for \\`addPassthroughCopy\\`. Received: 'throw-an-error'`\n\t});\n\n\tt.is(fs.existsSync(\"test/stubs-autocopy/_site13/test/index.html\"), false);\n});\n"
  },
  {
    "path": "test/I18nPluginTest.js",
    "content": "import test from \"ava\";\nimport { Comparator, LangUtils, default as I18nPlugin } from \"../src/Plugins/I18nPlugin.js\";\nimport Eleventy from \"../src/Eleventy.js\";\nimport { normalizeNewLines } from \"./Util/normalizeNewLines.js\";\n\ntest(\"Comparator.isLangCode\", (t) => {\n  t.is(Comparator.isLangCode(null), false);\n  t.is(Comparator.isLangCode(undefined), false);\n\n  t.is(Comparator.isLangCode(\"en\"), true);\n  t.is(Comparator.isLangCode(\"en-us\"), true);\n\n  t.is(Comparator.isLangCode(\"dee\"), false);\n  t.is(Comparator.isLangCode(\"en_us\"), false);\n  t.is(Comparator.isLangCode(\"d\"), false);\n  t.is(Comparator.isLangCode(\"deed\"), false);\n  t.is(Comparator.isLangCode(\"deede\"), false);\n  t.is(Comparator.isLangCode(\"deedee\"), false);\n});\n\ntest(\"LangUtils.swapLanguageCode\", (t) => {\n  t.is(LangUtils.swapLanguageCode(\"/\"), \"/\"); // skip\n  t.is(LangUtils.swapLanguageCode(\"/\", \"en\"), \"/\"); // skip\n  t.is(LangUtils.swapLanguageCode(\"/es/\", \"en\"), \"/en/\");\n  t.is(LangUtils.swapLanguageCode(\"/es/\", \"not\"), \"/es/\"); // skip\n  t.is(LangUtils.swapLanguageCode(\"/not-a-lang/\", \"en\"), \"/not-a-lang/\"); // skip\n  t.is(LangUtils.swapLanguageCode(\"/es/es/es/\", \"en\"), \"/en/es/es/\"); // first only\n});\n\ntest(\"contentMap Event from Eleventy\", async (t) => {\n  t.plan(4);\n  let elev = new Eleventy(\"./test/stubs-i18n/\", \"./test/stubs-i18n/_site\", {\n    config: function (eleventyConfig) {\n      eleventyConfig.addPlugin(I18nPlugin, {\n        defaultLanguage: \"en\",\n        errorMode: \"allow-fallback\",\n      });\n\n      eleventyConfig.on(\"eleventy.contentMap\", (maps) => {\n        t.truthy(maps);\n\n        // if future maps are added, they should be tested here\n        t.is(Object.keys(maps).length, 2);\n        t.deepEqual(maps.urlToInputPath, {\n          \"/en/\": {\n\t\t\t\t\t\tinputPath: \"./test/stubs-i18n/en/index.liquid\",\n\t\t\t\t\t\tgroupNumber: 0\n\t\t\t\t\t},\n          \"/en-us/\": {\n\t\t\t\t\t\tinputPath: \"./test/stubs-i18n/en-us/index.11ty.cjs\",\n\t\t\t\t\t\tgroupNumber: 0,\n\t\t\t\t\t},\n          \"/es/\": {\n\t\t\t\t\t\tinputPath: \"./test/stubs-i18n/es/index.njk\",\n\t\t\t\t\t\tgroupNumber: 0\n\t\t\t\t\t},\n          \"/non-lang-file/\": {\n\t\t\t\t\t\tinputPath: \"./test/stubs-i18n/non-lang-file.njk\",\n\t\t\t\t\t\tgroupNumber: 0\n\t\t\t\t\t},\n        });\n\n        t.deepEqual(maps.inputPathToUrl, {\n          \"./test/stubs-i18n/en/index.liquid\": [\"/en/\"],\n          \"./test/stubs-i18n/en-us/index.11ty.cjs\": [\"/en-us/\"],\n          \"./test/stubs-i18n/es/index.njk\": [\"/es/\"],\n          \"./test/stubs-i18n/non-lang-file.njk\": [\"/non-lang-file/\"],\n        });\n      });\n    },\n  });\n\n  await elev.toJSON();\n});\n\nfunction getContentFor(results, filename) {\n  let content = results.filter((entry) => entry.inputPath.endsWith(filename))[0].content;\n  return normalizeNewLines(content.trim());\n}\n\ntest(\"errorMode default (strict)\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-i18n/\", \"./test/stubs-i18n/_site\", {\n    quietMode: true,\n    config: function (eleventyConfig) {\n      eleventyConfig.addPlugin(I18nPlugin, {\n        _test: \"this is from errorMode default (strict)\",\n        defaultLanguage: \"en\",\n        // errorMode: \"allow-fallback\"\n      });\n    },\n  });\n\n  // TODO get rid of these?\n  await elev.initializeConfig();\n  elev.setIsVerbose(false);\n  elev.disableLogger();\n\n  await t.throwsAsync(async () => {\n    await elev.toJSON();\n  });\n});\n\ntest(\"locale_url and locale_links Filters\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-i18n/\", \"./test/stubs-i18n/_site\", {\n    config: function (eleventyConfig) {\n      eleventyConfig.addPlugin(I18nPlugin, {\n        _test: \"this is from locale_url and locale_links Filters\",\n        defaultLanguage: \"en\",\n        errorMode: \"allow-fallback\",\n      });\n    },\n  });\n\n  let results = await elev.toJSON();\n  t.is(\n    getContentFor(results, \"/non-lang-file.njk\"),\n    `/en/\n/en-us/\n/non-lang-file/\n[]\n[]\nen`\n  );\n\n  t.is(\n    getContentFor(results, \"/es/index.njk\"),\n    `/es/\n/es/\n/es/\n/en-us/\n/non-lang-file/\n[{\"url\":\"/en/\",\"lang\":\"en\",\"label\":\"English\"},{\"url\":\"/en-us/\",\"lang\":\"en-us\",\"label\":\"English\"}]\n[{\"url\":\"/en/\",\"lang\":\"en\",\"label\":\"English\"},{\"url\":\"/en-us/\",\"lang\":\"en-us\",\"label\":\"English\"}]\nes`\n  );\n\n  t.is(\n    getContentFor(results, \"/en/index.liquid\"),\n    `/en/\n/en/\n/en/\n/en-us/\n/non-lang-file/\n[{\"url\":\"/en-us/\",\"lang\":\"en-us\",\"label\":\"English\"},{\"url\":\"/es/\",\"lang\":\"es\",\"label\":\"Español\"}]\n[{\"url\":\"/en-us/\",\"lang\":\"en-us\",\"label\":\"English\"},{\"url\":\"/es/\",\"lang\":\"es\",\"label\":\"Español\"}]\nen`\n  );\n\n  t.is(\n    getContentFor(results, \"/en-us/index.11ty.cjs\"),\n    `/en-us/\n/en-us/\n/en-us/\n/es/\n/non-lang-file/\n[{\"url\":\"/en/\",\"lang\":\"en\",\"label\":\"English\"},{\"url\":\"/es/\",\"lang\":\"es\",\"label\":\"Español\"}]\n[{\"url\":\"/en/\",\"lang\":\"en\",\"label\":\"English\"},{\"url\":\"/es/\",\"lang\":\"es\",\"label\":\"Español\"}]\nen-us`\n  );\n});\n"
  },
  {
    "path": "test/IdAttributePluginTest.js",
    "content": "import test from \"ava\";\n\nimport { IdAttributePlugin } from \"../src/Plugins/IdAttributePlugin.js\";\nimport Eleventy from \"../src/Eleventy.js\";\n\ntest(\"Using the IdAttribute plugin #3356\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", \"./test/stubs-virtual/_site\", {\n    config: function (eleventyConfig) {\n      eleventyConfig.addPlugin(IdAttributePlugin);\n\n      eleventyConfig.addTemplate(\"test.njk\", `<h1>This is a heading</h1><h2 id=\"already\">This is another heading</h2><h2>This is another heading</h2><h3>This is another heading</h3>`, {});\n    },\n  });\n\n  let results = await elev.toJSON();\n\tt.is(results[0].content, `<h1 id=\"this-is-a-heading\">This is a heading</h1><h2 id=\"already\">This is another heading</h2><h2 id=\"this-is-another-heading\">This is another heading</h2><h3 id=\"this-is-another-heading-2\">This is another heading</h3>`);\n});\n\ntest(\"Using the IdAttribute plugin, ignore attribute #3356\", async (t) => {\n  let elev = new Eleventy({\n    input: \"./test/stubs-3356/\",\n    // configPath: false,\n    config: function (eleventyConfig) {\n      eleventyConfig.addPlugin(IdAttributePlugin);\n\n      eleventyConfig.addTemplate(\"test.njk\", `<h1>This is a heading</h1><h2 id=\"already\">This is another heading</h2><h2>This is another heading</h2><h3>This is another <span eleventy:id-ignore>heading</span></h3>`, {});\n    },\n  });\n\n  let results = await elev.toJSON();\n\tt.is(results[0].content, `<h1 id=\"this-is-a-heading\">This is a heading</h1><h2 id=\"already\">This is another heading</h2><h2 id=\"this-is-another-heading\">This is another heading</h2><h3 id=\"this-is-another\">This is another <span>heading</span></h3>`);\n});\n\ntest(\"Using the IdAttribute plugin with escaped quoted text\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", \"./test/stubs-virtual/_site\", {\n    config: function (eleventyConfig) {\n      eleventyConfig.addPlugin(IdAttributePlugin);\n\n      eleventyConfig.addTemplate(\"test.md\", `# This is a \\`\"heading\"\\``, {});\n    },\n  });\n\n  let results = await elev.toJSON();\n\tt.is(results[0].content.trim(), `<h1 id=\"this-is-a-heading\">This is a <code>&quot;heading&quot;</code></h1>`);\n});\n\ntest(\"Issue #3424, id attribute conflicts (id attribute supplied first)\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", \"./test/stubs-virtual/_site\", {\n    config: function (eleventyConfig) {\n      eleventyConfig.addPlugin(IdAttributePlugin);\n\n      eleventyConfig.addTemplate(\"test.html\", `<div id=\"testing\"></div><h1>Testing</h1>`, {});\n    },\n  });\n\n  let results = await elev.toJSON();\n\tt.is(results[0].content.trim(), `<div id=\"testing\"></div><h1 id=\"testing-2\">Testing</h1>`);\n});\n\ntest(\"Issue #3424, id attribute conflicts (id attribute supplied last)\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", \"./test/stubs-virtual/_site\", {\n    config: function (eleventyConfig) {\n      eleventyConfig.addPlugin(IdAttributePlugin);\n\n      eleventyConfig.addTemplate(\"test.html\", `<h1>Testing</h1><div id=\"testing\"></div>`, {});\n    },\n  });\n\n  let results = await elev.toJSON();\n\tt.is(results[0].content.trim(), `<h1 id=\"testing-2\">Testing</h1><div id=\"testing\"></div>`);\n});\n\ntest(\"Issue #3424, id attribute conflicts (hard coded id conflicts)\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", \"./test/stubs-virtual/_site\", {\n    config: function (eleventyConfig) {\n      eleventyConfig.addPlugin(IdAttributePlugin);\n\n      eleventyConfig.addTemplate(\"test.html\", `<h1>Testing</h1><h1 id=\"testing\">Testing</h1>`, {});\n    },\n  });\n\n  let results = await elev.toJSON();\n\tt.is(results[0].content.trim(), `<h1 id=\"testing-2\">Testing</h1><h1 id=\"testing\">Testing</h1>`);\n});\n\ntest(\"Issue #3424, id attribute conflicts (three deep, hard coded id conflicts)\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", \"./test/stubs-virtual/_site\", {\n    config: function (eleventyConfig) {\n      eleventyConfig.addPlugin(IdAttributePlugin);\n\n      eleventyConfig.addTemplate(\"test.html\", `<h1>Testing</h1><h1>Testing</h1><h1 id=\"testing\">Testing</h1>`, {});\n    },\n  });\n\n  let results = await elev.toJSON();\n\tt.is(results[0].content.trim(), `<h1 id=\"testing-3\">Testing</h1><h1 id=\"testing-2\">Testing</h1><h1 id=\"testing\">Testing</h1>`);\n});\n\ntest(\"Issue #3424, id attribute conflicts (four deep, hard coded id conflicts)\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", \"./test/stubs-virtual/_site\", {\n    config: function (eleventyConfig) {\n      eleventyConfig.addPlugin(IdAttributePlugin);\n\n      eleventyConfig.addTemplate(\"test.html\", `<h1>Testing</h1><h1>Testing</h1><h1>Testing</h1><h1 id=\"testing\">Testing</h1>`, {});\n    },\n  });\n\n  let results = await elev.toJSON();\n\tt.is(results[0].content.trim(), `<h1 id=\"testing-4\">Testing</h1><h1 id=\"testing-2\">Testing</h1><h1 id=\"testing-3\">Testing</h1><h1 id=\"testing\">Testing</h1>`);\n});\n\ntest(\"Issue #3424, id attribute conflicts (five deep, hard coded id conflicts)\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", \"./test/stubs-virtual/_site\", {\n    config: function (eleventyConfig) {\n      eleventyConfig.addPlugin(IdAttributePlugin);\n\n      eleventyConfig.addTemplate(\"test.html\", `<h1>Testing</h1><h1>Testing</h1><h1>Testing</h1><h1>Testing</h1><h1 id=\"testing\">Testing</h1>`, {});\n    },\n  });\n\n  let results = await elev.toJSON();\n\tt.is(results[0].content.trim(), `<h1 id=\"testing-5\">Testing</h1><h1 id=\"testing-2\">Testing</h1><h1 id=\"testing-3\">Testing</h1><h1 id=\"testing-4\">Testing</h1><h1 id=\"testing\">Testing</h1>`);\n});\n\ntest(\"Issue #3424, id attribute conflicts (two hard coded id conflicts)\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", \"./test/stubs-virtual/_site\", {\n    config: function (eleventyConfig) {\n      eleventyConfig.addPlugin(IdAttributePlugin);\n\n      eleventyConfig.addTemplate(\"test.html\", `<h1 id=\"testing\">Testing</h1><h1 id=\"testing\">Testing</h1>`, {});\n    },\n  });\n  elev.disableLogger();\n\n  let e = await t.throwsAsync(() => elev.toJSON());\n  t.is(e.originalError.originalError.toString(), `Error: You have more than one HTML \\`id\\` attribute using the same value (id=\"testing\") in your template (./test/stubs-virtual/test.html). You can disable this error in the IdAttribute plugin with the \\`checkDuplicates: false\\` option.`);\n});\n\ntest(\"Issue #3424, filter callback skips\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", \"./test/stubs-virtual/_site\", {\n    config: function (eleventyConfig) {\n      eleventyConfig.addPlugin(IdAttributePlugin, {\n        filter: function({ page }) {\n          if(page.inputPath.endsWith(\"test-skipped.html\")) {\n            return false;\n          }\n          return true;\n        }\n      });\n\n      eleventyConfig.addTemplate(\"test.html\", `<h1>Testing</h1><h1 id=\"testing\">Testing</h1>`, {});\n      eleventyConfig.addTemplate(\"test-skipped.html\", `<h1 id=\"testing\">Testing</h1><h1 id=\"testing\">Testing</h1>`, {});\n    },\n  });\n  elev.disableLogger();\n\n  let results = await elev.toJSON();\n\tt.is(results[0].content.trim(), `<h1 id=\"testing-2\">Testing</h1><h1 id=\"testing\">Testing</h1>`);\n\tt.is(results[1].content.trim(), `<h1 id=\"testing\">Testing</h1><h1 id=\"testing\">Testing</h1>`);\n});\n"
  },
  {
    "path": "test/ImportJsonSyncTest.js",
    "content": "import test from \"ava\";\nimport { TemplatePath } from \"@11ty/eleventy-utils\";\nimport { importJsonSync, findFilePathInParentDirs } from \"../src/Util/ImportJsonSync.js\";\n\ntest(\"Import a JSON\", t => {\n  t.deepEqual(Object.keys(importJsonSync(\"../../package.json\")).sort().slice(-2, -1).pop(), \"version\");\n});\n\ntest(\"getWorkingProjectPackageJson() traverse parent dirs\", t => {\n  let path = findFilePathInParentDirs(TemplatePath.absolutePath(\"test\"), \"package.json\");\n  let json = importJsonSync(path);\n  t.deepEqual(Object.keys(json).sort().slice(-2, -1).pop(), \"version\");\n});\n"
  },
  {
    "path": "test/InputPathToUrlPluginTest.js",
    "content": "import test from \"ava\";\n\nimport { TransformPlugin } from \"../src/Plugins/InputPathToUrl.js\";\nimport { default as HtmlBasePlugin } from \"../src/Plugins/HtmlBasePlugin.js\";\nimport Eleventy from \"../src/Eleventy.js\";\nimport { normalizeNewLines } from \"./Util/normalizeNewLines.js\";\n\nconst OUTPUT_HTML_STD = `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<meta name=\"description\" content=\"\">\n<title></title>\n<link rel=\"stylesheet\" href=\"/output.css\">\n<script src=\"/output.css\"></script>\n</head>\n<body>\n<a href=\"/\">Home</a>\n<a href=\"/tmpl/\">Test</a>\n<a href=\"/tmpl/#anchor\">Anchor</a>\n<a href=\"#anchor\">Anchor</a>\n<a href=\"./#anchor\">Anchor</a>\n<a href=\"?search=1\">Search</a>\n<a href=\"./?search=1\">Search</a>\n</body>\n</html>`;\n\nconst OUTPUT_HTML_BASE = `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<meta name=\"description\" content=\"\">\n<title></title>\n<link rel=\"stylesheet\" href=\"/gh-pages/output.css\">\n<script src=\"/gh-pages/output.css\"></script>\n</head>\n<body>\n<a href=\"/gh-pages/\">Home</a>\n<a href=\"/gh-pages/tmpl/\">Test</a>\n<a href=\"/gh-pages/tmpl/#anchor\">Anchor</a>\n<a href=\"#anchor\">Anchor</a>\n<a href=\"#anchor\">Anchor</a>\n<a href=\"?search=1\">Search</a>\n<a href=\"?search=1\">Search</a>\n</body>\n</html>`;\n\nfunction getContentFor(results, filename) {\n  let content = results.filter((entry) => entry.outputPath.endsWith(filename))[0].content;\n  return normalizeNewLines(content.trim());\n}\n\ntest(\"Using the transform (and the filter too)\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-pathtourl/\", \"./test/stubs-pathtourl/_site\", {\n    configPath: false,\n    config: function (eleventyConfig) {\n\t\t\t// FilterPlugin is available in the default config.\n      eleventyConfig.addPlugin(TransformPlugin);\n    },\n  });\n\n  let results = await elev.toJSON();\n\t// filter is already available in the default config.\n\tt.is(\n    getContentFor(results, \"/filter/index.html\"),\n    OUTPUT_HTML_STD\n  );\n  t.is(\n    getContentFor(results, \"/transform/index.html\"),\n    OUTPUT_HTML_STD\n  );\n});\n\ntest(\"Using the filter\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-pathtourl/\", \"./test/stubs-pathtourl/_site\", {\n\t\t// FilterPlugin is available in the default config.\n    configPath: false,\n  });\n\n  let results = await elev.toJSON();\n  t.is(\n    getContentFor(results, \"/filter/index.html\"),\n    OUTPUT_HTML_STD\n  );\n\tt.not(\n    getContentFor(results, \"/transform/index.html\"),\n    OUTPUT_HTML_STD\n  );\n});\n\ntest(\"Using the transform and the base plugin\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-pathtourl/\", \"./test/stubs-pathtourl/_site\", {\n    configPath: false,\n\t\tpathPrefix: \"/gh-pages/\",\n    config: function (eleventyConfig) {\n\t\t\televentyConfig.addPlugin(TransformPlugin);\n      eleventyConfig.addPlugin(HtmlBasePlugin);\n    },\n  });\n\n  let results = await elev.toJSON();\n  t.is(\n    getContentFor(results, \"/filter/index.html\"),\n    OUTPUT_HTML_BASE\n  );\n\tt.is(\n    getContentFor(results, \"/transform/index.html\"),\n    OUTPUT_HTML_BASE\n  );\n});\n\ntest(\"Using the transform and the base plugin, reverse order\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-pathtourl/\", \"./test/stubs-pathtourl/_site\", {\n    configPath: false,\n\t\tpathPrefix: \"/gh-pages/\",\n    config: function (eleventyConfig) {\n\t\t\televentyConfig.addPlugin(HtmlBasePlugin);\n\t\t\televentyConfig.addPlugin(TransformPlugin);\n    },\n  });\n\n  let results = await elev.toJSON();\n  t.is(\n    getContentFor(results, \"/filter/index.html\"),\n    OUTPUT_HTML_BASE\n  );\n\tt.is(\n    getContentFor(results, \"/transform/index.html\"),\n    OUTPUT_HTML_BASE\n  );\n});\n\n\ntest(\"Issue #3417 Using the transform with relative path (dot slash)\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", \"./test/stubs-virtual/_site\", {\n    configPath: false,\n    config: function (eleventyConfig) {\n\t\t\t// FilterPlugin is available in the default config.\n      eleventyConfig.addPlugin(TransformPlugin);\n\n      eleventyConfig.addTemplate(\"source/test.njk\", `<a href=\"./target.njk\">Target</a>`)\n      eleventyConfig.addTemplate(\"source/target.njk\", \"lol\")\n    },\n  });\n\n  let results = await elev.toJSON();\n\t// filter is already available in the default config.\n\tt.is(\n    getContentFor(results, \"/source/test/index.html\"),\n    `<a href=\"/source/target/\">Target</a>`\n  );\n});\n\ntest(\"Issue #3417 Using the transform with relative path (no dot slash)\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", \"./test/stubs-virtual/_site\", {\n    configPath: false,\n    config: function (eleventyConfig) {\n\t\t\t// FilterPlugin is available in the default config.\n      eleventyConfig.addPlugin(TransformPlugin);\n\n      eleventyConfig.addTemplate(\"source/test.njk\", `<a href=\"target.njk\">Target</a>`)\n      eleventyConfig.addTemplate(\"source/target.njk\", \"lol\")\n    },\n  });\n\n  let results = await elev.toJSON();\n\t// filter is already available in the default config.\n\tt.is(\n    getContentFor(results, \"/source/test/index.html\"),\n    `<a href=\"/source/target/\">Target</a>`\n  );\n});\n\ntest(\"Issue #3581 #build-cost-🧰\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", \"./test/stubs-virtual/_site\", {\n    configPath: false,\n    config: function (eleventyConfig) {\n      eleventyConfig.addPlugin(TransformPlugin);\n\n      eleventyConfig.addTemplate(\"source/test.njk\", `<a href=\"#built-cost-🧰\">Target</a>`)\n    },\n  });\n\n  let results = await elev.toJSON();\n\n  t.is(\n    getContentFor(results, \"/source/test/index.html\"),\n    `<a href=\"#built-cost-🧰\">Target</a>`\n  );\n});\n\ntest(\"Issue #3583 Markdown diacritics (no plugin)\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", \"./test/stubs-virtual/_site\", {\n    configPath: false,\n    config: function (eleventyConfig) {\n      eleventyConfig.addTemplate(\"test.md\", `[Target](</hypothèse/>)`)\n    },\n  });\n\n  let results = await elev.toJSON();\n\n  t.is(\n    getContentFor(results, \"/test/index.html\"),\n    `<p><a href=\"/hypoth%C3%A8se/\">Target</a></p>`\n  );\n});\n\ntest(\"Issue #3583 Diacritics\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", \"./test/stubs-virtual/_site\", {\n    configPath: false,\n    config: function (eleventyConfig) {\n      eleventyConfig.addPlugin(TransformPlugin);\n\n      eleventyConfig.addTemplate(\"test.md\", `[Target](/hypothèse.md)`)\n      eleventyConfig.addTemplate(\"hypothèse.md\", \"lol\")\n    },\n  });\n\n  let results = await elev.toJSON();\n\n  t.is(\n    getContentFor(results, \"/test/index.html\"),\n    `<p><a href=\"/hypothèse/\">Target</a></p>`\n  );\n});\n\ntest(\"Issue #3583 Diacritics Markdown raw\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", \"./test/stubs-virtual/_site\", {\n    configPath: false,\n    config: function (eleventyConfig) {\n      eleventyConfig.addPlugin(TransformPlugin);\n\n      eleventyConfig.addTemplate(\"test.md\", `[Target](</hypothèse.md>)`)\n      eleventyConfig.addTemplate(\"hypothèse.md\", \"lol\")\n    },\n  });\n\n  let results = await elev.toJSON();\n\n  t.is(\n    getContentFor(results, \"/test/index.html\"),\n    `<p><a href=\"/hypothèse/\">Target</a></p>`\n  );\n});\n\ntest(\"Issue #3583 #3559 Markdown link with spaces (no plugin)\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", \"./test/stubs-virtual/_site\", {\n    configPath: false,\n    config: function (eleventyConfig) {\n      eleventyConfig.addTemplate(\"test.md\", `[Target](</target 1/>)`)\n    },\n  });\n\n  let results = await elev.toJSON();\n\n  t.is(\n    getContentFor(results, \"/test/index.html\"),\n    `<p><a href=\"/target%201/\">Target</a></p>`\n  );\n});\n\ntest(\"Issue #3583 #3559 Markdown spaces\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", \"./test/stubs-virtual/_site\", {\n    configPath: false,\n    config: function (eleventyConfig) {\n      eleventyConfig.addPlugin(TransformPlugin);\n\n      // eleventyConfig.addFilter(\"encode_uri_component\", encodeURIComponent);\n\n      eleventyConfig.addTemplate(\"test.md\", `[Target](<target 1.md>)`)\n      eleventyConfig.addTemplate(\"target 1.md\", \"lol\", {\n        permalink: \"/{{ page.fileSlug | slugify }}/\"\n      })\n    },\n  });\n\n  let results = await elev.toJSON();\n\n  t.is(\n    getContentFor(results, \"/test/index.html\"),\n    `<p><a href=\"/target-1/\">Target</a></p>`\n  );\n});\n"
  },
  {
    "path": "test/Issue3467Test.js",
    "content": "import test from \"ava\";\nimport Eleventy from \"../src/Eleventy.js\";\n\ntest(\"Empty collections api #3467 (return undefined)\", async (t) => {\n\tlet elev = new Eleventy(\"./test/stubs-virtual\", \"./test/stubs-virtual/_site\", {\n\t\tconfig: function (eleventyConfig) {\n\t\t\televentyConfig.addTemplate(\"virtual.md\", `# Hello`);\n\n      eleventyConfig.addCollection(\"brokenCollection\", function(collection) {\n        // returns nothing\n      });\n\t\t},\n\t});\n\n\tlet results = await elev.toJSON();\n\n\tt.deepEqual(results.length, 1);\n\tt.deepEqual(results[0].content.trim(), `<h1>Hello</h1>`);\n\tt.deepEqual(results[0].rawInput, `# Hello`);\n});\n\ntest(\"Empty collections api #3467 (return false)\", async (t) => {\n\tlet elev = new Eleventy(\"./test/stubs-virtual\", \"./test/stubs-virtual/_site\", {\n\t\tconfig: function (eleventyConfig) {\n\t\t\televentyConfig.addTemplate(\"virtual.md\", `# Hello`);\n\n      eleventyConfig.addCollection(\"brokenCollection\", function(collection) {\n        return false;\n      });\n\t\t},\n\t});\n\n\tlet results = await elev.toJSON();\n\n\tt.deepEqual(results.length, 1);\n\tt.deepEqual(results[0].content.trim(), `<h1>Hello</h1>`);\n\tt.deepEqual(results[0].rawInput, `# Hello`);\n});\n\ntest(\"Empty collections api #3467 (return empty string)\", async (t) => {\n\tlet elev = new Eleventy(\"./test/stubs-virtual\", \"./test/stubs-virtual/_site\", {\n\t\tconfig: function (eleventyConfig) {\n\t\t\televentyConfig.addTemplate(\"virtual.md\", `# Hello`);\n\n      eleventyConfig.addCollection(\"brokenCollection\", function(collection) {\n        return \"\";\n      });\n\t\t},\n\t});\n\n\tlet results = await elev.toJSON();\n\n\tt.deepEqual(results.length, 1);\n\tt.deepEqual(results[0].content.trim(), `<h1>Hello</h1>`);\n\tt.deepEqual(results[0].rawInput, `# Hello`);\n});\n"
  },
  {
    "path": "test/Issue3788Test.js",
    "content": "import test from \"ava\";\nimport Eleventy from \"../src/Eleventy.js\";\n\ntest(\"#3788 Nunjucks shortcodes args\", async (t) => {\n\tlet elev = new Eleventy(\"test/noop\", false, {\n\t\tconfig(eleventyConfig) {\n\t\t\televentyConfig.addTemplate(\"index.njk\", `{% test %}:{% test \"\" %}`);\n\n\t\t\televentyConfig.addShortcode(\"test\", (args) => {\n\t\t\t\treturn JSON.stringify(args);\n\t\t\t})\n\t\t}\n\t});\n\n\tlet [result] = await elev.toJSON();\n\n\tt.is(result.content, `undefined:\"\"`);\n});\n"
  },
  {
    "path": "test/Issue3797Test.js",
    "content": "import test from \"ava\";\nimport Eleventy from \"../src/Eleventy.js\";\n\ntest(\"#3797 Virtual templates with empty includes\", async (t) => {\n  let elev = new Eleventy(\"test/noop\", false, {\n    config(eleventyConfig) {\n      eleventyConfig.setIncludesDirectory(\"\");\n      eleventyConfig.setLayoutsDirectory(\"_layouts\");\n      eleventyConfig.addTemplate(\"post1.md\", \"# Post1\", { layout: \"layout.html\" });\n      eleventyConfig.addTemplate(\"_layouts/layout.html\", \"{{ content }}\");\n    }\n  });\n\n  let [result] = await elev.toJSON();\n\n  t.truthy(result);\n});\n"
  },
  {
    "path": "test/Issue3808Test.js",
    "content": "import test from \"ava\";\nimport Eleventy from \"../src/Eleventy.js\";\n\ntest(\"#3808 addCollection in eleventy.before\", async (t) => {\n  let elev = new Eleventy(\"test/noop\", false, {\n    config(eleventyConfig) {\n      eleventyConfig.addTemplate(\"post1.md\", \"# Post1\");\n      eleventyConfig.addTemplate(\"post2.md\", \"# Post2\");\n      eleventyConfig.addTemplate(\"index.njk\", \"{{ collections.posts.length }}\");\n\n      eleventyConfig.on(\"eleventy.before\", async () => {\n      // eleventyConfig.on(\"eleventy.beforeConfig\", async (eleventyConfig) => {\n        eleventyConfig.addCollection(\"posts\", async collectionApi => {\n          return collectionApi.getFilteredByGlob(\"**/post*.md\");\n        });\n      })\n    }\n  });\n\n  let result = await elev.toJSON();\n\n  t.is(result.filter((entry) => entry.url === \"/\")[0]?.content.trim(), \"2\")\n});\n\n// /* broken */\n// export default function(eleventyConfig) {\n//   eleventyConfig.on(\"eleventy.before\", async () => {\n//     eleventyConfig.addCollection(\"posts\", collectionApi => {\n//       return collectionApi.getFilteredByGlob(\"**/post*.md\");\n//     });\n//   })\n// }\n\n// /* works */\n// export default function(eleventyConfig) {\n//   eleventyConfig.on(\"eleventy.beforeConfig\", async (eleventyConfig) => {\n//     eleventyConfig.addCollection(\"posts\", collectionApi => {\n//       return collectionApi.getFilteredByGlob(\"**/post*.md\");\n//     });\n//   })\n// }\n\n// /* works */\n// export default async function(eleventyConfig) {\n//   eleventyConfig.addCollection(\"posts\", collectionApi => {\n//     return collectionApi.getFilteredByGlob(\"**/post*.md\");\n//   });\n// }\n\n// /* works */\n// export default function(eleventyConfig) {\n//   eleventyConfig.addCollection(\"posts\", async collectionApi => {\n//     return collectionApi.getFilteredByGlob(\"**/post*.md\");\n//   });\n// }\n"
  },
  {
    "path": "test/Issue3809Test.js",
    "content": "import test from \"ava\";\n\nimport { spawnAsync } from \"../src/Util/spawn.js\";\n\ntest(\"#3809 parent directory for content, with global data files\", async (t) => {\n  let result = await spawnAsync(\n\t\t\"node\",\n\t\t// Formats https://www.git-scm.com/docs/git-log#_pretty_formats\n\t\t// %at author date, UNIX timestamp\n\t\t[\"../../../../cmd.cjs\", \"--to=json\"],\n    {\n      cwd: \"test/_issues/3809/.app/\"\n    }\n\t);\n\n  let json = JSON.parse(result);\n  t.is(json.length, 1);\n  t.is(json[0]?.content.trim(), \"My Application\");\n});\n"
  },
  {
    "path": "test/Issue3816Test.js",
    "content": "import markdownIt from \"markdown-it\";\nimport markdownItAbbr from \"markdown-it-abbr\";\n\nimport test from \"ava\";\nimport Eleventy from \"../src/Eleventy.js\";\n\ntest(\"#3816 amendLibrary and setLibrary together\", async (t) => {\n  t.plan(1);\n\n  let elev = new Eleventy(\"test/noop\", false, {\n    config(eleventyConfig) {\n      eleventyConfig.addTemplate(\"index.md\", \"# Heading\");\n      eleventyConfig.setLibrary(\"md\", markdownIt());\n      eleventyConfig.amendLibrary(\"md\", (mdLib) => {\n        // this will only run once, t.plan is important!\n        let before = mdLib.core.ruler.getRules(\"\").length;\n        mdLib.use(markdownItAbbr);\n        let after = mdLib.core.ruler.getRules(\"\").length;\n        t.is(after, before + 1);\n      });\n    }\n  });\n\n\n  await elev.toJSON();\n  await elev.restart();\n  await elev.toJSON();\n  await elev.restart();\n  await elev.toJSON();\n  await elev.restart();\n});\n"
  },
  {
    "path": "test/Issue3818Test.js",
    "content": "import test from \"ava\";\nimport Eleventy from \"../src/Eleventy.js\";\nimport WebCPlugin from \"@11ty/eleventy-plugin-webc\";\n\ntest(\"#3818 WebC Permalink\", async (t) => {\n  let elev = new Eleventy(\"test/noop\", false, {\n    config(eleventyConfig) {\n      eleventyConfig.addPlugin(WebCPlugin);\n      eleventyConfig.addTemplate(\"index.webc\", `---\neleventyComputed:\n  permalink: \"page/<f @raw=\\\\\"1\\\\\" webc:nokeep></f>/\"\n---`);\n    }\n  });\n\n\n  let [result] = await elev.toJSON();\n  t.is(result.url, \"/page/1/\");\n});\n\ntest(\"#3818 WebC Permalink Pagination JavaScript function\", async (t) => {\n  let elev = new Eleventy(\"test/noop\", false, {\n    config(eleventyConfig) {\n      eleventyConfig.addPlugin(WebCPlugin);\n      eleventyConfig.addTemplate(\"index.webc\", `---js\nconst pagination = {\n  data: \"posts\",\n  size: 2,\n};\nfunction permalink(data) {\n  return \\`page/\\${data.pagination.pageNumber + 1}/\\`;\n}\n---\n<a :href=\"$data.pagination.href.first\"></a>\n<a :href=\"$data.pagination.href.previous\"></a>\n<a :href=\"$data.pagination.href.next\"></a>\n<a :href=\"$data.pagination.href.last\"></a>`, {\n  posts: [\n    \"first\",\n    \"second\",\n    \"third\",\n    \"fourth\",\n  ]\n});\n    }\n  });\n\n  let [page1, page2] = await elev.toJSON();\n  t.is(page1.url, \"/page/1/\");\n  t.is(page1.content, `<a href=\"/page/1/\"></a>\n<a></a>\n<a href=\"/page/2/\"></a>\n<a href=\"/page/2/\"></a>`)\n  t.is(page2.url, \"/page/2/\");\n  t.is(page2.content, `<a href=\"/page/1/\"></a>\n<a href=\"/page/1/\"></a>\n<a></a>\n<a href=\"/page/2/\"></a>`)\n});\n\ntest(\"#3818 WebC Permalink Pagination, eleventyComputed.permalink String\", async (t) => {\n  let elev = new Eleventy(\"test/noop\", false, {\n    config(eleventyConfig) {\n      eleventyConfig.addPlugin(WebCPlugin);\n      eleventyConfig.addTemplate(\"index.webc\", `---\npagination:\n  data: posts\n  size: 2\neleventyComputed:\n  permalink: \"\\`page/$\\{pagination.pageNumber + 1}/\\`\"\n---\n<a :href=\"$data.pagination.href.first\"></a>\n<a :href=\"$data.pagination.href.previous\"></a>\n<a :href=\"$data.pagination.href.next\"></a>\n<a :href=\"$data.pagination.href.last\"></a>`, {\n  posts: [\n    \"first\",\n    \"second\",\n    \"third\",\n    \"fourth\",\n  ]\n});\n    }\n  });\n\n  let [page1, page2] = await elev.toJSON();\n  t.is(page1.url, \"/page/1/\");\n  t.is(page1.content, `<a href=\"/page/1/\"></a>\n<a></a>\n<a href=\"/page/2/\"></a>\n<a href=\"/page/2/\"></a>`)\n  t.is(page2.url, \"/page/2/\");\n  t.is(page2.content, `<a href=\"/page/1/\"></a>\n<a href=\"/page/1/\"></a>\n<a></a>\n<a href=\"/page/2/\"></a>`)\n});\n\ntest(\"#3818 WebC Permalink Pagination, permalink String\", async (t) => {\n  let elev = new Eleventy(\"test/noop\", false, {\n    config(eleventyConfig) {\n      eleventyConfig.addPlugin(WebCPlugin);\n      eleventyConfig.addTemplate(\"index.webc\", `---\npagination:\n  data: posts\n  size: 2\npermalink: \"\\`page/$\\{pagination.pageNumber + 1}/\\`\"\n---\n<a :href=\"$data.pagination.href.first\"></a>\n<a :href=\"$data.pagination.href.previous\"></a>\n<a :href=\"$data.pagination.href.next\"></a>\n<a :href=\"$data.pagination.href.last\"></a>`, {\n  posts: [\n    \"first\",\n    \"second\",\n    \"third\",\n    \"fourth\",\n  ]\n});\n    }\n  });\n\n  let [page1, page2] = await elev.toJSON();\n  t.is(page1.url, \"/page/1/\");\n  t.is(page1.content, `<a href=\"/page/1/\"></a>\n<a></a>\n<a href=\"/page/2/\"></a>\n<a href=\"/page/2/\"></a>`)\n  t.is(page2.url, \"/page/2/\");\n  t.is(page2.content, `<a href=\"/page/1/\"></a>\n<a href=\"/page/1/\"></a>\n<a></a>\n<a href=\"/page/2/\"></a>`)\n});\n"
  },
  {
    "path": "test/Issue3823Test.js",
    "content": "import test from \"ava\";\nimport Eleventy from \"../src/Eleventy.js\";\n\ntest(\"#3823 addCollection -> pagination over `collections`\", async (t) => {\n  let elev = new Eleventy(\"test/noop\", false, {\n    config(eleventyConfig) {\n      eleventyConfig.addTemplate(\"post1.md\", \"# Post1\");\n      eleventyConfig.addTemplate(\"post2.md\", \"# Post2\");\n      eleventyConfig.addTemplate(\"index.njk\", `---\npagination:\n  data: collections\n  size: 1\n  alias: tag\n  filter:\n    - all\n  addAllPagesToCollections: true\n---\n{{ tag }}`);\n\n      eleventyConfig.addCollection(\"posts\", async collectionApi => {\n        return collectionApi.getFilteredByGlob(\"**/post*.md\");\n      });\n    }\n  });\n\n  let results = await elev.toJSON();\n  results.sort();\n\n  t.is(results.length, 3);\n  t.is(results.filter((entry) => entry.url === \"/\")[0]?.content.trim(), \"posts\")\n});\n"
  },
  {
    "path": "test/Issue3825Test.js",
    "content": "import test from \"ava\";\nimport Eleventy from \"../src/Eleventy.js\";\n\ntest(\"#3825 #3834 addCollection consumes tag from pagination template\", async (t) => {\n  let elev = new Eleventy(\"test/noop\", false, {\n    config(eleventyConfig) {\n      eleventyConfig.addTemplate(\"post1.md\", \"# Post1\");\n      eleventyConfig.addTemplate(\"post2.md\", \"# Post2\");\n\n      eleventyConfig.addCollection(\"posts\", async collectionApi => {\n        return collectionApi.getFilteredByGlob(\"**/post*.md\");\n      });\n\n\t\t\televentyConfig.addCollection(\"myCollection\", collectionApi => {\n\t\t\t\t// populated by child.njk\n\t\t\t\treturn collectionApi.getFilteredByTag(\"childTag\");\n\t\t\t})\n\n      eleventyConfig.addTemplate(\"child.njk\", `---\npagination:\n  data: collections.posts\n  size: 1\n  alias: tag\n  filter:\n    - all\n  addAllPagesToCollections: true\ntags: childTag\n---\n{{ tag }}`);\n\n      eleventyConfig.addTemplate(\"index.njk\", `{{ collections.myCollection.length }}`, {\n        // eleventyImport: {\n        //   collections: [\"myCollection\"]\n        // }\n      });\n    }\n  });\n\n  let results = await elev.toJSON();\n\n  t.is(results.length, 5);\n  t.is(results.filter((entry) => entry.url === \"/\")[0]?.content.trim(), \"2\")\n});\n\ntest(\"#3825 addCollection consumes tag from pagination template\", async (t) => {\n  let elev = new Eleventy(\"test/noop\", false, {\n    config(eleventyConfig) {\n      eleventyConfig.addTemplate(\"post1.md\", \"# Post1\");\n      eleventyConfig.addTemplate(\"post2.md\", \"# Post2\");\n\n      eleventyConfig.addCollection(\"homepageLinks\", function(collectionApi) {\n        // glob consumes pagination over another userconfig collection\n        return collectionApi.getFilteredByGlob([\"**/principles.njk\"]);\n      });\n\n      eleventyConfig.addCollection(\"getAllPrinciplesOrderedByTitle\", function(collectionApi) {\n        return collectionApi.getFilteredByGlob(\"**/post*.md\");\n      });\n\n      eleventyConfig.addTemplate(\"principles.njk\", `---\npagination:\n  data: collections.getAllPrinciplesOrderedByTitle\n  size: 1\n  alias: tag\n  filter:\n    - all\n  addAllPagesToCollections: true\n---\n{{ tag }}`);\n\n      eleventyConfig.addTemplate(\"index.njk\", `{{ collections.homepageLinks.length }}`, {\n        // eleventyImport: {\n        //   collections: [\"homepageLinks\"]\n        // }\n      });\n    }\n  });\n\n  let results = await elev.toJSON();\n\n  t.is(results.length, 5);\n  t.is(results.filter((entry) => entry.url === \"/\")[0]?.content.trim(), \"2\")\n});\n\ntest(\"Side-issue #3825 #3834 tried to Reflect.has on a string in pagination\", async (t) => {\n  let elev = new Eleventy(\"test/noop\", false, {\n    config(eleventyConfig) {\n      eleventyConfig.addTemplate(\"post1.md\", \"# Post1\");\n      eleventyConfig.addTemplate(\"post2.md\", \"# Post2\");\n\n      eleventyConfig.addCollection(\"posts\", async collectionApi => {\n        return collectionApi.getFilteredByGlob(\"**/post*.md\");\n      });\n\n\t\t\televentyConfig.addCollection(\"myCollection\", collectionApi => {\n\t\t\t\t// populated by child.njk\n\t\t\t\treturn collectionApi.getFilteredByTag(\"someArbitraryTag\");\n\t\t\t})\n\n      eleventyConfig.addTemplate(\"child.njk\", `---\npagination:\n  data: collections.posts\n  size: 1\n  alias: tag\n  filter:\n    - all\n  addAllPagesToCollections: true\n# Warning: this is tag not the expected tags\ntag: someArbitraryTag\n---\n{{ tag }}`);\n\n      eleventyConfig.addTemplate(\"index.njk\", `{{ collections.myCollection.length }}`);\n    }\n  });\n\n  let results = await elev.toJSON();\n\n  t.is(results.length, 5);\n  t.is(results.filter((entry) => entry.url === \"/\")[0]?.content.trim(), \"0\")\n});\n"
  },
  {
    "path": "test/Issue3831Test.js",
    "content": "import test from \"ava\";\nimport Eleventy from \"../src/Eleventy.js\";\n\ntest(\"#3831 Computed Data regression\", async (t) => {\n  let elev = new Eleventy(\"test/noop\", false, {\n    config(eleventyConfig) {\n      eleventyConfig.addGlobalData(\"eleventyComputed\", {\n        first_letter: function (data) { return data.title[0] }\n      });\n\n      eleventyConfig.addTemplate(\"index.njk\", `---\ntitle: \"Title\"\nmetadata:\n  url: \"/url/\"\neleventyComputed:\n  \"id\": \"{{ metadata.url }}glossary/entity/#webpage\"\n---\n{{ id }}\n{{ first_letter }}`);\n    }\n  });\n\n  let results = await elev.toJSON();\n  results.sort();\n\n  t.is(results.length, 1);\n  t.is(results[0]?.content.trim(), `/url/glossary/entity/#webpage\nT`)\n});\n"
  },
  {
    "path": "test/Issue3833Test.js",
    "content": "import test from \"ava\";\nimport Eleventy from \"../src/Eleventy.js\";\n\ntest(\"#3831 Computed Data regression\", async (t) => {\n  let elev = new Eleventy(\"test/noop\", false, {\n    config(eleventyConfig) {\n\n      eleventyConfig.addTemplate(\"index.njk\", `---\ndate:\n  - April 1, 2025\n---`);\n    }\n  });\n  elev.disableLogger();\n\n  let e = await t.throwsAsync(() => elev.toJSON());\n  t.is(e.message, `Data cascade value for \\`date\\` (April 1, 2025) is invalid for ./test/noop/index.njk. Expected a JavaScript Date instance, luxon DateTime instance, or String value.`);\n});\n"
  },
  {
    "path": "test/Issue3850Test.js",
    "content": "import test from \"ava\";\nimport Eleventy from \"../src/Eleventy.js\";\n\ntest(\"#3850 Computed Data regression part 2\", async (t) => {\n  let elev = new Eleventy(\"test/noop\", false, {\n    config(eleventyConfig) {\n      eleventyConfig.addTemplate(\"index.njk\", `---\nsite:\n  download_link_mac: \"http://example.com/\"\neleventyComputed:\n  downloads:\n  -\n    links:\n    -\n      url: \"{{ site.download_link_mac }}\"\n---\n{{ site.download_link_mac }}:::{{ downloads | dump | safe }}`);\n    }\n  });\n\n  let results = await elev.toJSON();\n  results.sort();\n\n  t.is(results.length, 1);\n  t.is(results[0]?.content.trim(), `http://example.com/:::[{\"links\":[{\"url\":\"http://example.com/\"}]}]`)\n});\n"
  },
  {
    "path": "test/Issue3853Test.js",
    "content": "import test from \"ava\";\nimport path from \"node:path\";\nimport { TemplatePath } from \"@11ty/eleventy-utils\";\n\nimport { spawnAsync } from \"../src/Util/spawn.js\";\n\ntest(\"#3853 absolute path input should strip output from permalink\", async (t) => {\n  let input = path.join(process.cwd(), \"test/_issues/3853/deeper\");\n  let output = path.join(process.cwd(), \"test/_issues/3853/public/site\");\n  let result = await spawnAsync(\n\t\t\"node\",\n\t\t[\"../../../cmd.cjs\", `--input=${input}`, `--output=${output}`, \"--to=json\"],\n    {\n      cwd: \"test/_issues/3853/\"\n    }\n\t);\n\n  let json = JSON.parse(result);\n\n  t.is(json.length, 1);\n  t.is(json[0]?.outputPath, TemplatePath.standardizeFilePath(\"./public/site/index.html\"));\n  t.is(json[0]?.content.trim(), \"3853\");\n});\n"
  },
  {
    "path": "test/Issue3854Test.js",
    "content": "import test from \"ava\";\n\nimport { spawnAsync } from \"../src/Util/spawn.js\";\n\ntest(\"#3854 parent directory for content, with global data files\", async (t) => {\n  let result = await spawnAsync(\n\t\t\"node\",\n\t\t[\"../../../../cmd.cjs\", \"--to=json\"],\n    {\n      cwd: \"test/_issues/3854/app/\"\n    }\n\t);\n\n  let json = JSON.parse(result);\n  t.is(json.length, 2);\n\n  json.sort((a, b) => {\n    return a.inputPath.length - b.inputPath.length;\n  })\n\n  t.is(json[0]?.content.trim(), \"3854/parent\");\n  t.is(json[1]?.content.trim(), \"3854/child\");\n});\n"
  },
  {
    "path": "test/Issue3860Test.js",
    "content": "import test from \"ava\";\nimport Eleventy from \"../src/Eleventy.js\";\n\ntest(\"#3860 addCollection consumes `collections` but is missing `collections.all`\", async (t) => {\n  let elev = new Eleventy(\"test/noop\", false, {\n    config(eleventyConfig) {\n      eleventyConfig.addFilter(\"keys\", obj => Object.keys(obj));\n      eleventyConfig.addTemplate(\"post1.md\", \"# Post1\", { tags: [\"bar\"]});\n      eleventyConfig.addTemplate(\"post2.md\", \"# Post2\", { tags: [\"foo\"]});\n\n      eleventyConfig.addTemplate(\"tag.njk\", \"{{ collections | keys }}\", {\n\t\t    pagination: {\n\t\t      data: \"collections\",\n\t\t      size: 1,\n\t\t      alias: \"collection\",\n\t\t    },\n\t\t    permalink: \"tag/{{collection}}/index.html\",\n\t\t    eleventyExcludeFromCollections: true,\n\t\t  });\n    }\n  });\n\n  let results = await elev.toJSON();\n\n  let tagPages = results.filter((entry) => entry.inputPath.endsWith(\"tag.njk\"));\n  t.is(tagPages.length, 3);\n  t.is(tagPages[0]?.content.trim(), `bar,foo,all`)\n  t.is(tagPages[1]?.content.trim(), `bar,foo,all`)\n  t.is(tagPages[2]?.content.trim(), `bar,foo,all`)\n});\n"
  },
  {
    "path": "test/Issue3870IncrementalTest.js",
    "content": "import test from \"ava\";\nimport Eleventy from \"../src/Eleventy.js\";\n\n// This tests Eleventy Watch WITHOUT using the file system!\n\ntest(\"#3870 templateRender has not yet initialized (not incremental)\", async (t) => {\n  let runs = [\n    {\n      expected: `[]`,\n    },\n    {\n      expected: `[]`,\n    },\n  ];\n\n  t.plan(runs.length + 1);\n\n  let index = 0;\n  let elev = new Eleventy(\"test/stubs-virtual/\", \"test/stubs-virtual/_site\", {\n    configPath: \"test/stubs-virtual/eleventy.config.js\",\n    config(eleventyConfig) {\n      eleventyConfig.addTemplate(\"search.11ty.js\", class {\n        data() {\n          return {\n            permalink: '/search.json',\n            // permalink: false,\n            layout: false,\n            eleventyExcludeFromCollections: true,\n          };\n        }\n\n        async render(data) {\n          return '[]';\n        }\n      });\n\n      eleventyConfig.on(\"eleventy.after\", ({ results }) => {\n        t.is(results[0]?.content, runs[index].expected);\n      });\n    }\n  });\n\n  elev.disableLogger();\n  elev.setIncrementalBuild(true);\n  await elev.init();\n\n  let asyncTriggerFn = await elev.watch();\n\n  for(let run of runs) {\n    await asyncTriggerFn(\"test/stubs-virtual/eleventy.config.js\");\n    index++;\n  }\n\n  await elev.stopWatch();\n});\n"
  },
  {
    "path": "test/Issue3870Test.js",
    "content": "import test from \"ava\";\nimport Eleventy from \"../src/Eleventy.js\";\n\n// This tests Eleventy Watch WITHOUT using the file system!\n\ntest(\"#3870 templateRender has not yet initialized (not incremental)\", async (t) => {\n  let runs = [\n    {\n      expected: `[]`,\n    },\n    {\n      expected: `[]`,\n    },\n  ];\n\n  t.plan(runs.length + 1);\n\n  let index = 0;\n  let elev = new Eleventy(\"test/stubs-virtual/\", \"test/stubs-virtual/_site\", {\n    configPath: \"test/stubs-virtual/eleventy.config.js\",\n    config(eleventyConfig) {\n      eleventyConfig.addTemplate(\"search.11ty.js\", class {\n        data() {\n          return {\n            permalink: '/search.json',\n            // permalink: false,\n            layout: false,\n            eleventyExcludeFromCollections: true,\n          };\n        }\n\n        async render(data) {\n          return '[]';\n        }\n      });\n\n      eleventyConfig.on(\"eleventy.after\", ({ results }) => {\n        t.is(results[0]?.content, runs[index].expected);\n      });\n    }\n  });\n\n  elev.disableLogger();\n  await elev.init();\n\n  let asyncTriggerFn = await elev.watch();\n\n  for(let run of runs) {\n    await asyncTriggerFn(\"test/stubs-virtual/eleventy.config.js\");\n    index++;\n  }\n\n  await elev.stopWatch();\n});\n"
  },
  {
    "path": "test/Issue3875Test.js",
    "content": "import test from \"ava\";\nimport Eleventy from \"../src/Eleventy.js\";\n\ntest(\"#3875 numeric tags\", async (t) => {\n  let elev = new Eleventy(\"test/noop\", false, {\n    config(eleventyConfig) {\n      eleventyConfig.addFilter(\"keys\", (obj) => Object.keys(obj));\n      eleventyConfig.addTemplate(\"index.njk\", \"{{ collections | keys }}\", {\n        tags: [1,2,3]\n      });\n    }\n  });\n\n  let results = await elev.toJSON();\n  t.is(results[0].content, \"1,2,3,all\");\n});\n\ntest(\"#3875 numeric tags (via front matter)\", async (t) => {\n  let elev = new Eleventy(\"test/noop\", false, {\n    config(eleventyConfig) {\n      eleventyConfig.addFilter(\"keys\", (obj) => Object.keys(obj));\n      eleventyConfig.addTemplate(\"index.njk\", `---\ntags:\n  - 1\n  - 2\n  - 3\n---\n{{ collections | keys }}`);\n    }\n  });\n\n  let results = await elev.toJSON();\n  t.is(results[0].content, \"1,2,3,all\");\n});\n\ntest(\"#3875 consume a numeric tag collection (njk)\", async (t) => {\n  let elev = new Eleventy(\"test/noop\", false, {\n    config(eleventyConfig) {\n      eleventyConfig.addFilter(\"keyTypes\", (obj) => Object.keys(obj).map(entry => typeof entry).join(\",\"));\n      eleventyConfig.addTemplate(\"child.njk\", \"\", {\n        tags: [1]\n      });\n      eleventyConfig.addTemplate(\"index.njk\", `{{ collections | keyTypes }}:{{ collections[1].length }}:{{ collections['1'].length }}`);\n    }\n  });\n\n  let results = await elev.toJSON();\n  t.is(results.filter(entry => entry.inputPath.endsWith(\"index.njk\"))[0].content, \"string,string:1:1\");\n});\n\ntest(\"#3875 consume a numeric tag collection (liquid)\", async (t) => {\n  let elev = new Eleventy(\"test/noop\", false, {\n    config(eleventyConfig) {\n      eleventyConfig.addFilter(\"keyTypes\", (obj) => Object.keys(obj).map(entry => typeof entry).join(\",\"));\n      eleventyConfig.addTemplate(\"child.njk\", \"\", {\n        tags: [1]\n      });\n      eleventyConfig.addTemplate(\"index.liquid\", `{{ collections | keyTypes }}:{{ collections[1].length }}:{{ collections['1'].length }}`);\n    }\n  });\n\n  let results = await elev.toJSON();\n  t.is(results.filter(entry => entry.inputPath.endsWith(\"index.liquid\"))[0].content, \"string,string:1:1\");\n});\n\ntest(\"#3875 consume a numeric tag collection (11ty.js)\", async (t) => {\n  let elev = new Eleventy(\"test/noop\", false, {\n    config(eleventyConfig) {\n      eleventyConfig.addTemplate(\"child.njk\", \"\", {\n        tags: [1]\n      });\n      eleventyConfig.addTemplate(\"index.11ty.js\", {\n        render(data) {\n          return `${Object.keys(data.collections).map(entry => typeof entry).join(\",\")}:${data.collections[1].length}:${data.collections['1'].length}`\n        }\n      });\n    }\n  });\n\n  let results = await elev.toJSON();\n  t.is(results.filter(entry => entry.inputPath.endsWith(\"index.11ty.js\"))[0].content, \"string,string:1:1\");\n});\n"
  },
  {
    "path": "test/Issue434Test.js",
    "content": "import test from \"ava\";\nimport Eleventy from \"../src/Eleventy.js\";\n\ntest(\"#434 Using `with context` to access collections\", async (t) => {\n\tlet elev = new Eleventy(\"test/noop\", false, {\n\t\tconfig(eleventyConfig) {\n\t\t\televentyConfig.setIncludesDirectory(\"../stubs-434/_includes/\");\n\t\t\televentyConfig.addTemplate(\"index.njk\", `{% import \"macros.njk\" as forms with context %}{{ forms.label('test') }}`);\n\t\t}\n\t});\n\n\n\tlet [result] = await elev.toJSON();\n\tt.is(result.content, \"<label>test:1:/</label>\");\n});\n\ntest(\"#434 (not ideal) Filters in macros cannot access global data\", async (t) => {\n\tlet elev = new Eleventy(\"test/noop\", false, {\n\t\tconfig(eleventyConfig) {\n\t\t\televentyConfig.setIncludesDirectory(\"../stubs-434/_includes/\");\n\n      // This doesn’t work to fetch collections from macros\n\t\t\televentyConfig.addFilter(\"getCollection\", function(name) {\n\t\t\t\treturn this.collections?.[name] ||\n\t\t\t\t\tthis.ctx?.collections?.[name] ||\n\t\t\t\t\tthis.context?.environments?.collections?.[name];\n\t\t\t});\n\n\t\t\televentyConfig.addTemplate(\"index.njk\", `{% import \"macros-filter.njk\" as forms %}{{ forms.label('test') }}`);\n\t\t}\n\t});\n\n\n\tlet [result] = await elev.toJSON();\n\tt.is(result.content, \"<label>test:0</label>\");\n});\n"
  },
  {
    "path": "test/Issue775Test.js",
    "content": "import test from \"ava\";\nimport Eleventy from \"../src/Eleventy.js\";\n\ntest(\"#775 Using data cascade in Collection API\", async (t) => {\n\tlet elev = new Eleventy(\"test/noop\", false, {\n\t\tconfig(eleventyConfig) {\n\t\t\televentyConfig.addCollection(\"apic\", collectionApi => {\n\t\t\t\treturn collectionApi.getFilteredByTag(\"posts\").filter(entry => {\n\t\t\t\t\treturn entry.data.keep;\n\t\t\t\t});\n\t\t\t})\n\t\t\televentyConfig.addTemplate(\"post1.md\", `# Header`, { tags: \"posts\", keep: true });\n\t\t\televentyConfig.addTemplate(\"post2.md\", `# Header`, { tags: \"posts\" });\n\t\t\televentyConfig.addTemplate(\"post3.md\", `# Header`, { tags: \"posts\" });\n\t\t\televentyConfig.addTemplate(\"index.njk\", `{{ collections.apic.length }}`);\n\t\t}\n\t});\n\n\n\tlet [result] = await elev.toJSON();\n\tt.is(result.content, \"1\");\n});\n"
  },
  {
    "path": "test/JavaScriptDependenciesTest.js",
    "content": "import test from \"ava\";\nimport JavaScriptDependencies from \"../src/Util/JavaScriptDependencies.js\";\n\ntest(\"No node_modules\", async (t) => {\n  let deps = await JavaScriptDependencies.getDependencies([\n    \"./test/stubs-dependency-tree/index.cjs\",\n  ]);\n\n  t.deepEqual(deps, [\n    \"./test/stubs-dependency-tree/child.cjs\",\n    \"./test/stubs-dependency-tree/grandchild.cjs\",\n  ]);\n});\n"
  },
  {
    "path": "test/JavaScriptFrontMatterTest.js",
    "content": "import test from \"ava\";\nimport Eleventy from \"../src/Eleventy.js\";\n\ntest(\"Custom Front Matter Parsing Options (using JavaScript node-retrieve-globals)\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs/script-frontmatter/test.njk\", \"./_site\");\n  elev.disableLogger();\n\n  let result = await elev.toJSON();\n\n  t.deepEqual(result.length, 1);\n\n  t.is(result[0]?.content, `<div>Hi</div><div>Bye</div>`);\n});\n\ntest(\"Custom Front Matter Parsing Options (using JavaScript node-retrieve-globals), override project-wide front matter default.\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs/script-frontmatter/test-default.njk\", \"./_site\", {\n    config: (eleventyConfig) => {\n      eleventyConfig.setFrontMatterParsingOptions({\n        language: \"js\",\n      });\n    },\n  });\n  elev.disableLogger();\n\n  let result = await elev.toJSON();\n\n  t.deepEqual(result.length, 1);\n\n  t.is(result[0]?.content, `<div>Hi</div><div>Bye</div>`);\n});\n\ntest(\"Custom Front Matter Parsing Options (using backwards-compatible `js` instead of node-retrieve-globals)\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs/script-frontmatter/test-js.njk\", \"./_site\");\n  elev.disableLogger();\n\n  let result = await elev.toJSON();\n\n  t.deepEqual(result.length, 1);\n\n  t.is(result[0]?.content, `<div>HELLO!</div>`);\n});\n\n// https://github.com/11ty/eleventy/issues/3917\ntest(\"Issue #3917 previous JS object front matter shouldn’t have had implicit exports turned on\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual-nowrite\", \"./test/stubs-virtual-nowrite/_site\", {\n    config: function(eleventyConfig) {\n      eleventyConfig.addTemplate(\"test.njk\", `---js\n{\n  eleventyComputed: {\n    summary: async function (data) {\n      let textInsert = data ? 'something' : 'nothing';\n      return \"Some text\";\n    }\n  }\n}\n---\nHello`);\n    }\n  });\n  elev.disableLogger();\n\n  let result = await elev.toJSON();\n\n  t.deepEqual(result.length, 1);\n\n  t.is(result[0]?.content, `Hello`);\n});\n"
  },
  {
    "path": "test/LayoutCacheTest.js",
    "content": "import test from \"ava\";\n\nimport templateCache from \"../src/LayoutCache.js\";\nimport getNewTemplate from \"./_getNewTemplateForTests.js\";\n\ntest(\"Cache can save templates\", async (t) => {\n  templateCache.clear();\n\n  let tmpl = await getNewTemplate(\"./test/stubs/template.liquid\", \"./test/stubs/\", \"./dist\");\n\n  templateCache.add(tmpl);\n  t.is(templateCache.size(), 1);\n});\n\ntest(\"TemplateCache clear\", async (t) => {\n  templateCache.clear();\n\n  let tmpl = await getNewTemplate(\"./test/stubs/template.liquid\", \"./test/stubs/\", \"./dist\");\n\n  templateCache.add(tmpl);\n  t.is(templateCache.size(), 1);\n  templateCache.clear();\n  t.is(templateCache.size(), 0);\n});\n\ntest(\"TemplateCache has\", async (t) => {\n  templateCache.clear();\n\n  let tmpl = await getNewTemplate(\"./test/stubs/template.liquid\", \"./test/stubs/\", \"./dist\");\n\n  templateCache.add(tmpl);\n  // Only TemplateLayout is cached\n  t.is(templateCache.has(\"./test/stubs/template.liquid\"), false);\n});\n\ntest(\"TemplateCache get success\", async (t) => {\n  templateCache.clear();\n\n  let tmpl = await getNewTemplate(\"./test/stubs/template.liquid\", \"./test/stubs/\", \"./dist\");\n\n  templateCache.add(tmpl);\n\n  // Only TemplateLayout is cached\n  t.throws(() => {\n    templateCache.get(\"./test/stubs/template.liquid\");\n  });\n});\n\ntest(\"TemplateCache get fail\", async (t) => {\n  templateCache.clear();\n\n  let tmpl = await getNewTemplate(\"./test/stubs/template.liquid\", \"./test/stubs/\", \"./dist\");\n\n  templateCache.add(tmpl);\n  t.throws(function () {\n    templateCache.get(\"./test/stubs/template298374892.liquid\");\n  });\n});\n"
  },
  {
    "path": "test/LodashTest.js",
    "content": "import test from \"ava\";\nimport lodash from \"@11ty/lodash-custom\";\nimport { ProxyWrap } from \"../src/Util/Objects/ProxyWrap.js\";\nimport { DeepFreeze } from \"../src/Util/Objects/DeepFreeze.js\";\n\nconst { set: lodashSet } = lodash;\n\ntest(\"Lodash sanity tests\", t => {\n\tt.deepEqual(lodashSet({}, \"test\", true), {\"test\": true});\n\tt.deepEqual(lodashSet({}, \"metadata.title\", true), {\"metadata\": {title: true}});\n});\n\ntest(\"Lodash set on proxy\", t => {\n\tlet target = {};\n\tlet obj = ProxyWrap(target, {});\n\tlet ret = lodashSet(obj, \"metadata.title\", \"test\");\n\tt.deepEqual(ret, {metadata: {title: \"test\"}});\n});\n\ntest(\"Lodash set on proxy object with data\", t => {\n\t// let target = { metadata: { title: \"default\" } };\n\tlet target = {};\n\tlet fallback = {};\n\tlet obj = ProxyWrap(target, fallback);\n\tlodashSet(obj, \"metadata.title\", \"test\");\n\tt.deepEqual(obj, {metadata: {title: \"test\"}});\n});\n\n// TODO re-add support for frozen fallbacks\ntest.skip(\"Fallback is *not* mutated (is frozen) (does not exist in fallback)\", t => {\n\tlet fallback = {};\n\n\t// oh my god freeze is shallow\n\tDeepFreeze(fallback);\n\n\tlet target1 = ProxyWrap({ first: true, metadata: { a: 1 } }, fallback);\n\tlet target2 = ProxyWrap({ second: true, metadata: { b: 1 } }, fallback);\n\n\tlodashSet(fallback, \"metadata.c\", 999); // does nothing\n\n\tlodashSet(target1, \"metadata.c\", 1);\n\n\tt.is(target1.metadata.c, 1);\n\tt.is(target2.metadata.c, undefined);\n\n\tt.deepEqual(target1, { first: true, metadata: { a: 1, c: 1}});\n\tt.deepEqual(target2, { second: true, metadata: { b: 1 } });\n});\n\n// TODO re-add support for frozen fallbacks\ntest.skip(\"Fallback is *not* mutated (is frozen) (exists in fallback)\", t => {\n\tlet fallback = {\n\t\tmetadata: { d: 888 }\n\t};\n\n\t// Object.freeze is shallow\n\tDeepFreeze(fallback);\n\n\tlet target3 = ProxyWrap({ third: true }, fallback);\n\n\tt.deepEqual(target3, { third: true, metadata: { d: 888 }});\n\n\tlodashSet(target3, \"metadata.d\", \"all\");\n\n\tt.deepEqual(target3, { third: true, metadata: { d: \"all\" }});\n});\n"
  },
  {
    "path": "test/PaginationTest.js",
    "content": "import test from \"ava\";\n\nimport Eleventy from \"../src/Eleventy.js\";\nimport TemplateData from \"../src/Data/TemplateData.js\";\nimport Pagination from \"../src/Plugins/Pagination.js\";\nimport FileSystemSearch from \"../src/FileSystemSearch.js\";\nimport EleventyExtensionMap from \"../src/EleventyExtensionMap.js\";\nimport getNewTemplate from \"./_getNewTemplateForTests.js\";\nimport { getRenderedTemplates as getRenderedTmpls, renderTemplate } from \"./_getRenderedTemplates.js\";\nimport { getTemplateConfigInstance } from \"./_testHelpers.js\";\n\ntest(\"No data passed to pagination\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/paged/notpaged.njk\",\n    \"./test/stubs/\",\n    \"./dist\",\n  );\n\n  let paging = new Pagination(tmpl, {}, tmpl.config);\n\n  t.is(paging.pagedItems.length, 0);\n  t.is((await paging.getPageTemplates()).length, 0);\n});\n\ntest(\"No pagination\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/paged/notpaged.njk\",\n    \"./test/stubs/\",\n    \"./dist\",\n  );\n\n  let data = await tmpl.getData();\n  let paging = new Pagination(tmpl, data, tmpl.config);\n  paging.setTemplate(tmpl);\n\n  t.falsy(data.pagination);\n  t.is(paging.getPageCount(), 0);\n  t.is(paging.pagedItems.length, 0);\n  t.is((await paging.getPageTemplates()).length, 0);\n});\n\ntest(\"Empty paged data\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/paged/paged-empty.njk\",\n    \"./test/stubs/\",\n    \"./dist\",\n  );\n\n  let data = await tmpl.getData();\n  let paging = new Pagination(tmpl, data, tmpl.config);\n  paging.setTemplate(tmpl);\n\n  t.is(paging.getPageCount(), 0);\n  t.is(paging.pagedItems.length, 0);\n  t.is((await paging.getPageTemplates()).length, 0);\n});\n\ntest(\"Empty paged data with generatePageOnEmptyData enabled\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/paged/paged-empty-pageonemptydata.njk\",\n    \"./test/stubs/\",\n    \"./dist\",\n  );\n\n  let data = await tmpl.getData();\n  let paging = new Pagination(tmpl, data, tmpl.config);\n  paging.setTemplate(tmpl);\n\n  t.is(paging.getPageCount(), 1);\n  t.is(paging.pagedItems.length, 1);\n  t.is((await paging.getPageTemplates()).length, 1);\n});\n\ntest(\"Pagination enabled in frontmatter\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/paged/pagedresolve.njk\",\n    \"./test/stubs/\",\n    \"./dist\",\n  );\n\n  let data = await tmpl.getData();\n  let paging = new Pagination(tmpl, data, tmpl.config);\n  paging.setTemplate(tmpl);\n\n  t.truthy(data.testdata);\n  t.truthy(data.testdata.sub);\n\n  t.truthy(data.pagination);\n  t.is(data.pagination.data, \"testdata.sub\");\n  t.is(paging.getPageCount(), 2);\n  t.is(data.pagination.size, 4);\n});\n\ntest(\"Resolve paged data in frontmatter\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/paged/pagedresolve.njk\",\n    \"./test/stubs/\",\n    \"./dist\",\n  );\n\n  let data = await tmpl.getData();\n  let paging = new Pagination(tmpl, data, tmpl.config);\n  paging.setTemplate(tmpl);\n  t.is(paging._resolveItems().length, 8);\n  t.is(paging.getPageCount(), 2);\n  t.is(paging.pagedItems.length, 2);\n});\n\ntest(\"Paginate data in frontmatter\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/paged/pagedinlinedata.njk\",\n    \"./test/stubs/\",\n    \"./dist\",\n  );\n\n  let data = await tmpl.getData();\n  let pages = await tmpl.getTemplates(data);\n  t.is(pages.length, 2);\n\n  t.is(pages[0].outputPath, \"./dist/paged/pagedinlinedata/index.html\");\n  t.is(\n    (await renderTemplate(pages[0].template, pages[0].data)).trim(),\n    \"<ol><li>item1</li><li>item2</li><li>item3</li><li>item4</li></ol>\"\n  );\n\n  t.is(pages[1].outputPath, \"./dist/paged/pagedinlinedata/1/index.html\");\n  t.is(\n    (await renderTemplate(pages[1].template, pages[1].data)).trim(),\n    \"<ol><li>item5</li><li>item6</li><li>item7</li><li>item8</li></ol>\"\n  );\n});\n\ntest(\"Paginate external data file\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs\",\n      output: \"dist\",\n    }\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  dataObj.setFileSystemSearch(new FileSystemSearch());\n  await dataObj.getGlobalData();\n\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/paged/paged.njk\",\n    \"./test/stubs/\",\n    \"./dist\",\n    dataObj,\n    null,\n    eleventyConfig\n  );\n\n  let data = await tmpl.getData();\n\n  // local data\n  t.truthy(data.items.sub.length);\n\n  let pages = await tmpl.getTemplates(data);\n  t.is(pages.length, 2);\n\n  t.is(pages[0].outputPath, \"./dist/paged/index.html\");\n  t.is(\n    (await renderTemplate(pages[0].template, pages[0].data)).trim(),\n    \"<ol><li>item1</li><li>item2</li><li>item3</li><li>item4</li><li>item5</li></ol>\"\n  );\n\n  t.is(pages[1].outputPath, \"./dist/paged/1/index.html\");\n  t.is(\n    (await renderTemplate(pages[1].template, pages[1].data)).trim(),\n    \"<ol><li>item6</li><li>item7</li><li>item8</li></ol>\"\n  );\n});\n\ntest(\"Permalink with pagination variables\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/paged/pagedpermalink.njk\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n\n  let data = await tmpl.getData();\n  let pages = await tmpl.getTemplates(data);\n\n  t.is(pages[0].outputPath, \"./dist/paged/slug-candidate/index.html\");\n  t.is(pages[1].outputPath, \"./dist/paged/another-slug-candidate/index.html\");\n});\n\ntest(\"Permalink with pagination variables (numeric)\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/paged/pagedpermalinknumeric.njk\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n\n  let data = await tmpl.getData();\n  let pages = await tmpl.getTemplates(data);\n\n  t.truthy(pages[0].data.pagination.firstPageLink);\n  t.truthy(pages[0].data.pagination.firstPageHref);\n  t.truthy(pages[0].data.pagination.lastPageLink);\n  t.truthy(pages[0].data.pagination.lastPageHref);\n  t.is(pages[0].outputPath, \"./dist/paged/page-0/index.html\");\n  t.falsy(pages[0].data.pagination.previousPageLink);\n  t.is(pages[0].data.pagination.nextPageLink, \"/paged/page-1/index.html\");\n  t.is(pages[0].data.pagination.nextPageHref, \"/paged/page-1/\");\n  t.is(pages[0].data.pagination.pageLinks.length, 2);\n  t.is(pages[0].data.pagination.links.length, 2);\n  t.is(pages[0].data.pagination.hrefs.length, 2);\n\n  t.is(pages[1].outputPath, \"./dist/paged/page-1/index.html\");\n  t.is(pages[1].data.pagination.previousPageLink, \"/paged/page-0/index.html\");\n  t.is(pages[1].data.pagination.previousPageHref, \"/paged/page-0/\");\n  t.falsy(pages[1].data.pagination.nextPageLink);\n  t.is(pages[1].data.pagination.pageLinks.length, 2);\n  t.is(pages[1].data.pagination.links.length, 2);\n  t.is(pages[1].data.pagination.hrefs.length, 2);\n});\n\ntest(\"Permalink with pagination variables (numeric, one indexed)\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/paged/pagedpermalinknumericoneindexed.njk\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n\n  let data = await tmpl.getData();\n  let pages = await tmpl.getTemplates(data);\n\n  t.is(pages[0].outputPath, \"./dist/paged/page-1/index.html\");\n  t.falsy(pages[0].data.pagination.previousPageLink);\n  t.is(pages[0].data.pagination.nextPageLink, \"/paged/page-2/index.html\");\n  t.is(pages[0].data.pagination.nextPageHref, \"/paged/page-2/\");\n  t.is(pages[0].data.pagination.pageLinks.length, 2);\n  t.is(pages[0].data.pagination.links.length, 2);\n  t.is(pages[0].data.pagination.hrefs.length, 2);\n\n  t.is(pages[1].outputPath, \"./dist/paged/page-2/index.html\");\n  t.is(pages[1].data.pagination.previousPageLink, \"/paged/page-1/index.html\");\n  t.is(pages[1].data.pagination.previousPageHref, \"/paged/page-1/\");\n  t.falsy(pages[1].data.pagination.nextPageLink);\n  t.is(pages[1].data.pagination.pageLinks.length, 2);\n  t.is(pages[1].data.pagination.links.length, 2);\n  t.is(pages[1].data.pagination.hrefs.length, 2);\n});\n\ntest(\"Permalink first and last page link with pagination variables (numeric)\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/paged/pagedpermalinknumeric.njk\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n\n  let data = await tmpl.getData();\n  let pages = await tmpl.getTemplates(data);\n\n  t.is(pages[0].data.pagination.firstPageLink, \"/paged/page-0/index.html\");\n  t.is(pages[0].data.pagination.lastPageLink, \"/paged/page-1/index.html\");\n\n  t.is(pages[1].data.pagination.firstPageLink, \"/paged/page-0/index.html\");\n  t.is(pages[1].data.pagination.lastPageLink, \"/paged/page-1/index.html\");\n});\n\ntest(\"Permalink first and last page link with pagination variables (numeric, one indexed)\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/paged/pagedpermalinknumericoneindexed.njk\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n\n  let data = await tmpl.getData();\n  let pages = await tmpl.getTemplates(data);\n\n  t.is(pages[0].data.pagination.firstPageLink, \"/paged/page-1/index.html\");\n  t.is(pages[0].data.pagination.lastPageLink, \"/paged/page-2/index.html\");\n\n  t.is(pages[1].data.pagination.firstPageLink, \"/paged/page-1/index.html\");\n  t.is(pages[1].data.pagination.lastPageLink, \"/paged/page-2/index.html\");\n});\n\ntest(\"Alias to page data\", async (t) => {\n  let tmpl = await getNewTemplate(\"./test/stubs/paged/pagedalias.njk\", \"./test/stubs/\", \"./dist\");\n\n  let data = await tmpl.getData();\n  let pages = await tmpl.getTemplates(data);\n\n  t.is(pages[0].outputPath, \"./dist/pagedalias/item1/index.html\");\n  t.is(pages[1].outputPath, \"./dist/pagedalias/item2/index.html\");\n\n  t.is((await renderTemplate(pages[0].template, pages[0].data)).trim(), \"item1\");\n  t.is((await renderTemplate(pages[1].template, pages[1].data)).trim(), \"item2\");\n});\n\ntest(\"Alias to page data (size 2)\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/paged/pagedaliassize2.njk\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n\n  let data = await tmpl.getData();\n  let pages = await tmpl.getTemplates(data);\n\n  t.is(pages[0].outputPath, \"./dist/pagedalias/item1/index.html\");\n  t.is(pages[1].outputPath, \"./dist/pagedalias/item3/index.html\");\n\n  t.is((await renderTemplate(pages[0].template, pages[0].data)).trim(), \"item1\");\n  t.is((await renderTemplate(pages[1].template, pages[1].data)).trim(), \"item3\");\n});\n\ntest(\"Permalink with pagination variables (and an if statement, nunjucks)\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/paged/pagedpermalinkif.njk\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n\n  let data = await tmpl.getData();\n  let pages = await tmpl.getTemplates(data);\n\n  t.is(pages[0].outputPath, \"./dist/paged/index.html\");\n  t.is(pages[1].outputPath, \"./dist/paged/page-1/index.html\");\n});\n\ntest(\"Permalink with pagination variables (and an if statement, liquid)\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/paged/pagedpermalinkif.liquid\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n\n  let data = await tmpl.getData();\n  let pages = await tmpl.getTemplates(data);\n\n  t.is(pages[0].outputPath, \"./dist/paged/index.html\");\n  t.is(pages[1].outputPath, \"./dist/paged/page-1/index.html\");\n});\n\ntest(\"Template with Pagination\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/paged/pagedpermalinkif.njk\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n\n  let data = await tmpl.getData();\n  let outputPath = await tmpl.getOutputPath(data);\n  t.is(outputPath, \"./dist/paged/index.html\");\n\n  let templates = await getRenderedTmpls(tmpl, data);\n  t.is(templates.length, 2);\n});\n\ntest(\"Issue 135\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs\",\n      output: \"dist\",\n    }\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  dataObj.setFileSystemSearch(new FileSystemSearch());\n  await dataObj.getGlobalData();\n\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/issue-135/template.njk\",\n    \"./test/stubs/\",\n    \"./dist\",\n    dataObj,\n    null,\n    eleventyConfig\n  );\n\n  let data = await tmpl.getData();\n  let templates = await getRenderedTmpls(tmpl, data);\n  t.is(data.articles.length, 1);\n  t.is(data.articles[0].title, \"Do you even paginate bro?\");\n  t.is(await templates[0].outputPath, \"./dist/blog/do-you-even-paginate-bro/index.html\");\n\n  let pages = await tmpl.getTemplates(data);\n  t.is(pages.length, 1);\n  t.is(pages[0].outputPath, \"./dist/blog/do-you-even-paginate-bro/index.html\");\n});\n\ntest(\"Template with Pagination, getTemplates has page variables set\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/paged/pagedpermalinkif.njk\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n\n  let data = await tmpl.getData();\n  let templates = await tmpl.getTemplates(data);\n  t.is(templates[0].data.page.url, \"/paged/\");\n  t.is(templates[0].data.page.outputPath, \"./dist/paged/index.html\");\n\n  t.is(templates[1].data.page.url, \"/paged/page-1/\");\n  t.is(templates[1].data.page.outputPath, \"./dist/paged/page-1/index.html\");\n});\n\ntest(\"Template with Pagination, has page variables set\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/paged/pagedpermalinkif.njk\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n\n  let data = await tmpl.getData();\n  let pages = await getRenderedTmpls(tmpl, data);\n  t.is(pages[0].data.page.url, \"/paged/\");\n  t.is(pages[0].data.page.outputPath, \"./dist/paged/index.html\");\n\n  t.is(pages[1].data.page.url, \"/paged/page-1/\");\n  t.is(pages[1].data.page.outputPath, \"./dist/paged/page-1/index.html\");\n});\n\ntest(\"Page over an object (use keys)\", async (t) => {\n  let tmpl = await getNewTemplate(\"./test/stubs/paged/pagedobject.njk\", \"./test/stubs/\", \"./dist\");\n\n  let data = await tmpl.getData();\n  let pages = await tmpl.getTemplates(data);\n  t.is(pages.length, 3);\n\n  t.is(pages[0].outputPath, \"./dist/paged/pagedobject/index.html\");\n  t.is(\n    (await renderTemplate(pages[0].template, pages[0].data)).trim(),\n    \"<ol><li>item1</li><li>item2</li><li>item3</li><li>item4</li></ol>\"\n  );\n\n  t.is(pages[1].outputPath, \"./dist/paged/pagedobject/1/index.html\");\n  t.is(\n    (await renderTemplate(pages[1].template, pages[1].data)).trim(),\n    \"<ol><li>item5</li><li>item6</li><li>item7</li><li>item8</li></ol>\"\n  );\n});\n\ntest(\"Page over an object (use values)\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/paged/pagedobjectvalues.njk\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n\n  let data = await tmpl.getData();\n  let pages = await tmpl.getTemplates(data);\n  t.is(pages.length, 3);\n\n  t.is(pages[0].outputPath, \"./dist/paged/pagedobjectvalues/index.html\");\n  t.is(\n    (await renderTemplate(pages[0].template, pages[0].data)).trim(),\n    \"<ol><li>itemvalue1</li><li>itemvalue2</li><li>itemvalue3</li><li>itemvalue4</li></ol>\"\n  );\n\n  t.is(pages[1].outputPath, \"./dist/paged/pagedobjectvalues/1/index.html\");\n  t.is(\n    (await renderTemplate(pages[1].template, pages[1].data)).trim(),\n    \"<ol><li>itemvalue5</li><li>itemvalue6</li><li>itemvalue7</li><li>itemvalue8</li></ol>\"\n  );\n});\n\ntest(\"Page over an object (filtered, array)\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/paged/pagedobjectfilterarray.njk\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n\n  let data = await tmpl.getData();\n  let pages = await tmpl.getTemplates(data);\n\n  t.is(\n    (await renderTemplate(pages[0].template, pages[0].data)).trim(),\n    \"<ol><li>item1</li><li>item2</li><li>item3</li><li>item5</li></ol>\"\n  );\n\n  t.is(\n    (await renderTemplate(pages[1].template, pages[1].data)).trim(),\n    \"<ol><li>item6</li><li>item7</li><li>item8</li><li>item9</li></ol>\"\n  );\n});\n\ntest(\"Page over an object (filtered, string)\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/paged/pagedobjectfilterstring.njk\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n\n  let data = await tmpl.getData();\n  let pages = await tmpl.getTemplates(data);\n  t.is(pages.length, 2);\n\n  t.is(\n    (await renderTemplate(pages[0].template, pages[0].data)).trim(),\n    \"<ol><li>item1</li><li>item2</li><li>item3</li><li>item5</li></ol>\"\n  );\n\n  t.is(\n    (await renderTemplate(pages[1].template, pages[1].data)).trim(),\n    \"<ol><li>item6</li><li>item7</li><li>item8</li><li>item9</li></ol>\"\n  );\n});\n\ntest(\"Pagination with deep data merge #147\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs\",\n      output: \"dist\",\n    }\n  });\n\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/paged/pagedinlinedata.njk\",\n    \"./test/stubs/\",\n    \"./dist\",\n    null,\n    null,\n    eleventyConfig\n  );\n  tmpl.config.keys.layout = \"layout\";\n\n  let data = await tmpl.getData();\n  let pages = await tmpl.getTemplates(data);\n  t.is(pages.length, 2);\n\n  t.is(pages[0].outputPath, \"./dist/paged/pagedinlinedata/index.html\");\n  t.is(\n    (await renderTemplate(pages[0].template, pages[0].data)).trim(),\n    \"<ol><li>item1</li><li>item2</li><li>item3</li><li>item4</li></ol>\"\n  );\n\n  t.is(pages[1].outputPath, \"./dist/paged/pagedinlinedata/1/index.html\");\n  t.is(\n    (await renderTemplate(pages[1].template, pages[1].data)).trim(),\n    \"<ol><li>item5</li><li>item6</li><li>item7</li><li>item8</li></ol>\"\n  );\n});\n\ntest(\"Pagination with deep data merge with alias #147\", async (t) => {\n  let tmpl = await getNewTemplate(\"./test/stubs/paged/pagedalias.njk\", \"./test/stubs/\", \"./dist\");\n  tmpl.config.dynamicPermalinks = true;\n\n  let data = await tmpl.getData();\n  let pages = await tmpl.getTemplates(data);\n\n  t.is(pages[0].outputPath, \"./dist/pagedalias/item1/index.html\");\n  t.is(pages[1].outputPath, \"./dist/pagedalias/item2/index.html\");\n\n  t.is((await renderTemplate(pages[0].template, pages[0].data)).trim(), \"item1\");\n  t.is((await renderTemplate(pages[1].template, pages[1].data)).trim(), \"item2\");\n});\n\ntest(\"Paginate data in frontmatter (reversed)\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/paged/pagedinlinedata-reverse.njk\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n\n  let data = await tmpl.getData();\n  let pages = await tmpl.getTemplates(data);\n  t.is(pages.length, 2);\n\n  t.is(pages[0].outputPath, \"./dist/paged/pagedinlinedata-reverse/index.html\");\n  t.is(\n    (await renderTemplate(pages[0].template, pages[0].data)).trim(),\n    \"<ol><li>item8</li><li>item7</li><li>item6</li><li>item5</li></ol>\"\n  );\n\n  t.is(pages[1].outputPath, \"./dist/paged/pagedinlinedata-reverse/1/index.html\");\n  t.is(\n    (await renderTemplate(pages[1].template, pages[1].data)).trim(),\n    \"<ol><li>item4</li><li>item3</li><li>item2</li><li>item1</li></ol>\"\n  );\n});\n\ntest(\"No circular dependency (does not throw)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance();\n\n  new Pagination(\n    null,\n    {\n      collections: {\n        tag1: [],\n      },\n      pagination: {\n        data: \"collections.tag1\",\n        size: 1,\n      },\n      tags: [\"tag2\"],\n    },\n    eleventyConfig\n  );\n\n  t.true(true);\n});\n\ntest(\"Circular dependency (pagination iterates over tag1 but also supplies pages to tag1)\", async (t) => {\n  await t.throwsAsync(async () => {\n    let eleventyConfig = await getTemplateConfigInstance();\n\n    new Pagination(\n      null,\n      {\n        collections: {\n          tag1: [],\n          tag2: [],\n        },\n        pagination: {\n          data: \"collections.tag1\",\n          size: 1,\n        },\n        tags: [\"tag1\"],\n      },\n      eleventyConfig\n    );\n  });\n});\n\ntest(\"Circular dependency but should not error because it uses eleventyExcludeFromCollections\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance();\n\n  new Pagination(\n    null,\n    {\n      eleventyExcludeFromCollections: true,\n      collections: {\n        tag1: [],\n        tag2: [],\n      },\n      pagination: {\n        data: \"collections.tag1\",\n        size: 1,\n      },\n      tags: [\"tag1\"],\n    },\n    eleventyConfig\n  );\n\n  t.true(true);\n});\n\ntest(\"Pagination `before` Callback\", async (t) => {\n  let tmpl = await getNewTemplate(\"./test/stubs/paged/paged-before.njk\", \"./test/stubs/\", \"./dist\");\n\n  let data = await tmpl.getData();\n  let templates = await tmpl.getTemplates(data);\n  t.deepEqual(templates[0].data.pagination.items, [\"item6\"]);\n  t.deepEqual(templates[0].data.myalias, \"item6\");\n});\n\ntest(\"Pagination `before` Callback with metadata\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/paged/paged-before-metadata.njk\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n\n  let data = await tmpl.getData();\n  let templates = await tmpl.getTemplates(data);\n  t.deepEqual(templates[0].data.pagination.items, [\"item3\"]);\n});\n\ntest(\"Pagination `before` Callback with a Filter\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/paged/paged-before-filter.njk\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n\n  let data = await tmpl.getData();\n  let templates = await tmpl.getTemplates(data);\n  t.deepEqual(templates[0].data.pagination.items, [\"item2\"]);\n  t.deepEqual(templates[0].data.myalias, \"item2\");\n});\n\ntest(\"Pagination `before` Callback with `reverse: true` (test order of operations)\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/paged/paged-before-and-reverse.njk\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n\n  let data = await tmpl.getData();\n  let templates = await tmpl.getTemplates(data);\n  t.deepEqual(templates[0].data.pagination.items, [\"item2\"]);\n});\n\ntest(\"Pagination new v0.10.0 href/hrefs\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs\",\n      output: \"dist\",\n    }\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  dataObj.setFileSystemSearch(new FileSystemSearch());\n  await dataObj.getGlobalData();\n\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/paged/paged.njk\",\n    \"./test/stubs/\",\n    \"./dist\",\n    dataObj,\n    null,\n    eleventyConfig\n  );\n\n  let data = await tmpl.getData();\n  let templates = await tmpl.getTemplates(data);\n  t.is(templates[0].data.pagination.hrefs.length, 2);\n  t.truthy(templates[0].data.pagination.href.first);\n  t.truthy(templates[0].data.pagination.href.last);\n  t.falsy(templates[0].data.pagination.href.previous);\n  t.truthy(templates[0].data.pagination.href.next);\n\n  t.is(templates[1].data.pagination.hrefs.length, 2);\n  t.truthy(templates[1].data.pagination.href.first);\n  t.truthy(templates[1].data.pagination.href.last);\n  t.truthy(templates[1].data.pagination.href.previous);\n  t.falsy(templates[1].data.pagination.href.next);\n});\n\ntest(\"Pagination new v0.10.0 page/pages\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs\",\n      output: \"dist\",\n    }\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  dataObj.setFileSystemSearch(new FileSystemSearch());\n  await dataObj.getGlobalData();\n\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/paged/paged.njk\",\n    \"./test/stubs/\",\n    \"./dist\",\n    dataObj,\n    null,\n    eleventyConfig\n  );\n\n  let data = await tmpl.getData();\n  let templates = await tmpl.getTemplates(data);\n\n  t.is(templates[0].data.pagination.pages.length, 2);\n  t.is(templates[0].data.pagination.pages[0].length, 5);\n  t.is(templates[0].data.pagination.pages[1].length, 3);\n  t.truthy(templates[0].data.pagination.page.first);\n  t.truthy(templates[0].data.pagination.page.last);\n  t.falsy(templates[0].data.pagination.page.previous);\n  t.truthy(templates[0].data.pagination.page.next);\n\n  t.is(templates[1].data.pagination.pages.length, 2);\n  t.is(templates[1].data.pagination.pages[0].length, 5);\n  t.is(templates[1].data.pagination.pages[1].length, 3);\n  t.truthy(templates[1].data.pagination.page.first);\n  t.truthy(templates[1].data.pagination.page.last);\n  t.truthy(templates[1].data.pagination.page.previous);\n  t.falsy(templates[1].data.pagination.page.next);\n});\n\ntest(\"Pagination new v0.10.0 alias\", async (t) => {\n  let tmpl = await getNewTemplate(\"./test/stubs/paged/pagedalias.njk\", \"./test/stubs/\", \"./dist\");\n\n  let data = await tmpl.getData();\n  let templates = await tmpl.getTemplates(data);\n\n  t.is(templates[0].data.pagination.alias, \"font.test\");\n  t.is(templates[1].data.pagination.alias, \"font.test\");\n});\n\ntest(\"Pagination make sure pageNumber is numeric for {{ pageNumber + 1 }} Issue #760\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/paged/pagedinlinedata.njk\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n\n  let data = await tmpl.getData();\n  let templates = await tmpl.getTemplates(data);\n  t.is(templates[0].data.pagination.pageNumber, 0);\n  t.not(templates[0].data.pagination.pageNumber, \"0\");\n});\n\ntest(\"Pagination mutable global data\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs/paged-global-data-mutable/\",\n      output: \"dist\",\n    }\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  dataObj.setFileSystemSearch(new FileSystemSearch());\n  await dataObj.getGlobalData();\n\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/paged-global-data-mutable/paged-differing-data-set.njk\",\n    \"./test/stubs/paged-global-data-mutable/\",\n    \"./dist\",\n    dataObj,\n    null,\n    eleventyConfig\n  );\n\n  let data = await tmpl.getData();\n  let templates = await tmpl.getTemplates(data);\n  t.is(templates.length, 3);\n  t.deepEqual(templates[0].data.pagination.items[0], {\n    key1: \"item1\",\n    key2: \"item2\",\n  });\n  t.deepEqual(templates[1].data.pagination.items[0], {\n    key3: \"item3\",\n    key4: \"item4\",\n  });\n  t.deepEqual(templates[2].data.pagination.items[0], {\n    key5: \"item5\",\n    key6: \"item6\",\n  });\n\n  t.deepEqual(templates[0].data.item, { key1: \"item1\", key2: \"item2\" });\n  t.deepEqual(templates[1].data.item, { key3: \"item3\", key4: \"item4\" });\n  t.deepEqual(templates[2].data.item, { key5: \"item5\", key6: \"item6\" });\n});\n\ntest(\"Pagination template/dir data files run once, Issue 919\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs-919\",\n      output: \"dist\",\n    }\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n\n  let tmpl = await getNewTemplate(\n    \"./test/stubs-919/test.njk\",\n    \"./test/stubs-919/\",\n    \"./dist\",\n    dataObj,\n    null,\n    eleventyConfig\n  );\n\n  let data = await tmpl.getData();\n  let templates = await tmpl.getTemplates(data);\n\n  t.is(templates.length, 3);\n  t.is(templates[0].data.test, templates[1].data.test);\n  t.is(templates[1].data.test, templates[2].data.test);\n});\n\ntest(\"Pagination and eleventyComputed permalink, issue #1555 and #1865\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/pagination-eleventycomputed-permalink.liquid\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n\n  let data = await tmpl.getData();\n  let templates = await tmpl.getTemplates(data);\n\n  t.is(templates[0].data.page.url, \"/venues/first/\");\n  t.is(templates[1].data.page.url, \"/venues/second/\");\n  t.is(templates[2].data.page.url, \"/venues/third/\");\n});\n\ntest(\"Pagination and eleventyComputed data, issues #2512, #2837, #3013\", async (t) => {\n  let templateLangs = [\"liquid\", \"html\", \"md\", \"njk\"];\n  let apostrophe = {\n    liquid: \"'\",\n    html: \"'\",\n    md: \"'\",\n    hbs: \"&amp;#x27;\",\n    mustache: \"&amp;#39;\",\n    njk: \"&amp;#39;\",\n  };\n\n  for (let lang of templateLangs) {\n    let msg = `lang: ${lang}`;\n    let le = lang === \"md\" ? \"\\n\" : \"\";\n\n    let elev = new Eleventy(`./test/stubs-3013/${lang}/`, `./test/stubs-3013/${lang}/_site`, {\n      source: \"cli\",\n      runMode: \"build\",\n    });\n    await elev.init();\n    let written = await elev.toJSON();\n\n    t.is(written[0].url, \"/paul-mescal/\", msg);\n    t.is(written[0].content, `<title>The Effervescent adventures of Paul Mescal</title>${le}`, msg);\n    t.is(written[1].url, \"/populace-and-power/\", msg);\n    t.is(\n      written[1].content,\n      `<title>Populace and Power: A user${apostrophe[lang]}s guide</title>${le}`,\n      msg\n    );\n  }\n});\n"
  },
  {
    "path": "test/PassthroughCopyBehaviorTest.js",
    "content": "import test from \"ava\";\nimport checkPassthroughCopyBehavior from \"../src/Util/PassthroughCopyBehaviorCheck.js\";\n\ntest(\"Standard use\", (t) => {\n  t.is(\n    checkPassthroughCopyBehavior(\n      {\n        serverPassthroughCopyBehavior: \"passthrough\",\n        serverOptions: {},\n      },\n      \"serve\"\n    ),\n    true\n  );\n});\n\ntest(\"Config fallback\", (t) => {\n  t.is(\n    checkPassthroughCopyBehavior(\n      {\n        serverPassthroughCopyBehavior: \"copy\",\n        serverOptions: {},\n      },\n      \"serve\"\n    ),\n    false\n  );\n});\n\ntest(\"Other dev server\", (t) => {\n  t.is(\n    checkPassthroughCopyBehavior(\n      {\n        serverPassthroughCopyBehavior: \"passthrough\",\n        serverOptions: {\n          module: \"somethingelse\",\n        },\n      },\n      \"serve\"\n    ),\n    false\n  );\n});\n\ntest(\"Non --serve run modes\", (t) => {\n  t.is(\n    checkPassthroughCopyBehavior(\n      {\n        serverPassthroughCopyBehavior: \"passthrough\",\n        serverOptions: {},\n      },\n      \"watch\"\n    ),\n    false\n  );\n\n  t.is(\n    checkPassthroughCopyBehavior(\n      {\n        serverPassthroughCopyBehavior: \"passthrough\",\n        serverOptions: {},\n      },\n      \"build\"\n    ),\n    false\n  );\n});\n"
  },
  {
    "path": "test/PathNormalizerTest.js",
    "content": "import test from \"ava\";\nimport path from \"path\";\n\nimport PathNormalizer from \"../src/Util/PathNormalizer.js\";\n\ntest(\"normalizeSeparator\", (t) => {\n  t.is(PathNormalizer.normalizeSeperator(\".\"), \".\");\n  t.is(PathNormalizer.normalizeSeperator(\"a/b\"), \"a/b\");\n  t.is(PathNormalizer.normalizeSeperator(\"a\\\\b\").replace(/\\\\/g, path.sep), \"a/b\");\n  t.is(PathNormalizer.normalizeSeperator(\"a\\\\b/c\").replace(/\\\\/g, path.sep), \"a/b/c\");\n  t.is(PathNormalizer.normalizeSeperator(undefined), undefined);\n});\n\ntest(\"getParts\", (t) => {\n  t.deepEqual(PathNormalizer.getParts(\".\"), []);\n  t.deepEqual(PathNormalizer.getParts(\"test/a/b\"), [\"test\", \"a\", \"b\"]);\n  t.deepEqual(PathNormalizer.getParts(\"test\\\\a\\\\b\".replace(/\\\\/g, path.sep)), [\"test\", \"a\", \"b\"]);\n});\n\ntest(\"getAllPaths\", (t) => {\n  t.deepEqual(PathNormalizer.getAllPaths(\".\"), []);\n  t.deepEqual(PathNormalizer.getAllPaths(\"test/a/b\"), [\"test\", \"test/a\", \"test/a/b\"]);\n  t.deepEqual(PathNormalizer.getAllPaths(\"test/a/b.liquid\"), [\"test\", \"test/a\", \"test/a/b.liquid\"]);\n});\n"
  },
  {
    "path": "test/PathPrefixer.js",
    "content": "import test from \"ava\";\nimport path from \"path\";\n\nimport PathPrefixer from \"../src/Util/PathPrefixer.js\";\n\ntest(\"joinUrlParts\", (t) => {\n  t.is(PathPrefixer.joinUrlParts(\"a\"), \"a\");\n  t.is(PathPrefixer.joinUrlParts(\"a\", \"b\"), \"a/b\");\n  t.is(PathPrefixer.joinUrlParts(\"\", \"a\", \"b\"), \"a/b\");\n  t.is(PathPrefixer.joinUrlParts(\"/a\", \"b\"), \"/a/b\");\n  t.is(PathPrefixer.joinUrlParts(\"a\", \"b\", \"c\"), \"a/b/c\");\n  t.is(PathPrefixer.joinUrlParts(\"a/b\", \"c/\"), \"a/b/c/\");\n});\n\ntest(\"joinUrlParts (Windows)\", (t) => {\n  // The replace calls are needed, since \"\\\" is a valid path char on unix\n  t.is(PathPrefixer.joinUrlParts(\"a\"), \"a\");\n  t.is(PathPrefixer.joinUrlParts(\"a\\\\b\".replace(/\\\\/g, path.sep)), \"a/b\");\n  t.is(PathPrefixer.joinUrlParts(\"\\\\a\\\\b\".replace(/\\\\/g, path.sep)), \"/a/b\");\n  t.is(PathPrefixer.joinUrlParts(\"a\\\\b\\\\c\".replace(/\\\\/g, path.sep)), \"a/b/c\");\n  t.is(PathPrefixer.joinUrlParts(\"a\\\\b\".replace(/\\\\/g, path.sep), \"c\"), \"a/b/c\");\n  t.is(PathPrefixer.joinUrlParts(\"a\\\\b\\\\c\\\\\".replace(/\\\\/g, path.sep)), \"a/b/c/\");\n  t.is(PathPrefixer.joinUrlParts(\"a\\\\b/c\\\\\".replace(/\\\\/g, path.sep)), \"a/b/c/\");\n});\n\ntest(\"normalizePathPrefix\", (t) => {\n  t.is(PathPrefixer.normalizePathPrefix(\"a\"), \"/a\");\n  t.is(PathPrefixer.normalizePathPrefix(\"a/b\"), \"/a/b\");\n  t.is(PathPrefixer.normalizePathPrefix(\"/a/b\"), \"/a/b\");\n  t.is(PathPrefixer.normalizePathPrefix(\"/\"), \"/\");\n  t.is(PathPrefixer.normalizePathPrefix(\"\"), \"/\");\n  t.is(PathPrefixer.normalizePathPrefix(undefined), \"/\");\n});\n"
  },
  {
    "path": "test/PluralizeTest.js",
    "content": "import test from \"ava\";\n\nimport pluralize from \"../src/Util/Pluralize.js\";\n\ntest(\"Pluralize\", (t) => {\n  t.is(pluralize(0, \"test\", \"tests\"), \"tests\");\n  t.is(pluralize(1, \"test\", \"tests\"), \"test\");\n  t.is(pluralize(2, \"test\", \"tests\"), \"tests\");\n  t.is(pluralize(3, \"test\", \"tests\"), \"tests\");\n  t.is(pluralize(3.5, \"test\", \"tests\"), \"tests\");\n});\n"
  },
  {
    "path": "test/PreserveClosingTagsPluginTest.js",
    "content": "import test from \"ava\";\n\nimport { PreserveClosingTagsPlugin } from \"../src/Plugins/PreserveClosingTagsPlugin.js\";\nimport Eleventy from \"../src/Eleventy.js\";\n\ntest(\"Using the PreserveClosingTagsPlugin plugin (meta off) #3356\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", \"./test/stubs-virtual/_site\", {\n    config: function (eleventyConfig) {\n      eleventyConfig.addPlugin(PreserveClosingTagsPlugin);\n\n      eleventyConfig.addTemplate(\"test.njk\", `<html><meta></html>`, {});\n    },\n  });\n\n  let results = await elev.toJSON();\n\tt.is(results[0].content, `<html><meta></html>`);\n});\n\ntest(\"Using the PreserveClosingTagsPlugin plugin (meta on) #3356\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", \"./test/stubs-virtual/_site\", {\n    config: function (eleventyConfig) {\n      eleventyConfig.addPlugin(PreserveClosingTagsPlugin, {\n        tags: [\"meta\"]\n      });\n\n      eleventyConfig.addTemplate(\"test.njk\", `<html><meta></html>`, {});\n    },\n  });\n\n  let results = await elev.toJSON();\n\tt.is(results[0].content, `<html><meta /></html>`);\n});\n\ntest(\"Using the PreserveClosingTagsPlugin plugin (meta on, link off) #3356\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", \"./test/stubs-virtual/_site\", {\n    config: function (eleventyConfig) {\n      eleventyConfig.addPlugin(PreserveClosingTagsPlugin, {\n        tags: [\"meta\"]\n      });\n\n      eleventyConfig.addTemplate(\"test.njk\", `<html><meta><link></html>`, {});\n    },\n  });\n\n  let results = await elev.toJSON();\n\tt.is(results[0].content, `<html><meta /><link></html>`);\n});\n\ntest(\"Using the PreserveClosingTagsPlugin plugin (meta on, link on) #3356\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", \"./test/stubs-virtual/_site\", {\n    config: function (eleventyConfig) {\n      eleventyConfig.addPlugin(PreserveClosingTagsPlugin, {\n        tags: [\"meta\", \"link\"]\n      });\n\n      eleventyConfig.addTemplate(\"test.njk\", `<html><meta><link></html>`, {});\n    },\n  });\n\n  let results = await elev.toJSON();\n\tt.is(results[0].content, `<html><meta /><link /></html>`);\n});\n\ntest(\"Using the PreserveClosingTagsPlugin plugin (meta on, link on, title off) #3356\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", \"./test/stubs-virtual/_site\", {\n    config: function (eleventyConfig) {\n      eleventyConfig.addPlugin(PreserveClosingTagsPlugin, {\n        tags: [\"meta\", \"link\"]\n      });\n\n      eleventyConfig.addTemplate(\"test.njk\", `<html><title>My Title</title><meta><link></html>`, {});\n    },\n  });\n\n  let results = await elev.toJSON();\n\tt.is(results[0].content, `<html><title>My Title</title><meta /><link /></html>`);\n});\n"
  },
  {
    "path": "test/ProjectDirectoriesTest.js",
    "content": "import test from \"ava\";\nimport ProjectDirectories from \"../src/Util/ProjectDirectories.js\";\n\ntest(\"Implied input\", t => {\n\tlet d = new ProjectDirectories();\n\n\tt.is(d.input, \"./\");\n\tt.is(d.inputFile, undefined);\n});\n\ntest(\"Input matches\", t => {\n\tlet d = new ProjectDirectories();\n\td.setInput(\"./test/\");\n\n\tt.is(d.input, \"./test/\");\n\tt.is(d.inputFile, undefined);\n});\n\ntest(\"Normalized input (has trailing slash)\", t => {\n\tlet d = new ProjectDirectories();\n\td.setInput(\"test/\");\n\tt.is(d.input, \"./test/\");\n\tt.is(d.inputFile, undefined);\n});\n\ntest(\"Normalized input (no trailing slash)\", t => {\n\tlet d = new ProjectDirectories();\n\td.setInput(\"test\");\n\tt.is(d.input, \"./test/\");\n\tt.is(d.inputFile, undefined);\n});\n\ntest(\"Input must exist\", t => {\n\tlet d = new ProjectDirectories();\n\tt.throws(() => d.setInput(\"does-not-exist\"));\n});\n\ntest(\"Input as file\", t => {\n\tlet d = new ProjectDirectories();\n\td.setInput(\"test/stubs/index.html\");\n\tt.is(d.input, \"./test/stubs/\");\n\tt.is(d.inputFile, \"./test/stubs/index.html\");\n});\n\ntest(\"Input as file (deep)\", t => {\n\tlet d = new ProjectDirectories();\n\td.setInput(\"test/stubs/img/stub.md\");\n\tt.is(d.input, \"./test/stubs/img/\");\n\tt.is(d.inputFile, \"./test/stubs/img/stub.md\");\n});\n\ntest(\"Input as file (deep with inputDir)\", t => {\n\tlet d = new ProjectDirectories();\n\td.setInput(\"test/stubs/img/stub.md\", \"test/stubs\");\n\tt.is(d.input, \"./test/stubs/\");\n\tt.is(d.inputFile, \"./test/stubs/img/stub.md\");\n});\n\ntest(\"Data directory, implied input and default data\", t => {\n\tlet d = new ProjectDirectories();\n\tt.is(d.data, \"./_data/\");\n});\n\ntest(\"Data directory matches, explicit input\", t => {\n\tlet d = new ProjectDirectories();\n\td.setInput(\"./test/\");\n\tt.is(d.data, \"./test/_data/\");\n});\n\ntest(\"Data directory matches, explicit input and data\", t => {\n\tlet d = new ProjectDirectories();\n\td.setInput(\"./test/\");\n\td.setData(\"mydata\");\n\tt.is(d.data, \"./test/mydata/\");\n});\n\ntest(\"Data directory matches, explicit input and data (order reversed)\", t => {\n\tlet d = new ProjectDirectories();\n\td.setData(\"mydata\");\n\td.setInput(\"./test/\");\n\tt.is(d.data, \"./test/mydata/\");\n});\n\ntest(\"includes implied, layouts are not\", t => {\n\tlet d = new ProjectDirectories();\n\tt.is(d.layouts, undefined);\n\tt.is(d.includes, \"./_includes/\");\n});\n\ntest(\"Layouts/includes, explicit\", t => {\n\tlet d = new ProjectDirectories();\n\td.setLayouts(\"layouts\");\n\td.setIncludes(\"includes\");\n\tt.is(d.layouts, \"./layouts/\");\n\tt.is(d.includes, \"./includes/\");\n\n\td.setInput(\"test\");\n\tt.is(d.layouts, \"./test/layouts/\");\n\tt.is(d.includes, \"./test/includes/\");\n\n\td.setLayouts(\"../layouts\");\n\td.setIncludes(\"../includes\");\n\tt.is(d.layouts, \"./layouts/\");\n\tt.is(d.includes, \"./includes/\");\n});\n\ntest(\"Output, implied\", t => {\n\tlet d = new ProjectDirectories();\n\tt.is(d.output, \"./_site/\");\n});\n\ntest(\"Content/template/input paths\", t => {\n\tlet d = new ProjectDirectories();\n\tt.is(d.getInputPath(\"test.md\"), \"./test.md\");\n\tt.is(d.getInputPath(\"./test.md\"), \"./test.md\");\n\tt.is(d.getLayoutPath(\"./layout.html\"), \"./_includes/layout.html\");\n\n\td.setInput(\"test\");\n\tt.is(d.getInputPath(\"test.md\"), \"./test/test.md\");\n\tt.is(d.getInputPath(\"./test.md\"), \"./test/test.md\");\n\tt.is(d.getLayoutPath(\"./layout.html\"), \"./test/_includes/layout.html\");\n});\n\ntest(\"Project file paths\", t => {\n\tlet d = new ProjectDirectories();\n\tt.is(d.getProjectPath(\"eleventy.config.js\"), \"./eleventy.config.js\");\n\tt.is(d.getProjectPath(\"./eleventy.config.js\"), \"./eleventy.config.js\");\n});\n\ntest(\"Input could be a glob!\", t => {\n\tlet d = new ProjectDirectories();\n\td.setInput(\"./test/*.md\");\n\tt.is(d.input, \"./test/\");\n\tt.is(d.inputFile, undefined);\n});\n\n\ntest(\"Setting values via config object\", t => {\n\tlet d = new ProjectDirectories();\n\n\td.setViaConfigObject({\n\t\tinput: \"test/stubs\",\n\t\toutput: \"dist\",\n\t});\n\n\tt.is(d.input, \"./test/stubs/\");\n\tt.is(d.inputFile, undefined);\n\tt.is(d.output, \"./dist/\");\n\tt.is(d.data, \"./test/stubs/_data/\");\n\tt.is(d.includes, \"./test/stubs/_includes/\");\n\tt.is(d.layouts, undefined);\n});\n\ntest(\"Setting values via config object (input relative dirs)\", t => {\n\tlet d = new ProjectDirectories();\n\td.setViaConfigObject({\n\t\tinput: \"test/stubs\",\n\t\tdata: \"globaldata\",\n\t\tlayouts: \"mylayouts\",\n\t\tincludes: \"components\",\n\t});\n\n\tt.is(d.input, \"./test/stubs/\");\n\tt.is(d.inputFile, undefined);\n\tt.is(d.output, \"./_site/\");\n\tt.is(d.data, \"./test/stubs/globaldata/\");\n\tt.is(d.includes, \"./test/stubs/components/\");\n\tt.is(d.layouts, \"./test/stubs/mylayouts/\");\n});\n\ntest(\"Setting values via config object (input relative dirs, parent dirs)\", t => {\n\tlet d = new ProjectDirectories();\n\td.setViaConfigObject({\n\t\tinput: \"test/stubs\",\n\t\tdata: \"../globaldata\",\n\t\tincludes: \"../components\",\n\t});\n\n\tt.is(d.input, \"./test/stubs/\");\n\tt.is(d.inputFile, undefined);\n\tt.is(d.output, \"./_site/\");\n\tt.is(d.data, \"./test/globaldata/\");\n\tt.is(d.includes, \"./test/components/\");\n\tt.is(d.layouts, undefined);\n});\n\ntest(\"Setting values via config object (eleventy-base-blog example)\", t => {\n\tlet d = new ProjectDirectories();\n\td.setViaConfigObject({\n\t\tinput: \"src\",\n\t\tincludes: \"../_includes\",\n\t\tdata: \"../_data\",\n\t\toutput: \"_site\"\n\t});\n\n\tt.is(d.input, \"./src/\");\n\tt.is(d.inputFile, undefined);\n\tt.is(d.output, \"./_site/\");\n\tt.is(d.data, \"./_data/\");\n\tt.is(d.includes, \"./_includes/\");\n\tt.is(d.layouts, undefined);\n});\n\ntest(\"Setting values via config object (empty string/false value)\", t => {\n\tlet d = new ProjectDirectories();\n\td.setViaConfigObject({\n\t\tinput: \"test/stubs\",\n\t\toutput: \"_site\",\n\t\tdata: false, // falsy supported for output, data, includes, and layouts (uses input dir)\n\t\tincludes: \"\",\n\t\t// layouts will be undefined when excluded here\n\t});\n\n\tt.is(d.input, \"./test/stubs/\");\n\tt.is(d.inputFile, undefined);\n\tt.is(d.output, \"./_site/\");\n\tt.is(d.data, \"./test/stubs/\");\n\tt.is(d.includes, \"./test/stubs/\");\n\tt.is(d.layouts, undefined);\n});\n\ntest(\"Setting values via config object (layouts is set but falsy)\", t => {\n\tlet d = new ProjectDirectories();\n\td.setViaConfigObject({\n\t\tinput: \"test/stubs\",\n\t\toutput: \"_site\",\n\t\tlayouts: \"\", // falsy supported for output, data, includes, and layouts (uses input dir)\n\t});\n\n\tt.is(d.input, \"./test/stubs/\");\n\tt.is(d.inputFile, undefined);\n\tt.is(d.output, \"./_site/\");\n\tt.is(d.data, \"./test/stubs/_data/\");\n\tt.is(d.includes, \"./test/stubs/_includes/\");\n\tt.is(d.layouts, \"./test/stubs/\");\n});\n\ntest(\"Setting values via config object (output is falsy)\", t => {\n\tlet d = new ProjectDirectories();\n\td.setViaConfigObject({\n\t\tinput: \"test/stubs\",\n\t\toutput: \"\",\n\t});\n\n\tt.is(d.input, \"./test/stubs/\");\n\tt.is(d.inputFile, undefined);\n\tt.is(d.output, \"./\");\n\tt.is(d.data, \"./test/stubs/_data/\");\n\tt.is(d.includes, \"./test/stubs/_includes/\");\n\tt.is(d.layouts, undefined);\n});\n\ntest(\"Setting values via config object (dots)\", t => {\n\tlet d = new ProjectDirectories();\n\td.setViaConfigObject({\n\t\tinput: \"test/stubs\",\n\t\toutput: \".\",\n\t\tincludes: \".\",\n\t\tlayouts: \".\",\n\t\tdata: \".\",\n\t});\n\n\tt.is(d.input, \"./test/stubs/\");\n\tt.is(d.inputFile, undefined);\n\tt.is(d.output, \"./\");\n\tt.is(d.data, \"./test/stubs/\");\n\tt.is(d.includes, \"./test/stubs/\");\n\tt.is(d.includes, \"./test/stubs/\");\n});\n\ntest(\"CLI values should override all others (both)\", t => {\n\tlet d = new ProjectDirectories();\n\td.setInput(\"src\");\n\td.setOutput(\"dist\");\n\td.freeze();\n\n\td.setViaConfigObject({\n\t\tinput: \"test/stubs\",\n\t\tincludes: \"myincludes\",\n\t});\n\n\tt.is(d.input, \"./src/\");\n\tt.is(d.inputFile, undefined);\n\tt.is(d.output, \"./dist/\");\n\tt.is(d.data, \"./src/_data/\");\n\tt.is(d.includes, \"./src/myincludes/\");\n\tt.is(d.layouts, undefined);\n});\n\ntest(\"CLI values should override all others (just input)\", t => {\n\tlet d = new ProjectDirectories();\n\td.setInput(\"src\");\n\td.freeze();\n\n\td.setViaConfigObject({\n\t\tinput: \"test/stubs\",\n\t\tincludes: \"myincludes\", // always okay, not a CLI param\n\t\toutput: \"dist\",\n\t});\n\n\tt.is(d.input, \"./src/\");\n\tt.is(d.inputFile, undefined);\n\tt.is(d.output, \"./dist/\");\n\tt.is(d.data, \"./src/_data/\");\n\tt.is(d.includes, \"./src/myincludes/\");\n\tt.is(d.layouts, undefined);\n});\n\ntest(\"CLI values should override all others (just output)\", t => {\n\tlet d = new ProjectDirectories();\n\td.setOutput(\"dist\");\n\td.freeze();\n\n\td.setViaConfigObject({\n\t\tinput: \"test/stubs\",\n\t\tincludes: \"myincludes\", // always okay, not a CLI param\n\t\toutput: \"someotherdir\",\n\t});\n\n\tt.is(d.input, \"./test/stubs/\");\n\tt.is(d.inputFile, undefined);\n\tt.is(d.output, \"./dist/\");\n\tt.is(d.data, \"./test/stubs/_data/\");\n\tt.is(d.includes, \"./test/stubs/myincludes/\");\n\tt.is(d.layouts, undefined);\n});\n\ntest(\"getLayoutPath (layouts dir)\", t => {\n\tlet d = new ProjectDirectories();\n\td.setViaConfigObject({\n\t\tinput: \"test/stubs\",\n\t\tlayouts: \"mylayouts\",\n\t\tincludes: \"components\",\n\t});\n\n\tt.is(d.getLayoutPath(\"layout.html\"), \"./test/stubs/mylayouts/layout.html\");\n\tt.is(d.getLayoutPathRelativeToInputDirectory(\"layout.html\"), \"mylayouts/layout.html\");\n});\n\ntest(\"getLayoutPath (includes dir)\", t => {\n\tlet d = new ProjectDirectories();\n\td.setViaConfigObject({\n\t\tinput: \"test/stubs\",\n\t\tincludes: \"components\",\n\t});\n\n\tt.is(d.getLayoutPath(\"layout.html\"), \"./test/stubs/components/layout.html\");\n\tt.is(d.getLayoutPathRelativeToInputDirectory(\"layout.html\"), \"components/layout.html\");\n});\n\ntest(\"isFileIn*Folder\", t => {\n\tlet d = new ProjectDirectories();\n\n\tt.is(d.isFileInProjectFolder(\"test.njk\"), true);\n\tt.is(d.isFileInProjectFolder(\"../test.njk\"), false);\n\n\tt.is(d.isFileInOutputFolder(\"test.njk\"), false);\n\tt.is(d.isFileInOutputFolder(\"../test.njk\"), false);\n\tt.is(d.isFileInOutputFolder(\"../_site/test.html\"), false);\n\tt.is(d.isFileInOutputFolder(\"_site/test.html\"), true);\n});\n\ntest(\"isFileInOutputFolder (change output folder)\", t => {\n\tlet d = new ProjectDirectories();\n\td.setOutput(\"yolo\")\n\n\tt.is(d.isFileInOutputFolder(\"test.njk\"), false);\n\tt.is(d.isFileInOutputFolder(\"../test.njk\"), false);\n\tt.is(d.isFileInOutputFolder(\"_site/test.html\"), false);\n\tt.is(d.isFileInOutputFolder(\"../_site/test.html\"), false);\n\tt.is(d.isFileInOutputFolder(\"yolo/test.html\"), true);\n});\n"
  },
  {
    "path": "test/ProjectTemplateFormatsTest.js",
    "content": "import test from \"ava\";\nimport ProjectTemplateFormats from \"../src/Util/ProjectTemplateFormats.js\";\n\nfunction getTestInstance() {\n  let tf = new ProjectTemplateFormats();\n  return tf;\n}\n\ntest(\"Empty formats\", t => {\n  let tf = new ProjectTemplateFormats();\n  t.deepEqual(tf.getTemplateFormats(), []);\n});\n\ntest(\"Return all eligible on no config or CLI\", t => {\n  let tf = getTestInstance();\n\n  t.deepEqual(tf.getTemplateFormats(), []);\n});\n\n\n// CLI\ntest(\"CLI\", t => {\n  let tf = getTestInstance();\n  tf.setViaCommandLine(\"md,html\");\n\n  t.deepEqual(tf.getTemplateFormats(), [\"md\", \"html\"]);\n});\n\ntest(\"CLI empty\", t => {\n  let tf = getTestInstance();\n  tf.setViaCommandLine(\"\");\n\n  t.deepEqual(tf.getTemplateFormats(), []);\n});\n\ntest(\"CLI *\", t => {\n  let tf = getTestInstance();\n  tf.setViaCommandLine(\"*\");\n\n  t.deepEqual(tf.getTemplateFormats(), []);\n});\n\n\n// Config Set\ntest(\"Config\", t => {\n  let tf = getTestInstance();\n  tf.setViaConfig(\"md,html\");\n\n  t.deepEqual(tf.getTemplateFormats(), [\"md\", \"html\"]);\n});\n\ntest(\"Config empty\", t => {\n  let tf = getTestInstance();\n  tf.setViaConfig(\"\");\n\n  t.deepEqual(tf.getTemplateFormats(), []);\n});\n\ntest(\"Config *\", t => {\n  let tf = getTestInstance();\n  tf.setViaConfig(\"*\");\n\n  t.deepEqual(tf.getTemplateFormats(), []);\n});\n\n\n// Config Add\ntest(\"Config Add\", t => {\n  let tf = getTestInstance();\n  // add without set unions all with new\n  tf.addViaConfig(\"md,html\");\n\n  t.deepEqual(tf.getTemplateFormats(), [\"md\", \"html\"]);\n});\n\ntest(\"Config Add (not yet known)\", t => {\n  let tf = getTestInstance();\n  // add without set unions all with new\n  tf.addViaConfig(\"vue\");\n\n  t.deepEqual(tf.getTemplateFormats(), [\"vue\"]);\n});\n\ntest(\"Config Add empty\", t => {\n  let tf = getTestInstance();\n  tf.addViaConfig(\"\");\n\n  t.deepEqual(tf.getTemplateFormats(), []);\n});\n\ntest(\"Config Add *\", t => {\n  let tf = getTestInstance();\n  t.throws(() => {\n    tf.addViaConfig(\"*\");\n  }, {\n    message: '`addTemplateFormats(\"*\")` is not supported for project template syntaxes.'\n  });\n});\n\ntest(\"Config Add Multiple\", t => {\n  let tf = getTestInstance();\n// While this does support multiple addTemplateFormat calls from config, they are collected and addViaConfig is only called once.\n  // add without set unions all with new\n  tf.addViaConfig(\"vue\");\n  tf.addViaConfig(\"pug\");\n  tf.addViaConfig(\"zbbbbb\");\n\n  t.deepEqual(tf.getTemplateFormats(), [\"zbbbbb\"]);\n});\n\n\n// CLI and Config (CLI wins every time)\ntest(\"CLI + Config\", t => {\n  let tf = getTestInstance();\n  tf.setViaCommandLine(\"md,html\");\n  tf.setViaConfig(\"liquid\");\n  tf.addViaConfig(\"vue\");\n\n  t.deepEqual(tf.getTemplateFormats(), [\"md\", \"html\"]);\n});\n\ntest(\"CLI + Config empty\", t => {\n  let tf = getTestInstance();\n  tf.setViaCommandLine(\"\");\n  tf.setViaConfig(\"liquid\");\n  tf.addViaConfig(\"vue\");\n\n  t.deepEqual(tf.getTemplateFormats(), []);\n});\n\ntest(\"CLI + Config *\", t => {\n  let tf = getTestInstance();\n  tf.setViaCommandLine(\"*\");\n  tf.setViaConfig(\"liquid\");\n  tf.addViaConfig(\"vue\");\n\n  t.deepEqual(tf.getTemplateFormats(), [\"liquid\", \"vue\"]);\n});\n\n\n// Config set and add\ntest(\"Config set/add\", t => {\n  let tf = getTestInstance();\n  tf.setViaConfig(\"liquid\");\n  tf.addViaConfig(\"vue\");\n\n  t.deepEqual(tf.getTemplateFormats(), [\"liquid\", \"vue\"]);\n});\n\ntest(\"Config set/add set undefined\", t => {\n  let tf = getTestInstance();\n  tf.setViaConfig(undefined);\n  tf.addViaConfig(\"vue\");\n\n  t.deepEqual(tf.getTemplateFormats(), [\"vue\"]);\n});\n\ntest(\"Config set/add add undefined\", t => {\n  let tf = getTestInstance();\n  tf.setViaConfig(\"vue\");\n  tf.addViaConfig(undefined);\n\n  t.deepEqual(tf.getTemplateFormats(), [\"vue\"]);\n});\n\ntest(\"Config set/add set empty\", t => {\n  let tf = getTestInstance();\n  tf.setViaConfig(\"\");\n  tf.addViaConfig(\"vue\");\n\n  t.deepEqual(tf.getTemplateFormats(), [\"vue\"]);\n});\n\ntest(\"Config set/add add empty\", t => {\n  let tf = getTestInstance();\n  tf.setViaConfig(\"vue\");\n  tf.addViaConfig(\"\");\n\n  t.deepEqual(tf.getTemplateFormats(), [\"vue\"]);\n});\n\ntest(\"Config set/add both empty\", t => {\n  let tf = getTestInstance();\n  tf.setViaConfig(\"\");\n  tf.addViaConfig(\"\");\n\n  t.deepEqual(tf.getTemplateFormats(), []);\n});\n\ntest(\"Config set *, add\", t => {\n  let tf = getTestInstance();\n  tf.setViaConfig(\"*\");\n  tf.addViaConfig(\"vue\");\n\n  t.deepEqual(tf.getTemplateFormats(), [\"vue\"]);\n});\n"
  },
  {
    "path": "test/ProxyWrapTest.js",
    "content": "import test from \"ava\";\nimport { ProxyWrap } from \"../src/Util/Objects/ProxyWrap.js\";\n\ntest(\"Basic wrap\", (t) => {\n  let test = ProxyWrap({}, { a: 1 });\n  t.is(test.a, 1);\n});\n\ntest(\"Nested wrap\", (t) => {\n  let test = ProxyWrap({}, { child: { a: 1, b: 1 } });\n  t.truthy(test.child);\n  t.deepEqual(test.child, { a: 1, b: 1 });\n  t.deepEqual(test.child.a, 1);\n  t.deepEqual(test.child.b, 1);\n});\n\ntest(\"Double nested wrap\", (t) => {\n  let child = {\n    a: 1,\n    b: 1,\n    c: {\n      grandchild: 1,\n    },\n  };\n  let test = ProxyWrap(\n    {},\n    {\n      child,\n    }\n  );\n  t.truthy(test.child);\n  t.deepEqual(test.child, child);\n  t.deepEqual(test.child.a, 1);\n  t.deepEqual(test.child.b, 1);\n  t.deepEqual(test.child.c.grandchild, 1);\n});\n\ntest(\"Array\", (t) => {\n  let test = ProxyWrap({}, { child: [1, 2, 3] });\n  t.deepEqual(test.child, [1, 2, 3]);\n  t.deepEqual(test.child[1], 2);\n});\n\ntest(\"Array nested\", (t) => {\n  let test = ProxyWrap({}, { child: [1, [2], 3] });\n  t.deepEqual(test.child, [1, [2], 3]);\n  t.deepEqual(test.child[1], [2]);\n  t.deepEqual(test.child[1][0], 2);\n});\n\ntest(\"Fails for invalid target\", (t) => {\n  t.throws(() => ProxyWrap(true, {}));\n});\n\ntest(\"Fails for invalid fallback\", (t) => {\n  t.throws(() => ProxyWrap({}, true));\n});\n\ntest(\"Frozen Object\", (t) => {\n  let test = ProxyWrap({}, Object.freeze({ eleventy: { generator: \"Eleventy v3.0.0\" } }));\n  t.deepEqual(test.eleventy.generator, \"Eleventy v3.0.0\");\n});\n\n\ntest(\"Frozen Nested Object\", (t) => {\n  let test = ProxyWrap({ eleventy: {} }, { eleventy: Object.freeze({ generator: \"Eleventy v3.0.0\" }) });\n  t.deepEqual(test.eleventy.generator, \"Eleventy v3.0.0\");\n});\n"
  },
  {
    "path": "test/ReservedDataTest.js",
    "content": "import test from \"ava\";\nimport ReservedData from \"../src/Util/ReservedData.js\";\nimport Eleventy from \"../src/Eleventy.js\";\n\ntest(\"No reserved Keys\", t => {\n  t.deepEqual(ReservedData.getReservedKeys({ key: {} }).sort(), []);\n});\n\ntest(\"Standard keys are reserved\", t => {\n  t.deepEqual(ReservedData.getReservedKeys({ content: \"\" }).sort(), [\"content\"]);\n  t.deepEqual(ReservedData.getReservedKeys({ collections: {} }).sort(), [\"collections\"]);\n  t.deepEqual(ReservedData.getReservedKeys({ content: \"\", collections: {} }).sort(), [\"collections\", \"content\"]);\n});\n\ntest(\"`page` subkeys\", t => {\n  t.deepEqual(ReservedData.getReservedKeys({ page: {} }).sort(), []);\n  t.deepEqual(ReservedData.getReservedKeys({ page: \"\" }).sort(), [\"page\"]);\n  t.deepEqual(ReservedData.getReservedKeys({ page: { date: \"\", otherkey: \"\" } }).sort(), [\"page.date\"]);\n  t.deepEqual(ReservedData.getReservedKeys({ page: { inputPath: \"\", otherkey: \"\" } }).sort(), [\"page.inputPath\"]);\n  t.deepEqual(ReservedData.getReservedKeys({ page: { fileSlug: \"\", otherkey: \"\" } }).sort(), [\"page.fileSlug\"]);\n  t.deepEqual(ReservedData.getReservedKeys({ page: { filePathStem: \"\", otherkey: \"\" } }).sort(), [\"page.filePathStem\"]);\n  t.deepEqual(ReservedData.getReservedKeys({ page: { outputFileExtension: \"\", otherkey: \"\" } }).sort(), [\"page.outputFileExtension\"]);\n  t.deepEqual(ReservedData.getReservedKeys({ page: { templateSyntax: \"\", otherkey: \"\" } }).sort(), [\"page.templateSyntax\"]);\n  t.deepEqual(ReservedData.getReservedKeys({ page: { url: \"\", otherkey: \"\" } }).sort(), [\"page.url\"]);\n  t.deepEqual(ReservedData.getReservedKeys({ page: { outputPath: \"\", otherkey: \"\" } }).sort(), [\"page.outputPath\"]);\n  t.deepEqual(ReservedData.getReservedKeys({ page: { date: \"\", outputPath: \"\", otherkey: \"\" } }).sort(), [\"page.date\", \"page.outputPath\"]);\n});\n\ntest(\"Eleventy freeze data set via config API throws error (page)\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    configPath: \"./test/stubs-virtual/eleventy.config.js\",\n    config: eleventyConfig => {\n      eleventyConfig.addGlobalData(\"page\", \"lol no\");\n      eleventyConfig.addTemplate(\"index.html\", ``);\n    }\n  });\n  elev.disableLogger();\n\n  await t.throwsAsync(() => elev.toJSON(), {\n    message: 'You attempted to set one of Eleventy’s reserved data property names: page (source: ./test/stubs-virtual/eleventy.config.js). You can opt-out of this behavior with `eleventyConfig.setFreezeReservedData(false)` or rename/remove the property in your data cascade that conflicts with Eleventy’s reserved property names (e.g. `eleventy`, `pkg`, and others). Learn more: https://v3.11ty.dev/docs/data-eleventy-supplied/'\n  });\n});\n\ntest(\"Eleventy freeze data set via config API throws error (eleventy)\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    configPath: \"./test/stubs-virtual/eleventy.config.js\",\n    config: eleventyConfig => {\n      eleventyConfig.addGlobalData(\"eleventy\", \"lol no\");\n      eleventyConfig.addTemplate(\"index.html\", ``);\n    }\n  });\n  elev.disableLogger();\n\n  await t.throwsAsync(() => elev.toJSON(), {\n    message: 'You attempted to set one of Eleventy’s reserved data property names: eleventy (source: ./test/stubs-virtual/eleventy.config.js). You can opt-out of this behavior with `eleventyConfig.setFreezeReservedData(false)` or rename/remove the property in your data cascade that conflicts with Eleventy’s reserved property names (e.g. `eleventy`, `pkg`, and others). Learn more: https://v3.11ty.dev/docs/data-eleventy-supplied/'\n  });\n});\n\ntest(\"Eleventy freeze data set global data file throws error (page)\", async (t) => {\n  let elev = new Eleventy({\n    input: \"./test/stubs-freeze/page/\",\n    config: eleventyConfig => {\n      eleventyConfig.addTemplate(\"index.html\", ``);\n    }\n  });\n  elev.disableLogger();\n\n  await t.throwsAsync(() => elev.toJSON(), {\n    message: 'You attempted to set one of Eleventy’s reserved data property names: page.url (source: ./test/stubs-freeze/page/_data/page.js). You can opt-out of this behavior with `eleventyConfig.setFreezeReservedData(false)` or rename/remove the property in your data cascade that conflicts with Eleventy’s reserved property names (e.g. `eleventy`, `pkg`, and others). Learn more: https://v3.11ty.dev/docs/data-eleventy-supplied/'\n  });\n});\n\ntest(\"Eleventy freeze data set global data file throws error (eleventy)\", async (t) => {\n  let elev = new Eleventy({\n    input: \"./test/stubs-freeze/eleventy/\",\n    config: eleventyConfig => {\n      eleventyConfig.addTemplate(\"index.html\", ``);\n    }\n  });\n  elev.disableLogger();\n\n  await t.throwsAsync(() => elev.toJSON(), {\n    message: 'You attempted to set one of Eleventy’s reserved data property names: eleventy (source: ./test/stubs-freeze/eleventy/_data/eleventy.js). You can opt-out of this behavior with `eleventyConfig.setFreezeReservedData(false)` or rename/remove the property in your data cascade that conflicts with Eleventy’s reserved property names (e.g. `eleventy`, `pkg`, and others). Learn more: https://v3.11ty.dev/docs/data-eleventy-supplied/'\n  });\n});\n"
  },
  {
    "path": "test/SemverCheckTest.js",
    "content": "import test from \"ava\";\nimport semver from \"semver\";\n\ntest(\"Satisfies sanity checks with beta/canary\", (t) => {\n  t.true(\n    semver.satisfies(\"1.0.0-beta.1\", \">= 0.7.0\", {\n      includePrerelease: true,\n    }),\n    \"1.0 Beta is valid for 0.x\"\n  );\n\n  t.true(\n    semver.satisfies(\"1.0.0-canary.1\", \">= 0.7.0\", {\n      includePrerelease: true,\n    }),\n    \"1.0 Canary is valid for 0.x\"\n  );\n\n  t.true(\n    semver.satisfies(\"1.0.0-beta.1\", \">= 0.7.0\", {\n      includePrerelease: true,\n    }),\n    \"1.0 Beta is valid for 0.x\"\n  );\n\n  t.true(\n    semver.satisfies(\"1.0.0\", \">= 0.7.0\", {\n      includePrerelease: true,\n    }),\n    \"1.0 is valid for 0.x\"\n  );\n\n  // keep canary around, it won’t have the `includePrerelease` option in `UserConfig->versionCheck`\n  t.true(\n    semver.satisfies(\"1.0.0\", \">=0.7 || >=1.0.0-canary\", {\n      includePrerelease: true,\n    })\n  );\n  t.true(\n    semver.satisfies(\"1.0.0-beta.1\", \">=0.7 || >=1.0.0-canary\", {\n      includePrerelease: true,\n    })\n  );\n  t.true(\n    semver.satisfies(\"1.0.0-canary.1\", \">=0.7 || >=1.0.0-canary\", {\n      includePrerelease: true,\n    })\n  );\n});\n"
  },
  {
    "path": "test/SortableTest.js",
    "content": "import test from \"ava\";\nimport { DateTime } from \"luxon\";\n\nimport Sortable from \"../src/Util/Objects/Sortable.js\";\n\ntest(\"get Sort Function\", (t) => {\n  let s = new Sortable();\n  t.deepEqual(s.getSortFunction(), Sortable.sortFunctionAlphabeticAscending);\n});\n\ntest(\"Alphabetic Ascending\", (t) => {\n  let s = new Sortable();\n  t.false(s.isSortNumeric);\n  t.true(s.isSortAscending);\n\n  s.add(\"a\");\n  s.add(\"z\");\n  s.add(\"m\");\n  t.deepEqual(s.sort(), [\"a\", \"m\", \"z\"]);\n});\n\ntest(\"Alphabetic Ascending (shortcut)\", (t) => {\n  let s = new Sortable();\n  s.add(\"a\");\n  s.add(\"z\");\n  s.add(\"m\");\n  t.deepEqual(s.sortAscending(), [\"a\", \"m\", \"z\"]);\n});\n\ntest(\"Alphabetic Descending\", (t) => {\n  let s = new Sortable();\n  s.setSortDescending();\n  t.false(s.isSortNumeric);\n  t.false(s.isSortAscending);\n\n  s.add(\"a\");\n  s.add(\"z\");\n  s.add(\"m\");\n  t.deepEqual(s.sort(), [\"z\", \"m\", \"a\"]);\n});\n\ntest(\"Alphabetic Descending (shortcut)\", (t) => {\n  let s = new Sortable();\n  s.add(\"a\");\n  s.add(\"z\");\n  s.add(\"m\");\n  t.deepEqual(s.sortDescending(), [\"z\", \"m\", \"a\"]);\n});\n\ntest(\"Numeric Ascending\", (t) => {\n  let s = new Sortable();\n  s.setSortNumeric(true);\n  t.true(s.isSortNumeric);\n  t.true(s.isSortAscending);\n\n  s.add(1);\n  s.add(4);\n  s.add(2);\n  t.deepEqual(s.sort(), [1, 2, 4]);\n});\n\ntest(\"Numeric Descending\", (t) => {\n  let s = new Sortable();\n  s.setSortNumeric(true);\n  s.setSortDescending();\n  t.true(s.isSortNumeric);\n  t.false(s.isSortAscending);\n\n  s.add(1);\n  s.add(4);\n  s.add(2);\n  t.deepEqual(s.sort(), [4, 2, 1]);\n});\n\ntest(\"Date Assumptions\", (t) => {\n  t.is(DateTime.fromISO(\"2007-10-10\") - new Date(2007, 9, 10).getTime(), 0);\n  t.is(DateTime.fromISO(\"2008-10-10\") - new Date(2008, 9, 10).getTime(), 0);\n  t.not(DateTime.fromISO(\"2008-10-10\") - new Date(2007, 9, 10).getTime(), 0);\n});\n\ntest(\"Date and Sortable Assumptions\", (t) => {\n  // Sortable works here without extra code because Luxon’s valueOf works in equality comparison (for alphabetic lists)\n  t.is(\n    Sortable.sortFunctionAlphabeticAscending(\n      DateTime.fromISO(\"2007-10-10\"),\n      new Date(2007, 9, 10).getTime()\n    ),\n    0\n  );\n  t.is(\n    Sortable.sortFunctionAlphabeticDescending(\n      DateTime.fromISO(\"2007-10-10\"),\n      new Date(2007, 9, 10).getTime()\n    ),\n    0\n  );\n\n  t.is(\n    Sortable.sortFunctionAlphabeticAscending(\n      DateTime.fromISO(\"2008-10-10\"),\n      new Date(2007, 9, 10).getTime()\n    ),\n    1\n  );\n  t.is(\n    Sortable.sortFunctionAlphabeticDescending(\n      DateTime.fromISO(\"2008-10-10\"),\n      new Date(2007, 9, 10).getTime()\n    ),\n    -1\n  );\n\n  // Sortable works here without extra code because Luxon’s valueOf works in subtraction (for numeric lists)\n  t.is(\n    Sortable.sortFunctionNumericAscending(\n      DateTime.fromISO(\"2008-10-10\"),\n      new Date(2008, 9, 10).getTime()\n    ),\n    0\n  );\n  t.is(\n    Sortable.sortFunctionNumericDescending(\n      DateTime.fromISO(\"2008-10-10\"),\n      new Date(2008, 9, 10).getTime()\n    ),\n    0\n  );\n\n  t.true(\n    Sortable.sortFunctionNumericAscending(\n      DateTime.fromISO(\"2008-10-10\"),\n      new Date(2007, 9, 10).getTime()\n    ) > 0\n  );\n  t.true(\n    Sortable.sortFunctionNumericDescending(\n      DateTime.fromISO(\"2008-10-10\"),\n      new Date(2007, 9, 10).getTime()\n    ) < 0\n  );\n});\n\ntest(\"Date Ascending\", (t) => {\n  let s = new Sortable();\n  let date1 = DateTime.fromISO(\"2007-10-10\");\n  let date2 = DateTime.fromISO(\"2008-10-10\");\n  let date3 = DateTime.fromISO(\"2009-10-10\");\n  s.add(date3);\n  s.add(date2);\n  s.add(date1);\n  t.deepEqual(s.sort(), [date1, date2, date3]);\n});\n\ntest(\"Date Descending\", (t) => {\n  let s = new Sortable();\n  s.setSortDescending();\n  let date1 = DateTime.fromISO(\"2007-10-10\");\n  let date2 = DateTime.fromISO(\"2008-10-10\");\n  let date3 = DateTime.fromISO(\"2009-10-10\");\n  s.add(date2);\n  s.add(date3);\n  s.add(date1);\n  t.deepEqual(s.sort(), [date3, date2, date1]);\n});\n\ntest(\"Alphabetic Ascending (short str sort arg)\", (t) => {\n  let s = new Sortable();\n  s.add(\"a\");\n  s.add(\"z\");\n  s.add(\"m\");\n  t.deepEqual(s.sort(\"A-Z\"), [\"a\", \"m\", \"z\"]);\n});\n\ntest(\"Alphabetic Descending (short str sort arg)\", (t) => {\n  let s = new Sortable();\n  s.add(\"a\");\n  s.add(\"z\");\n  s.add(\"m\");\n  t.deepEqual(s.sort(\"Z-A\"), [\"z\", \"m\", \"a\"]);\n});\n\ntest(\"Invalid Sort Function Name\", (t) => {\n  let s = new Sortable();\n  t.throws(() => s.sort(\"INVALID SORT STRING\"));\n});\n\ntest(\"Ascending / Descending Sorting Setters (asc, no param)\", (t) => {\n  let s = new Sortable();\n  s.isSortAscending = false;\n  s.setSortAscending();\n  t.is(s.isSortAscending, true);\n});\n\ntest(\"Ascending / Descending Sorting Setters (desc, no param)\", (t) => {\n  let s = new Sortable();\n  s.isSortAscending = true;\n  s.setSortDescending();\n  t.is(s.isSortAscending, false);\n});\n\ntest(\"Ascending / Descending Sorting Setters (asc, true)\", (t) => {\n  let s = new Sortable();\n  s.isSortAscending = false;\n  s.setSortAscending(true);\n  t.is(s.isSortAscending, true);\n});\n\ntest(\"Ascending / Descending Sorting Setters (asc, false)\", (t) => {\n  let s = new Sortable();\n  s.isSortAscending = true;\n  s.setSortAscending(false);\n  t.is(s.isSortAscending, false);\n});\n\ntest(\"Ascending / Descending Sorting Setters (desc, true)\", (t) => {\n  let s = new Sortable();\n  s.isSortAscending = true;\n  s.setSortDescending(true);\n  t.is(s.isSortAscending, false);\n});\n\ntest(\"Ascending / Descending Sorting Setters (desc, false)\", (t) => {\n  let s = new Sortable();\n  s.isSortAscending = false;\n  s.setSortDescending(false);\n  t.is(s.isSortAscending, true);\n});\n"
  },
  {
    "path": "test/TemplateCollectionTest.js",
    "content": "import test from \"ava\";\n\nimport { isGlobMatch } from \"../src/Util/GlobMatcher.js\";\nimport Collection from \"../src/TemplateCollection.js\";\nimport Sortable from \"../src/Util/Objects/Sortable.js\";\n\nimport getNewTemplateForTests from \"../test/_getNewTemplateForTests.js\";\nimport { getTemplateConfigInstance } from \"./_testHelpers.js\";\n\nfunction getNewTemplate(filename, input, output, eleventyConfig) {\n  return getNewTemplateForTests(filename, input, output, null, null, eleventyConfig);\n}\n\nfunction getNewTemplateByNumber(num, eleventyConfig) {\n  let extensions = [\"md\", \"md\", \"md\", \"md\", \"md\", \"html\", \"njk\", \"md\", \"md\", \"md\"];\n\n  return getNewTemplateForTests(\n    `./test/stubs/collection/test${num}.${extensions[num - 1]}`,\n    \"./test/stubs/\",\n    \"./test/stubs/_site\",\n    null,\n    null,\n    eleventyConfig,\n  );\n}\n\nasync function addTemplate(collection, template) {\n  let data = await template.getData();\n  for (let map of await template.getTemplates(data)) {\n    collection.add(map);\n  }\n}\n\ntest(\"Basic setup\", async (t) => {\n\tlet eleventyConfig = await getTemplateConfigInstance();\n\n  let tmpl1 = await getNewTemplateByNumber(1, eleventyConfig);\n  let tmpl2 = await getNewTemplateByNumber(2, eleventyConfig);\n  let tmpl3 = await getNewTemplateByNumber(3, eleventyConfig);\n\n  let c = new Collection();\n  await addTemplate(c, tmpl1);\n  await addTemplate(c, tmpl2);\n  await addTemplate(c, tmpl3);\n\n  t.is(c.length, 3);\n});\n\ntest(\"sortFunctionDate\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance();\n\n  let tmpl1 = await getNewTemplateByNumber(1, eleventyConfig);\n  let tmpl4 = await getNewTemplateByNumber(4, eleventyConfig);\n  let tmpl5 = await getNewTemplateByNumber(5, eleventyConfig);\n\n  let c = new Collection();\n  await addTemplate(c, tmpl1);\n  await addTemplate(c, tmpl4);\n  await addTemplate(c, tmpl5);\n\n  let posts = c.sort(Sortable.sortFunctionDate);\n  t.is(posts.length, 3);\n  t.deepEqual(posts[0].template, tmpl4);\n  t.deepEqual(posts[1].template, tmpl1);\n  t.deepEqual(posts[2].template, tmpl5);\n});\n\ntest(\"sortFunctionDateInputPath\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance();\n\n  let tmpl1 = await getNewTemplateByNumber(1, eleventyConfig);\n  let tmpl4 = await getNewTemplateByNumber(4, eleventyConfig);\n  let tmpl5 = await getNewTemplateByNumber(5, eleventyConfig);\n\n  let c = new Collection();\n  await addTemplate(c, tmpl1);\n  await addTemplate(c, tmpl4);\n  await addTemplate(c, tmpl5);\n\n  let posts = c.sort(Sortable.sortFunctionDateInputPath);\n  t.is(posts.length, 3);\n  t.deepEqual(posts[0].template, tmpl4);\n  t.deepEqual(posts[1].template, tmpl1);\n  t.deepEqual(posts[2].template, tmpl5);\n});\n\ntest(\"getFilteredByTag\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance();\n\n  let tmpl1 = await getNewTemplateByNumber(1, eleventyConfig);\n  let tmpl2 = await getNewTemplateByNumber(2, eleventyConfig);\n  let tmpl3 = await getNewTemplateByNumber(3, eleventyConfig);\n\n  let c = new Collection();\n  await addTemplate(c, tmpl1);\n  await addTemplate(c, tmpl2);\n  await addTemplate(c, tmpl3);\n\n  let posts = c.getFilteredByTag(\"post\");\n  t.is(posts.length, 2);\n  t.deepEqual(posts[0].template, tmpl1);\n  t.deepEqual(posts[1].template, tmpl3);\n\n  let cats = c.getFilteredByTag(\"cat\");\n  t.is(cats.length, 2);\n  t.deepEqual(cats[0].template, tmpl2);\n  t.deepEqual(cats[1].template, tmpl3);\n\n  let dogs = c.getFilteredByTag(\"dog\");\n  t.is(dogs.length, 1);\n  t.deepEqual(dogs[0].template, tmpl1);\n});\n\ntest(\"getFilteredByTag (added out of order, sorted)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance();\n\n  let tmpl1 = await getNewTemplateByNumber(1, eleventyConfig);\n  let tmpl2 = await getNewTemplateByNumber(2, eleventyConfig);\n  let tmpl3 = await getNewTemplateByNumber(3, eleventyConfig);\n\n  let c = new Collection();\n  await addTemplate(c, tmpl3);\n  await addTemplate(c, tmpl2);\n  await addTemplate(c, tmpl1);\n\n  let posts = c.getFilteredByTag(\"post\");\n  t.is(posts.length, 2);\n  t.deepEqual(posts[0].template, tmpl1);\n  t.deepEqual(posts[1].template, tmpl3);\n\n  let cats = c.getFilteredByTag(\"cat\");\n  t.truthy(cats.length);\n  t.is(cats.length, 2);\n  t.deepEqual(cats[0].template, tmpl2);\n  t.deepEqual(cats[1].template, tmpl3);\n\n  let dogs = c.getFilteredByTag(\"dog\");\n  t.truthy(dogs.length);\n  t.deepEqual(dogs[0].template, tmpl1);\n});\n\ntest(\"getFilteredByTags\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance();\n\n  let tmpl1 = await getNewTemplateByNumber(1, eleventyConfig);\n  let tmpl2 = await getNewTemplateByNumber(2, eleventyConfig);\n  let tmpl3 = await getNewTemplateByNumber(3, eleventyConfig);\n\n  let c = new Collection();\n  await addTemplate(c, tmpl1);\n  await addTemplate(c, tmpl2);\n  await addTemplate(c, tmpl3);\n\n  let postsAndCats = c.getFilteredByTags(\"post\", \"cat\");\n  t.is(postsAndCats.length, 1);\n  t.deepEqual(postsAndCats[0].template, tmpl3);\n\n  let cats = c.getFilteredByTags(\"cat\");\n  t.is(cats.length, 2);\n  t.deepEqual(cats[0].template, tmpl2);\n  t.deepEqual(cats[1].template, tmpl3);\n\n  let dogs = c.getFilteredByTags(\"dog\");\n  t.is(dogs.length, 1);\n  t.deepEqual(dogs[0].template, tmpl1);\n});\n\ntest(\"getFilteredByTags (added out of order, sorted)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance();\n\n  let tmpl1 = await getNewTemplateByNumber(1, eleventyConfig);\n  let tmpl2 = await getNewTemplateByNumber(2, eleventyConfig);\n  let tmpl3 = await getNewTemplateByNumber(3, eleventyConfig);\n\n  let c = new Collection();\n  await addTemplate(c, tmpl3);\n  await addTemplate(c, tmpl2);\n  await addTemplate(c, tmpl1);\n\n  let postsAndCats = c.getFilteredByTags(\"post\", \"cat\");\n  t.truthy(postsAndCats.length);\n  t.is(postsAndCats.length, 1);\n  t.deepEqual(postsAndCats[0].template, tmpl3);\n\n  let cats = c.getFilteredByTags(\"cat\");\n  t.truthy(cats.length);\n  t.is(cats.length, 2);\n  t.deepEqual(cats[0].template, tmpl2);\n  t.deepEqual(cats[1].template, tmpl3);\n\n  let dogs = c.getFilteredByTags(\"dog\");\n  t.truthy(dogs.length);\n  t.is(dogs.length, 1);\n  t.deepEqual(dogs[0].template, tmpl1);\n});\n\ntest(\"getFilteredByGlob\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance();\n\n  let tmpl1 = await getNewTemplateByNumber(1, eleventyConfig);\n  let tmpl6 = await getNewTemplateByNumber(6, eleventyConfig);\n  let tmpl7 = await getNewTemplateByNumber(7, eleventyConfig);\n\n  let c = new Collection();\n  await addTemplate(c, tmpl1);\n  await addTemplate(c, tmpl6);\n  await addTemplate(c, tmpl7);\n\n  let markdowns = c.getFilteredByGlob(\"./**/*.md\");\n  t.is(markdowns.length, 1);\n  t.deepEqual(markdowns[0].template, tmpl1);\n});\n\ntest(\"getFilteredByGlob no dash dot\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance();\n\n  let tmpl1 = await getNewTemplateByNumber(1, eleventyConfig);\n  let tmpl6 = await getNewTemplateByNumber(6, eleventyConfig);\n  let tmpl7 = await getNewTemplateByNumber(7, eleventyConfig);\n\n  let c = new Collection();\n  await addTemplate(c, tmpl1);\n  await addTemplate(c, tmpl6);\n  await addTemplate(c, tmpl7);\n\n  let markdowns = c.getFilteredByGlob(\"**/*.md\");\n  t.is(markdowns.length, 1);\n  t.deepEqual(markdowns[0].template, tmpl1);\n\n  let htmls = c.getFilteredByGlob(\"**/*.{html,njk}\");\n  t.is(htmls.length, 2);\n  t.deepEqual(htmls[0].template, tmpl6);\n  t.deepEqual(htmls[1].template, tmpl7);\n});\n\ntest(\"partial match on tag string, issue 95\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance();\n\n  let cat = await getNewTemplate(\n    \"./test/stubs/issue-95/cat.md\",\n    \"./test/stubs/\",\n    \"./test/stubs/_site\",\n    eleventyConfig,\n  );\n  let notacat = await getNewTemplate(\n    \"./test/stubs/issue-95/notacat.md\",\n    \"./test/stubs/\",\n    \"./test/stubs/_site\",\n    eleventyConfig,\n  );\n\n  let c = new Collection();\n  await addTemplate(c, cat);\n  await addTemplate(c, notacat);\n\n  let posts = c.getFilteredByTag(\"cat\");\n  t.is(posts.length, 1);\n});\n\n// Swapped to `micromatch` in 3.0.0-alpha.17, and later to `picomatch`.\n// The test can stay as a sanity check.\ntest(\"micromatch assumptions, issue #127\", async (t) => {\n  function isMatch(filepath, globs) {\n    return isGlobMatch(filepath, globs);\n  }\n  t.true(\n    isMatch(\"src/bookmarks/test.md\", [\"**/+(bookmarks|posts|screencasts)/**/!(index)*.md\"]),\n  );\n\n  t.true(\n    isMatch(\"./src/bookmarks/test.md\", [\"./**/+(bookmarks|posts|screencasts)/**/!(index)*.md\"]),\n  );\n\n  let c = new Collection();\n  let globs = c.getGlobs(\"**/+(bookmarks|posts|screencasts)/**/!(index)*.md\");\n  t.deepEqual(globs, [\"./**/+(bookmarks|posts|screencasts)/**/!(index)*.md\"]);\n\n  t.true(isMatch(\"./src/bookmarks/test.md\", globs));\n  t.false(isMatch(\"./src/bookmarks/index.md\", globs));\n  t.false(isMatch(\"./src/bookmarks/index2.md\", globs));\n  t.true(isMatch(\"./src/_content/bookmarks/2018-03-27-git-message.md\", globs));\n});\n\ntest(\"Sort in place (issue #352)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance();\n\n  let tmpl1 = await getNewTemplateByNumber(1, eleventyConfig);\n  let tmpl4 = await getNewTemplateByNumber(4, eleventyConfig);\n  let tmpl5 = await getNewTemplateByNumber(5, eleventyConfig);\n\n  let c = new Collection();\n  await addTemplate(c, tmpl1);\n  await addTemplate(c, tmpl4);\n  await addTemplate(c, tmpl5);\n\n  let posts = c.getAllSorted();\n  t.is(posts.length, 3);\n  t.deepEqual(posts[0].template, tmpl4);\n  t.deepEqual(posts[1].template, tmpl1);\n  t.deepEqual(posts[2].template, tmpl5);\n\n  let posts2 = c.getAllSorted().reverse();\n  t.is(posts2.length, 3);\n  t.deepEqual(posts2[0].template, tmpl5);\n  t.deepEqual(posts2[1].template, tmpl1);\n  t.deepEqual(posts2[2].template, tmpl4);\n\n  let posts3 = c.getAllSorted().reverse();\n  t.is(posts3.length, 3);\n  t.deepEqual(posts3[0].template, tmpl5);\n  t.deepEqual(posts3[1].template, tmpl1);\n  t.deepEqual(posts3[2].template, tmpl4);\n});\n\ntest(\"getFilteredByTag with excludes\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance();\n\n  let tmpl8 = await getNewTemplateByNumber(8, eleventyConfig);\n  let tmpl9 = await getNewTemplateByNumber(9, eleventyConfig);\n  let tmpl10 = await getNewTemplateByNumber(10, eleventyConfig);\n\n  let c = new Collection();\n  await addTemplate(c, tmpl8);\n  await addTemplate(c, tmpl9);\n  await addTemplate(c, tmpl10);\n\n  let posts = c.getFilteredByTag(\"post\");\n  t.is(posts.length, 0);\n\n  let offices = c.getFilteredByTag(\"office\");\n  offices.sort(Sortable.sortFunctionDate);\n\n  t.is(offices.length, 2);\n  t.deepEqual(offices[0].template, tmpl10);\n  t.deepEqual(offices[1].template, tmpl9);\n});\n"
  },
  {
    "path": "test/TemplateConfigTest.js",
    "content": "import test from \"ava\";\nimport md from \"markdown-it\";\n\nimport TemplateConfig from \"../src/TemplateConfig.js\";\nimport defaultConfig from \"../src/defaultConfig.js\";\n\nimport { getTemplateConfigInstance } from \"./_testHelpers.js\";\n\ntest(\"Template Config local config overrides base config\", async (t) => {\n  let templateCfg = new TemplateConfig(defaultConfig, \"./test/stubs/config.cjs\");\n  await templateCfg.init();\n\n  let cfg = templateCfg.getConfig();\n\n  t.is(cfg.markdownTemplateEngine, \"njk\");\n  t.is(cfg.templateFormats.join(\",\"), \"md,njk\");\n\n  // merged, not overwritten\n  t.true(Object.keys(cfg.keys).length > 1);\n  t.truthy(Object.keys(cfg.nunjucksFilters).length);\n\n  t.is(Object.keys(cfg.transforms).length, 4);\n\n  t.is(\n    cfg.transforms.prettyHtml(`<html><body><div></div></body></html>`, \"test.html\"),\n    `<html>\n  <body>\n    <div></div>\n  </body>\n</html>`,\n  );\n});\n\ntest(\"Add liquid tag\", async (t) => {\n  let templateCfg = new TemplateConfig(defaultConfig, \"./test/stubs/config.cjs\");\n  templateCfg.userConfig.addLiquidTag(\"myTagName\", function () {});\n\n  await templateCfg.init();\n\n  let cfg = templateCfg.getConfig();\n  t.not(Object.keys(cfg.liquidTags).indexOf(\"myTagName\"), -1);\n});\n\ntest(\"Add nunjucks tag\", async (t) => {\n  let templateCfg = new TemplateConfig(defaultConfig, \"./test/stubs/config.cjs\");\n  templateCfg.userConfig.addNunjucksTag(\"myNunjucksTag\", function () {});\n\n  await templateCfg.init();\n\n  let cfg = templateCfg.getConfig();\n  t.not(Object.keys(cfg.nunjucksTags).indexOf(\"myNunjucksTag\"), -1);\n});\n\ntest(\"Add nunjucks global\", async (t) => {\n  let templateCfg = new TemplateConfig(defaultConfig, \"./test/stubs/config.cjs\");\n  templateCfg.userConfig.addNunjucksGlobal(\"myNunjucksGlobal1\", function () {});\n  templateCfg.userConfig.addNunjucksGlobal(\"myNunjucksGlobal2\", 42);\n\n  await templateCfg.init();\n\n  let cfg = templateCfg.getConfig();\n  t.not(Object.keys(cfg.nunjucksGlobals).indexOf(\"myNunjucksGlobal1\"), -1);\n  t.not(Object.keys(cfg.nunjucksGlobals).indexOf(\"myNunjucksGlobal2\"), -1);\n});\n\ntest(\"Add liquid filter\", async (t) => {\n  let templateCfg = new TemplateConfig(defaultConfig, \"./test/stubs/config.cjs\");\n  templateCfg.userConfig.addLiquidFilter(\"myFilterName\", function (liquidEngine) {\n    return {};\n  });\n\n  await templateCfg.init();\n\n  let cfg = templateCfg.getConfig();\n  t.not(Object.keys(cfg.liquidFilters).indexOf(\"myFilterName\"), -1);\n});\n\ntest(\"Add nunjucks filter\", async (t) => {\n  let templateCfg = new TemplateConfig(defaultConfig, \"./test/stubs/config.cjs\");\n  templateCfg.userConfig.addNunjucksFilter(\"myFilterName\", function () {});\n\n  await templateCfg.init();\n\n  let cfg = templateCfg.getConfig();\n  t.not(Object.keys(cfg.nunjucksFilters).indexOf(\"myFilterName\"), -1);\n});\n\ntest(\"Add universal filter\", async (t) => {\n  let templateCfg = new TemplateConfig(defaultConfig, \"./test/stubs/config.cjs\");\n  templateCfg.userConfig.addFilter(\"myFilterName\", function () {});\n\n  await templateCfg.init();\n\n  let cfg = templateCfg.getConfig();\n  t.not(Object.keys(cfg.liquidFilters).indexOf(\"myFilterName\"), -1);\n  t.not(Object.keys(cfg.nunjucksFilters).indexOf(\"myFilterName\"), -1);\n});\n\ntest(\"Add namespaced universal filter\", async (t) => {\n  let templateCfg = new TemplateConfig(defaultConfig, \"./test/stubs/config.cjs\");\n  templateCfg.userConfig.namespace(\"testNamespace\", function () {\n    templateCfg.userConfig.addFilter(\"MyFilterName\", function () {});\n  });\n\n  await templateCfg.init();\n\n  let cfg = templateCfg.getConfig();\n  t.not(Object.keys(cfg.liquidFilters).indexOf(\"testNamespaceMyFilterName\"), -1);\n  t.not(Object.keys(cfg.nunjucksFilters).indexOf(\"testNamespaceMyFilterName\"), -1);\n});\n\ntest(\"Add namespaced universal filter using underscore\", async (t) => {\n  let templateCfg = new TemplateConfig(defaultConfig, \"./test/stubs/config.cjs\");\n  templateCfg.userConfig.namespace(\"testNamespace_\", function () {\n    templateCfg.userConfig.addFilter(\"myFilterName\", function () {});\n  });\n\n  await templateCfg.init();\n\n  let cfg = templateCfg.getConfig();\n  t.not(Object.keys(cfg.liquidFilters).indexOf(\"testNamespace_myFilterName\"), -1);\n  t.not(Object.keys(cfg.nunjucksFilters).indexOf(\"testNamespace_myFilterName\"), -1);\n});\n\ntest(\"Add namespaced plugin\", async (t) => {\n  let templateCfg = new TemplateConfig();\n\n  templateCfg.userConfig.namespace(\"testNamespace\", function () {\n    templateCfg.userConfig.addPlugin(function (eleventyConfig) {\n      eleventyConfig.addFilter(\"MyFilterName\", function () {});\n    });\n  });\n\n  await templateCfg.init();\n\n  let cfg = templateCfg.getConfig();\n  t.not(Object.keys(cfg.liquidFilters).indexOf(\"testNamespaceMyFilterName\"), -1);\n  t.not(Object.keys(cfg.nunjucksFilters).indexOf(\"testNamespaceMyFilterName\"), -1);\n});\n\ntest(\"Add namespaced plugin using underscore\", async (t) => {\n  let templateCfg = new TemplateConfig(defaultConfig, \"./test/stubs/config.cjs\");\n  templateCfg.userConfig.namespace(\"testNamespace_\", function () {\n    templateCfg.userConfig.addPlugin(function (config) {\n      config.addFilter(\"myFilterName\", function () {});\n    });\n  });\n\n  await templateCfg.init();\n\n  let cfg = templateCfg.getConfig();\n  t.not(Object.keys(cfg.liquidFilters).indexOf(\"testNamespace_myFilterName\"), -1);\n  t.not(Object.keys(cfg.nunjucksFilters).indexOf(\"testNamespace_myFilterName\"), -1);\n});\n\ntest(\"Empty namespace\", async (t) => {\n  let templateCfg = new TemplateConfig(defaultConfig, \"./test/stubs/config.cjs\");\n  templateCfg.userConfig.namespace(\"\", function () {\n    templateCfg.userConfig.addNunjucksFilter(\"myFilterName\", function () {});\n  });\n\n  await templateCfg.init();\n\n  let cfg = templateCfg.getConfig();\n  t.not(Object.keys(cfg.nunjucksFilters).indexOf(\"myFilterName\"), -1);\n});\n\ntest(\"Nested Empty Inner namespace\", async (t) => {\n  let templateCfg = new TemplateConfig(defaultConfig, \"./test/stubs/config.cjs\");\n\n  templateCfg.userConfig.namespace(\"testNs\", function () {\n    templateCfg.userConfig.namespace(\"\", function () {\n      templateCfg.userConfig.addNunjucksFilter(\"myFilterName\", function () {});\n    });\n  });\n\n  await templateCfg.init();\n\n  let cfg = templateCfg.getConfig();\n  t.not(Object.keys(cfg.nunjucksFilters).indexOf(\"testNsmyFilterName\"), -1);\n});\n\ntest(\"Nested Empty Outer namespace\", async (t) => {\n  let templateCfg = new TemplateConfig(defaultConfig, \"./test/stubs/config.cjs\");\n  templateCfg.userConfig.namespace(\"\", function () {\n    templateCfg.userConfig.namespace(\"testNs\", function () {\n      templateCfg.userConfig.addNunjucksFilter(\"myFilterName\", function () {});\n    });\n  });\n\n  await templateCfg.init();\n\n  let cfg = templateCfg.getConfig();\n  t.not(Object.keys(cfg.nunjucksFilters).indexOf(\"testNsmyFilterName\"), -1);\n});\n\n// important for backwards compatibility with old\n// `module.exports = function (eleventyConfig, pluginNamespace) {`\n// plugin code\ntest(\"Non-string namespaces are ignored\", async (t) => {\n  let templateCfg = new TemplateConfig(defaultConfig, \"./test/stubs/config.cjs\");\n  templateCfg.userConfig.namespace([\"lkdsjflksd\"], function () {\n    templateCfg.userConfig.addNunjucksFilter(\"myFilterName\", function () {});\n  });\n\n  await templateCfg.init();\n\n  let cfg = templateCfg.getConfig();\n  t.not(Object.keys(cfg.nunjucksFilters).indexOf(\"myFilterName\"), -1);\n});\n\ntest(\".addPlugin oddity: I don’t think pluginNamespace was ever passed in here, but we don’t want this to break\", async (t) => {\n  let templateCfg = new TemplateConfig(defaultConfig, \"./test/stubs/config.cjs\");\n\n  templateCfg.userConfig.addPlugin(function (eleventyConfig, pluginNamespace) {\n    eleventyConfig.namespace(pluginNamespace, () => {\n      eleventyConfig.addNunjucksFilter(\"myFilterName\", function () {});\n    });\n  });\n\n  await templateCfg.init();\n\n  let cfg = templateCfg.getConfig();\n  t.not(Object.keys(cfg.nunjucksFilters).indexOf(\"myFilterName\"), -1);\n});\n\ntest(\"Test url universal filter with custom pathPrefix (no slash)\", async (t) => {\n  let templateCfg = new TemplateConfig(defaultConfig, \"./test/stubs/config.cjs\");\n  templateCfg.setPathPrefix(\"/testdirectory/\");\n\n  await templateCfg.init();\n\n  let cfg = templateCfg.getConfig();\n  t.is(cfg.pathPrefix, \"/testdirectory/\");\n});\n\ntest(\"setTemplateFormats(string)\", async (t) => {\n  let templateCfg = new TemplateConfig(defaultConfig, \"./test/stubs/config.cjs\");\n  // 0.11.0 removes dupes\n  templateCfg.userConfig.setTemplateFormats(\"njk, liquid, njk\");\n\n  await templateCfg.init();\n\n  let cfg = templateCfg.getConfig();\n  t.deepEqual(cfg.templateFormats, [\"njk\", \"liquid\"]);\n});\n\ntest(\"setTemplateFormats(array)\", async (t) => {\n  let templateCfg = new TemplateConfig(defaultConfig, \"./test/stubs/config.cjs\");\n  templateCfg.userConfig.setTemplateFormats([\"njk\", \"liquid\"]);\n\n  await templateCfg.init();\n\n  let cfg = templateCfg.getConfig();\n  t.deepEqual(cfg.templateFormats, [\"njk\", \"liquid\"]);\n});\n\ntest(\"setTemplateFormats(array, size 1)\", async (t) => {\n  let templateCfg = new TemplateConfig(defaultConfig, \"./test/stubs/config.cjs\");\n  templateCfg.userConfig.setTemplateFormats([\"liquid\"]);\n\n  await templateCfg.init();\n\n  let cfg = templateCfg.getConfig();\n  t.deepEqual(cfg.templateFormats, [\"liquid\"]);\n});\n\ntest(\"setTemplateFormats(empty array)\", async (t) => {\n  let templateCfg = new TemplateConfig(defaultConfig, \"./test/stubs/config.cjs\");\n  templateCfg.userConfig.setTemplateFormats([]);\n\n  await templateCfg.init();\n\n  let cfg = templateCfg.getConfig();\n  t.deepEqual(cfg.templateFormats, []);\n});\n\ntest(\"setTemplateFormats(null)\", async (t) => {\n  let templateCfg = new TemplateConfig(defaultConfig, \"./test/stubs/config.cjs\");\n  templateCfg.userConfig.setTemplateFormats(null);\n\n  await templateCfg.init();\n\n  let cfg = templateCfg.getConfig();\n  t.deepEqual([...cfg.templateFormats].sort(), [\"md\", \"njk\"]);\n});\n\ntest(\"setTemplateFormats(undefined)\", async (t) => {\n  let templateCfg = new TemplateConfig(defaultConfig, \"./test/stubs/config.cjs\");\n  templateCfg.userConfig.setTemplateFormats(undefined);\n\n  await templateCfg.init();\n\n  let cfg = templateCfg.getConfig();\n  t.deepEqual([...cfg.templateFormats].sort(), [\"md\", \"njk\"]);\n});\n\ntest(\"multiple setTemplateFormats calls\", async (t) => {\n  let templateCfg = new TemplateConfig(defaultConfig, \"./test/stubs/config.cjs\");\n  templateCfg.userConfig.setTemplateFormats(\"njk\");\n  templateCfg.userConfig.setTemplateFormats(\"pug\");\n\n  await templateCfg.init();\n\n  let cfg = templateCfg.getConfig();\n  t.deepEqual(cfg.templateFormats, [\"pug\"]);\n});\n\ntest(\"addTemplateFormats()\", async (t) => {\n  let templateCfg = new TemplateConfig(defaultConfig, \"./test/stubs/config.cjs\");\n  templateCfg.userConfig.addTemplateFormats(\"vue\");\n  await templateCfg.init();\n\n  let cfg = templateCfg.getConfig();\n  // should have ALL of the original defaults\n  t.deepEqual(cfg.templateFormats, [\"md\", \"njk\", \"vue\"]);\n});\n\ntest(\"addTemplateFormats() via Plugin\", async (t) => {\n  let templateCfg = new TemplateConfig();\n  templateCfg.userConfig.addTemplateFormats(\"pug\");\n  templateCfg.userConfig.addPlugin(cfg => {\n    cfg.addTemplateFormats(\"webc\");\n  });\n  await templateCfg.init();\n\n  let cfg = templateCfg.getConfig();\n  t.deepEqual(cfg.templateFormats, [\"liquid\", \"md\", \"njk\", \"html\", \"11ty.js\", \"pug\", \"webc\"]);\n});\n\n\ntest(\"both setTemplateFormats and addTemplateFormats\", async (t) => {\n  // Template Formats can come from three places\n  // defaultConfig.js config API (not used yet)\n  // defaultConfig.js config return object\n  // project config file config API\n  // project config file config return object\n\n  let templateCfg = new TemplateConfig(defaultConfig, \"./test/stubs/config.cjs\");\n  templateCfg.userConfig.addTemplateFormats(\"vue\");\n  templateCfg.userConfig.setTemplateFormats(\"pug\");\n  await templateCfg.init();\n\n  let cfg = templateCfg.getConfig();\n  t.deepEqual(cfg.templateFormats, [\"pug\", \"vue\"]);\n});\n\ntest(\"addTemplateFormats() Array\", async (t) => {\n  let templateCfg = new TemplateConfig(defaultConfig, \"./test/stubs/config.cjs\");\n  templateCfg.userConfig.addTemplateFormats(\"vue2\");\n  templateCfg.userConfig.addTemplateFormats([\"vue\"]);\n  templateCfg.userConfig.addTemplateFormats([\"text\", \"txt\"]);\n  await templateCfg.init();\n\n  let cfg = templateCfg.getConfig();\n  // should have ALL of the original defaults\n  t.deepEqual(cfg.templateFormats, [\"md\", \"njk\", \"vue2\", \"vue\", \"text\", \"txt\"]);\n});\n\ntest(\"libraryOverrides\", async (t) => {\n  let mdLib = md();\n  let templateCfg = new TemplateConfig(defaultConfig, \"./test/stubs/config.cjs\");\n  templateCfg.userConfig.setLibrary(\"md\", mdLib);\n  await templateCfg.init();\n\n  let cfg = templateCfg.getConfig();\n  t.falsy(cfg.libraryOverrides.ldkja);\n  t.falsy(cfg.libraryOverrides.njk);\n  t.truthy(cfg.libraryOverrides.md);\n  t.deepEqual(mdLib, cfg.libraryOverrides.md);\n});\n\ntest(\"addGlobalData\", async (t) => {\n  let templateCfg = new TemplateConfig(defaultConfig, \"./test/stubs/config.cjs\");\n  templateCfg.userConfig.addGlobalData(\"function\", () => new Date());\n\n  await templateCfg.init();\n\n  let cfg = templateCfg.getConfig();\n  t.not(Object.keys(cfg.globalData).indexOf(\"function\"), -1);\n});\n\ntest(\"Properly throws error on missing module #182\", async (t) => {\n  await t.throwsAsync(async () => {\n    let templateCfg = new TemplateConfig(defaultConfig, \"./test/stubs/broken-config.cjs\");\n\n    await templateCfg.init();\n\n    templateCfg.getConfig();\n  });\n});\n\ntest(\"Properly throws error when config returns a Promise\", async (t) => {\n  await t.throwsAsync(async () => {\n    let templateCfg = new TemplateConfig(defaultConfig, \"./test/stubs/config-promise.js\");\n    await templateCfg.init();\n\n    templateCfg.getConfig();\n  });\n});\n\ntest(\".addWatchTarget adds a watch target\", async (t) => {\n  let templateCfg = new TemplateConfig(defaultConfig, \"./test/stubs/config.cjs\");\n  templateCfg.userConfig.addWatchTarget(\"/testdirectory/\");\n\n  await templateCfg.init();\n\n  let cfg = templateCfg.getConfig();\n  t.deepEqual(cfg.additionalWatchTargets, [\"/testdirectory/\"]);\n});\n\ntest(\"Nested .addPlugin calls\", async (t) => {\n  t.plan(2);\n  let templateCfg = new TemplateConfig();\n\n  templateCfg.userConfig.addPlugin(function OuterPlugin(eleventyConfig) {\n    t.truthy(true);\n\n    eleventyConfig.addPlugin(function InnerPlugin(eleventyConfig) {\n      t.truthy(true);\n    });\n  });\n\n  await templateCfg.init();\n\n  templateCfg.getConfig();\n});\n\ntest(\"Nested .addPlugin calls (×3)\", async (t) => {\n  t.plan(3);\n  let templateCfg = new TemplateConfig();\n\n  templateCfg.userConfig.addPlugin(function OuterPlugin(eleventyConfig) {\n    t.truthy(true);\n\n    eleventyConfig.addPlugin(function InnerPlugin(eleventyConfig) {\n      t.truthy(true);\n\n      eleventyConfig.addPlugin(function InnerPlugin(eleventyConfig) {\n        t.truthy(true);\n      });\n    });\n  });\n\n  await templateCfg.init();\n\n  templateCfg.getConfig();\n});\n\ntest(\"Nested .addPlugin calls order\", async (t) => {\n  t.plan(3);\n  let templateCfg = new TemplateConfig();\n  let order = [];\n\n  templateCfg.userConfig.addPlugin(function OuterPlugin(eleventyConfig) {\n    order.push(1);\n    t.deepEqual(order, [1]);\n\n    eleventyConfig.addPlugin(function InnerPlugin(eleventyConfig) {\n      order.push(2);\n      t.deepEqual(order, [1, 2]);\n\n      eleventyConfig.addPlugin(function InnerPlugin(eleventyConfig) {\n        order.push(3);\n        t.deepEqual(order, [1, 2, 3]);\n      });\n    });\n  });\n\n  await templateCfg.init();\n\n  templateCfg.getConfig();\n});\n\ntest(\"Nested .addPlugin calls. More complex order\", async (t) => {\n  t.plan(5);\n  let templateCfg = new TemplateConfig();\n  let order = [];\n\n  templateCfg.userConfig.addPlugin(function OuterPlugin(eleventyConfig) {\n    order.push(\"1\");\n    t.deepEqual(order, [\"1\"]);\n\n    eleventyConfig.addPlugin(function InnerPlugin(eleventyConfig) {\n      order.push(\"2\");\n      t.deepEqual(order, [\"1\", \"2\"]);\n\n      eleventyConfig.addPlugin(function InnerPlugin(eleventyConfig) {\n        order.push(\"3a\");\n        t.deepEqual(order, [\"1\", \"2\", \"3a\"]);\n      });\n\n      eleventyConfig.addPlugin(function InnerPlugin(eleventyConfig) {\n        order.push(\"3b\");\n        t.deepEqual(order, [\"1\", \"2\", \"3a\", \"3b\"]);\n      });\n    });\n\n    eleventyConfig.addPlugin(function InnerPlugin(eleventyConfig) {\n      order.push(\"2b\");\n      t.deepEqual(order, [\"1\", \"2\", \"3a\", \"3b\", \"2b\"]);\n    });\n  });\n\n  await templateCfg.init();\n\n  templateCfg.getConfig();\n});\n\ntest(\".addPlugin has access to pathPrefix\", async (t) => {\n  t.plan(1);\n  let templateCfg = new TemplateConfig();\n\n  templateCfg.userConfig.addPlugin(function (eleventyConfig) {\n    t.is(eleventyConfig.pathPrefix, \"/\");\n  });\n\n  await templateCfg.init();\n\n  templateCfg.getConfig();\n});\n\ntest(\".addPlugin has access to pathPrefix (override method)\", async (t) => {\n  t.plan(1);\n  let templateCfg = new TemplateConfig();\n  templateCfg.setPathPrefix(\"/test/\");\n\n  templateCfg.userConfig.addPlugin(function (eleventyConfig) {\n    t.is(eleventyConfig.pathPrefix, \"/test/\");\n  });\n\n  await templateCfg.init();\n\n  templateCfg.getConfig();\n});\n\ntest(\"falsy pathPrefix should fall back to default\", async (t) => {\n  t.plan(1);\n  let templateCfg = new TemplateConfig(defaultConfig, \"./test/stubs/config-empty-pathprefix.cjs\");\n\n  templateCfg.userConfig.addPlugin(function (eleventyConfig) {\n    t.is(eleventyConfig.pathPrefix, \"/\");\n  });\n\n  await templateCfg.init();\n\n  templateCfg.getConfig();\n});\n\ntest(\"Add async plugin\", async (t) => {\n  let templateCfg = new TemplateConfig();\n\n  await templateCfg.userConfig.addPlugin(async (eleventyConfig) => {\n    await new Promise((resolve) => {\n      setTimeout(() => {\n        eleventyConfig.addFilter(\"myFilterName\", function () {});\n        resolve();\n      }, 10);\n    });\n  });\n\n  await templateCfg.init();\n\n  let cfg = templateCfg.getConfig();\n  t.not(Object.keys(cfg.liquidFilters).indexOf(\"myFilterName\"), -1);\n  t.not(Object.keys(cfg.nunjucksFilters).indexOf(\"myFilterName\"), -1);\n});\n\ntest(\"Async namespace\", async (t) => {\n  let templateCfg = new TemplateConfig();\n\n  await templateCfg.userConfig.namespace(\"testNamespace\", async (eleventyConfig) => {\n    await new Promise((resolve) => {\n      setTimeout(() => {\n        eleventyConfig.addFilter(\"MyFilterName\", function () {});\n        resolve();\n      }, 10);\n    });\n  });\n\n  await templateCfg.init();\n\n  let cfg = templateCfg.getConfig();\n  t.not(Object.keys(cfg.liquidFilters).indexOf(\"testNamespaceMyFilterName\"), -1);\n  t.not(Object.keys(cfg.nunjucksFilters).indexOf(\"testNamespaceMyFilterName\"), -1);\n});\n\ntest(\"ProjectDirectories instance exists in user accessible config\", async (t) => {\n\tlet eleventyConfig = await getTemplateConfigInstance();\n  let cfg = eleventyConfig.getConfig();\n\n  t.truthy(cfg.directories);\n  t.is(cfg.directories.input, \"./\");\n  t.is(cfg.directories.data, \"./_data/\");\n  t.is(cfg.directories.includes, \"./_includes/\");\n  t.is(cfg.directories.layouts, undefined);\n  t.is(cfg.directories.output, \"./_site/\");\n\n\tt.throws(() => {\n\t\tcfg.directories.input = \"should not work\";\n\t});\n\tt.throws(() => {\n\t\tcfg.directories.data = \"should not work\";\n\t});\n\tt.throws(() => {\n\t\tcfg.directories.includes = \"should not work\";\n\t});\n\tt.throws(() => {\n\t\tcfg.directories.layouts = \"should not work\";\n\t});\n\tt.throws(() => {\n\t\tcfg.directories.output = \"should not work\";\n\t});\n});\n\ntest(\"Test getters #3310\", async (t) => {\n  let templateCfg = new TemplateConfig();\n  let userCfg = templateCfg.userConfig;\n\n  userCfg.addShortcode(\"myShortcode\", function () {});\n  userCfg.addShortcode(\"myAsyncShortcode\", async function () {});\n\n  userCfg.addPairedShortcode(\"myPairedShortcode\", function () {});\n  userCfg.addPairedShortcode(\"myPairedAsyncShortcode\", async function () {});\n\n  userCfg.addFilter(\"myFilter\", function () {});\n  userCfg.addFilter(\"myAsyncFilter\", async function () {});\n  userCfg.addPlugin(function (eleventyConfig) {\n    eleventyConfig.addFilter(\"myPluginFilter\", function () {});\n  });\n\n  await templateCfg.init();\n\n  let filterNames = Object.keys(userCfg.getFilters());\n  t.true(filterNames.includes(\"myFilter\"));\n  t.true(filterNames.includes(\"myAsyncFilter\"));\n  t.true(filterNames.includes(\"myPluginFilter\"));\n\n  let filterNamesSync = Object.keys(userCfg.getFilters({ type: \"sync\" }));\n  t.true(filterNamesSync.includes(\"myFilter\"));\n  t.false(filterNamesSync.includes(\"myAsyncFilter\"));\n  t.true(filterNamesSync.includes(\"myPluginFilter\"));\n\n  let filterNamesAsync = Object.keys(userCfg.getFilters({ type: \"async\" }));\n  t.false(filterNamesAsync.includes(\"myFilter\"));\n  t.true(filterNamesAsync.includes(\"myAsyncFilter\"));\n  t.false(filterNamesAsync.includes(\"myPluginFilter\"));\n\n  t.truthy(userCfg.getFilter(\"myFilter\"));\n  t.truthy(userCfg.getFilter(\"myAsyncFilter\"));\n  t.truthy(userCfg.getFilter(\"myPluginFilter\"));\n\n  let shortcodeNames = Object.keys(userCfg.getShortcodes());\n  t.true(shortcodeNames.includes(\"myShortcode\"));\n  t.true(shortcodeNames.includes(\"myAsyncShortcode\"));\n\n  let shortcodeNamesSync = Object.keys(userCfg.getShortcodes({ type: \"sync\" }));\n  t.true(shortcodeNamesSync.includes(\"myShortcode\"));\n  t.false(shortcodeNamesSync.includes(\"myAsyncShortcode\"));\n\n  let shortcodeNamesAsync = Object.keys(userCfg.getShortcodes({ type: \"async\" }));\n  t.false(shortcodeNamesAsync.includes(\"myShortcode\"));\n  t.true(shortcodeNamesAsync.includes(\"myAsyncShortcode\"));\n\n  t.truthy(userCfg.getShortcode(\"myShortcode\"));\n  t.truthy(userCfg.getShortcode(\"myAsyncShortcode\"));\n\n  let pairedShortcodeNames = Object.keys(userCfg.getPairedShortcodes());\n  t.true(pairedShortcodeNames.includes(\"myPairedShortcode\"));\n  t.true(pairedShortcodeNames.includes(\"myPairedAsyncShortcode\"));\n\n  let pairedShortcodeNamesSync = Object.keys(userCfg.getPairedShortcodes({ type: \"sync\" }));\n  t.true(pairedShortcodeNamesSync.includes(\"myPairedShortcode\"));\n  t.false(pairedShortcodeNamesSync.includes(\"myPairedAsyncShortcode\"));\n\n  let pairedShortcodeNamesAsync = Object.keys(userCfg.getPairedShortcodes({ type: \"async\" }));\n  t.false(pairedShortcodeNamesAsync.includes(\"myPairedShortcode\"));\n  t.true(pairedShortcodeNamesAsync.includes(\"myPairedAsyncShortcode\"));\n\n  t.truthy(userCfg.getPairedShortcode(\"myPairedShortcode\"));\n  t.truthy(userCfg.getPairedShortcode(\"myPairedAsyncShortcode\"));\n});\n"
  },
  {
    "path": "test/TemplateDataTest.js",
    "content": "import test from \"ava\";\nimport semver from \"semver\";\nimport { createRequire } from \"module\";\nimport { Merge } from \"@11ty/eleventy-utils\";\n\nimport TemplateData from \"../src/Data/TemplateData.js\";\nimport FileSystemSearch from \"../src/FileSystemSearch.js\";\nimport EleventyExtensionMap from \"../src/EleventyExtensionMap.js\";\nimport { isTypeScriptSupported } from \"../src/Util/FeatureTests.cjs\";\n\nimport { getTemplateConfigInstance, getTemplateConfigInstanceCustomCallback } from \"./_testHelpers.js\";\n\nconst pkg = createRequire(import.meta.url)(\"../package.json\");\n\nasync function testGetLocalData(tmplData, templatePath) {\n  let localDataPaths = await tmplData.getLocalDataPaths(templatePath);\n  let importedData = await tmplData.combineLocalData(localDataPaths);\n  let globalData = await tmplData.getGlobalData();\n\n  // OK-ish: shallow merge when combining template/data dir files with global data files\n  let localData = Object.assign({}, globalData, importedData);\n  // debug(\"`getLocalData` for %o: %O\", templatePath, localData);\n  return localData;\n}\n\ntest(\"Create\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs\"\n    }\n  })\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  dataObj.setFileSystemSearch(new FileSystemSearch());\n\n  let data = await dataObj.getGlobalData();\n\n  t.true(Object.keys(data[eleventyConfig.getConfig().keys.package]).length > 0);\n});\n\ntest(\"getGlobalData()\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs\"\n    }\n  });\n\n  let config = eleventyConfig.getConfig();\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  dataObj.setFileSystemSearch(new FileSystemSearch());\n\n  t.is(dataObj.getGlobalData().toString(), \"[object Promise]\");\n\n  let data = await dataObj.getGlobalData();\n  t.is(data.globalData.datakey1, \"datavalue1\", \"simple data value\");\n  t.is(\n    data.globalData.datakey2,\n    \"{{pkg.name}}\",\n    `variables, resolve ${config.keys.package} to its value.`\n  );\n\n  t.true(\n    Object.keys(data[config.keys.package]).length > 0,\n    `package.json imported to data in ${config.keys.package}`\n  );\n});\n\ntest(\"getGlobalData() use default processing (false)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs\"\n    }\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  dataObj.setFileSystemSearch(new FileSystemSearch());\n\n  let data = await dataObj.getGlobalData();\n  t.is(data.globalData.datakey2, \"{{pkg.name}}\", `variables should not resolve`);\n});\n\ntest(\"Data dir does not exist\", async (t) => {\n  await t.throwsAsync(async () => {\n    let eleventyConfig = await getTemplateConfigInstance({\n      dir: {\n        input: \"test/thisdirectorydoesnotexist\"\n      }\n    });\n    let dataObj = new TemplateData(eleventyConfig);\n    dataObj.setProjectUsingEsm(true);\n    await dataObj.getGlobalData();\n  }, {\n    message: \"The \\\"test/thisdirectorydoesnotexist\\\" `input` parameter (directory or file path) must exist on the file system (unless detected as a glob by the `tinyglobby` package)\"\n  });\n});\n\ntest(\"Add local data\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs\"\n    }\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  dataObj.setFileSystemSearch(new FileSystemSearch());\n\n  let data = await dataObj.getGlobalData();\n\n  t.is(data.globalData.datakey1, \"datavalue1\");\n  t.is(data.globalData.datakey2, \"{{pkg.name}}\");\n\n  let withLocalData = await testGetLocalData(dataObj, \"./test/stubs/component/component.njk\");\n  t.is(withLocalData.globalData.datakey1, \"datavalue1\");\n  t.is(withLocalData.globalData.datakey2, \"{{pkg.name}}\");\n  t.is(withLocalData.localdatakey1, \"localdatavalue1\");\n\n  // from the js file\n  // this checks priority/overrides\n  t.is(withLocalData.localdatakeyfromcjs, \"common-js-howdydoody\");\n  t.is(withLocalData.localdatakeyfromjs, \"howdydoody\");\n  t.is(withLocalData.localdatakeyfromjs2, \"howdy2\");\n});\n\ntest(\"Get local data async JS\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs\"\n    }\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  dataObj.setFileSystemSearch(new FileSystemSearch());\n\n  let withLocalData = await testGetLocalData(dataObj, \"./test/stubs/component-async/component.njk\");\n\n  // from the js file\n  t.is(withLocalData.localdatakeyfromjs, \"howdydoody\");\n  t.is(withLocalData.localdatakeyfromcjs, \"common-js-howdydoody\");\n});\n\ntest(\"addLocalData() doesn’t exist but doesn’t fail (template file does exist)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs\"\n    }\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  dataObj.setFileSystemSearch(new FileSystemSearch());\n\n  let data = await dataObj.getGlobalData();\n  let beforeDataKeyCount = Object.keys(data);\n\n  // template file does exist\n  let withLocalData = await testGetLocalData(\n    dataObj,\n    \"./test/stubs/datafiledoesnotexist/template.njk\"\n  );\n  t.is(withLocalData.globalData.datakey1, \"datavalue1\");\n  t.is(withLocalData.globalData.datakey2, \"{{pkg.name}}\");\n  t.deepEqual(Object.keys(withLocalData), beforeDataKeyCount);\n});\n\ntest(\"addLocalData() doesn’t exist but doesn’t fail (template file does not exist)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs\"\n    }\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  dataObj.setFileSystemSearch(new FileSystemSearch());\n\n  let data = await dataObj.getGlobalData();\n  let beforeDataKeyCount = Object.keys(data);\n\n  let withLocalData = await testGetLocalData(\n    dataObj,\n    \"./test/stubs/datafiledoesnotexist/templatedoesnotexist.njk\"\n  );\n  t.is(withLocalData.globalData.datakey1, \"datavalue1\");\n  t.is(withLocalData.globalData.datakey2, \"{{pkg.name}}\");\n  t.deepEqual(Object.keys(withLocalData), beforeDataKeyCount);\n});\n\ntest(\"Global Dir Directory\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \".\"\n    }\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n\n  t.deepEqual(dataObj.getGlobalDataGlob(), [\"./_data/**/*.{json,mjs,cjs,js}\"]);\n});\n\ntest(\"Global Dir Directory with Constructor Path Arg\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs\"\n    }\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n\n  t.deepEqual(dataObj.getGlobalDataGlob(), [\"./test/stubs/_data/**/*.{json,mjs,cjs,js}\"]);\n});\n\ntest(\"getAllGlobalData() with other data files\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs\"\n    }\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  dataObj.setFileSystemSearch(new FileSystemSearch());\n\n  let data = await dataObj.getGlobalData();\n  let dataFilePaths = await dataObj.getGlobalDataFiles();\n\n  t.true(dataFilePaths.length > 0);\n  t.true(\n    dataFilePaths.filter((path) => {\n      return path.indexOf(\"./test/stubs/_data/globalData.json\") === 0;\n    }).length > 0\n  );\n\n  t.truthy(data.globalData);\n  t.is(data.globalData.datakey1, \"datavalue1\");\n\n  t.truthy(data.testData);\n  t.deepEqual(data.testData, {\n    testdatakey1: \"testdatavalue1\",\n  });\n  t.deepEqual(data.subdir.testDataSubdir, {\n    subdirkey: \"subdirvalue\",\n  });\n});\n\ntest(\"getAllGlobalData() with js object data file\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs\"\n    }\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  dataObj.setFileSystemSearch(new FileSystemSearch());\n\n  let data = await dataObj.getGlobalData();\n  let dataFilePaths = await dataObj.getGlobalDataFiles();\n\n  t.true(\n    dataFilePaths.filter((path) => {\n      return path.indexOf(\"./test/stubs/_data/globalData2.cjs\") === 0;\n    }).length > 0\n  );\n\n  t.truthy(data.globalData2);\n  t.is(data.globalData2.datakeyfromjs, \"howdy\");\n});\n\ntest(\"getAllGlobalData() with js function data file\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs\"\n    }\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  dataObj.setFileSystemSearch(new FileSystemSearch());\n\n  let data = await dataObj.getGlobalData();\n  let dataFilePaths = await dataObj.getGlobalDataFiles();\n\n  t.true(\n    dataFilePaths.filter((path) => {\n      return path.indexOf(\"./test/stubs/_data/globalDataFn.js\") === 0;\n    }).length > 0\n  );\n\n  t.truthy(data.globalDataFn);\n  t.is(data.globalDataFn.datakeyfromjsfn, \"howdy\");\n});\n\ntest(\"getAllGlobalData() with config globalData\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback({\n    input: \"test/stubs\"\n  }, function(cfg) {\n    cfg.addGlobalData(\"example\", () => \"one\");\n    cfg.addGlobalData(\"example2\", async () => \"two\");\n    cfg.addGlobalData(\"example3\", \"static\");\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  dataObj.setFileSystemSearch(new FileSystemSearch());\n\n  let data = await dataObj.getGlobalData();\n\n  t.is(data.example, \"one\");\n  t.is(data.example2, \"two\");\n  t.is(data.example3, \"static\");\n});\n\ntest(\"getAllGlobalData() with common js function data file\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs\"\n    }\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  dataObj.setFileSystemSearch(new FileSystemSearch());\n\n  let data = await dataObj.getGlobalData();\n  let dataFilePaths = await dataObj.getGlobalDataFiles();\n\n  t.true(\n    dataFilePaths.filter((path) => {\n      return path.indexOf(\"./test/stubs/_data/globalDataFnCJS.cjs\") === 0;\n    }).length > 0\n  );\n\n  t.truthy(data.globalDataFnCJS);\n  t.is(data.globalDataFnCJS.datakeyfromcjsfn, \"common-cjs-howdy\");\n});\n\ntest(\"getDataValue() without template engine preprocessing\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs\"\n    }\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n\n  let data = await dataObj.getDataValue(\"./test/stubs/_data/testDataLiquid.json\", {\n    pkg: { name: \"pkgname\" },\n  });\n\n  t.deepEqual(data, {\n    datakey1: \"datavalue1\",\n    datakey2: \"{{ pkg.name }}\",\n  });\n});\n\ntest(\"getLocalDataPaths\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs\"\n    }\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  let paths = await dataObj.getLocalDataPaths(\"./test/stubs/component/component.liquid\");\n\n  t.deepEqual(paths, [\n    \"./test/stubs/stubs.json\",\n    \"./test/stubs/stubs.11tydata.json\",\n    \"./test/stubs/stubs.11tydata.mjs\",\n    \"./test/stubs/stubs.11tydata.cjs\",\n    \"./test/stubs/stubs.11tydata.js\",\n\n    \"./test/stubs/component/component.json\",\n    \"./test/stubs/component/component.11tydata.json\",\n    \"./test/stubs/component/component.11tydata.mjs\",\n    \"./test/stubs/component/component.11tydata.cjs\",\n    \"./test/stubs/component/component.11tydata.js\",\n  ]);\n});\n\ntest(\"getLocalDataPaths (with setDataFileBaseName #1699)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback({\n    input: \"test/stubs\"\n  }, function(cfg) {\n    cfg.setDataFileBaseName(\"index\");\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  let paths = await dataObj.getLocalDataPaths(\"./test/stubs/component/component.liquid\");\n\n  t.deepEqual(paths, [\n    \"./test/stubs/index.11tydata.json\",\n    \"./test/stubs/index.11tydata.mjs\",\n    \"./test/stubs/index.11tydata.cjs\",\n    \"./test/stubs/index.11tydata.js\",\n\n    \"./test/stubs/component/index.11tydata.json\",\n    \"./test/stubs/component/index.11tydata.mjs\",\n    \"./test/stubs/component/index.11tydata.cjs\",\n    \"./test/stubs/component/index.11tydata.js\",\n\n    \"./test/stubs/component/component.json\",\n    \"./test/stubs/component/component.11tydata.json\",\n    \"./test/stubs/component/component.11tydata.mjs\",\n    \"./test/stubs/component/component.11tydata.cjs\",\n    \"./test/stubs/component/component.11tydata.js\",\n  ]);\n});\n\ntest(\"getLocalDataPaths (with empty setDataFileSuffixes #1699)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback({\n    input: \"test/stubs\"\n  }, function(cfg) {\n    cfg.setDataFileSuffixes([]);\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  let paths = await dataObj.getLocalDataPaths(\"./test/stubs/component/component.liquid\");\n\n  t.deepEqual(paths, []);\n});\n\ntest(\"getLocalDataPaths (with setDataFileSuffixes override #1699)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback({\n    input: \"test/stubs\"\n  }, function(cfg) {\n    cfg.setDataFileSuffixes([\".howdy\"]);\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  let paths = await dataObj.getLocalDataPaths(\"./test/stubs/component/component.liquid\");\n\n  t.deepEqual(paths, [\n    \"./test/stubs/stubs.howdy.json\",\n    \"./test/stubs/stubs.howdy.mjs\",\n    \"./test/stubs/stubs.howdy.cjs\",\n    \"./test/stubs/stubs.howdy.js\",\n\n    \"./test/stubs/component/component.howdy.json\",\n    \"./test/stubs/component/component.howdy.mjs\",\n    \"./test/stubs/component/component.howdy.cjs\",\n    \"./test/stubs/component/component.howdy.js\",\n  ]);\n});\n\ntest(\"getLocalDataPaths (with setDataFileSuffixes empty string override #1699)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback({\n    input: \"test/stubs\"\n  }, function(cfg) {\n    cfg.setDataFileSuffixes([\"\"]);\n  });\n\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  let paths = await dataObj.getLocalDataPaths(\"./test/stubs/component/component.liquid\");\n\n  t.deepEqual(paths, [\"./test/stubs/stubs.json\", \"./test/stubs/component/component.json\"]);\n});\n\ntest(\"getLocalDataPaths (with setDataFileSuffixes override with two entries #1699)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback({\n    input: \"test/stubs\"\n  }, function(cfg) {\n    cfg.setDataFileSuffixes([\".howdy\", \"\"]);\n  });\n\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  let paths = await dataObj.getLocalDataPaths(\"./test/stubs/component/component.liquid\");\n\n  t.deepEqual(paths, [\n    \"./test/stubs/stubs.json\",\n    \"./test/stubs/stubs.howdy.json\",\n    \"./test/stubs/stubs.howdy.mjs\",\n    \"./test/stubs/stubs.howdy.cjs\",\n    \"./test/stubs/stubs.howdy.js\",\n\n    \"./test/stubs/component/component.json\",\n    \"./test/stubs/component/component.howdy.json\",\n    \"./test/stubs/component/component.howdy.mjs\",\n    \"./test/stubs/component/component.howdy.cjs\",\n    \"./test/stubs/component/component.howdy.js\",\n  ]);\n});\n\ntest(\"getLocalDataPaths (with setDataFileSuffixes and setDataFileBaseName #1699)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback({\n    input: \"test/stubs\"\n  }, function(cfg) {\n    cfg.setDataFileBaseName(\"index\");\n    cfg.setDataFileSuffixes([\".howdy\", \"\"]);\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  let paths = await dataObj.getLocalDataPaths(\"./test/stubs/component/component.liquid\");\n\n  t.deepEqual(paths, [\n    \"./test/stubs/index.howdy.json\",\n    \"./test/stubs/index.howdy.mjs\",\n    \"./test/stubs/index.howdy.cjs\",\n    \"./test/stubs/index.howdy.js\",\n\n    \"./test/stubs/component/index.howdy.json\",\n    \"./test/stubs/component/index.howdy.mjs\",\n    \"./test/stubs/component/index.howdy.cjs\",\n    \"./test/stubs/component/index.howdy.js\",\n\n    \"./test/stubs/component/component.json\",\n    \"./test/stubs/component/component.howdy.json\",\n    \"./test/stubs/component/component.howdy.mjs\",\n    \"./test/stubs/component/component.howdy.cjs\",\n    \"./test/stubs/component/component.howdy.js\",\n  ]);\n});\n\ntest(\"Deeper getLocalDataPaths\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance();\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  let paths = await dataObj.getLocalDataPaths(\"./test/stubs/component/component.liquid\");\n\n  t.deepEqual(paths, [\n    \"./test/test.json\",\n    \"./test/test.11tydata.json\",\n    \"./test/test.11tydata.mjs\",\n    \"./test/test.11tydata.cjs\",\n    \"./test/test.11tydata.js\",\n    \"./test/stubs/stubs.json\",\n    \"./test/stubs/stubs.11tydata.json\",\n    \"./test/stubs/stubs.11tydata.mjs\",\n    \"./test/stubs/stubs.11tydata.cjs\",\n    \"./test/stubs/stubs.11tydata.js\",\n    \"./test/stubs/component/component.json\",\n    \"./test/stubs/component/component.11tydata.json\",\n    \"./test/stubs/component/component.11tydata.mjs\",\n    \"./test/stubs/component/component.11tydata.cjs\",\n    \"./test/stubs/component/component.11tydata.js\",\n  ]);\n});\n\ntest(\"getLocalDataPaths with an 11ty js template\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs\"\n    }\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  let paths = await dataObj.getLocalDataPaths(\"./test/stubs/component/component.11ty.js\");\n\n  t.deepEqual(paths, [\n    \"./test/stubs/stubs.json\",\n    \"./test/stubs/stubs.11tydata.json\",\n    \"./test/stubs/stubs.11tydata.mjs\",\n    \"./test/stubs/stubs.11tydata.cjs\",\n    \"./test/stubs/stubs.11tydata.js\",\n    \"./test/stubs/component/component.json\",\n    \"./test/stubs/component/component.11tydata.json\",\n    \"./test/stubs/component/component.11tydata.mjs\",\n    \"./test/stubs/component/component.11tydata.cjs\",\n    \"./test/stubs/component/component.11tydata.js\",\n  ]);\n});\n\ntest(\"getLocalDataPaths with inputDir passed in (trailing slash)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs\"\n    }\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  let paths = await dataObj.getLocalDataPaths(\"./test/stubs/component/component.liquid\");\n\n  t.deepEqual(paths, [\n    \"./test/stubs/stubs.json\",\n    \"./test/stubs/stubs.11tydata.json\",\n    \"./test/stubs/stubs.11tydata.mjs\",\n    \"./test/stubs/stubs.11tydata.cjs\",\n    \"./test/stubs/stubs.11tydata.js\",\n    \"./test/stubs/component/component.json\",\n    \"./test/stubs/component/component.11tydata.json\",\n    \"./test/stubs/component/component.11tydata.mjs\",\n    \"./test/stubs/component/component.11tydata.cjs\",\n    \"./test/stubs/component/component.11tydata.js\",\n  ]);\n});\n\ntest(\"getLocalDataPaths with inputDir passed in (no trailing slash)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs\"\n    }\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  let paths = await dataObj.getLocalDataPaths(\"./test/stubs/component/component.liquid\");\n\n  t.deepEqual(paths, [\n    \"./test/stubs/stubs.json\",\n    \"./test/stubs/stubs.11tydata.json\",\n    \"./test/stubs/stubs.11tydata.mjs\",\n    \"./test/stubs/stubs.11tydata.cjs\",\n    \"./test/stubs/stubs.11tydata.js\",\n    \"./test/stubs/component/component.json\",\n    \"./test/stubs/component/component.11tydata.json\",\n    \"./test/stubs/component/component.11tydata.mjs\",\n    \"./test/stubs/component/component.11tydata.cjs\",\n    \"./test/stubs/component/component.11tydata.js\",\n  ]);\n});\n\ntest(\"getLocalDataPaths with inputDir passed in (no leading slash)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs\"\n    }\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  let paths = await dataObj.getLocalDataPaths(\"./test/stubs/component/component.liquid\");\n\n  t.deepEqual(paths, [\n    \"./test/stubs/stubs.json\",\n    \"./test/stubs/stubs.11tydata.json\",\n    \"./test/stubs/stubs.11tydata.mjs\",\n    \"./test/stubs/stubs.11tydata.cjs\",\n    \"./test/stubs/stubs.11tydata.js\",\n    \"./test/stubs/component/component.json\",\n    \"./test/stubs/component/component.11tydata.json\",\n    \"./test/stubs/component/component.11tydata.mjs\",\n    \"./test/stubs/component/component.11tydata.cjs\",\n    \"./test/stubs/component/component.11tydata.js\",\n  ]);\n});\n\ntest(\"getRawImports\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs\"\n    }\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  let data = await dataObj.getRawImports();\n\n  t.is(data.pkg.name, \"@11ty/eleventy\");\n});\n\ntest(\"getTemplateDataFileGlob\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs\"\n    }\n  });\n\n  let tw = new TemplateData(eleventyConfig);\n\n  t.deepEqual(await tw.getTemplateDataFileGlob(), [\n    `./test/stubs/**/*.{json,11tydata.mjs,11tydata.cjs,11tydata.js${isTypeScriptSupported() ? \",11tydata.mts,11tydata.cts,11tydata.ts\" : \"\"}}`,\n  ]);\n});\n\n// https://github.com/11ty/eleventy/issues/3937\ntest(\"TemplateData.merge is now Merge()\", (t) => {\n  t.deepEqual(\n    Merge(\n      {\n        tags: [1, 2, 3],\n      },\n      {\n        tags: [4, 5, 6],\n      }\n    ),\n    { tags: [1, 2, 3, 4, 5, 6] }\n  );\n});\n\ntest(\"TemplateData.cleanupData\", (t) => {\n  t.deepEqual(TemplateData.cleanupData({}), {});\n  t.deepEqual(TemplateData.cleanupData({ tags: null }), { tags: [] });\n  t.deepEqual(TemplateData.cleanupData({ tags: \"\" }), { tags: [] });\n  t.deepEqual(TemplateData.cleanupData({ tags: [] }), { tags: [] });\n  t.deepEqual(TemplateData.cleanupData({ tags: \"test\" }), { tags: [\"test\"] });\n  t.deepEqual(TemplateData.cleanupData({ tags: [\"test1\", \"test2\"] }), {\n    tags: [\"test1\", \"test2\"],\n  });\n});\n\ntest(\"Parent directory for data (Issue #337)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"./test/stubs-337/src/\",\n      data: \"../data/\",\n    }\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  dataObj.setFileSystemSearch(new FileSystemSearch());\n\n  let data = await dataObj.getGlobalData();\n\n  t.deepEqual(data.xyz, {\n    hi: \"bye\",\n  });\n});\n\ntest(\"Dots in datafile path (Issue #1242)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"./test/stubs-1242/\",\n    }\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  dataObj.setFileSystemSearch(new FileSystemSearch());\n\n  let data = await dataObj.getGlobalData();\n\n  t.deepEqual(data[\"xyz.dottest\"], {\n    hi: \"bye\",\n    test: {\n      abc: 42,\n    },\n  });\n});\n\ntest(\"addGlobalData values\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback({\n    input: \"./test/stubs-global-data-config-api/\"\n  }, function(cfg) {\n    cfg.addGlobalData(\"myFunction\", () => \"fn-value\");\n    cfg.addGlobalData(\"myPromise\", () => {\n      return new Promise((resolve) => {\n        setTimeout(resolve, 100, \"promise-value\");\n      });\n    });\n    cfg.addGlobalData(\"myAsync\", async () => Promise.resolve(\"promise-value\"));\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  dataObj.setFileSystemSearch(new FileSystemSearch());\n  let data = await dataObj.getGlobalData();\n\n  t.is(data.myFunction, \"fn-value\");\n  t.is(data.myPromise, \"promise-value\");\n  t.is(data.myAsync, \"promise-value\");\n});\n\ntest(\"addGlobalData should execute once.\", async (t) => {\n  let count = 0;\n\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback({\n    input: \"./test/stubs-global-data-config-api/\"\n  }, function(cfg) {\n    cfg.addGlobalData(\"count\", () => {\n      count++;\n      return count;\n    });\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  dataObj.setFileSystemSearch(new FileSystemSearch());\n\n  let data = await dataObj.getGlobalData();\n\n  t.is(data.count, 1);\n  t.is(count, 1);\n});\n\ntest(\"addGlobalData complex key\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback({\n    input: \"./test/stubs-global-data-config-api-nested/\"\n  }, function(cfg) {\n    cfg.addGlobalData(\"deep.nested.one\", () => \"first\");\n    cfg.addGlobalData(\"deep.nested.two\", () => \"second\");\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  dataObj.setFileSystemSearch(new FileSystemSearch());\n  let data = await dataObj.getGlobalData();\n\n  t.is(data.deep.existing, true);\n  t.is(data.deep.nested.one, \"first\");\n  t.is(data.deep.nested.two, \"second\");\n});\n\ntest(\"eleventy.version and eleventy.generator returned from data\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback({\n    input: \"./test/stubs-empty/\"\n  }, function(cfg) {\n    cfg.addGlobalData(\"deep.nested.one\", () => \"first\");\n    cfg.addGlobalData(\"deep.nested.two\", () => \"second\");\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  dataObj.setFileSystemSearch(new FileSystemSearch());\n  let data = await dataObj.getGlobalData();\n\n  let version = semver.coerce(pkg.version).toString();\n\n  t.is(data.eleventy.version, version);\n  t.is(data.eleventy.generator, `Eleventy v${version}`);\n\n  t.is(data.deep.nested.one, \"first\");\n  t.is(data.deep.nested.two, \"second\");\n});\n\ntest(\"getGlobalData() empty json file\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"./test/stubs-empty-json-data/\",\n    }\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n\n  dataObj.setFileSystemSearch(new FileSystemSearch());\n\n  let data = await dataObj.getGlobalData();\n  t.deepEqual(data.empty, {});\n});\n\ntest(\"ESM data file\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"./test/stubs-data-esm/\",\n    }\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  dataObj.setFileSystemSearch(new FileSystemSearch());\n\n  let data = await dataObj.getGlobalData();\n  t.is(data.module.default, \"es module default\");\n  t.is(data.module.named, \"es module named\");\n  t.is(data.commonjs, \"commonjs default\");\n});\n\ntest(\"Test collection names from data (empty assigned)\", async (t) => {\n  t.deepEqual(TemplateData.getIncludedCollectionNames({\n    tags: [],\n  }), [\"all\"]);\n\n  t.deepEqual(TemplateData.getIncludedCollectionNames({\n    tags: [],\n    eleventyExcludeFromCollections: true\n  }), []);\n\n  t.deepEqual(TemplateData.getIncludedCollectionNames({\n    tags: [],\n    eleventyExcludeFromCollections: [\"one\"]\n  }), [\"all\"]);\n});\n\ntest(\"Test collection names from data (tags assigned)\", async (t) => {\n  t.deepEqual(TemplateData.getIncludedCollectionNames({\n    tags: [\"one\", \"two\"],\n  }), [\"all\", \"one\", \"two\"]);\n\n  t.deepEqual(TemplateData.getIncludedCollectionNames({\n    tags: [\"one\", \"two\"],\n    eleventyExcludeFromCollections: true,\n  }), []);\n\n  t.deepEqual(TemplateData.getIncludedCollectionNames({\n    tags: [\"one\", \"two\"],\n    eleventyExcludeFromCollections: [\"one\"],\n  }), [\"all\", \"two\"]);\n});\n\ntest(\"Test tag names from data (empty assigned)\", async (t) => {\n  t.deepEqual(TemplateData.getIncludedTagNames({\n    tags: [],\n  }), []);\n\n  t.deepEqual(TemplateData.getIncludedTagNames({\n    tags: [],\n    eleventyExcludeFromCollections: true\n  }), []);\n\n  t.deepEqual(TemplateData.getIncludedTagNames({\n    tags: [],\n    eleventyExcludeFromCollections: [\"one\"]\n  }), []);\n});\n\ntest(\"Test tag names from data (tags assigned)\", async (t) => {\n  t.deepEqual(TemplateData.getIncludedTagNames({\n    tags: [\"one\", \"two\"],\n  }), [\"one\", \"two\"]);\n\n  t.deepEqual(TemplateData.getIncludedTagNames({\n    tags: [\"one\", \"two\"],\n    eleventyExcludeFromCollections: true,\n  }), []);\n\n  t.deepEqual(TemplateData.getIncludedTagNames({\n    tags: [\"one\", \"two\"],\n    eleventyExcludeFromCollections: [\"one\"],\n  }), [\"two\"]);\n});\n"
  },
  {
    "path": "test/TemplateDepGraphTest.js",
    "content": "import test from \"ava\";\nimport { TemplateDepGraph } from \"../src/Util/TemplateDepGraph.js\";\n\ntest(\"Using new Template DepGraph\", async (t) => {\n  let graph = new TemplateDepGraph();\n\n  graph.addTemplate(\"template-paginated-over-all.njk\", [\"all\"], []);\n  graph.addTemplate(\"template-paginated-over-userconfig.njk\", [\"[userconfig]\"], []);\n  graph.addTemplate(\"template-1.njk\", [], [\"all\", \"posts\"]);\n  graph.addTemplate(\"template-2.njk\", [], [\"all\", \"posts\", \"dog\"]);\n  graph.addTemplate(\"template-paginated-collections.njk\", [\"[keys]\"], []);\n  graph.addConfigCollectionName(\"myCollection\");\n\n  t.deepEqual(graph.unfilteredOrder(), [\n    \"template-1.njk\",\n    \"template-2.njk\",\n    \"__collection:posts\",\n    \"__collection:dog\",\n    \"__collection:[basic]\",\n    \"__collection:[userconfig]\",\n    \"template-paginated-over-userconfig.njk\",\n    \"__collection:myCollection\",\n    \"__collection:[keys]\",\n    \"template-paginated-collections.njk\",\n    \"__collection:all\",\n    \"template-paginated-over-all.njk\",\n  ]);\n\n  t.deepEqual(graph.overallOrder(), [\n    \"template-1.njk\",\n    \"template-2.njk\",\n    \"__collection:posts\",\n    \"__collection:dog\",\n    \"template-paginated-over-userconfig.njk\",\n    \"__collection:myCollection\",\n    \"__collection:[keys]\",\n    \"template-paginated-collections.njk\",\n    \"__collection:all\",\n    \"template-paginated-over-all.njk\",\n    \"__collection:all\",\n  ]);\n});\n"
  },
  {
    "path": "test/TemplateEngineManagerTest.js",
    "content": "import test from \"ava\";\n\nimport TemplateEngineManager from \"../src/Engines/TemplateEngineManager.js\";\nimport EleventyExtensionMap from \"../src/EleventyExtensionMap.js\";\nimport TemplateConfig from \"../src/TemplateConfig.js\";\n\ntest(\"Unsupported engine\", async (t) => {\n  await t.throwsAsync(async () => {\n    let eleventyConfig = new TemplateConfig();\n    let tem = new TemplateEngineManager(eleventyConfig);\n    await tem.getEngine(\"doesnotexist\");\n  });\n});\n\ntest(\"Supported engine\", async (t) => {\n  let eleventyConfig = new TemplateConfig();\n  await eleventyConfig.init();\n\n  let tem = new TemplateEngineManager(eleventyConfig);\n  t.truthy(tem.hasEngine(\"11ty.js\"));\n});\n\ntest(\"Supported custom engine\", async (t) => {\n  let eleventyConfig = new TemplateConfig();\n  eleventyConfig.userConfig.extensionMap.add({\n    extension: \"txt\",\n    key: \"txt\",\n    compile: function (str, inputPath) {\n      // plaintext\n      return function (data) {\n        return str;\n      };\n    },\n  });\n  await eleventyConfig.init();\n\n  let extensionMap = new EleventyExtensionMap(eleventyConfig);\n\n  let tem = new TemplateEngineManager(eleventyConfig);\n\n  t.truthy(tem.hasEngine(\"txt\"));\n  let engine = await tem.getEngine(\"txt\", extensionMap);\n  let fn = await engine.compile(\"<p>This is plaintext</p>\");\n  t.is(await fn({ author: \"zach\" }), \"<p>This is plaintext</p>\");\n});\n\ntest(\"Custom engine with custom init\", async (t) => {\n  let initCount = 0;\n  let compileCount = 0;\n  let eleventyConfig = new TemplateConfig();\n  eleventyConfig.userConfig.extensionMap.add({\n    extension: \"custom1\",\n    key: \"custom1\",\n    init: async function () {\n      // do custom things, but only once\n      initCount++;\n    },\n    compile: function (str, inputPath) {\n      compileCount++;\n      return () => str;\n    },\n  });\n  await eleventyConfig.init();\n\n  let extensionMap = new EleventyExtensionMap(eleventyConfig);\n\n  // let config = eleventyConfig.getConfig();\n  let tem = new TemplateEngineManager(eleventyConfig);\n\n  t.truthy(tem.hasEngine(\"custom1\"));\n  let engine = await tem.getEngine(\"custom1\", extensionMap);\n  let fn = await engine.compile(\"<p>This is plaintext</p>\");\n  t.is(await fn({}), \"<p>This is plaintext</p>\");\n\n  let engine2 = await tem.getEngine(\"custom1\");\n  t.is(engine, engine2);\n\n  let fn2 = await engine2.compile(\"<p>This is plaintext</p>\");\n  t.is(await fn2({}), \"<p>This is plaintext</p>\");\n\n  t.is(initCount, 1, \"Should have only run the init callback once\");\n  t.is(compileCount, 2, \"Should have only run the compile callback twice\");\n});\n\ntest(\"getEngineLib\", async (t) => {\n  let eleventyConfig = new TemplateConfig();\n  await eleventyConfig.init();\n  let extensionMap = new EleventyExtensionMap(eleventyConfig);\n\n  let tem = new TemplateEngineManager(eleventyConfig);\n  let engine = await tem.getEngine(\"md\", extensionMap);\n  t.truthy(engine.getEngineLib());\n});\n"
  },
  {
    "path": "test/TemplateEngineTest.js",
    "content": "import test from \"ava\";\n\nimport TemplateEngine from \"../src/Engines/TemplateEngine.js\";\n\nimport { getTemplateConfigInstance } from \"./_testHelpers.js\"\n\ntest(\"Unsupported engine\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance();\n  let engine = new TemplateEngine(\"doesnotexist\", eleventyConfig);\n  t.is(engine.getName(), \"doesnotexist\");\n});\n\ntest(\"Supported engine\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance();\n  t.is(new TemplateEngine(\"liquid\", eleventyConfig).getName(), \"liquid\");\n});\n"
  },
  {
    "path": "test/TemplateFileSlugTest.js",
    "content": "import test from \"ava\";\n\nimport TemplateFileSlug from \"../src/TemplateFileSlug.js\";\nimport EleventyExtensionMap from \"../src/EleventyExtensionMap.js\";\n\nimport { getTemplateConfigInstance } from \"./_testHelpers.js\";\n\nasync function getNewSlugInstance(path, inputDir) {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: inputDir\n    }\n  });\n\n  let extensionMap = new EleventyExtensionMap(eleventyConfig);\n  extensionMap.setFormats([]);\n  let fs = new TemplateFileSlug(path, extensionMap, eleventyConfig);\n  return fs;\n}\n\ntest(\"Easy slug\", async (t) => {\n  let fs = await getNewSlugInstance(\"./file.html\");\n  t.is(fs.getSlug(), \"file\");\n  t.is(fs.getFullPathWithoutExtension(), \"/file\");\n});\n\ntest(\"Easy slug with dot\", async (t) => {\n  let fs = await getNewSlugInstance(\"./file.test.html\");\n  t.is(fs.getSlug(), \"file.test\");\n  t.is(fs.getFullPathWithoutExtension(), \"/file.test\");\n});\n\ntest(\"Easy slug with dot 11ty.js\", async (t) => {\n  let fs = await getNewSlugInstance(\"./file.test.11ty.js\");\n  t.is(fs.getSlug(), \"file.test\");\n  t.is(fs.getFullPathWithoutExtension(), \"/file.test\");\n});\n\ntest(\"Easy slug with date\", async (t) => {\n  let fs = await getNewSlugInstance(\"./2018-01-01-file.html\");\n  t.is(fs.getSlug(), \"file\");\n  t.is(fs.getFullPathWithoutExtension(), \"/file\");\n});\n\ntest(\"Easy slug with date and dot in slug\", async (t) => {\n  let fs = await getNewSlugInstance(\"./2018-01-01-file.test.html\");\n  t.is(fs.getSlug(), \"file.test\");\n  t.is(fs.getFullPathWithoutExtension(), \"/file.test\");\n});\n\ntest(\"Easy slug, index\", async (t) => {\n  let fs = await getNewSlugInstance(\"./index.html\");\n  t.is(fs.getSlug(), \"\");\n  t.is(fs.getFullPathWithoutExtension(), \"/index\");\n});\n\ntest(\"Easy slug with date, index\", async (t) => {\n  let fs = await getNewSlugInstance(\"./2018-01-01-index.html\");\n  t.is(fs.getSlug(), \"\");\n  t.is(fs.getFullPathWithoutExtension(), \"/index\");\n});\n\ntest(\"Easy slug with only a date and no suffix\", async (t) => {\n  let fs = await getNewSlugInstance(\"./2018-01-01.html\");\n  t.is(fs.getSlug(), \"2018-01-01\");\n  t.is(fs.getFullPathWithoutExtension(), \"/2018-01-01\");\n});\n\n/* Directories */\n\ntest(\"Easy slug with dir\", async (t) => {\n  let fs = await getNewSlugInstance(\"./test/file.html\");\n  t.is(fs.getSlug(), \"file\");\n  t.is(fs.getFullPathWithoutExtension(), \"/test/file\");\n});\n\ntest(\"Easy slug with dot with dir\", async (t) => {\n  let fs = await getNewSlugInstance(\"./test/file.test.html\");\n  t.is(fs.getSlug(), \"file.test\");\n  t.is(fs.getFullPathWithoutExtension(), \"/test/file.test\");\n});\n\ntest(\"Easy slug with date with dir\", async (t) => {\n  let fs = await getNewSlugInstance(\"./test/2018-01-01-file.html\");\n  t.is(fs.getSlug(), \"file\");\n  t.is(fs.getFullPathWithoutExtension(), \"/test/file\");\n});\n\ntest(\"Easy slug with date and dot in slug with dir\", async (t) => {\n  let fs = await getNewSlugInstance(\"./test/2018-01-01-file.test.html\");\n  t.is(fs.getSlug(), \"file.test\");\n  t.is(fs.getFullPathWithoutExtension(), \"/test/file.test\");\n});\n\ntest(\"Easy slug, index with dir\", async (t) => {\n  let fs = await getNewSlugInstance(\"./test/index.html\");\n  t.is(fs.getSlug(), \"test\");\n  t.is(fs.getFullPathWithoutExtension(), \"/test/index\");\n});\n\ntest(\"Easy slug with date, index with dir\", async (t) => {\n  let fs = await getNewSlugInstance(\"./test/2018-01-01-index.html\");\n  t.is(fs.getSlug(), \"test\");\n  t.is(fs.getFullPathWithoutExtension(), \"/test/index\");\n});\n\ntest(\"Strips date from dir name\", async (t) => {\n  let fs = await getNewSlugInstance(\"./2021-11-20-my-awesome-post/index.md\");\n  t.is(fs.getSlug(), \"my-awesome-post\");\n  t.is(fs.getFullPathWithoutExtension(), \"/2021-11-20-my-awesome-post/index\");\n});\n\n/* Pass Input dir */\ntest(\"Easy slug, input dir\", async (t) => {\n  let fs = await getNewSlugInstance(\"./file.html\", \".\");\n  t.is(fs.getSlug(), \"file\");\n  t.is(fs.getFullPathWithoutExtension(), \"/file\");\n});\n\ntest(\"Easy slug with dot, input dir\", async (t) => {\n  let fs = await getNewSlugInstance(\"./file.test.html\", \".\");\n  t.is(fs.getSlug(), \"file.test\");\n  t.is(fs.getFullPathWithoutExtension(), \"/file.test\");\n});\n\ntest(\"Easy slug with date, input dir\", async (t) => {\n  let fs = await getNewSlugInstance(\"./2018-01-01-file.html\", \".\");\n  t.is(fs.getSlug(), \"file\");\n  t.is(fs.getFullPathWithoutExtension(), \"/file\");\n});\n\ntest(\"Easy slug with date and dot in slug, input dir\", async (t) => {\n  let fs = await getNewSlugInstance(\"./2018-01-01-file.test.html\", \".\");\n  t.is(fs.getSlug(), \"file.test\");\n  t.is(fs.getFullPathWithoutExtension(), \"/file.test\");\n});\n\ntest(\"Easy slug, index, input dir\", async (t) => {\n  let fs = await getNewSlugInstance(\"./index.html\", \".\");\n  t.is(fs.getSlug(), \"\");\n  t.is(fs.getFullPathWithoutExtension(), \"/index\");\n});\n\ntest(\"Easy slug with date, index, input dir\", async (t) => {\n  let fs = await getNewSlugInstance(\"./2018-01-01-index.html\", \".\");\n  t.is(fs.getSlug(), \"\");\n  t.is(fs.getFullPathWithoutExtension(), \"/index\");\n});\n\n/* Directories and Input Dir */\n\ntest(\"Easy slug with dir and input dir\", async (t) => {\n  let fs = await getNewSlugInstance(\"./test/file.html\", \"./test\");\n  t.is(fs.getSlug(), \"file\");\n  t.is(fs.getFullPathWithoutExtension(), \"/file\");\n});\n\ntest(\"Easy slug with dot with dir and input dir\", async (t) => {\n  let fs = await getNewSlugInstance(\"./test/file.test.html\", \"./test\");\n  t.is(fs.getSlug(), \"file.test\");\n  t.is(fs.getFullPathWithoutExtension(), \"/file.test\");\n});\n\ntest(\"Easy slug with date with dir and input dir\", async (t) => {\n  let fs = await getNewSlugInstance(\"./test/2018-01-01-file.html\", \"./test\");\n  t.is(fs.getSlug(), \"file\");\n  t.is(fs.getFullPathWithoutExtension(), \"/file\");\n});\n\ntest(\"Easy slug with date and dot in slug with dir and input dir\", async (t) => {\n  let fs = await getNewSlugInstance(\"./test/2018-01-01-file.test.html\", \"./test\");\n  t.is(fs.getSlug(), \"file.test\");\n  t.is(fs.getFullPathWithoutExtension(), \"/file.test\");\n});\n\ntest(\"Easy slug, index with dir and input dir\", async (t) => {\n  let fs = await getNewSlugInstance(\"./test/index.html\", \"./test\");\n  t.is(fs.getSlug(), \"\");\n  t.is(fs.getFullPathWithoutExtension(), \"/index\");\n});\n\ntest(\"Easy slug with date, index with dir and input dir\", async (t) => {\n  let fs = await getNewSlugInstance(\"./test/2018-01-01-index.html\", \"./test\");\n  t.is(fs.getSlug(), \"\");\n  t.is(fs.getFullPathWithoutExtension(), \"/index\");\n});\n\ntest(\"Easy slug with multiple dirs\", async (t) => {\n  let fs = await getNewSlugInstance(\"./dir1/dir2/dir3/file.html\", \".\");\n  t.is(fs.getSlug(), \"file\");\n  t.is(fs.getFullPathWithoutExtension(), \"/dir1/dir2/dir3/file\");\n});\n\ntest(\"Easy slug with multiple dirs and an index file\", async (t) => {\n  let fs = await getNewSlugInstance(\"./dir1/dir2/dir3/index.html\", \".\");\n  t.is(fs.getSlug(), \"dir3\");\n  t.is(fs.getFullPathWithoutExtension(), \"/dir1/dir2/dir3/index\");\n});\n"
  },
  {
    "path": "test/TemplateGlobTest.js",
    "content": "import test from \"ava\";\nimport { glob } from 'tinyglobby';\nimport { TemplatePath } from \"@11ty/eleventy-utils\";\n\nimport TemplateGlob from \"../src/TemplateGlob.js\";\n\ntest(\"TemplatePath assumptions\", (t) => {\n  t.is(TemplatePath.normalize(\"ignoredFolder\"), \"ignoredFolder\");\n  t.is(TemplatePath.normalize(\"./ignoredFolder\"), \"ignoredFolder\");\n  t.is(TemplatePath.normalize(\"./ignoredFolder/\"), \"ignoredFolder\");\n});\n\ntest(\"Normalize string argument\", (t) => {\n  t.deepEqual(TemplateGlob.map(\"views\"), \"./views\");\n  t.deepEqual(TemplateGlob.map(\"views/\"), \"./views\");\n  t.deepEqual(TemplateGlob.map(\"./views\"), \"./views\");\n  t.deepEqual(TemplateGlob.map(\"./views/\"), \"./views\");\n});\n\ntest(\"Normalize with nots\", (t) => {\n  t.deepEqual(TemplateGlob.map(\"!views\"), \"!./views\");\n  t.deepEqual(TemplateGlob.map(\"!views/\"), \"!./views\");\n  t.deepEqual(TemplateGlob.map(\"!./views\"), \"!./views\");\n  t.deepEqual(TemplateGlob.map(\"!./views/\"), \"!./views\");\n});\n\ntest(\"Normalize with globstar\", (t) => {\n  t.deepEqual(TemplateGlob.map(\"!views/**\"), \"!./views/**\");\n  t.deepEqual(TemplateGlob.map(\"!./views/**\"), \"!./views/**\");\n});\n\ntest(\"Normalize with globstar and star\", (t) => {\n  t.deepEqual(TemplateGlob.map(\"!views/**/*\"), \"!./views/**/*\");\n  t.deepEqual(TemplateGlob.map(\"!./views/**/*\"), \"!./views/**/*\");\n});\n\ntest(\"Normalize with globstar and star and file extension\", (t) => {\n  t.deepEqual(TemplateGlob.map(\"!views/**/*.json\"), \"!./views/**/*.json\");\n  t.deepEqual(TemplateGlob.map(\"!./views/**/*.json\"), \"!./views/**/*.json\");\n});\n\ntest(\"NormalizePath with globstar and star and file extension\", (t) => {\n  t.deepEqual(TemplateGlob.normalizePath(\"views\", \"/\", \"**/*.json\"), \"./views/**/*.json\");\n  t.deepEqual(TemplateGlob.normalizePath(\"./views\", \"/\", \"**/*.json\"), \"./views/**/*.json\");\n});\n\ntest(\"NormalizePath with globstar and star and file extension (errors)\", (t) => {\n  t.throws(() => {\n    TemplateGlob.normalizePath(\"!views/**/*.json\");\n  });\n\n  t.throws(() => {\n    TemplateGlob.normalizePath(\"!views\", \"/\", \"**/*.json\");\n  });\n\n  t.throws(() => {\n    TemplateGlob.normalizePath(\"!./views/**/*.json\");\n  });\n\n  t.throws(() => {\n    TemplateGlob.normalizePath(\"!./views\", \"/\", \"**/*.json\");\n  });\n});\n\ntest(\"Normalize array argument\", (t) => {\n  t.deepEqual(TemplateGlob.map([\"views\", \"content\"]), [\"./views\", \"./content\"]);\n  t.deepEqual(TemplateGlob.map(\"views/\"), \"./views\");\n  t.deepEqual(TemplateGlob.map(\"./views\"), \"./views\");\n  t.deepEqual(TemplateGlob.map(\"./views/\"), \"./views\");\n});\n\ntest(\"matuzo project issue with fastglob assumptions\", async (t) => {\n  let dotslashincludes = await glob(\n    TemplateGlob.map([\n      \"./test/stubs/globby/**/*.html\",\n      \"!./test/stubs/globby/_includes/**/*\",\n      \"!./test/stubs/globby/_data/**/*\",\n    ])\n  );\n\n  t.is(\n    dotslashincludes.filter(function (file) {\n      return file.indexOf(\"_includes\") > -1;\n    }).length,\n    0\n  );\n\n  let globincludes = await glob(\n    TemplateGlob.map([\n      \"test/stubs/globby/**/*.html\",\n      \"!./test/stubs/globby/_includes/**/*\",\n      \"!./test/stubs/globby/_data/**/*\",\n    ])\n  );\n  t.is(\n    globincludes.filter(function (file) {\n      return file.indexOf(\"_includes\") > -1;\n    }).length,\n    0\n  );\n});\n\n// `fast-glob` isn't used any more, but the test can stay as a sanity check.\ntest(\"fastglob assumptions\", async (t) => {\n  let globbed = await glob(\"test/stubs/ignoredFolder/**\");\n  t.is(globbed.length, 1);\n\n  let globbed2 = await glob(\"test/stubs/ignoredFolder/**/*\");\n  t.is(globbed2.length, 1);\n\n  let globbed3 = await glob([\n    \"./test/stubs/ignoredFolder/**/*.md\",\n    \"!./test/stubs/ignoredFolder/**\",\n  ]);\n  t.is(globbed3.length, 0);\n\n  let globbed4 = await glob([\"./test/stubs/ignoredFolder/*.md\", \"!./test/stubs/ignoredFolder/**\"]);\n  t.is(globbed4.length, 0);\n\n  let globbed5 = await glob([\n    \"./test/stubs/ignoredFolder/ignored.md\",\n    \"!./test/stubs/ignoredFolder/**\",\n  ]);\n  t.is(globbed5.length, 0);\n});\n"
  },
  {
    "path": "test/TemplateLayoutPathResolverTest.js",
    "content": "import test from \"ava\";\n\nimport TemplateLayoutPathResolver from \"../src/TemplateLayoutPathResolver.js\";\nimport EleventyExtensionMap from \"../src/EleventyExtensionMap.js\";\n\nimport { getTemplateConfigInstance, getTemplateConfigInstanceCustomCallback } from \"./_testHelpers.js\";\n\nasync function getResolverInstance(path, inputDir, { eleventyConfig, map } = {}) {\n  if (!eleventyConfig) {\n    eleventyConfig = await getTemplateConfigInstance({\n      dir: {\n        input: inputDir\n      }\n    });\n  }\n\n  if (!map) {\n    map = new EleventyExtensionMap(eleventyConfig);\n    map.setFormats([\"liquid\", \"md\", \"njk\", \"html\", \"11ty.js\"]);\n  }\n\n  return new TemplateLayoutPathResolver(path, map, eleventyConfig);\n}\n\ntest(\"Layout\", async (t) => {\n  let res = await getResolverInstance(\"default\", \"./test/stubs\");\n  t.is(res.getFileName(), \"default.liquid\");\n});\n\ntest(\"Layout already has extension\", async (t) => {\n  let res = await getResolverInstance(\"default.liquid\", \"./test/stubs\");\n  t.is(res.getFileName(), \"default.liquid\");\n});\n\ntest(\"Layout (uses empty string includes folder)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    templateFormats: [\"liquid\"],\n    dir: {\n      input: \"test/stubs\",\n      includes: \"\"\n    }\n  });\n\n  let res = await getResolverInstance(\"includesemptystring\", \"./test/stubs\", {\n    eleventyConfig,\n  });\n\n  t.is(res.getFileName(), \"includesemptystring.liquid\");\n});\n\ntest(\"Layout (uses empty string includes folder) already has extension\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    templateFormats: [\"liquid\"],\n    dir: {\n      input: \"test/stubs\",\n      includes: \"\"\n    }\n  });\n\n  let res = await getResolverInstance(\"includesemptystring.liquid\", \"./test/stubs\", {\n    eleventyConfig,\n  });\n\n  t.is(res.getFileName(), \"includesemptystring.liquid\");\n});\n\ntest(\"Layout (uses layouts folder)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    templateFormats: [\"liquid\"],\n    dir: {\n      input: \"test/stubs\",\n      layouts: \"_layouts\",\n      includes: \"_includes\",\n    }\n  });\n\n  let res = await getResolverInstance(\"layoutsdefault\", \"./test/stubs\", {\n    eleventyConfig,\n  });\n\n  t.is(res.getFileName(), \"layoutsdefault.liquid\");\n});\n\ntest(\"Layout (uses layouts folder) already has extension\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    templateFormats: [\"liquid\"],\n    dir: {\n      input: \"test/stubs\",\n      layouts: \"_layouts\",\n    }\n  });\n\n  let res = await getResolverInstance(\"layoutsdefault.liquid\", \"./test/stubs\", {\n    eleventyConfig,\n  });\n\n  t.is(res.getFileName(), \"layoutsdefault.liquid\");\n});\n\ntest(\"Layout (uses empty string layouts folder)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    templateFormats: [\"liquid\"],\n    dir: {\n      input: \"test/stubs\",\n      layouts: \"\",\n    }\n  });\n\n  let res = await getResolverInstance(\"layoutsemptystring\", \"./test/stubs\", {\n    eleventyConfig,\n  });\n\n  t.is(res.getFileName(), \"layoutsemptystring.liquid\");\n});\n\ntest(\"Layout (uses empty string layouts folder) no template resolution\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback({\n    input: \"test/stubs\",\n    layouts: \"\"\n  }, function(cfg) {\n    cfg.setLayoutResolution(false);\n  });\n\n  let res = await getResolverInstance(\"layoutsemptystring\", \"./test/stubs\", {\n    eleventyConfig,\n  });\n\n  t.throws(() => {\n    res.getFileName();\n  }, {\n    message: `You’re trying to use a layout that does not exist: test/stubs/layoutsemptystring (via \\`layout: layoutsemptystring\\`)`\n  });\n});\n\ntest(\"Layout (uses empty string layouts folder) already has extension\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    templateFormats: [\"liquid\"],\n    dir: {\n      input: \"test/stubs\",\n      layouts: \"\",\n    }\n  });\n\n  let res = await getResolverInstance(\"layoutsemptystring.liquid\", \"./test/stubs\", {\n    eleventyConfig,\n  });\n\n  t.is(res.getFileName(), \"layoutsemptystring.liquid\");\n});\n\ntest(\"Layout subdir\", async (t) => {\n  let res = await getResolverInstance(\"layouts/inasubdir\", \"./test/stubs\");\n  t.is(res.getFileName(), \"layouts/inasubdir.njk\");\n});\n\ntest(\"Layout subdir already has extension\", async (t) => {\n  let res = await getResolverInstance(\"layouts/inasubdir.njk\", \"./test/stubs\");\n  t.is(res.getFileName(), \"layouts/inasubdir.njk\");\n});\n\ntest(\"Multiple layouts exist with the same file base, pick one\", async (t) => {\n  let res = await getResolverInstance(\"multiple\", \"./test/stubs\");\n  // pick the first one if multiple exist.\n  t.is(res.getFileName(), \"multiple.liquid\");\n});\n\ntest(\"Multiple layouts exist but we are being explicit—layout already has extension\", async (t) => {\n  let res = await getResolverInstance(\"multiple.liquid\", \"./test/stubs\");\n  t.is(res.getFileName(), \"multiple.liquid\");\n\n  let res2 = await getResolverInstance(\"multiple.md\", \"./test/stubs\");\n  t.is(res2.getFileName(), \"multiple.md\");\n});\n\ntest(\"Layout is aliased to a new location\", async (t) => {\n  let tl = await getResolverInstance(\"post\", \"./test/stubs\");\n  tl.addLayoutAlias(\"post\", \"layouts/post.liquid\");\n  tl.init();\n\n  t.is(tl.getFileName(), \"layouts/post.liquid\");\n});\n\ntest(\"Global default with empty string alias\", async (t) => {\n  let tl = await getResolverInstance(\"\", \"./test/stubs\");\n  tl.addLayoutAlias(\"\", \"layouts/post.liquid\");\n  tl.init();\n\n  t.is(tl.getFileName(), \"layouts/post.liquid\");\n});\n\ntest(\"Global default with empty string alias (but no alias exists for this instance)\", async (t) => {\n  let tl = await getResolverInstance(\"layout.liquid\", \"./test/stubs\");\n  tl.addLayoutAlias(\"\", \"layouts/post.liquid\");\n  tl.init();\n\n  t.throws(() => {\n    tl.getFileName();\n  });\n});\n\ntest(\"Layout has no alias and does not exist\", async (t) => {\n  let tl = await getResolverInstance(\"shouldnotexist\", \"./test/stubs\");\n  tl.addLayoutAlias(\"post\", \"layouts/post.liquid\");\n  tl.init();\n\n  t.throws(() => {\n    tl.getFileName();\n  });\n\n  t.throws(() => {\n    tl.getFullPath();\n  });\n});\n"
  },
  {
    "path": "test/TemplateLayoutTest.js",
    "content": "import test from \"ava\";\n\nimport TemplateLayout from \"../src/TemplateLayout.js\";\nimport EleventyExtensionMap from \"../src/EleventyExtensionMap.js\";\nimport TemplateEngineManager from \"../src/Engines/TemplateEngineManager.js\";\n\nimport { renderLayoutViaLayout } from \"./_getRenderedTemplates.js\";\nimport { getTemplateConfigInstance } from \"./_testHelpers.js\";\n\nasync function getTemplateLayoutInstance(key, inputDir, map) {\n  let eleventyConfig = await getTemplateConfigInstance({\n\t\tdir: {\n\t\t\tinput: inputDir,\n\t\t}\n\t});\n\n  let mgr = new TemplateEngineManager(eleventyConfig);\n  if (!map) {\n    map = new EleventyExtensionMap(eleventyConfig);\n    map.setFormats([\"liquid\", \"md\", \"njk\", \"html\", \"11ty.js\"]);\n    map.engineManager = mgr;\n  }\n  let layout = new TemplateLayout(key, map, eleventyConfig);\n  return layout;\n}\n\ntest(\"Creation\", async (t) => {\n  let tl = await getTemplateLayoutInstance(\"base\", \"./test/stubs\");\n  t.is(tl.getInputPath(), \"./test/stubs/_includes/base.njk\");\n\n  await t.throwsAsync(async () => {\n    await getTemplateLayoutInstance(\"doesnotexist\", \"./test/stubs\");\n  });\n});\n\ntest(\"Get Layout Chain\", async (t) => {\n  let tl = await getTemplateLayoutInstance(\"layouts/layout-inherit-a.njk\", \"./test/stubs\");\n\n  await tl.getData();\n\n  t.deepEqual(await tl.getLayoutChain(), [\n    \"./test/stubs/_includes/layouts/layout-inherit-a.njk\",\n    \"./test/stubs/_includes/layouts/layout-inherit-b.njk\",\n    \"./test/stubs/_includes/layouts/layout-inherit-c.njk\",\n  ]);\n});\n\ntest(\"Get Front Matter Data\", async (t) => {\n  let tl = await getTemplateLayoutInstance(\"layouts/layout-inherit-a.njk\", \"./test/stubs\");\n  t.is(tl.getInputPath(), \"./test/stubs/_includes/layouts/layout-inherit-a.njk\");\n\n  let data = await tl.getData();\n\n  t.deepEqual(data, {\n    inherits: \"a\",\n    secondinherits: \"b\",\n    thirdinherits: \"c\",\n  });\n\n  t.deepEqual(await tl.getLayoutChain(), [\n    \"./test/stubs/_includes/layouts/layout-inherit-a.njk\",\n    \"./test/stubs/_includes/layouts/layout-inherit-b.njk\",\n    \"./test/stubs/_includes/layouts/layout-inherit-c.njk\",\n  ]);\n\n  t.deepEqual(await tl.getData(), {\n    inherits: \"a\",\n    secondinherits: \"b\",\n    thirdinherits: \"c\",\n  });\n});\n\ntest(\"Render Layout\", async (t) => {\n  let tl = await getTemplateLayoutInstance(\"layouts/layout-inherit-a.njk\", \"./test/stubs\");\n  t.is(\n    (\n      await renderLayoutViaLayout(tl, {\n        inherits: \"a\",\n        secondinherits: \"b\",\n        thirdinherits: \"c\",\n      })\n    ).trim(),\n    \"a b a c\"\n  );\n});\n\ntest(\"Render Layout (Pass in template content)\", async (t) => {\n  let tl = await getTemplateLayoutInstance(\"layouts/layout-inherit-a.njk\", \"./test/stubs\");\n  t.is(\n    (\n      await renderLayoutViaLayout(tl,\n        { inherits: \"a\", secondinherits: \"b\", thirdinherits: \"c\" },\n        \"TEMPLATE_CONTENT\"\n      )\n    ).trim(),\n    \"TEMPLATE_CONTENT a b a c\"\n  );\n});\n\ntest(\"Render Layout (Pass in undefined template content)\", async (t) => {\n  let tl = await getTemplateLayoutInstance(\"layouts/layout-contentdump.njk\", \"./test/stubs\");\n  t.is(\n    await renderLayoutViaLayout(tl, { inherits: \"a\", secondinherits: \"b\", thirdinherits: \"c\" }, undefined),\n    \"this is bad a b a c\"\n  );\n});\n\ntest(\"Render Layout (Pass in null template content)\", async (t) => {\n  let tl = await getTemplateLayoutInstance(\"layouts/layout-contentdump.njk\", \"./test/stubs\");\n  t.is(\n    await renderLayoutViaLayout(tl, { inherits: \"a\", secondinherits: \"b\", thirdinherits: \"c\" }, null),\n    \" a b a c\"\n  );\n});\n\ntest(\"Render Layout (Pass in empty template content)\", async (t) => {\n  let tl = await getTemplateLayoutInstance(\"layouts/layout-contentdump.njk\", \"./test/stubs\");\n  t.is(await renderLayoutViaLayout(tl, { inherits: \"a\", secondinherits: \"b\", thirdinherits: \"c\" }, \"\"), \" a b a c\");\n});\n\ntest(\"Cache Duplicates (use full key for cache)\", async (t) => {\n  // if two different layouts used the same filename but had different inputdirs, make sure templatelayout cache is unique\n  let tla = await getTemplateLayoutInstance(\n    \"layout.njk\",\n    \"./test/stubs/templateLayoutCacheDuplicates\"\n  );\n  t.is((await renderLayoutViaLayout(tla, {})).trim(), \"Hello A\");\n\n  let tlb = await getTemplateLayoutInstance(\n    \"layout.njk\",\n    \"./test/stubs/templateLayoutCacheDuplicates-b\"\n  );\n  t.is((await renderLayoutViaLayout(tlb, {})).trim(), \"Hello B\");\n\n  t.is((await renderLayoutViaLayout(tla, {})).trim(), \"Hello A\");\n});\n\ntest(\"Throw an error if a layout references itself as the layout\", async (t) => {\n  await t.throwsAsync(async () => {\n    const tl = await getTemplateLayoutInstance(\n      \"layout-cycle-self.njk\",\n      \"./test/stubs-circular-layout\"\n    );\n\n    const layoutChain = await tl._testGetLayoutChain();\n    return layoutChain;\n  });\n});\n\ntest(\"Throw an error if a circular layout chain is detected\", async (t) => {\n  await t.throwsAsync(async () => {\n    const tl = await getTemplateLayoutInstance(\n      \"layout-cycle-a.njk\",\n      \"./test/stubsstubs-circular-layout\"\n    );\n    const layoutChain = await tl._testGetLayoutChain();\n    return layoutChain;\n  });\n});\n"
  },
  {
    "path": "test/TemplateMapTest-ComputedData.js",
    "content": "import test from \"ava\";\n\nimport TemplateData from \"../src/Data/TemplateData.js\";\nimport TemplateMap from \"../src/TemplateMap.js\";\n\nimport getNewTemplate from \"./_getNewTemplateForTests.js\";\nimport { getTemplateConfigInstance } from \"./_testHelpers.js\";\nimport EleventyExtensionMap from \"../src/EleventyExtensionMap.js\";\n\ntest(\"Computed data can see tag generated collections\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs-computed-collections\"\n    }\n  });\n\n  let tm = new TemplateMap(eleventyConfig);\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  let tmpl = await getNewTemplate(\n    \"./test/stubs-computed-collections/collections.njk\",\n    \"./test/stubs-computed-collections/\",\n    \"./dist\",\n    dataObj,\n    null,\n    eleventyConfig\n  );\n\n  await tm.add(tmpl);\n\n  let dataObj2 = new TemplateData(eleventyConfig);\n  dataObj2.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  let tmpl2 = await getNewTemplate(\n    \"./test/stubs-computed-collections/dog.njk\",\n    \"./test/stubs-computed-collections/\",\n    \"./dist\",\n    dataObj2,\n    null,\n    eleventyConfig\n  );\n\n  await tm.add(tmpl2);\n\n  await tm.cache();\n\n  let map = tm.getMap();\n\n  t.is(map[0].inputPath.endsWith(\"collections.njk\"), true);\n\n  t.truthy(map[0].data.collections.all);\n  t.is(map[0].data.collections.all.length, 2);\n  t.truthy(map[0].data.collections.dog);\n  t.is(map[0].data.collections.dog.length, 1);\n  t.truthy(map[0].data.dogCollection);\n  t.is(map[0].data.dogCollection.length, 1);\n  t.is(map[0].data.test, \"hello\");\n\n  // THEY ARE THE SAME\n  t.is(map[0].data.dogCollection, map[0].data.collections.dog);\n});\n\ntest(\"Computed data can see paginated data, Issue #1138\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs-computed-pagination\"\n    }\n  });\n  let tm = new TemplateMap(eleventyConfig);\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  let tmpl = await getNewTemplate(\n    \"./test/stubs-computed-pagination/paginated.njk\",\n    \"./test/stubs-computed-pagination/\",\n    \"./dist\",\n    dataObj,\n    null,\n    eleventyConfig\n  );\n\n  await tm.add(tmpl);\n\n  let dataObj2 = new TemplateData(eleventyConfig);\n  dataObj2.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  let tmpl2 = await getNewTemplate(\n    \"./test/stubs-computed-pagination/child.11ty.cjs\",\n    \"./test/stubs-computed-pagination/\",\n    \"./dist\",\n    dataObj2,\n    null,\n    eleventyConfig\n  );\n\n  await tm.add(tmpl2);\n\n  await tm.cache();\n\n  let map = tm.getMap();\n\n  t.is(map.length, 2);\n\n  // paginated template tests\n  t.is(map[0].inputPath.endsWith(\"paginated.njk\"), true);\n  t.is(map[0]._pages.length, 2);\n\n  t.is(map[0]._pages[0].data.venue, \"first\");\n  t.is(map[0]._pages[0].data.title, \"first\");\n  t.is(map[0]._pages[0].url, \"/venues/first/\");\n  t.truthy(map[0]._pages[0].data.collections);\n  t.truthy(map[0]._pages[0].data.collections.venue);\n  t.is(map[0]._pages[0].data.collections.venue.length, 2);\n\n  t.is(map[0]._pages[1].data.venue, \"second\");\n  t.is(map[0]._pages[1].data.title, \"second\");\n  t.is(map[0]._pages[1].url, \"/venues/second/\");\n  t.truthy(map[0]._pages[1].data.collections.venue);\n  t.is(map[0]._pages[1].data.collections.venue.length, 2);\n\n  // consumer of paginated template tests\n  t.is(map[1]._pages.length, 1);\n  // computed prop from venues\n  t.truthy(map[1]._pages[0].data.venues);\n  t.is(map[1]._pages[0].data.venues.length, 2);\n});\n\ntest(\"Computed data in directory data file consumes data file data, Issue #1137\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs-computed-dirdata\"\n    }\n  });\n\n  let tm = new TemplateMap(eleventyConfig);\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  let tmpl = await getNewTemplate(\n    \"./test/stubs-computed-dirdata/dir/first.11ty.cjs\",\n    \"./test/stubs-computed-dirdata/\",\n    \"./dist\",\n    dataObj,\n    null,\n    eleventyConfig\n  );\n\n  await tm.add(tmpl);\n\n  let dataObj2 = new TemplateData(eleventyConfig);\n  dataObj2.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  let tmpl2 = await getNewTemplate(\n    \"./test/stubs-computed-dirdata/dir/second.11ty.cjs\",\n    \"./test/stubs-computed-dirdata/\",\n    \"./dist\",\n    dataObj2,\n    null,\n    eleventyConfig\n  );\n\n  await tm.add(tmpl2);\n\n  await tm.cache();\n\n  let map = tm.getMap();\n\n  t.is(map.length, 2);\n  t.is(map[0]._pages[0].data.webmentions, \"first\");\n  t.is(map[1]._pages[0].data.webmentions, \"second\");\n});\n\ntest(\"Computed data can filter collections (and other array methods)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs-computed-collections-filter\"\n    }\n  });\n\n  let tm = new TemplateMap(eleventyConfig);\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  let tmpl = await getNewTemplate(\n    \"./test/stubs-computed-collections-filter/collections.njk\",\n    \"./test/stubs-computed-collections-filter/\",\n    \"./dist\",\n    dataObj,\n    null,\n    eleventyConfig\n  );\n\n  await tm.add(tmpl);\n\n  let dataObj2 = new TemplateData(eleventyConfig);\n  dataObj2.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  let tmpl2 = await getNewTemplate(\n    \"./test/stubs-computed-collections-filter/dog.njk\",\n    \"./test/stubs-computed-collections-filter/\",\n    \"./dist\",\n    dataObj2,\n    null,\n    eleventyConfig\n  );\n\n  await tm.add(tmpl2);\n\n  await tm.cache();\n\n  let map = tm.getMap();\n\n  t.is(map[0].inputPath.endsWith(\"collections.njk\"), true);\n\n  t.truthy(map[0].data.collections.all);\n  t.is(map[0].data.collections.all.length, 2);\n  t.truthy(map[0].data.collections.dog);\n  t.is(map[0].data.collections.dog.length, 1);\n  t.truthy(map[0].data.dogCollection);\n  t.is(map[0].data.dogCollection.length, 1);\n  t.is(map[0].data.test, \"hello\");\n\n  // Deeply equal but not the same per `filter` use.\n  t.not(map[0].data.dogCollection, map[0].data.collections.dog);\n  t.deepEqual(map[0].data.dogCollection, map[0].data.collections.dog);\n});\n"
  },
  {
    "path": "test/TemplateMapTest.js",
    "content": "import test from \"ava\";\n\nimport TemplateMap from \"../src/TemplateMap.js\";\nimport TemplateCollection from \"../src/TemplateCollection.js\";\nimport UsingCircularTemplateContentReferenceError from \"../src/Errors/UsingCircularTemplateContentReferenceError.js\";\nimport TemplateContentUnrenderedTemplateError from \"../src/Errors/TemplateContentUnrenderedTemplateError.js\";\nimport { normalizeNewLines } from \"./Util/normalizeNewLines.js\";\n\nimport getNewTemplateForTests from \"./_getNewTemplateForTests.js\";\nimport { getRenderedTemplates as getRenderedTmpls, renderTemplate } from \"./_getRenderedTemplates.js\";\nimport { getTemplateConfigInstance, getTemplateConfigInstanceCustomCallback } from \"./_testHelpers.js\";\n\nfunction getNewTemplate(filename, input, output, eleventyConfig) {\n  return getNewTemplateForTests(filename, input, output, null, null, eleventyConfig);\n}\n\nfunction getNewTemplateByNumber(num, eleventyConfig) {\n  return getNewTemplate(\n    `./test/stubs/templateMapCollection/test${num}.md`,\n    \"./test/stubs/\",\n    \"./test/stubs/_site\",\n    eleventyConfig,\n  );\n}\n\nasync function testRenderWithoutLayouts(template, data) {\n  let ret = await template.renderPageEntryWithoutLayout({\n    rawInput: await template.getPreRender(),\n    data,\n  });\n  return ret;\n}\n\nasync function addTemplate(collection, template) {\n  let data = await template.getData();\n  for (let map of await template.getTemplates(data)) {\n    collection.add(map);\n  }\n}\n\ntest(\"TemplateMap has collections added\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance();\n  let tmpl1 = await getNewTemplateByNumber(1, eleventyConfig);\n  let tmpl2 = await getNewTemplateByNumber(2, eleventyConfig);\n\n  let tm = new TemplateMap(eleventyConfig);\n  await tm.add(tmpl1);\n  await tm.add(tmpl2);\n  await tm.cache();\n\n  t.is(tm.getMap().length, 2);\n  t.is(tm.collection.getAll().length, 2);\n});\n\ntest(\"TemplateMap compared to Collection API\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance();\n\n  let tmpl1 = await getNewTemplateByNumber(1, eleventyConfig);\n  let tmpl4 = await getNewTemplateByNumber(4, eleventyConfig);\n  let tm = new TemplateMap(eleventyConfig);\n  await tm.add(tmpl1);\n  await tm.add(tmpl4);\n  await tm.cache();\n\n  let map = tm.getMap();\n  t.deepEqual(map[0].template, tmpl1);\n  t.deepEqual(map[0].data.collections.post[0].template, tmpl1);\n  t.deepEqual(map[1].template, tmpl4);\n  t.deepEqual(map[1].data.collections.post[1].template, tmpl4);\n\n  let c = new TemplateCollection();\n  await addTemplate(c, tmpl1);\n  await addTemplate(c, tmpl4);\n\n  let posts = c.getFilteredByTag(\"post\");\n  t.is(posts.length, 2);\n  t.deepEqual(posts[0].template, tmpl1);\n  t.deepEqual(posts[1].template, tmpl4);\n});\n\ntest(\"populating the collection twice should clear the previous values (--watch was making it cumulative)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance();\n\n  let tmpl1 = await getNewTemplateByNumber(1, eleventyConfig);\n  let tmpl2 = await getNewTemplateByNumber(2, eleventyConfig);\n\n  let tm = new TemplateMap(eleventyConfig);\n  await tm.add(tmpl1);\n  await tm.add(tmpl2);\n\n  await tm.cache();\n  await tm.cache();\n\n  t.is(tm.getMap().length, 2);\n});\n\ntest(\"TemplateMap adds collections data and has templateContent values\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance();\n\n  let tmpl1 = await getNewTemplateByNumber(1, eleventyConfig);\n  let tmpl2 = await getNewTemplateByNumber(2, eleventyConfig);\n\n  let tm = new TemplateMap(eleventyConfig);\n  await tm.add(tmpl1);\n  await tm.add(tmpl2);\n\n  let map = tm.getMap();\n  t.falsy(map[0].data.collections);\n  t.falsy(map[1].data.collections);\n\n  await tm.cache();\n\n  t.truthy(map[0]._pages[0].templateContent);\n  t.truthy(map[1]._pages[0].templateContent);\n  t.truthy(map[0].data.collections);\n  t.truthy(map[1].data.collections);\n  t.is(map[0].data.collections.post.length, 1);\n  t.is(map[0].data.collections.all.length, 2);\n  t.is(map[1].data.collections.post.length, 1);\n  t.is(map[1].data.collections.all.length, 2);\n\n  t.is(\n    await testRenderWithoutLayouts(map[0].template, map[0].data),\n    map[0]._pages[0].templateContent,\n  );\n  t.is(\n    await testRenderWithoutLayouts(map[1].template, map[1].data),\n    map[1]._pages[0].templateContent,\n  );\n});\n\ntest(\"TemplateMap circular references (map without templateContent)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance();\n\n  let tmpl3 = await getNewTemplateByNumber(3, eleventyConfig);\n\n  let tm = new TemplateMap(eleventyConfig);\n  await tm.add(tmpl3);\n\n  let map = tm.getMap();\n  t.falsy(map[0].data.collections);\n\n  await tm.cache();\n  t.truthy(map[0]._pages[0].templateContent);\n  t.truthy(map[0].data.collections);\n  t.is(map[0].data.collections.all.length, 1);\n\n  t.is(\n    await testRenderWithoutLayouts(map[0].template, map[0].data),\n    map[0]._pages[0].templateContent,\n  );\n});\n\ntest(\"TemplateMap circular references (map.templateContent)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance();\n\n  let tm = new TemplateMap(eleventyConfig);\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/templateMapCollection/templateContent.md\",\n    \"./test/stubs/\",\n    \"./test/stubs/_site\",\n    eleventyConfig,\n  );\n  await tm.add(tmpl);\n\n  let map = tm.getMap();\n  t.falsy(map[0].data.collections);\n\n  await t.throwsAsync(\n    async () => {\n      await tm.cache();\n    },\n    {\n      instanceOf: UsingCircularTemplateContentReferenceError,\n    },\n  );\n});\n\ntest(\"Issue #115, mixing pagination and collections\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance();\n\n  let tmplFoos = await getNewTemplate(\n    \"./test/stubs/issue-115/template-foos.liquid\",\n    \"./test/stubs/\",\n    \"./test/stubs/_site\",\n    eleventyConfig,\n  );\n  let tmplBars = await getNewTemplate(\n    \"./test/stubs/issue-115/template-bars.liquid\",\n    \"./test/stubs/\",\n    \"./test/stubs/_site\",\n    eleventyConfig,\n  );\n  let tmplIndex = await getNewTemplate(\n    \"./test/stubs/issue-115/index.liquid\",\n    \"./test/stubs/\",\n    \"./test/stubs/_site\",\n    eleventyConfig,\n  );\n\n  let tm = new TemplateMap(eleventyConfig);\n  await tm.add(tmplFoos);\n  await tm.add(tmplBars);\n  await tm.add(tmplIndex);\n  await tm.cache();\n\n  let map = tm.getMap();\n  t.is(map.length, 3);\n  t.deepEqual(map[2].template, tmplIndex);\n\n  let collections = await tm._testGetAllCollectionsData();\n  t.is(Object.keys(collections.all).length, 3);\n  t.is(Object.keys(collections.foos).length, 1);\n  t.is(Object.keys(collections.bars).length, 1);\n  t.is(Object.keys((await tm._testGetCollectionsData()).all).length, 3);\n  t.is(Object.keys((await tm._testGetCollectionsData()).foos).length, 1);\n  t.is(Object.keys((await tm._testGetCollectionsData()).bars).length, 1);\n\n  t.truthy(map[0].data.collections);\n  t.truthy(map[1].data.collections);\n  t.truthy(map[2].data.collections);\n  t.truthy(Object.keys(map[2].data.collections).length);\n\n  t.is(Object.keys(map[0].data.collections.all).length, 3);\n  t.is(Object.keys(map[0].data.collections.foos).length, 1);\n  t.is(Object.keys(map[0].data.collections.bars).length, 1);\n\n  t.is(Object.keys(map[1].data.collections.all).length, 3);\n  t.is(Object.keys(map[1].data.collections.foos).length, 1);\n  t.is(Object.keys(map[1].data.collections.bars).length, 1);\n\n  t.is(Object.keys(map[2].data.collections.all).length, 3);\n  t.is(Object.keys(map[2].data.collections.foos).length, 1);\n  t.is(Object.keys(map[2].data.collections.bars).length, 1);\n\n  let entry = await getRenderedTmpls(map[2].template, map[2].data);\n  t.deepEqual(\n    normalizeNewLines(entry[0].templateContent),\n    `This page is foos\nThis page is bars\n`,\n  );\n});\n\ntest(\"Issue #115 with layout, mixing pagination and collections\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n\t\tdir: {\n\t\t\tinput: \"./test/stubs/\",\n    \toutput: \"./test/stubs/_site\",\n\t\t}\n\t});\n\n  let tmplFoos = await getNewTemplate(\n    \"./test/stubs/issue-115/template-foos.liquid\",\n    \"./test/stubs/\",\n    \"./test/stubs/_site\",\n    eleventyConfig,\n  );\n  let tmplBars = await getNewTemplate(\n    \"./test/stubs/issue-115/template-bars.liquid\",\n    \"./test/stubs/\",\n    \"./test/stubs/_site\",\n    eleventyConfig,\n  );\n  let tmplIndex = await getNewTemplate(\n    \"./test/stubs/issue-115/index-with-layout.liquid\",\n    \"./test/stubs/\",\n    \"./test/stubs/_site\",\n    eleventyConfig,\n  );\n\n  let tm = new TemplateMap(eleventyConfig);\n  await tm.add(tmplFoos);\n  await tm.add(tmplBars);\n  await tm.add(tmplIndex);\n  await tm.cache();\n\n  let map = tm.getMap();\n  t.is(map.length, 3);\n  t.deepEqual(map[2].template, tmplIndex);\n\n  let collections = await tm._testGetAllCollectionsData();\n  t.is(Object.keys(collections.all).length, 3);\n  t.is(Object.keys(collections.foos).length, 1);\n  t.is(Object.keys(collections.bars).length, 1);\n  t.is(Object.keys((await tm._testGetCollectionsData()).all).length, 3);\n  t.is(Object.keys((await tm._testGetCollectionsData()).foos).length, 1);\n  t.is(Object.keys((await tm._testGetCollectionsData()).bars).length, 1);\n\n  t.truthy(map[0].data.collections);\n  t.truthy(map[1].data.collections);\n  t.truthy(map[2].data.collections);\n  t.truthy(Object.keys(map[2].data.collections).length);\n\n  t.is(Object.keys(map[0].data.collections.all).length, 3);\n  t.is(Object.keys(map[0].data.collections.foos).length, 1);\n  t.is(Object.keys(map[0].data.collections.bars).length, 1);\n\n  t.is(Object.keys(map[1].data.collections.all).length, 3);\n  t.is(Object.keys(map[1].data.collections.foos).length, 1);\n  t.is(Object.keys(map[1].data.collections.bars).length, 1);\n\n  t.is(Object.keys(map[2].data.collections.all).length, 3);\n  t.is(Object.keys(map[2].data.collections.foos).length, 1);\n  t.is(Object.keys(map[2].data.collections.bars).length, 1);\n\n  let entry = await getRenderedTmpls(map[2].template, map[2].data);\n  t.deepEqual(\n    normalizeNewLines(entry[0].templateContent),\n    `This page is foos\nThis page is bars\n`,\n  );\n});\n\ntest(\"TemplateMap adds collections data and has page data values using .cache()\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n\t\tdir: {\n\t\t\tinput: \"./test/stubs/\",\n    \toutput: \"./test/stubs/_site\",\n\t\t}\n\t});\n\n  let tmpl1 = await getNewTemplateByNumber(1, eleventyConfig);\n  let tmpl2 = await getNewTemplateByNumber(2, eleventyConfig);\n\n  let tm = new TemplateMap(eleventyConfig);\n  await tm.add(tmpl1);\n  await tm.add(tmpl2);\n\n  let map = tm.getMap();\n  await tm.cache();\n  t.is(map[0].data.page.url, \"/templateMapCollection/test1/\");\n  t.is(map[0].data.page.outputPath, \"./test/stubs/_site/templateMapCollection/test1/index.html\");\n  t.is(map[0].data.page.inputPath, \"./test/stubs/templateMapCollection/test1.md\");\n  t.is(map[0].data.page.fileSlug, \"test1\");\n  t.truthy(map[0].data.page.date);\n});\n\ntest(\"TemplateMap adds collections data and has page data values using ._testGetCollectionsData()\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n\t\tdir: {\n\t\t\tinput: \"./test/stubs/\",\n    \toutput: \"./test/stubs/_site\",\n\t\t}\n\t});\n\n  let tmpl1 = await getNewTemplateByNumber(1, eleventyConfig);\n  let tmpl2 = await getNewTemplateByNumber(2, eleventyConfig);\n\n  let tm = new TemplateMap(eleventyConfig);\n  await tm.add(tmpl1);\n  await tm.add(tmpl2);\n\n  let collections = await tm._testGetCollectionsData();\n  t.is(collections.all[0].url, \"/templateMapCollection/test1/\");\n  t.is(collections.all[0].outputPath, \"./test/stubs/_site/templateMapCollection/test1/index.html\");\n\n  t.is(collections.all[0].data.page.url, \"/templateMapCollection/test1/\");\n  t.is(\n    collections.all[0].data.page.outputPath,\n    \"./test/stubs/_site/templateMapCollection/test1/index.html\",\n  );\n  t.is(collections.all[0].data.page.inputPath, \"./test/stubs/templateMapCollection/test1.md\");\n  t.is(collections.all[0].data.page.fileSlug, \"test1\");\n});\n\ntest(\"Url should be available in user config collections API calls\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback(\n    {\n\t\t\tinput: \"./test/stubs/\",\n    \toutput: \"./test/stubs/_site\",\n\t\t},\n    function(cfg) {\n      cfg.addCollection(\"userCollection\", function (collection) {\n        return collection.getAll();\n      });\n    }\n  );\n\n  let tmpl1 = await getNewTemplateByNumber(1, eleventyConfig);\n  let tmpl2 = await getNewTemplateByNumber(2, eleventyConfig);\n\n  let tm = new TemplateMap(eleventyConfig);\n  await tm.add(tmpl1);\n  await tm.add(tmpl2);\n\n  let collections = await tm._testGetCollectionsData();\n  t.truthy(collections.userCollection);\n  t.truthy(collections.userCollection.length);\n  t.is(collections.userCollection[0].url, \"/templateMapCollection/test1/\");\n  t.is(\n    collections.userCollection[0].outputPath,\n    \"./test/stubs/_site/templateMapCollection/test1/index.html\",\n  );\n\n  t.is(collections.userCollection[0].data.page.url, \"/templateMapCollection/test1/\");\n  t.is(\n    collections.userCollection[0].data.page.outputPath,\n    \"./test/stubs/_site/templateMapCollection/test1/index.html\",\n  );\n});\n\ntest(\"Url should be available in user config collections API calls (test in callback)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback(\n    {\n\t\t\tinput: \"./test/stubs/\",\n    \toutput: \"./test/stubs/_site\",\n\t\t},\n    function(cfg) {\n      cfg.addCollection(\"userCollection\", function (collection) {\n        let all = collection.getAll();\n        t.is(all[0].url, \"/templateMapCollection/test1/\");\n        t.is(all[0].outputPath, \"./test/stubs/_site/templateMapCollection/test1/index.html\");\n        t.is(all[1].url, \"/templateMapCollection/test2/\");\n        t.is(all[1].outputPath, \"./test/stubs/_site/templateMapCollection/test2/index.html\");\n\n        return all;\n      });\n    }\n  );\n\n  let tm = new TemplateMap(eleventyConfig);\n  let tmpl1 = await getNewTemplateByNumber(1, eleventyConfig);\n  let tmpl2 = await getNewTemplateByNumber(2, eleventyConfig);\n  await tm.add(tmpl1);\n  await tm.add(tmpl2);\n  await tm.cache();\n});\n\ntest(\"Should be able to paginate a tag generated collection\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance();\n\n  let tmpl1 = await getNewTemplateByNumber(1, eleventyConfig);\n  let tmpl2 = await getNewTemplateByNumber(2, eleventyConfig);\n\n  let tm = new TemplateMap(eleventyConfig);\n  await tm.add(tmpl1);\n  await tm.add(tmpl2);\n\n  let pagedTmpl = await getNewTemplate(\n    \"./test/stubs/templateMapCollection/paged-tag.md\",\n    \"./test/stubs/\",\n    \"./test/stubs/_site\",\n    eleventyConfig,\n  );\n  await tm.add(pagedTmpl);\n\n  let collections = await tm._testGetCollectionsData();\n  t.truthy(collections.dog);\n  t.truthy(collections.dog.length);\n});\n\ntest(\"Should be able to paginate a user config collection\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback(\n    {\n\t\t\tinput: \"./test/stubs/\",\n    \toutput: \"./test/stubs/_site\",\n\t\t},\n    function(cfg) {\n      cfg.addCollection(\"userCollection\", function (collection) {\n        let all = collection.getFilteredByTag(\"dog\");\n        return all;\n      });\n    }\n  );\n\n  let tmpl1 = await getNewTemplateByNumber(1, eleventyConfig);\n  let tmpl2 = await getNewTemplateByNumber(2, eleventyConfig);\n\n  let tm = new TemplateMap(eleventyConfig);\n  await tm.add(tmpl1);\n  await tm.add(tmpl2);\n\n  let pagedTmpl = await getNewTemplate(\n    \"./test/stubs/templateMapCollection/paged-cfg.md\",\n    \"./test/stubs/\",\n    \"./test/stubs/_site\",\n    eleventyConfig,\n  );\n  await tm.add(pagedTmpl);\n\n  let collections = await tm._testGetCollectionsData();\n  t.truthy(collections.userCollection);\n  t.truthy(collections.userCollection.length);\n});\n\ntest(\"Should be able to paginate a user config collection (uses rendered permalink)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback(\n    {\n\t\t\tinput: \"./test/stubs/\",\n    \toutput: \"./test/stubs/_site\",\n\t\t},\n    function(cfg) {\n      cfg.addCollection(\"userCollection\", function (collection) {\n        let all = collection.getFilteredByTag(\"dog\");\n        t.is(all[0].url, \"/templateMapCollection/test1/\");\n        t.is(all[0].outputPath, \"./test/stubs/_site/templateMapCollection/test1/index.html\");\n        return all;\n      });\n    }\n  );\n\n  let tmpl1 = await getNewTemplateByNumber(1, eleventyConfig);\n  let tmpl2 = await getNewTemplateByNumber(2, eleventyConfig);\n\n  let tm = new TemplateMap(eleventyConfig);\n  await tm.add(tmpl1);\n  await tm.add(tmpl2);\n\n  let pagedTmpl = await getNewTemplate(\n    \"./test/stubs/templateMapCollection/paged-cfg-permalink.md\",\n    \"./test/stubs/\",\n    \"./test/stubs/_site\",\n    eleventyConfig,\n  );\n  await tm.add(pagedTmpl);\n\n  let collections = await tm._testGetCollectionsData();\n  t.truthy(collections.userCollection);\n  t.truthy(collections.userCollection.length);\n\n  let urls = [];\n  for (let item of collections.all) {\n    urls.push(item.url);\n  }\n  t.is(urls.indexOf(\"/test-title/hello/\") > -1, true);\n});\n\ntest(\"Should be able to paginate a user config collection (paged template is also tagged)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback(\n    {\n\t\t\tinput: \"./test/stubs/\",\n    \toutput: \"./test/stubs/_site\",\n\t\t},\n    function(cfg) {\n      cfg.addCollection(\"userCollection\", function (collection) {\n        let all = collection.getFilteredByTag(\"dog\");\n        return all;\n      });\n    }\n  );\n\n  let tmpl1 = await getNewTemplateByNumber(1, eleventyConfig);\n  let tmpl2 = await getNewTemplateByNumber(2, eleventyConfig);\n  let tmpl4 = await getNewTemplateByNumber(4, eleventyConfig);\n\n  let tm = new TemplateMap(eleventyConfig);\n  await tm.add(tmpl1); // has dog tag\n  await tm.add(tmpl2); // does not have dog tag\n  await tm.add(tmpl4); // has dog tag\n\n  let pagedTmpl = await getNewTemplate(\n    \"./test/stubs/templateMapCollection/paged-cfg-tagged.md\",\n    \"./test/stubs/\",\n    \"./test/stubs/_site\",\n    eleventyConfig,\n  );\n  await tm.add(pagedTmpl);\n\n  let collections = await tm._testGetCollectionsData();\n  t.is(collections.dog.length, 2);\n\n  t.truthy(collections.haha);\n  t.is(collections.haha.length, 1);\n  t.is(collections.haha[0].url, \"/templateMapCollection/paged-cfg-tagged/\");\n});\n\ntest(\"Should be able to paginate a user config collection (paged template is also tagged, add all pages to collections)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback(\n    {\n\t\t\tinput: \"./test/stubs/\",\n    \toutput: \"./test/stubs/_site\",\n\t\t},\n    function(cfg) {\n      cfg.addCollection(\"userCollection\", function (collection) {\n        let all = collection.getFilteredByTag(\"dog\");\n        return all;\n      });\n    }\n  );\n\n  let tmpl1 = await getNewTemplateByNumber(1, eleventyConfig);\n  let tmpl2 = await getNewTemplateByNumber(2, eleventyConfig);\n  let tmpl4 = await getNewTemplateByNumber(4, eleventyConfig);\n\n  let tm = new TemplateMap(eleventyConfig);\n  await tm.add(tmpl1); // has dog tag\n  await tm.add(tmpl2); // does not have dog tag\n  await tm.add(tmpl4); // has dog tag\n\n  let pagedTmpl = await getNewTemplate(\n    \"./test/stubs/templateMapCollection/paged-cfg-tagged-apply-to-all.md\",\n    \"./test/stubs/\",\n    \"./test/stubs/_site\",\n    eleventyConfig,\n  );\n  await tm.add(pagedTmpl);\n\n  let collections = await tm._testGetCollectionsData();\n  t.is(collections.dog.length, 2);\n\n  t.truthy(collections.haha);\n  t.is(collections.haha.length, 2);\n  t.is(collections.haha[0].url, \"/templateMapCollection/paged-cfg-tagged-apply-to-all/\");\n  t.is(collections.haha[1].url, \"/templateMapCollection/paged-cfg-tagged-apply-to-all/1/\");\n});\n\ntest(\"Should be able to paginate a user config collection (paged template is also tagged, uses custom rendered permalink)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback(\n    {\n\t\t\tinput: \"./test/stubs/\",\n    \toutput: \"./test/stubs/_site\",\n\t\t},\n    function(cfg) {\n      cfg.addCollection(\"userCollection\", function (collection) {\n        let all = collection.getFilteredByTag(\"dog\");\n        return all;\n      });\n    }\n  );\n\n  let tmpl1 = await getNewTemplateByNumber(1, eleventyConfig);\n  let tmpl2 = await getNewTemplateByNumber(2, eleventyConfig);\n  let tmpl4 = await getNewTemplateByNumber(4, eleventyConfig);\n\n  let tm = new TemplateMap(eleventyConfig);\n  await tm.add(tmpl1); // has dog tag\n  await tm.add(tmpl2); // does not have dog tag\n  await tm.add(tmpl4); // has dog tag\n\n  let pagedTmpl = await getNewTemplate(\n    \"./test/stubs/templateMapCollection/paged-cfg-tagged-permalink.md\",\n    \"./test/stubs/\",\n    \"./test/stubs/_site\",\n    eleventyConfig,\n  );\n  await tm.add(pagedTmpl);\n\n  let collections = await tm._testGetCollectionsData();\n  t.truthy(collections.haha);\n  t.is(collections.haha.length, 1);\n  t.is(collections.haha[0].url, \"/test-title/goodbye/\");\n});\n\ntest(\"Should be able to paginate a user config collection (paged template is also tagged, uses custom rendered permalink, add all pages to collections)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback(\n    {\n\t\t\tinput: \"./test/stubs/\",\n    \toutput: \"./test/stubs/_site\",\n\t\t},\n    function(cfg) {\n      cfg.addCollection(\"userCollection\", function (collection) {\n        let all = collection.getFilteredByTag(\"dog\");\n        return all;\n      });\n    }\n  );\n\n  let tmpl1 = await getNewTemplateByNumber(1, eleventyConfig);\n  let tmpl2 = await getNewTemplateByNumber(2, eleventyConfig);\n  let tmpl4 = await getNewTemplateByNumber(4, eleventyConfig);\n\n  let tm = new TemplateMap(eleventyConfig);\n  await tm.add(tmpl1); // has dog tag\n  await tm.add(tmpl2); // does not have dog tag\n  await tm.add(tmpl4); // has dog tag\n\n  let pagedTmpl = await getNewTemplate(\n    \"./test/stubs/templateMapCollection/paged-cfg-tagged-permalink-apply-to-all.md\",\n    \"./test/stubs/\",\n    \"./test/stubs/_site\",\n    eleventyConfig,\n  );\n  await tm.add(pagedTmpl);\n\n  let collections = await tm._testGetCollectionsData();\n  t.truthy(collections.haha);\n  t.is(collections.haha.length, 2);\n  t.is(collections.haha[0].url, \"/test-title/goodbye/\");\n  t.is(collections.haha[1].url, \"/test-title-4/goodbye/\");\n});\n\ntest(\"TemplateMap render and templateContent are the same (templateContent doesn’t have layout but makes proper use of layout front matter data)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n\t\tdir: {\n\t\t\tinput: \"./test/stubs/\",\n    \toutput: \"./test/stubs/_site\",\n\t\t}\n\t});\n\n  let tm = new TemplateMap(eleventyConfig);\n  let tmplLayout = await getNewTemplate(\n    \"./test/stubs/templateMapCollection/testWithLayout.md\",\n    \"./test/stubs/\",\n    \"./test/stubs/_site\",\n    eleventyConfig,\n  );\n\n  await tm.add(tmplLayout);\n\n  let map = tm.getMap();\n  await tm.cache();\n  t.is(map[0]._pages[0].templateContent.trim(), \"<p>Inherited</p>\");\n  t.is((await renderTemplate(map[0].template, map[0].data)).trim(), \"<p>Inherited</p>\");\n});\n\ntest(\"Should be able to paginate a tag generated collection (and it has templateContent)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance();\n\n  let tmpl1 = await getNewTemplateByNumber(1, eleventyConfig);\n  let tmpl2 = await getNewTemplateByNumber(2, eleventyConfig);\n  let tmpl4 = await getNewTemplateByNumber(4, eleventyConfig);\n\n  let tm = new TemplateMap(eleventyConfig);\n  await tm.add(tmpl1); // has dog tag\n  await tm.add(tmpl2); // does not have dog tag\n  await tm.add(tmpl4); // has dog tag\n\n  let pagedTmpl = await getNewTemplate(\n    \"./test/stubs/templateMapCollection/paged-tag-dogs-templateContent.md\",\n    \"./test/stubs/\",\n    \"./test/stubs/_site\",\n    eleventyConfig,\n  );\n  await tm.add(pagedTmpl);\n\n  await tm.cache();\n\n  let pagedMapEntry = tm.getMapEntryForInputPath(\n    \"./test/stubs/templateMapCollection/paged-tag-dogs-templateContent.md\",\n  );\n\n  let templates = await getRenderedTmpls(pagedMapEntry.template, pagedMapEntry.data);\n  t.is(templates.length, 2);\n  t.is(templates[0].data.pagination.pageNumber, 0);\n  t.is(templates[1].data.pagination.pageNumber, 1);\n\n  t.is(\n    templates[0].templateContent.trim(),\n    `<p>Before</p>\n<h1>Test 1</h1>\n<p>After</p>`,\n  );\n  t.is(\n    templates[1].templateContent.trim(),\n    `<p>Before</p>\n<h1>Test 4</h1>\n<p>After</p>`,\n  );\n});\n\ntest(\"Should be able to paginate a tag generated collection when aliased (and it has templateContent)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance();\n\n  let tmpl1 = await getNewTemplateByNumber(1, eleventyConfig);\n  let tmpl2 = await getNewTemplateByNumber(2, eleventyConfig);\n  let tmpl4 = await getNewTemplateByNumber(4, eleventyConfig);\n\n  let tm = new TemplateMap(eleventyConfig);\n  await tm.add(tmpl1); // has dog tag\n  await tm.add(tmpl2); // does not have dog tag\n  await tm.add(tmpl4); // has dog tag\n\n  let pagedTmpl = await getNewTemplate(\n    \"./test/stubs/templateMapCollection/paged-tag-dogs-templateContent-alias.md\",\n    \"./test/stubs/\",\n    \"./test/stubs/_site\",\n    eleventyConfig,\n  );\n  await tm.add(pagedTmpl);\n\n  await tm.cache();\n\n  let pagedMapEntry = tm.getMapEntryForInputPath(\n    \"./test/stubs/templateMapCollection/paged-tag-dogs-templateContent-alias.md\",\n  );\n\n  let templates = await getRenderedTmpls(pagedMapEntry.template, pagedMapEntry.data);\n  t.is(templates.length, 1);\n  t.is(templates[0].data.pagination.pageNumber, 0);\n  t.is(\n    templates[0].templateContent.trim(),\n    `<p>Before</p>\n<h1>Test 1</h1>\n<h1>Test 4</h1>\n<p>After</p>`,\n  );\n});\n\ntest(\"Issue #253: Paginated template with a tag should put multiple pages into a collection\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback(\n    {\n\t\t\tinput: \"./test/stubs/\",\n    \toutput: \"./test/stubs/_site\",\n\t\t},\n    function(cfg) {\n      cfg.addCollection(\"userCollection\", function (collection) {\n        // TODO test user config collections (no actual tests against this collection yet)\n        let all = collection.getFilteredByTag(\"dog\");\n        return all;\n      });\n    }\n  );\n\n  let tmpl1 = await getNewTemplateByNumber(1, eleventyConfig);\n  let tmpl2 = await getNewTemplateByNumber(2, eleventyConfig);\n  let tmpl4 = await getNewTemplateByNumber(4, eleventyConfig);\n\n  let tm = new TemplateMap(eleventyConfig);\n  await tm.add(tmpl1);\n  await tm.add(tmpl2);\n  await tm.add(tmpl4);\n\n  let pagedTmpl = await getNewTemplate(\n    \"./test/stubs/tagged-pagination-multiples/test.njk\",\n    \"./test/stubs/\",\n    \"./test/stubs/_site\",\n    eleventyConfig,\n  );\n  await tm.add(pagedTmpl);\n\n  let collections = await tm._testGetCollectionsData();\n  t.is(collections.dog.length, 2);\n\n  t.truthy(collections.haha);\n  t.is(collections.haha.length, 2);\n  t.is(collections.haha[0].url, \"/tagged-pagination-multiples/test/\");\n  t.is(collections.haha[1].url, \"/tagged-pagination-multiples/test/1/\");\n});\n\ntest(\"getUserConfigCollectionNames\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback(\n    {},\n    function(cfg) {\n      cfg.addCollection(\"userCollection\", function (collection) {\n        return collection.getAll();\n      });\n      cfg.addCollection(\"otherUserCollection\", function (collection) {\n        return collection.getAll();\n      });\n    }\n  );\n\n  let tm = new TemplateMap(eleventyConfig);\n  t.deepEqual(tm.getUserConfigCollectionNames(), [\"userCollection\", \"otherUserCollection\"]);\n});\n\ntest(\"isUserConfigCollectionName\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback(\n    {},\n    function(cfg) {\n      cfg.addCollection(\"userCollection\", function (collection) {\n        return collection.getAll();\n      });\n    }\n  );\n\n  let tm = new TemplateMap(eleventyConfig);\n\n  t.is(tm.isUserConfigCollectionName(\"userCollection\"), true);\n  t.is(tm.isUserConfigCollectionName(\"userCollection2\"), false);\n});\n\ntest(\"Dependency Map should have nodes that have no dependencies and no dependents\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance();\n\n  let tmpl1 = await getNewTemplateByNumber(1, eleventyConfig);\n  let tmpl5 = await getNewTemplateByNumber(5, eleventyConfig);\n\n  let tm = new TemplateMap(eleventyConfig);\n  await tm.add(tmpl1);\n  await tm.add(tmpl5);\n\n  await tm.cache();\n\n  let deps = tm.getTemplateOrder();\n  t.true(deps.filter((dep) => dep.indexOf(\"test5.md\") > -1).length > 0);\n\n  let collections = await tm._testGetCollectionsData();\n  t.is(collections.all.length, 2);\n});\n\ntest(\"Dependency Map should have include orphan user config collections (in the correct order) #3711\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback(\n    {},\n    function(cfg) {\n      cfg.addCollection(\"userCollection\", function (collection) {\n        return collection.getAll();\n      });\n    }\n  );\n\n  let tmpl1 = await getNewTemplateByNumber(1, eleventyConfig);\n  let tmpl5 = await getNewTemplateByNumber(5, eleventyConfig);\n\n  let tm = new TemplateMap(eleventyConfig);\n  await tm.add(tmpl1);\n  await tm.add(tmpl5);\n\n  await tm.cache();\n\n  let deps = tm.getTemplateOrder();\n  t.deepEqual(deps,  [\n    './test/stubs/templateMapCollection/test1.md',\n    '__collection:post',\n    '__collection:dog',\n    './test/stubs/templateMapCollection/test5.md',\n    '__collection:userCollection',\n    '__collection:[keys]',\n    '__collection:all'\n  ]);\n\n  let collections = await tm._testGetCollectionsData();\n  t.is(collections.all.length, 2);\n  t.is(collections.userCollection.length, 2);\n});\n\ntest(\"Dependency Map should have include orphan user config collections, mapped by user collection api to tag #3711\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback(\n    {},\n    function(cfg) {\n      cfg.addCollection(\"userCollection\", function (collection) {\n        return collection.getFilteredByTag(\"post\");\n      });\n    }\n  );\n\n  let tmpl1 = await getNewTemplateByNumber(1, eleventyConfig);\n  let tmpl5 = await getNewTemplateByNumber(5, eleventyConfig);\n\n  let tm = new TemplateMap(eleventyConfig);\n  await tm.add(tmpl1);\n  await tm.add(tmpl5);\n\n  await tm.cache();\n\n  let deps = tm.getTemplateOrder();\n  t.deepEqual(deps,  [\n    './test/stubs/templateMapCollection/test1.md',\n    '__collection:post',\n    '__collection:dog',\n    './test/stubs/templateMapCollection/test5.md',\n    '__collection:userCollection',\n    '__collection:[keys]',\n    '__collection:all',\n  ]);\n\n  let collections = await tm._testGetCollectionsData();\n  t.is(collections.all.length, 2);\n  t.is(collections.userCollection.length, 1);\n});\n\ntest(\"Template pages should not have layouts when added to collections\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n\t\tdir: {\n\t\t\tinput: \"./test/stubs/\",\n    \toutput: \"./test/stubs/_site\",\n\t\t}\n\t});\n\n  let tm = new TemplateMap(eleventyConfig);\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/collection-layout-wrap.njk\",\n    \"./test/stubs/\",\n    \"./test/stubs/_site\",\n    eleventyConfig,\n  );\n  await tm.add(tmpl);\n  t.is(await renderTemplate(tmpl, await tmpl.getData()), \"<div>Layout Test</div>\");\n\n  let collections = await tm._testGetCollectionsData();\n  t.is(collections.all.length, 1);\n  t.is(collections.all[0].templateContent, \"Layout Test\");\n});\n\ntest(\"Paginated template pages should not have layouts when added to collections\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n\t\tdir: {\n\t\t\tinput: \"test/stubs\",\n\t\t\toutput: \"test/stubs/_site\",\n\t\t}\n\t});\n\n  let tm = new TemplateMap(eleventyConfig);\n\n  let pagedTmpl = await getNewTemplate(\n    \"./test/stubs/tagged-pagination-multiples-layout/test.njk\",\n    \"./test/stubs/\",\n    \"./test/stubs/_site\",\n    eleventyConfig,\n  );\n  await tm.add(pagedTmpl);\n\n  let collections = await tm._testGetCollectionsData();\n\n  t.is(collections.all.length, 3);\n  t.is(collections.all[0].templateContent, \"one\");\n  t.is(collections.all[1].templateContent, \"two\");\n  t.is(collections.all[2].templateContent, \"three\");\n});\n\ntest(\"Tag pages. Allow pagination over all collections a la `data: collections`\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance();\n\n  let tmpl1 = await getNewTemplateByNumber(1, eleventyConfig);\n  let tmpl2 = await getNewTemplateByNumber(2, eleventyConfig);\n\n  let tm = new TemplateMap(eleventyConfig);\n\n  let pagedTmpl = await getNewTemplate(\n    \"./test/stubs/page-target-collections/tagpages.njk\",\n    \"./test/stubs/\",\n    \"./test/stubs/_site\",\n    eleventyConfig,\n  );\n\n  await tm.add(pagedTmpl);\n  await tm.add(tmpl1);\n  await tm.add(tmpl2);\n\n  let collections = await tm._testGetCollectionsData();\n  t.is(collections.all.length, 3);\n\n  let collectionTagPagesTemplateContents = new Set(\n    collections.all\n      .filter(function (entry) {\n        return entry.inputPath.endsWith(\"tagpages.njk\");\n      })\n      .map(function (entry) {\n        return entry.templateContent.trim();\n      }),\n  );\n  t.deepEqual(collectionTagPagesTemplateContents, new Set([\"post\"]));\n});\n\ntest(\"Tag pages (all pages added to collections). Allow pagination over all collections a la `data: collections`\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance();\n\n  let tmpl1 = await getNewTemplateByNumber(1, eleventyConfig);\n  let tmpl2 = await getNewTemplateByNumber(2, eleventyConfig);\n\n  let tm = new TemplateMap(eleventyConfig);\n\n  let pagedTmpl = await getNewTemplate(\n    \"./test/stubs/page-target-collections/tagpagesall.njk\",\n    \"./test/stubs/\",\n    \"./test/stubs/_site\",\n    eleventyConfig,\n  );\n\n  await tm.add(pagedTmpl);\n  await tm.add(tmpl1);\n  await tm.add(tmpl2);\n\n  let collections = await tm._testGetCollectionsData();\n  t.is(collections.all.length, 5);\n\n  let collectionTagPagesTemplateContents = new Set(\n    collections.all\n      .filter(function (entry) {\n        return entry.inputPath.endsWith(\"tagpagesall.njk\");\n      })\n      .map(function (entry) {\n        return entry.templateContent.trim();\n      }),\n  );\n  t.deepEqual(collectionTagPagesTemplateContents, new Set([\"post\", \"dog\", \"cat\"]));\n});\n\ntest(\"eleventyExcludeFromCollections\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance();\n\n  let tmpl1 = await getNewTemplateByNumber(1, eleventyConfig);\n\n  let tm = new TemplateMap(eleventyConfig);\n  await tm.add(tmpl1);\n\n  let excludedTmpl = await getNewTemplate(\n    \"./test/stubs/eleventyExcludeFromCollections.njk\",\n    \"./test/stubs/\",\n    \"./test/stubs/_site\",\n    eleventyConfig,\n  );\n\n  await tm.add(excludedTmpl);\n\n  await tm.cache();\n\n  t.is(tm.getMap().length, 2);\n\n  let collections = await tm._testGetCollectionsData();\n  t.is(collections.all.length, 1);\n  t.is(collections.post.length, 1);\n  t.is(collections.dog.length, 1);\n});\n\ntest(\"eleventyExcludeFromCollections and permalink: false\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance();\n\n  let tmpl1 = await getNewTemplateByNumber(1, eleventyConfig);\n\n  let tm = new TemplateMap(eleventyConfig);\n  await tm.add(tmpl1);\n\n  let excludedTmpl = await getNewTemplate(\n    \"./test/stubs/eleventyExcludeFromCollectionsPermalinkFalse.njk\",\n    \"./test/stubs/\",\n    \"./test/stubs/_site\",\n    eleventyConfig,\n  );\n\n  await tm.add(excludedTmpl);\n\n  await tm.cache();\n\n  t.is(tm.getMap().length, 2);\n\n  let collections = await tm._testGetCollectionsData();\n  t.is(collections.all.length, 1);\n  t.is(collections.post.length, 1);\n  t.is(collections.dog.length, 1);\n});\n\ntest(\"Paginate over collections.all\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance();\n\n  let tmpl1 = await getNewTemplateByNumber(1, eleventyConfig);\n  let tmpl2 = await getNewTemplateByNumber(2, eleventyConfig);\n\n  let tm = new TemplateMap(eleventyConfig);\n\n  let pagedTmpl = await getNewTemplate(\n    \"./test/stubs/page-target-collections/paginateall.njk\",\n    \"./test/stubs/\",\n    \"./test/stubs/_site\",\n    eleventyConfig,\n  );\n\n  await tm.add(pagedTmpl);\n  await tm.add(tmpl1);\n  await tm.add(tmpl2);\n\n  let collections = await tm._testGetCollectionsData();\n  t.is(collections.all.length, 4);\n  t.is(\n    collections.all.filter(function (entry) {\n      return entry.inputPath.endsWith(\"test1.md\");\n    }).length,\n    1,\n  );\n  t.is(\n    collections.all.filter(function (entry) {\n      return entry.inputPath.endsWith(\"test2.md\");\n    }).length,\n    1,\n  );\n  t.is(\n    collections.all.filter(function (entry) {\n      return entry.inputPath.endsWith(\"paginateall.njk\");\n    }).length,\n    2,\n  );\n\n  let map = tm.getMap();\n  t.is(map[0].inputPath, \"./test/stubs/page-target-collections/paginateall.njk\");\n  t.is(map[0]._pages.length, 2);\n  t.is(map[0]._pages[0].templateContent, \"INPUT PATH:./test/stubs/templateMapCollection/test1.md\");\n  t.is(map[0]._pages[1].templateContent, \"INPUT PATH:./test/stubs/templateMapCollection/test2.md\");\n  t.is(map[1].inputPath, \"./test/stubs/templateMapCollection/test1.md\");\n  t.is(map[1]._pages[0].templateContent.trim(), \"<h1>Test 1</h1>\");\n  t.is(map[2].inputPath, \"./test/stubs/templateMapCollection/test2.md\");\n  t.is(map[2]._pages[0].templateContent.trim(), \"<h1>Test 2</h1>\");\n});\n\ntest(\"Paginate over collections (tag pages) related to #3823\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance();\n\n  let tmpl1 = await getNewTemplateByNumber(1, eleventyConfig);\n  let tmpl2 = await getNewTemplateByNumber(2, eleventyConfig);\n\n  let tm = new TemplateMap(eleventyConfig);\n\n  let tagPagesTmpl = await getNewTemplate(\n    \"./test/stubs/page-target-collections/tagpagesall.njk\",\n    \"./test/stubs/\",\n    \"./test/stubs/_site\",\n    eleventyConfig,\n  );\n\n  await tm.add(tagPagesTmpl);\n  await tm.add(tmpl1);\n  await tm.add(tmpl2);\n\n  let collections = await tm._testGetCollectionsData();\n  // 2 individual templates, 3 pages for tagpagesall\n  t.deepEqual(collections.all.map(entry => entry.url).sort(), [\n    '/test/stubs/page-target-collections/tagpagesall/',\n    '/test/stubs/page-target-collections/tagpagesall/1/',\n    '/test/stubs/page-target-collections/tagpagesall/2/',\n    '/test/stubs/templateMapCollection/test1/',\n    '/test/stubs/templateMapCollection/test2/',\n  ]);\n});\n\ntest(\"Paginate over collections.all WITH a paginate over collections (tag pages) related to #3823\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance();\n\n  let tmpl1 = await getNewTemplateByNumber(1, eleventyConfig);\n  let tmpl2 = await getNewTemplateByNumber(2, eleventyConfig);\n\n  let tm = new TemplateMap(eleventyConfig);\n\n  let pagedTmpl = await getNewTemplate(\n    \"./test/stubs/page-target-collections/paginateall.njk\",\n    \"./test/stubs/\",\n    \"./test/stubs/_site\",\n    eleventyConfig,\n  );\n  let tagPagesTmpl = await getNewTemplate(\n    \"./test/stubs/page-target-collections/tagpagesall.njk\",\n    \"./test/stubs/\",\n    \"./test/stubs/_site\",\n    eleventyConfig,\n  );\n\n  await tm.add(pagedTmpl);\n  await tm.add(tagPagesTmpl);\n  await tm.add(tmpl1);\n  await tm.add(tmpl2);\n\n  let collections = await tm._testGetCollectionsData();\n  // 2 individual templates, 3 pages for tagpagesall, 5 pages from paginateall to paginate the 2+3\n  t.is(collections.all.length, 10);\n});\n\ntest(\"Test a transform with a layout (via templateMap)\", async (t) => {\n  t.plan(7);\n  let eleventyConfig = await getTemplateConfigInstance({\n\t\tdir: {\n\t\t\tinput: \"./test/stubs-475/\",\n\t\t\toutput: \"./test/stubs-475/_site\",\n\t\t}\n\t});\n\n  let tm = new TemplateMap(eleventyConfig);\n  let tmpl = await getNewTemplate(\n    \"./test/stubs-475/transform-layout/transform-layout.njk\",\n    \"./test/stubs-475/\",\n    \"./test/stubs-475/_site\",\n    eleventyConfig,\n  );\n\n  tmpl.addLinter(function (content, inputPath, outputPath) {\n    // should be pre-transform content\n    t.is(content, \"<html><body>This is content.</body></html>\");\n    t.true(inputPath.endsWith(\"transform-layout.njk\"));\n    t.true(outputPath.endsWith(\"transform-layout/index.html\"));\n  });\n\n  tmpl.setTransforms({\n    transformName: function (content, outputPath) {\n      t.is(content, \"<html><body>This is content.</body></html>\");\n      t.true(outputPath.endsWith(\"transform-layout/index.html\"));\n      return \"OVERRIDE BY A TRANSFORM\";\n    }\n  });\n\n  await tm.add(tmpl);\n\n  await tm.cache();\n  t.is(tm.getMap().length, 1);\n\n  for (let entry of tm.getMap()) {\n    for (let page of entry._pages) {\n      t.is(await page.template.renderPageEntry(page), \"OVERRIDE BY A TRANSFORM\");\n    }\n  }\n});\n\ntest(\"Async user collection addCollection method\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback(\n    {\n\t\t\tinput: \"./test/stubs/\",\n    \toutput: \"./test/stubs/_site\",\n\t\t},\n    function(cfg) {\n      cfg.addCollection(\"userCollection\", async function (collection) {\n        return new Promise((resolve) => {\n          setTimeout(function () {\n            resolve(collection.getAll());\n          }, 50);\n        });\n      });\n    }\n  );\n\n  let tmpl1 = await getNewTemplateByNumber(1, eleventyConfig);\n\n  let tm = new TemplateMap(eleventyConfig);\n  await tm.add(tmpl1);\n\n  let collections = await tm._testGetCollectionsData();\n  t.is(collections.userCollection[0].url, \"/templateMapCollection/test1/\");\n\n  t.is(collections.userCollection[0].data.collections.userCollection.length, 1);\n});\n\ntest(\"Duplicate permalinks in template map\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance();\n\n  let tmpl1 = await getNewTemplate(\n    \"./test/stubs/permalink-conflicts/test1.md\",\n    \"./test/stubs/\",\n    \"./test/stubs/_site\",\n    eleventyConfig,\n  );\n  let tmpl2 = await getNewTemplate(\n    \"./test/stubs/permalink-conflicts/test2.md\",\n    \"./test/stubs/\",\n    \"./test/stubs/_site\",\n    eleventyConfig,\n  );\n\n  let tm = new TemplateMap(eleventyConfig);\n  await tm.add(tmpl1);\n  await tm.add(tmpl2);\n  await t.throwsAsync(async () => {\n    await tm.cache();\n  });\n});\n\ntest(\"No duplicate permalinks in template map, using false\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance();\n\n  let tmpl1 = await getNewTemplate(\n    \"./test/stubs/permalink-conflicts-false/test1.md\",\n    \"./test/stubs/\",\n    \"./test/stubs/_site\",\n    eleventyConfig,\n  );\n  let tmpl2 = await getNewTemplate(\n    \"./test/stubs/permalink-conflicts-false/test2.md\",\n    \"./test/stubs/\",\n    \"./test/stubs/_site\",\n    eleventyConfig,\n  );\n\n  let tm = new TemplateMap(eleventyConfig);\n  await tm.add(tmpl1);\n  await tm.add(tmpl2);\n  await tm.cache();\n  t.true(true);\n});\n\ntest(\"Duplicate permalinks in template map, no leading slash\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance();\n\n  let tmpl1 = await getNewTemplate(\n    \"./test/stubs/permalink-conflicts/test1.md\",\n    \"./test/stubs/\",\n    \"./test/stubs/_site\",\n    eleventyConfig,\n  );\n  let tmpl3 = await getNewTemplate(\n    \"./test/stubs/permalink-conflicts/test3.md\",\n    \"./test/stubs/\",\n    \"./test/stubs/_site\",\n    eleventyConfig,\n  );\n\n  let tm = new TemplateMap(eleventyConfig);\n  await tm.add(tmpl1);\n  await tm.add(tmpl3);\n\n  await t.throwsAsync(async () => {\n    await tm.cache();\n  });\n});\n\ntest(\"TemplateMap circular references (map.templateContent) using eleventyExcludeFromCollections and collections.all\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance();\n\n  let tm = new TemplateMap(eleventyConfig);\n  let tmplExcluded = await getNewTemplate(\n    \"./test/stubs/issue-522/excluded.md\",\n    \"./test/stubs/\",\n    \"./test/stubs/_site\",\n    eleventyConfig,\n  );\n  await tm.add(tmplExcluded);\n\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/issue-522/template.md\",\n    \"./test/stubs/\",\n    \"./test/stubs/_site\",\n    eleventyConfig,\n  );\n\n  await tm.add(tmpl);\n\n  let map = tm.getMap();\n  t.falsy(map[0].data.collections);\n\n  await tm.cache();\n\n  let deps = tm.getTemplateOrder();\n  t.deepEqual(deps, [\n    \"./test/stubs/issue-522/excluded.md\",\n    \"./test/stubs/issue-522/template.md\",\n    \"__collection:[keys]\",\n    \"__collection:all\",\n  ]);\n\n  t.is(tm.getMap().length, 2);\n\n  let collections = await tm._testGetCollectionsData();\n  t.is(collections.all.length, 1);\n});\n\ntest(\"permalink object with build\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance();\n\n  let tm = new TemplateMap(eleventyConfig);\n  let tmplLayout = await getNewTemplate(\n    \"./test/stubs/permalink-build/permalink-build.md\",\n    \"./test/stubs/\",\n    \"./test/stubs/_site\",\n    eleventyConfig,\n  );\n\n  await tm.add(tmplLayout);\n\n  let map = tm.getMap();\n  await tm.cache();\n\n  t.is(map[0]._pages.length, 1);\n});\n\ntest(\"permalink object without build (defaults to `read` mode)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance();\n\n  let tm = new TemplateMap(eleventyConfig);\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/permalink-nobuild/permalink-nobuild.md\",\n    \"./test/stubs/\",\n    \"./test/stubs/_site\",\n    eleventyConfig,\n  );\n\n  await tm.add(tmpl);\n\n  let map = tm.getMap();\n  await tm.cache();\n\n  t.is(map[0]._pages.length, 1);\n  t.throws(\n    () => {\n      map[0]._pages[0].templateContent;\n    },\n    {\n      instanceOf: TemplateContentUnrenderedTemplateError,\n    },\n  );\n});\n\ntest(\"eleventy.layouts Event\", async (t) => {\n  t.plan(1);\n\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback(\n    {\n\t\t\tinput: \"./test/stubs-layouts-event/\",\n\t\t\toutput: \"./test/stubs-layouts-event/_site\",\n\t\t},\n    function(cfg) {\n      cfg.on(\"eleventy.layouts\", (layoutMap) => {\n        t.deepEqual(layoutMap, {\n          \"./test/stubs-layouts-event/_includes/first.liquid\": [\"./test/stubs-layouts-event/page.md\"],\n          \"./test/stubs-layouts-event/_includes/second.liquid\": [\n            \"./test/stubs-layouts-event/page.md\",\n            \"./test/stubs-layouts-event/_includes/first.liquid\",\n          ],\n          \"./test/stubs-layouts-event/_includes/third.liquid\": [\n            \"./test/stubs-layouts-event/page.md\",\n            \"./test/stubs-layouts-event/_includes/first.liquid\",\n            \"./test/stubs-layouts-event/_includes/second.liquid\",\n          ],\n        });\n      });\n    }\n  );\n\n  let tm = new TemplateMap(eleventyConfig);\n  let tmpl = await getNewTemplate(\n    \"./test/stubs-layouts-event/page.md\",\n    \"./test/stubs-layouts-event/\",\n    \"./test/stubs-layouts-event/_site\",\n    eleventyConfig,\n  );\n\n  await tm.add(tmpl);\n  await tm.cache();\n});\n"
  },
  {
    "path": "test/TemplatePassthroughManagerTest.js",
    "content": "import test from \"ava\";\nimport fs from \"fs\";\n\nimport TemplatePassthroughManager from \"../src/TemplatePassthroughManager.js\";\nimport TemplateConfig from \"../src/TemplateConfig.js\";\nimport FileSystemSearch from \"../src/FileSystemSearch.js\";\nimport EleventyExtensionMap from \"../src/EleventyExtensionMap.js\";\nimport ProjectDirectories from \"../src/Util/ProjectDirectories.js\";\n\nimport { getTemplateConfigInstance, getTemplateConfigInstanceCustomCallback, getEleventyFilesInstance, deleteDirectory } from \"./_testHelpers.js\";\n\ntest(\"Get paths from Config\", async (t) => {\n  let eleventyConfig = new TemplateConfig();\n  eleventyConfig.userConfig.passthroughCopies = {\n    img: { outputPath: true },\n  };\n  await eleventyConfig.init();\n\n  let mgr = new TemplatePassthroughManager(eleventyConfig);\n\n  t.deepEqual(mgr.getConfigPaths(), [{ inputPath: \"./img\", outputPath: true, copyOptions: {} }]);\n});\n\ntest(\"isPassthroughCopyFile\", async (t) => {\n  let eleventyConfig = new TemplateConfig();\n  eleventyConfig.userConfig.passthroughCopies = {\n    img: { outputPath: true },\n    fonts: { outputPath: true },\n  };\n  await eleventyConfig.init();\n\n  let mgr = new TemplatePassthroughManager(eleventyConfig);\n  mgr.extensionMap = new EleventyExtensionMap(eleventyConfig);\n\n  t.false(mgr.isPassthroughCopyFile([]));\n  t.false(mgr.isPassthroughCopyFile([], \"\"));\n  t.false(mgr.isPassthroughCopyFile([], null));\n\n  t.truthy(mgr.isPassthroughCopyFile([], \"./img/test.png\"));\n  t.deepEqual(mgr.isPassthroughCopyFile([], \"./img/test.png\"), {\n    inputPath: \"./img\",\n    outputPath: true,\n    copyOptions: {},\n  });\n\n  t.truthy(mgr.isPassthroughCopyFile([], \"./fonts/Roboto.woff\"));\n  t.deepEqual(mgr.isPassthroughCopyFile([], \"./fonts/Roboto.woff\"), {\n    inputPath: \"./fonts\",\n    outputPath: true,\n    copyOptions: {},\n  });\n\n  t.false(mgr.isPassthroughCopyFile([], \"./docs/test.njk\"));\n  t.false(mgr.isPassthroughCopyFile([], \"./other-dir/test.png\"));\n  t.true(mgr.isPassthroughCopyFile([\"hi\", \"./other-dir/test.png\"], \"./other-dir/test.png\"));\n});\n\ntest(\"Get glob paths from config\", async (t) => {\n  let eleventyConfig = new TemplateConfig();\n  eleventyConfig.userConfig.passthroughCopies = {\n    \"test/stubs/img\": { outputPath: true },\n    \"test/stubs/img/**\": { outputPath: \"./\" },\n    \"test/stubs/img/*.js\": { outputPath: \"./\" },\n  };\n  await eleventyConfig.init();\n\n  let mgr = new TemplatePassthroughManager(eleventyConfig);\n\n  t.deepEqual(mgr.getConfigPathGlobs(), [\n    \"./test/stubs/img/**\",\n    \"./test/stubs/img/**\",\n    \"./test/stubs/img/*.js\",\n  ]);\n});\n\ntest(\"Get file paths\", async (t) => {\n  let eleventyConfig = new TemplateConfig();\n  await eleventyConfig.init();\n\n  let mgr = new TemplatePassthroughManager(eleventyConfig);\n  mgr.extensionMap = new EleventyExtensionMap(eleventyConfig);\n\n  t.deepEqual(mgr.getNonTemplatePaths([\"test.png\"]), [\"test.png\"]);\n});\n\ntest(\"Get file paths (filter out real templates)\", async (t) => {\n  let eleventyConfig = new TemplateConfig();\n  await eleventyConfig.init();\n\n  let mgr = new TemplatePassthroughManager(eleventyConfig);\n  mgr.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  mgr.extensionMap.setFormats([\"njk\"]);\n\n  t.deepEqual(mgr.getNonTemplatePaths([\"test.njk\"]), []);\n});\n\ntest(\"Get file paths (filter out real templates), multiple\", async (t) => {\n  let eleventyConfig = new TemplateConfig();\n  await eleventyConfig.init();\n\n  let mgr = new TemplatePassthroughManager(eleventyConfig);\n  mgr.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  mgr.extensionMap.setFormats([\"njk\"]);\n\n  t.deepEqual(mgr.getNonTemplatePaths([\"test.njk\", \"test.png\"]), [\"test.png\"]);\n});\n\ntest(\"Get file paths with a js file (filter out real templates), multiple\", async (t) => {\n  let eleventyConfig = new TemplateConfig();\n  await eleventyConfig.init();\n\n  let mgr = new TemplatePassthroughManager(eleventyConfig);\n  mgr.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  mgr.extensionMap.setFormats([\"njk\"]);\n\n  t.deepEqual(mgr.getNonTemplatePaths([\"test.njk\", \"test.js\"]), [\"test.js\"]);\n});\n\n// This test used to be for passthroughFileCopy: false in config\ntest(\"Get file paths (one image path)\", async (t) => {\n  let eleventyConfig = new TemplateConfig();\n  await eleventyConfig.init();\n\n  let mgr = new TemplatePassthroughManager(eleventyConfig);\n  mgr.extensionMap = new EleventyExtensionMap(eleventyConfig);\n\n  t.deepEqual(mgr.getNonTemplatePaths([\"test.png\"]), [\"test.png\"]);\n});\n\ntest(\"Naughty paths outside of project dir\", async (t) => {\n  let dirs = new ProjectDirectories();\n\tdirs.setInput(\"./test/stubs/template-passthrough2/\");\n\tdirs.setOutput(\"./test/stubs/template-passthrough2/_site/\");\n\n  let eleventyConfig = new TemplateConfig();\n  eleventyConfig.setDirectories(dirs);\n  eleventyConfig.userConfig.passthroughCopies = {\n    \"../static\": { outputPath: true },\n    \"../*\": { outputPath: \"./\" },\n    \"./test/stubs/template-passthrough2/static/*.css\": { outputPath: \"./\" },\n    \"./test/stubs/template-passthrough2/static/*.js\": { outputPath: \"../../\" },\n    \"./test/stubs/template-passthrough2/img.jpg\": { outputPath: \"../../\" },\n  };\n  await eleventyConfig.init();\n\n  let mgr = new TemplatePassthroughManager(eleventyConfig);\n  mgr.setFileSystemSearch(new FileSystemSearch());\n\n  await t.throwsAsync(async function () {\n    for (let path of mgr.getConfigPaths()) {\n      let pass = mgr.getTemplatePassthroughForPath(path);\n      await mgr.copyPassthrough(pass);\n    }\n  }, {\n    message: `Having trouble copying './test/stubs/template-passthrough2/static/*.js'`\n  });\n\n  const output = [\n    \"./test/stubs/template-passthrough2/_site/static\",\n    \"./test/stubs/template-passthrough2/_site/nope.txt\",\n    \"./test/stubs/template-passthrough2/_site/nope/\",\n    \"./test/stubs/test.js\",\n    \"./test/stubs/img.jpg\",\n  ];\n\n  for (let path of output) {\n    t.false(fs.existsSync(path));\n  }\n});\n\ntest(\"getAllNormalizedPaths\", async (t) => {\n  let eleventyConfig = new TemplateConfig();\n  eleventyConfig.userConfig.passthroughCopies = {\n    img: { outputPath: true },\n  };\n  await eleventyConfig.init();\n\n  let mgr = new TemplatePassthroughManager(eleventyConfig);\n  t.deepEqual(mgr.getAllNormalizedPaths(), [\n    { inputPath: \"./img\", outputPath: true, copyOptions: {} },\n  ]);\n});\n\ntest(\"getAllNormalizedPaths with globs\", async (t) => {\n  let eleventyConfig = new TemplateConfig();\n  eleventyConfig.userConfig.passthroughCopies = {\n    img: { outputPath: true },\n    \"img/**\": { outputPath: \"./\" },\n    \"img/*.js\": { outputPath: \"./\" },\n  };\n  await eleventyConfig.init();\n\n  let mgr = new TemplatePassthroughManager(eleventyConfig);\n  t.deepEqual(mgr.getAllNormalizedPaths(), [\n    { inputPath: \"./img\", outputPath: true, copyOptions: {} },\n    { inputPath: \"./img/**\", outputPath: \"\", copyOptions: {} },\n    { inputPath: \"./img/*.js\", outputPath: \"\", copyOptions: {} },\n  ]);\n});\n\ntest(\"getAliasesFromPassthroughResults with Unicode filenames\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance();\n\n  let mgr = new TemplatePassthroughManager(eleventyConfig);\n  let results = [\n    { map: { \"./path/file\": \"_site/path/file\" } },\n    { map: { \"./测试.用例/⌘\": \"_site/测试.用例/⌘\" } },\n    { map: { \"./path/测试.用例\": \"_site/path/测试.用例\" } },\n    { map: { \"./测试.用例/file\": \"_site/测试.用例/file\" } },\n  ]\n  t.deepEqual(mgr.getAliasesFromPassthroughResults(results), {\n    \"/path/file\": \"./path/file\",\n    \"/%E6%B5%8B%E8%AF%95.%E7%94%A8%E4%BE%8B/%E2%8C%98\": \"./测试.用例/⌘\",\n    \"/path/%E6%B5%8B%E8%AF%95.%E7%94%A8%E4%BE%8B\": \"./path/测试.用例\",\n    \"/%E6%B5%8B%E8%AF%95.%E7%94%A8%E4%BE%8B/file\": \"./测试.用例/file\",\n  });\n});\n\ntest(\"Look for uniqueness on template passthrough paths #1677\", async (t) => {\n  let formats = [];\n\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback({\n    input: \"test/stubs/template-passthrough-duplicates/\",\n    output: \"test/stubs/template-passthrough-duplicates/_site\"\n  }, function(cfg) {\n    cfg.passthroughCopies = {\n      \"./test/stubs/template-passthrough-duplicates/input/**/*.png\": {\n        outputPath: \"./\",\n      },\n    };\n  });\n\n  let { passthroughManager } = getEleventyFilesInstance(formats, eleventyConfig);\n\n  await t.throwsAsync(async function () {\n    await passthroughManager.copyAll();\n  }, {\n    message: `Multiple passthrough copy files are trying to write to the same output file (./test/stubs/template-passthrough-duplicates/_site/avatar.png). ./test/stubs/template-passthrough-duplicates/input/avatar.png and ./test/stubs/template-passthrough-duplicates/input/src/views/avatar.png`\n  });\n\n  deleteDirectory(\"test/stubs/template-passthrough-duplicates/_site/\");\n});\n\ntest(\"Incremental passthrough, issue #3285\", async (t) => {\n  let eleventyConfig = new TemplateConfig();\n  eleventyConfig.userConfig.addPassthroughCopy({ './test/stubs-3285/src/scripts': 'scripts' });\n  await eleventyConfig.init();\n\n  let mgr = new TemplatePassthroughManager(eleventyConfig);\n  mgr.setIncrementalFile(\"./test/stubs-3285/src/scripts/hello-world.js\");\n  t.deepEqual(mgr.getAllNormalizedPaths([]), [\n    { copyOptions: {}, inputPath: \"./test/stubs-3285/src/scripts\", outputPath: \"scripts\" },\n  ]);\n});\n"
  },
  {
    "path": "test/TemplatePassthroughTest.js",
    "content": "import test from \"ava\";\n\nimport FileSystemSearch from \"../src/FileSystemSearch.js\";\nimport TemplatePassthrough from \"../src/TemplatePassthrough.js\";\n\nimport { getTemplateConfigInstance } from \"./_testHelpers.js\";\n\nasync function getTemplatePassthrough(path, outputDir, inputDir) {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: inputDir,\n      output: outputDir,\n    }\n  });\n\n  if (typeof path === \"object\") {\n    let p = new TemplatePassthrough(path, eleventyConfig);\n    p.setFileSystemSearch(new FileSystemSearch());\n    return p;\n  }\n\n  let p = new TemplatePassthrough(\n    { inputPath: path, outputPath: true },\n    eleventyConfig\n  );\n  p.setFileSystemSearch(new FileSystemSearch());\n  return p;\n}\n\ntest(\"Constructor\", async (t) => {\n  let pass = await getTemplatePassthrough(\"avatar.png\", \"_site\", \".\");\n  t.truthy(pass);\n  t.is(pass.outputPath, true);\n  t.is(await pass.getOutputPath(), \"_site/avatar.png\");\n});\n\ntest(\"Constructor, input directory in inputPath is stripped\", async (t) => {\n  let pass = await getTemplatePassthrough(\"src/avatar.png\", \"_site\", \"src\");\n  t.is(pass.outputPath, true);\n  t.is(await pass.getOutputPath(), \"_site/avatar.png\");\n\n  let pass2 = await getTemplatePassthrough(\n    { inputPath: \"src/avatar.png\", outputPath: \"avatar.png\" },\n    \"_site\",\n    \"src\"\n  );\n  t.is(pass2.outputPath, \"avatar.png\");\n  t.is(await pass2.getOutputPath(), \"_site/avatar.png\");\n});\n\ntest(\"Constructor, input directory in inputPath is stripped, duplicate directory names\", async (t) => {\n  let pass = await getTemplatePassthrough(\"src/src/avatar.png\", \"_site\", \"src\");\n  t.is(pass.outputPath, true);\n  t.is(await pass.getOutputPath(), \"_site/src/avatar.png\");\n\n  let pass2 = await getTemplatePassthrough(\n    { inputPath: \"src/src/avatar.png\", outputPath: \"src/avatar.png\" },\n    \"_site\",\n    \"src\"\n  );\n  t.is(pass2.outputPath, \"src/avatar.png\");\n  t.is(await pass2.getOutputPath(), \"_site/src/avatar.png\");\n});\n\ntest(\"Constructor, input directory (object param, directory)\", async (t) => {\n  let pass = await getTemplatePassthrough(\n    { inputPath: \"src/test\", outputPath: \"test\" },\n    \"_site\",\n    \"src\"\n  );\n  t.is(pass.outputPath, \"test\");\n  t.is(await pass.getOutputPath(), \"_site/test\");\n});\n\ntest(\"Constructor, input directory, path missing input directory\", async (t) => {\n  let pass = await getTemplatePassthrough(\"avatar.png\", \"_site\", \"src\");\n  t.is(pass.outputPath, true);\n  t.is(await pass.getOutputPath(), \"_site/avatar.png\");\n\n  let pass2 = await getTemplatePassthrough(\n    { inputPath: \"avatar.png\", outputPath: \"avatar.png\" },\n    \"_site\",\n    \"src\"\n  );\n  t.is(pass2.outputPath, \"avatar.png\");\n  t.is(await pass2.getOutputPath(), \"_site/avatar.png\");\n});\n\ntest(\"Constructor not Dry Run\", async (t) => {\n  let pass = await getTemplatePassthrough(\"avatar.png\", \"_site\", \".\");\n  t.is(pass.outputPath, true);\n  t.is(pass.isDryRun, false);\n});\n\ntest(\"Constructor Dry Run\", async (t) => {\n  let pass = await getTemplatePassthrough(\"avatar.png\", \"_site\", \".\");\n  pass.setDryRun(true);\n  t.is(pass.outputPath, true);\n  t.is(pass.isDryRun, true);\n});\n\ntest(\"Origin path isn’t included in output when targeting a directory\", async (t) => {\n  let pass = await getTemplatePassthrough(\"img\", \"_site\", \"test/stubs\");\n  t.is(pass.outputPath, true);\n  t.is(await pass.getOutputPath(), \"_site/img\");\n});\n\ntest(\"Origin path isn’t included in output when targeting a directory several levels deep\", async (t) => {\n  let pass = await getTemplatePassthrough(\"img\", \"_site\", \"test/stubs/subdir\");\n  t.is(pass.outputPath, true);\n  t.is(await pass.getOutputPath(), \"_site/img\");\n});\n\ntest(\"Target directory’s subdirectory structure is retained\", async (t) => {\n  let pass = await getTemplatePassthrough(\"subdir/img\", \"_site\", \"test/stubs\");\n  t.is(pass.outputPath, true);\n  t.is(await pass.getOutputPath(), \"_site/subdir/img\");\n\n  let pass2 = await getTemplatePassthrough(\n    { inputPath: \"subdir/img\", outputPath: \"subdir/img\" },\n    \"_site\",\n    \"test/stubs\"\n  );\n  t.is(await pass2.getOutputPath(), \"_site/subdir/img\");\n});\n\ntest(\"Origin path isn’t included in output when targeting a file\", async (t) => {\n  let pass = await getTemplatePassthrough(\"avatar.png\", \"_site\", \"test/stubs\");\n  t.is(pass.outputPath, true);\n  t.is(await pass.getOutputPath(), \"_site/avatar.png\");\n});\n\ntest(\"Origin path isn’t included in output when targeting a file several levels deep\", async (t) => {\n  let pass = await getTemplatePassthrough(\"avatar.png\", \"_site\", \"test/stubs/subdir/img\");\n  t.is(pass.outputPath, true);\n  t.is(await pass.getOutputPath(), \"_site/avatar.png\");\n});\n\ntest(\"Full input file path and deep input path\", async (t) => {\n  let tp = await getTemplatePassthrough(\"test/views/avatar.png\", \"_site\", \"test/views/\");\n  t.is(await tp.getOutputPath(), \"_site/avatar.png\");\n\n  let tp2 = await getTemplatePassthrough(\"test/views/avatar.png\", \"_site\", \"test/views\");\n  t.is(await tp2.getOutputPath(), \"_site/avatar.png\");\n\n  let tp3 = await getTemplatePassthrough(\"test/views/avatar.png\", \"_site/\", \"test/views\");\n  t.is(await tp3.getOutputPath(), \"_site/avatar.png\");\n\n  let tp4 = await getTemplatePassthrough(\"test/views/avatar.png\", \"./_site\", \"./test/views\");\n  t.is(await tp4.getOutputPath(), \"_site/avatar.png\");\n\n  let tp5 = await getTemplatePassthrough(\"./test/views/avatar.png\", \"./_site/\", \"./test/views/\");\n  t.is(await tp5.getOutputPath(), \"_site/avatar.png\");\n\n  let tp6 = await getTemplatePassthrough(\"./test/views/avatar.png\", \"_site\", \"test/views/\");\n  t.is(await tp6.getOutputPath(), \"_site/avatar.png\");\n});\n\ntest(\".htaccess\", async (t) => {\n  let pass = await getTemplatePassthrough(\".htaccess\", \"_site\", \".\");\n  t.is(pass.outputPath, true);\n  t.is(await pass.getOutputPath(), \"_site/.htaccess\");\n});\n\ntest(\".htaccess with input dir\", async (t) => {\n  let pass = await getTemplatePassthrough(\".htaccess\", \"_site\", \"test/stubs\");\n  t.is(pass.outputPath, true);\n  t.is(await pass.getOutputPath(), \"_site/.htaccess\");\n});\n\ntest(\"getFiles where not glob and file does not exist\", async (t) => {\n  const inputPath = \".htaccess\";\n  let pass = await getTemplatePassthrough(inputPath, \"_site\", \"test/stubs\");\n  t.is(pass.outputPath, true);\n  const files = await pass.getFiles(inputPath);\n  t.deepEqual(files, []);\n});\n\ntest(\"getFiles where not glob and directory does not exist\", async (t) => {\n  const inputPath = \"./test/stubs/template-passthrough/static/not-exists/\";\n  let pass = await getTemplatePassthrough(inputPath, \"_site\", \"test/stubs\");\n  t.is(pass.outputPath, true);\n  const files = await pass.getFiles(inputPath);\n  t.deepEqual(files, []);\n});\n\ntest(\"getFiles with glob\", async (t) => {\n  const inputPath = \"./test/stubs/template-passthrough/static/**\";\n  let pass = await getTemplatePassthrough(inputPath, \"_site\", \"test/views\");\n  t.is(pass.outputPath, true);\n\n  const files = await pass.getFiles(inputPath);\n  t.deepEqual(\n    files.sort(),\n    [\n      \"./test/stubs/template-passthrough/static/test.css\",\n      \"./test/stubs/template-passthrough/static/test.js\",\n      \"./test/stubs/template-passthrough/static/nested/test-nested.css\",\n    ].sort()\n  );\n\n  t.is(\n    await pass.getOutputPath(files.filter((entry) => entry.endsWith(\"test.css\"))[0]),\n    \"_site/test/stubs/template-passthrough/static/test.css\"\n  );\n  t.is(\n    await pass.getOutputPath(files.filter((entry) => entry.endsWith(\"test.js\"))[0]),\n    \"_site/test/stubs/template-passthrough/static/test.js\"\n  );\n  t.is(\n    await pass.getOutputPath(files.filter((entry) => entry.endsWith(\"test-nested.css\"))[0]),\n    \"_site/test/stubs/template-passthrough/static/nested/test-nested.css\"\n  );\n});\ntest(\"getFiles with glob 2\", async (t) => {\n  const inputPath = \"./test/stubs/template-passthrough/static/**/*.js\";\n  let pass = await getTemplatePassthrough(inputPath, \"_site\", \"test/views\");\n  t.is(pass.outputPath, true);\n  const files = await pass.getFiles(inputPath);\n  t.deepEqual(files, [\"./test/stubs/template-passthrough/static/test.js\"]);\n  t.is(await pass.getOutputPath(files[0]), \"_site/test/stubs/template-passthrough/static/test.js\");\n});\n\ntest(\"Directory where outputPath is true\", async (t) => {\n  let pass = await getTemplatePassthrough(\n    { inputPath: \"./static\", outputPath: true },\n    \"_site\",\n    \"test/stubs\"\n  );\n  t.is(pass.outputPath, true);\n  t.is(await pass.getOutputPath(), \"_site/static\");\n});\n\ntest(\"Nested directory where outputPath is remapped\", async (t) => {\n  let pass = await getTemplatePassthrough(\n    { inputPath: \"./static/nested\", outputPath: \"./test\" },\n    \"_site\",\n    \"test/stubs\"\n  );\n  t.is(pass.outputPath, \"./test\");\n  t.is(await pass.getOutputPath(), \"_site/test\");\n});\n\ntest(\"Glob pattern\", async (t) => {\n  const globResolvedPath = \"./test/stubs/template-passthrough/static/test.js\";\n  let pass = await getTemplatePassthrough(\n    {\n      inputPath: \"./test/stubs/template-passthrough/static/*.js\",\n      outputPath: \"./directory/\",\n    },\n    \"_site\",\n    \"test/stubs\"\n  );\n  t.is(pass.outputPath, \"./directory/\");\n  t.is(await pass.getOutputPath(globResolvedPath), \"_site/directory/test.js\");\n});\n\ntest(\"Output paths match with different templatePassthrough methods\", async (t) => {\n  let pass1 = await getTemplatePassthrough(\n    { inputPath: \"./static/nested\", outputPath: \"./test\" },\n    \"_site\",\n    \"test/stubs\"\n  );\n\n  let pass2 = await getTemplatePassthrough(\"avatar.png\", \"_site/test\", \".\");\n  t.is(await pass1.getOutputPathForGlobFile(\"avatar.png\"), await pass2.getOutputPath());\n});\n\n// ToDo: Currently can't do :(\n// test(\"File renamed\", async t => {\n//   let pass = await getTemplatePassthrough(\n//     {\n//       inputPath: \"./test/stubs/template-passthrough/static/test.js\",\n//       outputPath: \"./rename.js\"\n//     },\n//     \"_site\",\n//     \"test/stubs\"\n//   );\n//   t.truthy(pass);\n//   t.is(await pass.getOutputPath(), \"_site/rename.js\");\n// });\n\ntest(\"Bug with incremental file copying to a directory output, issue #2278 #1038\", async (t) => {\n  let pass1 = await getTemplatePassthrough(\n    { inputPath: \"./test/stubs/public/test.css\", outputPath: \"/\" },\n    \"test/stubs\",\n    \".\"\n  );\n\n  t.is(await pass1.getOutputPath(), \"test/stubs/test.css\");\n});\n\ntest(\"Bug with incremental dir copying to a directory output, issue #2278 #1038\", async (t) => {\n  let pass1 = await getTemplatePassthrough(\n    { inputPath: \"./test/stubs/public/\", outputPath: \"/\" },\n    \"test/stubs\",\n    \".\"\n  );\n\n  t.is(await pass1.getOutputPath(), \"./test/stubs/\");\n});\n"
  },
  {
    "path": "test/TemplatePermalinkTest.js",
    "content": "import test from \"ava\";\nimport TemplatePermalink from \"../src/TemplatePermalink.js\";\n\nconst { generate } = TemplatePermalink;\n\ntest(\"Simple straight permalink\", (t) => {\n  t.is(\n    new TemplatePermalink(\"permalinksubfolder/test.html\").toOutputPath(),\n    \"permalinksubfolder/test.html\"\n  );\n  t.is(\n    new TemplatePermalink(\"./permalinksubfolder/test.html\").toOutputPath(),\n    \"permalinksubfolder/test.html\"\n  );\n\n  t.is(\n    new TemplatePermalink(\"permalinksubfolder/test.html\").toHref(),\n    \"/permalinksubfolder/test.html\"\n  );\n  t.is(\n    new TemplatePermalink(\"./permalinksubfolder/test.html\").toHref(),\n    \"/permalinksubfolder/test.html\"\n  );\n  t.is(new TemplatePermalink(\"./testindex.html\").toHref(), \"/testindex.html\");\n  t.is(\n    new TemplatePermalink(\"./permalinksubfolder/testindex.html\").toHref(),\n    \"/permalinksubfolder/testindex.html\"\n  );\n});\n\ntest(\"Permalink without filename\", (t) => {\n  t.is(\n    new TemplatePermalink(\"permalinksubfolder/\").toOutputPath(),\n    \"permalinksubfolder/index.html\"\n  );\n  t.is(\n    new TemplatePermalink(\"./permalinksubfolder/\").toOutputPath(),\n    \"permalinksubfolder/index.html\"\n  );\n  t.is(\n    new TemplatePermalink(\"/permalinksubfolder/\").toOutputPath(),\n    \"/permalinksubfolder/index.html\"\n  );\n\n  t.is(new TemplatePermalink(\"permalinksubfolder/\").toHref(), \"/permalinksubfolder/\");\n  t.is(new TemplatePermalink(\"./permalinksubfolder/\").toHref(), \"/permalinksubfolder/\");\n  t.is(new TemplatePermalink(\"/permalinksubfolder/\").toHref(), \"/permalinksubfolder/\");\n});\n\ntest(\"Permalink with pagination subdir\", (t) => {\n  t.is(\n    new TemplatePermalink(\"permalinksubfolder/test.html\", \"0/\").toOutputPath(),\n    \"permalinksubfolder/0/test.html\"\n  );\n  t.is(\n    new TemplatePermalink(\"permalinksubfolder/test.html\", \"1/\").toOutputPath(),\n    \"permalinksubfolder/1/test.html\"\n  );\n\n  t.is(\n    new TemplatePermalink(\"permalinksubfolder/test.html\", \"0/\").toHref(),\n    \"/permalinksubfolder/0/test.html\"\n  );\n  t.is(\n    new TemplatePermalink(\"permalinksubfolder/test.html\", \"1/\").toHref(),\n    \"/permalinksubfolder/1/test.html\"\n  );\n});\n\ntest(\"Permalink generate\", (t) => {\n  t.is(generate(\"./\", \"index\").toOutputPath(), \"index.html\");\n  t.is(generate(\"./\", \"index\").toHref(), \"/\");\n  t.is(generate(\".\", \"index\").toOutputPath(), \"index.html\");\n  t.is(generate(\".\", \"index\").toHref(), \"/\");\n  t.is(generate(\".\", \"test\").toOutputPath(), \"test/index.html\");\n  t.is(generate(\".\", \"test\").toHref(), \"/test/\");\n  t.is(generate(\".\", \"test\", \"0/\").toOutputPath(), \"test/0/index.html\");\n  t.is(generate(\".\", \"test\", \"0/\").toHref(), \"/test/0/\");\n  t.is(generate(\".\", \"test\", \"1/\").toOutputPath(), \"test/1/index.html\");\n  t.is(generate(\".\", \"test\", \"1/\").toHref(), \"/test/1/\");\n});\n\ntest(\"Permalink generate with suffix\", (t) => {\n  t.is(generate(\".\", \"test\", null).toOutputPath(), \"test/index.html\");\n  t.is(generate(\".\", \"test\", null).toHref(), \"/test/\");\n  t.is(generate(\".\", \"test\", \"1/\").toOutputPath(), \"test/1/index.html\");\n  t.is(generate(\".\", \"test\", \"1/\").toHref(), \"/test/1/\");\n});\n\ntest(\"Permalink generate with new extension\", (t) => {\n  t.is(generate(\".\", \"test\", null, \"css\").toOutputPath(), \"test.css\");\n  t.is(generate(\".\", \"test\", null, \"css\").toHref(), \"/test.css\");\n  t.is(generate(\".\", \"test\", \"1/\", \"css\").toOutputPath(), \"1/test.css\");\n  t.is(generate(\".\", \"test\", \"1/\", \"css\").toHref(), \"/1/test.css\");\n});\n\ntest(\"Permalink generate with subfolders\", (t) => {\n  t.is(generate(\"permalinksubfolder/\", \"index\").toOutputPath(), \"permalinksubfolder/index.html\");\n  t.is(\n    generate(\"permalinksubfolder/\", \"test\").toOutputPath(),\n    \"permalinksubfolder/test/index.html\"\n  );\n  t.is(\n    generate(\"permalinksubfolder/\", \"test\", \"1/\").toOutputPath(),\n    \"permalinksubfolder/test/1/index.html\"\n  );\n\n  t.is(generate(\"permalinksubfolder/\", \"index\").toHref(), \"/permalinksubfolder/\");\n  t.is(generate(\"permalinksubfolder/\", \"test\").toHref(), \"/permalinksubfolder/test/\");\n  t.is(\n    generate(\"permalinksubfolder/\", \"test\", \"1/\").toHref(),\n    \"/permalinksubfolder/test/1/\"\n  );\n});\n\ntest(\"Permalink matching folder and filename\", (t) => {\n  let hasDupe = TemplatePermalink._hasDuplicateFolder;\n  t.is(hasDupe(\"subfolder\", \"component\"), false);\n  t.is(hasDupe(\"subfolder/\", \"component\"), false);\n  t.is(hasDupe(\".\", \"component\"), false);\n\n  t.is(hasDupe(\"component\", \"component\"), true);\n  t.is(hasDupe(\"component/\", \"component\"), true);\n\n  t.is(generate(\"component/\", \"component\").toOutputPath(), \"component/index.html\");\n  t.is(generate(\"component/\", \"component\").toHref(), \"/component/\");\n});\n\ntest(\"Permalink Object, just build\", (t) => {\n  t.is(\n    new TemplatePermalink({\n      build: \"permalinksubfolder/test.html\",\n    }).toOutputPath(),\n    \"permalinksubfolder/test.html\"\n  );\n\n  t.is(\n    new TemplatePermalink({\n      build: false,\n    }).toOutputPath(),\n    false\n  );\n\n  t.throws(() => {\n    new TemplatePermalink({\n      build: true,\n    }).toOutputPath();\n  });\n});\n\ntest(\"Permalink Object, empty object\", (t) => {\n  t.is(new TemplatePermalink({}).toOutputPath(), false);\n\n  t.is(new TemplatePermalink({}).toHref(), false);\n});\n\ntest(\"Permalink generate apache content negotiation #761\", (t) => {\n  let tp = new TemplatePermalink(\"index.es.html\");\n  tp.setUrlTransforms([\n    function ({ url }) {\n      return \"/\";\n    },\n  ]);\n\n  t.is(tp.toHref(), \"/\");\n  t.is(tp.toOutputPath(), \"index.es.html\");\n\n  // Note that generate does some preprocessing to the raw permalink value (compared to `new TemplatePermalink`)\n  let tp1 = TemplatePermalink.generate(\"\", \"index.es\");\n  tp1.setUrlTransforms([\n    function ({ url }) {\n      return \"/\";\n    },\n  ]);\n  t.is(tp1.toHref(), \"/\");\n  // best paired with https://v3.11ty.dev/docs/data-eleventy-supplied/#filepathstem for index.es.html\n  t.is(tp1.toOutputPath(), \"index.es/index.html\");\n});\n\ntest(\"Permalink generate apache content negotiation with subdirectory #761\", (t) => {\n  let tp = new TemplatePermalink(\"test/index.es.html\");\n  tp.setUrlTransforms([\n    function ({ url }) {\n      return \"/test/\";\n    },\n  ]);\n\n  t.is(tp.toHref(), \"/test/\");\n  t.is(tp.toOutputPath(), \"test/index.es.html\");\n\n  // Note that generate does some preprocessing to the raw permalink value (compared to `new TemplatePermalink`)\n  let tp1 = TemplatePermalink.generate(\"test\", \"index.es\");\n  tp1.setUrlTransforms([\n    function ({ url }) {\n      return \"/test/\";\n    },\n  ]);\n\n  t.is(tp1.toHref(), \"/test/\");\n  // best paired with https://v3.11ty.dev/docs/data-eleventy-supplied/#filepathstem for test/index.es.html\n  t.is(tp1.toOutputPath(), \"test/index.es/index.html\");\n});\n\ntest(\"Permalink generate apache content negotiation non-index file name #761\", (t) => {\n  // Note that generate does some preprocessing to the raw permalink value (compared to `new TemplatePermalink`)\n  let tp = TemplatePermalink.generate(\"permalinksubfolder\", \"about.es\");\n\n  t.is(tp.toHref(), \"/permalinksubfolder/about.es/\");\n  t.is(tp.toOutputPath(), \"permalinksubfolder/about.es/index.html\");\n});\n\ntest(\"Permalink generate with urlTransforms #761\", (t) => {\n  // Note that TemplatePermalink.generate is used by Template and different from new TemplatePermalink\n  let tp = TemplatePermalink.generate(\"permalinksubfolder\", \"index.es\");\n\n  tp.setUrlTransforms([\n    function ({ url }) {\n      return \"/permalinksubfolder/\";\n    },\n  ]);\n\n  t.is(tp.toHref(), \"/permalinksubfolder/\");\n  // best paired with https://v3.11ty.dev/docs/data-eleventy-supplied/#filepathstem for permalinksubfolder/index.es.html\n  t.is(tp.toOutputPath(), \"permalinksubfolder/index.es/index.html\");\n});\n\ntest(\"Permalink generate with urlTransforms (skip via undefined) #761\", (t) => {\n  // Note that TemplatePermalink.generate is used by Template and different from new TemplatePermalink\n  let tp = TemplatePermalink.generate(\"permalinksubfolder\", \"index.es\");\n\n  tp.setUrlTransforms([\n    function ({ url }) {\n      // return nothing\n    },\n  ]);\n\n  t.is(tp.toHref(), \"/permalinksubfolder/index.es/\");\n  // best paired with https://v3.11ty.dev/docs/data-eleventy-supplied/#filepathstem for permalinksubfolder/index.es.html\n  t.is(tp.toOutputPath(), \"permalinksubfolder/index.es/index.html\");\n});\n\ntest(\"Permalink generate with 2 urlTransforms #761\", (t) => {\n  // Note that TemplatePermalink.generate is used by Template and different from new TemplatePermalink\n  let tp = TemplatePermalink.generate(\"permalinksubfolder\", \"index.es\");\n  tp.setUrlTransforms([\n    function ({ url }) {\n      return \"/abc/\";\n    },\n    function ({ url }) {\n      return \"/def/\";\n    },\n  ]);\n\n  t.is(tp.toHref(), \"/def/\");\n  // best paired with https://v3.11ty.dev/docs/data-eleventy-supplied/#filepathstem for permalinksubfolder/index.es.html\n  t.is(tp.toOutputPath(), \"permalinksubfolder/index.es/index.html\");\n});\n\ntest(\"Permalink generate with urlTransforms returns index.html #761\", (t) => {\n  // Note that TemplatePermalink.generate is used by Template and different from new TemplatePermalink\n  let tp = TemplatePermalink.generate(\"permalinksubfolder\", \"index.es\");\n  tp.setUrlTransforms([\n    function ({ url }) {\n      return \"/abc/index.html\";\n    },\n  ]);\n\n  t.is(tp.toHref(), \"/abc/\");\n  // best paired with https://v3.11ty.dev/docs/data-eleventy-supplied/#filepathstem for permalinksubfolder/index.es.html\n  t.is(tp.toOutputPath(), \"permalinksubfolder/index.es/index.html\");\n});\n\ntest(\"Permalink generate with urlTransforms code (index file) #761\", (t) => {\n  let tp1 = new TemplatePermalink(\"index.es.html\");\n\n  tp1.setUrlTransforms([\n    function ({ url, urlStem }) {\n      t.is(url, \"/index.es.html\");\n      t.is(urlStem, \"/index.es\");\n\n      if (url.match(/\\.[a-z]{2}\\.html$/i)) {\n        // trailing slash\n        return url.slice(0, -1 * \".en.html\".length) + \"/\";\n      }\n    },\n  ]);\n\n  t.is(tp1.toHref(), \"/\");\n\n  let tp2 = new TemplatePermalink(\"index.es.html\");\n\n  tp2.setUrlTransforms([\n    function ({ url, urlStem }) {\n      t.is(url, \"/index.es.html\");\n      t.is(urlStem, \"/index.es\");\n\n      if (url.match(/\\.[a-z]{2}\\.html$/i)) {\n        // no trailing slash\n        return url.slice(0, -1 * \".en.html\".length);\n      }\n    },\n  ]);\n\n  t.is(tp2.toHref(), \"/\");\n});\n\ntest(\"Permalink generate with urlTransforms code (not index file) #761\", (t) => {\n  let tp1 = new TemplatePermalink(\"about.es.html\");\n\n  tp1.setUrlTransforms([\n    function ({ url, urlStem }) {\n      t.is(url, \"/about.es.html\");\n      t.is(urlStem, \"/about.es\");\n\n      if (url.match(/\\.[a-z]{2}\\.html$/i)) {\n        // trailing slash\n        return url.slice(0, -1 * \".en.html\".length) + \"/\";\n      }\n    },\n  ]);\n\n  t.is(tp1.toHref(), \"/about/\");\n\n  let tp2 = new TemplatePermalink(\"about.es.html\");\n\n  tp2.setUrlTransforms([\n    function ({ url }) {\n      if (url.match(/\\.[a-z]{2}\\.html$/i)) {\n        // no trailing slash\n        return url.slice(0, -1 * \".en.html\".length);\n      }\n    },\n  ]);\n\n  t.is(tp2.toHref(), \"/about\");\n});\n\ntest(\"Permalink generate with urlTransforms code (index file with subdir) #761\", (t) => {\n  let tp1 = new TemplatePermalink(\"subdir/index.es.html\");\n\n  tp1.setUrlTransforms([\n    function ({ url, urlStem }) {\n      t.is(url, \"/subdir/index.es.html\");\n      t.is(urlStem, \"/subdir/index.es\");\n\n      if (url.match(/\\.[a-z]{2}\\.html$/i)) {\n        // trailing slash\n        return url.slice(0, -1 * \".en.html\".length) + \"/\";\n      }\n    },\n  ]);\n\n  t.is(tp1.toHref(), \"/subdir/\");\n\n  let tp2 = new TemplatePermalink(\"subdir/index.es.html\");\n\n  tp2.setUrlTransforms([\n    function ({ url, urlStem }) {\n      t.is(url, \"/subdir/index.es.html\");\n      t.is(urlStem, \"/subdir/index.es\");\n\n      if (url.match(/\\.[a-z]{2}\\.html$/i)) {\n        // no trailing slash\n        return url.slice(0, -1 * \".en.html\".length);\n      }\n    },\n  ]);\n\n  t.is(tp2.toHref(), \"/subdir/\");\n});\n\ntest(\"Permalink generate with urlTransforms code (not-index file with subdir) #761\", (t) => {\n  let tp1 = new TemplatePermalink(\"subdir/about.es.html\");\n\n  tp1.setUrlTransforms([\n    function ({ url, urlStem }) {\n      if (url.match(/\\.[a-z]{2}\\.html$/i)) {\n        t.is(url, \"/subdir/about.es.html\");\n        t.is(urlStem, \"/subdir/about.es\");\n\n        // trailing slash\n        return url.slice(0, -1 * \".en.html\".length) + \"/\";\n      }\n    },\n  ]);\n\n  t.is(tp1.toHref(), \"/subdir/about/\");\n\n  let tp2 = new TemplatePermalink(\"subdir/about.es.html\");\n\n  tp2.setUrlTransforms([\n    function ({ url, urlStem }) {\n      t.is(url, \"/subdir/about.es.html\");\n      t.is(urlStem, \"/subdir/about.es\");\n\n      if (url.match(/\\.[a-z]{2}\\.html$/i)) {\n        // no trailing slash\n        return url.slice(0, -1 * \".en.html\".length);\n      }\n    },\n  ]);\n\n  t.is(tp2.toHref(), \"/subdir/about\");\n});\n"
  },
  {
    "path": "test/TemplateRenderCustomTest.js",
    "content": "import test from \"ava\";\nimport { createSSRApp } from \"vue\";\nimport { renderToString } from \"@vue/server-renderer\";\nimport * as sass from \"sass\";\n\nimport TemplateRender from \"../src/TemplateRender.js\";\nimport EleventyExtensionMap from \"../src/EleventyExtensionMap.js\";\n\nimport getNewTemplate from \"./_getNewTemplateForTests.js\";\nimport { renderTemplate } from \"./_getRenderedTemplates.js\";\nimport { getTemplateConfigInstance, getTemplateConfigInstanceCustomCallback } from \"./_testHelpers.js\";\nimport TemplateEngineManager from \"../src/Engines/TemplateEngineManager.js\";\n\n\nasync function getNewTemplateRender(name, inputDir, eleventyConfig, extensionMap) {\n  if (!eleventyConfig) {\n    eleventyConfig = await getTemplateConfigInstance();\n  }\n\n\n  if (!extensionMap) {\n    extensionMap = new EleventyExtensionMap(eleventyConfig);\n    extensionMap.setFormats([]);\n  }\n\n  let mgr = new TemplateEngineManager(eleventyConfig);\n  extensionMap.engineManager = mgr;\n\n  let tr = new TemplateRender(name, eleventyConfig);\n  tr.extensionMap = extensionMap;\n  await tr.init();\n\n  return tr;\n}\n\ntest(\"Custom plaintext Render\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback(\n    {},\n    function(cfg) {\n      // addExtension() API\n      cfg.addExtension(\"txt\", {\n        compile: function (str, inputPath) {\n          // plaintext\n          return function (data) {\n            return str;\n          };\n        },\n      });\n    }\n  );\n\n  let tr = await getNewTemplateRender(\"txt\", null, eleventyConfig);\n  let fn = await tr.getCompiledTemplate(\"<p>Paragraph</p>\");\n  t.is(await fn(), \"<p>Paragraph</p>\");\n  t.is(await fn({}), \"<p>Paragraph</p>\");\n});\n\ntest(\"Custom Markdown Render with `compile` override\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback(\n    {},\n    function(cfg) {\n      // addExtension() API\n      cfg.addExtension(\"md\", {\n        compile: function (str, inputPath) {\n          return function (data) {\n            return `<not-markdown>${str.trim()}</not-markdown>`;\n          };\n        },\n      });\n    }\n  );\n\n  let tr = await getNewTemplateRender(\"md\", null, eleventyConfig);\n\n  let fn = await tr.getCompiledTemplate(\"# Markdown?\");\n  t.is((await fn()).trim(), \"<not-markdown># Markdown?</not-markdown>\");\n  t.is((await fn({})).trim(), \"<not-markdown># Markdown?</not-markdown>\");\n});\n\ntest(\"Custom Markdown Render without `compile` override\", async (t) => {\n  let initCalled = false;\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback(\n    {},\n    function(cfg) {\n      // addExtension() API\n      cfg.addExtension(\"md\", {\n        init: function () {\n          initCalled = true;\n        },\n      });\n    }\n  );\n\n  let tr = await getNewTemplateRender(\"md\", null, eleventyConfig);\n\n  let fn = await tr.getCompiledTemplate(\"# Header\");\n  t.is(initCalled, true);\n  t.is((await fn()).trim(), \"<h1>Header</h1>\");\n  t.is((await fn({})).trim(), \"<h1>Header</h1>\");\n});\n\ntest(\"Custom Markdown Render with `compile` override + call to default compiler\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback(\n    {},\n    function(cfg) {\n      // addExtension() API\n      cfg.addExtension(\"md\", {\n        compile: function (str, inputPath) {\n          return async function (data) {\n            const result = await this.defaultRenderer(data);\n            return `<custom-wrapper>${result.trim()}</custom-wrapper>`;\n          };\n        },\n      });\n    }\n  );\n\n  let tr = await getNewTemplateRender(\"md\", null, eleventyConfig);\n\n  let fn = await tr.getCompiledTemplate(\"Hey {{name}}\");\n  t.is((await fn()).trim(), \"<custom-wrapper><p>Hey</p></custom-wrapper>\");\n  t.is((await fn({ name: \"Zach\" })).trim(), \"<custom-wrapper><p>Hey Zach</p></custom-wrapper>\");\n});\n\ntest(\"Custom Vue Render\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback(\n    {},\n    function(cfg) {\n      // addExtension() API\n      cfg.addExtension(\"vue\", {\n        compile: function (str) {\n          return async function (data) {\n            const app = createSSRApp({\n              template: str,\n              data: function () {\n                return data;\n              },\n            });\n\n            return renderToString(app);\n          };\n        },\n      });\n    }\n  );\n\n  let tr = await getNewTemplateRender(\"vue\", null, eleventyConfig);\n  let fn = await tr.getCompiledTemplate('<p v-html=\"test\">Paragraph</p>');\n  t.is(await fn({ test: \"Hello\" }), \"<p>Hello</p>\");\n});\n\ntest(\"Custom Sass Render\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback(\n    {},\n    function(cfg) {\n      // addExtension() API\n      cfg.addExtension(\"sass\", {\n        compile: function (str, inputPath) {\n          // TODO declare data variables as SASS variables?\n          return async function (data) {\n            return new Promise(function (resolve, reject) {\n              sass.render(\n                {\n                  data: str,\n                  includePaths: [tr.inputDir, tr.includesDir],\n                  style: \"expanded\",\n                  indentType: \"space\",\n                  // TODO\n                  // sourcemap: \"file\",\n                  outFile: \"test_this_is_to_not_write_a_file.css\",\n                },\n                function (error, result) {\n                  if (error) {\n                    reject(error);\n                  } else {\n                    resolve(result.css.toString(\"utf8\"));\n                  }\n                },\n              );\n            });\n          };\n        },\n      });\n    }\n  );\n\n  let tr = await getNewTemplateRender(\"sass\", null, eleventyConfig);\n  let fn = await tr.getCompiledTemplate(\"$color: blue; p { color: $color; }\");\n  t.is(\n    (await fn({})).trim(),\n    `p {\n  color: blue;\n}`,\n  );\n});\n\n/*\nserverPrefetch: function() {\n    return this.getBlogAuthors().then(response => this.glossary = response)\n  },\n*/\ntest(\"JavaScript functions should not be mutable but not *that* mutable\", async (t) => {\n  t.plan(3);\n\n  let instance = {\n    dataForCascade: function () {\n      // was mutating this.config.javascriptFunctions!\n      this.shouldnotmutatethething = 1;\n      return {};\n    },\n  };\n\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback(\n    {},\n    function(cfg) {\n      // addExtension() API\n      cfg.addExtension(\"js1\", {\n        getData: [\"dataForCascade\"],\n        getInstanceFromInputPath: function (inputPath) {\n          t.truthy(true);\n          return instance;\n        },\n        compile: function (str, inputPath) {\n          t.falsy(this.config.javascriptFunctions.shouldnotmutatethething);\n\n          // plaintext\n          return (data) => {\n            return str;\n          };\n        },\n      });\n    }\n  );\n\n  let tmpl = await getNewTemplate(\n    \"./test/stubs-custom-extension/test.js1\",\n    \"./test/stubs-custom-extension/\",\n    \"dist\",\n    null,\n    null,\n    eleventyConfig,\n  );\n  let data = await tmpl.getData();\n  t.is(await renderTemplate(tmpl, data), \"<p>Paragraph</p>\");\n});\n\ntest(\"Return undefined in compile to ignore #2267\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback(\n    {},\n    function(cfg) {\n      // addExtension() API\n      cfg.addExtension(\"txt\", {\n        compile: function (str, inputPath) {\n          return;\n        },\n      });\n    }\n  );\n\n  let tr = await getNewTemplateRender(\"txt\", null, eleventyConfig);\n  let fn = await tr.getCompiledTemplate(\"<p>Paragraph</p>\");\n  t.is(fn, undefined);\n});\n\ntest(\"Simple alias to Markdown Render\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback(\n    {},\n    function(cfg) {\n      cfg.addExtension(\"mdx\", {\n        key: \"md\",\n      });\n    }\n  );\n\n  let tr = await getNewTemplateRender(\"mdx\", null, eleventyConfig);\n\n  let fn = await tr.getCompiledTemplate(\"# Header\");\n  t.is((await fn()).trim(), \"<h1>Header</h1>\");\n  t.is((await fn({})).trim(), \"<h1>Header</h1>\");\n});\n\n// NOTE: Breaking change in 3.0 `import` does not allow aliasing to non-.js file names\ntest.skip(\"Breaking Change (3.0): Simple alias to JavaScript Render\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback(\n    {},\n    function(cfg) {\n      cfg.addExtension(\"11ty.custom\", {\n        key: \"11ty.js\",\n      });\n    }\n  );\n\n  let tr = await getNewTemplateRender(\"./test/stubs/string.11ty.custom\", null, eleventyConfig);\n  let fn = await tr.getCompiledTemplate();\n  t.is(await fn({ name: \"Bill\" }), \"<p>Zach</p>\");\n});\n\n// NOTE: Breaking change in 3.0 `import` does not allow aliasing to non-.js file names\ntest.skip(\"Breaking Change (3.0): Override to JavaScript Render\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback(\n    {},\n    function(cfg) {\n      cfg.addExtension(\"11ty.custom\", {\n        key: \"11ty.js\",\n        init: function () {},\n      });\n    }\n  );\n\n  let tr = await getNewTemplateRender(\"./test/stubs/string.11ty.custom\", null, eleventyConfig);\n  let fn = await tr.getCompiledTemplate();\n  t.is(await fn({ name: \"Bill\" }), \"<p>Zach</p>\");\n});\n\n// NOTE: Breaking change in 3.0 `import` does not allow aliasing to non-.js file names\ntest.skip(\"Breaking Change (3.0): Two simple aliases to JavaScript Render\", async (t) => {\n  t.plan(2);\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback(\n    {},\n    function(cfg) {\n      cfg.addExtension([\"11ty.custom\", \"11ty.possum\"], {\n        key: \"11ty.js\", // esm\n      });\n    }\n  );\n\n  let map = new EleventyExtensionMap(eleventyConfig); // reuse this\n  map.setFormats([]);\n\n  let tr = await getNewTemplateRender(\"./test/stubs/string.11ty.custom\", null, eleventyConfig, map);\n  let fn = await tr.getCompiledTemplate();\n  t.is(await fn({}), \"<p>Zach</p>\");\n\n  let tr2 = await getNewTemplateRender(\n    \"./test/stubs/string.11ty.possum\",\n    null,\n    eleventyConfig,\n    map,\n  );\n  let fn2 = await tr2.getCompiledTemplate();\n  t.is(await fn2({}), \"<p>Possum</p>\");\n});\n\ntest(\"Double override (one simple alias to custom) works fine\", async (t) => {\n  t.plan(3);\n\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback(\n    {},\n    function(cfg) {\n      cfg.addExtension([\"11ty.possum\"], {\n        init: function () {\n          t.true(true);\n        },\n        compile: function(content, inputPath) {\n          t.true(true);\n          return (data) => \"Appended \" + content;\n        }\n      });\n\n      cfg.addExtension([\"customhtml\"], {\n        key: \"11ty.possum\",\n      });\n    }\n  );\n\n  let map = new EleventyExtensionMap(eleventyConfig); // reuse this\n  // map.setFormats([\"11ty.possum\", \"11ty.custom\"]);\n  map.setFormats([\"customhtml\"]);\n\n  let tr = await getNewTemplateRender(\n    \"customhtml\",\n    null,\n    eleventyConfig,\n    map,\n  );\n\n  let fn = await tr.getCompiledTemplate(\"Template content\");\n  t.is(await fn({}), \"Appended Template content\");\n});\n\n\ntest(\"Double override (two simple aliases)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback(\n    {},\n    function(cfg) {\n      cfg.addExtension([\"11ty.possum\"], {\n        key: \"html\",\n      });\n\n      cfg.addExtension([\"customhtml\"], {\n        key: \"11ty.possum\",\n      });\n    }\n  );\n\n  let map = new EleventyExtensionMap(eleventyConfig); // reuse this\n  // map.setFormats([\"11ty.possum\", \"11ty.custom\"]);\n  map.setFormats([\"customhtml\"]);\n\n  let tr = await getNewTemplateRender(\n    \"customhtml\",\n    null,\n    eleventyConfig,\n    map,\n  );\n\n  let fn = await tr.getCompiledTemplate(\"Template content\");\n  t.is(await fn({}), \"Template content\");\n});\n\ntest(\"Double override (two complex aliases) is supported as of 3.0\", async (t) => {\n  t.plan(5);\n\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback(\n    {},\n    function(cfg) {\n      cfg.addExtension([\"possum\"], {\n        key: \"html\",\n        init: function () {\n          t.true(true);\n        },\n        compile: function() {\n          t.true(true);\n          return async function(data) {\n            const result = await this.defaultRenderer(data);\n            return `possum|${result}`;\n          };\n        }\n      });\n\n      cfg.addExtension([\"11ty.custom\"], {\n        key: \"possum\",\n        init: function () {\n          t.true(true);\n        },\n        compile: function() {\n          t.true(true);\n          return async function(data) {\n            const result = await this.defaultRenderer(data);\n            return `11ty.custom|${result}`;\n          };\n        }\n      });\n    }\n  );\n\n  let map = new EleventyExtensionMap(eleventyConfig); // reuse this\n  map.setFormats([\"possum\", \"11ty.custom\"]);\n\n  let tr = await getNewTemplateRender(\n    \"11ty.custom\",\n    null,\n    eleventyConfig,\n    map,\n  );\n\n  let fn = await tr.getCompiledTemplate(\"Template content\");\n  t.is(await fn({}), \"11ty.custom|possum|Template content\");\n});\n\ntest(\"Double override (not aliases) throws an error\", async (t) => {\n  await t.throwsAsync(\n    async () => {\n      await getTemplateConfigInstanceCustomCallback(\n        {},\n        function(cfg) {\n          cfg.addExtension([\"md\"], {\n            compile: function (inputContent, inputPath) {\n              return () => inputContent;\n            },\n          });\n\n          cfg.addExtension([\"md\"], {\n            compile: function (inputContent, inputPath) {\n              return () => inputContent;\n            },\n          });\n        }\n      );\n    },\n    {\n      message:\n        'An attempt was made to override the \"md\" template syntax twice (via the `addExtension` configuration API). A maximum of one override is currently supported.',\n    },\n  );\n});\n"
  },
  {
    "path": "test/TemplateRenderHTMLTest.js",
    "content": "import test from \"ava\";\nimport TemplateRender from \"../src/TemplateRender.js\";\nimport EleventyExtensionMap from \"../src/EleventyExtensionMap.js\";\nimport TemplateEngineManager from \"../src/Engines/TemplateEngineManager.js\";\n\nimport { getTemplateConfigInstance } from \"./_testHelpers.js\";\n\nasync function getNewTemplateRender(name, inputDir) {\n  let eleventyConfig = await getTemplateConfigInstance({\n\t\tdir: {\n\t\t\tinput: inputDir\n\t\t}\n\t});\n\n  let tr = new TemplateRender(name, eleventyConfig);\n  tr.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  tr.extensionMap.engineManager = new TemplateEngineManager(eleventyConfig);\n  tr.extensionMap.setFormats([]);\n  await tr.init();\n  return tr;\n}\n\n// HTML\ntest(\"HTML\", async (t) => {\n  let tr = await getNewTemplateRender(\"html\");\n  t.is(tr.getEngineName(), \"html\");\n});\n\ntest(\"HTML Render\", async (t) => {\n  let tr = await getNewTemplateRender(\"html\");\n  let fn = await tr.getCompiledTemplate(\"<p>Paragraph</p>\");\n  t.is(await fn(), \"<p>Paragraph</p>\");\n  t.is(await fn({}), \"<p>Paragraph</p>\");\n});\n\ntest(\"HTML Render: Parses HTML using liquid engine (default, with data)\", async (t) => {\n  let tr = await getNewTemplateRender(\"html\");\n  let fn = await tr.getCompiledTemplate(\"<h1>{{title}}</h1>\");\n  t.is((await fn({ title: \"My Title\" })).trim(), \"<h1>My Title</h1>\");\n});\n\ntest(\"HTML Render: Set HTML engine to false, don’t parse\", async (t) => {\n  let tr = await getNewTemplateRender(\"html\");\n  tr.setHtmlEngine(false);\n\n  let fn = await tr.getCompiledTemplate(\"<h1>{{title}}</h1>\");\n  t.is((await fn()).trim(), \"<h1>{{title}}</h1>\");\n});\n\ntest(\"HTML Render: Pass in an override (liquid)\", async (t) => {\n  let tr = await getNewTemplateRender(\"html\");\n  tr.setHtmlEngine(\"liquid\");\n  let fn = await tr.getCompiledTemplate(\"<h1>{{title}}</h1>\");\n\n  t.is((await fn({ title: \"My Title\" })).trim(), \"<h1>My Title</h1>\");\n});\n"
  },
  {
    "path": "test/TemplateRenderJavaScriptTest.js",
    "content": "import test from \"ava\";\n\nimport TemplateRender from \"../src/TemplateRender.js\";\nimport Eleventy from \"../src/Eleventy.js\";\nimport EleventyExtensionMap from \"../src/EleventyExtensionMap.js\";\nimport TemplateEngineManager from \"../src/Engines/TemplateEngineManager.js\";\n\nimport { getTemplateConfigInstance } from \"./_testHelpers.js\";\n\nasync function getNewTemplateRender(name, inputDir, extendedConfig) {\n  let eleventyConfig = await getTemplateConfigInstance({\n\t\tdir: {\n\t\t\tinput: inputDir\n\t\t}\n\t}, null, extendedConfig);\n\n  eleventyConfig.setProjectUsingEsm(true);\n\n  let tr = new TemplateRender(name, eleventyConfig);\n  tr.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  tr.extensionMap.engineManager = new TemplateEngineManager(eleventyConfig);\n  tr.extensionMap.setFormats([]);\n  await tr.init();\n\n  return tr;\n}\n\ntest(\"JS\", async (t) => {\n  t.is((await getNewTemplateRender(\"11ty.js\")).getEngineName(), \"11ty.js\");\n  t.is((await getNewTemplateRender(\"./test/stubs/filename.11ty.js\")).getEngineName(), \"11ty.js\");\n  t.is((await getNewTemplateRender(\"11ty.cjs\")).getEngineName(), \"11ty.js\");\n  t.is((await getNewTemplateRender(\"./test/stubs/filename.11ty.cjs\")).getEngineName(), \"11ty.js\");\n});\n\ntest(\"JS Render a string (no data)\", async (t) => {\n  let tr = await getNewTemplateRender(\"./test/stubs/string.11ty.cjs\");\n  let fn = await tr.getCompiledTemplate();\n  t.is(await fn({ name: \"Bill\" }), \"<p>Zach</p>\");\n});\n\ntest(\"JS Render a promise (no data)\", async (t) => {\n  let tr = await getNewTemplateRender(\"./test/stubs/promise.11ty.cjs\");\n  let fn = await tr.getCompiledTemplate();\n  t.is(await fn({ name: \"Bill\" }), \"<p>Zach</p>\");\n});\n\ntest(\"JS Render a buffer (no data)\", async (t) => {\n  let tr = await getNewTemplateRender(\"./test/stubs/buffer.11ty.cjs\");\n  let fn = await tr.getCompiledTemplate();\n  t.is(await fn({ name: \"Bill\" }), \"<p>tést</p>\");\n});\n\ntest(\"JS Render a function\", async (t) => {\n  let tr = await getNewTemplateRender(\"./test/stubs/function.11ty.cjs\");\n  let fn = await tr.getCompiledTemplate();\n  t.is(await fn({ name: \"Zach\" }), \"<p>Zach</p>\");\n  t.is(await fn({ name: \"Bill\" }), \"<p>Bill</p>\");\n});\n\ntest(\"JS Render a function (arrow syntax)\", async (t) => {\n  let tr = await getNewTemplateRender(\"./test/stubs/function-arrow.11ty.cjs\");\n  let fn = await tr.getCompiledTemplate();\n  t.is(await fn({ name: \"Zach\" }), \"<p>Zach</p>\");\n  t.is(await fn({ name: \"Bill\" }), \"<p>Bill</p>\");\n});\n\ntest(\"JS Render a function, returns a Buffer\", async (t) => {\n  let tr = await getNewTemplateRender(\"./test/stubs/function-buffer.11ty.cjs\");\n  let fn = await tr.getCompiledTemplate();\n  t.is(await fn({ name: \"tést\" }), \"<p>tést</p>\");\n  t.is(await fn({ name: \"Zach\" }), \"<p>Zach</p>\");\n  t.is(await fn({ name: \"Bill\" }), \"<p>Bill</p>\");\n});\n\ntest(\"JS Render a function (Markdown)\", async (t) => {\n  let tr = await getNewTemplateRender(\"./test/stubs/function-markdown.11ty.cjs\");\n  await tr.setEngineOverride(\"11ty.js,md\");\n\n  let fn = await tr.getCompiledTemplate();\n  t.is((await fn({ name: \"Zach\" })).trim(), \"<h1>Zach</h1>\");\n  t.is((await fn({ name: \"Bill\" })).trim(), \"<h1>Bill</h1>\");\n});\n\ntest(\"JS Render a function (Collections)\", async (t) => {\n  let tr = await getNewTemplateRender(\"./test/stubs/use-collection.11ty.cjs\");\n  let fn = await tr.getCompiledTemplate();\n  t.is(\n    (\n      await fn({\n        collections: {\n          post: [\n            {\n              data: {\n                title: \"Testing\",\n              },\n            },\n            {\n              data: {\n                title: \"Testing2\",\n              },\n            },\n          ],\n        },\n      })\n    ).trim(),\n    `<ul><li>Testing</li><li>Testing2</li></ul>`\n  );\n});\n\ntest(\"JS Render an async function\", async (t) => {\n  let tr = await getNewTemplateRender(\"./test/stubs/function-async.11ty.cjs\");\n  let fn = await tr.getCompiledTemplate();\n  t.is(await fn({ name: \"Zach\" }), \"<p>Zach</p>\");\n  t.is(await fn({ name: \"Bill\" }), \"<p>Bill</p>\");\n});\n\ntest(\"JS Render with a Class\", async (t) => {\n  let tr = await getNewTemplateRender(\"./test/stubs/class.11ty.cjs\");\n  let fn = await tr.getCompiledTemplate();\n  t.is(await fn({ name: \"Zach\" }), \"<p>ZachBillTed</p>\");\n  t.is(await fn({ name: \"Bill\" }), \"<p>BillBillTed</p>\");\n});\n\ntest(\"JS Render with a Class, returns a buffer\", async (t) => {\n  let tr = await getNewTemplateRender(\"./test/stubs/class-buffer.11ty.cjs\");\n  let fn = await tr.getCompiledTemplate();\n  t.is(await fn({ name: \"Zách\" }), \"<p>ZáchBillTed</p>\");\n  t.is(await fn({ name: \"Zach\" }), \"<p>ZachBillTed</p>\");\n  t.is(await fn({ name: \"Bill\" }), \"<p>BillBillTed</p>\");\n});\n\ntest(\"JS Render with a Class, async render\", async (t) => {\n  let tr = await getNewTemplateRender(\"./test/stubs/class-async.11ty.cjs\");\n  let fn = await tr.getCompiledTemplate();\n  t.is(await fn({ name: \"Zach\" }), \"<p>Zach</p>\");\n  t.is(await fn({ name: \"Bill\" }), \"<p>Bill</p>\");\n});\n\ntest(\"JS Render using Vue\", async (t) => {\n  let tr = await getNewTemplateRender(\"./test/stubs/vue.11ty.cjs\");\n  let fn = await tr.getCompiledTemplate();\n  t.is(await fn({ name: \"Zach\" }), \"<p>Hello Zach, this is a Vue template.</p>\");\n  t.is(await fn({ name: \"Bill\" }), \"<p>Hello Bill, this is a Vue template.</p>\");\n});\n\ntest(\"JS Render using Vue (with a layout)\", async (t) => {\n  let tr = await getNewTemplateRender(\"./test/stubs/vue-layout.11ty.cjs\");\n  let fn = await tr.getCompiledTemplate();\n  t.is(\n    await fn({ name: \"Zach\" }),\n    `<!doctype html>\n<title>Test</title>\n<p>Hello Zach, this is a Vue template.</p>`\n  );\n});\n\ntest(\"JS Render with a function\", async (t) => {\n  t.plan(8);\n\n  let tr = await getNewTemplateRender(\"./test/stubs/function-filter.11ty.cjs\", undefined, {\n    javascriptFunctions: {\n      upper: function (val) {\n        t.is(this.page.url, \"/hi/\");\n        // sanity check to make sure data didn’t propagate\n        t.not(this.name, \"Zach\");\n        t.not(this.name, \"Bill\");\n        return new String(val).toUpperCase();\n      },\n    },\n  });\n\n  let fn = await tr.getCompiledTemplate();\n  t.is(await fn({ name: \"Zach\", page: { url: \"/hi/\" } }), \"<p>ZACHT9000</p>\");\n  t.is(await fn({ name: \"Bill\", page: { url: \"/hi/\" } }), \"<p>BILLT9000</p>\");\n});\n\ntest(\"JS Render with a function and async filter\", async (t) => {\n  t.plan(4);\n\n  let tr = await getNewTemplateRender(\"./test/stubs/function-async-filter.11ty.cjs\", undefined, {\n    javascriptFunctions: {\n      upper: function (val) {\n        return new Promise((resolve) => {\n          t.is(this.page.url, \"/hi/\");\n          resolve(new String(val).toUpperCase());\n        });\n      },\n    },\n  });\n\n  let fn = await tr.getCompiledTemplate();\n  t.is(await fn({ name: \"Zach\", page: { url: \"/hi/\" } }), \"<p>ZACH</p>\");\n  t.is(await fn({ name: \"Bill\", page: { url: \"/hi/\" } }), \"<p>BILL</p>\");\n});\n\ntest(\"JS Render with a function prototype\", async (t) => {\n  t.plan(4);\n  let tr = await getNewTemplateRender(\"./test/stubs/function-prototype.11ty.cjs\", undefined, {\n    javascriptFunctions: {\n      upper: function (val) {\n        t.is(this.page.url, \"/hi/\");\n        return new String(val).toUpperCase();\n      },\n    },\n  });\n\n  let fn = await tr.getCompiledTemplate();\n  t.is(await fn({ name: \"Zach\", page: { url: \"/hi/\" } }), \"<p>ZACHBillT9001</p>\");\n  t.is(await fn({ name: \"Bill\", page: { url: \"/hi/\" } }), \"<p>BILLBillT9001</p>\");\n});\n\ntest(\"JS Class Render with a function\", async (t) => {\n  t.plan(4);\n\n  let tr = await getNewTemplateRender(\"./test/stubs/class-filter.11ty.cjs\", undefined, {\n    javascriptFunctions: {\n      upper: function (val) {\n        t.is(this.page.url, \"/hi/\");\n        return new String(val).toUpperCase();\n      },\n    },\n  });\n\n  let fn = await tr.getCompiledTemplate();\n  t.is(await fn({ name: \"Zach\", page: { url: \"/hi/\" } }), \"<p>ZACHBillTed</p>\");\n  t.is(await fn({ name: \"Bill\", page: { url: \"/hi/\" } }), \"<p>BILLBillTed</p>\");\n});\n\ntest(\"JS Class Async Render with a function\", async (t) => {\n  t.plan(4);\n\n  let tr = await getNewTemplateRender(\"./test/stubs/class-async-filter.11ty.cjs\", undefined, {\n    javascriptFunctions: {\n      upper: function (val) {\n        t.is(this.page.url, \"/hi/\");\n        return new String(val).toUpperCase();\n      },\n    },\n  });\n\n  let fn = await tr.getCompiledTemplate();\n  // Overrides all names to Ted\n  t.is(await fn({ name: \"Zach\", page: { url: \"/hi/\" } }), \"<p>ZACHBillTed</p>\");\n  t.is(await fn({ name: \"Bill\", page: { url: \"/hi/\" } }), \"<p>BILLBillTed</p>\");\n});\n\ntest(\"JS Class Async Render with a function (sync function, throws error)\", async (t) => {\n  let tr = await getNewTemplateRender(\"./test/stubs/function-throws.11ty.cjs\", undefined, {\n    javascriptFunctions: {\n      upper: function (val) {\n        throw new Error(\"JS Class Async Render with a function (sync function, throws error)\");\n      },\n    },\n  });\n\n  let error = await t.throwsAsync(async () => {\n    let fn = await tr.getCompiledTemplate();\n    await fn({ name: \"Zach\" });\n  });\n  t.true(\n    error.message.indexOf(\"JS Class Async Render with a function (sync function, throws error)\") >\n      -1\n  );\n});\n\ntest(\"JS Class Async Render with a function (async function, throws error)\", async (t) => {\n  let tr = await getNewTemplateRender(\"./test/stubs/function-throws-async.11ty.cjs\", undefined, {\n    javascriptFunctions: {\n      upper: async function (val) {\n        throw new Error(\"JS Class Async Render with a function (async function, throws error)\");\n      },\n    },\n  });\n\n  let error = await t.throwsAsync(async () => {\n    let fn = await tr.getCompiledTemplate();\n    await fn({ name: \"Zach\" });\n  });\n  t.true(\n    error.message.indexOf(\"JS Class Async Render with a function (async function, throws error)\") >\n      -1\n  );\n});\n\ntest(\"JS function has access to built in filters\", async (t) => {\n  t.plan(6);\n  let tr = await getNewTemplateRender(\"./test/stubs/function-fns.11ty.cjs\");\n\n  let fn = await tr.getCompiledTemplate();\n  await fn({ avaTest: t, page: { url: \"/hi/\" } });\n});\n\ntest(\"Class has access to built in filters\", async (t) => {\n  t.plan(6);\n  let tr = await getNewTemplateRender(\"./test/stubs/class-fns.11ty.cjs\");\n\n  let fn = await tr.getCompiledTemplate();\n  await fn({ avaTest: t, page: { url: \"/hi/\" } });\n});\n\ntest(\"Class has page property already and keeps it\", async (t) => {\n  t.plan(2);\n  let tr = await getNewTemplateRender(\"./test/stubs/class-fns-has-page.11ty.cjs\");\n  let fn = await tr.getCompiledTemplate();\n  await fn({ avaTest: t, page: { url: \"/hi/\" } });\n});\n\ntest(\"File has default function export and another one too, issue #3288\", async (t) => {\n  let tr = await getNewTemplateRender(\"./test/stubs/default-export-and-others.11ty.js\");\n  let fn = await tr.getCompiledTemplate();\n  t.is(await fn(), \"<h1>hello</h1>\")\n});\n\ntest(\"File has default class export and another one too, issue #3359\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs/default-class-export-and-others.11ty.js\", \"\");\n  let results = await elev.toJSON();\n\n  t.is(results[0].content, \"<div>hello</div>\")\n});\n\ntest(\"File has default function export and another one for data too, issue #3359\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs/default-function-export-and-named-data.11ty.js\", \"\");\n  let results = await elev.toJSON();\n\n  t.is(results[0].content, \"<h1>Hello World</h1>\")\n});\n\ntest(\"File has default function export and another one for data too, issue #3359 (CommonJS)\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs/default-function-export-and-named-data.11ty.cjs\", \"\");\n  let results = await elev.toJSON();\n\n  t.is(results[0].content, \"<h1>Hello World</h1>\")\n});\n"
  },
  {
    "path": "test/TemplateRenderLiquidTest.js",
    "content": "import test from \"ava\";\nimport { Liquid, Drop } from \"liquidjs\";\n\nimport Eleventy from \"../src/Eleventy.js\";\nimport TemplateRender from \"../src/TemplateRender.js\";\nimport EleventyExtensionMap from \"../src/EleventyExtensionMap.js\";\n\nimport { getTemplateConfigInstance } from \"./_testHelpers.js\";\nimport TemplateEngineManager from \"../src/Engines/TemplateEngineManager.js\";\n\nasync function getNewTemplateRender(name, inputDir, userConfig = {}) {\n\tlet eleventyConfig = await getTemplateConfigInstance({\n\t\tdir: {\n\t\t\tinput: inputDir\n\t\t}\n\t}, null, userConfig);\n\n  let tr = new TemplateRender(name, eleventyConfig);\n  tr.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  tr.extensionMap.engineManager = new TemplateEngineManager(eleventyConfig);\n  tr.extensionMap.setFormats([]);\n  await tr.init();\n  return tr;\n}\n\nasync function getPromise(resolveTo) {\n  return new Promise(function (resolve) {\n    setTimeout(function () {\n      resolve(resolveTo);\n    });\n  });\n}\n\n// Liquid\ntest(\"Liquid\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\");\n  t.is(tr.getEngineName(), \"liquid\");\n});\n\ntest(\"Liquid Render Addition\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\");\n  let fn = await tr.getCompiledTemplate(\"<p>{{ number | plus: 1 }}</p>\");\n  t.is(await fn({ number: 1 }), \"<p>2</p>\");\n});\n\ntest(\"Liquid Render Raw\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\");\n  let fn = await tr.getCompiledTemplate(\"<p>{% raw %}{{name}}{% endraw %}</p>\");\n  t.is(await fn({ name: \"tim\" }), \"<p>{{name}}</p>\");\n});\n\ntest(\"Liquid Render Raw Multiline\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\");\n  let fn = await tr.getCompiledTemplate(\n    `<p>{% raw %}\n{{name}}\n{% endraw %}</p>`\n  );\n  t.is(\n    await fn({ name: \"tim\" }),\n    `<p>\n{{name}}\n</p>`\n  );\n});\n\ntest(\"Liquid Render (with Helper)\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\");\n  let fn = await tr.getCompiledTemplate(\"<p>{{name | capitalize}}</p>\");\n  t.is(await fn({ name: \"tim\" }), \"<p>Tim</p>\");\n});\n\ntest(\"Liquid Render Include\", async (t) => {\n  let tr1 = await getNewTemplateRender(\"liquid\", \"./test/stubs/\");\n  t.is(tr1.getEngineName(), \"liquid\");\n\n  let tr2 = await getNewTemplateRender(\"liquid\", \"./test/stubs/\", {\n    liquidOptions: {\n      dynamicPartials: false,\n    },\n  });\n\n  let fn = await tr2.getCompiledTemplate(\"<p>{% include included %}</p>\");\n  t.is(await fn(), \"<p>This is an include.</p>\");\n});\n\ntest(\"Liquid Render Relative Include (dynamicPartials off)\", async (t) => {\n  let tr1 = await getNewTemplateRender(\"liquid\", \"./test/stubs/\");\n  t.is(tr1.getEngineName(), \"liquid\");\n\n  let tr2 = await getNewTemplateRender(\"liquid\", \"./test/stubs/\", {\n    liquidOptions: {\n      dynamicPartials: false,\n    },\n  });\n\n  // Important note: when inputPath is set to `liquid`, this *only* uses _includes relative paths in Liquid->compile\n  let fn = await tr2.getCompiledTemplate(\"<p>{% include ./included %}</p>\");\n  t.is(await fn(), \"<p>This is an include.</p>\");\n});\n\ntest(\"Liquid Render Relative Include (dynamicPartials on)\", async (t) => {\n  let tr1 = await getNewTemplateRender(\"liquid\", \"./test/stubs/\");\n  t.is(tr1.getEngineName(), \"liquid\");\n\n  let tr2 = await getNewTemplateRender(\"liquid\", \"./test/stubs/\");\n\n  // Important note: when inputPath is set to `liquid`, this *only* uses _includes relative paths in Liquid->compile\n  let fn = await tr2.getCompiledTemplate(\"<p>{% include './included' %}</p>\");\n  t.is(await fn(), \"<p>This is an include.</p>\");\n});\n\ntest(\"Liquid Render Relative (current dir) Include\", async (t) => {\n  let tr = await getNewTemplateRender(\n    \"./test/stubs/relative-liquid/does_not_exist_and_thats_ok.liquid\",\n    \"./test/stubs/\",\n    {\n      liquidOptions: {\n        dynamicPartials: false,\n      },\n    }\n  );\n\n  let fn = await tr.getCompiledTemplate(\"<p>{% include ./dir/included %}</p>\");\n  t.is(await fn(), \"<p>TIME IS RELATIVE.</p>\");\n});\n\ntest(\"Liquid Render Relative (parent dir) Include\", async (t) => {\n  let tr = await getNewTemplateRender(\n    \"./test/stubs/relative-liquid/dir/does_not_exist_and_thats_ok.liquid\",\n    \"./test/stubs/\",\n    {\n      liquidOptions: {\n        dynamicPartials: false,\n      },\n    }\n  );\n\n  let fn = await tr.getCompiledTemplate(\"<p>{% include ../dir/included %}</p>\");\n  t.is(await fn(), \"<p>TIME IS RELATIVE.</p>\");\n});\n\ntest(\"Liquid Render Relative (relative include should ignore _includes dir) Include\", async (t) => {\n  let tr = await getNewTemplateRender(\n    \"./test/stubs/does_not_exist_and_thats_ok.liquid\",\n    \"./test/stubs/\",\n    {}\n  );\n\n  let fn = await tr.getCompiledTemplate(`<p>{% include './included' %}</p>`);\n  t.is(await fn(), \"<p>This is not in the includes dir.</p>\");\n});\n\ntest(\"Liquid Render Include with Liquid Suffix\", async (t) => {\n  let tr1 = await getNewTemplateRender(\"liquid\", \"./test/stubs/\");\n  t.is(tr1.getEngineName(), \"liquid\");\n\n  let tr2 = await getNewTemplateRender(\"liquid\", \"./test/stubs/\", {\n    liquidOptions: {\n      dynamicPartials: false,\n    },\n  });\n\n  let fn = await tr2.getCompiledTemplate(\"<p>{% include included.liquid %}</p>\");\n  t.is(await fn(), \"<p>This is an include.</p>\");\n});\n\ntest(\"Liquid Render Include with HTML Suffix\", async (t) => {\n  let tr1 = await getNewTemplateRender(\"liquid\", \"./test/stubs/\");\n  t.is(tr1.getEngineName(), \"liquid\");\n\n  let tr2 = await getNewTemplateRender(\"liquid\", \"./test/stubs/\", {\n    liquidOptions: {\n      dynamicPartials: false,\n    },\n  });\n\n  let fn = await tr2.getCompiledTemplate(\"<p>{% include included.html %}</p>\");\n  t.is(await fn(), \"<p>This is an include.</p>\");\n});\n\ntest(\"Liquid Render Include with HTML Suffix and Data Pass in\", async (t) => {\n  let tr1 = await getNewTemplateRender(\"liquid\", \"./test/stubs/\");\n  t.is(tr1.getEngineName(), \"liquid\");\n\n  let tr2 = await getNewTemplateRender(\"liquid\", \"./test/stubs/\", {\n    liquidOptions: {\n      dynamicPartials: false,\n    },\n  });\n\n  let fn = await tr2.getCompiledTemplate(\"{% include included-data.html, myVariable: 'myValue' %}\");\n  t.is((await fn()).trim(), \"This is an include. myValue\");\n});\n\ntest(\"Liquid Custom Filter\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\", \"./test/stubs/\");\n  tr.engine.addFilter(\"prefixWithZach\", function (val) {\n    return \"Zach\" + val;\n  });\n\n  t.is(await tr._testRender(\"{{ 'test' | prefixWithZach }}\", {}), \"Zachtest\");\n});\n\ntest(\"Liquid Async Filter\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\", \"test/stubs\");\n  tr.engine.addFilter(\"myAsyncFilter\", async function (value) {\n    return new Promise((resolve, reject) => {\n      setTimeout(function () {\n        resolve(`HI${value}`);\n      }, 100);\n    });\n  });\n\n  let fn = await tr.getCompiledTemplate(\"{{ 'test' | myAsyncFilter }}\");\n  t.is((await fn()).trim(), \"HItest\");\n});\n\ntest(\"Issue 3206: Strict variables and custom filters in includes\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\", \"test/stubs\", {\n    liquidOptions: {\n      strictVariables: true\n    }\n  });\n  tr.engine.addFilter(\"makeItFoo\", function () {\n    return \"foo\";\n  });\n  let fn = await tr.getCompiledTemplate(`<p>{% render \"custom-filter\", name: \"Zach\" %}</p>`);\n  t.is((await fn()), \"<p>foo</p>\");\n});\n\ntest(\"Liquid Custom Tag prefixWithZach\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\", \"./test/stubs/\");\n  tr.engine.addTag(\"prefixWithZach\", function (liquidEngine) {\n    return {\n      parse: function (tagToken, remainTokens) {\n        this.str = tagToken.args; // name\n      },\n      render: function (ctx, hash) {\n        var str = liquidEngine.evalValueSync(this.str, ctx.environments); // 'alice'\n        return Promise.resolve(\"Zach\" + str); // 'Alice'\n      },\n    };\n  });\n\n  t.is(await tr._testRender(\"{% prefixWithZach name %}\", { name: \"test\" }), \"Zachtest\");\n});\n\ntest(\"Liquid Custom Tag postfixWithZach\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\", \"./test/stubs/\");\n  tr.engine.addTag(\"postfixWithZach\", function (liquidEngine) {\n    return {\n      parse: function (tagToken, remainTokens) {\n        this.str = tagToken.args;\n      },\n      render: async function (ctx, hash) {\n        var str = await liquidEngine.evalValue(this.str, ctx.environments);\n        return Promise.resolve(str + \"Zach\");\n      },\n    };\n  });\n\n  t.is(await tr._testRender(\"{% postfixWithZach name %}\", { name: \"test\" }), \"testZach\");\n});\n\ntest(\"Liquid Custom Tag Unquoted String\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\", \"./test/stubs/\");\n  tr.engine.addTag(\"testUnquotedStringTag\", function (liquidEngine) {\n    return {\n      parse: function (tagToken, remainTokens) {\n        this.str = tagToken.args;\n      },\n      render: function (scope, hash) {\n        return Promise.resolve(this.str + \"Zach\");\n      },\n    };\n  });\n\n  t.is(\n    await tr._testRender(\"{% testUnquotedStringTag _posts/2016-07-26-name-of-post.md %}\", {\n      name: \"test\",\n    }),\n    \"_posts/2016-07-26-name-of-post.mdZach\"\n  );\n});\n\ntest(\"Liquid addTag errors\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\", \"./test/stubs/\");\n  t.throws(() => {\n    tr.engine.addTag(\"badSecondArgument\", {});\n  });\n});\n\ntest(\"Liquid addTags\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\", \"./test/stubs/\");\n  tr.engine.addCustomTags({\n    postfixWithZach: function (liquidEngine) {\n      return {\n        parse: function (tagToken, remainTokens) {\n          this.str = tagToken.args;\n        },\n        render: async function (ctx, hash) {\n          var str = await liquidEngine.evalValue(this.str, ctx.environments);\n          return Promise.resolve(str + \"Zach\");\n        },\n      };\n    },\n  });\n\n  t.is(await tr._testRender(\"{% postfixWithZach name %}\", { name: \"test\" }), \"testZach\");\n});\n\ntest(\"Liquid Shortcode\", async (t) => {\n  t.plan(3);\n\n  let tr = await getNewTemplateRender(\"liquid\", \"./test/stubs/\");\n  tr.engine.addShortcode(\"postfixWithZach\", function (str) {\n    // Data in context\n    t.is(this.page.url, \"/hi/\");\n    t.not(this.name, \"test\");\n\n    return str + \"Zach\";\n  });\n\n  t.is(\n    await tr._testRender(\"{% postfixWithZach name %}\", {\n      name: \"test\",\n      page: {\n        url: \"/hi/\",\n      },\n    }),\n    \"testZach\"\n  );\n});\n\ntest(\"Liquid Shortcode returns promise\", async (t) => {\n  t.plan(2);\n\n  let tr = await getNewTemplateRender(\"liquid\", \"./test/stubs/\");\n  tr.engine.addShortcode(\"postfixWithZach\", function (str) {\n    // Data in context\n    t.is(this.page.url, \"/hi/\");\n\n    return new Promise(function (resolve) {\n      setTimeout(function () {\n        resolve(str + \"Zach\");\n      });\n    });\n  });\n\n  t.is(\n    await tr._testRender(\"{% postfixWithZach name %}\", {\n      name: \"test\",\n      page: {\n        url: \"/hi/\",\n      },\n    }),\n    \"testZach\"\n  );\n});\n\ntest(\"Liquid Shortcode returns promise (await inside)\", async (t) => {\n  t.plan(2);\n\n  let tr = await getNewTemplateRender(\"liquid\", \"./test/stubs/\");\n  tr.engine.addShortcode(\"postfixWithZach\", async function (str) {\n    // Data in context\n    t.is(this.page.url, \"/hi/\");\n\n    return await getPromise(str + \"Zach\");\n  });\n\n  t.is(\n    await tr._testRender(\"{% postfixWithZach name %}\", {\n      name: \"test\",\n      page: {\n        url: \"/hi/\",\n      },\n    }),\n    \"testZach\"\n  );\n});\n\ntest(\"Liquid Shortcode returns promise (no await inside)\", async (t) => {\n  t.plan(2);\n\n  let tr = await getNewTemplateRender(\"liquid\", \"./test/stubs/\");\n  tr.engine.addShortcode(\"postfixWithZach\", async function (str) {\n    // Data in context\n    t.is(this.page.url, \"/hi/\");\n    return getPromise(str + \"Zach\");\n  });\n\n  t.is(\n    await tr._testRender(\"{% postfixWithZach name %}\", {\n      name: \"test\",\n      page: {\n        url: \"/hi/\",\n      },\n    }),\n    \"testZach\"\n  );\n});\n\ntest(\"Liquid Shortcode Safe Output\", async (t) => {\n  t.plan(2);\n  let tr = await getNewTemplateRender(\"liquid\", \"./test/stubs/\");\n  tr.engine.addShortcode(\"postfixWithZach\", function (str) {\n    // Data in context\n    t.is(this.page.url, \"/hi/\");\n    return `<span>${str}</span>`;\n  });\n\n  t.is(\n    await tr._testRender(\"{% postfixWithZach name %}\", {\n      name: \"test\",\n      page: {\n        url: \"/hi/\",\n      },\n    }),\n    \"<span>test</span>\"\n  );\n});\n\ntest(\"Liquid Paired Shortcode\", async (t) => {\n  t.plan(2);\n  let tr = await getNewTemplateRender(\"liquid\", \"./test/stubs/\");\n  tr.engine.addPairedShortcode(\"postfixWithZach\", function (content, str) {\n    // Data in context\n    t.is(this.page.url, \"/hi/\");\n    return str + content + \"Zach\";\n  });\n\n  t.is(\n    await tr._testRender(\"{% postfixWithZach name %}Content{% endpostfixWithZach %}\", {\n      name: \"test\",\n      page: {\n        url: \"/hi/\",\n      },\n    }),\n    \"testContentZach\"\n  );\n});\n\ntest(\"Liquid Async Paired Shortcode\", async (t) => {\n  t.plan(2);\n  let tr = await getNewTemplateRender(\"liquid\", \"./test/stubs/\");\n  tr.engine.addPairedShortcode(\"postfixWithZach\", function (content, str) {\n    // Data in context\n    t.is(this.page.url, \"/hi/\");\n    return new Promise(function (resolve) {\n      setTimeout(function () {\n        resolve(str + content + \"Zach\");\n      });\n    });\n  });\n\n  t.is(\n    await tr._testRender(\"{% postfixWithZach name %}Content{% endpostfixWithZach %}\", {\n      name: \"test\",\n      page: {\n        url: \"/hi/\",\n      },\n    }),\n    \"testContentZach\"\n  );\n});\n\ntest(\"Liquid Render Include Subfolder\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\", \"./test/stubs/\", {\n    liquidOptions: {\n      dynamicPartials: false,\n    },\n  });\n\n  let fn = await tr.getCompiledTemplate(`<p>{% include subfolder/included.liquid %}</p>`);\n  t.is(await fn(), \"<p>This is an include.</p>\");\n});\n\ntest(\"Liquid Render Include Subfolder HTML\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\", \"./test/stubs/\", {\n    liquidOptions: {\n      dynamicPartials: false,\n    },\n  });\n\n  let fn = await tr.getCompiledTemplate(`<p>{% include subfolder/included.html %}</p>`);\n  t.is(await fn(), \"<p>This is an include.</p>\");\n});\n\ntest(\"Liquid Render Include Subfolder No file extension\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\", \"./test/stubs/\", {\n    liquidOptions: {\n      dynamicPartials: false,\n    },\n  });\n\n  let fn = await tr.getCompiledTemplate(`<p>{% include subfolder/included %}</p>`);\n  t.is(await fn(), \"<p>This is an include.</p>\");\n});\n\n// Related to https://github.com/harttle/liquidjs/issues/61\n// Note that we swapped the dynamicPartials default in Eleventy 1.0 from false to true\ntest(\"Liquid Render Include Subfolder Single quotes\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\", \"./test/stubs/\");\n  let fn = await tr.getCompiledTemplate(`<p>{% include 'subfolder/included.liquid' %}</p>`);\n  t.is(await fn(), \"<p>This is an include.</p>\");\n});\n\ntest(\"Liquid Render Include Subfolder Double quotes\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\", \"./test/stubs/\");\n  let fn = await tr.getCompiledTemplate(`<p>{% include \"subfolder/included.liquid\" %}</p>`);\n  t.is(await fn(), \"<p>This is an include.</p>\");\n});\n\ntest(\"Liquid Render Include Subfolder Single quotes HTML\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\", \"./test/stubs/\");\n  let fn = await tr.getCompiledTemplate(`<p>{% include 'subfolder/included.html' %}</p>`);\n  t.is(await fn(), \"<p>This is an include.</p>\");\n});\n\ntest(\"Liquid Render Include Subfolder Double quotes HTML\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\", \"./test/stubs/\");\n  let fn = await tr.getCompiledTemplate(`<p>{% include \"subfolder/included.html\" %}</p>`);\n  t.is(await fn(), \"<p>This is an include.</p>\");\n});\n\ntest(\"Liquid Render Include Subfolder Single quotes No file extension\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\", \"./test/stubs/\");\n  let fn = await tr.getCompiledTemplate(`<p>{% include 'subfolder/included' %}</p>`);\n  t.is(await fn(), \"<p>This is an include.</p>\");\n});\n\ntest(\"Liquid Render Include Subfolder Double quotes No file extension\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\", \"./test/stubs/\");\n  let fn = await tr.getCompiledTemplate(`<p>{% include \"subfolder/included\" %}</p>`);\n  t.is(await fn(), \"<p>This is an include.</p>\");\n});\n/* End tests related to dynamicPartials */\n\ntest(\"Liquid Options Overrides\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\", \"./test/stubs/\", {\n    liquidOptions: {\n      dynamicPartials: false,\n    },\n  });\n\n  let options = tr.engine.getLiquidOptions();\n  t.is(options.dynamicPartials, false);\n});\n\ntest(\"Liquid Render Include Subfolder Single quotes no extension dynamicPartials true\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\", \"./test/stubs/\");\n  let fn = await tr.getCompiledTemplate(`<p>{% include 'subfolder/included' %}</p>`);\n  t.is(await fn(), \"<p>This is an include.</p>\");\n});\n\ntest(\"Liquid Render Include Subfolder Single quotes (relative include current dir) dynamicPartials true\", async (t) => {\n  let tr = await getNewTemplateRender(\n    \"./test/stubs/does_not_exist_and_thats_ok.liquid\",\n    \"./test/stubs/\",\n    {}\n  );\n  let fn = await tr.getCompiledTemplate(`<p>{% include './relative-liquid/dir/included' %}</p>`);\n  t.is(await fn(), \"<p>TIME IS RELATIVE.</p>\");\n});\n\ntest(\"Liquid Render Include Subfolder Single quotes (relative include parent dir) dynamicPartials true\", async (t) => {\n  let tr = await getNewTemplateRender(\n    \"./test/stubs/subfolder/does_not_exist_and_thats_ok.liquid\",\n    \"./test/stubs/\",\n    {}\n  );\n  let fn = await tr.getCompiledTemplate(`<p>{% include '../relative-liquid/dir/included' %}</p>`);\n  t.is(await fn(), \"<p>TIME IS RELATIVE.</p>\");\n});\n\ntest(\"Liquid Render Include Subfolder Double quotes no extension dynamicPartials true\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\", \"./test/stubs/\");\n  let fn = await tr.getCompiledTemplate(`<p>{% include \"subfolder/included\" %}</p>`);\n  t.is(await fn(), \"<p>This is an include.</p>\");\n});\n\ntest(\"Liquid Render Include Subfolder Single quotes dynamicPartials true\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\", \"./test/stubs/\");\n  let fn = await tr.getCompiledTemplate(`<p>{% include 'subfolder/included.liquid' %}</p>`);\n  t.is(await fn(), \"<p>This is an include.</p>\");\n});\n\ntest(\"Liquid Render Include Subfolder Double quotes dynamicPartials true\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\", \"./test/stubs/\");\n  let fn = await tr.getCompiledTemplate(`<p>{% include \"subfolder/included.liquid\" %}</p>`);\n  t.is(await fn(), \"<p>This is an include.</p>\");\n});\n\ntest(\"Liquid Render Include Subfolder Single quotes HTML dynamicPartials true\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\", \"./test/stubs/\");\n  let fn = await tr.getCompiledTemplate(`<p>{% include 'subfolder/included.html' %}</p>`);\n  t.is(await fn(), \"<p>This is an include.</p>\");\n});\n\ntest(\"Liquid Render Include Subfolder Double quotes HTML dynamicPartials true\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\", \"./test/stubs/\");\n  let fn = await tr.getCompiledTemplate(`<p>{% include \"subfolder/included.html\" %}</p>`);\n  t.is(await fn(), \"<p>This is an include.</p>\");\n});\n\ntest(\"Liquid Render Include Subfolder Single quotes HTML dynamicPartials true, data passed in\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\", \"./test/stubs/\");\n  let fn = await tr.getCompiledTemplate(\n    `<p>{% include 'subfolder/included.html', myVariable: 'myValue' %}</p>`\n  );\n  t.is(await fn(), \"<p>This is an include.</p>\");\n});\n\ntest(\"Liquid Render Include Subfolder Double quotes HTML dynamicPartials true, data passed in\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\", \"./test/stubs/\");\n  let fn = await tr.getCompiledTemplate(\n    `<p>{% include \"subfolder/included.html\", myVariable: \"myValue\" %}</p>`\n  );\n  t.is(await fn(), \"<p>This is an include.</p>\");\n});\n\ntest(\"Liquid Render: with Library Override\", async (t) => {\n  const tr = await getNewTemplateRender(\"liquid\");\n\n  tr.engine.setLibrary(new Liquid());\n\n  const fn = await tr.getCompiledTemplate(\"<p>{{name | capitalize}}</p>\");\n  t.is(await fn({ name: \"tim\" }), \"<p>Tim</p>\");\n});\n\ntest(\"Liquid Paired Shortcode with Tag Inside\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\", \"./test/stubs/\");\n  tr.engine.addPairedShortcode(\"postfixWithZach\", function (content, str) {\n    return str + content + \"Zach\";\n  });\n\n  t.is(\n    await tr._testRender(\n      \"{% postfixWithZach name %}Content{% if tester %}If{% endif %}{% endpostfixWithZach %}\",\n      { name: \"test\", tester: true }\n    ),\n    \"testContentIfZach\"\n  );\n});\n\ntest(\"Liquid Nested Paired Shortcode\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\", \"./test/stubs/\");\n  tr.engine.addPairedShortcode(\"postfixWithZach\", function (content, str) {\n    return str + content + \"Zach\";\n  });\n\n  t.is(\n    await tr._testRender(\n      \"{% postfixWithZach name %}Content{% postfixWithZach name2 %}Content{% endpostfixWithZach %}{% endpostfixWithZach %}\",\n      { name: \"test\", name2: \"test2\" }\n    ),\n    \"testContenttest2ContentZachZach\"\n  );\n});\n\ntest(\"Liquid Shortcode Multiple Args\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\", \"./test/stubs/\");\n  tr.engine.addShortcode(\"postfixWithZach\", function (str, str2) {\n    return str + str2 + \"Zach\";\n  });\n\n  t.is(\n    await tr._testRender(\"{% postfixWithZach name other %}\", {\n      name: \"test\",\n      other: \"howdy\",\n    }),\n    \"testhowdyZach\"\n  );\n});\n\ntest(\"Liquid Include Scope Leak\", async (t) => {\n  let tr1 = await getNewTemplateRender(\"liquid\", \"./test/stubs/\");\n  t.is(tr1.getEngineName(), \"liquid\");\n\n  // This is by design, `include` assigns value to its parent scope,\n  // use `{% render %}` for separated, clean scope\n  // see: https://github.com/harttle/liquidjs/issues/404#issuecomment-955660149\n  let tr2 = await getNewTemplateRender(\"liquid\", \"./test/stubs/\");\n  let fn = await tr2.getCompiledTemplate(\"<p>{% include 'scopeleak' %}{{ test }}</p>\");\n  t.is(await fn({ test: 1 }), \"<p>22</p>\");\n});\n\ntest(\"Liquid Render Scope Leak\", async (t) => {\n  let tr1 = await getNewTemplateRender(\"liquid\", \"./test/stubs/\");\n  t.is(tr1.getEngineName(), \"liquid\");\n\n  let tr2 = await getNewTemplateRender(\"liquid\", \"./test/stubs/\");\n  // see scopeleak.liquid\n  let fn = await tr2.getCompiledTemplate(\"<p>{% render 'scopeleak' %}-{{ test }}</p>\");\n  t.is(await fn({ test: 1 }), \"<p>2-1</p>\");\n});\n\n// Note: this strictFilters default changed in 1.0 from false to true\ntest(\"Liquid Missing Filter Issue #183 (no strictFilters)\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\", \"./test/stubs/\", {\n    liquidOptions: {\n      strictFilters: false,\n    },\n  });\n\n  try {\n    await tr._testRender(\"{{ 'test' | prefixWithZach }}\", {});\n    t.pass(\"Did not error.\");\n  } catch (e) {\n    t.fail(\"Threw an error.\");\n  }\n});\n\n// Note: this strictFilters default changed in 1.0 from false to true\ntest(\"Liquid Missing Filter Issue #183\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\", \"./test/stubs/\");\n\n  try {\n    await tr._testRender(\"{{ 'test' | prefixWithZach }}\", {});\n    t.fail(\"Did not error.\");\n  } catch (e) {\n    t.pass(\"Threw an error.\");\n  }\n});\n\ntest(\"Issue 258: Liquid Render Date\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\");\n  let fn = await tr.getCompiledTemplate(\"<p>{{ myDate }}</p>\");\n  let dateStr = await fn({ myDate: new Date(Date.UTC(2016, 0, 1, 0, 0, 0)) });\n  t.is(dateStr.slice(0, 3), \"<p>\");\n  t.is(dateStr.slice(-4), \"</p>\");\n  t.not(dateStr.slice(2, 1), '\"');\n});\n\ntest(\"Issue 347: Liquid addTags with space in argument\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\", \"./test/stubs/\");\n  tr.engine.addCustomTags({\n    issue347CustomTag: function (liquidEngine) {\n      return {\n        parse: function (tagToken, remainTokens) {\n          this.str = tagToken.args;\n        },\n        render: async function (scope, hash) {\n          var str = await liquidEngine.evalValue(this.str, scope);\n          return Promise.resolve(str + \"Zach\");\n        },\n      };\n    },\n  });\n\n  t.is(\n    await tr._testRender(\"{% issue347CustomTag 'te st' %}\", {\n      name: \"slkdjflksdjf\",\n    }),\n    \"te stZach\"\n  );\n});\n\ntest(\"Issue 347: Liquid Shortcode, string argument\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\", \"./test/stubs/\");\n  tr.engine.addShortcode(\"issue347\", function (str) {\n    return str + \"Zach\";\n  });\n\n  t.is(await tr._testRender(\"{% issue347 'test' %}\", { name: \"alkdsjfkslja\" }), \"testZach\");\n});\n\ntest(\"Issue 347: Liquid Shortcode string argument with space, double quotes\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\", \"./test/stubs/\");\n  tr.engine.addShortcode(\"issue347b\", function (str) {\n    return str + \"Zach\";\n  });\n\n  t.is(\n    await tr._testRender('{% issue347b \"test 2\" \"test 3\" %}', {\n      name: \"alkdsjfkslja\",\n    }),\n    \"test 2Zach\"\n  );\n});\n\ntest(\"Issue 347: Liquid Shortcode string argument with space, single quotes\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\", \"./test/stubs/\");\n  tr.engine.addShortcode(\"issue347\", function (str) {\n    return str + \"Zach\";\n  });\n\n  t.is(await tr._testRender(\"{% issue347 'test 2' %}\", { name: \"alkdsjfkslja\" }), \"test 2Zach\");\n});\n\ntest(\"Issue 347: Liquid Shortcode string argument with space, combination of quotes\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\", \"./test/stubs/\");\n  tr.engine.addShortcode(\"issue347\", function (str, str2) {\n    return str + str2 + \"Zach\";\n  });\n\n  t.is(\n    await tr._testRender(\"{% issue347 'test 2' \\\"test 3\\\" %}\", {\n      name: \"alkdsjfkslja\",\n    }),\n    \"test 2test 3Zach\"\n  );\n});\n\ntest(\"Issue 347: Liquid Shortcode multiple arguments, comma separated\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\", \"./test/stubs/\");\n  tr.engine.addShortcode(\"issue347\", function (str, str2) {\n    return str + str2 + \"Zach\";\n  });\n\n  t.is(\n    await tr._testRender(\"{% issue347 'test 2', \\\"test 3\\\" %}\", {\n      name: \"alkdsjfkslja\",\n    }),\n    \"test 2test 3Zach\"\n  );\n});\n\ntest(\"Issue 347: Liquid Shortcode multiple arguments, comma separated, one is an integer\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\", \"./test/stubs/\");\n  tr.engine.addShortcode(\"issue347\", function (str, str2) {\n    return str + str2 + \"Zach\";\n  });\n\n  t.is(\n    await tr._testRender(\"{% issue347 'test 2', 3 %}\", {\n      name: \"alkdsjfkslja\",\n    }),\n    \"test 23Zach\"\n  );\n});\n\ntest(\"Issue 347: Liquid Shortcode multiple arguments, comma separated, one is a float\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\", \"./test/stubs/\");\n  tr.engine.addShortcode(\"issue347\", function (str, str2) {\n    return str + str2 + \"Zach\";\n  });\n\n  t.is(\n    await tr._testRender(\"{% issue347 'test 2', 3.23 %}\", {\n      name: \"alkdsjfkslja\",\n    }),\n    \"test 23.23Zach\"\n  );\n});\n\ntest(\"Issue 347: Liquid Shortcode boolean argument\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\", \"./test/stubs/\");\n  tr.engine.addShortcode(\"issue347\", function (bool) {\n    return bool ? \"Zach\" : \"Not Zach\";\n  });\n\n  t.is(await tr._testRender(\"{% issue347 true %}\", { name: \"alkdsjfkslja\" }), \"Zach\");\n  t.is(await tr._testRender(\"{% issue347 false %}\", { name: \"alkdsjfkslja\" }), \"Not Zach\");\n});\n\ntest(\"Issue 347: Liquid Paired Shortcode with Spaces\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\", \"./test/stubs/\");\n  tr.engine.addPairedShortcode(\"postfixWithZach\", function (content, str1, num, str2) {\n    return str1 + num + str2 + content + \"Zach\";\n  });\n\n  t.is(\n    await tr._testRender(\n      \"{% postfixWithZach 'My Name', 1234, \\\"Other\\\" %}Content{% endpostfixWithZach %}\",\n      { name: \"test\" }\n    ),\n    \"My Name1234OtherContentZach\"\n  );\n});\n\ntest(\"Liquid Render with dash variable Issue #567\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\");\n\n  let fn = await tr.getCompiledTemplate(\"<p>{{ my-global-name }}</p>\");\n  t.is(await fn({ \"my-global-name\": \"Zach\" }), \"<p>Zach</p>\");\n});\n\ntest(\"Issue 600: Liquid Shortcode argument page.url\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\", \"./test/stubs/\");\n  tr.engine.addShortcode(\"issue600\", function (str) {\n    return str + \"Zach\";\n  });\n\n  t.is(\n    await tr._testRender(\"{% issue600 page.url %}\", {\n      page: { url: \"alkdsjfkslja\" },\n    }),\n    \"alkdsjfksljaZach\"\n  );\n});\n\ntest(\"Issue 600: Liquid Shortcode argument with dashes\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\", \"./test/stubs/\");\n  tr.engine.addShortcode(\"issue600b\", function (str) {\n    return str + \"Zach\";\n  });\n\n  t.is(\n    await tr._testRender(\"{% issue600b page-url %}\", {\n      \"page-url\": \"alkdsjfkslja\",\n    }),\n    \"alkdsjfksljaZach\"\n  );\n});\n\ntest(\"Issue 600: Liquid Shortcode argument with underscores\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\", \"./test/stubs/\");\n  tr.engine.addShortcode(\"issue600c\", function (str) {\n    return str + \"Zach\";\n  });\n\n  t.is(\n    await tr._testRender(\"{% issue600c page_url %}\", {\n      page_url: \"alkdsjfkslja\",\n    }),\n    \"alkdsjfksljaZach\"\n  );\n});\n\ntest(\"Issue 611: Run a function\", async (t) => {\n  // function calls in Nunjucks can be replaced by custom Drops\n  let tr = await getNewTemplateRender(\"liquid\", \"./test/stubs/\");\n  class CustomDrop extends Drop {\n    valueOf() {\n      return \"alkdsjfksljaZach\";\n    }\n  }\n  t.is(\n    await tr._testRender(\"{{ test }}\", {\n      test: new CustomDrop(),\n    }),\n    \"alkdsjfksljaZach\"\n  );\n});\n\ntest(\"Liquid Shortcode (with sync function, error throwing)\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\", \"./test/stubs/\");\n  tr.engine.addShortcode(\"postfixWithZach\", function (str) {\n    throw new Error(\"Liquid Shortcode (with sync function, error throwing)\");\n  });\n\n  let error = await t.throwsAsync(async () => {\n    await tr._testRender(\"{% postfixWithZach name %}\", { name: \"test\" });\n  });\n  t.true(error.message.indexOf(\"Liquid Shortcode (with sync function, error throwing)\") > -1);\n});\n\ntest(\"Liquid Shortcode (with async function, error throwing)\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\", \"./test/stubs/\");\n  tr.engine.addShortcode(\"postfixWithZach\", async function (str) {\n    throw new Error(\"Liquid Shortcode (with async function, error throwing)\");\n  });\n\n  let error = await t.throwsAsync(async () => {\n    await tr._testRender(\"{% postfixWithZach name %}\", { name: \"test\" });\n  });\n  t.true(error.message.indexOf(\"Liquid Shortcode (with async function, error throwing)\") > -1);\n});\n\ntest(\"Liquid Render a false #1069\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\");\n  let fn = await tr.getCompiledTemplate(\"{{ falseValue }}\");\n  t.is(await fn({ falseValue: false }), \"false\");\n});\n\ntest(\"Liquid Render Square Brackets #680 dash single quotes\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\");\n  let fn = await tr.getCompiledTemplate(\"<p>{{ test['hey-a'] }}</p>\");\n  t.is(await fn({ test: { \"hey-a\": 1 } }), \"<p>1</p>\");\n});\n\ntest(\"Liquid Render Square Brackets #680 dash single quotes spaces\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\");\n  let fn = await tr.getCompiledTemplate(\"<p>{{ test[ 'hey-a' ] }}</p>\");\n  t.is(await fn({ test: { \"hey-a\": 1 } }), \"<p>1</p>\");\n});\n\ntest(\"Liquid Render Square Brackets #680 dash double quotes\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\");\n  let fn = await tr.getCompiledTemplate('<p>{{ test[\"hey-a\"] }}</p>');\n  t.is(await fn({ test: { \"hey-a\": 1 } }), \"<p>1</p>\");\n});\n\ntest(\"Liquid Render Square Brackets #680 dash double quotes spaces\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\");\n  let fn = await tr.getCompiledTemplate('<p>{{ test[ \"hey-a\" ] }}</p>');\n  t.is(await fn({ test: { \"hey-a\": 1 } }), \"<p>1</p>\");\n});\n\ntest(\"Liquid Render Square Brackets #680 variable reference\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\");\n  let fn = await tr.getCompiledTemplate(\"<p>{{ test[ref] }}</p>\");\n  t.is(await fn({ test: { \"hey-a\": 1 }, ref: \"hey-a\" }), \"<p>1</p>\");\n});\n\ntest(\"Liquid Render Square Brackets #680 variable reference array\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\");\n  let fn = await tr.getCompiledTemplate(\"<p>{{ test[ref[0]] }}</p>\");\n  t.is(await fn({ test: { \"hey-a\": 1 }, ref: [\"hey-a\"] }), \"<p>1</p>\");\n});\n\ntest(\"Liquid bypass compilation\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\");\n\n  t.is(tr.engine.needsCompilation(\"<p>{{ me }}</p>\"), true);\n  t.is(tr.engine.needsCompilation(\"<p>{% comment %}{% endcomment %}</p>\"), true);\n  t.is(tr.engine.needsCompilation(\"<p>test</p>\"), false);\n});\n\ntest(\"Liquid reverse filter in {{ }}\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\");\n  // https://liquidjs.com/filters/reverse.html\n  let fn = await tr.getCompiledTemplate(\"{{ test | reverse | join: ',' }}\");\n  t.is(await fn({ test: [1, 2, 3] }), \"3,2,1\");\n});\n\ntest(\"Liquid reverse filter in {% for %}\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\");\n  // https://liquidjs.com/tags/for.html#reversed\n  let fn = await tr.getCompiledTemplate(\"{% for num in test reversed %}{{ num }}{% endfor %}\");\n  t.is(await fn({ test: [1, 2, 3] }), \"321\");\n});\n\ntest(\"Liquid Parse for Symbols\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\");\n  let engine = tr.engine;\n\n  t.deepEqual(engine.parseForSymbols(\"<p>{{ name }}</p>\"), [\"name\"]);\n  t.deepEqual(engine.parseForSymbols(\"<p>{{ eleventy.deep.nested }}</p>\"), [\n    \"eleventy.deep.nested\",\n  ]);\n  t.deepEqual(engine.parseForSymbols(\"<p>{{ a }} {{ b }}</p>\"), [\"a\", \"b\"]);\n  t.deepEqual(engine.parseForSymbols(\"<p>{% if true %}{{ c }}{% endif %}</p>\"), [\"c\"]);\n  t.deepEqual(engine.parseForSymbols(\"<p>{% if false %}{{ c }}{% endif %}</p>\"), [\"c\"]);\n\n  t.deepEqual(engine.parseForSymbols(\"{{ collections.all[0] }}>\"), [\n    // Note that the Nunjucks parser returns collections.all\n    \"collections.all[0]\",\n  ]);\n  t.deepEqual(engine.parseForSymbols(\"{{ collections.mine }}>\"), [\"collections.mine\"]);\n\n  t.deepEqual(engine.parseForSymbols(\"{{ collections.mine | test }}>\"), [\"collections.mine\"]);\n});\n\ntest(\"Eleventy shortcode uses new built-in Liquid argument parsing behavior (spaces)\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: eleventyConfig => {\n      eleventyConfig.setLiquidParameterParsing(\"builtin\");\n      eleventyConfig.addShortcode(\"test\", (...args) => {\n        return JSON.stringify(args);\n      })\n      eleventyConfig.addTemplate(\"index.liquid\", `{% test abc def %}`, {\n        abc: 123,\n        def: 456\n      });\n    }\n  });\n  elev.disableLogger();\n\n  let [result] = await elev.toJSON();\n  t.deepEqual(result.content, \"[123,456]\");\n});\n\ntest(\"Eleventy shortcode uses new built-in Liquid argument parsing behavior (commas)\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: eleventyConfig => {\n      eleventyConfig.setLiquidParameterParsing(\"builtin\");\n      eleventyConfig.addShortcode(\"test\", (...args) => {\n        return JSON.stringify(args);\n      })\n      eleventyConfig.addTemplate(\"index.liquid\", `{% test abc, def %}`, {\n        abc: 123,\n        def: 456\n      });\n    }\n  });\n  elev.disableLogger();\n\n  let [result] = await elev.toJSON();\n  t.deepEqual(result.content, \"[123,456]\");\n});\n\ntest(\"Eleventy shortcode uses new built-in Liquid argument parsing behavior (commas, no spaces)\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: eleventyConfig => {\n      eleventyConfig.setLiquidParameterParsing(\"builtin\");\n      eleventyConfig.addShortcode(\"test\", (...args) => {\n        return JSON.stringify(args);\n      })\n      eleventyConfig.addTemplate(\"index.liquid\", `{% test abc,def %}`, {\n        abc: 123,\n        def: 456\n      });\n    }\n  });\n  elev.disableLogger();\n\n  let [result] = await elev.toJSON();\n  t.deepEqual(result.content, \"[123,456]\");\n});\n\ntest(\"Eleventy paired shortcode uses new built-in Liquid argument parsing behavior (spaces)\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: eleventyConfig => {\n      eleventyConfig.setLiquidParameterParsing(\"builtin\");\n      eleventyConfig.addPairedShortcode(\"test\", (...args) => {\n        return JSON.stringify(args);\n      })\n      eleventyConfig.addTemplate(\"index.liquid\", `{% test abc def %}hi{% endtest %}`, {\n        abc: 123,\n        def: 456\n      });\n    }\n  });\n  elev.disableLogger();\n\n  let [result] = await elev.toJSON();\n  t.deepEqual(result.content, `[\"hi\",123,456]`);\n});\n\ntest(\"Eleventy paired shortcode uses new built-in Liquid argument parsing behavior (commas)\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: eleventyConfig => {\n      eleventyConfig.setLiquidParameterParsing(\"builtin\");\n      eleventyConfig.addPairedShortcode(\"test\", (...args) => {\n        return JSON.stringify(args);\n      })\n      eleventyConfig.addTemplate(\"index.liquid\", `{% test abc, def %}hi{% endtest %}`, {\n        abc: 123,\n        def: 456\n      });\n    }\n  });\n  elev.disableLogger();\n\n  let [result] = await elev.toJSON();\n  t.deepEqual(result.content, `[\"hi\",123,456]`);\n});\n\ntest(\"Eleventy paired shortcode uses new built-in Liquid argument parsing behavior (commas, no spaces)\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: eleventyConfig => {\n      eleventyConfig.setLiquidParameterParsing(\"builtin\");\n      eleventyConfig.addPairedShortcode(\"test\", (...args) => {\n        return JSON.stringify(args);\n      })\n      eleventyConfig.addTemplate(\"index.liquid\", `{% test abc,def %}hi{% endtest %}`, {\n        abc: 123,\n        def: 456\n      });\n    }\n  });\n  elev.disableLogger();\n\n  let [result] = await elev.toJSON();\n  t.deepEqual(result.content, `[\"hi\",123,456]`);\n});\n\ntest(\"jsTruthy default changed, breaking in v4 #3507\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: eleventyConfig => {\n      eleventyConfig.addTemplate(\"index.liquid\", `{% if emptyString %}notempty{% endif %}-{% if zero %}notzero{% endif %}-{% if not emptyString %}empty{% endif %}-{% if not zero %}zero{% endif %}`, {\n        emptyString: \"\",\n        zero: 0\n      });\n    }\n  });\n\n  let [result] = await elev.toJSON();\n  t.deepEqual(result.content, `--empty-zero`);\n});\n\ntest(\"Use globals for page/eleventy/collections for use inside {% render %} #1541\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs/stubs-1541/\", undefined, {\n    config: eleventyConfig => {\n      eleventyConfig.addTemplate(\"index.liquid\", `{% render \"render-source.liquid\" %}`);\n    }\n  });\n\n  let [result] = await elev.toJSON();\n  t.deepEqual(result.content, `/ via script collections.all size: 1`);\n});\n"
  },
  {
    "path": "test/TemplateRenderMarkdownPluginTest.js",
    "content": "import test from \"ava\";\nimport md from \"markdown-it\";\n\nimport TemplateRender from \"../src/TemplateRender.js\";\nimport EleventyExtensionMap from \"../src/EleventyExtensionMap.js\";\nimport TemplateEngineManager from \"../src/Engines/TemplateEngineManager.js\";\n\nimport { getTemplateConfigInstance } from \"./_testHelpers.js\";\n\nasync function getNewTemplateRender(name, inputDir) {\n  let eleventyConfig = await getTemplateConfigInstance();\n\n  let tr = new TemplateRender(name, eleventyConfig);\n  tr.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  tr.extensionMap.engineManager = new TemplateEngineManager(eleventyConfig);\n  tr.extensionMap.setFormats([]);\n  await tr.init();\n  return tr;\n}\n\nconst createTestMarkdownPlugin = () => {\n  const plugin = (md) => {\n    md.core.ruler.after(\"inline\", \"replace-link\", function (state) {\n      plugin.environment = state.env;\n      const link = state.tokens[1].children[0].attrs[0][1];\n      state.tokens[1].children[0].attrs[0][1] = `${link}?data=${state.env.some}`;\n      return false;\n    });\n  };\n  plugin.environment = {};\n  return plugin;\n};\n\ntest(\"Markdown Render: with HTML prerender, sends context data to the markdown library\", async (t) => {\n  let tr = await getNewTemplateRender(\"md\");\n\n  const plugin = createTestMarkdownPlugin();\n  let mdLib = md().use(plugin);\n  tr.engine.setLibrary(mdLib);\n\n  const data = { some: \"data\" };\n\n  let fn = await tr.getCompiledTemplate(\"[link text](http://link.com)\");\n  let result = await fn(data);\n  t.deepEqual(plugin.environment, data);\n  t.is(result, '<p><a href=\"http://link.com?data=data\">link text</a></p>\\n');\n});\n\ntest(\"Markdown Render: without HTML prerender, sends context data to the markdown library\", async (t) => {\n  let tr = await getNewTemplateRender(\"md\");\n\n  const plugin = createTestMarkdownPlugin();\n  let mdLib = md().use(plugin);\n  tr.engine.setLibrary(mdLib);\n  tr.setHtmlEngine(false);\n\n  const data = { some: \"data\" };\n\n  let fn = await tr.getCompiledTemplate(\"[link text](http://link.com)\");\n  let result = await fn(data);\n  t.deepEqual(plugin.environment, data);\n  t.is(result, '<p><a href=\"http://link.com?data=data\">link text</a></p>\\n');\n});\n\ntest(\"Markdown Render: renderer that only implements the render function\", async (t) => {\n  let tr = await getNewTemplateRender(\"md\");\n  tr.engine.setLibrary({\n    render: (content) => {\n      const [_, text, href] = content.match(/\\[(.*)\\]\\((.*)\\)/);\n      return `<p><a href=\"${href}\">${text}</a></p>\\n`;\n    },\n  });\n  tr.setHtmlEngine(false);\n\n  let fn = await tr.getCompiledTemplate(\"[link text](http://link.com)\");\n  let result = await fn();\n  t.is(result, '<p><a href=\"http://link.com\">link text</a></p>\\n');\n});\n"
  },
  {
    "path": "test/TemplateRenderMarkdownTest.js",
    "content": "import test from \"ava\";\nimport md from \"markdown-it\";\nimport { full as mdEmoji } from 'markdown-it-emoji'\nimport eleventySyntaxHighlightPlugin from \"@11ty/eleventy-plugin-syntaxhighlight\";\n\nimport TemplateRender from \"../src/TemplateRender.js\";\nimport Liquid from \"../src/Engines/Liquid.js\";\nimport Nunjucks from \"../src/Engines/Nunjucks.js\";\nimport EleventyExtensionMap from \"../src/EleventyExtensionMap.js\";\nimport TemplateEngineManager from \"../src/Engines/TemplateEngineManager.js\";\n\nimport { normalizeNewLines } from \"./Util/normalizeNewLines.js\";\nimport { getTemplateConfigInstance, getTemplateConfigInstanceCustomCallback } from \"./_testHelpers.js\";\n\nasync function getNewTemplateRender(name, inputDir, eleventyConfig) {\n  if (!eleventyConfig) {\n    eleventyConfig = await getTemplateConfigInstance({\n      dir: {\n        input: inputDir\n      }\n    });\n  }\n\n  let tr = new TemplateRender(name, eleventyConfig);\n  tr.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  tr.extensionMap.engineManager = new TemplateEngineManager(eleventyConfig);\n  tr.extensionMap.setFormats([]);\n  await tr.init();\n  return tr;\n}\n\n// Markdown\ntest(\"Markdown\", async (t) => {\n  let tr = await getNewTemplateRender(\"md\");\n  t.is(tr.getEngineName(), \"md\");\n});\n\ntest(\"Markdown Render: Parses base markdown, no data\", async (t) => {\n  let tr = await getNewTemplateRender(\"md\");\n  let fn = await tr.getCompiledTemplate(\"# My Title\");\n  t.is((await fn()).trim(), \"<h1>My Title</h1>\");\n});\n\ntest(\"Markdown Render: Markdown should work with HTML too\", async (t) => {\n  let tr = await getNewTemplateRender(\"md\");\n  let fn = await tr.getCompiledTemplate(\"<h1>My Title</h1>\");\n  t.is((await fn()).trim(), \"<h1>My Title</h1>\");\n});\n\ntest(\"Markdown Render: Parses markdown using liquid engine (default, with data)\", async (t) => {\n  let tr = await getNewTemplateRender(\"md\");\n  let fn = await tr.getCompiledTemplate(\"# {{title}}\");\n  t.is((await fn({ title: \"My Title\" })).trim(), \"<h1>My Title</h1>\");\n});\n\ntest(\"Markdown Render: Ignore markdown, use only preprocess engine (useful for variable resolution in permalinks)\", async (t) => {\n  let tr = await getNewTemplateRender(\"md\");\n  tr.setUseMarkdown(false);\n\n  let fn = await tr.getCompiledTemplate(\"{{title}}\");\n  t.is((await fn({ title: \"My Title\" })).trim(), \"My Title\");\n});\n\ntest(\"Markdown Render: Skip markdown and preprocess engine (issue #466)\", async (t) => {\n  let tr = await getNewTemplateRender(\"md\");\n  tr.setMarkdownEngine(false);\n  tr.setUseMarkdown(false);\n  let fn = await tr.getCompiledTemplate(\"404.html\");\n  t.is((await fn({ title: \"My Title\" })).trim(), \"404.html\");\n});\n\ntest(\"Markdown Render: Set markdown engine to false, don’t parse\", async (t) => {\n  let tr = await getNewTemplateRender(\"md\");\n  tr.setMarkdownEngine(false);\n  let fn = await tr.getCompiledTemplate(\"# {{title}}\");\n  t.is((await fn()).trim(), \"<h1>{{title}}</h1>\");\n});\n\ntest(\"Markdown Render: Set markdown engine to false, don’t parse (test with HTML input)\", async (t) => {\n  let tr = await getNewTemplateRender(\"md\");\n  tr.setMarkdownEngine(false);\n  let fn = await tr.getCompiledTemplate(\"<h1>{{title}}</h1>\");\n\n  t.is((await fn()).trim(), \"<h1>{{title}}</h1>\");\n});\n\ntest(\"Markdown Render: Pass in an override (liquid)\", async (t) => {\n  let tr = await getNewTemplateRender(\"md\");\n  tr.setMarkdownEngine(\"liquid\");\n  let fn = await tr.getCompiledTemplate(\"# {{title}}\");\n\n  t.is((await fn({ title: \"My Title\" })).trim(), \"<h1>My Title</h1>\");\n});\n\ntest(\"Markdown Render: Strikethrough\", async (t) => {\n  let tr = await getNewTemplateRender(\"md\");\n  let fn = await tr.getCompiledTemplate(\"~~No~~\");\n  t.is((await fn()).trim(), \"<p><s>No</s></p>\");\n});\n\ntest(\"Markdown Render: Strikethrough in a Header\", async (t) => {\n  let tr = await getNewTemplateRender(\"md\");\n  let fn = await tr.getCompiledTemplate(\"# ~~No~~\");\n  t.is((await fn()).trim(), \"<h1><s>No</s></h1>\");\n});\n\ntest(\"Markdown Render: with Library Override\", async (t) => {\n  let tr = await getNewTemplateRender(\"md\");\n\n  let mdLib = md();\n  tr.engine.setLibrary(mdLib);\n  t.is(mdLib.render(\":)\").trim(), \"<p>:)</p>\");\n\n  let fn = await tr.getCompiledTemplate(\":)\");\n  t.is((await fn()).trim(), \"<p>:)</p>\");\n});\n\ntest(\"Markdown Render: with Library Override and a Plugin\", async (t) => {\n  let tr = await getNewTemplateRender(\"md\");\n\n  let mdLib = md().use(mdEmoji);\n  tr.engine.setLibrary(mdLib);\n  t.is(mdLib.render(\":)\").trim(), \"<p>😃</p>\");\n\n  let fn = await tr.getCompiledTemplate(\":)\");\n  t.is((await fn()).trim(), \"<p>😃</p>\");\n});\n\ntest(\"Markdown Render: use a custom highlighter\", async (t) => {\n  let tr = await getNewTemplateRender(\"md\");\n\n  let mdLib = md();\n  mdLib.set({\n    highlight: function (str, lang) {\n      return \"This is overrrrrrride\";\n    },\n  });\n  tr.engine.setLibrary(mdLib);\n\n  let fn = await tr.getCompiledTemplate(`\\`\\`\\`\nThis is some code.\n\\`\\`\\``);\n  t.is((await fn()).trim(), \"<pre><code>This is overrrrrrride</code></pre>\");\n});\n\ntest(\"Markdown Render: use prism highlighter (no language)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback(\n    {},\n    function(cfg) {\n      cfg.addPlugin(eleventySyntaxHighlightPlugin);\n    }\n  );\n\n  let tr = await getNewTemplateRender(\"md\", null, eleventyConfig);\n\n  let markdownHighlight = eleventyConfig.getConfig().markdownHighlighter;\n  let mdLib = md();\n  mdLib.set({\n    highlight: markdownHighlight,\n  });\n  tr.engine.setLibrary(mdLib);\n\n  let fn = await tr.getCompiledTemplate(`\\`\\`\\`\nThis is some code.\n\\`\\`\\``);\n  t.is(\n    (await fn()).trim(),\n    `<pre><code>This is some code.\n</code></pre>`\n  );\n});\n\ntest(\"Markdown Render: use prism highlighter\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback(\n    {},\n    function(cfg) {\n      cfg.addPlugin(eleventySyntaxHighlightPlugin);\n    }\n  );\n\n  let tr = await getNewTemplateRender(\"md\");\n\n  let markdownHighlight = eleventyConfig.getConfig().markdownHighlighter;\n\n  let mdLib = md();\n  mdLib.set({\n    highlight: markdownHighlight,\n  });\n  tr.engine.setLibrary(mdLib);\n\n  let fn = await tr.getCompiledTemplate(`\\`\\`\\` js\nvar key = \"value\";\n\\`\\`\\``);\n  t.is(\n    (await fn()).trim(),\n    `<pre class=\"language-js\"><code class=\"language-js\"><span class=\"token keyword\">var</span> key <span class=\"token operator\">=</span> <span class=\"token string\">\"value\"</span><span class=\"token punctuation\">;</span></code></pre>`\n  );\n});\n\ntest(\"Markdown Render: use prism highlighter (no space before language)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback(\n    {},\n    function(cfg) {\n      cfg.addPlugin(eleventySyntaxHighlightPlugin);\n    }\n  );\n\n  let tr = await getNewTemplateRender(\"md\", null, eleventyConfig);\n\n  let markdownHighlight = eleventyConfig.getConfig().markdownHighlighter;\n\n  let mdLib = md();\n  mdLib.set({\n    highlight: markdownHighlight,\n  });\n  tr.engine.setLibrary(mdLib);\n\n  let fn = await tr.getCompiledTemplate(`\\`\\`\\`js\nvar key = \"value\";\n\\`\\`\\``);\n  t.is(\n    (await fn()).trim(),\n    `<pre class=\"language-js\"><code class=\"language-js\"><span class=\"token keyword\">var</span> key <span class=\"token operator\">=</span> <span class=\"token string\">\"value\"</span><span class=\"token punctuation\">;</span></code></pre>`\n  );\n});\n\ntest(\"Markdown Render: use prism highlighter, line highlighting\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback(\n    {},\n    function(cfg) {\n      cfg.addPlugin(eleventySyntaxHighlightPlugin);\n    }\n  );\n\n  let tr = await getNewTemplateRender(\"md\", null, eleventyConfig);\n  let markdownHighlight = eleventyConfig.getConfig().markdownHighlighter;\n\n  let mdLib = md();\n  mdLib.set({\n    highlight: markdownHighlight,\n  });\n  tr.engine.setLibrary(mdLib);\n\n  let fn = await tr.getCompiledTemplate(`\\`\\`\\`js/0\nvar key = \"value\";\n\\`\\`\\``);\n  t.is(\n    (await fn()).trim(),\n    `<pre class=\"language-js\"><code class=\"language-js\"><mark class=\"highlight-line highlight-line-active\"><span class=\"token keyword\">var</span> key <span class=\"token operator\">=</span> <span class=\"token string\">\"value\"</span><span class=\"token punctuation\">;</span></mark></code></pre>`\n  );\n});\n\ntest(\"Markdown Render: use prism highlighter, line highlighting with fallback `text` language.\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback(\n    {},\n    function(cfg) {\n      cfg.addPlugin(eleventySyntaxHighlightPlugin);\n    }\n  );\n\n  let tr = await getNewTemplateRender(\"md\", null, eleventyConfig);\n\n  let cfg = eleventyConfig.getConfig();\n  let markdownHighlight = cfg.markdownHighlighter;\n\n  let mdLib = md();\n  mdLib.set({\n    highlight: markdownHighlight,\n  });\n  tr.engine.setLibrary(mdLib);\n\n  let fn = await tr.getCompiledTemplate(`\\`\\`\\` text/0\nvar key = \"value\";\n\\`\\`\\``);\n  t.is(\n    (await fn()).trim(),\n    `<pre class=\"language-text\"><code class=\"language-text\"><mark class=\"highlight-line highlight-line-active\">var key = \"value\";</mark></code></pre>`\n  );\n});\n\ntest(\"Markdown Render: use Markdown inside of a Liquid shortcode (Issue #536)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance();\n\n  let tr = await getNewTemplateRender(\"md\", null, eleventyConfig);\n\n  let liquidEngine = new Liquid(\"liquid\", eleventyConfig);\n  liquidEngine.addShortcode(\"testShortcode\", function () {\n    return \"## My Other Title\";\n  });\n  tr.setMarkdownEngine(liquidEngine);\n\n  let fn = await tr.getCompiledTemplate(`# {{title}}\n{% testShortcode %}`);\n  t.is(\n    (\n      await fn({\n        title: \"My Title\",\n        otherTitle: \"My Other Title\",\n      })\n    ).trim(),\n    `<h1>My Title</h1>\n<h2>My Other Title</h2>`\n  );\n});\n\ntest(\"Markdown Render: use Markdown inside of a Nunjucks shortcode (Issue #536)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance();\n  let tr = await getNewTemplateRender(\"md\", null, eleventyConfig);\n  let nunjucksEngine = new Nunjucks(\"njk\", eleventyConfig);\n  nunjucksEngine.addShortcode(\"testShortcode\", function () {\n    return \"## My Other Title\";\n  });\n  tr.setMarkdownEngine(nunjucksEngine);\n\n  let fn = await tr.getCompiledTemplate(`# {{title}}\n{% testShortcode %}`);\n  t.is(\n    (\n      await fn({\n        title: \"My Title\",\n        otherTitle: \"My Other Title\",\n      })\n    ).trim(),\n    `<h1>My Title</h1>\n<h2>My Other Title</h2>`\n  );\n});\n\ntest(\"Markdown Render: use Markdown inside of a Liquid paired shortcode (Issue #536)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance();\n  let tr = await getNewTemplateRender(\"md\", null, eleventyConfig);\n\n  let liquidEngine = new Liquid(\"liquid\", eleventyConfig);\n  liquidEngine.addPairedShortcode(\"testShortcode\", function (content) {\n    return content;\n  });\n  tr.setMarkdownEngine(liquidEngine);\n\n  let fn = await tr.getCompiledTemplate(`# {{title}}\n{% testShortcode %}## My Other Title{% endtestShortcode %}`);\n  t.is(\n    (\n      await fn({\n        title: \"My Title\",\n        otherTitle: \"My Other Title\",\n      })\n    ).trim(),\n    `<h1>My Title</h1>\n<h2>My Other Title</h2>`\n  );\n});\n\ntest(\"Markdown Render: use Markdown inside of a Nunjucks paired shortcode (Issue #536)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance();\n  let tr = await getNewTemplateRender(\"md\", null, eleventyConfig);\n\n  let nunjucksEngine = new Nunjucks(\"njk\", eleventyConfig);\n  nunjucksEngine.addPairedShortcode(\"testShortcode\", function (content) {\n    return content;\n  });\n  tr.setMarkdownEngine(nunjucksEngine);\n\n  let fn = await tr.getCompiledTemplate(`# {{title}}\n{% testShortcode %}## My Other Title{% endtestShortcode %}`);\n  t.is(\n    (\n      await fn({\n        title: \"My Title\",\n        otherTitle: \"My Other Title\",\n      })\n    ).trim(),\n    `<h1>My Title</h1>\n<h2>My Other Title</h2>`\n  );\n});\n\ntest(\"Markdown Render: Disable indented code blocks by default. Issue #2438\", async (t) => {\n  let tr = await getNewTemplateRender(\"md\");\n  let fn = await tr.getCompiledTemplate(\"    This is a test\");\n  t.is((await fn()).trim(), \"<p>This is a test</p>\");\n});\n\ntest(\"Markdown Render: setLibrary does not have disabled indented code blocks either. Issue #2438\", async (t) => {\n  let tr = await getNewTemplateRender(\"md\");\n\n  let mdLib = md();\n  tr.engine.setLibrary(mdLib);\n\n  let fn = await tr.getCompiledTemplate(\"    This is a test\");\n  let content = await fn();\n  t.is(content.trim(), \"<p>This is a test</p>\");\n});\n\ntest(\"Markdown Render: use amendLibrary to re-enable indented code blocks. Issue #2438\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback(\n    {},\n    function(cfg) {\n      cfg.amendLibrary(\"md\", (lib) => lib.enable(\"code\"));\n    }\n  );\n\n  let tr = await getNewTemplateRender(\"md\", null, eleventyConfig);\n\n  let fn = await tr.getCompiledTemplate(\"    This is a test\");\n  let content = await fn();\n  t.is(\n    normalizeNewLines(content.trim()),\n    `<pre><code>This is a test\n</code></pre>`\n  );\n});\n\ntest(\"Markdown Render: amendLibrary works with setLibrary to re-enable indented code blocks. Issue #2438\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback(\n    {},\n    function(cfg) {\n      cfg.amendLibrary(\"md\", (lib) => lib.enable(\"code\"));\n    }\n  );\n\n  let tr = await getNewTemplateRender(\"md\", null, eleventyConfig);\n\n  let mdLib = md();\n  tr.engine.setLibrary(mdLib);\n\n  let fn = await tr.getCompiledTemplate(\"    This is a test\");\n  let content = await fn();\n  t.is(\n    normalizeNewLines(content.trim()),\n    `<pre><code>This is a test\n</code></pre>`\n  );\n});\n\ntest(\"Markdown Render: multiple amendLibrary calls. Issue #2438\", async (t) => {\n  t.plan(3);\n\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback(\n    {},\n    function(cfg) {\n      cfg.amendLibrary(\"md\", (lib) => {\n        t.true(true);\n        lib.enable(\"code\");\n      });\n      cfg.amendLibrary(\"md\", (lib) => {\n        t.true(true);\n        lib.disable(\"code\");\n      });\n    }\n  );\n\n  let tr = await getNewTemplateRender(\"md\", null, eleventyConfig);\n\n  let fn = await tr.getCompiledTemplate(\"    This is a test\");\n  let content = await fn();\n  t.is(normalizeNewLines(content.trim()), \"<p>This is a test</p>\");\n});\n\ntest(\"Markdown Render: use amendLibrary to add a Plugin\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback(\n    {},\n    function(cfg) {\n      cfg.amendLibrary(\"md\", (mdLib) => mdLib.use(mdEmoji));\n    }\n  );\n\n  let tr = await getNewTemplateRender(\"md\", null, eleventyConfig);\n  let fn = await tr.getCompiledTemplate(\":)\");\n  t.is((await fn()).trim(), \"<p>😃</p>\");\n});\n"
  },
  {
    "path": "test/TemplateRenderNunjucksTest.js",
    "content": "import test from \"ava\";\nimport Nunjucks from \"@11ty/nunjucks\";\n\nimport TemplateRender from \"../src/TemplateRender.js\";\nimport EleventyExtensionMap from \"../src/EleventyExtensionMap.js\";\nimport TemplateEngineManager from \"../src/Engines/TemplateEngineManager.js\";\n\nimport { normalizeNewLines } from \"./Util/normalizeNewLines.js\";\nimport { getTemplateConfigInstance, getTemplateConfigInstanceCustomCallback } from \"./_testHelpers.js\";\n\nasync function getNewTemplateRender(name, inputDir, eleventyConfig) {\n  if (!eleventyConfig) {\n    eleventyConfig = await getTemplateConfigInstance({\n      dir: {\n        input: inputDir\n      }\n    });\n  }\n\n  let tr = new TemplateRender(name, eleventyConfig);\n  tr.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  tr.extensionMap.engineManager = new TemplateEngineManager(eleventyConfig);\n  tr.extensionMap.setFormats([]);\n  await tr.init();\n  return tr;\n}\n\nasync function getPromise(resolveTo) {\n  return new Promise(function (resolve) {\n    setTimeout(function () {\n      resolve(resolveTo);\n    });\n  });\n}\n\n// Nunjucks\ntest(\"Nunjucks\", async (t) => {\n  let tr = await getNewTemplateRender(\"njk\");\n  t.is(tr.getEngineName(), \"njk\");\n});\n\ntest(\"Nunjucks Render\", async (t) => {\n  let tr = await getNewTemplateRender(\"njk\");\n  let fn = await tr.getCompiledTemplate(\"<p>{{ name }}</p>\");\n  t.is(await fn({ name: \"Zach\" }), \"<p>Zach</p>\");\n});\n\ntest(\"Nunjucks Render Addition\", async (t) => {\n  let tr = await getNewTemplateRender(\"njk\");\n  let fn = await tr.getCompiledTemplate(\"<p>{{ number + 1 }}</p>\");\n  t.is(await fn({ number: 1 }), \"<p>2</p>\");\n});\n\ntest(\"Nunjucks Render Extends\", async (t) => {\n  let tr = await getNewTemplateRender(\"njk\", \"test/stubs\");\n  let fn = await tr.getCompiledTemplate(\n    \"{% extends 'base.njk' %}{% block content %}This is a child.{% endblock %}\"\n  );\n  t.is(await fn(), \"<p>This is a child.</p>\");\n});\n\ntest(\"Nunjucks Render Relative Extends\", async (t) => {\n  let tr = await getNewTemplateRender(\n    \"./test/stubs/njk-relative/dir/does_not_exist_and_thats_ok.njk\",\n    \"test/stubs\"\n  );\n  let fn = await tr.getCompiledTemplate(\n    \"{% extends '../dir/base.njk' %}{% block content %}This is a child.{% endblock %}\"\n  );\n  t.is(await fn(), \"<p>This is a child.</p>\");\n});\n\ntest(\"Nunjucks Render Include\", async (t) => {\n  let tr = await getNewTemplateRender(\"njk\", \"test/stubs\");\n  let fn = await tr.getCompiledTemplate(\"<p>{% include 'included.njk' %}</p>\");\n  t.is(await fn(), \"<p>This is an include.</p>\");\n});\n\ntest(\"Nunjucks Render Include (different extension)\", async (t) => {\n  let tr = await getNewTemplateRender(\"njk\", \"test/stubs\");\n  let fn = await tr.getCompiledTemplate(\"<p>{% include 'included.nunj' %}</p>\");\n  t.is(await fn(), \"<p>Nunjabusiness</p>\");\n});\n\ntest(\"Nunjucks Render Include (different extension, subdir)\", async (t) => {\n  let tr = await getNewTemplateRender(\"njk\", \"test/stubs\");\n  let fn = await tr.getCompiledTemplate(\"<p>{% include 'subfolder/included.nunj' %}</p>\");\n  t.is(await fn(), \"<p>Nunjabusiness2</p>\");\n});\n\ntest(\"Nunjucks Render Relative Include Issue #190\", async (t) => {\n  let tr = await getNewTemplateRender(\n    \"./test/stubs/njk-relative/does_not_exist_and_thats_ok.njk\",\n    \"./test/stubs\"\n  );\n  let fn = await tr.getCompiledTemplate(\"<p>{% include './dir/included.njk' %}</p>\");\n  t.is(await fn(), \"<p>HELLO FROM THE OTHER SIDE.</p>\");\n});\n\ntest(\"Nunjucks Render Relative Include (using ..) Issue #190\", async (t) => {\n  let tr = await getNewTemplateRender(\n    \"./test/stubs/njk-relative/dir/does_not_exist_and_thats_ok.njk\",\n    \"./test/stubs\"\n  );\n  let fn = await tr.getCompiledTemplate(\"<p>{% include '../dir/included.njk' %}</p>\");\n  t.is(await fn(), \"<p>HELLO FROM THE OTHER SIDE.</p>\");\n\n  // should look in _includes too, related to Issue #633\n  let fn2a = await tr.getCompiledTemplate(\"<p>{% include 'included-relative.njk' %}</p>\");\n  t.is(await fn2a(), \"<p>akdlsjafkljdskl</p>\");\n\n  // should look in _includes too Issue #633\n  // let fn3 = await tr.getCompiledTemplate(\n  //   \"<p>{% include '../_includes/included-relative.njk' %}</p>\"\n  // );\n  // t.is(await fn3(), \"<p>akdlsjafkljdskl</p>\");\n});\n\ntest(\"Nunjucks Render Relative Include (using current dir) Issue #190\", async (t) => {\n  let tr = await getNewTemplateRender(\n    \"./test/stubs/njk-relative/dir/does_not_exist_and_thats_ok.njk\",\n    \"./test/stubs\"\n  );\n  let fn = await tr.getCompiledTemplate(\"<p>{% include './included.njk' %}</p>\");\n  t.is(await fn(), \"<p>HELLO FROM THE OTHER SIDE.</p>\");\n\n  // This fails because ./ doesn’t look in _includes (this is good)\n  // let fn = await tr.getCompiledTemplate(\n  //   \"<p>{% include './included-relative.njk' %}</p>\"\n  // );\n  // t.is(await fn(), \"<p>akdlsjafkljdskl</p>\");\n});\n\ntest(\"Nunjucks Render Relative Include (ambiguous path, file exists in _includes and in current dir) Issue #190\", async (t) => {\n  let tr = await getNewTemplateRender(\n    \"./test/stubs/njk-relative/dir/does_not_exist_and_thats_ok.njk\",\n    \"./test/stubs\"\n  );\n  let fn = await tr.getCompiledTemplate(\n    // should prefer to use _includes first\n    // more specifically, this will not use the current dir at all.\n    \"<p>{% include 'included.njk' %}</p>\"\n  );\n  t.is(await fn(), \"<p>This is an include.</p>\");\n\n  // This fails, a leading dot is required for a relative include\n  // let tr2 = getNewTemplateRender(\"./test/stubs/njk-relative/dir/does_not_exist_and_thats_ok.njk\", \"./test/stubs\");\n  // let fn2 = await tr.getCompiledTemplate(\n  //   \"<p>{% include 'unique-include-123.njk' %}</p>\"\n  // );\n  // t.is(await fn2(), \"<p>HELLO FROM THE OTHER SIDE.</p>\");\n});\n\ntest(\"Nunjucks Async Filter\", async (t) => {\n  let tr = await getNewTemplateRender(\"njk\", \"test/stubs\");\n  let engine = tr.engine;\n  engine.addFilters(\n    {\n      myAsyncFilter: function (value, callback) {\n        setTimeout(function () {\n          callback(null, `HI${value}`);\n        }, 100);\n      },\n    },\n    true\n  );\n  let fn = await tr.getCompiledTemplate(\"{{ 'test' | myAsyncFilter }}\");\n  t.is((await fn()).trim(), \"HItest\");\n});\n\ntest(\"Nunjucks Render set with a filter\", async (t) => {\n  let tr = await getNewTemplateRender(\"njk\", \"test/stubs\");\n  let engine = tr.engine;\n  engine.addFilters({\n    uppercase: function (str) {\n      return str.toUpperCase();\n    },\n  });\n  let fn = await tr.getCompiledTemplate(`{% set test = \"hi\" | uppercase %}{{ test }}`);\n  t.is((await fn()).trim(), `HI`);\n});\n\ntest(\"Nunjucks Render Include a JS file (Issue 398)\", async (t) => {\n  let tr = await getNewTemplateRender(\"njk\", \"test/stubs\");\n  let engine = tr.engine;\n  engine.addFilters({\n    jsmin: function (str) {\n      return str;\n    },\n  });\n  let fn = await tr.getCompiledTemplate(\n    \"{% set ga %}{% include 'test.js' %}{% endset %}{{ ga | safe | jsmin }}\"\n  );\n  t.is((await fn()).trim(), `/* THIS IS A COMMENT */ alert(\"Issue #398\");`);\n});\n\ntest(\"Nunjucks Render Include Subfolder\", async (t) => {\n  let tr = await getNewTemplateRender(\"njk\", \"test/stubs\");\n  let fn = await tr.getCompiledTemplate(\"<p>{% include 'subfolder/included.html' %}</p>\");\n  t.is(await fn(), \"<p>This is an include.</p>\");\n});\n\ntest(\"Nunjucks Render Include Double Quotes\", async (t) => {\n  let tr = await getNewTemplateRender(\"njk\", \"test/stubs\");\n  let fn = await tr.getCompiledTemplate(`<p>{% include \"included.njk\" %}</p>`);\n  t.is(await fn(), \"<p>This is an include.</p>\");\n});\n\ntest(\"Nunjucks Render Include Subfolder Double Quotes\", async (t) => {\n  let tr = await getNewTemplateRender(\"njk\", \"test/stubs\");\n  let fn = await tr.getCompiledTemplate(`<p>{% include \"subfolder/included.html\" %}</p>`);\n  t.is(await fn(), \"<p>This is an include.</p>\");\n});\n\ntest(\"Nunjucks Render Imports\", async (t) => {\n  let tr = await getNewTemplateRender(\"njk\", \"test/stubs\");\n  let fn = await tr.getCompiledTemplate(\n    \"{% import 'imports.njk' as forms %}<div>{{ forms.label('Name') }}</div>\"\n  );\n  t.is(await fn(), \"<div><label>Name</label></div>\");\n});\n\ntest(\"Nunjucks Render Relative Imports\", async (t) => {\n  let tr = await getNewTemplateRender(\n    \"./test/stubs/njk-relative/dir/does_not_exist_and_thats_ok.njk\",\n    \"test/stubs\"\n  );\n  let fn = await tr.getCompiledTemplate(\n    \"{% import '../dir/imports.njk' as forms %}<div>{{ forms.label('Name') }}</div>\"\n  );\n  t.is(await fn(), \"<div><label>Name</label></div>\");\n});\n\ntest(\"Nunjucks Render Imports From\", async (t) => {\n  let tr = await getNewTemplateRender(\"njk\", \"test/stubs\");\n  let fn = await tr.getCompiledTemplate(\n    \"{% from 'imports.njk' import label %}<div>{{ label('Name') }}</div>\"\n  );\n  t.is(await fn(), \"<div><label>Name</label></div>\");\n});\n\ntest(\"Nunjucks getEngineLib\", async (t) => {\n  let tr = await getNewTemplateRender(\"njk\", \"./test/stubs/\");\n  t.truthy(tr.engine.getEngineLib());\n});\n\ntest(\"Nunjucks Render: with Library Override\", async (t) => {\n  let tr = await getNewTemplateRender(\"njk\");\n\n  let env = new Nunjucks.Environment(new Nunjucks.FileSystemLoader(\"./test/stubs/_includes/\"));\n  tr.engine.setLibrary(env);\n\n  let fn = await tr.getCompiledTemplate(\"<p>{{ name }}</p>\");\n  t.is(await fn({ name: \"Zach\" }), \"<p>Zach</p>\");\n});\n\ntest(\"Nunjucks Render with getGlobals Issue #567\", async (t) => {\n  let tr = await getNewTemplateRender(\"njk\");\n  let env = tr.engine.getEngineLib();\n  env.addGlobal(\"getGlobals\", function () {\n    return this.getVariables();\n  });\n\n  let fn = await tr.getCompiledTemplate(\"<p>{{ getGlobals()['my-global-name'] }}</p>\");\n  t.is(await fn({ \"my-global-name\": \"Zach\" }), \"<p>Zach</p>\");\n});\n\ntest(\"Nunjucks Render with getVarFromString Filter Issue #567\", async (t) => {\n  let tr = await getNewTemplateRender(\"njk\");\n  let env = tr.engine.getEngineLib();\n  env.addFilter(\"getVarFromString\", function (varName) {\n    return this.getVariables()[varName];\n  });\n\n  let fn = await tr.getCompiledTemplate(\"<p>{{ 'my-global-name' | getVarFromString }}</p>\");\n  t.is(await fn({ \"my-global-name\": \"Zach\" }), \"<p>Zach</p>\");\n});\n\ntest(\"Nunjucks Shortcode without args #372\", async (t) => {\n  let tr = await getNewTemplateRender(\"njk\", \"./test/stubs/\");\n  tr.engine.addShortcode(\"postfixWithZach\", function (arg1) {\n    return arg1 + \"Zach\";\n  });\n\n  t.is(await tr._testRender(\"{% postfixWithZach %}\", {}), \"undefinedZach\");\n});\n\ntest(\"Nunjucks Shortcode\", async (t) => {\n  t.plan(3);\n\n  let tr = await getNewTemplateRender(\"njk\", \"./test/stubs/\");\n  tr.engine.addShortcode(\"postfixWithZach\", function (str) {\n    // Data in context\n    t.is(this.page.url, \"/hi/\");\n    // sanity check that all data is not carried forward\n    t.not(this.name, \"test\");\n\n    return str + \"Zach\";\n  });\n\n  t.is(\n    await tr._testRender(\"{% postfixWithZach name %}\", {\n      name: \"test\",\n      page: {\n        url: \"/hi/\",\n      },\n    }),\n    \"testZach\"\n  );\n});\n\ntest(\"Nunjucks Async Shortcode\", async (t) => {\n  t.plan(2);\n\n  let tr = await getNewTemplateRender(\"njk\", \"./test/stubs/\");\n  tr.engine.addShortcode(\n    \"postfixWithZach\",\n    async function (str) {\n      // Data in context\n      t.is(this.page.url, \"/hi/\");\n\n      return new Promise(function (resolve) {\n        setTimeout(function () {\n          resolve(str + \"Zach\");\n        });\n      });\n    },\n    true\n  );\n\n  t.is(\n    await tr._testRender(\"{% postfixWithZach name %}\", {\n      name: \"test\",\n      page: {\n        url: \"/hi/\",\n      },\n    }),\n    \"testZach\"\n  );\n});\n\ntest(\"Nunjucks Async function Shortcode\", async (t) => {\n  t.plan(2);\n\n  let tr = await getNewTemplateRender(\"njk\", \"./test/stubs/\");\n  tr.engine.addShortcode(\n    \"postfixWithZach\",\n    async function (str) {\n      // Data in context\n      t.is(this.page.url, \"/hi/\");\n\n      return await getPromise(str + \"Zach\");\n    },\n    true\n  );\n\n  t.is(\n    await tr._testRender(\"{% postfixWithZach name %}\", {\n      name: \"test\",\n      page: {\n        url: \"/hi/\",\n      },\n    }),\n    \"testZach\"\n  );\n});\n\ntest(\"Nunjucks sync function Shortcode (error throwing)\", async (t) => {\n  let tr = await getNewTemplateRender(\"njk\", \"./test/stubs/\");\n  tr.engine.addShortcode(\n    \"postfixWithZach\",\n    function (str) {\n      throw new Error(\"Nunjucks sync function Shortcode (error throwing)\");\n    },\n    false\n  );\n\n  let error = await t.throwsAsync(async () => {\n    await tr._testRender(\"{% postfixWithZach name %}\", { name: \"test\" });\n  });\n\n  t.true(\n    error.message.indexOf(\n      \"EleventyNunjucksError: Error with Nunjucks shortcode `postfixWithZach`\"\n    ) > -1\n  );\n  t.true(\n    error.cause.message.startsWith(\n      \"Error with Nunjucks shortcode `postfixWithZach`\"\n    )\n  );\n  t.true(\n    error.cause.originalError.message.startsWith(\n      \"Nunjucks sync function Shortcode (error throwing)\"\n    )\n  );\n});\n\ntest(\"Nunjucks Async function Shortcode (error throwing)\", async (t) => {\n  let tr = await getNewTemplateRender(\"njk\", \"./test/stubs/\");\n  tr.engine.addShortcode(\n    \"postfixWithZachError\",\n    async function (str) {\n      throw new Error(\"Nunjucks Async function Shortcode (error throwing)\");\n    },\n    true\n  );\n\n  let error = await t.throwsAsync(async () => {\n    await tr._testRender(\"{% postfixWithZachError name %}\", { name: \"test\" });\n  });\n  t.true(\n    error.message.indexOf(\n      \"EleventyNunjucksError: Error with Nunjucks shortcode `postfixWithZachError`\"\n    ) > -1\n  );\n  t.true(\n    error.cause.message.startsWith(\n      \"Error with Nunjucks shortcode `postfixWithZachError`\"\n    )\n  );\n  t.true(\n    error.cause.originalError.message.startsWith(\n      \"Nunjucks Async function Shortcode (error throwing)\"\n    )\n  );\n});\n\ntest(\"Nunjucks sync function paired Shortcode (error throwing)\", async (t) => {\n  let tr = await getNewTemplateRender(\"njk\", \"./test/stubs/\");\n  tr.engine.addPairedShortcode(\n    \"postfixWithZachError\",\n    function (str) {\n      throw new Error(\n        \"Nunjucks sync function paired Shortcode (error throwing)\"\n      );\n    },\n    false\n  );\n\n  let error = await t.throwsAsync(async () => {\n    await tr._testRender(\"{% postfixWithZachError name %}hi{% endpostfixWithZachError %}\", {\n      name: \"test\",\n    });\n  });\n\n  t.true(\n    error.message.indexOf(\n      \"EleventyNunjucksError: Error with Nunjucks paired shortcode `postfixWithZachError`\"\n    ) > -1\n  );\n  t.true(\n    error.cause.message.startsWith(\n      \"Error with Nunjucks paired shortcode `postfixWithZachError`\"\n    )\n  );\n  t.true(\n    error.cause.originalError.message.startsWith(\n      \"Nunjucks sync function paired Shortcode (error throwing)\"\n    )\n  );\n});\n\ntest(\"Nunjucks Async function paired Shortcode (error throwing)\", async (t) => {\n  let tr = await getNewTemplateRender(\"njk\", \"./test/stubs/\");\n  tr.engine.addPairedShortcode(\n    \"postfixWithZachError\",\n    async function (str) {\n      throw new Error(\n        \"Nunjucks Async function paired Shortcode (error throwing)\"\n      );\n    },\n    true\n  );\n\n  let error = await t.throwsAsync(async () => {\n    await tr._testRender(\"{% postfixWithZachError name %}hi{% endpostfixWithZachError %}\", {\n      name: \"test\",\n    });\n  });\n\n  t.true(\n    error.message.indexOf(\n      \"EleventyNunjucksError: Error with Nunjucks paired shortcode `postfixWithZachError`\"\n    ) > -1\n  );\n  t.true(\n    error.cause.message.startsWith(\n      \"Error with Nunjucks paired shortcode `postfixWithZachError`\"\n    )\n  );\n  t.true(\n    error.cause.originalError.message.startsWith(\n      \"Nunjucks Async function paired Shortcode (error throwing)\"\n    )\n  );\n});\n\ntest(\"Nunjucks Shortcode Safe Output\", async (t) => {\n  t.plan(2);\n\n  let tr = await getNewTemplateRender(\"njk\", \"./test/stubs/\");\n  tr.engine.addShortcode(\"postfixWithZach\", function (str) {\n    // Data in context\n    t.is(this.page.url, \"/hi/\");\n\n    return `<span>${str}</span>`;\n  });\n\n  t.is(\n    await tr._testRender(\"{% postfixWithZach name %}\", {\n      name: \"test\",\n      page: {\n        url: \"/hi/\",\n      },\n    }),\n    \"<span>test</span>\"\n  );\n});\n\ntest(\"Nunjucks Shortcode return non-string value\", async (t) => {\n  let tr = await getNewTemplateRender(\"njk\", \"./test/stubs/\");\n  tr.engine.addShortcode(\"getYear\", function () {\n    return 2022;\n  });\n\n  t.is(await tr._testRender(\"{% getYear %}\"), \"2022\");\n});\n\ntest(\"Nunjucks Paired Shortcode\", async (t) => {\n  t.plan(2);\n\n  let tr = await getNewTemplateRender(\"njk\", \"./test/stubs/\");\n  tr.engine.addPairedShortcode(\"postfixWithZach\", function (content, str) {\n    // Data in context\n    t.is(this.page.url, \"/hi/\");\n\n    return str + content + \"Zach\";\n  });\n\n  t.is(\n    await tr._testRender(\"{% postfixWithZach name %}Content{% endpostfixWithZach %}\", {\n      name: \"test\",\n      page: {\n        url: \"/hi/\",\n      },\n    }),\n    \"testContentZach\"\n  );\n});\n\ntest(\"Nunjucks Async Paired Shortcode\", async (t) => {\n  t.plan(2);\n\n  let tr = await getNewTemplateRender(\"njk\", \"./test/stubs/\");\n  tr.engine.addPairedShortcode(\n    \"postfixWithZach\",\n    function (content, str) {\n      // Data in context\n      t.is(this.page.url, \"/hi/\");\n\n      return new Promise(function (resolve) {\n        setTimeout(function () {\n          resolve(str + content + \"Zach\");\n        });\n      });\n    },\n    true\n  );\n\n  t.is(\n    await tr._testRender(\"{% postfixWithZach name %}Content{% endpostfixWithZach %}\", {\n      name: \"test\",\n      page: {\n        url: \"/hi/\",\n      },\n    }),\n    \"testContentZach\"\n  );\n});\n\ntest(\"Nunjucks Nested Async Paired Shortcode\", async (t) => {\n  t.plan(3);\n\n  let tr = await getNewTemplateRender(\"njk\", \"./test/stubs/\");\n  tr.engine.addPairedShortcode(\n    \"postfixWithZach\",\n    function (content, str) {\n      // Data in context\n      t.is(this.page.url, \"/hi/\");\n\n      return new Promise(function (resolve) {\n        setTimeout(function () {\n          resolve(str + content + \"Zach\");\n        });\n      });\n    },\n    true\n  );\n\n  t.is(\n    await tr._testRender(\n      \"{% postfixWithZach name %}Content{% postfixWithZach name2 %}Content{% endpostfixWithZach %}{% endpostfixWithZach %}\",\n      {\n        name: \"test\",\n        name2: \"test2\",\n        page: {\n          url: \"/hi/\",\n        },\n      }\n    ),\n    \"testContenttest2ContentZachZach\"\n  );\n});\n\ntest(\"Nunjucks Paired Shortcode without args #372\", async (t) => {\n  let tr = await getNewTemplateRender(\"njk\", \"./test/stubs/\");\n  tr.engine.addPairedShortcode(\"postfixWithZach\", function (content, arg1) {\n    // Data in context\n    t.is(this.page.url, \"/hi/\");\n\n    return content + arg1 + \"Zach\";\n  });\n\n  t.is(\n    await tr._testRender(\"{% postfixWithZach %}Content{% endpostfixWithZach %}\", {\n      name: \"test\",\n      page: {\n        url: \"/hi/\",\n      },\n    }),\n    \"ContentundefinedZach\"\n  );\n});\n\ntest(\"Nunjucks Paired Shortcode with Tag Inside\", async (t) => {\n  t.plan(2);\n\n  let tr = await getNewTemplateRender(\"njk\", \"./test/stubs/\");\n  tr.engine.addPairedShortcode(\"postfixWithZach\", function (content, str) {\n    // Data in context\n    t.is(this.page.url, \"/hi/\");\n\n    return str + content + \"Zach\";\n  });\n\n  t.is(\n    await tr._testRender(\n      \"{% postfixWithZach name %}Content{% if tester %}If{% endif %}{% endpostfixWithZach %}\",\n      {\n        name: \"test\",\n        tester: true,\n        page: {\n          url: \"/hi/\",\n        },\n      }\n    ),\n    \"testContentIfZach\"\n  );\n});\n\ntest(\"Nunjucks Nested Paired Shortcode\", async (t) => {\n  t.plan(3);\n\n  let tr = await getNewTemplateRender(\"njk\", \"./test/stubs/\");\n  tr.engine.addPairedShortcode(\"postfixWithZach\", function (content, str) {\n    // Data in context\n    t.is(this.page.url, \"/hi/\");\n\n    return str + content + \"Zach\";\n  });\n\n  t.is(\n    await tr._testRender(\n      \"{% postfixWithZach name %}Content{% postfixWithZach name2 %}Content{% endpostfixWithZach %}{% endpostfixWithZach %}\",\n      {\n        name: \"test\",\n        name2: \"test2\",\n        page: {\n          url: \"/hi/\",\n        },\n      }\n    ),\n    \"testContenttest2ContentZachZach\"\n  );\n});\n\ntest(\"Nunjucks Shortcode Multiple Args\", async (t) => {\n  t.plan(2);\n\n  let tr = await getNewTemplateRender(\"njk\", \"./test/stubs/\");\n  tr.engine.addShortcode(\"postfixWithZach\", function (str, str2) {\n    // Data in context\n    t.is(this.page.url, \"/hi/\");\n\n    return str + str2 + \"Zach\";\n  });\n\n  t.is(\n    await tr._testRender(\"{% postfixWithZach name, other %}\", {\n      name: \"test\",\n      other: \"howdy\",\n      page: {\n        url: \"/hi/\",\n      },\n    }),\n    \"testhowdyZach\"\n  );\n});\n\ntest(\"Nunjucks Shortcode Multiple Args (Comma is required)\", async (t) => {\n  let tr = await getNewTemplateRender(\"njk\", \"./test/stubs/\");\n  tr.engine.addShortcode(\"postfixWithZach\", function (str, str2) {\n    return str + str2 + \"Zach\";\n  });\n\n  await t.throwsAsync(async () => {\n    await tr._testRender(\"{% postfixWithZach name other %}\", {\n      name: \"test\",\n      other: \"howdy\",\n    });\n  });\n});\n\ntest(\"Nunjucks Shortcode Named Args\", async (t) => {\n  t.plan(2);\n\n  let tr = await getNewTemplateRender(\"njk\", \"./test/stubs/\");\n  tr.engine.addShortcode(\"postfixWithZach\", function (arg) {\n    // Data in context\n    t.is(this.page.url, \"/hi/\");\n\n    return arg.arg1 + arg.arg2 + \"Zach\";\n  });\n\n  t.is(\n    await tr._testRender(\"{% postfixWithZach arg1=name, arg2=other %}\", {\n      name: \"test\",\n      other: \"howdy\",\n      page: {\n        url: \"/hi/\",\n      },\n    }),\n    \"testhowdyZach\"\n  );\n});\n\ntest(\"Nunjucks Shortcode Named Args (Reverse Order)\", async (t) => {\n  t.plan(2);\n\n  let tr = await getNewTemplateRender(\"njk\", \"./test/stubs/\");\n  tr.engine.addShortcode(\"postfixWithZach\", function (arg) {\n    // Data in context\n    t.is(this.page.url, \"/hi/\");\n\n    return arg.arg1 + arg.arg2 + \"Zach\";\n  });\n\n  t.is(\n    await tr._testRender(\"{% postfixWithZach arg2=other, arg1=name %}\", {\n      name: \"test\",\n      other: \"howdy\",\n      page: {\n        url: \"/hi/\",\n      },\n    }),\n    \"testhowdyZach\"\n  );\n});\n\ntest(\"Nunjucks Shortcode Named Args (JS notation)\", async (t) => {\n  t.plan(2);\n\n  let tr = await getNewTemplateRender(\"njk\", \"./test/stubs/\");\n  tr.engine.addShortcode(\"postfixWithZach\", function (arg) {\n    // Data in context\n    t.is(this.page.url, \"/hi/\");\n\n    return arg.arg1 + arg.arg2 + \"Zach\";\n  });\n\n  t.is(\n    await tr._testRender(\"{% postfixWithZach { arg1: name, arg2: other } %}\", {\n      name: \"test\",\n      other: \"howdy\",\n      page: {\n        url: \"/hi/\",\n      },\n    }),\n    \"testhowdyZach\"\n  );\n});\n\ntest(\"Nunjucks Test if statements on arrays (Issue #524)\", async (t) => {\n  let tr = await getNewTemplateRender(\"njk\", \"./test/stubs/\");\n\n  t.is(\n    await tr._testRender(\"{% if 'first' in tags %}Success.{% endif %}\", {\n      tags: [\"first\", \"second\"],\n    }),\n    \"Success.\"\n  );\n\n  t.is(\n    await tr._testRender(\"{% if 'sdfsdfs' in tags %}{% else %}Success.{% endif %}\", {\n      tags: [\"first\", \"second\"],\n    }),\n    \"Success.\"\n  );\n\n  t.is(\n    await tr._testRender(\"{% if false %}{% elseif 'first' in tags %}Success.{% endif %}\", {\n      tags: [\"first\", \"second\"],\n    }),\n    \"Success.\"\n  );\n\n  t.is(\n    await tr._testRender(\"{% if tags.includes('first') %}Success.{% endif %}\", {\n      tags: [\"first\", \"second\"],\n    }),\n    \"Success.\"\n  );\n\n  t.is(\n    await tr._testRender(\"{% if tags.includes('dsds') %}{% else %}Success.{% endif %}\", {\n      tags: [\"first\", \"second\"],\n    }),\n    \"Success.\"\n  );\n\n  t.is(\n    await tr._testRender(\"{% if false %}{% elseif tags.includes('first') %}Success.{% endif %}\", {\n      tags: [\"first\", \"second\"],\n    }),\n    \"Success.\"\n  );\n});\n\ntest(\"Issue 611: Run a function\", async (t) => {\n  // This does not work in Liquid\n  let tr = await getNewTemplateRender(\"njk\", \"./test/stubs/\");\n\n  t.is(\n    await tr._testRender(\"{{ test() }}\", {\n      test: function () {\n        return \"alkdsjfksljaZach\";\n      },\n    }),\n    \"alkdsjfksljaZach\"\n  );\n});\n\ntest(\"Nunjucks bypass compilation\", async (t) => {\n  let tr = await getNewTemplateRender(\"njk\");\n\n  t.is(tr.engine.needsCompilation(\"<p>{{ me }}</p>\"), true);\n  t.is(tr.engine.needsCompilation(\"<p>{% tag %}{% endtag %}</p>\"), true);\n  t.is(tr.engine.needsCompilation(\"<p>test</p>\"), false);\n});\n\ntest(\"Nunjucks Parse for Symbols\", async (t) => {\n  let tr = await getNewTemplateRender(\"njk\");\n  let engine = tr.engine;\n\n  t.deepEqual(engine.parseForSymbols(\"<p>{{ name }}</p>\"), [\"name\"]);\n  t.deepEqual(engine.parseForSymbols(\"<p>{{ eleventy.deep.nested }}</p>\"), [\n    \"eleventy.deep.nested\",\n  ]);\n  t.deepEqual(engine.parseForSymbols(\"<p>{{ a }} {{ b }}</p>\"), [\"a\", \"b\"]);\n  t.deepEqual(engine.parseForSymbols(\"<p>{% if true %}{{ c }}{% endif %}</p>\"), [\"c\"]);\n  t.deepEqual(engine.parseForSymbols(\"<p>{% if false %}{{ c }}{% endif %}</p>\"), [\"c\"]);\n  t.deepEqual(engine.parseForSymbols(\"{{ collections.all[0] }}>\"), [\n    // Note that the Liquid parser returns collections.all[0]\n    \"collections.all\",\n  ]);\n  t.deepEqual(engine.parseForSymbols(\"{{ collections.mine }}>\"), [\"collections.mine\"]);\n\n  t.deepEqual(engine.parseForSymbols(\"{{ collections.mine | test }}>\"), [\n    // TODO not ideal to have `test` in here?\n    \"test\",\n    \"collections.mine\",\n  ]);\n});\n\ntest(\"Nunjucks Parse for Symbols with custom block\", async (t) => {\n  let tr = await getNewTemplateRender(\"njk\");\n  let engine = tr.engine;\n  engine.config.nunjucksShortcodes.test = function () {};\n\n  t.deepEqual(engine.parseForSymbols(\"<p>{{ name }} {% test %}</p>\"), [\"name\"]);\n});\n\ntest(\"Use addNunjucksGlobal with function\", async (t) => {\n  let templateConfig = await getTemplateConfigInstanceCustomCallback(\n    {},\n    function(cfg) {\n      cfg.addNunjucksGlobal(\"fortytwo\", function () {\n        return 42;\n      });\n    }\n  );\n\n  let tr = await getNewTemplateRender(\"njk\", null, templateConfig);\n\n  let fn = await tr.getCompiledTemplate(\"<p>{{ fortytwo() }}</p>\");\n  t.is(await fn(), \"<p>42</p>\");\n});\n\ntest(\"Use addNunjucksGlobal with literal\", async (t) => {\n  let templateConfig = await getTemplateConfigInstanceCustomCallback(\n    {},\n    function(cfg) {\n      cfg.addNunjucksGlobal(\"fortytwo\", 42);\n    }\n  );\n\n  let tr = await getNewTemplateRender(\"njk\", null, templateConfig);\n\n  let fn = await tr.getCompiledTemplate(\"<p>{{ fortytwo }}</p>\");\n  t.is(await fn(), \"<p>42</p>\");\n});\n\n// Async not supported here\ntest.skip(\"Use addNunjucksGlobal with async function\", async (t) => {\n  let templateConfig = await getTemplateConfigInstanceCustomCallback(\n    {},\n    function(cfg) {\n      cfg.addNunjucksGlobal(\"fortytwo\", getPromise(42));\n    }\n  );\n\n  let tr = await getNewTemplateRender(\"njk\", null, templateConfig);\n\n  let fn = await tr.getCompiledTemplate(\"<p>{{ fortytwo() }}</p>\");\n  t.is(await fn(), \"<p>42</p>\");\n});\n\ntest(\"Use config driven Nunjucks Environment Options (throws on undefined variable)\", async (t) => {\n  let templateConfig = await getTemplateConfigInstanceCustomCallback(\n    {},\n    function(cfg) {\n      cfg.setNunjucksEnvironmentOptions({\n        throwOnUndefined: true,\n      });\n    }\n  );\n\n  let tr = await getNewTemplateRender(\"njk\", null, templateConfig);\n\n  let fn = await tr.getCompiledTemplate(\"<p>   {{ test }}</p>\");\n  await t.throwsAsync(async () => {\n    await fn({});\n  });\n});\n\ntest(\"Use config driven Nunjucks Environment Options (autoescape)\", async (t) => {\n  let templateConfig = await getTemplateConfigInstanceCustomCallback(\n    {},\n    function(cfg) {\n      cfg.setNunjucksEnvironmentOptions({\n        autoescape: false,\n      });\n    }\n  );\n\n  let tr = await getNewTemplateRender(\"njk\", null, templateConfig);\n\n  let fn = await tr.getCompiledTemplate(\"<p>{{ test }}</p>\");\n  t.is(\n    await fn({\n      test: \"<b>Hi</b>\",\n    }),\n    \"<p><b>Hi</b></p>\"\n  );\n});\n\ntest(\"Nunjucks Shortcode in a loop (everything is sync)\", async (t) => {\n  let templateConfig = await getTemplateConfigInstanceCustomCallback(\n    {\n      input: \"test/stubs-njk-async/\"\n    },\n    function(cfg) {\n      cfg.addNunjucksShortcode(\"genericshortcode\", function (str) {\n        return str;\n      });\n    }\n  );\n\n  let tr = await getNewTemplateRender(\"njk\", \"./test/stubs-njk-async/\", templateConfig);\n\n  let fn = await tr.getCompiledTemplate(\n    \"{% for item in list %}{% include 'loop.njk' %}{% endfor %}\"\n  );\n\n  t.is(\n    await fn({\n      list: [\"a\", \"b\", \"c\"],\n    }),\n    \"included_a-aincluded_b-bincluded_c-c\"\n  );\n});\n\n// TODO!\ntest.skip(\"Weird issue with number arguments in a loop (not parsing literals properly?)\", async (t) => {\n  let templateConfig = await getTemplateConfigInstanceCustomCallback(\n    {\n      input: \"test/stubs-njk-async/\"\n    },\n    function(cfg) {\n      cfg.addNunjucksShortcode(\"genericshortcode\", function (str) {\n        return str;\n      });\n    }\n  );\n\n  let tr = await getNewTemplateRender(\"njk\", \"./test/stubs-njk-async/\", templateConfig);\n  let fn = await tr.getCompiledTemplate(\n    \"{% for item in list %}{{item}}-{% genericshortcode item %}{% endfor %}\"\n  );\n\n  t.is(\n    await fn({\n      list: [1, 2, 3],\n    }),\n    \"1-12-23-3\"\n  );\n});\n\ntest(\"Use a precompiled Nunjucks template\", async (t) => {\n  // custom loader object\n\n  let templateConfig = await getTemplateConfigInstanceCustomCallback(\n    {},\n    function(cfg) {\n      cfg.setNunjucksPrecompiledTemplates({\n        \"RenderDirect:BQAPaWxMHTxOqfCSB_bEoWTvtWt-obPbnZTUznRl9LA\": (function () {\n          function root(env, context, frame, runtime, cb) {\n            var lineno = 0;\n            var colno = 0;\n            var output = \"\";\n            try {\n              var parentTemplate = null;\n              var t_1;\n              t_1 = 34;\n              frame.set(\"nunjucksVar\", t_1, true);\n              if (frame.topLevel) {\n                context.setVariable(\"nunjucksVar\", t_1);\n              }\n              if (frame.topLevel) {\n                context.addExport(\"nunjucksVar\", t_1);\n              }\n              output += runtime.suppressValue(\n                runtime.contextOrFrameLookup(context, frame, \"hi\"),\n                env.opts.autoescape\n              );\n              output += \"\\n\";\n              output += runtime.suppressValue(\n                runtime.contextOrFrameLookup(context, frame, \"nunjucksVar\"),\n                env.opts.autoescape\n              );\n              if (parentTemplate) {\n                parentTemplate.rootRenderFunc(env, context, frame, runtime, cb);\n              } else {\n                cb(null, output);\n              }\n            } catch (e) {\n              cb(runtime.handleError(e, lineno, colno));\n            }\n          }\n          return {\n            root: root,\n          };\n        })(),\n      });\n    }\n  );\n  await templateConfig.init();\n\n  let tr = await getNewTemplateRender(\"njk\", null, templateConfig);\n\n  // Just pass a unique key in here if you’re using precompiled templates via config.\n  let fn = await tr.getCompiledTemplate(\"RenderDirect:BQAPaWxMHTxOqfCSB_bEoWTvtWt-obPbnZTUznRl9LA\");\n  t.is(\n    normalizeNewLines(\n      await fn({\n        hi: \"Zach\",\n      })\n    ),\n    `Zach\n34`\n  );\n});\n\ntest(\"Make sure addFilter is async-friendly for Nunjucks\", async (t) => {\n  let templateConfig = await getTemplateConfigInstanceCustomCallback(\n    {},\n    function(cfg) {\n      // requires async function\n      cfg.addFilter(\"fortytwo\", async function (val, val2) {\n        return getPromise(val + val2);\n      });\n    }\n  );\n\n  let tr = await getNewTemplateRender(\"njk\", null, templateConfig);\n\n  let fn = await tr.getCompiledTemplate(\"<p>{{ 10 | fortytwo(2) }}</p>\");\n  t.is(await fn(), \"<p>12</p>\");\n});\n\ntest(\"Throw an error when you return a promise in addFilter for Nunjucks\", async (t) => {\n  let templateConfig = await getTemplateConfigInstanceCustomCallback(\n    {},\n    function(cfg) {\n      // requires async function\n      cfg.addFilter(\"fortytwo\", function () {\n        return getPromise(42);\n      });\n    }\n  );\n\n  let tr = await getNewTemplateRender(\"njk\", null, templateConfig);\n  let fn = await tr.getCompiledTemplate(\"<p>{{ 'hi' | fortytwo }}</p>\");\n  await t.throwsAsync(fn);\n});\n\ntest(\"addAsyncFilter for Nunjucks\", async (t) => {\n  let templateConfig = await getTemplateConfigInstanceCustomCallback(\n    {},\n    function(cfg) {\n      // works without async function (can return promise)\n      cfg.addAsyncFilter(\"fortytwo\", function (val, val2) {\n        return getPromise(val + val2);\n      });\n    }\n  );\n\n  let tr = await getNewTemplateRender(\"njk\", null, templateConfig);\n\n  let fn = await tr.getCompiledTemplate(\"<p>{{ 10 | fortytwo(2) }}</p>\");\n  t.is(await fn(), \"<p>12</p>\");\n});\n\ntest(\"Asynchronous filters (via addNunjucksFilter) for Nunjucks\", async (t) => {\n  let templateConfig = await getTemplateConfigInstanceCustomCallback(\n    {},\n    function(cfg) {\n      // works without async function (can return promise)\n      cfg.addNunjucksFilter(\n        \"fortytwo\",\n        function (value1, value2, callback) {\n          setTimeout(function () {\n            callback(null, value1 + value2);\n          }, 100);\n        },\n        true\n      );\n    }\n  );\n\n  let tr = await getNewTemplateRender(\"njk\", null, templateConfig);\n\n  let fn = await tr.getCompiledTemplate(\"<p>{{ 10 | fortytwo(2) }}</p>\");\n  t.is(await fn(), \"<p>12</p>\");\n});\n\ntest(\"Asynchronous filters (via addNunjucksAsyncFilter) for Nunjucks\", async (t) => {\n  let templateConfig = await getTemplateConfigInstanceCustomCallback(\n    {},\n    function(cfg) {\n      // works without async function (can return promise)\n      cfg.addNunjucksAsyncFilter(\"fortytwo\", function (value1, value2, callback) {\n        setTimeout(function () {\n          callback(null, value1 + value2);\n        }, 100);\n      });\n    }\n  );\n\n  let tr = await getNewTemplateRender(\"njk\", null, templateConfig);\n\n  let fn = await tr.getCompiledTemplate(\"<p>{{ 10 | fortytwo(2) }}</p>\");\n  t.is(await fn(), \"<p>12</p>\");\n});\n\ntest(\"Nunjucks Shortcode with this.env #3175\", async (t) => {\n  t.plan(2);\n  let tr = await getNewTemplateRender(\"njk\", \"./test/stubs/\");\n  tr.engine.addShortcode(\"postfixWithZach\", function () {\n    t.truthy(this.env);\n    return \"Zach\";\n  });\n\n  t.is(await tr._testRender(\"{% postfixWithZach %}\", {}), \"Zach\");\n});\n"
  },
  {
    "path": "test/TemplateRenderPluginTest.js",
    "content": "import test from \"ava\";\n\nimport {\n  default as RenderPlugin,\n  File as RenderPluginFile,\n  String as RenderPluginString,\n  RenderManager,\n} from \"../src/Plugins/RenderPlugin.js\";\nimport Eleventy from \"../src/Eleventy.js\";\n\nimport { normalizeNewLines } from \"./Util/normalizeNewLines.js\";\n\nasync function getTestOutput(input, configCallback = function () {}) {\n  let elev = new Eleventy(input, \"./_site/\", {\n    config: function (eleventyConfig) {\n      eleventyConfig.addPlugin(RenderPlugin);\n      configCallback(eleventyConfig);\n    },\n  });\n\n  elev.setIsVerbose(false);\n\n  // Careful with this!\n  // elev.disableLogger();\n\n  let result = await elev.toJSON();\n\n  if (!result.length) {\n    throw new Error(`No Eleventy JSON output found for input: ${input}`);\n  }\n  return result;\n}\n\nasync function getTestOutputForFile(inputFile, configCallback) {\n  let result = await getTestOutput(inputFile, configCallback);\n  let html = normalizeNewLines(result[0].content.trim());\n  return html;\n}\n\ntest(\"Use liquid in nunjucks\", async (t) => {\n  let html = await getTestOutputForFile(\"./test/stubs-render-plugin/liquid.njk\");\n  t.is(\n    html,\n    `nunjucksHi\n69\n\n\n* liquidHi test test liquidBye 138`\n  );\n});\n\ntest(\"Use liquid+markdown in 11ty.js\", async (t) => {\n  let html = await getTestOutputForFile(\"./test/stubs-render-plugin/liquid-md.11ty.cjs\");\n  t.is(\n    html,\n    `<h1>Markdown</h1>\n<ul>\n<li>2</li>\n</ul>`\n  );\n});\n\ntest(\"Use nunjucks in 11ty.js\", async (t) => {\n  let html = await getTestOutputForFile(\"./test/stubs-render-plugin/nunjucks.11ty.cjs\");\n  t.is(html, `* iHtpircsavaj`);\n});\n\n// This is not yet supported and currently throws an error.\ntest.skip(\"Use 11ty.js in liquid\", async (t) => {\n  let html = await getTestOutputForFile(\"./test/stubs-render-plugin/11tyjs.liquid\");\n  t.is(html, `TESTING`);\n});\n\ntest(\"Use nunjucks in liquid\", async (t) => {\n  let html = await getTestOutputForFile(\"./test/stubs-render-plugin/nunjucks.liquid\");\n  t.is(\n    html,\n    `* iHdiuqil\n* lfjksdlba`\n  );\n});\n\ntest(\"Use markdown in liquid\", async (t) => {\n  let html = await getTestOutputForFile(\"./test/stubs-render-plugin/md.liquid\");\n  t.is(\n    html,\n    `<h1>Hello {{ hi }}</h1>\n<ul>\n<li>Testing</li>\n</ul>`\n  );\n});\n\ntest(\"Use nunjucks file in njk (uses renderTemplate inside of the nunjucks file)\", async (t) => {\n  let html = await getTestOutputForFile(\"./test/stubs-render-plugin/njk-file.njk\");\n  t.is(\n    html,\n    `TESTING\n\nTESTING IN LIQUID\n\n* 999`\n  );\n});\n\ntest(\"Use 11ty.js file in njk\", async (t) => {\n  let html = await getTestOutputForFile(\"./test/stubs-render-plugin/11tyjs-file.njk\");\n  t.is(html, `TESTING`);\n});\n\n// 3.0 breaking change, we can’t alias to 11ty.js any more\ntest.skip(\"Breaking Change (3.0): Use txt file in njk (override to 11ty.js)\", async (t) => {\n  let html = await getTestOutputForFile(\"./test/stubs-render-plugin/11tyjs-file-override.njk\");\n  t.is(html, `TESTING`);\n});\n\n// Skip this for now, toJSON calls actually change the exitCode of the process when they error,\n// which is not ideal.\ntest.skip(\"Use nunjucks file in liquid but it doesn’t exist\", async (t) => {\n  await t.throwsAsync(async () => {\n    await getTestOutputForFile(\"./test/stubs-render-plugin/njk-file-not-exist.liquid\");\n  });\n});\n\ntest(\"No syntax passed, uses parent page syntax: liquid\", async (t) => {\n  let html = await getTestOutputForFile(\"./test/stubs-render-plugin/false.liquid\");\n  t.is(\n    html,\n    `# Hello Bruno\n* Testing`\n  );\n});\n\ntest(\"No syntax passed (uses parent page syntax), but does pass data: liquid\", async (t) => {\n  let html = await getTestOutputForFile(\"./test/stubs-render-plugin/data-no-templatelang.liquid\");\n  t.is(\n    html,\n    `# Hello Bruno\n* Testing`\n  );\n});\n\n// Not yet supported\ntest.skip(\"renderFile but the target has front matter.\", async (t) => {\n  let html = await getTestOutputForFile(\"./test/stubs-render-plugin/using-frontmatter.liquid\");\n  t.is(html, `frontmatterString`);\n});\n\n// Idea from https://twitter.com/raymondcamden/status/1460961620247650312\ntest(\"Capture nunjucks render output to a liquid variable\", async (t) => {\n  let html = await getTestOutputForFile(\"./test/stubs-render-plugin/capture-njk.liquid\");\n  t.is(html, `4`);\n});\n\n// Idea from https://twitter.com/raymondcamden/status/1460961620247650312\n// Possibly blocked by async in {% set %} https://github.com/mozilla/nunjucks/issues/815\ntest(\"Capture liquid render output to a njk variable\", async (t) => {\n  let html = await getTestOutputForFile(\"./test/stubs-render-plugin/capture-liquid.njk\");\n  t.is(html, `4`);\n});\n\ntest(\"Remap non-object data to data._ if object is not passed in\", async (t) => {\n  let html = await getTestOutputForFile(\"./test/stubs-render-plugin/bad-data.njk\");\n  t.is(html, \"string\");\n});\n\ntest(\"Direct use of render string plugin, rendering Nunjucks (and nested Liquid)\", async (t) => {\n  let renderMgr = new RenderManager();\n  renderMgr.config(function (eleventyConfig) {\n    eleventyConfig.addFilter(\"testing\", function () {\n      return \"tested.\";\n    });\n  });\n  let fn = await renderMgr.compile(\n    `{%- set nunjucksVar = 69 -%}\n{{ hi }}\n{{ nunjucksVar }}\n{{ \"who\" | testing }}\n{% renderTemplate \"liquid\", argData %}\n{% assign liquidVar = 138 %}\n* {{ hi }} test test {{ bye }} {{ liquidVar }}\n{% endrenderTemplate %}\n`,\n    \"njk\"\n  );\n\n  let data = {\n    hi: \"nunjucksHi\",\n    argData: {\n      hi: \"liquidHi\",\n      bye: \"liquidBye\",\n    },\n  };\n  let html = await fn(data);\n\n  t.is(\n    normalizeNewLines(html.trim()),\n    `nunjucksHi\n69\ntested.\n\n\n* liquidHi test test liquidBye 138`\n  );\n});\n\ntest(\"Direct use of render string plugin, rendering Liquid (and nested Nunjucks)\", async (t) => {\n  let renderMgr = new RenderManager();\n  renderMgr.config(function (eleventyConfig) {\n    eleventyConfig.addFilter(\"testing\", function () {\n      return \"tested.\";\n    });\n  });\n\n  let fn = await renderMgr.compile(\n    `{%- assign liquidVar = 69 -%}\n{{ hi }}\n{{ liquidVar }}\n{{ \"who\" | testing }}\n{% renderTemplate \"njk\", argData %}\n{% set njkVar = 138 %}\n* {{ hi }} test test {{ bye }} {{ njkVar }}\n{% endrenderTemplate %}\n`,\n    \"liquid\"\n  );\n  let data = {\n    hi: \"liquidHi\",\n    argData: {\n      hi: \"njkHi\",\n      bye: \"njkBye\",\n    },\n  };\n  let html = await fn(data);\n\n  t.is(\n    normalizeNewLines(html.trim()),\n    `liquidHi\n69\ntested.\n\n\n* njkHi test test njkBye 138`\n  );\n});\n\ntest(\"Direct use of render file plugin, rendering Nunjucks (and nested Liquid)\", async (t) => {\n  let fn = await RenderPluginFile(\"./test/stubs-render-plugin/liquid-direct.njk\", {\n    config: function (eleventyConfig) {\n      eleventyConfig.addPlugin(RenderPlugin);\n      eleventyConfig.addFilter(\"testing\", function () {\n        return \"tested.\";\n      });\n    },\n  });\n  let data = {\n    hi: \"liquidHi\",\n    argData: {\n      hi: \"njkHi\",\n      bye: \"njkBye\",\n    },\n  };\n  let html = await fn(data);\n\n  t.is(\n    normalizeNewLines(html.trim()),\n    `liquidHi\n69\ntested.\n\n\n* njkHi test test njkBye 138`\n  );\n});\n\ntest(\"Use page in renderTemplate (liquid in liquid)\", async (t) => {\n  let html = await getTestOutputForFile(\"./test/stubs-render-plugin/liquid-page.liquid\");\n  t.is(html, `/liquid-page/`);\n});\n\ntest(\"Use page in renderTemplate (liquid in njk)\", async (t) => {\n  let html = await getTestOutputForFile(\"./test/stubs-render-plugin/liquid-page.njk\");\n  t.is(html, `/liquid-page/`);\n});\n\ntest(\"Use page in renderTemplate (njk in liquid)\", async (t) => {\n  let html = await getTestOutputForFile(\"./test/stubs-render-plugin/njk-page.liquid\");\n  t.is(html, `/njk-page/`);\n});\n\ntest(\"Use eleventy in renderTemplate (njk in liquid)\", async (t) => {\n  let html = await getTestOutputForFile(\"./test/stubs-render-plugin/njk-eleventy.liquid\");\n  t.true(html.startsWith(\"4.\"));\n});\n\ntest(\"Use eleventy in renderTemplate (liquid in njk)\", async (t) => {\n  let html = await getTestOutputForFile(\"./test/stubs-render-plugin/liquid-eleventy.njk\");\n  t.true(html.startsWith(\"4.\"));\n});\ntest.skip(\"Use nunjucks in liquid (access to all global data)\", async (t) => {\n  let html = await getTestOutputForFile(\"./test/stubs-render-plugin/nunjucks-global.liquid\");\n  t.is(html, `globalHi`);\n});\n\ntest.skip(\"Use liquid in njk (access to all global data)\", async (t) => {\n  let html = await getTestOutputForFile(\"./test/stubs-render-plugin/liquid-global.njk\");\n  t.is(html, `globalHi`);\n});\n\ntest(\"renderContent filter #3369 #3370 via renderTemplate (njk)\", async (t) => {\n  let html = await getTestOutputForFile(\"./test/stubs-render-plugin/nunjucks-frontmatter.njk\", (eleventyConfig) => {\n    eleventyConfig.addShortcode(\"test\", () => \"test content\")\n  });\n  t.is(html, \"test content\");\n});\n\ntest(\"#3368 #3810 config init bug with RenderManager\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-3810/\", false, {\n    configPath: \"./test/stubs-3810/eleventy.config.js\",\n  });\n  let results = await elev.toJSON();\n  t.is(results[0].content, `<h1>Sign up for our newsletter!</h1>`);\n});\n"
  },
  {
    "path": "test/TemplateRenderTest.js",
    "content": "import test from \"ava\";\n\nimport TemplateRender from \"../src/TemplateRender.js\";\nimport EleventyExtensionMap from \"../src/EleventyExtensionMap.js\";\nimport TemplateEngineManager from \"../src/Engines/TemplateEngineManager.js\";\n\nimport { getTemplateConfigInstance } from \"./_testHelpers.js\";\n\nasync function getNewTemplateRender(name, inputDir) {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: inputDir\n    }\n  });\n\n  let tr = new TemplateRender(name, eleventyConfig);\n  tr.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  tr.extensionMap.engineManager = new TemplateEngineManager(eleventyConfig);\n  tr.extensionMap.setFormats([]);\n  await tr.init();\n  return tr;\n}\n\ntest(\"Basic\", async (t) => {\n  await t.throwsAsync(async () => {\n    let tr = await getNewTemplateRender(\"sldkjfkldsj\");\n    tr.init(\"sldkjfkldsj\");\n  });\n});\n\ntest(\"Includes Dir\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\", \"./test/stubs\");\n  t.is(tr.getIncludesDir(), \"./test/stubs/_includes/\");\n});\n\ntest(\"Invalid override\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\", \"./test/stubs\");\n  await t.throwsAsync(async () => {\n    await tr.setEngineOverride(\"lslkdjf\");\n  });\n});\n\ntest(\"Valid Override\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\", \"./test/stubs\");\n  await tr.setEngineOverride(\"njk\");\n  t.is(tr.getEngineName(), \"njk\");\n  t.truthy(tr.isEngine(\"njk\"));\n});\n\ntest(\"Parse Overrides to get Prioritized Engine List\", async (t) => {\n  t.deepEqual(TemplateRender.parseEngineOverrides(\"\"), []);\n  t.deepEqual(TemplateRender.parseEngineOverrides(null), []);\n  t.deepEqual(TemplateRender.parseEngineOverrides(undefined), []);\n  t.deepEqual(TemplateRender.parseEngineOverrides(false), []);\n  t.deepEqual(TemplateRender.parseEngineOverrides(\"html\"), []);\n  t.deepEqual(TemplateRender.parseEngineOverrides(\"html,html\"), []);\n  t.deepEqual(TemplateRender.parseEngineOverrides(\"html,md,md\"), [\"md\"]);\n  t.deepEqual(TemplateRender.parseEngineOverrides(\"liquid,md\"), [\"md\", \"liquid\"]);\n  t.deepEqual(TemplateRender.parseEngineOverrides(\"liquid\"), [\"liquid\"]);\n  t.deepEqual(TemplateRender.parseEngineOverrides(\"njk\"), [\"njk\"]);\n  t.deepEqual(TemplateRender.parseEngineOverrides(\"liquid,html\"), [\"liquid\"]);\n  t.deepEqual(TemplateRender.parseEngineOverrides(\"liquid,md,html\"), [\"md\", \"liquid\"]);\n  t.deepEqual(TemplateRender.parseEngineOverrides(\"njk,njk\"), [\"njk\"]);\n\n  t.throws(function () {\n    TemplateRender.parseEngineOverrides(\"njk,liquid\");\n  });\n  t.throws(function () {\n    TemplateRender.parseEngineOverrides(\"liquid,njk,html\");\n  });\n});\n\ntest(\"Make sure getEnginesList returns a string\", async (t) => {\n  let tr = await getNewTemplateRender(\"liquid\", \"./test/stubs\");\n  t.is(tr.getEnginesList(\"njk,md\"), \"njk,md\");\n});\n"
  },
  {
    "path": "test/TemplateTest-CompileOptions.js",
    "content": "import test from \"ava\";\n\nimport TemplateData from \"../src/Data/TemplateData.js\";\nimport EleventyExtensionMap from \"../src/EleventyExtensionMap.js\";\n\nimport getNewTemplate from \"./_getNewTemplateForTests.js\";\nimport { renderTemplate } from \"./_getRenderedTemplates.js\";\nimport { getTemplateConfigInstanceCustomCallback } from \"./_testHelpers.js\";\n\ntest(\"Custom extension (.txt) with custom permalink compile function\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback({\n    input: \"test/stubs\",\n    output: \"dist\",\n  }, function(cfg) {\n    cfg.extensionMap.add({\n      extension: \"txt\",\n      key: \"txt\",\n      compileOptions: {\n        cache: false,\n        // pass in your own custom permalink function.\n        permalink: async function (permalinkString, inputPath) {\n          t.is(permalinkString, \"custom-extension.lit\");\n          t.is(inputPath, \"./test/stubs/custom-extension.txt\");\n          return async function () {\n            return \"HAHA_THIS_ALWAYS_GOES_HERE.txt\";\n          };\n        },\n      },\n      compile: function (str, inputPath) {\n        // plaintext\n        return function (data) {\n          return str;\n        };\n      },\n    });\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/custom-extension.txt\",\n    \"./test/stubs/\",\n    \"dist\",\n    dataObj,\n    null,\n    eleventyConfig\n  );\n\n  let data = await tmpl.getData();\n  t.is(await renderTemplate(tmpl, data), \"Sample content\");\n  let testObj = await tmpl.getOutputLocations(data);\n  t.is(testObj.href, \"/HAHA_THIS_ALWAYS_GOES_HERE.txt\");\n  t.is(testObj.path, \"./dist/HAHA_THIS_ALWAYS_GOES_HERE.txt\");\n  t.is(testObj.rawPath, \"HAHA_THIS_ALWAYS_GOES_HERE.txt\");\n});\n\ntest(\"Custom extension with and compileOptions.permalink = false\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback({\n    input: \"test/stubs\",\n    output: \"dist\",\n  }, function(cfg) {\n    cfg.extensionMap.add({\n      extension: \"txt\",\n      key: \"txt\",\n      compileOptions: {\n        cache: false,\n        permalink: false,\n      },\n      compile: function (str, inputPath) {\n        // plaintext\n        return function (data) {\n          return str;\n        };\n      },\n    });\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/custom-extension.txt\",\n    \"./test/stubs/\",\n    \"dist\",\n    dataObj,\n    null,\n    eleventyConfig\n  );\n\n  let data = await tmpl.getData();\n  t.is(await renderTemplate(tmpl, data), \"Sample content\");\n  let testObj = await tmpl.getOutputLocations(data);\n  t.is(testObj.href, false);\n  t.is(testObj.path, false);\n  t.is(testObj.rawPath, false);\n});\n\ntest(\"Custom extension with and opt-out of permalink compilation\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback({\n    input: \"test/stubs\",\n    output: \"dist\",\n  }, function(cfg) {\n    cfg.extensionMap.add({\n      extension: \"txt\",\n      key: \"txt\",\n      compileOptions: {\n        cache: false,\n        permalink: \"raw\",\n      },\n      compile: function (str, inputPath) {\n        // plaintext\n        return function (data) {\n          return str;\n        };\n      },\n    });\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/custom-extension.txt\",\n    \"./test/stubs/\",\n    \"dist\",\n    dataObj,\n    null,\n    eleventyConfig\n  );\n\n  let data = await tmpl.getData();\n  t.is(await renderTemplate(tmpl, data), \"Sample content\");\n  let testObj = await tmpl.getOutputLocations(data);\n  t.is(testObj.href, \"/custom-extension.lit\");\n  t.is(testObj.path, \"./dist/custom-extension.lit\");\n  t.is(testObj.rawPath, \"custom-extension.lit\");\n});\n\ntest(\"Custom extension (.txt) with custom permalink compile function but no permalink in the data cascade\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback({\n    input: \"test/stubs\",\n    output: \"dist\",\n  }, function(cfg) {\n    cfg.extensionMap.add({\n      extension: \"txt\",\n      key: \"txt\",\n      compileOptions: {\n        cache: false,\n        // pass in your own custom permalink function.\n        permalink: async function (permalinkString, inputPath) {\n          t.is(permalinkString, undefined);\n          t.is(inputPath, \"./test/stubs/custom-extension-no-permalink.txt\");\n\n          return async function () {\n            return \"HAHA_THIS_ALWAYS_GOES_HERE.txt\";\n          };\n        },\n      },\n      compile: function (str, inputPath) {\n        // plaintext\n        return function (data) {\n          return str;\n        };\n      },\n    });\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/custom-extension-no-permalink.txt\",\n    \"./test/stubs/\",\n    \"dist\",\n    dataObj,\n    null,\n    eleventyConfig\n  );\n\n  let data = await tmpl.getData();\n  t.is(await renderTemplate(tmpl, data), \"Sample content\");\n  let testObj = await tmpl.getOutputLocations(data);\n  t.is(testObj.href, \"/HAHA_THIS_ALWAYS_GOES_HERE.txt\");\n  t.is(testObj.path, \"./dist/HAHA_THIS_ALWAYS_GOES_HERE.txt\");\n  t.is(testObj.rawPath, \"HAHA_THIS_ALWAYS_GOES_HERE.txt\");\n});\n\ntest(\"Custom extension (.txt) with custom permalink compile function (that returns a string not a function) but no permalink in the data cascade\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback({\n    input: \"test/stubs\",\n    output: \"dist\",\n  }, function(cfg) {\n    cfg.extensionMap.add({\n      extension: \"txt\",\n      key: \"txt\",\n      compileOptions: {\n        cache: false,\n        permalink: async function (permalinkString, inputPath) {\n          t.is(permalinkString, undefined);\n          t.is(inputPath, \"./test/stubs/custom-extension-no-permalink.txt\");\n\n          // unique part of this test: this is a string, not a function\n          return \"HAHA_THIS_ALWAYS_GOES_HERE.txt\";\n        },\n      },\n      compile: function (str, inputPath) {\n        // plaintext\n        return function (data) {\n          return str;\n        };\n      },\n    });\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/custom-extension-no-permalink.txt\",\n    \"./test/stubs/\",\n    \"dist\",\n    dataObj,\n    null,\n    eleventyConfig\n  );\n\n  let data = await tmpl.getData();\n  t.is(await renderTemplate(tmpl, data), \"Sample content\");\n  let testObj = await tmpl.getOutputLocations(data);\n  t.is(testObj.href, \"/HAHA_THIS_ALWAYS_GOES_HERE.txt\");\n  t.is(testObj.path, \"./dist/HAHA_THIS_ALWAYS_GOES_HERE.txt\");\n  t.is(testObj.rawPath, \"HAHA_THIS_ALWAYS_GOES_HERE.txt\");\n});\n\ntest(\"Custom extension (.txt) with custom permalink compile function that returns false\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback({\n    input: \"test/stubs\",\n    output: \"dist\",\n  }, function(cfg) {\n    cfg.extensionMap.add({\n      extension: \"txt\",\n      key: \"txt\",\n      compileOptions: {\n        cache: false,\n        permalink: async function (permalinkString, inputPath) {\n          t.is(permalinkString, undefined);\n          t.is(inputPath, \"./test/stubs/custom-extension-no-permalink.txt\");\n\n          // unique part of this test: this returns false\n          return false;\n        },\n      },\n      compile: function (str, inputPath) {\n        // plaintext\n        return function (data) {\n          return str;\n        };\n      },\n    });\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/custom-extension-no-permalink.txt\",\n    \"./test/stubs/\",\n    \"dist\",\n    dataObj,\n    null,\n    eleventyConfig\n  );\n\n  let data = await tmpl.getData();\n  t.is(await renderTemplate(tmpl, data), \"Sample content\");\n  let testObj = await tmpl.getOutputLocations(data);\n  t.is(testObj.href, false);\n  t.is(testObj.path, false);\n  t.is(testObj.rawPath, false);\n});\n\ntest(\"Custom extension (.txt) that returns undefined from compile\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback({\n    input: \"test/stubs\",\n    output: \"dist\",\n  }, function(cfg) {\n    cfg.extensionMap.add({\n      extension: \"txt\",\n      key: \"txt\",\n      compileOptions: {\n        cache: false,\n      },\n      compile: function (str, inputPath) {\n        t.is(str, \"Sample content\");\n        return function (data) {\n          return undefined;\n        };\n      },\n    });\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/custom-extension-no-permalink.txt\",\n    \"./test/stubs/\",\n    \"dist\",\n    dataObj,\n    null,\n    eleventyConfig\n  );\n\n  let data = await tmpl.getData();\n  t.is(await renderTemplate(tmpl, data), undefined);\n  let pages = await tmpl.getTemplates(data);\n  for (let page of pages) {\n    page.templateContent = undefined;\n    t.is(page.templateContent, undefined); // shouldn’t throw an error\n  }\n});\n"
  },
  {
    "path": "test/TemplateTest-ComputedData.js",
    "content": "import test from \"ava\";\n\nimport Eleventy from \"../src/Eleventy.js\";\nimport TemplateData from \"../src/Data/TemplateData.js\";\nimport EleventyExtensionMap from \"../src/EleventyExtensionMap.js\";\n\nimport getNewTemplate from \"./_getNewTemplateForTests.js\";\nimport { renderTemplate } from \"./_getRenderedTemplates.js\";\nimport { getTemplateConfigInstance, getTemplateConfigInstanceCustomCallback, sortEleventyResults } from \"./_testHelpers.js\";\n\nasync function getRenderedData(tmpl, pageNumber = 0) {\n  let data = await tmpl.getData();\n  let templates = await tmpl.getTemplates(data);\n  return templates[pageNumber].data;\n}\n\ntest(\"eleventyComputed\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/eleventyComputed/first.njk\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n  let data = await getRenderedData(tmpl);\n  t.is((await renderTemplate(tmpl, data)).trim(), \"hi:value2-value1.css\");\n});\n\ntest(\"eleventyComputed overrides existing value.\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/eleventyComputed/override.njk\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n  let data = await getRenderedData(tmpl);\n  t.is(data.key1, \"override\");\n  t.is((await renderTemplate(tmpl, data)).trim(), \"hi:override\");\n});\n\ntest(\"eleventyComputed overrides existing value and reuses that upstream value\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/eleventyComputed/override-reuse.njk\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n  let data = await getRenderedData(tmpl);\n  t.is(data.key1, \"over(value1)ride\");\n  t.is((await renderTemplate(tmpl, data)).trim(), \"hi:over(value1)ride\");\n});\n\ntest(\"eleventyComputed permalink\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/eleventyComputed/permalink.njk\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n  let templates = await tmpl.getTemplates(await tmpl.getData());\n  let data = templates[0].data;\n  t.is(data.page.url, \"/haha-value1.html\");\n  t.is(data.page.outputPath, \"./dist/haha-value1.html\");\n  t.is(data.permalink, \"haha-value1.html\");\n  t.is(data.nested.key3, \"value1\");\n  t.is(data.nested.key4, \"depends on computed value1\");\n  t.is(data.dependsOnPage, \"depends:/haha-value1.html\");\n});\n\ntest(\"eleventyComputed simple permalink\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/eleventyComputed/permalink-simple.njk\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n  let templates = await tmpl.getTemplates(await tmpl.getData());\n  let data = templates[0].data;\n  t.is(data.page.url, \"/haha-value1.html\");\n  t.is(data.page.outputPath, \"./dist/haha-value1.html\");\n  t.is(data.permalink, \"haha-value1.html\");\n});\n\ntest(\"eleventyComputed permalink using slug\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/eleventyComputed/permalink-slug.njk\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n  let templates = await tmpl.getTemplates(await tmpl.getData());\n  let data = templates[0].data;\n  t.is(data.page.url, \"/haha-this-is-a-string.html\");\n  t.is(data.page.outputPath, \"./dist/haha-this-is-a-string.html\");\n  t.is(data.permalink, \"haha-this-is-a-string.html\");\n});\n\ntest(\"eleventyComputed js front matter (function)\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/eleventyComputed/second.njk\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n  let data = await getRenderedData(tmpl);\n  t.is(data.key3, \"value3-value2-value1.css\");\n  t.is((await renderTemplate(tmpl, data)).trim(), \"hi:value2-value1.css\");\n});\n\ntest(\"eleventyComputed js front matter key reuses and overrides\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/eleventyComputed/third.njk\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n  let data = await getRenderedData(tmpl);\n  t.is(data.key1, \"value2-value1\");\n  t.is((await renderTemplate(tmpl, data)).trim(), \"hi:value2-value1\");\n});\n\ntest(\"eleventyComputed true primitive\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/eleventyComputed/true.njk\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n  let data = await getRenderedData(tmpl);\n  t.is(data.key1, \"value1\");\n  t.is(data.key2, true);\n  t.is(data.key3, false);\n  t.is(data.key4, 324);\n});\n\ntest(\"eleventyComputed relies on global data\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs\",\n      output: \"dist\",\n    }\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/eleventyComputed/use-global-data.njk\",\n    \"./test/stubs/\",\n    \"./dist\",\n    dataObj,\n    null,\n    eleventyConfig\n  );\n\n  let fetchedData = await tmpl.getData();\n  let templates = await tmpl.getTemplates(fetchedData);\n  let data = templates[0].data;\n  t.is(data.image, \"datavalue1\");\n});\n\ntest(\"eleventyComputed intermixes with global data\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback({\n    input: \"test/stubs-computed-global\",\n    output: \"dist\",\n  }, function(cfg) {\n    // Defaulted in v1\n    // cfg.setDataDeepMerge(true);\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n\n  let tmpl = await getNewTemplate(\n    \"./test/stubs-computed-global/intermix.njk\",\n    \"./test/stubs-computed-global/\",\n    \"./dist\",\n    dataObj,\n    null,\n    eleventyConfig\n  );\n\n  let fetchedData = await tmpl.getData();\n  t.truthy(fetchedData.eleventyComputed.image);\n  t.truthy(fetchedData.eleventyComputed.image2);\n  t.truthy(fetchedData.eleventyComputed.image3);\n  t.truthy(fetchedData.eleventyComputed.eleventyNavigation.key);\n\n  let templates = await tmpl.getTemplates(fetchedData);\n  let data = templates[0].data;\n  t.is(data.image, \"first\");\n  t.is(data.image2, \"second\");\n  t.is(data.image3, \"third-global\");\n  t.is(data.eleventyNavigation.key, \"nested-first-global\");\n});\n\ntest(\"eleventyComputed using symbol parsing on template strings (nunjucks)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback({\n    input: \"test/stubs-computed-symbolparse\",\n    output: \"dist\",\n  }, function(cfg) {\n    cfg.addNunjucksFilter(\"fail\", function (str) {\n      // Filter expects a certain String format, don’t use the (((11ty))) string hack\n      if (!str || str.length !== 1) {\n        throw new Error(\"Expect a one character string\");\n      }\n      return `${str}`;\n    });\n  });\n\n  let tmpl = await getNewTemplate(\n    \"./test/stubs-computed-symbolparse/test.njk\",\n    \"./test/stubs-computed-symbolparse/\",\n    \"./test/stubs-computed-symbolparse/_site\",\n    null,\n    null,\n    eleventyConfig\n  );\n\n  let data = await getRenderedData(tmpl);\n  t.is(data.a, \"a\");\n  t.is(data.b, \"b\");\n  t.is(data.c, \"ab\");\n});\n\ntest(\"eleventyComputed using symbol parsing on template strings (liquid)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback({\n    input: \"test/stubs-computed-symbolparse\",\n    output: \"dist\",\n  }, function(cfg) {\n    cfg.addLiquidFilter(\"fail\", function (str) {\n      // Filter expects a certain String format, don’t use the (((11ty))) string hack\n      if (!str || str.length !== 1) {\n        throw new Error(\"Expect a one character string: \" + str);\n      }\n      return `${str}`;\n    });\n  });\n\n  let tmpl = await getNewTemplate(\n    \"./test/stubs-computed-symbolparse/test.liquid\",\n    \"./test/stubs-computed-symbolparse/\",\n    \"./test/stubs-computed-symbolparse/_site\",\n    null,\n    null,\n    eleventyConfig\n  );\n\n  let data = await getRenderedData(tmpl);\n  t.is(data.a, \"a\");\n  t.is(data.b, \"b\");\n  t.is(data.c, \"ab\");\n});\n\ntest(\"eleventyComputed render strings in arrays\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs-computed-array/test.liquid\",\n    \"./test/stubs-computed-array/\",\n    \"./test/stubs-computed-array/_site\"\n  );\n\n  let data = await getRenderedData(tmpl);\n  t.deepEqual(data.array, [\"static value\", \"test\"]);\n  t.is(data.notArray, \"test\");\n});\n\ntest(\"Issue #3728 Computed data with arrays of different sizes, arrays are treated as a single unit\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: eleventyConfig => {\n      eleventyConfig.addTemplate(\"index.njk\", `---js\nconst arr = [1,2,3,4,5];\nconst eleventyComputed = {\n  arr: [\"a\", \"b\", \"c\"]\n}\n---\n{{ arr }}`);\n    }\n  });\n  elev.disableLogger();\n\n  let result = await elev.toJSON();\n  t.is(result.length, 1);\n  t.is(result[0].content, `a,b,c`);\n});\n\ntest(\"Issue #3827 with Computed data with arrays and layouts (×2 eleventyComputed)\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: eleventyConfig => {\n      eleventyConfig.setUseTemplateCache(false);\n      eleventyConfig.addTemplate(\"_includes/base.njk\", `---js\nconst eleventyComputed = {\n  arr: [\"base\", \"base2\"]\n};\n---\n{{ content | safe }}`);\n\n      eleventyConfig.addTemplate(\"page1.njk\", `---js\nconst layout = \"base.njk\";\n---\n{{ arr }}`);\n\n    eleventyConfig.addTemplate(\"page2.njk\", `---js\nconst layout = \"base.njk\";\nconst eleventyComputed = {\n  arr: [\"override\", \"override2\"]\n}\n---\n{{ arr }}`);\n    }\n  });\n\n  elev.disableLogger();\n\n  let result = await elev.toJSON();\n  result.sort(sortEleventyResults);\n\n  t.is(result.length, 2);\n\n  t.is(result[0].inputPath, `./test/stubs-virtual/page2.njk`);\n  // A merge happened here because it was eleventyComputed -> eleventyComputed array merge before computed data\n  t.is(result[0].content, `base,base2,override,override2`);\n\n  t.is(result[1].inputPath, `./test/stubs-virtual/page1.njk`);\n  t.is(result[1].content, `base,base2`);\n});\n\ntest(\"Issue #3728 with Computed data with arrays and layouts (×1 eleventyComputed)\", async (t) => {\n  let elev = new Eleventy(\"./test/stubs-virtual/\", undefined, {\n    config: eleventyConfig => {\n      eleventyConfig.addTemplate(\"_includes/base.njk\", `---js\nconst arr = [\"base\", 1];\n---\n{{ content | safe }}`);\n\n      eleventyConfig.addTemplate(\"page1.njk\", `---js\nconst layout = \"base.njk\";\n---\n{{ arr }}`);\n\n    eleventyConfig.addTemplate(\"page2.njk\", `---js\nconst layout = \"base.njk\";\nconst eleventyComputed = {\n  arr: [\"override\", 2]\n}\n---\n{{ arr }}`);\n    }\n  });\n\n  elev.disableLogger();\n\n  let result = await elev.toJSON();\n  result.sort(sortEleventyResults);\n\n  t.is(result.length, 2);\n\n  t.is(result[0].content, `override,2`);\n  t.is(result[1].content, `base,1`);\n});\n"
  },
  {
    "path": "test/TemplateTest-CustomExtensions.js",
    "content": "import test from \"ava\";\nimport { marked } from \"marked\";\n\nimport TemplateData from \"../src/Data/TemplateData.js\";\nimport EleventyExtensionMap from \"../src/EleventyExtensionMap.js\";\n\nimport getNewTemplate from \"./_getNewTemplateForTests.js\";\nimport { renderTemplate } from \"./_getRenderedTemplates.js\";\nimport { getTemplateConfigInstanceCustomCallback } from \"./_testHelpers.js\";\n\ntest(\"Using getData: false without getInstanceFromInputPath works ok\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback({\n    input: \"test/stubs\"\n  }, function(cfg) {\n    cfg.extensionMap.add({\n      extension: \"txt\",\n      key: \"txt\",\n      compileOptions: {\n        cache: false,\n      },\n      getData: false,\n      compile: function (str, inputPath) {\n        // plaintext\n        return function (data) {\n          return str;\n        };\n      },\n    });\n  })\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/custom-extension.txt\",\n    \"./test/stubs/\",\n    \"dist\",\n    dataObj,\n    null,\n    eleventyConfig\n  );\n\n  let data = await tmpl.getData();\n  t.is(await renderTemplate(tmpl, data), \"Sample content\");\n});\n\ntest(\"Using getData: true without getInstanceFromInputPath should error\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback({\n    input: \"test/stubs\"\n  }, function(cfg) {\n    cfg.extensionMap.add({\n      extension: \"txt\",\n      key: \"txt\",\n      compileOptions: {\n        cache: false,\n      },\n      getData: true,\n      compile: function (str, inputPath) {\n        // plaintext\n        return function (data) {\n          return str;\n        };\n      },\n    });\n  })\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/custom-extension.txt\",\n    \"./test/stubs/\",\n    \"dist\",\n    dataObj,\n    null,\n    eleventyConfig\n  );\n\n  await t.throwsAsync(async () => {\n    await tmpl.getData();\n  }, {\n    message: \"`getInstanceFromInputPath` callback missing from \\'txt\\' template engine plugin. It is required when `getData` is in use. You can set `getData: false` to opt-out of this.\"\n  });\n});\n\ntest(\"Using getData: [] without getInstanceFromInputPath should error\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback({\n    input: \"test/stubs\"\n  }, function(cfg) {\n    cfg.extensionMap.add({\n      extension: \"txt\",\n      key: \"txt\",\n      compileOptions: {\n        cache: false,\n      },\n      getData: [],\n      compile: function (str, inputPath) {\n        // plaintext\n        return function (data) {\n          return str;\n        };\n      },\n    });\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/custom-extension.txt\",\n    \"./test/stubs/\",\n    \"dist\",\n    dataObj,\n    null,\n    eleventyConfig\n  );\n\n  await t.throwsAsync(async () => {\n    await tmpl.getData();\n  }, {\n    message: \"`getInstanceFromInputPath` callback missing from \\'txt\\' template engine plugin. It is required when `getData` is in use. You can set `getData: false` to opt-out of this.\"\n  });\n});\n\ntest(\"Using getData: true and getInstanceFromInputPath to get data from instance\", async (t) => {\n  let globalData = {\n    topLevelData: true,\n  };\n\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback({\n    input: \"test/stubs\"\n  }, function(cfg) {\n    cfg.extensionMap.add({\n      extension: \"txt\",\n      key: \"txt\",\n      compileOptions: {\n        cache: false,\n      },\n      getData: true,\n      getInstanceFromInputPath: function () {\n        return {\n          data: globalData,\n        };\n      },\n      compile: function (str, inputPath) {\n        // plaintext\n        return function (data) {\n          return str;\n        };\n      },\n    });\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/custom-extension.txt\",\n    \"./test/stubs/\",\n    \"dist\",\n    dataObj,\n    null,\n    eleventyConfig\n  );\n\n  let data = await tmpl.getData();\n  t.is(data.topLevelData, true);\n});\n\ntest(\"Using eleventyDataKey to get a different key data from instance\", async (t) => {\n  let globalData = {\n    topLevelData: true,\n  };\n\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback({\n    input: \"test/stubs\"\n  }, function(cfg) {\n    cfg.extensionMap.add({\n      extension: \"txt\",\n      key: \"txt\",\n      compileOptions: {\n        cache: false,\n      },\n      getData: [],\n      getInstanceFromInputPath: function () {\n        return {\n          eleventyDataKey: [\"otherProp\"],\n          otherProp: globalData,\n        };\n      },\n      compile: function (str, inputPath) {\n        // plaintext\n        return function (data) {\n          return str;\n        };\n      },\n    });\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/custom-extension.txt\",\n    \"./test/stubs/\",\n    \"dist\",\n    dataObj,\n    null,\n    eleventyConfig\n  );\n\n  let data = await tmpl.getData();\n  t.is(data.topLevelData, true);\n});\n\ntest(\"Uses default renderer (no compile function) when you override an existing extension\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback({\n    input: \"test/stubs\"\n  }, function(cfg) {\n    cfg.extensionMap.add({\n      extension: \"liquid\",\n      key: \"liquid\",\n      compileOptions: {\n        cache: false,\n      },\n    });\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/default.liquid\",\n    \"./test/stubs/\",\n    \"dist\",\n    dataObj,\n    null,\n    eleventyConfig\n  );\n\n  let data = await tmpl.getData();\n  t.is(await renderTemplate(tmpl, data), \"hi\");\n});\n\ntest(\"Access to default renderer when you override an existing extension\", async (t) => {\n  t.plan(2);\n\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback({\n    input: \"test/stubs\"\n  }, function(cfg) {\n\t\tcfg.extensionMap.add({\n\t\t\textension: \"liquid\",\n\t\t\tkey: \"liquid\",\n\t\t\tcompileOptions: {\n\t\t\t\tcache: false,\n\t\t\t},\n\t\t\tcompile: function (str, inputPath) {\n\t\t\t\t// plaintext\n\t\t\t\treturn function (data) {\n\t\t\t\t\tt.true(true);\n\t\t\t\t\treturn this.defaultRenderer();\n\t\t\t\t};\n\t\t\t},\n\t\t});\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/default.liquid\",\n    \"./test/stubs/\",\n    \"dist\",\n    dataObj,\n    null,\n    eleventyConfig\n  );\n\n  let data = await tmpl.getData();\n  t.is(await renderTemplate(tmpl, data), \"hi\");\n});\n\ntest(\"Overridden liquid gets used from a markdown template\", async (t) => {\n  t.plan(2);\n\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback({\n    input: \"test/stubs\"\n  }, function(cfg) {\n\t\tcfg.extensionMap.add({\n\t\t\textension: \"liquid\",\n\t\t\tkey: \"liquid\",\n\t\t\tcompileOptions: {\n\t\t\t\tcache: false,\n\t\t\t},\n\t\t\tcompile: function (str, inputPath) {\n\t\t\t\tt.true(true);\n\n\t\t\t\t// plaintext\n\t\t\t\treturn function (data) {\n\t\t\t\t\treturn this.defaultRenderer();\n\t\t\t\t};\n\t\t\t},\n\t\t});\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/default.md\",\n    \"./test/stubs/\",\n    \"dist\",\n    dataObj,\n    null,\n    eleventyConfig\n  );\n\n  let data = await tmpl.getData();\n  t.is((await renderTemplate(tmpl, data)).trim(), \"<p>hi</p>\");\n});\n\ntest(\"Use marked for markdown\", async (t) => {\n  t.plan(2);\n\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback({\n    input: \"test/stubs\"\n  }, function(cfg) {\n\t\tcfg.extensionMap.add({\n\t\t\textension: \"md\",\n\t\t\tkey: \"md\",\n\t\t\tcompileOptions: {\n\t\t\t\tcache: false,\n\t\t\t},\n\t\t\tcompile: function (str, inputPath) {\n\t\t\t\tlet html = marked.parse(str);\n\t\t\t\t// plaintext\n\t\t\t\treturn function (data) {\n\t\t\t\t\tt.true(true);\n\t\t\t\t\treturn html;\n\t\t\t\t};\n\t\t\t},\n\t\t});\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/default-no-liquid.md\",\n    \"./test/stubs/\",\n    \"dist\",\n    dataObj,\n    null,\n    eleventyConfig\n  );\n\n  let data = await tmpl.getData();\n  t.is((await renderTemplate(tmpl, data)).trim(), \"<p>hi</p>\");\n});\n\ntest(\"Use defaultRenderer for markdown\", async (t) => {\n  t.plan(2);\n\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback({\n    input: \"test/stubs\"\n  }, function(cfg) {\n\t\tcfg.extensionMap.add({\n\t\t\textension: \"md\",\n\t\t\tkey: \"md\",\n\t\t\tcompileOptions: {\n\t\t\t\tcache: false,\n\t\t\t},\n\t\t\tcompile: function (str, inputPath) {\n\t\t\t\treturn function (data) {\n\t\t\t\t\tt.true(true);\n\t\t\t\t\treturn this.defaultRenderer(data);\n\t\t\t\t};\n\t\t\t},\n\t\t});\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/default.md\",\n    \"./test/stubs/\",\n    \"dist\",\n    dataObj,\n    null,\n    eleventyConfig\n  );\n\n  let data = await tmpl.getData();\n  t.is((await renderTemplate(tmpl, data)).trim(), \"<p>hi</p>\");\n});\n\ntest(\"Front matter in a custom extension\", async (t) => {\n  t.plan(2);\n\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback({\n    input: \"test/stubs\"\n  }, function(cfg) {\n\t\tcfg.extensionMap.add({\n\t\t\textension: \"txt\",\n\t\t\tkey: \"txt\",\n\t\t\tcompileOptions: {\n\t\t\t\tcache: false,\n\t\t\t},\n\t\t\tcompile: function (str, inputPath) {\n\t\t\t\treturn function (data) {\n\t\t\t\t\treturn str;\n\t\t\t\t};\n\t\t\t},\n\t\t});\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/default-frontmatter.txt\",\n    \"./test/stubs/\",\n    \"dist\",\n    dataObj,\n    null,\n    eleventyConfig\n  );\n\n  let data = await tmpl.getData();\n  t.is(data.frontmatter, 1);\n  t.is((await renderTemplate(tmpl, data)).trim(), \"hi\");\n});\n\ntest(\"Access to default renderer when you override an existing extension (async compile function, arrow render function)\", async (t) => {\n  t.plan(2);\n\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback({\n    input: \"test/stubs\"\n  }, function(cfg) {\n\t\tcfg.extensionMap.add({\n\t\t\textension: \"liquid\",\n\t\t\tkey: \"liquid\",\n\t\t\tcompileOptions: {\n\t\t\t\tcache: false,\n\t\t\t},\n\t\t\tcompile: async function (str, inputPath) {\n\t\t\t\t// plaintext\n\t\t\t\treturn async (data) => {\n\t\t\t\t\tt.true(true);\n\t\t\t\t\treturn this.defaultRenderer();\n\t\t\t\t};\n\t\t\t},\n\t\t});\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/default.liquid\",\n    \"./test/stubs/\",\n    \"dist\",\n    dataObj,\n    null,\n    eleventyConfig\n  );\n\n  let data = await tmpl.getData();\n  t.is(await renderTemplate(tmpl, data), \"hi\");\n});\n\ntest(\"Access to default renderer when you override an existing extension (async compile function, async render function)\", async (t) => {\n  t.plan(2);\n\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback({\n    input: \"test/stubs\"\n  }, function(cfg) {\n\t\tcfg.extensionMap.add({\n\t\t\textension: \"liquid\",\n\t\t\tkey: \"liquid\",\n\t\t\tcompileOptions: {\n\t\t\t\tcache: false,\n\t\t\t},\n\t\t\tcompile: async function (str, inputPath) {\n\t\t\t\t// plaintext\n\t\t\t\treturn async function (data) {\n\t\t\t\t\tt.true(true);\n\t\t\t\t\treturn this.defaultRenderer();\n\t\t\t\t};\n\t\t\t},\n\t\t});\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/default.liquid\",\n    \"./test/stubs/\",\n    \"dist\",\n    dataObj,\n    null,\n    eleventyConfig\n  );\n\n  let data = await tmpl.getData();\n  t.is(await renderTemplate(tmpl, data), \"hi\");\n});\n\ntest(\"Return undefined in compile to ignore #2267\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback({\n    input: \"test/stubs\"\n  }, function(cfg) {\n\t\tcfg.extensionMap.add({\n\t\t\textension: \"txt\",\n\t\t\tkey: \"txt\",\n\t\t\tcompileOptions: {\n\t\t\t\tcache: false,\n\t\t\t},\n\t\t\tgetData: false,\n\t\t\tcompile: function (str, inputPath) {\n\t\t\t\treturn;\n\t\t\t},\n\t\t});\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/custom-extension.txt\",\n    \"./test/stubs/\",\n    \"dist\",\n    dataObj,\n    null,\n    eleventyConfig\n  );\n\n  let data = await tmpl.getData();\n  t.is(await renderTemplate(tmpl, data), undefined);\n});\n\ntest(\"Return undefined in compile to ignore (async compile function) #2350\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback({\n    input: \"test/stubs\"\n  }, function(cfg) {\n\t\tcfg.extensionMap.add({\n\t\t\textension: \"txt\",\n\t\t\tkey: \"txt\",\n\t\t\tcompileOptions: {\n\t\t\t\tcache: false,\n\t\t\t},\n\t\t\tgetData: false,\n\t\t\tcompile: async function (str, inputPath) {\n\t\t\t\treturn;\n\t\t\t},\n\t\t});\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/custom-extension.txt\",\n    \"./test/stubs/\",\n    \"dist\",\n    dataObj,\n    null,\n    eleventyConfig\n  );\n\n  let data = await tmpl.getData();\n  t.is(await renderTemplate(tmpl, data), undefined);\n});\n"
  },
  {
    "path": "test/TemplateTest-DataCascade.js",
    "content": "import test from \"ava\";\n\nimport TemplateData from \"../src/Data/TemplateData.js\";\n\nimport getNewTemplate from \"./_getNewTemplateForTests.js\";\nimport { getTemplateConfigInstance } from \"./_testHelpers.js\";\n\n// Prior to and including 0.10.0 this mismatched the documentation)! (Issue #915)\ntest(\"Layout front matter does not override template data files\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n\t\tdir: {\n\t\t\tinput: \"test/stubs-data-cascade/layout-data-files\",\n\t\t\toutput: \"dist\"\n\t\t}\n\t});\n\n  let dataObj = new TemplateData(eleventyConfig);\n  let tmpl = await getNewTemplate(\n    \"./test/stubs-data-cascade/layout-data-files/test.njk\",\n    \"./test/stubs-data-cascade/layout-data-files/\",\n    \"./dist\",\n    dataObj,\n    null,\n    eleventyConfig\n  );\n\n  let data = await tmpl.getData();\n  t.is(data.shared, \"datafile\");\n});\n\ntest(\"Layout front matter should not override global data (sanity check, Issue 915)\", async (t) => {\n\tlet eleventyConfig = await getTemplateConfigInstance({\n\t\tdir: {\n\t\t\tinput: \"test/stubs-data-cascade/global-versus-layout\",\n\t\t\toutput: \"dist\"\n\t\t}\n\t});\n\n  let dataObj = new TemplateData(eleventyConfig);\n  let tmpl = await getNewTemplate(\n    \"./test/stubs-data-cascade/global-versus-layout/test.njk\",\n    \"./test/stubs-data-cascade/global-versus-layout/\",\n    \"./dist\",\n    dataObj,\n    null,\n    eleventyConfig\n  );\n\n  let data = await tmpl.getData();\n  t.is(data.cascade, \"from-layout-file\");\n});\n\ntest(\"Template data files should be more specific in data cascade than Layout front matter (breaking change in 1.0, issue 915)\", async (t) => {\n\tlet eleventyConfig = await getTemplateConfigInstance({\n\t\tdir: {\n\t\t\tinput: \"test/stubs-data-cascade/layout-versus-tmpldatafile\",\n\t\t\toutput: \"dist\"\n\t\t}\n\t});\n\n  let dataObj = new TemplateData(eleventyConfig);\n  let tmpl = await getNewTemplate(\n    \"./test/stubs-data-cascade/layout-versus-tmpldatafile/test.njk\",\n    \"./test/stubs-data-cascade/layout-versus-tmpldatafile/\",\n    \"./dist\",\n    dataObj,\n    null,\n    eleventyConfig\n  );\n\n  let data = await tmpl.getData();\n  t.is(data.cascade, \"template-data-file\");\n});\n\ntest(\"Directory data files should be more specific in data cascade than Layout front matter (breaking change in 1.0, issue 915)\", async (t) => {\n\tlet eleventyConfig = await getTemplateConfigInstance({\n\t\tdir: {\n\t\t\tinput: \"test/stubs-data-cascade/layout-versus-dirdatafile/src/\",\n\t\t\toutput: \"dist\"\n\t\t}\n\t});\n\n  let dataObj = new TemplateData(eleventyConfig);\n  let tmpl = await getNewTemplate(\n    \"./test/stubs-data-cascade/layout-versus-dirdatafile/src/test.njk\",\n    \"./test/stubs-data-cascade/layout-versus-dirdatafile/src/\",\n    \"./dist\",\n    dataObj,\n    null,\n    eleventyConfig\n  );\n\n  let data = await tmpl.getData();\n  t.is(data.cascade, \"dir-data-file\");\n});\n"
  },
  {
    "path": "test/TemplateTest-Dates.js",
    "content": "import test from \"ava\";\nimport getNewTemplate from \"./_getNewTemplateForTests.js\";\n\nasync function getRenderedData(tmpl, pageNumber = 0) {\n  let data = await tmpl.getData();\n  let templates = await tmpl.getTemplates(data);\n  return templates[pageNumber].data;\n}\n\ntest(\"getMappedDate (empty, assume created)\", async (t) => {\n  let tmpl = await getNewTemplate(\"./test/stubs/dates/file1.md\", \"./test/stubs/\", \"./dist\");\n  let data = await getRenderedData(tmpl);\n  let date = await tmpl.getMappedDate(data);\n\n  t.true(date instanceof Date);\n  t.truthy(date.getTime());\n});\n\ntest(\"getMappedDate (explicit date, yaml String)\", async (t) => {\n  let tmpl = await getNewTemplate(\"./test/stubs/dates/file2.md\", \"./test/stubs/\", \"./dist\");\n  let data = await getRenderedData(tmpl);\n  let date = await tmpl.getMappedDate(data);\n\n  t.true(date instanceof Date);\n  t.truthy(date.getTime());\n  t.is(Date.UTC(2016, 0, 1), date.getTime());\n});\n\ntest(\"getMappedDate (explicit date, yaml Date)\", async (t) => {\n  let tmpl = await getNewTemplate(\"./test/stubs/dates/file2b.md\", \"./test/stubs/\", \"./dist\");\n  let data = await getRenderedData(tmpl);\n  let date = await tmpl.getMappedDate(data);\n\n  t.true(date instanceof Date);\n  t.truthy(date.getTime());\n  t.is(Date.UTC(2016, 0, 1), date.getTime());\n});\n\ntest(\"getMappedDate (explicit date, yaml Date and string should be the same)\", async (t) => {\n  let tmplA = await getNewTemplate(\"./test/stubs/dates/file2.md\", \"./test/stubs/\", \"./dist\");\n  let dataA = await getRenderedData(tmplA);\n  let stringDate = await tmplA.getMappedDate(dataA);\n\n  let tmplB = await getNewTemplate(\"./test/stubs/dates/file2b.md\", \"./test/stubs/\", \"./dist\");\n  let dataB = await getRenderedData(tmplB);\n  let yamlDate = await tmplB.getMappedDate(dataB);\n\n  t.truthy(stringDate);\n  t.truthy(yamlDate);\n  t.deepEqual(stringDate, yamlDate);\n});\n\ntest(\"getMappedDate (modified date)\", async (t) => {\n  let tmpl = await getNewTemplate(\"./test/stubs/dates/file3.md\", \"./test/stubs/\", \"./dist\");\n  let data = await getRenderedData(tmpl);\n  let date = await tmpl.getMappedDate(data);\n\n  t.true(date instanceof Date);\n  t.truthy(date.getTime());\n});\n\ntest(\"getMappedDate (created date)\", async (t) => {\n  let tmpl = await getNewTemplate(\"./test/stubs/dates/file4.md\", \"./test/stubs/\", \"./dist\");\n  let data = await getRenderedData(tmpl);\n  let date = await tmpl.getMappedDate(data);\n\n  t.true(date instanceof Date);\n  t.truthy(date.getTime());\n});\n\ntest(\"getMappedDate (falls back to filename date)\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/dates/2018-01-01-file5.md\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n  let data = await getRenderedData(tmpl);\n  let date = await tmpl.getMappedDate(data);\n\n  t.true(date instanceof Date);\n  t.truthy(date.getTime());\n  t.is(Date.UTC(2018, 0, 1), date.getTime());\n});\n\ntest(\"getMappedDate (found multiple dates, picks first)\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/dates/2019-01-01-folder/2020-01-01-file.md\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n  let data = await getRenderedData(tmpl);\n  let date = await tmpl.getMappedDate(data);\n\n  t.true(date instanceof Date);\n  t.truthy(date.getTime());\n  t.is(Date.UTC(2019, 0, 1), date.getTime());\n});\n"
  },
  {
    "path": "test/TemplateTest-JavaScript.js",
    "content": "import test from \"ava\";\nimport semver from \"semver\";\n\nimport getNewTemplate from \"./_getNewTemplateForTests.js\";\nimport { getRenderedTemplates as getRenderedTmpls } from \"./_getRenderedTemplates.js\";\nimport { getTemplateConfigInstanceCustomCallback } from \"./_testHelpers.js\";\n\ntest(\"JavaScript template type (function)\", async (t) => {\n  let tmpl = await getNewTemplate(\"./test/stubs/function.11ty.cjs\", \"./test/stubs/\", \"./dist\");\n\n  let data = await tmpl.getData();\n  t.is(await tmpl.getOutputPath(data), \"./dist/function/index.html\");\n  data.name = \"Zach\";\n\n  let pages = await getRenderedTmpls(tmpl, data);\n  t.is(pages[0].templateContent.trim(), \"<p>Zach</p>\");\n\n  // New in 2.0.0-canary.19 Issue #1522\n  t.is(pages[0].content.trim(), \"<p>Zach</p>\");\n  t.is(pages[0].page.url, \"/function/\");\n});\n\ntest(\"JavaScript template type (class with data getter)\", async (t) => {\n  let tmpl = await getNewTemplate(\"./test/stubs/class-data.11ty.cjs\", \"./test/stubs/\", \"./dist\");\n\n  let data = await tmpl.getData();\n  t.is(await tmpl.getOutputPath(data), \"./dist/class-data/index.html\");\n\n  let pages = await getRenderedTmpls(tmpl, data);\n  t.is(pages[0].templateContent.trim(), \"<p>Ted</p>\");\n  t.is(pages[0].content.trim(), \"<p>Ted</p>\");\n});\n\ntest(\"JavaScript template type (class with data method)\", async (t) => {\n  let tmpl = await getNewTemplate(\"./test/stubs/class-data-fn.11ty.cjs\", \"./test/stubs/\", \"./dist\");\n\n  let data = await tmpl.getData();\n  t.is(await tmpl.getOutputPath(data), \"./dist/class-data-fn/index.html\");\n\n  let pages = await getRenderedTmpls(tmpl, data);\n  t.is(pages[0].templateContent.trim(), \"<p>Ted</p>\");\n  t.is(pages[0].content.trim(), \"<p>Ted</p>\");\n});\n\nif (semver.gte(process.version, \"12.4.0\")) {\n  test(\"JavaScript template type (class fields)\", async (t) => {\n    let tmpl = await getNewTemplate(\n      \"./test/stubs/classfields-data.11ty.cjs\",\n      \"./test/stubs/\",\n      \"./dist\"\n    );\n\n    let data = await tmpl.getData();\n    t.is(await tmpl.getOutputPath(data), \"./dist/classfields-data/index.html\");\n\n    let pages = await getRenderedTmpls(tmpl, data);\n    t.is(pages[0].templateContent.trim(), \"<p>Ted</p>\");\n    t.is(pages[0].content.trim(), \"<p>Ted</p>\");\n  });\n}\n\ntest(\"JavaScript template type (class with shorthand data method)\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/class-data-fn-shorthand.11ty.cjs\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n\n  let data = await tmpl.getData();\n  t.is(await tmpl.getOutputPath(data), \"./dist/class-data-fn-shorthand/index.html\");\n\n  let pages = await getRenderedTmpls(tmpl, data);\n  t.is(pages[0].templateContent.trim(), \"<p>Ted</p>\");\n  t.is(pages[0].content.trim(), \"<p>Ted</p>\");\n});\n\ntest(\"JavaScript template type (class with async data method)\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/class-async-data-fn.11ty.cjs\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n\n  let data = await tmpl.getData();\n  t.is(await tmpl.getOutputPath(data), \"./dist/class-async-data-fn/index.html\");\n\n  let pages = await getRenderedTmpls(tmpl, data);\n  t.is(pages[0].templateContent.trim(), \"<p>Ted</p>\");\n  t.is(pages[0].content.trim(), \"<p>Ted</p>\");\n});\n\ntest(\"JavaScript template type (class with data getter and a javascriptFunction)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback({\n    input: \"test/stubs\",\n    output: \"dist\",\n  }, function(cfg) {\n    cfg.addJavaScriptFunction(\"upper\", function (val) {\n      return new String(val).toUpperCase();\n    });\n  });\n\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/class-data-filter.11ty.cjs\",\n    \"./test/stubs/\",\n    \"./dist\",\n    null,\n    null,\n    eleventyConfig\n  );\n\n  let data = await tmpl.getData();\n  t.is(await tmpl.getOutputPath(data), \"./dist/class-data-filter/index.html\");\n  let pages = await getRenderedTmpls(tmpl, data);\n  t.is(pages[0].content.trim(), \"<p>TED</p>\");\n  t.is(pages[0].templateContent.trim(), \"<p>TED</p>\");\n});\n\ntest(\"JavaScript template type (class with data method and a javascriptFunction)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback({\n    input: \"test/stubs\",\n    output: \"dist\",\n  }, function(cfg) {\n    cfg.addJavaScriptFunction(\"upper\", function (val) {\n      return new String(val).toUpperCase();\n    });\n  });\n\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/class-data-fn-filter.11ty.cjs\",\n    \"./test/stubs/\",\n    \"./dist\",\n    null,\n    null,\n    eleventyConfig\n  );\n\n  let data = await tmpl.getData();\n  t.is(await tmpl.getOutputPath(data), \"./dist/class-data-fn-filter/index.html\");\n  let pages = await getRenderedTmpls(tmpl, data);\n  t.is(pages[0].content.trim(), \"<p>TED</p>\");\n  t.is(pages[0].templateContent.trim(), \"<p>TED</p>\");\n});\n\ntest(\"JavaScript template type (class with data permalink)\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/class-data-permalink.11ty.cjs\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n  let data = await tmpl.getData();\n  t.is(await tmpl.getOutputPath(data), \"./dist/my-permalink/index.html\");\n});\n\ntest(\"JavaScript template type (class with data permalink using a buffer)\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/class-data-permalink-buffer.11ty.cjs\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n  let data = await tmpl.getData();\n  t.is(await tmpl.getOutputPath(data), \"./dist/my-permalink/index.html\");\n});\n\ntest(\"JavaScript template type (class with data permalink function)\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/class-data-permalink-fn.11ty.cjs\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n  let data = await tmpl.getData();\n  t.is(await tmpl.getOutputPath(data), \"./dist/my-permalink/value1/index.html\");\n});\n\ntest(\"JavaScript template type (class with data permalink function using a buffer)\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/class-data-permalink-fn-buffer.11ty.cjs\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n  let data = await tmpl.getData();\n  t.is(await tmpl.getOutputPath(data), \"./dist/my-permalink/value1/index.html\");\n});\n\ntest(\"JavaScript template type (class with data permalink async function)\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/class-data-permalink-async-fn.11ty.cjs\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n  let data = await tmpl.getData();\n  t.is(await tmpl.getOutputPath(data), \"./dist/my-permalink/value1/index.html\");\n});\n\ntest(\"JavaScript template type (class with data permalink function using a filter)\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/class-data-permalink-fn-filter.11ty.cjs\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n\n  let data = await tmpl.getData();\n  t.is(await tmpl.getOutputPath(data), \"./dist/my-permalink/my-super-cool-title/index.html\");\n});\n\ntest(\"JavaScript template type (should use the same class instance for data and render)\", async (t) => {\n  let tmpl = await getNewTemplate(\"./test/stubs/oneinstance.11ty.cjs\", \"./test/stubs/\", \"./dist\");\n\n  let data = await tmpl.getData();\n  let pages = await getRenderedTmpls(tmpl, data);\n  // the template renders the random number created in the class constructor\n  // the data returns the random number created in the class constructor\n  // if they are different, the class is not reused.\n  t.is(pages[0].content.trim(), `<p>Ted${data.rand}</p>`);\n  t.is(pages[0].templateContent.trim(), `<p>Ted${data.rand}</p>`);\n});\n\ntest(\"JavaScript template type (multiple exports)\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/multipleexports.11ty.cjs\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n\n  let data = await tmpl.getData();\n  let pages = await getRenderedTmpls(tmpl, data);\n  t.is(pages[0].content.trim(), \"<p>Ted</p>\");\n  t.is(pages[0].templateContent.trim(), \"<p>Ted</p>\");\n});\n\ntest(\"JavaScript template type (multiple exports, promises)\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/multipleexports-promises.11ty.cjs\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n\n  let data = await tmpl.getData();\n  t.is(data.name, \"Ted\");\n\n  let pages = await getRenderedTmpls(tmpl, data);\n  t.is(pages[0].content.trim(), \"<p>Ted</p>\");\n  t.is(pages[0].templateContent.trim(), \"<p>Ted</p>\");\n});\n\ntest(\"JavaScript template type (object)\", async (t) => {\n  let tmpl = await getNewTemplate(\"./test/stubs/object.11ty.cjs\", \"./test/stubs/\", \"./dist\");\n\n  let data = await tmpl.getData();\n  t.is(data.name, \"Ted\");\n\n  let pages = await getRenderedTmpls(tmpl, data);\n  t.is(pages[0].content.trim(), \"<p>Ted</p>\");\n  t.is(pages[0].templateContent.trim(), \"<p>Ted</p>\");\n});\n\ntest(\"JavaScript template type (object, no render method)\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/object-norender.11ty.cjs\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n\n  let data = await tmpl.getData();\n  t.is(data.name, \"Ted\");\n\n  let pages = await getRenderedTmpls(tmpl, data);\n  t.is(pages[0].templateContent.trim(), \"\");\n  t.is(pages[0].content.trim(), \"\");\n});\n\ntest(\"JavaScript template type (class, no render method)\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/class-norender.11ty.cjs\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n\n  let data = await tmpl.getData();\n  t.is(data.name, \"Ted\");\n\n  let pages = await getRenderedTmpls(tmpl, data);\n  t.is(pages[0].templateContent.trim(), \"\");\n  t.is(pages[0].content.trim(), \"\");\n});\ntest(\"JavaScript template type (data returns a string)\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/exports-flatdata.11ty.cjs\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n\n  await t.throwsAsync(async () => {\n    await tmpl.getData();\n  });\n});\n"
  },
  {
    "path": "test/TemplateTest.js",
    "content": "import test from \"ava\";\nimport fs from \"fs\";\nimport pretty from \"pretty\";\nimport TOML from \"@iarna/toml\";\n\nimport TemplateData from \"../src/Data/TemplateData.js\";\nimport FileSystemSearch from \"../src/FileSystemSearch.js\";\nimport EleventyExtensionMap from \"../src/EleventyExtensionMap.js\";\nimport EleventyErrorUtil from \"../src/Errors/EleventyErrorUtil.js\";\nimport TemplateContentPrematureUseError from \"../src/Errors/TemplateContentPrematureUseError.js\";\nimport { normalizeNewLines } from \"./Util/normalizeNewLines.js\";\n\nimport getNewTemplate from \"./_getNewTemplateForTests.js\";\nimport { getRenderedTemplates as getRenderedTmpls, renderLayout, renderTemplate } from \"./_getRenderedTemplates.js\";\nimport { getTemplateConfigInstance, getTemplateConfigInstanceCustomCallback } from \"./_testHelpers.js\";\nimport TemplateEngineManager from \"../src/Engines/TemplateEngineManager.js\";\n\nasync function getRenderedData(tmpl, pageNumber = 0) {\n  let data = await tmpl.getData();\n  let templates = await tmpl.getTemplates(data);\n  return templates[pageNumber].data;\n}\n\nfunction cleanHtml(str) {\n  return pretty(str, { ocd: true });\n}\n\nasync function _testCompleteRender(tmpl) {\n  let data = await tmpl.getData();\n  let entries = await tmpl.getTemplateMapEntries(data);\n\n  let nestedContent = await Promise.all(\n    entries.map(async (entry) => {\n      entry._pages = await entry.template.getTemplates(entry.data);\n      return Promise.all(\n        entry._pages.map(async (page) => {\n          page.templateContent = await page.template.renderPageEntryWithoutLayout(page);\n          return page.template.renderPageEntry(page);\n        })\n      );\n    })\n  );\n\n  let contents = [].concat(...nestedContent);\n  return contents;\n}\n\ntest(\"getTemplateSubFolder\", async (t) => {\n  let tmpl = await getNewTemplate(\"./test/stubs/template.liquid\", \"./test/stubs/\", \"./dist\");\n  t.is(tmpl.getTemplateSubfolder(), \"\");\n});\n\ntest(\"getTemplateSubFolder, output is a subdir of input\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/template.liquid\",\n    \"./test/stubs/\",\n    \"./test/stubs/_site\"\n  );\n  t.is(tmpl.getTemplateSubfolder(), \"\");\n});\n\ntest(\"output path maps to an html file\", async (t) => {\n  let tmpl = await getNewTemplate(\"./test/stubs/template.liquid\", \"./test/stubs/\", \"./dist\");\n  t.is(tmpl.inputDir, \"./test/stubs/\");\n  t.is(tmpl.outputDir, \"./dist/\");\n  t.is(tmpl.getTemplateSubfolder(), \"\");\n\n  let data = await tmpl.getData();\n  t.is(await tmpl.getOutputPath(data), \"./dist/template/index.html\");\n});\n\ntest(\"subfolder outputs to a subfolder\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/subfolder/subfolder.liquid\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n  let data = await tmpl.getData();\n  t.is(tmpl.parsed.dir, \"./test/stubs/subfolder\");\n  t.is(tmpl.getTemplateSubfolder(), \"subfolder\");\n  t.is(await tmpl.getOutputPath(data), \"./dist/subfolder/index.html\");\n});\n\ntest(\"subfolder outputs to double subfolder\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/subfolder/subfolder/subfolder.liquid\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n  let data = await tmpl.getData();\n  t.is(tmpl.parsed.dir, \"./test/stubs/subfolder/subfolder\");\n  t.is(tmpl.getTemplateSubfolder(), \"subfolder/subfolder\");\n  t.is(await tmpl.getOutputPath(data), \"./dist/subfolder/subfolder/index.html\");\n});\n\ntest(\"HTML files output to the same as the input directory have a file suffix added (only if index, this is not index).\", async (t) => {\n  let tmpl = await getNewTemplate(\"./test/stubs/testing.html\", \"./test/stubs\", \"./test/stubs\");\n  let data = await tmpl.getData();\n  t.is(await tmpl.getOutputPath(data), \"./test/stubs/testing/index.html\");\n});\n\ntest(\"HTML files output to the same as the input directory have a file suffix added (only if index, this _is_ index).\", async (t) => {\n  let tmpl = await getNewTemplate(\"./test/stubs/index.html\", \"./test/stubs\", \"./test/stubs\");\n  let data = await tmpl.getData();\n  t.is(await tmpl.getOutputPath(data), \"./test/stubs/index.html\");\n});\n\ntest(\"HTML files output to the same as the input directory have a file suffix added (only if index, this _is_ index, subfolder).\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/subfolder/index.html\",\n    \"./test/stubs\",\n    \"./test/stubs\"\n  );\n  let data = await tmpl.getData();\n  t.is(await tmpl.getOutputPath(data), \"./test/stubs/subfolder/index.html\");\n});\n\ntest(\"Test raw front matter from template (yaml)\", async (t) => {\n  // https://github.com/jonschlinkert/gray-matter/blob/master/examples/yaml.js\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/templateFrontMatter.liquid\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n  t.truthy(await tmpl.getInputContent(), \"template exists and can be opened.\");\n\n  t.is((await tmpl._testGetFrontMatter()).data.key1, \"value1\");\n  t.is((await tmpl._testGetFrontMatter()).data.key3, \"value3\");\n\n  let data = await tmpl.getData();\n  t.is(data.key1, \"value1\");\n  t.is(data.key3, \"value3\");\n\n  let pages = await getRenderedTmpls(tmpl, data);\n  t.is(pages[0].templateContent.trim(), \"c:value1:value2:value3\");\n});\n\ntest(\"Test raw front matter from template (json)\", async (t) => {\n  // https://github.com/jonschlinkert/gray-matter/blob/master/examples/json.js\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/templateFrontMatterJson.liquid\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n\n  t.is((await tmpl._testGetFrontMatter()).data.key1, \"value1\");\n  t.is((await tmpl._testGetFrontMatter()).data.key3, \"value3\");\n\n  let data = await tmpl.getData();\n  t.is(data.key1, \"value1\");\n  t.is(data.key3, \"value3\");\n\n  let pages = await getRenderedTmpls(tmpl, data);\n  t.is(pages[0].templateContent.trim(), \"c:value1:value2:value3\");\n});\n\ntest(\"Test raw front matter from template (js)\", async (t) => {\n  // https://github.com/jonschlinkert/gray-matter/blob/master/examples/javascript.js\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/templateFrontMatterJs.njk\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n\n  t.is((await tmpl._testGetFrontMatter()).data.key1, \"value1\");\n  t.is((await tmpl._testGetFrontMatter()).data.key3, \"value3\");\n\n  let data = await tmpl.getData();\n  t.is(data.key1, \"value1\");\n  t.is(data.key3, \"value3\");\n\n  let pages = await getRenderedTmpls(tmpl, data);\n  t.is(pages[0].templateContent.trim(), \"c:value1:VALUE2:value3\");\n});\n\ntest(\"Test that getData() works\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/templateFrontMatter.liquid\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n  let data = await tmpl.getData();\n\n  t.is(data.key1, \"value1\");\n  t.is(data.key3, \"value3\");\n});\n\ntest(\"One Layout (using new content var)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs\",\n      output: \"dist\"\n    }\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/templateWithLayoutKey.liquid\",\n    \"./test/stubs/\",\n    \"dist\",\n    dataObj,\n    null,\n    eleventyConfig\n  );\n\n  t.is((await tmpl._testGetFrontMatter()).data[tmpl.config.keys.layout], \"defaultLayout\");\n\n  let data = await tmpl.getData();\n  t.is(data[tmpl.config.keys.layout], \"defaultLayout\");\n\n  t.is(\n    normalizeNewLines(cleanHtml(await renderLayout(tmpl, data))),\n    `<div id=\"layout\">\n  <p>Hello.</p>\n</div>`\n  );\n\n  t.is(data.keymain, \"valuemain\");\n  t.is(data.keylayout, \"valuelayout\");\n});\n\ntest(\"One Layout (using content)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs\",\n      output: \"dist\"\n    }\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/templateWithLayoutContent.liquid\",\n    \"./test/stubs/\",\n    \"dist\",\n    dataObj,\n    null,\n    eleventyConfig\n  );\n\n  t.is((await tmpl._testGetFrontMatter()).data[tmpl.config.keys.layout], \"defaultLayoutLayoutContent\");\n\n  let data = await tmpl.getData();\n  t.is(data[tmpl.config.keys.layout], \"defaultLayoutLayoutContent\");\n\n  t.is(\n    normalizeNewLines(cleanHtml(await renderLayout(tmpl, data))),\n    `<div id=\"layout\">\n  <p>Hello.</p>\n</div>`\n  );\n\n  t.is(data.keymain, \"valuemain\");\n  t.is(data.keylayout, \"valuelayout\");\n});\n\ntest(\"One Layout (layouts disabled)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs\",\n      output: \"dist\"\n    }\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/templateWithLayoutContent.liquid\",\n    \"./test/stubs/\",\n    \"dist\",\n    dataObj,\n    null,\n    eleventyConfig\n  );\n\n  t.is((await tmpl._testGetFrontMatter()).data[tmpl.config.keys.layout], \"defaultLayoutLayoutContent\");\n\n  let data = await tmpl.getData();\n  t.is(data[tmpl.config.keys.layout], \"defaultLayoutLayoutContent\");\n\n  t.is(cleanHtml(await tmpl.renderPageEntryWithoutLayout({\n    rawInput: await tmpl.getPreRender(),\n    data: data\n  })), \"<p>Hello.</p>\");\n\n  t.is(data.keymain, \"valuemain\");\n  t.is(data.keylayout, \"valuelayout\");\n});\n\ntest(\"One Layout (liquid test)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs\",\n      output: \"dist\"\n    }\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/templateWithLayout.liquid\",\n    \"./test/stubs/\",\n    \"dist\",\n    dataObj,\n    null,\n    eleventyConfig\n  );\n\n  t.is((await tmpl._testGetFrontMatter()).data[tmpl.config.keys.layout], \"layoutLiquid.liquid\");\n\n  let data = await tmpl.getData();\n  t.is(data[tmpl.config.keys.layout], \"layoutLiquid.liquid\");\n\n  t.is(\n    normalizeNewLines(cleanHtml(await renderLayout(tmpl, data))),\n    `<div id=\"layout\">\n  <p>Hello.</p>\n</div>`\n  );\n\n  t.is(data.keymain, \"valuemain\");\n  t.is(data.keylayout, \"valuelayout\");\n});\n\ntest(\"Two Layouts\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs\",\n      output: \"dist\"\n    }\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/templateTwoLayouts.liquid\",\n    \"./test/stubs/\",\n    \"dist\",\n    dataObj,\n    null,\n    eleventyConfig\n  );\n\n  t.is((await tmpl._testGetFrontMatter()).data[tmpl.config.keys.layout], \"layout-a\");\n\n  let data = await tmpl.getData();\n  t.is(data[tmpl.config.keys.layout], \"layout-a\");\n  t.is(data.key1, \"value1\");\n\n  t.is(\n    normalizeNewLines(cleanHtml(await renderLayout(tmpl, data))),\n    `<div id=\"layout-b\">\n  <div id=\"layout-a\">\n    <p>value2-a</p>\n  </div>\n</div>`\n  );\n\n  t.is(data.daysPosted, 152);\n});\n\ntest(\"Liquid template\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs\",\n      output: \"dist\"\n    }\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/formatTest.liquid\",\n    \"./test/stubs/\",\n    \"dist\",\n    dataObj,\n    null,\n    eleventyConfig\n  );\n\n  t.is(await renderTemplate(tmpl, await tmpl.getData()), \"<p>Zach</p>\");\n});\n\ntest(\"Liquid template with include\", async (t) => {\n  let tmpl = await getNewTemplate(\"./test/stubs/includer.liquid\", \"./test/stubs/\", \"dist\");\n\n  t.is((await renderTemplate(tmpl, await tmpl.getData())).trim(), \"<p>This is an include.</p>\");\n});\n\ntest(\"Permalink output directory\", async (t) => {\n  let tmpl = await getNewTemplate(\"./test/stubs/permalinked.liquid\", \"./test/stubs/\", \"./dist\");\n  let data = await tmpl.getData();\n  t.is(await tmpl.getOutputPath(data), \"./dist/permalinksubfolder/index.html\");\n});\n\ntest(\"Permalink output directory from layout\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/permalink-in-layout.liquid\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n  let data = await tmpl.getData();\n  t.is(await tmpl.getOutputPath(data), \"./dist/hello/index.html\");\n});\n\ntest(\"Permalink output directory from layout (fileslug)\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/permalink-in-layout-fileslug.liquid\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n  let data = await tmpl.getData();\n  t.is(await tmpl.getOutputPath(data), \"./dist/test/permalink-in-layout-fileslug/index.html\");\n});\n\ntest(\"Layout from template-data-file that has a permalink (fileslug) Issue #121\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs\",\n      output: \"dist\"\n    }\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/permalink-data-layout/test.njk\",\n    \"./test/stubs/\",\n    \"./dist\",\n    dataObj\n  );\n\n  let data = await tmpl.getData();\n  let renderedTmpl = (await getRenderedTmpls(tmpl, data))[0];\n  t.is(renderedTmpl.templateContent, \"Wrapper:Test 1:test\");\n  t.is(await tmpl.getOutputPath(data), \"./dist/test/index.html\");\n});\n\ntest(\"Fileslug in an 11ty.js template Issue #588\", async (t) => {\n  let tmpl = await getNewTemplate(\"./test/stubs/fileslug.11ty.cjs\", \"./test/stubs/\", \"./dist\");\n\n  let data = await tmpl.getData();\n  let renderedTmpl = (await getRenderedTmpls(tmpl, data))[0];\n  t.is(renderedTmpl.templateContent, \"<p>fileslug</p>\");\n});\n\ntest(\"Local template data file import (without a global data json)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs\",\n      output: \"dist\"\n    }\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  dataObj.setFileSystemSearch(new FileSystemSearch());\n  await dataObj.getGlobalData();\n\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/component/component.njk\",\n    \"./test/stubs/\",\n    \"./dist\",\n    dataObj\n  );\n\n  let data = await tmpl.getData();\n  t.deepEqual(await dataObj.getLocalDataPaths(tmpl.getInputPath()), [\n    \"./test/stubs/stubs.json\",\n    \"./test/stubs/stubs.11tydata.json\",\n    \"./test/stubs/stubs.11tydata.mjs\",\n    \"./test/stubs/stubs.11tydata.cjs\",\n    \"./test/stubs/stubs.11tydata.js\",\n    \"./test/stubs/component/component.json\",\n    \"./test/stubs/component/component.11tydata.json\",\n    \"./test/stubs/component/component.11tydata.mjs\",\n    \"./test/stubs/component/component.11tydata.cjs\",\n    \"./test/stubs/component/component.11tydata.js\",\n  ]);\n  t.is(data.localdatakey1, \"localdatavalue1\");\n  t.is(await renderTemplate(tmpl, data), \"localdatavalue1\");\n});\n\ntest(\"Local template data file import (two subdirectories deep)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs\",\n      output: \"dist\"\n    }\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  dataObj.setFileSystemSearch(new FileSystemSearch());\n  await dataObj.getGlobalData();\n\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/firstdir/seconddir/component.njk\",\n    \"./test/stubs/\",\n    \"./dist\",\n    dataObj\n  );\n\n  t.deepEqual(await dataObj.getLocalDataPaths(tmpl.getInputPath()), [\n    \"./test/stubs/stubs.json\",\n    \"./test/stubs/stubs.11tydata.json\",\n    \"./test/stubs/stubs.11tydata.mjs\",\n    \"./test/stubs/stubs.11tydata.cjs\",\n    \"./test/stubs/stubs.11tydata.js\",\n    \"./test/stubs/firstdir/firstdir.json\",\n    \"./test/stubs/firstdir/firstdir.11tydata.json\",\n    \"./test/stubs/firstdir/firstdir.11tydata.mjs\",\n    \"./test/stubs/firstdir/firstdir.11tydata.cjs\",\n    \"./test/stubs/firstdir/firstdir.11tydata.js\",\n    \"./test/stubs/firstdir/seconddir/seconddir.json\",\n    \"./test/stubs/firstdir/seconddir/seconddir.11tydata.json\",\n    \"./test/stubs/firstdir/seconddir/seconddir.11tydata.mjs\",\n    \"./test/stubs/firstdir/seconddir/seconddir.11tydata.cjs\",\n    \"./test/stubs/firstdir/seconddir/seconddir.11tydata.js\",\n    \"./test/stubs/firstdir/seconddir/component.json\",\n    \"./test/stubs/firstdir/seconddir/component.11tydata.json\",\n    \"./test/stubs/firstdir/seconddir/component.11tydata.mjs\",\n    \"./test/stubs/firstdir/seconddir/component.11tydata.cjs\",\n    \"./test/stubs/firstdir/seconddir/component.11tydata.js\",\n  ]);\n});\n\ntest(\"Posts inherits local JSON, layouts\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs\",\n      output: \"dist\"\n    }\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  dataObj.setFileSystemSearch(new FileSystemSearch());\n  await dataObj.getGlobalData();\n\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/posts/post1.njk\",\n    \"./test/stubs/\",\n    \"./dist\",\n    dataObj\n  );\n\n  let localDataPaths = await dataObj.getLocalDataPaths(tmpl.getInputPath());\n  t.deepEqual(localDataPaths, [\n    \"./test/stubs/stubs.json\",\n    \"./test/stubs/stubs.11tydata.json\",\n    \"./test/stubs/stubs.11tydata.mjs\",\n    \"./test/stubs/stubs.11tydata.cjs\",\n    \"./test/stubs/stubs.11tydata.js\",\n    \"./test/stubs/posts/posts.json\",\n    \"./test/stubs/posts/posts.11tydata.json\",\n    \"./test/stubs/posts/posts.11tydata.mjs\",\n    \"./test/stubs/posts/posts.11tydata.cjs\",\n    \"./test/stubs/posts/posts.11tydata.js\",\n    \"./test/stubs/posts/post1.json\",\n    \"./test/stubs/posts/post1.11tydata.json\",\n    \"./test/stubs/posts/post1.11tydata.mjs\",\n    \"./test/stubs/posts/post1.11tydata.cjs\",\n    \"./test/stubs/posts/post1.11tydata.js\",\n  ]);\n\n  let localData = await dataObj.getTemplateDirectoryData(tmpl.getInputPath());\n  t.is(localData.layout, \"mylocallayout.njk\");\n\n  let globalData = await dataObj.getGlobalData();\n  t.truthy(globalData.pkg);\n\n  let data = await tmpl.getData();\n  t.is(localData.layout, \"mylocallayout.njk\");\n\n  t.is(\n    normalizeNewLines((await renderTemplate(tmpl, data)).trim()),\n    `<div id=\"locallayout\">Post1\n</div>`\n  );\n});\n\ntest(\"Template and folder name are the same, make sure data imports work ok\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs\",\n      output: \"dist\"\n    }\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  dataObj.setFileSystemSearch(new FileSystemSearch());\n  await dataObj.getGlobalData();\n\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/posts/posts.njk\",\n    \"./test/stubs/\",\n    \"./dist\",\n    dataObj\n  );\n\n  let localDataPaths = await dataObj.getLocalDataPaths(tmpl.getInputPath());\n  t.deepEqual(localDataPaths, [\n    \"./test/stubs/stubs.json\",\n    \"./test/stubs/stubs.11tydata.json\",\n    \"./test/stubs/stubs.11tydata.mjs\",\n    \"./test/stubs/stubs.11tydata.cjs\",\n    \"./test/stubs/stubs.11tydata.js\",\n    \"./test/stubs/posts/posts.json\",\n    \"./test/stubs/posts/posts.11tydata.json\",\n    \"./test/stubs/posts/posts.11tydata.mjs\",\n    \"./test/stubs/posts/posts.11tydata.cjs\",\n    \"./test/stubs/posts/posts.11tydata.js\",\n  ]);\n\n  let localData = await dataObj.getTemplateDirectoryData(tmpl.getInputPath());\n  t.is(localData.layout, \"mylocallayout.njk\");\n\n  let globalData = await dataObj.getGlobalData();\n  t.truthy(globalData.pkg);\n\n  let data = await tmpl.getData();\n  t.is(localData.layout, \"mylocallayout.njk\");\n\n  t.is(\n    normalizeNewLines((await renderTemplate(tmpl, data)).trim()),\n    `<div id=\"locallayout\">Posts\n</div>`\n  );\n});\n\ntest(\"Clone the template\", async (t) => {\n  let tmpl = await getNewTemplate(\"./test/stubs/default.liquid\", \"./test/stubs/\", \"./dist\");\n\n  let data = await tmpl.getData();\n\n  let cloned = await tmpl.clone();\n  let clonedData = await cloned.getData();\n\n  t.is(await tmpl.getOutputPath(data), \"./dist/default/index.html\");\n  t.is(await cloned.getOutputPath(clonedData), \"./dist/default/index.html\");\n  t.is(cloned.extensionMap, tmpl.extensionMap);\n});\n\ntest(\"getRenderedData() has all the page variables\", async (t) => {\n  let tmpl = await getNewTemplate(\"./test/stubs/template.liquid\", \"./test/stubs/\", \"./dist\");\n  let data = await getRenderedData(tmpl);\n\n  t.truthy(data.page.url);\n  t.is(data.page.url, \"/template/\");\n  t.is(data.page.fileSlug, \"template\");\n  t.is(data.page.filePathStem, \"/template\");\n  t.truthy(data.page.date.getTime());\n  t.is(data.page.inputPath, \"./test/stubs/template.liquid\");\n  t.is(data.page.outputPath, \"./dist/template/index.html\");\n});\n\ntest(\"Issue #603: page.date Liquid\", async (t) => {\n  let tmpl = await getNewTemplate(\"./test/stubs/pagedate.liquid\", \"./test/stubs/\", \"./dist\");\n  let data = await tmpl.getData();\n\n  t.truthy(data.page.date);\n  t.truthy(data.page.date.toUTCString());\n\n  let pages = await getRenderedTmpls(tmpl, data);\n  t.is(pages[0].templateContent.trim(), data.page.date.toString());\n});\n\ntest(\"Issue #603: page.date Nunjucks\", async (t) => {\n  let tmpl = await getNewTemplate(\"./test/stubs/pagedate.njk\", \"./test/stubs/\", \"./dist\");\n  let data = await tmpl.getData();\n\n  t.truthy(data.page.date);\n  t.truthy(data.page.date.toUTCString());\n\n  let pages = await getRenderedTmpls(tmpl, data);\n  t.is(pages[0].templateContent.trim(), data.page.date.toString());\n});\n\ntest(\"Issue #603: page.date.toUTCString() Nunjucks\", async (t) => {\n  // Note this is not supported in Liquid\n  let tmpl = await getNewTemplate(\"./test/stubs/pagedateutc.njk\", \"./test/stubs/\", \"./dist\");\n  let data = await tmpl.getData();\n\n  t.truthy(data.page.date);\n  t.truthy(data.page.date.toUTCString());\n\n  let pages = await getRenderedTmpls(tmpl, data);\n  t.is(pages[0].templateContent.trim(), data.page.date.toUTCString());\n});\n\ntest(\"getTemplates() data has all the root variables\", async (t) => {\n  let tmpl = await getNewTemplate(\"./test/stubs/template.liquid\", \"./test/stubs/\", \"./dist\");\n  let data = await tmpl.getData();\n  let templates = await tmpl.getTemplates(data);\n\n  t.is(templates[0].url, \"/template/\");\n  t.is(templates[0].fileSlug, \"template\");\n  t.is(templates[0].filePathStem, \"/template\");\n  t.truthy(templates[0].date.getTime());\n  t.is(templates[0].inputPath, \"./test/stubs/template.liquid\");\n  t.is(templates[0].outputPath, \"./dist/template/index.html\");\n});\n\ntest(\"getTemplates() data has all the page variables\", async (t) => {\n  let tmpl = await getNewTemplate(\"./test/stubs/template.liquid\", \"./test/stubs/\", \"./dist\");\n  let data = await tmpl.getData();\n  let templates = await tmpl.getTemplates(data);\n\n  t.is(templates[0].data.page.url, \"/template/\");\n  t.is(templates[0].data.page.fileSlug, \"template\");\n  t.is(templates[0].filePathStem, \"/template\");\n  t.truthy(templates[0].data.page.date.getTime());\n  t.is(templates[0].data.page.inputPath, \"./test/stubs/template.liquid\");\n  t.is(templates[0].data.page.outputPath, \"./dist/template/index.html\");\n});\n\n// Warning `getRenderedTemplates()` is a test function now, so this might be a test testing the tests\ntest(\"getRenderedTemplates() data has all the page variables\", async (t) => {\n  let tmpl = await getNewTemplate(\"./test/stubs/template.liquid\", \"./test/stubs/\", \"./dist\");\n  let data = await tmpl.getData();\n\n  let templates = await getRenderedTmpls(tmpl, data);\n  t.is(templates[0].data.page.url, \"/template/\");\n  t.is(templates[0].data.page.fileSlug, \"template\");\n  t.is(templates[0].filePathStem, \"/template\");\n  t.truthy(templates[0].data.page.date.getTime());\n  t.is(templates[0].data.page.inputPath, \"./test/stubs/template.liquid\");\n  t.is(templates[0].data.page.outputPath, \"./dist/template/index.html\");\n});\n\ntest(\"getRenderedData() has good slug (empty, index)\", async (t) => {\n  let tmpl = await getNewTemplate(\"./test/stubs/index.liquid\", \"./test/stubs/\", \"./dist\");\n  let data = await getRenderedData(tmpl);\n  t.is(data.page.fileSlug, \"\");\n  t.is(data.page.filePathStem, \"/index\");\n});\n\ntest(\"getRenderedData() has good slug\", async (t) => {\n  let tmpl = await getNewTemplate(\"./test/stubs/includer.liquid\", \"./test/stubs/\", \"./dist\");\n  let data = await getRenderedData(tmpl);\n  t.is(data.page.fileSlug, \"includer\");\n  t.is(data.page.filePathStem, \"/includer\");\n});\n\ntest(\"Override base templating engine from .liquid to njk\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/overrides/test-njk.liquid\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n\n  t.is((await renderTemplate(tmpl, await tmpl.getData())).trim(), \"My Title\");\n});\n\ntest(\"Override base templating engine from markdown to 11ty.js, then markdown\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/test-override-js-markdown.11ty.cjs\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n\n  t.is((await renderTemplate(tmpl, await tmpl.getData())).trim(), \"<h1>This is markdown</h1>\");\n});\n\ntest(\"Override base templating engine from .liquid to md\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/overrides/test-md.liquid\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n\n  t.is((await renderTemplate(tmpl, await tmpl.getData())).trim(), \"<h1>My Title</h1>\");\n});\n\ntest(\"Override base templating engine from .liquid to njk,md\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/overrides/test-multiple.md\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n\n  t.is((await renderTemplate(tmpl, await tmpl.getData())).trim(), \"<h1>My Title</h1>\");\n});\n\ntest(\"Override base templating engine from .njk to liquid,md\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/overrides/test-multiple2.njk\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n\n  t.is((await renderTemplate(tmpl, await tmpl.getData())).trim(), \"<h1>My Title</h1>\");\n});\n\ntest(\"Override base templating engine from .html to njk\", async (t) => {\n  let tmpl = await getNewTemplate(\"./test/stubs/overrides/test.html\", \"./test/stubs/\", \"./dist\");\n\n  t.is((await renderTemplate(tmpl, await tmpl.getData())).trim(), \"<h2>My Title</h2>\");\n});\n\ntest(\"Override base templating engine from .html to (nothing)\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/overrides/test-empty.html\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n\n  t.is((await renderTemplate(tmpl, await tmpl.getData())).trim(), \"<h2>{{ title }}</h2>\");\n});\n\ntest(\"Override base templating engine should error with bad string\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/overrides/test-error.njk\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n\n  await t.throwsAsync(async () => {\n    await renderTemplate(tmpl, await tmpl.getData());\n  });\n});\n\ntest(\"Override base templating engine (bypasses markdown)\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/overrides/test-bypass.md\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n\n  t.is((await renderTemplate(tmpl, await tmpl.getData())).trim(), \"# My Title\");\n});\n\ntest(\"Override base templating engine to (nothing)\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/overrides/test-empty.md\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n\n  // not parsed\n  t.is((await renderTemplate(tmpl, await tmpl.getData())).trim(), \"# {{ title }}\");\n});\n\ntest(\"Override base templating engine from .njk to liquid (with a layout that uses njk)\", async (t) => {\n  let tmpl = await getNewTemplate(\"./test/stubs/overrides/layout.njk\", \"./test/stubs/\", \"./dist\");\n\n  t.is((await renderTemplate(tmpl, await tmpl.getData())).trim(), `<div id=\"layoutvalue\"><h2>8</h2></div>`);\n});\n\ntest(\"Override base templating engine from .njk to nothing (with a layout that uses njk)\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/overrides/layoutfalse.njk\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n\n  t.is(\n    (await renderTemplate(tmpl, await tmpl.getData())).trim(),\n    `<div id=\"layoutvalue\"><h2><%= title %></h2></div>`\n  );\n});\n\ntest(\"Using a markdown source file (with a layout that uses njk), markdown shouldn’t render in layout file\", async (t) => {\n  let tmpl = await getNewTemplate(\"./test/stubs/overrides/test.md\", \"./test/stubs/\", \"./dist\");\n\n  t.is(\n    normalizeNewLines((await renderTemplate(tmpl, await tmpl.getData())).trim()),\n    `# Layout header\n\n<div id=\"layoutvalue\"><h1>My Title</h1>\n</div>`\n  );\n});\n\ntest(\"renderDirect on a markdown file, permalink should not render markdown\", async (t) => {\n  let tmpl = await getNewTemplate(\"./test/stubs/permalink-markdown.md\", \"./test/stubs/\", \"./dist\");\n  let data = await tmpl.getData();\n  t.is(\n    await tmpl.renderDirect(\"/news/my-test-file/index.html\", {}, true),\n    \"/news/my-test-file/index.html\"\n  );\n\n  t.is(await tmpl.getRawOutputPath(data), \"/news/my-test-file/index.html\");\n});\n\ntest(\"renderDirect on a markdown file, permalink should not render markdown (with variable)\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/permalink-markdown-var.md\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n  let data = await tmpl.getData();\n  t.is(\n    await tmpl.renderDirect(\"/news/{{ slug }}/index.html\", { slug: \"my-title\" }, true),\n    \"/news/my-title/index.html\"\n  );\n\n  t.is(await tmpl.getRawOutputPath(data), \"/news/my-title/index.html\");\n});\n\ntest(\"renderDirect on a markdown file, permalink should not render markdown (has override)\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/permalink-markdown-override.md\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n  let data = await tmpl.getData();\n  t.is(\n    await tmpl.renderDirect(\"/news/my-test-file/index.html\", {}, true),\n    \"/news/my-test-file/index.html\"\n  );\n\n  t.is(await tmpl.getRawOutputPath(data), \"/news/my-test-file/index.html\");\n});\n\n/* Transforms */\ntest(\"Test a transform\", async (t) => {\n  t.plan(2);\n\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/template.liquid\",\n    \"./test/stubs/\",\n    \"./test/stubs/_site\"\n  );\n\n  tmpl.setTransforms({\n    transformName: function (content, outputPath) {\n      t.true(outputPath.endsWith(\".html\"));\n      return \"OVERRIDE BY A TRANSFORM\";\n    }\n  });\n\n  let renders = await _testCompleteRender(tmpl);\n  t.is(renders[0], \"OVERRIDE BY A TRANSFORM\");\n});\n\n// #789: https://github.com/11ty/eleventy/issues/789\ntest(\"Test a transform (does it have this.inputPath?)\", async (t) => {\n  t.plan(3);\n\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/template.liquid\",\n    \"./test/stubs/\",\n    \"./test/stubs/_site\"\n  );\n\n  tmpl.setTransforms({\n    transformName: function (content, outputPath) {\n      t.true(outputPath.endsWith(\".html\"));\n      t.true(!!this.inputPath);\n      return \"OVERRIDE BY A TRANSFORM\";\n    }\n  });\n\n  let renders = await _testCompleteRender(tmpl);\n  t.is(renders[0], \"OVERRIDE BY A TRANSFORM\");\n});\n\ntest(\"Test a transform with pages\", async (t) => {\n  t.plan(5);\n\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/transform-pages/template.njk\",\n    \"./test/stubs/\",\n    \"./test/stubs/_site\"\n  );\n\n  tmpl.setTransforms({\n    transformName: function (content, outputPath) {\n      // should run twice, one for each page\n      t.true(content.length > 0);\n      t.true(outputPath.endsWith(\".html\"));\n      return \"OVERRIDE BY A TRANSFORM\";\n    }\n  });\n\n  let renders = await _testCompleteRender(tmpl);\n  t.is(renders[0], \"OVERRIDE BY A TRANSFORM\");\n});\n\ntest(\"Test a transform with a layout\", async (t) => {\n  t.plan(3);\n\n  let tmpl = await getNewTemplate(\n    \"./test/stubs-475/transform-layout/transform-layout.njk\",\n    \"./test/stubs-475/\",\n    \"./test/stubs-475/_site\"\n  );\n\n  tmpl.setTransforms({\n    transformName: function (content, outputPath) {\n      t.is(content, \"<html><body>This is content.</body></html>\");\n      t.true(outputPath.endsWith(\".html\"));\n      return \"OVERRIDE BY A TRANSFORM\";\n    }\n  });\n\n  let renders = await _testCompleteRender(tmpl);\n  t.is(renders[0], \"OVERRIDE BY A TRANSFORM\");\n});\n\ntest(\"Test a single asynchronous transform\", async (t) => {\n  t.plan(2);\n\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/template.liquid\",\n    \"./test/stubs/\",\n    \"./test/stubs/_site\"\n  );\n\n  tmpl.setTransforms({\n    transformName: async function (content, outputPath) {\n      t.true(outputPath.endsWith(\"template/index.html\"));\n\n      return new Promise((resolve) => {\n        setTimeout(function (str, outputPath) {\n          resolve(\"OVERRIDE BY A TRANSFORM\");\n        }, 50);\n      });\n    }\n  });\n\n  let renders = await _testCompleteRender(tmpl);\n  t.is(renders[0], \"OVERRIDE BY A TRANSFORM\");\n});\n\ntest(\"Test multiple asynchronous transforms\", async (t) => {\n  t.plan(3);\n\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/template.liquid\",\n    \"./test/stubs/\",\n    \"./test/stubs/_site\"\n  );\n\n  tmpl.setTransforms({\n    transformName1: async function (content, outputPath) {\n      t.true(outputPath.endsWith(\"template/index.html\"));\n\n      return new Promise((resolve, reject) => {\n        setTimeout(function (str, outputPath) {\n          resolve(\"lowercase transform\");\n        }, 50);\n      });\n    },\n    // uppercase\n    transformName2: async function (str, outputPath) {\n      t.true(outputPath.endsWith(\"template/index.html\"));\n\n      return new Promise((resolve, reject) => {\n        setTimeout(function () {\n          resolve(str.toUpperCase());\n        }, 50);\n      });\n    }\n  });\n\n  let renders = await _testCompleteRender(tmpl);\n  t.is(renders[0], \"LOWERCASE TRANSFORM\");\n});\n\ntest(\"Test a linter\", async (t) => {\n  t.plan(4);\n\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/transform-pages/template.njk\",\n    \"./test/stubs/\",\n    \"./test/stubs/_site\"\n  );\n\n  tmpl.addLinter(function (str, inputPath, outputPath) {\n    t.true(inputPath.endsWith(\"template.njk\"));\n    t.true(outputPath.endsWith(\"index.html\"));\n  });\n\n  await _testCompleteRender(tmpl);\n});\n\ntest(\"Front Matter Tags (Single)\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/templatetest-frontmatter/single.njk\",\n    \"./test/stubs/\",\n    \"dist\"\n  );\n  let { data: frontmatter } = await tmpl.getFrontMatterData();\n  t.deepEqual(frontmatter.tags, [\"single-tag\"]);\n\n  let fulldata = await tmpl.getData();\n  t.deepEqual(fulldata.tags, [\"single-tag\"]);\n\n  let pages = await getRenderedTmpls(tmpl, fulldata);\n  t.is(pages[0].templateContent.trim(), \"Has single-tag\");\n});\n\ntest(\"Front Matter Tags (Multiple)\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/templatetest-frontmatter/multiple.njk\",\n    \"./test/stubs/\",\n    \"dist\"\n  );\n  let { data: frontmatter } = await tmpl.getFrontMatterData();\n  t.deepEqual(frontmatter.tags, [\"multi-tag\", \"multi-tag-2\"]);\n\n  let fulldata = await tmpl.getData();\n  t.deepEqual(fulldata.tags, [\"multi-tag\", \"multi-tag-2\"]);\n\n  let pages = await getRenderedTmpls(tmpl, fulldata);\n  t.is(pages[0].templateContent.trim(), \"Has multi-tag-2\");\n});\n\ntest(\"Front matter date with quotes (liquid), issue #258\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/frontmatter-date/test.liquid\",\n    \"./test/stubs/\",\n    \"dist\"\n  );\n\n  let data = await tmpl.getData();\n  t.is(data.mydate.toISOString(), \"2009-04-15T11:34:34.000Z\");\n\n  let pages = await getRenderedTmpls(tmpl, data);\n  t.is(pages[0].templateContent.trim(), \"2009-04-15\");\n});\n\ntest(\"Front matter date with quotes (njk), issue #258\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/frontmatter-date/test.njk\",\n    \"./test/stubs/\",\n    \"dist\"\n  );\n\n  let data = await tmpl.getData();\n  t.is(data.mydate.toISOString(), \"2009-04-15T00:34:34.000Z\");\n\n  let pages = await getRenderedTmpls(tmpl, data);\n  t.is(pages[0].templateContent.trim(), \"2009-04-15T00:34:34.000Z\");\n});\n\ntest(\"Data Cascade (Deep merge)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback({\n    input: \"test\",\n    output: \"dist\"\n  }, function(cfg) {\n    // Default changed in 1.0\n    // cfg.setDataDeepMerge(true);\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  dataObj.setFileSystemSearch(new FileSystemSearch());\n  await dataObj.getGlobalData();\n\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/data-cascade/template.njk\",\n    \"./test/\",\n    \"./dist\",\n    dataObj,\n    null,\n    eleventyConfig\n  );\n\n  let data = await tmpl.getData();\n  t.deepEqual(Object.keys(data).sort(), [\n    \"datafile\",\n    \"eleventy\",\n    \"frontmatter\",\n    \"page\",\n    \"parent\",\n    \"pkg\",\n    \"tags\",\n  ]);\n\n  t.deepEqual(Object.keys(data.parent).sort(), [\"child\", \"datafile\", \"frontmatter\"]);\n\n  t.is(data.parent.child, -2);\n});\n\ntest(\"Data Cascade Tag Merge (Deep merge)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback({\n    input: \"test/stubs\",\n    output: \"dist\"\n  }, function(cfg) {\n    // Default changed in 1.0\n    // cfg.setDataDeepMerge(true);\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  dataObj.setFileSystemSearch(new FileSystemSearch());\n  await dataObj.getGlobalData();\n\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/data-cascade/template.njk\",\n    \"./test/stubs/\",\n    \"./dist\",\n    dataObj,\n    null,\n    eleventyConfig\n  );\n\n  let data = await tmpl.getData();\n  t.deepEqual(data.tags.sort(), [\"tagA\", \"tagB\", \"tagC\", \"tagD\"]);\n});\n\ntest(\"Data Cascade Tag Merge (Deep Merge - Deduplication)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback({\n    input: \"test/stubs\",\n    output: \"dist\"\n  }, function(cfg) {\n    // Default changed in 1.0\n    // cfg.setDataDeepMerge(true);\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  dataObj.setFileSystemSearch(new FileSystemSearch());\n  await dataObj.getGlobalData();\n\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/data-cascade/template.njk\",\n    \"./test/stubs/\",\n    \"./dist\",\n    dataObj,\n    null,\n    eleventyConfig\n  );\n\n  let data = await tmpl.getData();\n  t.deepEqual(data.tags.sort(), [\"tagA\", \"tagB\", \"tagC\", \"tagD\"]);\n});\n\ntest('Local data inherits tags string ([tags] vs \"tags\") Deep Merge', async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback({\n    input: \"test/stubs\",\n    output: \"dist\"\n  }, function(cfg) {\n    // Default changed in 1.0\n    // cfg.setDataDeepMerge(true);\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  dataObj.setFileSystemSearch(new FileSystemSearch());\n  await dataObj.getGlobalData();\n\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/local-data-tags/component.njk\",\n    \"./test/stubs/\",\n    \"./dist\",\n    dataObj,\n    null,\n    eleventyConfig\n  );\n\n  let data = await tmpl.getData();\n  t.deepEqual(data.tags.sort(), [\"tag1\", \"tag2\", \"tag3\"]);\n});\n\ntest(\"Throws a Premature Template Content Error (njk)\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/prematureTemplateContent/test.njk\",\n    \"./test/stubs/\",\n    \"./test/stubs/_site\"\n  );\n\n  let data = await tmpl.getData();\n  let mapEntries = await tmpl.getTemplates(data);\n  let error = t.throws(() => {\n    mapEntries[0].templateContent;\n  });\n  t.is(EleventyErrorUtil.isPrematureTemplateContentError(error), true);\n});\n\ntest(\"Throws a Premature Template Content Error from rendering (njk)\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/prematureTemplateContent/test.njk\",\n    \"./test/stubs/\",\n    \"./test/stubs/_site\"\n  );\n\n  let data = await tmpl.getData();\n  let mapEntries = await tmpl.getTemplateMapEntries(data);\n  let pageEntries = await tmpl.getTemplates({\n    page: {},\n    sample: {\n      get templateContent() {\n        throw new TemplateContentPrematureUseError(\n          \"Tried to use templateContent too early (test.njk)\"\n        );\n      },\n    },\n  });\n  let error = await t.throwsAsync(async () => {\n    await tmpl.renderPageEntry(pageEntries[0]);\n  });\n  t.is(EleventyErrorUtil.isPrematureTemplateContentError(error), true);\n});\n\ntest(\"Throws a Premature Template Content Error (liquid)\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/prematureTemplateContent/test.liquid\",\n    \"./test/stubs/\",\n    \"./test/stubs/_site\"\n  );\n\n  let data = await tmpl.getData();\n  let mapEntries = await tmpl.getTemplates(data);\n  let error = t.throws(() => {\n    mapEntries[0].templateContent;\n  });\n  t.is(EleventyErrorUtil.isPrematureTemplateContentError(error), true);\n});\n\ntest(\"Throws a Premature Template Content Error (11ty.js)\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/prematureTemplateContent/test.11ty.cjs\",\n    \"./test/stubs/\",\n    \"./test/stubs/_site\"\n  );\n\n  let data = await tmpl.getData();\n  let mapEntries = await tmpl.getTemplates(data);\n  let error = t.throws(() => {\n    mapEntries[0].templateContent;\n  });\n  t.is(EleventyErrorUtil.isPrematureTemplateContentError(error), true);\n});\n\ntest(\"Throws a Premature Template Content Error (md)\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/prematureTemplateContent/test.md\",\n    \"./test/stubs/\",\n    \"./test/stubs/_site\"\n  );\n\n  let data = await tmpl.getData();\n  let mapEntries = await tmpl.getTemplates(data);\n  let error = t.throws(() => {\n    mapEntries[0].templateContent;\n  });\n  t.is(EleventyErrorUtil.isPrematureTemplateContentError(error), true);\n});\n\ntest(\"Throws a Premature Template Content Error from rendering (md)\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/prematureTemplateContent/test.md\",\n    \"./test/stubs/\",\n    \"./test/stubs/_site\"\n  );\n\n  let data = await tmpl.getData();\n  let mapEntries = await tmpl.getTemplateMapEntries(data);\n  let pageEntries = await tmpl.getTemplates({\n    page: {},\n    sample: {\n      get templateContent() {\n        throw new TemplateContentPrematureUseError(\n          \"Tried to use templateContent too early (test.md)\"\n        );\n      },\n    },\n  });\n  let error = await t.throwsAsync(async () => {\n    await tmpl.renderPageEntry(pageEntries[0]);\n  });\n  t.is(EleventyErrorUtil.isPrematureTemplateContentError(error), true);\n});\n\ntest(\"Issue 413 weird date format\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs-413/date-frontmatter.md\",\n    \"./test/stubs-413/\",\n    \"./dist\"\n  );\n\n  await t.throwsAsync(async function () {\n    await tmpl.getData();\n  }, {\n    message: \"Data cascade value for `date` (2019-03-13 20:18:42 +0000) is invalid for ./test/stubs-413/date-frontmatter.md\"\n  });\n});\n\ntest(\"Custom Front Matter Parsing Options\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback({}, function(cfg) {\n    cfg.setFrontMatterParsingOptions({\n      excerpt: true,\n    })\n  });\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/custom-frontmatter/template.njk\",\n    \"./test/stubs/\",\n    \"./dist\",\n    undefined,\n    undefined,\n    eleventyConfig,\n  );\n\n  let frontmatter = await tmpl._testGetFrontMatter();\n\n  t.is(frontmatter.data.front, \"hello\");\n\n  t.is(frontmatter.excerpt.trim(), \"This is an excerpt.\");\n  t.is(\n    normalizeNewLines(frontmatter.content.trim()),\n    `This is an excerpt.\nThis is content.`\n  );\n\n  let fulldata = await tmpl.getData();\n  t.is(fulldata.page.excerpt.trim(), \"This is an excerpt.\");\n});\n\ntest(\"Custom Front Matter Parsing Options (using alias)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback({}, function(cfg) {\n    cfg.setFrontMatterParsingOptions({\n      excerpt: true,\n      excerpt_alias: \"my_excerpt\",\n    })\n  });\n\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/custom-frontmatter/template.njk\",\n    \"./test/stubs/\",\n    \"./dist\",\n    undefined,\n    undefined,\n    eleventyConfig,\n  );\n\n  let frontmatter = await tmpl._testGetFrontMatter();\n  t.is(frontmatter.data.front, \"hello\");\n\n  t.is(\n    normalizeNewLines(frontmatter.content.trim()),\n    `This is an excerpt.\nThis is content.`\n  );\n\n  let fulldata = await tmpl.getData();\n  t.is(fulldata.my_excerpt.trim(), \"This is an excerpt.\");\n});\n\ntest(\"Custom Front Matter Parsing Options (no newline before excerpt separator)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback({}, function(cfg) {\n    cfg.setFrontMatterParsingOptions({\n      excerpt: true,\n    })\n  });\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/custom-frontmatter/template-newline1.njk\",\n    \"./test/stubs/\",\n    \"./dist\",\n    undefined,\n    undefined,\n    eleventyConfig,\n  );\n\n  let frontmatter = await tmpl._testGetFrontMatter();\n  t.is(frontmatter.data.front, \"hello\");\n\n  t.is(frontmatter.excerpt.trim(), \"This is an excerpt.\");\n  t.is(\n    normalizeNewLines(frontmatter.content.trim()),\n    `This is an excerpt.\nThis is content.`\n  );\n\n  let fulldata = await tmpl.getData();\n  t.is(fulldata.page.excerpt.trim(), \"This is an excerpt.\");\n});\n\ntest(\"Custom Front Matter Parsing Options (no newline after excerpt separator)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback({}, function(cfg) {\n    cfg.setFrontMatterParsingOptions({\n      excerpt: true,\n    })\n  });\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/custom-frontmatter/template-newline3.njk\",\n    \"./test/stubs/\",\n    \"./dist\",\n    undefined,\n    undefined,\n    eleventyConfig,\n  );\n\n  let frontmatter = await tmpl._testGetFrontMatter();\n  t.is(\n    normalizeNewLines(frontmatter.content.trim()),\n    `This is an excerpt.\nThis is content.`\n  );\n});\n\ntest(\"Custom Front Matter Parsing Options (no newlines before or after excerpt separator)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback({}, function(cfg) {\n    cfg.setFrontMatterParsingOptions({\n      excerpt: true,\n    })\n  });\n\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/custom-frontmatter/template-newline2.njk\",\n    \"./test/stubs/\",\n    \"./dist\",\n    undefined,\n    undefined,\n    eleventyConfig\n  );\n\n  let frontmatter = await tmpl._testGetFrontMatter();\n  t.is(frontmatter.content.trim(), \"This is an excerpt.This is content.\");\n});\n\ntest(\"Custom Front Matter Parsing Options (html comment separator)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback({}, function(cfg) {\n    cfg.setFrontMatterParsingOptions({\n      excerpt: true,\n      excerpt_separator: \"<!-- excerpt -->\",\n    })\n  });\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/custom-frontmatter/template-excerpt-comment.njk\",\n    \"./test/stubs/\",\n    \"./dist\",\n    undefined,\n    undefined,\n    eleventyConfig\n  );\n\n  let frontmatter = await tmpl._testGetFrontMatter();\n  t.is(frontmatter.data.front, \"hello\");\n\n  t.is(frontmatter.excerpt.trim(), \"This is an excerpt.\");\n  t.is(\n    normalizeNewLines(frontmatter.content.trim()),\n    `This is an excerpt.\nThis is content.`\n  );\n\n  let fulldata = await tmpl.getData();\n  t.is(fulldata.page.excerpt.trim(), \"This is an excerpt.\");\n});\n\ntest(\"Custom Front Matter Parsing Options (using TOML)\", async (t) => {\n  // Currently fails on Windows, needs https://github.com/jonschlinkert/gray-matter/issues/92\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/custom-frontmatter/template-toml.njk\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n  tmpl.config.frontMatterParsingOptions = {\n    engines: {\n      toml: TOML.parse.bind(TOML),\n    },\n  };\n\n  let frontmatter = await tmpl._testGetFrontMatter();\n  t.deepEqual(frontmatter.data, {\n    front: \"hello\",\n  });\n  t.is(frontmatter.content.trim(), \"This is content.\");\n\n  let fulldata = await tmpl.getData();\n  t.is(fulldata.front, \"hello\");\n});\n\ntest(\"global variable with dashes Issue #567 (liquid)\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/global-dash-variable.liquid\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n\n  let data = await tmpl.getData();\n  t.is(data[\"is-it-tasty\"], \"Yes\");\n\n  let pages = await getRenderedTmpls(tmpl, data);\n  t.is(pages[0].templateContent.trim(), \"Yes\");\n});\n\ntest(\"Issue #446: Layout has a permalink with a different template language than content\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/layout-permalink-difflang/test.md\",\n    \"./test/stubs/layout-permalink-difflang/\",\n    \"dist\"\n  );\n\n  let data = await tmpl.getData();\n  // this call is needed for page data to be added\n  let pages = await getRenderedTmpls(tmpl, data);\n\n  t.is(data.permalink, \"/{{ page.fileSlug }}/\");\n  t.is(data.page.url, \"/test/\");\n});\n\ntest(\"Get Layout Chain\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs-incremental/layout-chain/test.njk\",\n    \"./test/stubs-incremental/layout-chain/\",\n    \"./dist\"\n  );\n\n  let data = await tmpl.getData();\n  let layout = tmpl.getLayout(data.layout);\n\n  t.deepEqual(await layout.getLayoutChain(), [\n    \"./test/stubs-incremental/layout-chain/_includes/base.njk\",\n    \"./test/stubs-incremental/layout-chain/_includes/parent.njk\",\n  ]);\n});\n\ntest(\"Engine Singletons\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs/engine-singletons\",\n      output: \"dist\"\n    }\n  });\n\n  let map = new EleventyExtensionMap(eleventyConfig);\n  map.engineManager = new TemplateEngineManager(eleventyConfig);\n  map.setFormats([\"njk\"]);\n  let tmpl1 = await getNewTemplate(\n    \"./test/stubs/engine-singletons/first.njk\",\n    \"./test/stubs/engine-singletons/\",\n    \"./dist\",\n    null,\n    map,\n    eleventyConfig\n  );\n  let tmpl2 = await getNewTemplate(\n    \"./test/stubs/engine-singletons/second.njk\",\n    \"./test/stubs/engine-singletons/\",\n    \"./dist\",\n    null,\n    map,\n    eleventyConfig\n  );\n\n  t.deepEqual(tmpl1.engine, tmpl2.engine);\n});\n\ntest(\"Make sure layout cache takes new changes during watch (nunjucks)\", async (t) => {\n  let filePath = \"./test/stubs-layout-cache/_includes/include-script-1.js\";\n\n  fs.writeFileSync(filePath, `alert(\"hi\");`, \"utf8\");\n\n  let tmpl = await getNewTemplate(\n    \"./test/stubs-layout-cache/test.njk\",\n    \"./test/stubs-layout-cache/\",\n    \"./dist\"\n  );\n\n  let data = await tmpl.getData();\n\n  t.is((await renderTemplate(tmpl, data)).trim(), '<script>alert(\"hi\");</script>');\n\n  fs.writeFileSync(filePath, `alert(\"bye\");`, \"utf8\");\n\n  tmpl.config.events.emit(\"eleventy#templateModified\", filePath);\n\n  t.is((await renderTemplate(tmpl, data)).trim(), '<script>alert(\"bye\");</script>');\n});\n\ntest(\"Make sure layout cache takes new changes during watch (liquid)\", async (t) => {\n  let filePath = \"./test/stubs-layout-cache/_includes/include-script-2.js\";\n  fs.writeFileSync(filePath, `alert(\"hi\");`, \"utf8\");\n\n  let tmpl = await getNewTemplate(\n    \"./test/stubs-layout-cache/test.liquid\",\n    \"./test/stubs-layout-cache/\",\n    \"./dist\"\n  );\n\n  let data = await tmpl.getData();\n\n  t.is((await renderTemplate(tmpl, data)).trim(), '<script>alert(\"hi\");</script>');\n\n  fs.writeFileSync(filePath, `alert(\"bye\");`, \"utf8\");\n\n  // Trigger that the file has changed\n  tmpl.eleventyConfig.setPreviousBuildModifiedFile(filePath);\n\n  t.is((await renderTemplate(tmpl, data)).trim(), '<script>alert(\"bye\");</script>');\n});\n\ntest(\"Add Extension via Configuration (txt file)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback({\n    input: \"test/stubs\",\n    output: \"dist\"\n  }, function(cfg) {\n    cfg.addExtension(\"txt\", {\n      isIncrementalMatch: function (incrementalFilePath) {\n        // do some kind of check\n        return this.inputPath === incrementalFilePath;\n      },\n      compile: function (str, inputPath) {\n        // plaintext\n        return function (data) {\n          return str;\n        };\n      },\n    });\n  });\n\n  let map = new EleventyExtensionMap(eleventyConfig);\n  map.engineManager = new TemplateEngineManager(eleventyConfig);\n  map.setFormats([]);\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/default.txt\",\n    \"./test/stubs/\",\n    \"./dist\",\n    null,\n    map,\n    eleventyConfig\n  );\n\n  let extensions = tmpl.getExtensionEntries();\n  t.deepEqual(extensions[0].key, \"txt\");\n  t.deepEqual(extensions[0].extension, \"txt\");\n\n  t.truthy(tmpl.isFileRelevantToThisTemplate(\"./test/stubs/default.txt\"));\n  t.falsy(tmpl.isFileRelevantToThisTemplate(\"./test/stubs/default2.txt\"));\n  t.falsy(tmpl.isFileRelevantToThisTemplate(\"./test/stubs/default.njk\"));\n\n  t.truthy(\n    tmpl.isFileRelevantToThisTemplate(\"./test/stubs/default.txt\", {\n      isFullTemplate: true,\n    })\n  );\n  t.falsy(\n    tmpl.isFileRelevantToThisTemplate(\"./test/stubs/default2.txt\", {\n      isFullTemplate: true,\n    })\n  );\n  t.falsy(\n    tmpl.isFileRelevantToThisTemplate(\"./test/stubs/default.njk\", {\n      isFullTemplate: true,\n    })\n  );\n});\n\ntest(\"permalink object with build\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/permalink-build/permalink-build.md\",\n    \"./test/stubs/\",\n    \"./test/stubs/_site\"\n  );\n  let data = await tmpl.getData();\n  t.is(await tmpl.getRawOutputPath(data), \"/url/index.html\");\n  t.is(await tmpl.getOutputHref(data), \"/url/\");\n});\n\ntest(\"permalink object _getLink\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/permalink-nobuild/permalink-nobuild.md\",\n    \"./test/stubs/\",\n    \"./test/stubs/_site\"\n  );\n\n  let link2 = await tmpl._getLink({\n    permalink: {\n      build: \"/build/\",\n    },\n  });\n  t.is(await link2.toOutputPath(), \"/build/index.html\");\n  t.is(await link2.toHref(), \"/build/\");\n});\n\ntest(\"permalink object _getLink (array of invalid, previously serverless URLs)\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/permalink-nobuild/permalink-nobuild.md\",\n    \"./test/stubs/\",\n    \"./test/stubs/_site\"\n  );\n\n  // Array of URLs is supported\n  let link = await tmpl._getLink({\n    test: \"a\",\n    permalink: {\n      someotherkey: [],\n    },\n  });\n  t.is(await link.toOutputPath(), false);\n  t.is(await link.toHref(), false);\n});\n\ntest(\"Permalink is an object but an empty object (inherit default behavior)\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/permalink-empty-object/empty-object.md\",\n    \"./test/stubs/\",\n    \"./test/stubs/_site\"\n  );\n  let data = await tmpl.getData();\n\n  let outputHref = await tmpl.getOutputHref(data);\n  t.is(outputHref, \"/permalink-empty-object/empty-object/\");\n\n  let outputLink = await tmpl.getRawOutputPath(data);\n  t.is(outputLink, \"permalink-empty-object/empty-object/index.html\");\n\n  let outputPath = await tmpl.getOutputPath(data);\n  t.is(outputPath, \"./test/stubs/_site/permalink-empty-object/empty-object/index.html\");\n\n  let { href, rawPath, path } = await tmpl.getOutputLocations(data);\n  t.is(href, \"/permalink-empty-object/empty-object/\");\n  t.is(rawPath, \"permalink-empty-object/empty-object/index.html\");\n  t.is(path, \"./test/stubs/_site/permalink-empty-object/empty-object/index.html\");\n});\n\ntest(\"eleventyComputed returns permalink object Issue #1898\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/stubs-computed-permalink/eleventycomputed-object.11ty.cjs\",\n    \"./test/stubs/stubs-computed-permalink/\",\n    \"./test/stubs/stubs-computed-permalink/_site\"\n  );\n\n  let data = await tmpl.getData();\n  let [page] = await tmpl.getTemplates(data);\n\n  t.is(page.url, \"/i18n/en/\");\n  t.is(page.outputPath, \"./test/stubs/stubs-computed-permalink/_site/i18n/en/index.html\");\n});\n\ntest(\"eleventyComputed returns nested permalink object Issue #1898\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/stubs-computed-permalink/eleventycomputed-nested-object.11ty.cjs\",\n    \"./test/stubs/stubs-computed-permalink/\",\n    \"./test/stubs/stubs-computed-permalink/_site\"\n  );\n\n  let data = await tmpl.getData();\n  let [page] = await tmpl.getTemplates(data);\n\n  t.is(page.url, \"/i18n/en/\");\n  t.is(page.outputPath, \"./test/stubs/stubs-computed-permalink/_site/i18n/en/index.html\");\n});\n\ntest(\"eleventyComputed returns permalink object using permalink string (with replace) Issue #1898\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/stubs-computed-permalink/eleventycomputed-object-replace.11ty.cjs\",\n    \"./test/stubs/stubs-computed-permalink/\",\n    \"./test/stubs/stubs-computed-permalink/_site\"\n  );\n\n  let data = await tmpl.getData();\n  let [page] = await tmpl.getTemplates(data);\n\n  t.is(page.url, \"/i18n/en/\");\n  t.is(page.outputPath, \"./test/stubs/stubs-computed-permalink/_site/i18n/en/index.html\");\n});\n\ntest(\"page.templateSyntax works with templateEngineOverride\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/overrides/page-templatesyntax.md\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n\n  t.is((await renderTemplate(tmpl, await tmpl.getData())).trim(), \"<p>njk,md</p>\");\n});\n\n// Inspired by https://github.com/11ty/eleventy/pull/1691\ntest(\"Error messaging, returning literals (not objects) from custom data extension\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback({\n    input: \"test/stubs-1691\",\n    output: \"dist\"\n  }, function(cfg) {\n    cfg.addDataExtension(\"txt\", {\n      parser: (s) => s,\n    });\n  });\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n\n  let tmpl = await getNewTemplate(\n    \"./test/stubs-1691/template.njk\",\n    \"./test/stubs-1691/\",\n    \"dist\",\n    dataObj,\n    null,\n    eleventyConfig\n  );\n\n  let data = await tmpl.getData();\n  t.is(data.str, \"Testing\");\n});\n"
  },
  {
    "path": "test/TemplateTest_Permalink.js",
    "content": "import test from \"ava\";\nimport fs from \"fs\";\n\nimport TemplateData from \"../src/Data/TemplateData.js\";\nimport getNewTemplate from \"./_getNewTemplateForTests.js\";\n\nimport { getTemplateConfigInstance } from \"./_testHelpers.js\";\n\nasync function writeMapEntries(mapEntries) {\n  let promises = [];\n  for (let entry of mapEntries) {\n    if (entry.template.behavior.isWriteable()) {\n      promises.push(entry.template._write(entry.outputPath, entry.templateContent));\n    }\n  }\n  return Promise.all(promises);\n}\n\nasync function getTemplateMapEntriesWithContent(template, data) {\n  let entries = await template.getTemplateMapEntries(data);\n\n  return Promise.all(\n    entries.map(async (entry) => {\n      entry._pages = await entry.template.getTemplates(entry.data);\n      await Promise.all(\n        entry._pages.map(async (page) => {\n          page.templateContent = await page.template.renderPageEntryWithoutLayout(page);\n          return page;\n        })\n      );\n      return entry;\n    })\n  );\n}\n\ntest(\"permalink: false\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/permalink-false/test.md\",\n    \"./test/stubs/\",\n    \"./test/stubs/_site\"\n  );\n\n  let data = await tmpl.getData();\n\n  let mapEntries = await getTemplateMapEntriesWithContent(tmpl, data);\n  for (let entry of mapEntries) {\n    t.is(entry.template.behavior.isWriteable(), false);\n    t.is(entry.data.page.url, false);\n    t.is(entry.data.page.outputPath, false);\n  }\n\n  await writeMapEntries(mapEntries);\n\n  // Input file exists (sanity check for paths)\n  t.is(fs.existsSync(\"./test/stubs/permalink-false/\"), true);\n  t.is(fs.existsSync(\"./test/stubs/permalink-false/test.md\"), true);\n\n  // Output does not exist\n  t.is(fs.existsSync(\"./test/stubs/_site/permalink-false/\"), false);\n  t.is(fs.existsSync(\"./test/stubs/_site/permalink-false/test/\"), false);\n  t.is(fs.existsSync(\"./test/stubs/_site/permalink-false/test/index.html\"), false);\n});\n\ntest(\"permalink: false inside of eleventyComputed, Issue #1754\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/permalink-false-computed/test.md\",\n    \"./test/stubs/\",\n    \"./test/stubs/_site\"\n  );\n\n  let data = await tmpl.getData();\n  let mapEntries = await getTemplateMapEntriesWithContent(tmpl, data);\n  for (let entry of mapEntries) {\n    t.is(entry.template.behavior.isWriteable(), false);\n    t.is(entry.data.page.url, false);\n    t.is(entry.data.page.outputPath, false);\n  }\n  await writeMapEntries(mapEntries);\n\n  // Input file exists (sanity check for paths)\n  t.is(fs.existsSync(\"./test/stubs/permalink-false-computed/\"), true);\n  t.is(fs.existsSync(\"./test/stubs/permalink-false-computed/test.md\"), true);\n\n  // Output does not exist\n  t.is(fs.existsSync(\"./test/stubs/_site/permalink-false-computed/\"), false);\n  t.is(fs.existsSync(\"./test/stubs/_site/permalink-false-computed/test/\"), false);\n  t.is(fs.existsSync(\"./test/stubs/_site/permalink-false-computed/test/index.html\"), false);\n});\n\ntest(\"permalink: true\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/permalink-true/permalink-true.md\",\n    \"./test/stubs/\",\n    \"./test/stubs/_site\"\n  );\n\n  let data = await tmpl.getData();\n  await t.throwsAsync(async () => {\n    await tmpl.getRawOutputPath(data);\n  });\n});\n\ntest(\"Disable dynamic permalinks\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/dynamic-permalink/test.njk\",\n    \"./test/stubs/\",\n    \"./test/stubs/_site\"\n  );\n  let data = await tmpl.getData();\n\n  t.is(await tmpl.getRawOutputPath(data), \"/{{justastring}}/index.html\");\n  t.is(await tmpl.getOutputHref(data), \"/{{justastring}}/\");\n  // TODO https://github.com/11ty/eleventy-plugin-webc/issues/32\n  // t.is(data.page.url, \"/{{justastring}}/\")\n});\n\ntest(\"Permalink with variables!\", async (t) => {\n  let tmpl = await getNewTemplate(\"./test/stubs/permalinkdata.njk\", \"./test/stubs/\", \"./dist\");\n\n  let data = await tmpl.getData();\n\n  t.is(await tmpl.getOutputPath(data), \"./dist/subdir/slug-candidate/index.html\");\n});\n\ntest(\"Permalink with variables and JS front matter!\", async (t) => {\n  let tmpl = await getNewTemplate(\"./test/stubs/permalinkdata-jsfn.njk\", \"./test/stubs/\", \"./dist\");\n  let data = await tmpl.getData();\n  t.is(await tmpl.getOutputPath(data), \"./dist/subdir/slug/index.html\");\n});\n\ntest(\"Use a JavaScript function for permalink in any template language\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/permalinkdata-jspermalinkfn.njk\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n\n  let data = await tmpl.getData();\n  t.is(await tmpl.getOutputPath(data), \"./dist/subdir/slug/index.html\");\n});\n\ntest(\"Permalink with dates!\", async (t) => {\n  let tmpl = await getNewTemplate(\"./test/stubs/permalinkdate.liquid\", \"./test/stubs/\", \"./dist\");\n  let data = await tmpl.getData();\n  t.is(await tmpl.getOutputPath(data), \"./dist/2016/01/01/index.html\");\n});\n\ntest.skip(\"Permalink with dates on file name regex!\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/2016-02-01-permalinkdate.liquid\",\n    \"./test/stubs/\",\n    \"./dist\"\n  );\n  let data = await tmpl.getData();\n  t.is(await tmpl.getOutputPath(data), \"./dist/2016/02/01/index.html\");\n});\n\ntest(\"Reuse permalink in directory specific data file\", async (t) => {\n\tlet eleventyConfig = await getTemplateConfigInstance({\n\t\tdir: {\n\t\t\tinput: \"test/stubs\",\n\t\t\toutput: \"dist\"\n\t\t}\n\t});\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  let tmpl = await getNewTemplate(\n    \"./test/stubs/reuse-permalink/test1.liquid\",\n    \"./test/stubs/\",\n    \"./dist\",\n    dataObj\n  );\n  let data = await tmpl.getData();\n  t.is(await tmpl.getOutputPath(data), \"./dist/2016/01/01/index.html\");\n});\n\ntest(\"Using slugify filter!\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/slugify-filter/test.njk\",\n    \"./test/slugify-filter/\",\n    \"./dist\"\n  );\n  let data = await tmpl.getData();\n  t.is(await tmpl.getOutputPath(data), \"./dist/subdir/slug-love-candidate-lyublyu/index.html\");\n});\n\ntest(\"Using slugify filter with comma and apostrophe\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/slugify-filter/comma.njk\",\n    \"./test/slugify-filter/\",\n    \"./dist\"\n  );\n  let data = await tmpl.getData();\n  t.is(await tmpl.getOutputPath(data), \"./dist/subdir/hi-i-m-zach/index.html\");\n});\n\ntest(\"Using slug filter with options params\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/slugify-filter/slug-options.njk\",\n    \"./test/slugify-filter/\",\n    \"./dist\"\n  );\n  let data = await tmpl.getData();\n  t.is(await tmpl.getOutputPath(data), \"./dist/subdir/hi_i_am_zach/index.html\");\n});\n\ntest(\"Using slugify filter with options params\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/slugify-filter/slugify-options.njk\",\n    \"./test/slugify-filter/\",\n    \"./dist\"\n  );\n  let data = await tmpl.getData();\n  t.is(await tmpl.getOutputPath(data), \"./dist/subdir/hi-i-m-z-ach/index.html\");\n});\n\ntest(\"Using slugify filter with a number #854\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/slugify-filter/slugify-number.njk\",\n    \"./test/slugify-filter/\",\n    \"./dist\"\n  );\n  let data = await tmpl.getData();\n  t.is(await tmpl.getOutputPath(data), \"./dist/subdir/1/index.html\");\n});\n\ntest(\"Using slug filter with a number #854\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/slugify-filter/slug-number.njk\",\n    \"./test/slugify-filter/\",\n    \"./dist\"\n  );\n  let data = await tmpl.getData();\n  t.is(await tmpl.getOutputPath(data), \"./dist/subdir/1/index.html\");\n});\n\ntest.skip(\"Using slugify filter with multibyte\", async (t) => {\n  let tmpl = await getNewTemplate(\n    \"./test/slugify-filter/multibyte.njk\",\n    \"./test/slugify-filter/\",\n    \"./dist\"\n  );\n  let data = await tmpl.getData();\n  t.is(await tmpl.getOutputPath(data), \"./dist/subdir/test-猫/index.html\");\n});\n"
  },
  {
    "path": "test/TemplateWriterTest.js",
    "content": "import test from \"ava\";\nimport fs from \"fs\";\nimport { glob } from \"tinyglobby\";\nimport path from \"path\";\n\nimport EleventyExtensionMap from \"../src/EleventyExtensionMap.js\";\nimport { isTypeScriptSupported } from \"../src/Util/FeatureTests.cjs\";\n\nimport { normalizeNewLines } from \"./Util/normalizeNewLines.js\";\nimport { getRenderedTemplates as getRenderedTmpls } from \"./_getRenderedTemplates.js\";\nimport { getTemplateConfigInstance, getTemplateConfigInstanceCustomCallback, getTemplateWriterInstance, deleteDirectory } from \"./_testHelpers.js\";\n\n// TODO make sure if output is a subdir of input dir that they don’t conflict.\ntest(\"Output is a subdir of input\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs/writeTest\",\n      output: \"test/stubs/writeTest/_writeTestSite\"\n    }\n  });\n\n  let { templateWriter: tw, eleventyFiles: evf } = getTemplateWriterInstance([\"liquid\", \"md\"], eleventyConfig);\n\n  let files = await glob(evf.getFileGlobs());\n  t.deepEqual(evf.getRawFiles(), [\"./test/stubs/writeTest/**/*.{liquid,md}\"]);\n  t.true(files.length > 0);\n\n  let { template: tmpl } = tw._createTemplate(files[0]);\n  t.is(tmpl.inputDir, \"./test/stubs/writeTest/\");\n\n  let data = await tmpl.getData();\n  t.is(await tmpl.getOutputPath(data), \"./test/stubs/writeTest/_writeTestSite/test/index.html\");\n});\n\ntest(\"_createTemplateMap\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs/writeTest\",\n      output: \"test/stubs/writeTest/_writeTestSite\"\n    }\n  });\n\n  let { templateWriter: tw } = getTemplateWriterInstance([\"liquid\", \"md\"], eleventyConfig);\n\n  let paths = await tw._getAllPaths();\n  t.true(paths.length > 0);\n  t.is(paths[0], \"./test/stubs/writeTest/test.md\");\n\n  let templateMap = await tw._createTemplateMap(paths);\n  let map = templateMap.getMap();\n  t.true(map.length > 0);\n  t.truthy(map[0].template);\n  t.truthy(map[0].data);\n});\n\ntest(\"_createTemplateMap (no leading dot slash)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs/writeTest\",\n      output: \"test/stubs/_writeTestSite\"\n    }\n  });\n\n  let { templateWriter: tw } = getTemplateWriterInstance([\"liquid\", \"md\"], eleventyConfig);\n\n  let paths = await tw._getAllPaths();\n  t.true(paths.length > 0);\n  t.is(paths[0], \"./test/stubs/writeTest/test.md\");\n});\n\ntest(\"_testGetCollectionsData\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs/collection\",\n      output: \"test/stubs/_site\"\n    }\n  });\n\n  let { templateWriter: tw } = getTemplateWriterInstance([\"md\"], eleventyConfig);\n\n  let paths = await tw._getAllPaths();\n  let templateMap = await tw._createTemplateMap(paths);\n  let collectionsData = await templateMap._testGetCollectionsData();\n  t.is(collectionsData.post.length, 2);\n  t.is(collectionsData.cat.length, 2);\n  t.is(collectionsData.dog.length, 1);\n});\n\n// TODO remove this (used by other test things)\ntest(\"_testGetAllTags\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs/collection\",\n      output: \"test/stubs/_site\"\n    }\n  });\n\n  let { templateWriter: tw } = getTemplateWriterInstance([\"md\"], eleventyConfig);\n\n  let paths = await tw._getAllPaths();\n  let templateMap = await tw._createTemplateMap(paths);\n  let tags = templateMap._testGetAllTags();\n\n  t.deepEqual(tags.sort(), [\"cat\", \"dog\", \"post\", \"office\"].sort());\n});\n\ntest(\"Collection of files sorted by date\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs/dates\",\n      output: \"test/stubs/_site\"\n    }\n  });\n\n  let { templateWriter: tw } = getTemplateWriterInstance(\n    [\"md\"],\n    eleventyConfig,\n  );\n\n  let paths = await tw._getAllPaths();\n  let templateMap = await tw._createTemplateMap(paths);\n  let collectionsData = await templateMap._testGetCollectionsData();\n  t.is(collectionsData.dateTestTag.length, 6);\n});\n\ntest(\"__testGetCollectionsData with custom collection (ascending)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback({\n    input: \"test/stubs/collection2\",\n    output: \"test/stubs/_site\"\n  }, function(config) {\n    config.addCollection(\"customPostsAsc\", function (collection) {\n      return collection.getFilteredByTag(\"post\").sort(function (a, b) {\n        return a.date - b.date;\n      });\n    });\n  });\n\n  let { templateWriter: tw } = getTemplateWriterInstance([\"md\"], eleventyConfig);\n  let paths = await tw._getAllPaths();\n  let templateMap = await tw._createTemplateMap(paths);\n  let collectionsData = await templateMap._testGetCollectionsData();\n  t.is(collectionsData.customPostsAsc.length, 2);\n  t.is(path.parse(collectionsData.customPostsAsc[0].inputPath).base, \"test1.md\");\n  t.is(path.parse(collectionsData.customPostsAsc[1].inputPath).base, \"test2.md\");\n});\n\ntest(\"__testGetCollectionsData with custom collection (descending)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback({\n    input: \"test/stubs/collection2\",\n    output: \"test/stubs/_site\"\n  }, function(eleventyConfig) {\n    eleventyConfig.addCollection(\"customPosts\", function (collection) {\n      return collection.getFilteredByTag(\"post\").sort(function (a, b) {\n        return b.date - a.date;\n      });\n    });\n  });\n\n\n  let { templateWriter: tw } = getTemplateWriterInstance([\"md\"], eleventyConfig);\n\n  let paths = await tw._getAllPaths();\n  let templateMap = await tw._createTemplateMap(paths);\n  let collectionsData = await templateMap._testGetCollectionsData();\n\n  t.is(collectionsData.customPosts.length, 2);\n  t.is(path.parse(collectionsData.customPosts[0].inputPath).base, \"test2.md\");\n  t.is(path.parse(collectionsData.customPosts[1].inputPath).base, \"test1.md\");\n});\n\ntest(\"__testGetCollectionsData with custom collection (filter only to markdown input)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback({\n    input: \"test/stubs/collection2\",\n    output: \"test/stubs/_site\"\n  }, function(config) {\n    config.addCollection(\"onlyMarkdown\", function (collection) {\n      return collection.getAllSorted().filter(function (item) {\n        let extension = item.inputPath.split(\".\").pop();\n        return extension === \"md\";\n      });\n    });\n  });\n\n  let { templateWriter: tw } = getTemplateWriterInstance([\"md\"], eleventyConfig);\n\n\n  let paths = await tw._getAllPaths();\n  let templateMap = await tw._createTemplateMap(paths);\n  let collectionsData = await templateMap._testGetCollectionsData();\n  t.is(collectionsData.onlyMarkdown.length, 2);\n  t.is(path.parse(collectionsData.onlyMarkdown[0].inputPath).base, \"test1.md\");\n  t.is(path.parse(collectionsData.onlyMarkdown[1].inputPath).base, \"test2.md\");\n});\n\ntest(\"Pagination with a Collection\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs/paged/collection\",\n      output: \"test/stubs/_site\"\n    }\n  });\n\n  let { templateWriter: tw } = getTemplateWriterInstance([\"njk\"], eleventyConfig);\n\n  let paths = await tw._getAllPaths();\n  let templateMap = await tw._createTemplateMap(paths);\n\n  let collectionsData = await templateMap._testGetCollectionsData();\n  t.is(collectionsData.tag1.length, 3);\n  t.is(collectionsData.pagingtag.length, 1);\n\n  let mapEntry = templateMap.getMapEntryForInputPath(\"./test/stubs/paged/collection/main.njk\");\n  t.truthy(mapEntry);\n  t.is(mapEntry.inputPath, \"./test/stubs/paged/collection/main.njk\");\n  t.is(mapEntry._pages.length, 2);\n  t.is(mapEntry._pages[0].outputPath, \"./test/stubs/_site/main/index.html\");\n  t.is(mapEntry._pages[1].outputPath, \"./test/stubs/_site/main/1/index.html\");\n\n  t.is(mapEntry._pages[0].templateContent.trim(), \"<ol><li>/test1/</li><li>/test2/</li></ol>\");\n  t.is(mapEntry._pages[1].templateContent.trim(), \"<ol><li>/test3/</li></ol>\");\n});\n\ntest(\"Pagination with a Collection from another Paged Template\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs/paged/cfg-collection-tag-cfg-collection\",\n      output: \"test/stubs/_site\"\n    }\n  });\n\n  let { templateWriter: tw } = getTemplateWriterInstance([\"njk\"], eleventyConfig);\n\n  let paths = await tw._getAllPaths();\n  let templateMap = await tw._createTemplateMap(paths);\n\n  let collectionsData = await templateMap._testGetCollectionsData();\n  t.is(collectionsData.tag1.length, 3);\n  t.is(collectionsData.pagingtag.length, 2);\n\n  let map1 = templateMap.getMapEntryForInputPath(\n    \"./test/stubs/paged/cfg-collection-tag-cfg-collection/paged-main.njk\",\n  );\n  t.is(map1._pages[0].templateContent.trim(), \"<ol><li>/test1/</li><li>/test2/</li></ol>\");\n  t.is(map1._pages[1].templateContent.trim(), \"<ol><li>/test3/</li></ol>\");\n\n  let map2 = templateMap.getMapEntryForInputPath(\n    \"./test/stubs/paged/cfg-collection-tag-cfg-collection/paged-downstream.njk\",\n  );\n  t.is(map2._pages[0].templateContent.trim(), \"<ol><li>/paged-main/</li></ol>\");\n  t.is(map2._pages[1].templateContent.trim(), \"<ol><li>/paged-main/1/</li></ol>\");\n});\n\ntest(\"Pagination with a Collection (apply all pages to collections)\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs/paged/collection-apply-to-all\",\n      output: \"test/stubs/_site\"\n    }\n  });\n\n  let { templateWriter: tw } = getTemplateWriterInstance([\"njk\"], eleventyConfig);\n\n  let paths = await tw._getAllPaths();\n  let templateMap = await tw._createTemplateMap(paths);\n\n  let collectionsData = await templateMap._testGetCollectionsData();\n  t.is(collectionsData.tag1.length, 3);\n  t.is(collectionsData.pagingtag.length, 2);\n\n  let mapEntry = templateMap.getMapEntryForInputPath(\n    \"./test/stubs/paged/collection-apply-to-all/main.njk\",\n  );\n  t.truthy(mapEntry);\n  t.is(mapEntry.inputPath, \"./test/stubs/paged/collection-apply-to-all/main.njk\");\n\n  let { template: mainTmpl } = tw._createTemplate(\n    \"./test/stubs/paged/collection-apply-to-all/main.njk\",\n  );\n  let data = await mainTmpl.getData();\n  let outputPath = await mainTmpl.getOutputPath(data);\n  t.is(outputPath, \"./test/stubs/_site/main/index.html\");\n\n  let templates = await getRenderedTmpls(mapEntry.template, mapEntry.data);\n  t.is(templates.length, 2);\n  t.is(\n    await templates[0].template.getOutputPath(templates[0].data),\n    \"./test/stubs/_site/main/index.html\",\n  );\n  t.is(templates[0].outputPath, \"./test/stubs/_site/main/index.html\");\n  t.is(\n    await templates[1].template.getOutputPath(templates[1].data),\n    \"./test/stubs/_site/main/1/index.html\",\n  );\n  t.is(templates[1].outputPath, \"./test/stubs/_site/main/1/index.html\");\n\n  // test content\n  t.is(templates[0].templateContent.trim(), \"<ol><li>/test1/</li><li>/test2/</li></ol>\");\n  t.is(templates[1].templateContent.trim(), \"<ol><li>/test3/</li></ol>\");\n});\n\ntest(\"Use a collection inside of a template\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs/collection-template\",\n      output: \"test/stubs/collection-template/_site\"\n    }\n  });\n\n  let { templateWriter: tw } = getTemplateWriterInstance([\"liquid\"], eleventyConfig);\n\n  let paths = await tw._getAllPaths();\n  let templateMap = await tw._createTemplateMap(paths);\n\n  let collectionsData = await templateMap._testGetCollectionsData();\n  t.is(collectionsData.dog.length, 1);\n\n  let mapEntry = templateMap.getMapEntryForInputPath(\n    \"./test/stubs/collection-template/template.liquid\",\n  );\n  t.truthy(mapEntry);\n  t.is(mapEntry.inputPath, \"./test/stubs/collection-template/template.liquid\");\n\n  let { template: mainTmpl } = tw._createTemplate(\n    \"./test/stubs/collection-template/template.liquid\",\n  );\n  let data = await mainTmpl.getData();\n  let outputPath = await mainTmpl.getOutputPath(data);\n  t.is(outputPath, \"./test/stubs/collection-template/_site/template/index.html\");\n\n  let templates = await getRenderedTmpls(mapEntry.template, mapEntry.data);\n\n  // test content\n  t.is(\n    normalizeNewLines(templates[0].templateContent.trim()),\n    `Layout\n\nTemplate\n\nAll 2 templates\nTemplate 1 dog`,\n  );\n});\n\ntest(\"Use a collection inside of a layout\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs/collection-layout\",\n      output: \"test/stubs/collection-layout/_site\"\n    }\n  });\n\n  let { templateWriter: tw } = getTemplateWriterInstance([\"liquid\"], eleventyConfig);\n\n  let paths = await tw._getAllPaths();\n  let templateMap = await tw._createTemplateMap(paths);\n\n  let collectionsData = await templateMap._testGetCollectionsData();\n  t.is(collectionsData.dog.length, 1);\n\n  let mapEntry = templateMap.getMapEntryForInputPath(\n    \"./test/stubs/collection-layout/template.liquid\",\n  );\n  t.truthy(mapEntry);\n  t.is(mapEntry.inputPath, \"./test/stubs/collection-layout/template.liquid\");\n\n  let { template: mainTmpl } = tw._createTemplate(\"./test/stubs/collection-layout/template.liquid\");\n  let data = await mainTmpl.getData();\n  let outputPath = await mainTmpl.getOutputPath(data);\n  t.is(outputPath, \"./test/stubs/collection-layout/_site/template/index.html\");\n\n  let templates = await getRenderedTmpls(mapEntry.template, mapEntry.data);\n\n  // test content\n  t.is(\n    normalizeNewLines(templates[0].templateContent.trim()),\n    `Layout\n\nTemplate\n\nAll 2 templates\nLayout 1 dog`,\n  );\n});\n\ntest(\"Glob Watcher Files with Passthroughs\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs\",\n      output: \"test/stubs/_site\"\n    }\n  });\n\n  let { templateWriter: tw } = getTemplateWriterInstance([\"njk\", \"png\"], eleventyConfig);\n\n  t.deepEqual(tw.getPassthroughGlobs(), [\"./test/stubs/**/*.png\"]);\n});\n\ntest(\"Pagination and TemplateContent\", async (t) => {\n  deleteDirectory(\"./test/stubs/pagination-templatecontent/_site/\");\n\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs/pagination-templatecontent\",\n      output: \"test/stubs/pagination-templatecontent/_site\"\n    }\n  });\n\n  let { templateWriter: tw } = getTemplateWriterInstance([\"njk\", \"md\"], eleventyConfig);\n\n  tw.setVerboseOutput(false);\n  await tw.write();\n\n  let content = fs.readFileSync(\"./test/stubs/pagination-templatecontent/_site/index.html\", \"utf8\");\n  t.is(\n    content.trim(),\n    `<h1>Post 1</h1>\n<h1>Post 2</h1>`,\n  );\n\n  deleteDirectory(\"./test/stubs/pagination-templatecontent/_site/\");\n});\n\ntest(\"Custom collection returns array\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback({\n    input: \"test/stubs/collection2\",\n    output: \"test/stubs/_site\"\n  }, function(config) {\n    config.addCollection(\"returnAllInputPaths\", function (collection) {\n      return collection.getAllSorted().map(function (item) {\n        return item.inputPath;\n      });\n    });\n  });\n\n  let { templateWriter: tw } = getTemplateWriterInstance([\"md\"], eleventyConfig);\n\n  let paths = await tw._getAllPaths();\n  let templateMap = await tw._createTemplateMap(paths);\n  let collectionsData = await templateMap._testGetCollectionsData();\n  t.is(collectionsData.returnAllInputPaths.length, 2);\n  t.is(path.parse(collectionsData.returnAllInputPaths[0]).base, \"test1.md\");\n  t.is(path.parse(collectionsData.returnAllInputPaths[1]).base, \"test2.md\");\n});\n\ntest(\"Custom collection returns a string\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback({\n    input: \"test/stubs/collection2\",\n    output: \"test/stubs/_site\"\n  }, function(config) {\n    config.addCollection(\"returnATestString\", function () {\n      return \"test\";\n    });\n  });\n\n  let { templateWriter: tw } = getTemplateWriterInstance([\"md\"], eleventyConfig);\n  let paths = await tw._getAllPaths();\n  let templateMap = await tw._createTemplateMap(paths);\n  let collectionsData = await templateMap._testGetCollectionsData();\n  t.is(collectionsData.returnATestString, \"test\");\n});\n\ntest(\"Custom collection returns an object\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback({\n    input: \"test/stubs/collection2\",\n    output: \"test/stubs/_site\"\n  }, function(config) {\n    config.addCollection(\"returnATestObject\", function () {\n      return { test: \"value\" };\n    });\n  });\n\n  let { templateWriter: tw } = getTemplateWriterInstance([\"md\"], eleventyConfig);\n  let paths = await tw._getAllPaths();\n  let templateMap = await tw._createTemplateMap(paths);\n  let collectionsData = await templateMap._testGetCollectionsData();\n  t.deepEqual(collectionsData.returnATestObject, { test: \"value\" });\n});\n\ntest(\"fileSlug should exist in a collection\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs/collection-slug\",\n      output: \"test/stubs/collection-slug/_site\"\n    }\n  });\n\n  let { templateWriter: tw } = getTemplateWriterInstance([\"njk\"], eleventyConfig);\n\n  let paths = await tw._getAllPaths();\n  let templateMap = await tw._createTemplateMap(paths);\n\n  let collectionsData = await templateMap._testGetCollectionsData();\n  t.is(collectionsData.dog.length, 1);\n\n  let mapEntry = templateMap.getMapEntryForInputPath(\"./test/stubs/collection-slug/template.njk\");\n  t.truthy(mapEntry);\n  t.is(mapEntry.inputPath, \"./test/stubs/collection-slug/template.njk\");\n\n  let templates = await getRenderedTmpls(mapEntry.template, mapEntry.data);\n  t.is(templates[0].templateContent.trim(), \"fileSlug:/dog1/:dog1\");\n});\n\ntest(\"Write Test 11ty.js\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs/writeTestJS\",\n      output: \"test/stubs/_writeTestJSSite\"\n    }\n  });\n\n  let { templateWriter: tw, eleventyFiles: evf } = getTemplateWriterInstance([\"11ty.js\"], eleventyConfig);\n\n  let files = await glob(evf.getFileGlobs());\n  t.deepEqual(evf.getRawFiles(), [`./test/stubs/writeTestJS/**/*.{11ty.js,11ty.cjs,11ty.mjs${isTypeScriptSupported() ? \",11ty.ts,11ty.cts,11ty.mts\" : \"\"}}`]);\n  t.deepEqual(files, [\"test/stubs/writeTestJS/test.11ty.cjs\"]);\n\n  let { template: tmpl } = tw._createTemplate(files[0]);\n  let data = await tmpl.getData();\n  t.is(await tmpl.getOutputPath(data), \"./test/stubs/_writeTestJSSite/test/index.html\");\n});\n\ntest.skip(\"Markdown with alias\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs/writeTestMarkdown\",\n      output: \"test/stubs/_writeTestMarkdownSite\"\n    }\n  });\n\n  let map = new EleventyExtensionMap(eleventyConfig);\n  map.setFormats([\"md\"]);\n  map.config = {\n    templateExtensionAliases: {\n      markdown: \"md\",\n    },\n  };\n\n  let { templateWriter: tw, eleventyFiles: evf } = getTemplateWriterInstance([\"md\"], eleventyConfig);\n  evf._setExtensionMap(map);\n  evf.init();\n\n  let files = await glob(evf.getFileGlobs());\n  t.deepEqual(evf.getRawFiles(), [\n    \"./test/stubs/writeTestMarkdown/**/*.md\",\n    \"./test/stubs/writeTestMarkdown/**/*.markdown\",\n  ]);\n  t.true(files.indexOf(\"./test/stubs/writeTestMarkdown/sample.md\") > -1);\n  t.true(files.indexOf(\"./test/stubs/writeTestMarkdown/sample2.markdown\") > -1);\n\n  let { template: tmpl } = tw._createTemplate(files[0]);\n  tmpl._setExtensionMap(map);\n  t.is(await tmpl.getOutputPath(), \"./test/stubs/_writeTestMarkdownSite/sample/index.html\");\n\n  let { template: tmpl2 } = tw._createTemplate(files[1]);\n  tmpl2._setExtensionMap(map);\n  t.is(await tmpl2.getOutputPath(), \"./test/stubs/_writeTestMarkdownSite/sample2/index.html\");\n});\n\ntest.skip(\"JavaScript with alias\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n    dir: {\n      input: \"test/stubs/writeTestJS\",\n      output: \"test/stubs/_writeTestJSSite\"\n    }\n  });\n\n  let map = new EleventyExtensionMap(eleventyConfig);\n  map.setFormats([\"11ty.js\"]);\n  map.config = {\n    templateExtensionAliases: {\n      js: \"11ty.js\",\n    },\n  };\n\n  let { templateWriter: tw, eleventyFiles: evf } = getTemplateWriterInstance([\"11ty.js\"], eleventyConfig);\n\n  evf._setExtensionMap(map);\n  evf.init();\n\n  let files = await glob(evf.getFileGlobs());\n  t.deepEqual(\n    evf.getRawFiles().sort(),\n    [\"./test/stubs/writeTestJS/**/*.11ty.js\", \"./test/stubs/writeTestJS/**/*.js\"].sort(),\n  );\n  t.deepEqual(\n    files.sort(),\n    [\"./test/stubs/writeTestJS/sample.js\", \"./test/stubs/writeTestJS/test.11ty.js\"].sort(),\n  );\n\n  let { template: tmpl } = tw._createTemplate(files[0]);\n  t.is(await tmpl.getOutputPath(), \"./test/stubs/_writeTestJSSite/test/index.html\");\n});\n\ntest(\"Passthrough file output\", async (t) => {\n  deleteDirectory(\"./test/stubs/template-passthrough/_site/\");\n\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback({\n    input: \"test/stubs/template-passthrough\",\n    output: \"test/stubs/template-passthrough/_site\"\n  }, function(cfg){\n    cfg.addPassthroughCopy(\"./test/stubs/template-passthrough/static\");\n    cfg.addPassthroughCopy({\n\t\t\t\"./test/stubs/template-passthrough/static/\": \"./\",\n    });\n    cfg.addPassthroughCopy({\n\t\t\t\"./test/stubs/template-passthrough/static/**/*\": \"./all/\",\n    });\n    cfg.addPassthroughCopy({\n\t\t\t\"./test/stubs/template-passthrough/static/**/*.js\": \"./js/\",\n    });\n\n    // cfg.passthroughCopies = {\n    //   \"./test/stubs/template-passthrough/static\": {\n    //     outputPath: true,\n    //   },\n    //   \"./test/stubs/template-passthrough/static/\": {\n    //     outputPath: \"./\",\n    //   },\n    //   \"./test/stubs/template-passthrough/static/**/*\": {\n    //     outputPath: \"./all/\",\n    //   },\n    //   \"./test/stubs/template-passthrough/static/**/*.js\": {\n    //     outputPath: \"./js/\",\n    //   },\n    // };\n  });\n\n  let { templateWriter: tw } = getTemplateWriterInstance([\"njk\", \"md\"], eleventyConfig);\n\n  await tw.write();\n\n  const output = [\n    \"./test/stubs/template-passthrough/_site/static/nested/test-nested.css\",\n    \"./test/stubs/template-passthrough/_site/all/test.js\",\n    \"./test/stubs/template-passthrough/_site/all/test.css\",\n    \"./test/stubs/template-passthrough/_site/all/test-nested.css\",\n    \"./test/stubs/template-passthrough/_site/js/\",\n    \"./test/stubs/template-passthrough/_site/js/test.js\",\n    \"./test/stubs/template-passthrough/_site/nested/\",\n    \"./test/stubs/template-passthrough/_site/nested/test-nested.css\",\n    \"./test/stubs/template-passthrough/_site/test.css\",\n    \"./test/stubs/template-passthrough/_site/test.js\",\n  ];\n\n  for (let path of output) {\n    t.true(fs.existsSync(path));\n  }\n\n  deleteDirectory(\"./test/stubs/template-passthrough/_site/\");\n});\n"
  },
  {
    "path": "test/TestUtilityTest.js",
    "content": "import test from \"ava\";\nimport { normalizeNewLines } from \"./Util/normalizeNewLines.js\";\n\ntest(\"normalizeNewLines\", (t) => {\n  t.is(normalizeNewLines(\"\\n\"), \"\\n\");\n  t.is(normalizeNewLines(\"\\r\\n\"), \"\\n\");\n  t.is(normalizeNewLines(\"\\r\\n\\n\"), \"\\n\\n\");\n  t.is(normalizeNewLines(\"\\r\\n\\r\\n\"), \"\\n\\n\");\n  t.is(normalizeNewLines(\"a\\r\\nhello\\r\\nhi\"), \"a\\nhello\\nhi\");\n});\n"
  },
  {
    "path": "test/TransformsUtilTest.js",
    "content": "import test from \"ava\";\nimport TransformsUtil from \"../src/Util/TransformsUtil.js\";\n\ntest(\"TransformsUtil.runall\", async (t) => {\n  t.is(await TransformsUtil.runAll(\"Test content\", {}, {\n    test: function(content) {\n      return content + \"Overridden!\"\n    }\n  }), \"Test contentOverridden!\");\n});\n\ntest(\"TransformsUtil.runall empty warning\", async (t) => {\n  t.plan(2);\n  t.is(await TransformsUtil.runAll(\"Test content\", {\n    inputPath: \"fake input path\",\n    outputPath: \"fake output path\",\n  }, {\n    test: function() {\n      return \"\";\n    }\n  },\n  {\n    logger: {\n      warn: (message) => {\n        t.is(message, 'Warning: Transform `test` returned empty when writing fake output path from fake input path.');\n      }\n    }\n  }), \"\");\n});\n"
  },
  {
    "path": "test/UrlTest.js",
    "content": "import test from \"ava\";\nimport TemplateConfig from \"../src/TemplateConfig.js\";\nimport url from \"../src/Filters/Url.js\";\n\ntest(\"Test url filter passing in pathPrefix from config\", async (t) => {\n  let eleventyConfig = new TemplateConfig();\n  await eleventyConfig.init();\n\n  let pp = eleventyConfig.getConfig().pathPrefix;\n  t.is(pp, \"/\");\n\n  t.is(url(\"test\", pp), \"test\");\n  t.is(url(\"/test\", pp), \"/test\");\n});\n\ntest(\"Test url filter without passing in pathPrefix\", async (t) => {\n  let eleventyConfig = new TemplateConfig();\n  await eleventyConfig.init();\n\n  let urlFilter = eleventyConfig.userConfig.getFilter(\"url\");\n\n  t.is(urlFilter(\"test\"), \"test\");\n  t.is(urlFilter(\"/test\"), \"/test\");\n});\n\ntest(\"Test url filter with passthrough urls\", (t) => {\n  // via https://gist.github.com/mxpv/034933deeebb26b62f14\n  t.is(url(\"http://foo.com/blah_blah\", \"\"), \"http://foo.com/blah_blah\");\n  t.is(url(\"http://foo.com/blah_blah/\", \"\"), \"http://foo.com/blah_blah/\");\n  t.is(url(\"http://foo.com/blah_blah_(wikipedia)\", \"\"), \"http://foo.com/blah_blah_(wikipedia)\");\n  t.is(\n    url(\"http://foo.com/blah_blah_(wikipedia)_(again)\", \"\"),\n    \"http://foo.com/blah_blah_(wikipedia)_(again)\"\n  );\n  t.is(url(\"http://www.example.com/wpstyle/?p=364\", \"\"), \"http://www.example.com/wpstyle/?p=364\");\n  t.is(\n    url(\"https://www.example.com/foo/?bar=baz&inga=42&quux\", \"\"),\n    \"https://www.example.com/foo/?bar=baz&inga=42&quux\"\n  );\n  t.is(\n    url(\"http://userid:password@example.com:8080\", \"\"),\n    \"http://userid:password@example.com:8080\"\n  );\n  t.is(\n    url(\"http://userid:password@example.com:8080/\", \"\"),\n    \"http://userid:password@example.com:8080/\"\n  );\n  t.is(url(\"http://userid@example.com\", \"\"), \"http://userid@example.com\");\n  t.is(url(\"http://userid@example.com/\", \"\"), \"http://userid@example.com/\");\n  t.is(url(\"http://userid@example.com:8080\", \"\"), \"http://userid@example.com:8080\");\n  t.is(url(\"http://userid@example.com:8080/\", \"\"), \"http://userid@example.com:8080/\");\n  t.is(url(\"http://userid:password@example.com\", \"\"), \"http://userid:password@example.com\");\n  t.is(url(\"http://userid:password@example.com/\", \"\"), \"http://userid:password@example.com/\");\n  t.is(url(\"http://142.42.1.1/\", \"\"), \"http://142.42.1.1/\");\n  t.is(url(\"http://142.42.1.1:8080/\", \"\"), \"http://142.42.1.1:8080/\");\n  t.is(url(\"http://foo.com/blah_(wikipedia)#cite-1\", \"\"), \"http://foo.com/blah_(wikipedia)#cite-1\");\n  t.is(\n    url(\"http://foo.com/blah_(wikipedia)_blah#cite-1\", \"\"),\n    \"http://foo.com/blah_(wikipedia)_blah#cite-1\"\n  );\n  t.is(\n    url(\"http://foo.com/(something)?after=parens\", \"\"),\n    \"http://foo.com/(something)?after=parens\"\n  );\n  t.is(\n    url(\"http://code.google.com/events/#&product=browser\", \"\"),\n    \"http://code.google.com/events/#&product=browser\"\n  );\n  t.is(url(\"http://j.mp\", \"\"), \"http://j.mp\");\n  t.is(url(\"ftp://foo.bar/baz\", \"\"), \"ftp://foo.bar/baz\");\n  t.is(\n    url(\"http://foo.bar/?q=Test%20URL-encoded%20stuff\", \"\"),\n    \"http://foo.bar/?q=Test%20URL-encoded%20stuff\"\n  );\n  t.is(\n    url(\"http://-.~_!$&'()*+,;=:%40:80%2f::::::@example.com\", \"\"),\n    \"http://-.~_!$&'()*+,;=:%40:80%2f::::::@example.com\"\n  );\n  t.is(url(\"http://1337.net\", \"\"), \"http://1337.net\");\n  t.is(url(\"http://a.b-c.de\", \"\"), \"http://a.b-c.de\");\n  t.is(url(\"http://223.255.255.254\", \"\"), \"http://223.255.255.254\");\n\n  t.is(url(\"http://✪df.ws/123\", \"\"), \"http://✪df.ws/123\");\n  t.is(url(\"http://➡.ws/䨹\", \"\"), \"http://➡.ws/䨹\");\n  t.is(url(\"http://⌘.ws\", \"\"), \"http://⌘.ws\");\n  t.is(url(\"http://⌘.ws/\", \"\"), \"http://⌘.ws/\");\n  t.is(url(\"http://foo.com/unicode_(✪)_in_parens\", \"\"), \"http://foo.com/unicode_(✪)_in_parens\");\n  t.is(url(\"http://☺.damowmow.com/\", \"\"), \"http://☺.damowmow.com/\");\n  t.is(url(\"http://مثال.إختبار\", \"\"), \"http://مثال.إختبار\");\n  t.is(url(\"http://例子.测试\", \"\"), \"http://例子.测试\");\n  t.is(url(\"http://उदाहरण.परीक्षा\", \"\"), \"http://उदाहरण.परीक्षा\");\n});\n\ntest(\"Test url filter\", (t) => {\n  t.is(url(\"/\", \"/\"), \"/\");\n  t.is(url(\"//\", \"/\"), \"/\");\n  t.is(url(undefined, \"/\"), \".\");\n  t.is(url(\"\", \"/\"), \".\");\n\n  // leave . and .. alone\n  t.is(url(\".\", \"/\"), \".\");\n  t.is(url(\"./\", \"/\"), \"./\");\n  t.is(url(\"..\", \"/\"), \"..\");\n  t.is(url(\"../\", \"/\"), \"../\");\n\n  t.is(url(\"test\", \"/\"), \"test\");\n  t.is(url(\"/test\", \"/\"), \"/test\");\n  t.is(url(\"//test\", \"/\"), \"//test\");\n  t.is(url(\"./test\", \"/\"), \"test\");\n  t.is(url(\"../test\", \"/\"), \"../test\");\n\n  t.is(url(\"test/\", \"/\"), \"test/\");\n  t.is(url(\"/test/\", \"/\"), \"/test/\");\n  t.is(url(\"//test/\", \"/\"), \"//test/\");\n  t.is(url(\"./test/\", \"/\"), \"test/\");\n  t.is(url(\"../test/\", \"/\"), \"../test/\");\n});\n\ntest(\"Test url filter with custom pathPrefix (empty, gets overwritten by root config `/`)\", (t) => {\n  t.is(url(\"/\", \"\"), \"/\");\n  t.is(url(\"//\", \"\"), \"/\");\n  t.is(url(undefined, \"\"), \".\");\n  t.is(url(\"\", \"\"), \".\");\n\n  // leave . and .. alone\n  t.is(url(\".\", \"\"), \".\");\n  t.is(url(\"./\", \"\"), \"./\");\n  t.is(url(\"..\", \"\"), \"..\");\n  t.is(url(\"../\", \"\"), \"../\");\n\n  t.is(url(\"test\", \"\"), \"test\");\n  t.is(url(\"/test\", \"\"), \"/test\");\n  t.is(url(\"//test\", \"\"), \"//test\");\n  t.is(url(\"./test\", \"\"), \"test\");\n  t.is(url(\"../test\", \"\"), \"../test\");\n\n  t.is(url(\"test/\", \"\"), \"test/\");\n  t.is(url(\"/test/\", \"\"), \"/test/\");\n  t.is(url(\"//test/\", \"\"), \"//test/\");\n  t.is(url(\"./test/\", \"\"), \"test/\");\n  t.is(url(\"../test/\", \"\"), \"../test/\");\n});\n\ntest(\"Test url filter with custom pathPrefix (leading slash)\", (t) => {\n  t.is(url(\"/\", \"/testdir\"), \"/testdir/\");\n  t.is(url(\"//\", \"/testdir\"), \"/testdir/\");\n  t.is(url(undefined, \"/testdir\"), \".\");\n  t.is(url(\"\", \"/testdir\"), \".\");\n\n  // leave . and .. alone\n  t.is(url(\".\", \"/testdir\"), \".\");\n  t.is(url(\"./\", \"/testdir\"), \"./\");\n  t.is(url(\"..\", \"/testdir\"), \"..\");\n  t.is(url(\"../\", \"/testdir\"), \"../\");\n\n  t.is(url(\"test\", \"/testdir\"), \"test\");\n  t.is(url(\"/test\", \"/testdir\"), \"/testdir/test\");\n  t.is(url(\"//test\", \"/testdir\"), \"//test\");\n  t.is(url(\"./test\", \"/testdir\"), \"test\");\n  t.is(url(\"../test\", \"/testdir\"), \"../test\");\n\n  t.is(url(\"test/\", \"/testdir\"), \"test/\");\n  t.is(url(\"/test/\", \"/testdir\"), \"/testdir/test/\");\n  t.is(url(\"//test/\", \"/testdir\"), \"//test/\");\n  t.is(url(\"./test/\", \"/testdir\"), \"test/\");\n  t.is(url(\"../test/\", \"/testdir\"), \"../test/\");\n});\n\ntest(\"Test url filter with custom pathPrefix (double slash)\", (t) => {\n  t.is(url(\"/\", \"/testdir/\"), \"/testdir/\");\n  t.is(url(\"//\", \"/testdir/\"), \"/testdir/\");\n  t.is(url(undefined, \"/testdir/\"), \".\");\n  t.is(url(\"\", \"/testdir/\"), \".\");\n\n  // leave . and .. alone\n  t.is(url(\".\", \"/testdir/\"), \".\");\n  t.is(url(\"./\", \"/testdir/\"), \"./\");\n  t.is(url(\"..\", \"/testdir/\"), \"..\");\n  t.is(url(\"../\", \"/testdir/\"), \"../\");\n\n  t.is(url(\"test\", \"/testdir/\"), \"test\");\n  t.is(url(\"/test\", \"/testdir/\"), \"/testdir/test\");\n  t.is(url(\"//test\", \"/testdir/\"), \"//test\");\n  t.is(url(\"./test\", \"/testdir/\"), \"test\");\n  t.is(url(\"../test\", \"/testdir/\"), \"../test\");\n\n  t.is(url(\"test/\", \"/testdir/\"), \"test/\");\n  t.is(url(\"/test/\", \"/testdir/\"), \"/testdir/test/\");\n  t.is(url(\"//test/\", \"/testdir/\"), \"//test/\");\n  t.is(url(\"./test/\", \"/testdir/\"), \"test/\");\n  t.is(url(\"../test/\", \"/testdir/\"), \"../test/\");\n});\n\ntest(\"Test url filter with custom pathPrefix (trailing slash)\", (t) => {\n  t.is(url(\"/\", \"testdir/\"), \"/testdir/\");\n  t.is(url(\"//\", \"testdir/\"), \"/testdir/\");\n  t.is(url(undefined, \"testdir/\"), \".\");\n  t.is(url(\"\", \"testdir/\"), \".\");\n\n  // leave . and .. alone\n  t.is(url(\".\", \"testdir/\"), \".\");\n  t.is(url(\"./\", \"testdir/\"), \"./\");\n  t.is(url(\"..\", \"testdir/\"), \"..\");\n  t.is(url(\"../\", \"testdir/\"), \"../\");\n\n  t.is(url(\"test\", \"testdir/\"), \"test\");\n  t.is(url(\"/test\", \"testdir/\"), \"/testdir/test\");\n  t.is(url(\"//test\", \"testdir/\"), \"//test\");\n  t.is(url(\"./test\", \"testdir/\"), \"test\");\n  t.is(url(\"../test\", \"testdir/\"), \"../test\");\n\n  t.is(url(\"test/\", \"testdir/\"), \"test/\");\n  t.is(url(\"/test/\", \"testdir/\"), \"/testdir/test/\");\n  t.is(url(\"//test/\", \"testdir/\"), \"//test/\");\n  t.is(url(\"./test/\", \"testdir/\"), \"test/\");\n  t.is(url(\"../test/\", \"testdir/\"), \"../test/\");\n});\n\ntest(\"Test url filter with custom pathPrefix (no slash)\", (t) => {\n  t.is(url(\"/\", \"testdir\"), \"/testdir/\");\n  t.is(url(\"//\", \"testdir\"), \"/testdir/\");\n  t.is(url(undefined, \"testdir\"), \".\");\n  t.is(url(\"\", \"testdir\"), \".\");\n\n  // leave . and .. alone\n  t.is(url(\".\", \"testdir\"), \".\");\n  t.is(url(\"./\", \"testdir\"), \"./\");\n  t.is(url(\"..\", \"testdir\"), \"..\");\n  t.is(url(\"../\", \"testdir\"), \"../\");\n\n  t.is(url(\"test\", \"testdir\"), \"test\");\n  t.is(url(\"/test\", \"testdir\"), \"/testdir/test\");\n  t.is(url(\"//test\", \"testdir\"), \"//test\");\n  t.is(url(\"//foo.com\", \"testdir\"), \"//foo.com\");\n  t.is(url(\"./test\", \"testdir\"), \"test\");\n  t.is(url(\"../test\", \"testdir\"), \"../test\");\n\n  t.is(url(\"test/\", \"testdir\"), \"test/\");\n  t.is(url(\"/test/\", \"testdir\"), \"/testdir/test/\");\n  t.is(url(\"//test/\", \"testdir\"), \"//test/\");\n  t.is(url(\"./test/\", \"testdir\"), \"test/\");\n  t.is(url(\"../test/\", \"testdir\"), \"../test/\");\n});\n"
  },
  {
    "path": "test/UserConfigTest.js",
    "content": "import test from \"ava\";\nimport UserConfig from \"../src/UserConfig.js\";\nimport memoize from \"../src/Util/MemoizeFunction.js\";\n\ntest(\"Template Formats\", (t) => {\n  let userCfg = new UserConfig();\n\n  t.falsy(userCfg.templateFormats);\n\n  userCfg.setTemplateFormats(\"njk,liquid\");\n  t.deepEqual(userCfg.templateFormats, \"njk,liquid\");\n\n  // setting multiple times takes the last one\n  userCfg.setTemplateFormats(\"njk,liquid,pug\");\n  userCfg.setTemplateFormats(\"njk,liquid\");\n  t.deepEqual(userCfg.templateFormats, \"njk,liquid\");\n});\n\ntest(\"Template Formats (Arrays)\", (t) => {\n  let userCfg = new UserConfig();\n\n  t.falsy(userCfg.templateFormats);\n\n  userCfg.setTemplateFormats([\"njk\", \"liquid\"]);\n  t.deepEqual(userCfg.templateFormats, [\"njk\", \"liquid\"]);\n\n  // setting multiple times takes the last one\n  userCfg.setTemplateFormats([\"njk\", \"liquid\", \"pug\"]);\n  userCfg.setTemplateFormats([\"njk\", \"liquid\"]);\n  t.deepEqual(userCfg.templateFormats, [\"njk\", \"liquid\"]);\n});\n\n// more in TemplateConfigTest.js\n\ntest(\"Events\", async (t) => {\n  await new Promise((resolve) => {\n    let userCfg = new UserConfig();\n    userCfg.on(\"testEvent\", function (arg1, arg2, arg3) {\n      t.is(arg1, \"arg1\");\n      t.is(arg2, \"arg2\");\n      t.is(arg3, \"arg3\");\n      resolve();\n    });\n\n    userCfg.emit(\"testEvent\", \"arg1\", \"arg2\", \"arg3\");\n  });\n});\n\ntest(\"Async Events\", async (t) => {\n  await new Promise((resolve) => {\n    let userCfg = new UserConfig();\n    let arg1;\n\n    userCfg.on(\n      \"asyncTestEvent\",\n      (_arg1) =>\n        new Promise((resolve) => {\n          setTimeout(() => {\n            arg1 = _arg1;\n            resolve();\n          }, 10);\n        })\n    );\n\n    userCfg.emit(\"asyncTestEvent\", \"arg1\").then(() => {\n      t.is(arg1, \"arg1\");\n      resolve();\n    });\n  });\n});\n\ntest(\"Add Collections\", (t) => {\n  let userCfg = new UserConfig();\n  userCfg.addCollection(\"myCollection\", function (collection) {});\n  t.deepEqual(Object.keys(userCfg.getCollections()), [\"myCollection\"]);\n});\n\ntest(\"Add Collections throws error on key collision\", (t) => {\n  let userCfg = new UserConfig();\n  userCfg.addCollection(\"myCollectionCollision\", function (collection) {});\n\n  t.throws(() => {\n    userCfg.addCollection(\"myCollectionCollision\", function (collection) {});\n  });\n});\n\ntest(\"Set manual Pass-through File Copy (single call)\", (t) => {\n  let userCfg = new UserConfig();\n  userCfg.addPassthroughCopy(\"img\");\n\n  t.deepEqual(userCfg.passthroughCopies[\"img\"], {\n    outputPath: true,\n    copyOptions: {},\n  });\n});\n\ntest(\"Set manual Pass-through File Copy (chained calls)\", (t) => {\n  let userCfg = new UserConfig();\n  userCfg\n    .addPassthroughCopy(\"css\")\n    .addPassthroughCopy(\"js\")\n    .addPassthroughCopy({ \"./src/static\": \"static\" })\n    .addPassthroughCopy({ \"./src/empty\": \"./\" });\n\n  t.deepEqual(userCfg.passthroughCopies[\"css\"], {\n    outputPath: true,\n    copyOptions: {},\n  });\n  t.deepEqual(userCfg.passthroughCopies[\"js\"], {\n    outputPath: true,\n    copyOptions: {},\n  });\n  t.deepEqual(userCfg.passthroughCopies[\"./src/static\"], {\n    outputPath: \"static\",\n    copyOptions: {},\n  });\n  t.deepEqual(userCfg.passthroughCopies[\"./src/empty\"], {\n    outputPath: \"./\",\n    copyOptions: {},\n  });\n});\n\ntest(\"Set manual Pass-through File Copy (glob patterns)\", (t) => {\n  let userCfg = new UserConfig();\n  userCfg.addPassthroughCopy({\n    \"./src/static/**/*\": \"renamed\",\n    \"./src/markdown/*.md\": \"\",\n  });\n\n  // does not exist\n  t.is(userCfg.passthroughCopies[\"css/**\"], undefined);\n  t.is(userCfg.passthroughCopies[\"js/**\"], undefined);\n\n  // exists\n  t.deepEqual(userCfg.passthroughCopies[\"./src/static/**/*\"], {\n    outputPath: \"renamed\",\n    copyOptions: {},\n  });\n  t.deepEqual(userCfg.passthroughCopies[\"./src/markdown/*.md\"], {\n    outputPath: \"\",\n    copyOptions: {},\n  });\n});\n\ntest(\"Set Template Formats (string)\", (t) => {\n  let userCfg = new UserConfig();\n  userCfg.setTemplateFormats(\"njk, liquid\");\n  t.deepEqual(userCfg.templateFormats, \"njk, liquid\");\n});\n\ntest(\"Set Template Formats (array)\", (t) => {\n  let userCfg = new UserConfig();\n  userCfg.setTemplateFormats([\"njk\", \"liquid\"]);\n  t.deepEqual(userCfg.templateFormats, [\"njk\", \"liquid\"]);\n});\n\ntest(\"Set Template Formats (js passthrough copy)\", (t) => {\n  let userCfg = new UserConfig();\n  userCfg.setTemplateFormats(\"njk, liquid, js\");\n  t.deepEqual(userCfg.templateFormats, \"njk, liquid, js\");\n});\n\ntest(\"Set Template Formats (11ty.js)\", (t) => {\n  let userCfg = new UserConfig();\n  userCfg.setTemplateFormats(\"njk, liquid, 11ty.js\");\n  t.deepEqual(userCfg.templateFormats, \"njk, liquid, 11ty.js\");\n});\n\ntest(\"Add Template Formats\", (t) => {\n  let userCfg = new UserConfig();\n  userCfg.addTemplateFormats(\"njk\");\n  userCfg.addTemplateFormats(\"webc\");\n  userCfg.addTemplateFormats(\"liquid\");\n  userCfg.addTemplateFormats(\"11ty.js\");\n\n  t.deepEqual(userCfg.templateFormatsAdded.sort(), [\"11ty.js\", \"liquid\", \"njk\", \"webc\"]);\n});\n\ntest(\"Resolve plugin\", async (t) => {\n  let userConfig = new UserConfig();\n  let plugin = await userConfig.resolvePlugin(\"@11ty/eleventy/html-base-plugin\");\n  t.truthy(typeof plugin === \"function\")\n});\n\ntest(\"Resolve plugin (invalid)\", async (t) => {\n  let userConfig = new UserConfig();\n  let e = await t.throwsAsync(async() => {\n    await userConfig.resolvePlugin(\"@11ty/eleventy/does-not-exist\");\n  });\n  t.truthy(e.message.startsWith(`Invalid name \"@11ty/eleventy/does-not-exist\" passed to resolvePlugin.`));\n});\n\ntest(\"Memoize filters (control)\", (t) => {\n  let userCfg = new UserConfig();\n  let count = 0;\n  userCfg.addFilter(\"increment\", (num) => {\n    count += num;\n  });\n\n  let increment = userCfg.getFilter(\"increment\");\n  increment(3);\n\n  t.is(count, 3);\n\n  increment(3);\n  t.is(count, 6);\n});\n\ntest(\"Memoize filters (memoized)\", (t) => {\n  let userCfg = new UserConfig();\n  let count = 0;\n  userCfg.addFilter(\"increment\", memoize((num) => {\n    count += num;\n  }));\n\n  let increment = userCfg.getFilter(\"increment\");\n\n  increment(1);\n  increment(1);\n  increment(1);\n  t.is(count, 1);\n\n  increment(2);\n  increment(2);\n  increment(2);\n  t.is(count, 3);\n\n  increment(3);\n  increment(3);\n  increment(3);\n  increment(3);\n  t.is(count, 6);\n});\n\ntest(\"Memoize async filters (memoized)\", async (t) => {\n  let userCfg = new UserConfig();\n  let count = 0;\n\n  userCfg.addFilter(\"increment\", memoize(async (num) => {\n    return new Promise(resolve => {\n      setTimeout(() => {\n        count += num;\n        resolve(count);\n      }, 50);\n    });\n  }));\n\n  let increment = userCfg.getFilter(\"increment\");\n  await increment(1);\n  await increment(1);\n  await increment(1);\n  t.is(count, 1);\n\n  await increment(2);\n  await increment(2);\n  await increment(2);\n  t.is(count, 3);\n\n  await increment(3);\n  await increment(3);\n  await increment(3);\n  await increment(3);\n  t.is(count, 6);\n});\n\n// JavaScript functions are included here for backwards compatibility https://github.com/11ty/eleventy/issues/3365\ntest(\"addJavaScriptFunction feeds into `getFilter` #3365\", (t) => {\n  let userCfg = new UserConfig();\n  userCfg.addJavaScriptFunction(\"increment\", num => num++);\n\n  t.is(typeof userCfg.getFilter(\"increment\"), \"function\");\n});\n"
  },
  {
    "path": "test/UserDataExtensionsTest.js",
    "content": "import test from \"ava\";\nimport fs from \"fs\";\nimport yaml from \"js-yaml\";\n\nimport TemplateConfig from \"../src/TemplateConfig.js\";\nimport FileSystemSearch from \"../src/FileSystemSearch.js\";\nimport TemplateData from \"../src/Data/TemplateData.js\";\n\nimport { getTemplateConfigInstanceCustomCallback } from \"./_testHelpers.js\";\nimport EleventyExtensionMap from \"../src/EleventyExtensionMap.js\";\n\ntest(\"Local data\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback(\n    {\n      input: \"test/stubs-630\"\n    },\n    function(cfg) {\n      cfg.addDataExtension(\"yaml\", { parser: (s) => yaml.load(s) });\n      cfg.addDataExtension(\"nosj\", { parser: (s) => JSON.parse(s) });\n    }\n  );\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  dataObj.setFileSystemSearch(new FileSystemSearch());\n\n  let data = await dataObj.getGlobalData();\n\n  // YAML GLOBAL DATA\n  t.is(data.globalData3.datakey1, \"datavalue3\");\n  t.is(data.globalData3.datakey2, \"{{pkg.name}}--yaml\");\n\n  // NOSJ (JSON) GLOBAL DATA\n  t.is(data.globalData4.datakey1, \"datavalue4\");\n  t.is(data.globalData4.datakey2, \"{{pkg.name}}--nosj\");\n\n  let withLocalData = await dataObj.getTemplateDirectoryData(\n    \"./test/stubs-630/component-yaml/component.njk\"\n  );\n\n  t.is(withLocalData.yamlKey1, \"yaml1\");\n  t.is(withLocalData.yamlKey2, \"yaml2\");\n  t.is(withLocalData.yamlKey3, \"yaml3\");\n  t.is(withLocalData.nosjKey1, \"nosj1\");\n  t.is(withLocalData.jsonKey1, \"json1\");\n  t.is(withLocalData.jsonKey2, \"json2\");\n  t.is(withLocalData.jsKey1, \"js1\");\n});\n\ntest(\"Local files\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback(\n    {\n      input: \"test/stubs-630\"\n    },\n    function(cfg) {\n      cfg.addDataExtension(\"yaml\", { parser: (s) => yaml.load(s) });\n      cfg.addDataExtension(\"nosj\", { parser: (s) => JSON.parse(s) });\n    }\n  );\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  let files = await dataObj.getLocalDataPaths(\"./test/stubs-630/component-yaml/component.njk\");\n  t.deepEqual(files, [\n    \"./test/stubs-630/stubs-630.yaml\",\n    \"./test/stubs-630/stubs-630.nosj\",\n    \"./test/stubs-630/stubs-630.json\",\n    \"./test/stubs-630/stubs-630.11tydata.yaml\",\n    \"./test/stubs-630/stubs-630.11tydata.nosj\",\n    \"./test/stubs-630/stubs-630.11tydata.json\",\n    \"./test/stubs-630/stubs-630.11tydata.mjs\",\n    \"./test/stubs-630/stubs-630.11tydata.cjs\",\n    \"./test/stubs-630/stubs-630.11tydata.js\",\n    \"./test/stubs-630/component-yaml/component-yaml.yaml\",\n    \"./test/stubs-630/component-yaml/component-yaml.nosj\",\n    \"./test/stubs-630/component-yaml/component-yaml.json\",\n    \"./test/stubs-630/component-yaml/component-yaml.11tydata.yaml\",\n    \"./test/stubs-630/component-yaml/component-yaml.11tydata.nosj\",\n    \"./test/stubs-630/component-yaml/component-yaml.11tydata.json\",\n    \"./test/stubs-630/component-yaml/component-yaml.11tydata.mjs\",\n    \"./test/stubs-630/component-yaml/component-yaml.11tydata.cjs\",\n    \"./test/stubs-630/component-yaml/component-yaml.11tydata.js\",\n    \"./test/stubs-630/component-yaml/component.yaml\",\n    \"./test/stubs-630/component-yaml/component.nosj\",\n    \"./test/stubs-630/component-yaml/component.json\",\n    \"./test/stubs-630/component-yaml/component.11tydata.yaml\",\n    \"./test/stubs-630/component-yaml/component.11tydata.nosj\",\n    \"./test/stubs-630/component-yaml/component.11tydata.json\",\n    \"./test/stubs-630/component-yaml/component.11tydata.mjs\",\n    \"./test/stubs-630/component-yaml/component.11tydata.cjs\",\n    \"./test/stubs-630/component-yaml/component.11tydata.js\",\n  ]);\n});\n\ntest(\"Global data\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback(\n    {\n      input: \"test/stubs-630\"\n    },\n    function(cfg) {\n      cfg.addDataExtension(\"yaml\", { parser: (s) => yaml.load(s) });\n      cfg.addDataExtension(\"nosj\", { parser: (s) => JSON.parse(s) });\n    }\n  );\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  dataObj.setFileSystemSearch(new FileSystemSearch());\n\n  t.deepEqual(dataObj.getGlobalDataGlob(), [\n    \"./test/stubs-630/_data/**/*.{nosj,yaml,json,mjs,cjs,js}\",\n  ]);\n\n  let data = await dataObj.getGlobalData();\n\n  // JS GLOBAL DATA\n  t.is(data.globalData0.datakey1, \"datavalue0\");\n\n  // CJS GLOBAL DATA\n  t.is(data.globalData1.datakey1, \"datavalue1\");\n\n  // JSON GLOBAL DATA\n  t.is(data.globalData2.datakey1, \"datavalue2\");\n  t.is(data.globalData2.datakey2, \"{{pkg.name}}--json\");\n\n  // YAML GLOBAL DATA\n  t.is(data.globalData3.datakey1, \"datavalue3\");\n  t.is(data.globalData3.datakey2, \"{{pkg.name}}--yaml\");\n\n  // NOSJ (JSON) GLOBAL DATA\n  t.is(data.globalData4.datakey1, \"datavalue4\");\n  t.is(data.globalData4.datakey2, \"{{pkg.name}}--nosj\");\n\n  t.is(data.subdir.globalDataSubdir.keyyaml, \"yaml\");\n});\n\ntest(\"Global data merging and priority\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback(\n    {\n      input: \"test/stubs-630\"\n    },\n    function(cfg) {\n      cfg.addDataExtension(\"yaml\", { parser: (s) => yaml.load(s) });\n      cfg.addDataExtension(\"nosj\", { parser: (s) => JSON.parse(s) });\n    }\n  );\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  dataObj.setFileSystemSearch(new FileSystemSearch());\n\n  let data = await dataObj.getGlobalData();\n\n  // TESTING GLOBAL DATA PRIORITY AND MERGING\n  t.is(data.mergingGlobalData.datakey0, \"js-value0\");\n  t.is(data.mergingGlobalData.datakey1, \"cjs-value1\");\n  t.is(data.mergingGlobalData.datakey2, \"json-value2\");\n  t.is(data.mergingGlobalData.datakey3, \"yaml-value3\");\n  t.is(data.mergingGlobalData.datakey4, \"nosj-value4\");\n\n  t.is(data.mergingGlobalData.jskey, \"js\");\n  t.is(data.mergingGlobalData.cjskey, \"cjs\");\n  t.is(data.mergingGlobalData.jsonkey, \"json\");\n  t.is(data.mergingGlobalData.yamlkey, \"yaml\");\n  t.is(data.mergingGlobalData.nosjkey, \"nosj\");\n});\n\ntest(\"Binary data files, encoding: null\", async (t) => {\n  t.plan(2);\n\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback(\n    {\n      input: \"test/stubs-2378\"\n    },\n    function(cfg) {\n      cfg.addDataExtension(\"jpg\", {\n        parser: (s) => {\n          t.true(Buffer.isBuffer(s));\n          // s is a Buffer, just return the length as a sample\n          return s.length;\n        },\n        encoding: null,\n      });\n    }\n  );\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  dataObj.setFileSystemSearch(new FileSystemSearch());\n\n  let data = await dataObj.getGlobalData();\n  t.is(data.images.dog, 43183);\n});\n\ntest(\"Binary data files, read: false\", async (t) => {\n  t.plan(2);\n\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback(\n    {\n      input: \"test/stubs-2378\"\n    },\n    function(cfg) {\n      cfg.addDataExtension(\"jpg\", {\n        parser: (s) => {\n          t.true(fs.existsSync(s));\n          // s is a Buffer, just return the length as a sample\n          return s;\n        },\n        read: false,\n      });\n    }\n  );\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  dataObj.setFileSystemSearch(new FileSystemSearch());\n\n  let data = await dataObj.getGlobalData();\n  t.is(data.images.dog, \"./test/stubs-2378/_data/images/dog.jpg\");\n});\n\ntest(\"Binary data files, encoding: null (multiple data extensions)\", async (t) => {\n  t.plan(4);\n\n  let eleventyConfig = await getTemplateConfigInstanceCustomCallback(\n    {\n      input: \"test/stubs-2378\"\n    },\n    function(cfg) {\n      cfg.addDataExtension(\"jpg, png\", {\n        parser: function (s) {\n          t.true(Buffer.isBuffer(s));\n          // s is a Buffer, just return the length as a sample\n          return s.length;\n        },\n        encoding: null,\n      });\n    }\n  );\n\n  let dataObj = new TemplateData(eleventyConfig);\n  dataObj.extensionMap = new EleventyExtensionMap(eleventyConfig);\n  dataObj.setProjectUsingEsm(true);\n  dataObj.setFileSystemSearch(new FileSystemSearch());\n\n  let data = await dataObj.getGlobalData();\n  t.is(data.images.dog, 43183);\n  t.is(data.images.dogpng, 2890);\n});\n\ntest(\"Missing `parser` property to addDataExtension object throws error\", async (t) => {\n  let eleventyConfig = new TemplateConfig();\n  t.throws(() => {\n    eleventyConfig.userConfig.addDataExtension(\"jpg\", {});\n  }, {\n    message: \"Expected `parser` property in second argument object to `eleventyConfig.addDataExtension`\"\n  });\n});\n"
  },
  {
    "path": "test/Util/normalizeNewLines.js",
    "content": "import os from 'node:os';\n\nfunction normalizeNewLines(str) {\n  return str.replace(/\\r\\n/g, \"\\n\");\n}\n\nfunction localizeNewLines(str) {\n  return normalizeNewLines(str).replace(/\\n/g, os.EOL);\n}\n\nexport {\n  normalizeNewLines,\n  localizeNewLines,\n};\n"
  },
  {
    "path": "test/Util/normalizeSeparators.js",
    "content": "import PathNormalizer from \"../../src/Util/PathNormalizer.js\";\n\nexport function normalizeSeparatorString(str) {\n  return PathNormalizer.normalizeSeperator(str);\n}\n\nexport function normalizeSeparatorArray(arr) {\n  return arr.map(entry => {\n    return PathNormalizer.normalizeSeperator(entry);\n  })\n}\n"
  },
  {
    "path": "test/UtilSetUnionTest.js",
    "content": "import test from \"ava\";\nimport { union } from \"../src/Util/SetUtil.js\";\n\ntest(\"Basic set union (zero)\", t => {\n  t.deepEqual(union(), new Set());\n});\n\ntest(\"Basic set union (one)\", t => {\n  let a = new Set([1,2,3]);\n  t.deepEqual(union(a), new Set([1,2,3]));\n});\n\ntest(\"Basic set union (two)\", t => {\n  let a = new Set([1,2,3]);\n  let b = new Set([3,4,5]);\n  t.deepEqual(union(a, b), new Set([1,2,3,4,5]));\n});\n\ntest(\"Basic set union (three)\", t => {\n  let a = new Set([0,1,2,3]);\n  let b = new Set([3,4,5]);\n  let c = new Set([3,4,5,6]);\n  t.deepEqual(union(a, b, c), new Set([0,1,2,3,4,5,6]));\n});\n"
  },
  {
    "path": "test/WatchQueueTest.js",
    "content": "import test from \"ava\";\nimport WatchQueue from \"../src/WatchQueue.js\";\n\ntest(\"Standard\", (t) => {\n  let watch = new WatchQueue();\n  t.is(watch.isBuildRunning(), false);\n\n  watch.setBuildRunning();\n  t.is(watch.isBuildRunning(), true);\n\n  watch.setBuildFinished();\n  t.is(watch.isBuildRunning(), false);\n});\n\ntest(\"Incremental\", (t) => {\n  let watch = new WatchQueue();\n  t.is(watch.getIncrementalFile(), false);\n\n  watch.incremental = true;\n  t.is(watch.getPendingQueueSize(), 0);\n  t.is(watch.getIncrementalFile(), false);\n  t.deepEqual(watch.getActiveQueue(), []);\n\n  watch.addToPendingQueue(\"test.md\");\n  t.is(watch.getPendingQueueSize(), 1);\n  t.is(watch.getActiveQueueSize(), 0);\n  t.is(watch.getIncrementalFile(), false);\n  t.deepEqual(watch.getActiveQueue(), []);\n\n  watch.setBuildRunning();\n  t.is(watch.getPendingQueueSize(), 0);\n  t.is(watch.getActiveQueueSize(), 1);\n  t.is(watch.getIncrementalFile(), \"./test.md\");\n  t.deepEqual(watch.getActiveQueue(), [\"./test.md\"]);\n\n  watch.setBuildFinished();\n  t.is(watch.getPendingQueueSize(), 0);\n  t.is(watch.getActiveQueueSize(), 0);\n  t.is(watch.getIncrementalFile(), false);\n  t.deepEqual(watch.getActiveQueue(), []);\n  t.deepEqual(watch.getPendingQueue(), []);\n});\n\ntest(\"Incremental queue 2\", (t) => {\n  let watch = new WatchQueue();\n  t.is(watch.getIncrementalFile(), false);\n\n  watch.incremental = true;\n  t.is(watch.getPendingQueueSize(), 0);\n  t.is(watch.getIncrementalFile(), false);\n  t.deepEqual(watch.getActiveQueue(), []);\n\n  watch.addToPendingQueue(\"test.md\");\n  watch.addToPendingQueue(\"test2.md\");\n  t.is(watch.getPendingQueueSize(), 2);\n  t.is(watch.getActiveQueueSize(), 0);\n  t.is(watch.getIncrementalFile(), false);\n  t.deepEqual(watch.getActiveQueue(), []);\n\n  watch.setBuildRunning();\n  t.is(watch.getPendingQueueSize(), 1);\n  t.is(watch.getActiveQueueSize(), 1);\n  t.is(watch.getIncrementalFile(), \"./test.md\");\n  t.deepEqual(watch.getActiveQueue(), [\"./test.md\"]);\n\n  watch.setBuildFinished();\n  t.is(watch.getPendingQueueSize(), 1);\n  t.is(watch.getActiveQueueSize(), 0);\n  t.is(watch.getIncrementalFile(), false);\n  t.deepEqual(watch.getPendingQueue(), [\"./test2.md\"]);\n  t.deepEqual(watch.getActiveQueue(), []);\n});\n\ntest(\"Incremental add while active\", (t) => {\n  let watch = new WatchQueue();\n  t.is(watch.getIncrementalFile(), false);\n\n  watch.incremental = true;\n  t.is(watch.getPendingQueueSize(), 0);\n  t.is(watch.getIncrementalFile(), false);\n  t.deepEqual(watch.getActiveQueue(), []);\n\n  watch.addToPendingQueue(\"test.md\");\n  t.is(watch.getPendingQueueSize(), 1);\n  t.is(watch.getActiveQueueSize(), 0);\n  t.is(watch.getIncrementalFile(), false);\n  t.deepEqual(watch.getActiveQueue(), []);\n\n  watch.setBuildRunning();\n  t.is(watch.getPendingQueueSize(), 0);\n  t.is(watch.getActiveQueueSize(), 1);\n  t.is(watch.getIncrementalFile(), \"./test.md\");\n  t.deepEqual(watch.getActiveQueue(), [\"./test.md\"]);\n\n  watch.addToPendingQueue(\"test2.md\");\n  t.is(watch.getPendingQueueSize(), 1);\n  t.is(watch.getActiveQueueSize(), 1);\n  t.is(watch.getIncrementalFile(), \"./test.md\");\n  t.deepEqual(watch.getActiveQueue(), [\"./test.md\"]);\n\n  watch.setBuildFinished();\n  t.is(watch.getPendingQueueSize(), 1);\n  t.is(watch.getActiveQueueSize(), 0);\n  t.is(watch.getIncrementalFile(), false);\n  t.deepEqual(watch.getPendingQueue(), [\"./test2.md\"]);\n  t.deepEqual(watch.getActiveQueue(), []);\n});\n\ntest(\"Non-incremental\", (t) => {\n  let watch = new WatchQueue();\n  t.is(watch.getIncrementalFile(), false);\n\n  t.is(watch.getPendingQueueSize(), 0);\n  t.is(watch.getIncrementalFile(), false);\n  t.deepEqual(watch.getActiveQueue(), []);\n\n  watch.addToPendingQueue(\"test.md\");\n  t.is(watch.getPendingQueueSize(), 1);\n  t.is(watch.getActiveQueueSize(), 0);\n  t.is(watch.getIncrementalFile(), false);\n  t.deepEqual(watch.getActiveQueue(), []);\n\n  watch.setBuildRunning();\n  t.is(watch.getPendingQueueSize(), 0);\n  t.is(watch.getActiveQueueSize(), 1);\n  t.is(watch.getIncrementalFile(), false);\n  t.deepEqual(watch.getActiveQueue(), [\"./test.md\"]);\n\n  watch.setBuildFinished();\n  t.is(watch.getPendingQueueSize(), 0);\n  t.is(watch.getActiveQueueSize(), 0);\n  t.is(watch.getIncrementalFile(), false);\n  t.deepEqual(watch.getActiveQueue(), []);\n});\n\ntest(\"Non-incremental queue 2\", (t) => {\n  let watch = new WatchQueue();\n  t.is(watch.getIncrementalFile(), false);\n\n  t.is(watch.getPendingQueueSize(), 0);\n  t.is(watch.getIncrementalFile(), false);\n  t.deepEqual(watch.getActiveQueue(), []);\n\n  watch.addToPendingQueue(\"test.md\");\n  watch.addToPendingQueue(\"test2.md\");\n  t.is(watch.getPendingQueueSize(), 2);\n  t.is(watch.getActiveQueueSize(), 0);\n  t.is(watch.getIncrementalFile(), false);\n  t.deepEqual(watch.getActiveQueue(), []);\n\n  watch.setBuildRunning();\n  t.is(watch.getPendingQueueSize(), 0);\n  t.is(watch.getActiveQueueSize(), 2);\n  t.is(watch.getIncrementalFile(), false);\n  t.deepEqual(watch.getActiveQueue(), [\"./test.md\", \"./test2.md\"]);\n\n  watch.setBuildFinished();\n  t.is(watch.getPendingQueueSize(), 0);\n  t.is(watch.getActiveQueueSize(), 0);\n  t.is(watch.getIncrementalFile(), false);\n  t.deepEqual(watch.getPendingQueue(), []);\n  t.deepEqual(watch.getActiveQueue(), []);\n});\n\ntest(\"Non-incremental add while active\", (t) => {\n  let watch = new WatchQueue();\n  t.is(watch.getIncrementalFile(), false);\n\n  t.is(watch.getPendingQueueSize(), 0);\n  t.is(watch.getIncrementalFile(), false);\n\n  watch.addToPendingQueue(\"test.md\");\n  t.is(watch.getPendingQueueSize(), 1);\n  t.is(watch.getActiveQueueSize(), 0);\n  t.is(watch.getIncrementalFile(), false);\n  t.deepEqual(watch.getActiveQueue(), []);\n\n  watch.setBuildRunning();\n  t.is(watch.getPendingQueueSize(), 0);\n  t.is(watch.getActiveQueueSize(), 1);\n  t.is(watch.getIncrementalFile(), false);\n  t.deepEqual(watch.getActiveQueue(), [\"./test.md\"]);\n\n  watch.addToPendingQueue(\"test.md\");\n  t.is(watch.getPendingQueueSize(), 1);\n  t.is(watch.getActiveQueueSize(), 1);\n  t.is(watch.getIncrementalFile(), false);\n  t.deepEqual(watch.getActiveQueue(), [\"./test.md\"]);\n\n  watch.setBuildFinished();\n  t.is(watch.getPendingQueueSize(), 1);\n  t.is(watch.getActiveQueueSize(), 0);\n  t.is(watch.getIncrementalFile(), false);\n  t.deepEqual(watch.getPendingQueue(), [\"./test.md\"]);\n  t.deepEqual(watch.getActiveQueue(), []);\n});\n\ntest(\"Active queue tests\", (t) => {\n  let watch = new WatchQueue();\n  watch.addToPendingQueue(\"test.md\");\n  watch.addToPendingQueue(\"test2.md\");\n  watch.addToPendingQueue(\"test.css\");\n\n  t.is(\n    watch.hasAllQueueFiles((path) => path.startsWith(\"./test\")),\n    false\n  );\n\n  watch.setBuildRunning();\n  t.is(watch.hasAllQueueFiles(\"slkdjflkjsdlkfj\"), false);\n  t.is(\n    watch.hasAllQueueFiles((path) => path.startsWith(\"./test\")),\n    true\n  );\n  t.is(\n    watch.hasAllQueueFiles((path) => path.endsWith(\".css\")),\n    false\n  );\n\n  t.is(watch.hasQueuedFile(\"./test.md\"), true);\n  t.is(watch.hasQueuedFile(\"./testsdkljfklja.md\"), false);\n  watch.setBuildFinished();\n\n  t.is(\n    watch.hasAllQueueFiles((path) => path.startsWith(\"./test\")),\n    false\n  );\n});\n\ntest(\"Active queue tests, all CSS files\", (t) => {\n  let watch = new WatchQueue();\n  watch.addToPendingQueue(\"test.css\");\n  watch.addToPendingQueue(\"test2.css\");\n  watch.addToPendingQueue(\"test3.css\");\n\n  t.is(\n    watch.hasAllQueueFiles((path) => path.endsWith(\".css\")),\n    false\n  );\n\n  watch.setBuildRunning();\n  t.is(\n    watch.hasAllQueueFiles((path) => path.endsWith(\".css\")),\n    true\n  );\n  watch.setBuildFinished();\n\n  t.is(\n    watch.hasAllQueueFiles((path) => path.endsWith(\".css\")),\n    false\n  );\n});\n"
  },
  {
    "path": "test/WatchTargetsTest.js",
    "content": "import test from \"ava\";\n\nimport TemplateConfig from \"../src/TemplateConfig.js\";\nimport EleventyWatchTargets from \"../src/WatchTargets.js\";\nimport JavaScriptDependencies from \"../src/Util/JavaScriptDependencies.js\";\n\ntest(\"Basic\", (t) => {\n  let targets = new EleventyWatchTargets();\n  targets.setProjectUsingEsm(true);\n\n  t.deepEqual(targets.getTargets(), []);\n\n  targets.add(\".eleventy.js\");\n  t.deepEqual(targets.getTargets(), [\"./.eleventy.js\"]);\n});\n\ntest(\"Removes duplicates\", (t) => {\n  let targets = new EleventyWatchTargets();\n  targets.setProjectUsingEsm(true);\n\n  targets.add(\".eleventy.js\");\n  targets.add(\"./.eleventy.js\");\n  t.deepEqual(targets.getTargets(), [\"./.eleventy.js\"]);\n});\n\ntest(\"Add array\", (t) => {\n  let targets = new EleventyWatchTargets();\n  targets.setProjectUsingEsm(true);\n\n  targets.add([\".eleventy.js\", \"b.js\"]);\n  targets.add([\"b.js\", \"c.js\"]);\n  t.deepEqual(targets.getTargets(), [\"./.eleventy.js\", \"./b.js\", \"./c.js\"]);\n});\n\ntest(\"Add and make glob\", (t) => {\n  let targets = new EleventyWatchTargets();\n  targets.setProjectUsingEsm(true);\n\n  // Note the `test` directory must exist here for this to pass.\n  targets.add([\"test\", \"test/b.js\"]);\n  t.deepEqual(targets.getTargets(), [\"./test\", \"./test/b.js\"]);\n});\n\ntest(\"JavaScript get dependencies\", async (t) => {\n  t.deepEqual(\n    await JavaScriptDependencies.getDependencies([\"./test/stubs/config-deps.cjs\"], true),\n    [\"./test/stubs/config-deps-upstream.cjs\"]\n  );\n});\n\ntest(\"JavaScript addDependencies\", async (t) => {\n  let targets = new EleventyWatchTargets();\n  targets.setProjectUsingEsm(true);\n\n  await targets.addDependencies(\"./test/stubs/config-deps.cjs\");\n  t.deepEqual(targets.getTargets(), [\"./test/stubs/config-deps-upstream.cjs\"]);\n\n  t.true(targets.uses(\"./test/stubs/config-deps.cjs\", \"./test/stubs/config-deps-upstream.cjs\"));\n  t.false(targets.uses(\"./test/stubs/config-deps.cjs\", \"./test/stubs/config-deps.cjs\"));\n});\n\ntest(\"JavaScript addDependencies (one file has two dependencies)\", async (t) => {\n  let targets = new EleventyWatchTargets();\n  targets.setProjectUsingEsm(true);\n\n  await targets.addDependencies(\"./test/stubs/dependencies/two-deps.11ty.cjs\");\n  t.deepEqual(targets.getTargets(), [\n    \"./test/stubs/dependencies/dep1.cjs\",\n    \"./test/stubs/dependencies/dep2.cjs\",\n  ]);\n\n  t.true(\n    targets.uses(\n      \"./test/stubs/dependencies/two-deps.11ty.cjs\",\n      \"./test/stubs/dependencies/dep1.cjs\"\n    )\n  );\n  t.true(\n    targets.uses(\n      \"./test/stubs/dependencies/two-deps.11ty.cjs\",\n      \"./test/stubs/dependencies/dep2.cjs\"\n    )\n  );\n  t.false(\n    targets.uses(\n      \"./test/stubs/dependencies/two-deps.11ty.cjs\",\n      \"./test/stubs/dependencies/dep3.cjs\"\n    )\n  );\n});\n\ntest(\"JavaScript addDependencies (skip JS deps)\", async (t) => {\n  let templateConfig = new TemplateConfig();\n  let targets = new EleventyWatchTargets(templateConfig);\n  targets.setProjectUsingEsm(true);\n  targets.watchJavaScriptDependencies = false;\n  await targets.addDependencies(\"./test/stubs/dependencies/two-deps.11ty.cjs\");\n\n  t.deepEqual(targets.getTargets(), []);\n\n  t.false(\n    targets.uses(\n      \"./test/stubs/dependencies/two-deps.11ty.cjs\",\n      \"./test/stubs/dependencies/dep1.cjs\"\n    )\n  );\n  t.false(\n    targets.uses(\n      \"./test/stubs/dependencies/two-deps.11ty.cjs\",\n      \"./test/stubs/dependencies/dep2.cjs\"\n    )\n  );\n  t.false(\n    targets.uses(\n      \"./test/stubs/dependencies/two-deps.11ty.cjs\",\n      \"./test/stubs/dependencies/dep3.cjs\"\n    )\n  );\n});\n\ntest(\"JavaScript addDependencies with a filter\", async (t) => {\n  let targets = new EleventyWatchTargets();\n  targets.setProjectUsingEsm(true);\n  await targets.addDependencies(\"./test/stubs/config-deps.cjs\", function (path) {\n    return path.indexOf(\"./test/stubs/\") === -1;\n  });\n  t.deepEqual(targets.getTargets(), []);\n  t.false(\n    targets.uses(\n      \"./test/stubs/dependencies/config-deps.cjs\",\n      \"./test/stubs/dependencies/config-deps-upstream.cjs\"\n    )\n  );\n});\n\ntest(\"add, addDependencies falsy values are filtered\", async (t) => {\n  let targets = new EleventyWatchTargets();\n  targets.setProjectUsingEsm(true);\n  targets.add(\"\");\n  await targets.addDependencies(\"\");\n  t.deepEqual(targets.getTargets(), []);\n});\n\ntest(\"add, addDependencies file does not exist\", async (t) => {\n  let targets = new EleventyWatchTargets();\n  targets.setProjectUsingEsm(true);\n\n  targets.add(\"./.eleventy-notfound.js\"); // does not exist\n  await targets.addDependencies(\"./.eleventy-notfound.js\"); // does not exist\n  t.deepEqual(targets.getTargets(), [\"./.eleventy-notfound.js\"]);\n});\n\ntest(\"getNewTargetsSinceLastReset\", (t) => {\n  let targets = new EleventyWatchTargets();\n  targets.setProjectUsingEsm(true);\n\n  targets.add(\"./.eleventy-notfound.js\"); // does not exist\n  t.deepEqual(targets.getNewTargetsSinceLastReset(), [\"./.eleventy-notfound.js\"]);\n  t.deepEqual(targets.getNewTargetsSinceLastReset(), [\"./.eleventy-notfound.js\"]);\n\n  targets.reset();\n  targets.add(\"./.eleventy-notfound2.js\");\n  t.deepEqual(targets.getNewTargetsSinceLastReset(), [\"./.eleventy-notfound2.js\"]);\n\n  targets.reset();\n  t.deepEqual(targets.getNewTargetsSinceLastReset(), []);\n});\n"
  },
  {
    "path": "test/_getNewTemplateForTests.js",
    "content": "import EleventyExtensionMap from \"../src/EleventyExtensionMap.js\";\nimport Template from \"../src/Template.js\";\nimport FileSystemSearch from \"../src/FileSystemSearch.js\";\nimport TemplateEngineManager from \"../src/Engines/TemplateEngineManager.js\";\n\nimport { getTemplateConfigInstance } from \"./_testHelpers.js\";\n\nexport default async function getNewTemplate(\n  path,\n  inputDir,\n  outputDir,\n  templateData = null,\n  map = null,\n  eleventyConfig = null\n) {\n  if (!eleventyConfig) {\n    eleventyConfig = await getTemplateConfigInstance({\n      dir: {\n        input: inputDir,\n        output: outputDir,\n      }\n    });\n  }\n\n  let engineManager = new TemplateEngineManager(eleventyConfig);\n  if (!map) {\n    map = new EleventyExtensionMap(eleventyConfig);\n    map.setFormats([\"liquid\", \"md\", \"njk\", \"html\", \"11ty.js\"]);\n    map.engineManager = engineManager;\n  }\n  if (templateData) {\n    templateData.setFileSystemSearch(new FileSystemSearch());\n    templateData.extensionMap = map;\n  }\n  let tmpl = new Template(path, templateData, map, eleventyConfig);\n\n  await tmpl.getTemplateRender();\n\n  return tmpl;\n}\n"
  },
  {
    "path": "test/_getRenderedTemplates.js",
    "content": "async function getRenderedTemplates(template, data) {\n  let pages = await template.getTemplates(data);\n  await Promise.all(\n    pages.map(async (page) => {\n      let content = await renderTemplate(page.template, page.data);\n\n      page.templateContent = content;\n    })\n  );\n  return pages;\n}\n\nasync function renderLayout(tmpl, tmplData) {\n\tlet content = await tmpl.renderPageEntryWithoutLayout({\n\t\trawInput: await tmpl.getPreRender(),\n\t\tdata: tmplData\n\t});\n\n\tlet layoutKey = tmplData[tmpl.config.keys.layout];\n\tlet layout = tmpl.getLayout(layoutKey);\n\treturn layout.renderPageEntry({\n\t\tdata: tmplData,\n\t\ttemplateContent: content,\n\t});\n}\n\nasync function renderLayoutViaLayout(layout, tmplData, templateContent) {\n\treturn layout.renderPageEntry({\n\t\tdata: tmplData,\n\t\ttemplateContent,\n\t});\n}\n\nasync function renderTemplate(tmpl, tmplData) {\n\tif (!tmplData) {\n\t\tthrow new Error(\"`tmplData` needs to be passed into render()\");\n\t}\n\n\tif (tmplData[tmpl.config.keys.layout]) {\n\t\treturn renderLayout(tmpl, tmplData);\n\t} else {\n\t\treturn tmpl.renderPageEntryWithoutLayout({\n\t\t\trawInput: await tmpl.getPreRender(),\n\t\t\tdata: tmplData\n\t\t});\n\t}\n}\n\nexport {\n\tgetRenderedTemplates,\n\trenderLayoutViaLayout,\n\trenderLayout,\n\trenderTemplate,\n};\n"
  },
  {
    "path": "test/_issues/0/content/index.html",
    "content": "<p>HTML</p>\n"
  },
  {
    "path": "test/_issues/0/eleventy.config.js",
    "content": "export default function(cfg) {\n};\n\nexport const config = {\n\tdir: {}\n}\n"
  },
  {
    "path": "test/_issues/0/issue-0-test.js",
    "content": "import test from \"ava\";\nimport { fileURLToPath } from \"node:url\";\nimport { parse } from \"node:path\";\nimport { spawnAsync } from \"../../../src/Util/spawn.js\";\n\nconst CURRENT_DIR = parse(fileURLToPath(import.meta.url)).dir;\n\ntest.skip(\"Issue #0 (this is a stub file)\", async (t) => {\n\tlet result = await spawnAsync(\n\t\t\"node\",\n\t\t[\"../../../cmd.cjs\", \"--to=json\"],\n\t\t{\n\t\t\tcwd: CURRENT_DIR,\n\t\t}\n\t);\n\n\tlet json = JSON.parse(result);\n\n\tt.is(json.length, 1);\n\tt.is(json[0]?.content.trim(), \"<p>HTML</p>\");\n});\n\n"
  },
  {
    "path": "test/_issues/2250/2250-test.js",
    "content": "import test from \"ava\";\nimport Eleventy from \"../../../src/Eleventy.js\";\n\ntest(\"Issue #2250, page is available in filters\", async (t) => {\n  let elev = new Eleventy(\"./test/_issues/2250/\", \"./test/_issues/2250/_site\", {\n    config: function (eleventyConfig) {\n      eleventyConfig.addFilter(\"getUrl\", function () {\n        return this.page.url;\n      });\n    },\n  });\n\n  let results = await elev.toJSON();\n  let nunjucks = results.filter((entry) => {\n    return entry.url.startsWith(\"/nunjucks/\");\n  });\n\n  t.is(nunjucks[0].content.trim(), \"/nunjucks/\");\n\n  let liquid = results.filter((entry) => {\n    return entry.url.startsWith(\"/liquid/\");\n  });\n\n  t.is(liquid[0].content.trim(), \"/liquid/\");\n\n  let javascript = results.filter((entry) => {\n    return entry.url.startsWith(\"/javascript/\");\n  });\n\n  t.is(javascript[0].content.trim(), \"/javascript/\");\n});\n"
  },
  {
    "path": "test/_issues/2250/javascript.11ty.cjs",
    "content": "module.exports = function () {\n  return this.getUrl();\n};\n"
  },
  {
    "path": "test/_issues/2250/liquid.liquid",
    "content": "{{ \"test\" | getUrl }}"
  },
  {
    "path": "test/_issues/2250/nunjucks.njk",
    "content": "{{ \"test\" | getUrl }}"
  },
  {
    "path": "test/_issues/3697/3697-test.js",
    "content": "// import path from \"node:path\";\n// import { fileURLToPath } from \"node:url\";\nimport test from \"ava\";\nimport Eleventy from \"../../../src/Eleventy.js\";\n\ntest(\"Number file names on global data files\", async t => {\n  // TODO fix absolute paths here\n  // let dir = path.parse(fileURLToPath(import.meta.url)).dir;\n  let dir = \"./test/_issues/3697/\";\n  let elev = new Eleventy(dir, undefined, {\n    config: function (eleventyConfig) {\n      eleventyConfig.addTemplate(\"index.11ty.js\", function(data) {\n        return '' + JSON.stringify(data.folder);\n      })\n    },\n  });\n\n  let results = await elev.toJSON();\n  t.is(results.length, 1);\n  t.is(results[0].content, `[{\"key\":\"value\"},null,null,{}]`);\n});\n"
  },
  {
    "path": "test/_issues/3697/_data/folder/0.json",
    "content": "{\n  \"key\": \"value\"\n}\n"
  },
  {
    "path": "test/_issues/3697/_data/folder/3.json",
    "content": "{}\n"
  },
  {
    "path": "test/_issues/3809/.app/.eleventy.js",
    "content": "export const config = {\n  dir: {\n    input: \"../\",\n    data: \".app/_data\",\n  }\n};\n"
  },
  {
    "path": "test/_issues/3809/.app/_data/app.json",
    "content": "{\"name\": \"My Application\"}\n"
  },
  {
    "path": "test/_issues/3809/index.njk",
    "content": "{{ app.name }}\n"
  },
  {
    "path": "test/_issues/3853/deeper/index.njk",
    "content": "3853\n"
  },
  {
    "path": "test/_issues/3854/app/.eleventy.js",
    "content": "export const config = {\n  dir: {\n    input: \"../\",\n  }\n};\n"
  },
  {
    "path": "test/_issues/3854/app/index.njk",
    "content": "3854/child\n"
  },
  {
    "path": "test/_issues/3854/index.njk",
    "content": "3854/parent\n"
  },
  {
    "path": "test/_issues/3896/eleventy-input-folder/3896.html",
    "content": "Issue 3896"
  },
  {
    "path": "test/_issues/3896/eleventy-input-folder/_archive/ignored.html",
    "content": "This should be ignored"
  },
  {
    "path": "test/_issues/3896/test-files/eleventy.config.js",
    "content": "import path from \"node:path\";\n\nexport default function(cfg) {\n  // Works\n  // cfg.ignores.add(\"../**/_archive/**\");\n\n  cfg.ignores.add(\"**/_archive/**\");\n};\n\nexport const config = {\n  dir: {\n    input: path.resolve(\"../eleventy-input-folder\"),\n    output: path.resolve(\"../_site\")\n  }\n}"
  },
  {
    "path": "test/_issues/3896/test-files/issue3896-test.js",
    "content": "import test from \"ava\";\nimport { TemplatePath } from \"@11ty/eleventy-utils\";\n\nimport { spawnAsync } from \"../../../../src/Util/spawn.js\";\n\ntest(\"#3896 ignores should respect relative parent directory ../\", async (t) => {\n  let result = await spawnAsync(\n\t\t\"node\",\n\t\t[\"../../../../cmd.cjs\", \"--to=json\"],\n    {\n      cwd: \"test/_issues/3896/test-files/\"\n    }\n\t);\n\n  let json = JSON.parse(result);\n\n  t.is(json.length, 1);\n  t.is(json[0]?.outputPath, TemplatePath.standardizeFilePath(\"../_site/3896/index.html\"));\n  t.is(json[0]?.content.trim(), \"Issue 3896\");\n});\n\n"
  },
  {
    "path": "test/_issues/3932/1/2025.html",
    "content": "{{ page.filePathStem }}\n"
  },
  {
    "path": "test/_issues/3932/eleventy.config.js",
    "content": "export default function(cfg) {\n};\n\nexport const config = {\n\tdir: {}\n}\n"
  },
  {
    "path": "test/_issues/3932/issue-3932-test.js",
    "content": "import test from \"ava\";\nimport { fileURLToPath } from \"node:url\";\nimport { parse } from \"node:path\";\nimport { spawnAsync } from \"../../../src/Util/spawn.js\";\n\nconst CURRENT_DIR = parse(fileURLToPath(import.meta.url)).dir;\n\ntest(\"Issue #3932\", async (t) => {\n\tlet result = await spawnAsync(\n\t\t\"node\",\n\t\t[\"../../../cmd.cjs\", \"--to=json\"],\n\t\t{\n\t\t\tcwd: CURRENT_DIR,\n\t\t}\n\t);\n\n\tlet json = JSON.parse(result);\n\n\tt.is(json.length, 1);\n\tt.is(json[0]?.inputPath.trim(), \"./1/2025.html\");\n\tt.is(json[0]?.content.trim(), \"/1/2025\");\n\tt.is(json[0]?.outputPath.trim(), \"./_site/1/2025/index.html\");\n});\n\n"
  },
  {
    "path": "test/_issues/975/975-test.js",
    "content": "import test from \"ava\";\n\nimport TemplateMap from \"../../../src/TemplateMap.js\";\nimport getNewTemplateForTests from \"../../_getNewTemplateForTests.js\";\nimport { getTemplateConfigInstance } from \"../../_testHelpers.js\";\n\nfunction getNewTemplate(filename, input, output, eleventyConfig) {\n  return getNewTemplateForTests(filename, input, output, null, null, eleventyConfig);\n}\n\ntest(\"Get ordered list of templates\", async (t) => {\n  let eleventyConfig = await getTemplateConfigInstance({\n\t\tdir: {\n\t\t\tinput: \"test/_issues/975/\",\n\t\t\toutput: \"test/_issues/975/_site\",\n\t\t}\n\t});\n\n  let tm = new TemplateMap(eleventyConfig);\n\n  // These two templates are add-order-dependent\n  await tm.add(\n    await getNewTemplate(\n      \"./test/_issues/975/post.md\",\n      \"./test/_issues/975/\",\n      \"./test/_issues/975/_site\",\n      eleventyConfig\n    )\n  );\n\n  await tm.add(\n    await getNewTemplate(\n      \"./test/_issues/975/another-post.md\",\n      \"./test/_issues/975/\",\n      \"./test/_issues/975/_site\",\n      eleventyConfig\n    )\n  );\n\n  // This template should be last\n  await tm.add(\n    await getNewTemplate(\n      \"./test/_issues/975/index.md\",\n      \"./test/_issues/975/\",\n      \"./test/_issues/975/_site\",\n      eleventyConfig\n    )\n  );\n\n  await tm.cache();\n\n  let order = tm.getTemplateOrder();\n  t.deepEqual(order, [\n    \"./test/_issues/975/post.md\",\n    \"./test/_issues/975/another-post.md\",\n    \"__collection:post\",\n    \"__collection:[keys]\",\n    \"./test/_issues/975/index.md\",\n    \"__collection:all\",\n  ]);\n});\n\ntest(\"Get ordered list of templates (reverse add)\", async (t) => {\n\tlet eleventyConfig = await getTemplateConfigInstance({\n\t\tdir: {\n\t\t\tinput: \"test/_issues/975/\",\n\t\t\toutput: \"test/_issues/975/_site\",\n\t\t}\n\t});\n\n  let tm = new TemplateMap(eleventyConfig);\n\n  // This template is now first\n  await tm.add(\n    await getNewTemplate(\n      \"./test/_issues/975/index.md\",\n      \"./test/_issues/975/\",\n      \"./test/_issues/975/_site\",\n      eleventyConfig\n    )\n  );\n\n  // These two templates are add-order-dependent\n  await tm.add(\n    await getNewTemplate(\n      \"./test/_issues/975/another-post.md\",\n      \"./test/_issues/975/\",\n      \"./test/_issues/975/_site\",\n      eleventyConfig\n    )\n  );\n\n  await tm.add(\n    await getNewTemplate(\n      \"./test/_issues/975/post.md\",\n      \"./test/_issues/975/\",\n      \"./test/_issues/975/_site\",\n      eleventyConfig\n    )\n  );\n\n  await tm.cache();\n\n  let order = tm.getTemplateOrder();\n  t.deepEqual(order, [\n    \"./test/_issues/975/another-post.md\",\n    \"./test/_issues/975/post.md\",\n    \"__collection:post\",\n    \"__collection:[keys]\",\n    \"./test/_issues/975/index.md\",\n    \"__collection:all\",\n  ]);\n});\n"
  },
  {
    "path": "test/_issues/975/another-post.md",
    "content": "---\ntags:\n  - post\n---\n"
  },
  {
    "path": "test/_issues/975/index.md",
    "content": "---\neleventyImport:\n  collections: [\"post\"]\n---\n"
  },
  {
    "path": "test/_issues/975/post.md",
    "content": "---\ntags:\n  - post\n---\n"
  },
  {
    "path": "test/_testHelpers.js",
    "content": "import { existsSync, rmSync } from \"node:fs\";\nimport { isPlainObject } from \"@11ty/eleventy-utils\";\nimport TemplateConfig from \"../src/TemplateConfig.js\";\nimport ProjectDirectories from \"../src/Util/ProjectDirectories.js\";\nimport EleventyExtensionMap from \"../src/EleventyExtensionMap.js\";\nimport TemplatePassthroughManager from \"../src/TemplatePassthroughManager.js\";\nimport EleventyFiles from \"../src/EleventyFiles.js\";\nimport FileSystemSearch from \"../src/FileSystemSearch.js\";\nimport TemplateWriter from \"../src/TemplateWriter.js\";\nimport TemplateEngineManager from \"../src/Engines/TemplateEngineManager.js\";\nimport TemplateData from \"../src/Data/TemplateData.js\";\n\nexport async function getTemplateConfigInstance(configObj, dirs, configObjOverride = undefined) {\n\tlet eleventyConfig;\n\tif(configObj instanceof TemplateConfig) {\n\t\televentyConfig = configObj;\n\t\tconfigObj = undefined;\n\n\t\tif(!(dirs instanceof ProjectDirectories)) {\n\t\t\tthrow new Error(\"Testing error: second argument to getTemplateConfigInstance must be a ProjectDirectories instance when the first argument is a TemplateConfig instance.\")\n\t\t}\n\t} else {\n\t\televentyConfig = new TemplateConfig();\n\t}\n\n\televentyConfig.setProjectUsingEsm(true);\n\n\tif(!(dirs instanceof ProjectDirectories)) {\n\t\tdirs = new ProjectDirectories();\n\t\tif(isPlainObject(configObj) && !configObj.dir) {\n\t\t\tthrow new Error(\"Testing error: missing `dir` property on config object literal passed in.\")\n\t\t}\n\t\tdirs.setViaConfigObject(configObj?.dir || {});\n\t}\n\n\televentyConfig.setDirectories(dirs);\n\n\tawait eleventyConfig.init(configObjOverride || configObj); // overrides\n\n\treturn eleventyConfig;\n}\n\nexport async function getTemplateConfigInstanceCustomCallback(dirObject, configCallback) {\n\tlet tmplCfg = new TemplateConfig();\n\n\tconfigCallback(tmplCfg.userConfig);\n\n\tlet dirs = new ProjectDirectories();\n\tdirs.setViaConfigObject(dirObject);\n\n\tlet eleventyConfig = await getTemplateConfigInstance(tmplCfg, dirs, {\n\t\tdir: dirObject\n\t});\n\treturn eleventyConfig;\n}\n\nexport function getTemplateWriterInstance(formats, templateConfig) {\n  let { eleventyFiles, passthroughManager } = getEleventyFilesInstance(formats, templateConfig);\n  let templateWriter = new TemplateWriter(\n    formats,\n    null,\n    templateConfig,\n  );\n\n  let engineManager = new TemplateEngineManager(templateConfig);\n  let map = new EleventyExtensionMap(templateConfig);\n  map.engineManager = engineManager;\n  map.setFormats(formats);\n\n  templateWriter.extensionMap = map;\n\n  templateWriter.setEleventyFiles(eleventyFiles);\n  templateWriter.setPassthroughManager(passthroughManager);\n\n  return {\n    templateWriter,\n    eleventyFiles,\n    passthroughManager,\n  }\n}\n\nexport function getEleventyFilesInstance(formats, templateConfig) {\n\tlet map = new EleventyExtensionMap(templateConfig);\n\tmap.setFormats(formats);\n\n  let fss = new FileSystemSearch();\n\tlet mgr = new TemplatePassthroughManager(templateConfig);\n\n\tmgr.extensionMap = map;\n\tmgr.setFileSystemSearch(fss);\n\n\tlet files = new EleventyFiles(formats, templateConfig);\n\tfiles.setPassthroughManager(mgr);\n\tfiles.setFileSystemSearch(fss);\n\tfiles.extensionMap = map;\n  files.templateData = new TemplateData(templateConfig);\n\tfiles.init();\n\n  return {\n    eleventyFiles: files,\n    passthroughManager: mgr,\n  };\n}\n\nexport function sortEleventyResults(a, b) {\n  if(b.inputPath > a.inputPath) {\n    return 1;\n  } else if(b.inputPath < a.inputPath) {\n    return -1;\n  }\n  return 0;\n}\n\nexport function deleteDirectory(dir) {\n  if(existsSync(dir)) {\n    rmSync(dir, { recursive: true });\n  }\n}\n"
  },
  {
    "path": "test/cmdTest.js",
    "content": "import test from \"ava\";\nimport { exec } from \"child_process\";\n\ntest(\"Test command line exit code success\", async (t) => {\n  await new Promise((resolve) => {\n    exec(\"node ./cmd.cjs --input=test/stubs/exitCode_success --dryrun\", (error, stdout, stderr) => {\n      t.falsy(error);\n      resolve();\n    });\n  });\n});\n\ntest(\"Test command line exit code for template error\", async (t) => {\n  await new Promise((resolve) => {\n    exec(\"node ./cmd.cjs --input=test/stubs/exitCode --dryrun\", (error, stdout, stderr) => {\n      t.is(error.code, 1);\n      resolve();\n    });\n  });\n});\n\ntest(\"Test command line exit code for global data error\", async (t) => {\n  await new Promise((resolve) => {\n    exec(\n      \"node ./cmd.cjs --input=test/stubs/exitCode_globalData --dryrun\",\n      (error, stdout, stderr) => {\n        t.is(error.code, 1);\n        resolve();\n      }\n    );\n  });\n});\n\ntest(\"Test data should not process in a --help\", async (t) => {\n  await new Promise((resolve) => {\n    exec(\n      \"node ./cmd.cjs --input=test/stubs/cmd-help-processing --help\",\n      (error, stdout, stderr) => {\n        t.falsy(error);\n        t.is(stdout.indexOf(\"THIS SHOULD NOT LOG TO CONSOLE\"), -1);\n        resolve();\n      }\n    );\n  });\n});\n\ntest(\"Test data should not process in a --version\", async (t) => {\n  await new Promise((resolve) => {\n    exec(\n      \"node ./cmd.cjs --input=test/stubs/cmd-help-processing --version\",\n      (error, stdout, stderr) => {\n        t.falsy(error);\n        t.is(stdout.indexOf(\"THIS SHOULD NOT LOG TO CONSOLE\"), -1);\n        resolve();\n      }\n    );\n  });\n});\n"
  },
  {
    "path": "test/file-system-search/file.txt",
    "content": ""
  },
  {
    "path": "test/noop/.gitkeep",
    "content": ""
  },
  {
    "path": "test/noop2/.gitkeep",
    "content": ""
  },
  {
    "path": "test/proxy-pagination-globaldata/_data/banner.js",
    "content": "export default {\n  content: \"BANNER TEXT\",\n};\n"
  },
  {
    "path": "test/proxy-pagination-globaldata/tmpl.liquid",
    "content": "---\npages:\n  - page 1\npagination:\n  data: pages\n  size: 1\n---\n{{ banner.content }}"
  },
  {
    "path": "test/proxy-pagination-globaldata/tmpl2.njk",
    "content": "---\npages:\n  - page 1\npagination:\n  data: pages\n  size: 1\n---\n{{ banner.content }}"
  },
  {
    "path": "test/proxy-pagination-globaldata/tmpl4.11ty.js",
    "content": "const data = {\n  pages: [\"page 1\"],\n  pagination: {\n    data: \"pages\",\n    size: 1,\n  },\n};\n\nconst render = (data) => `${data.banner.content}`;\n\nexport { data, render };\n"
  },
  {
    "path": "test/semverCoerceTest.js",
    "content": "import test from \"ava\";\nimport { coerce } from \"../src/Util/SemverCoerce.js\";\n\ntest(\"semverCoerce\", t => {\n  t.is(coerce(\"4.0.0\"), \"4.0.0\");\n  t.is(coerce(\"4.0.0-prerelease\"), \"4.0.0\");\n  t.is(coerce(\"4.0\"), \"4.0\");\n  t.is(coerce(\"v4.0\"), \"4.0\");\n});\n"
  },
  {
    "path": "test/slugify-filter/comma.njk",
    "content": "---\ntitle: \"Hi, I'm ZAch\"\npermalink: subdir/{{ title | slugify }}/index.html\n---\nSlugged.\n"
  },
  {
    "path": "test/slugify-filter/multibyte.njk",
    "content": "---\ntitle: \"test-猫\"\npermalink: subdir/{{ title | slugify }}/index.html\n---\nSlugged.\n"
  },
  {
    "path": "test/slugify-filter/slug-number.njk",
    "content": "---\nnumber: 1\npermalink: subdir/{{ number | slugify }}/index.html\n---\nSlugged.\n"
  },
  {
    "path": "test/slugify-filter/slug-options.njk",
    "content": "---\ntitle: \"Hi, I am ZAch\"\npermalink: \"subdir/{{ title | slugify({separator:'_'}) }}/index.html\"\n---\nSlugged.\n"
  },
  {
    "path": "test/slugify-filter/slugify-number.njk",
    "content": "---\nnumber: 1\npermalink: subdir/{{ number | slugify }}/index.html\n---\nSlugged.\n"
  },
  {
    "path": "test/slugify-filter/slugify-options.njk",
    "content": "---\ntitle: \"Hi, I'm ZAch\"\npermalink: \"subdir/{{ title | slugify({decamelize: true}) }}/index.html\"\n---\nSlugged.\n"
  },
  {
    "path": "test/slugify-filter/test.njk",
    "content": "---\ntitle: _Slug ♥ CANDIDATE люблю $#%-\npermalink: subdir/{{ title | slugify }}/index.html\n---\nSlugged.\n"
  },
  {
    "path": "test/stubs/.eleventyignore",
    "content": "ignoredFolder\n./ignoredFolder/ignored.md\n# This is a comment\n"
  },
  {
    "path": "test/stubs/2016-02-01-permalinkdate.liquid",
    "content": "---\ntitle: Date Permalink\npermalink: \"/{{ page.date | date: '%Y/%m/%d' }}/index.html\"\n---\nDate Permalinks"
  },
  {
    "path": "test/stubs/_data/globalData.json",
    "content": "{\n  \"datakey1\": \"datavalue1\",\n  \"datakey2\": \"{{pkg.name}}\"\n}\n"
  },
  {
    "path": "test/stubs/_data/globalData2.cjs",
    "content": "module.exports = {\n  datakeyfromjs: \"howdy\"\n};\n"
  },
  {
    "path": "test/stubs/_data/globalDataFn.js",
    "content": "import dep1 from \"../deps/dep1.cjs\";\n\nexport default function () {\n  return {\n    datakeyfromjsfn: \"howdy\",\n  };\n}\n"
  },
  {
    "path": "test/stubs/_data/globalDataFnCJS.cjs",
    "content": "const dep1 = require(\"../deps/dep1.cjs\");\n\nmodule.exports = function() {\n  return {\n    datakeyfromcjsfn: \"common-cjs-howdy\"\n  };\n};\n"
  },
  {
    "path": "test/stubs/_data/subdir/testDataSubdir.json",
    "content": "{\n  \"subdirkey\": \"subdirvalue\"\n}\n"
  },
  {
    "path": "test/stubs/_data/testData.json",
    "content": "{\n  \"testdatakey1\": \"testdatavalue1\"\n}\n"
  },
  {
    "path": "test/stubs/_data/testDataLiquid.json",
    "content": "{\n  \"datakey1\": \"datavalue1\",\n  \"datakey2\": \"{{ pkg.name }}\"\n}\n"
  },
  {
    "path": "test/stubs/_includes/base.njk",
    "content": "<p>{% block content %}This is a parent.{% endblock %}</p>"
  },
  {
    "path": "test/stubs/_includes/custom-filter.liquid",
    "content": "{{ name | makeItFoo }}"
  },
  {
    "path": "test/stubs/_includes/default.liquid",
    "content": ""
  },
  {
    "path": "test/stubs/_includes/defaultLayout.liquid",
    "content": "---\nkeylayout: valuelayout\npostRank: 4\ndaysPosted: 152\nyearsPosted: 0.4\n---\n\n<div id=\"layout\">\n  {{ content }}\n</div>\n"
  },
  {
    "path": "test/stubs/_includes/defaultLayoutLayoutContent.liquid",
    "content": "---\nkeylayout: valuelayout\npostRank: 4\ndaysPosted: 152\nyearsPosted: 0.4\n---\n\n<div id=\"layout\">\n  {{ content }}\n</div>"
  },
  {
    "path": "test/stubs/_includes/imports.njk",
    "content": "{% macro label(text) %}<label>{{ text }}</label>{% endmacro %}"
  },
  {
    "path": "test/stubs/_includes/included-data.html",
    "content": "This is an include. {{ myVariable }}\n"
  },
  {
    "path": "test/stubs/_includes/included-relative.njk",
    "content": "akdlsjafkljdskl"
  },
  {
    "path": "test/stubs/_includes/included.html",
    "content": "This is an include."
  },
  {
    "path": "test/stubs/_includes/included.liquid",
    "content": "This is an include."
  },
  {
    "path": "test/stubs/_includes/included.njk",
    "content": "This is an include."
  },
  {
    "path": "test/stubs/_includes/included.nunj",
    "content": "Nunjabusiness"
  },
  {
    "path": "test/stubs/_includes/layout-a.liquid",
    "content": "---\nlayout: layout-b\nkey1: value1-a\nupstream: value2-a\n---\n\n<div id=\"layout-a\">\n  {{ content }}\n</div>\n"
  },
  {
    "path": "test/stubs/_includes/layout-b.liquid",
    "content": "---\nkey1: value1-b\nupstream: value2-b\ndaysPosted: 154\n---\n\n<div id=\"layout-b\">\n  {{ content }}\n</div>\n"
  },
  {
    "path": "test/stubs/_includes/layoutLiquid.liquid",
    "content": "---\nkeylayout: valuelayout\n---\n\n<div id=\"layout\">\n  {{ content }}\n</div>\n"
  },
  {
    "path": "test/stubs/_includes/layouts/div-wrapper-layout.njk",
    "content": "<div>{{ content }}</div>"
  },
  {
    "path": "test/stubs/_includes/layouts/engineOverrides.njk",
    "content": "---\nlayoutkey: layoutvalue\n---\n<div id=\"{{layoutkey}}\">{{ content | safe }}</div>\n"
  },
  {
    "path": "test/stubs/_includes/layouts/engineOverridesMd.njk",
    "content": "---\nlayoutkey: layoutvalue\n---\n# Layout header\n\n<div id=\"{{layoutkey}}\">{{ content | safe }}</div>"
  },
  {
    "path": "test/stubs/_includes/layouts/inasubdir.njk",
    "content": ""
  },
  {
    "path": "test/stubs/_includes/layouts/issue-115.liquid",
    "content": "{% for foo in pagination.items -%}\n{{ foo.data.title }}\n{% endfor -%}\n{% for bar in collections.bars -%}\n{{ bar.data.title }}\n{% endfor -%}"
  },
  {
    "path": "test/stubs/_includes/layouts/layout-contentdump.njk",
    "content": "---\ninherits: a\nlayout: layouts/layout-inherit-b.njk\n---\n{{content | default(\"this is bad\")}} {{inherits}}"
  },
  {
    "path": "test/stubs/_includes/layouts/layout-inherit-a.njk",
    "content": "---\ninherits: a\nlayout: layouts/layout-inherit-b.njk\n---\n{{content}} {{inherits}}"
  },
  {
    "path": "test/stubs/_includes/layouts/layout-inherit-b.njk",
    "content": "---\ninherits: b\nsecondinherits: b\nlayout: layouts/layout-inherit-c.njk\n---\n{{ content | safe }} {{secondinherits}}"
  },
  {
    "path": "test/stubs/_includes/layouts/layout-inherit-c.njk",
    "content": "---\ninherits: c\nsecondinherits: c\nthirdinherits: c\n---\n{{ content | safe }} {{inherits}} {{thirdinherits}}"
  },
  {
    "path": "test/stubs/_includes/layouts/post.liquid",
    "content": ""
  },
  {
    "path": "test/stubs/_includes/layouts/templateMapCollection.njk",
    "content": "---\nupstream: Inherited\n---\n\n{{ content | safe }}"
  },
  {
    "path": "test/stubs/_includes/multiple.liquid",
    "content": ""
  },
  {
    "path": "test/stubs/_includes/multiple.md",
    "content": ""
  },
  {
    "path": "test/stubs/_includes/mylocallayout.njk",
    "content": "<div id=\"locallayout\">{{ content | safe }}</div>\n"
  },
  {
    "path": "test/stubs/_includes/permalink-data-layout.njk",
    "content": "---\npermalink: \"{{ page.fileSlug }}/index.html\"\n---\nWrapper:{{ content | safe }}"
  },
  {
    "path": "test/stubs/_includes/permalink-in-layout/layout-fileslug.liquid",
    "content": "---\npermalink: test/{{ page.fileSlug }}/\n---\n{{ content }}"
  },
  {
    "path": "test/stubs/_includes/permalink-in-layout/layout.liquid",
    "content": "---\npermalink: hello/index.html\n---\n{{ content }}"
  },
  {
    "path": "test/stubs/_includes/scopeleak.liquid",
    "content": "{% assign test = 2 %}{{ test }}"
  },
  {
    "path": "test/stubs/_includes/subfolder/included.html",
    "content": "This is an include."
  },
  {
    "path": "test/stubs/_includes/subfolder/included.liquid",
    "content": "This is an include."
  },
  {
    "path": "test/stubs/_includes/subfolder/included.nunj",
    "content": "Nunjabusiness2"
  },
  {
    "path": "test/stubs/_includes/test.js",
    "content": "/* THIS IS A COMMENT */ alert(\"Issue #398\");\n"
  },
  {
    "path": "test/stubs/_layouts/layoutsdefault.liquid",
    "content": ""
  },
  {
    "path": "test/stubs/add-extension/test.njk",
    "content": ""
  },
  {
    "path": "test/stubs/add-extension/test.txt",
    "content": ""
  },
  {
    "path": "test/stubs/broken-config.cjs",
    "content": "const missingModule = require(\"this-is-a-module-that-does-not-exist\");\n\nmodule.exports = function(eleventyConfig) {\n  eleventyConfig.addFilter(\"cssmin\", function(code) {\n    return missingModule(code);\n  });\n\n  return {};\n};\n"
  },
  {
    "path": "test/stubs/buffer.11ty.cjs",
    "content": "module.exports = Buffer.from(\"<p>tést</p>\");\n"
  },
  {
    "path": "test/stubs/cfg-directories-export/eleventy.config.js",
    "content": "export default function(eleventyConfig) {\n\n};\n\nexport const config = {\n  dir: {\n    input: \"src\",\n    includes: \"myincludes\",\n    data: \"mydata\",\n    output: \"dist\"\n  }\n};"
  },
  {
    "path": "test/stubs/cfg-directories-export/src/.gitkeep",
    "content": ""
  },
  {
    "path": "test/stubs/cfg-directories-export-cjs/eleventy.config.cjs",
    "content": "module.exports = function(eleventyConfig) {\n\n};\n\nmodule.exports.config = {\n  dir: {\n    input: \"src\",\n    includes: \"myincludes2\",\n    data: \"mydata2\",\n    output: \"dist2\"\n  }\n}"
  },
  {
    "path": "test/stubs/cfg-directories-export-cjs/src/.gitkeep",
    "content": ""
  },
  {
    "path": "test/stubs/class-async-data-fn.11ty.cjs",
    "content": "class Test {\n  async data() {\n    return new Promise((resolve, reject) => {\n      setTimeout(function() {\n        resolve({\n          name: \"Ted\"\n        });\n      }, 50);\n    });\n  }\n\n  render({ name }) {\n    return `<p>${name}</p>`;\n  }\n}\n\nmodule.exports = Test;\n"
  },
  {
    "path": "test/stubs/class-async-filter.11ty.cjs",
    "content": "class Test {\n  static returnsTed() {\n    return \"Ted\";\n  }\n\n  returnsBill() {\n    return \"Bill\";\n  }\n\n  async render({ name }) {\n    return Promise.resolve(\n      `<p>${this.upper(name)}${this.returnsBill()}${Test.returnsTed()}</p>`\n    );\n  }\n}\n\nmodule.exports = Test;\n"
  },
  {
    "path": "test/stubs/class-async.11ty.cjs",
    "content": "class Test {\n  async render({ name }) {\n    return Promise.resolve(`<p>${name}</p>`);\n  }\n}\n\nmodule.exports = Test;\n"
  },
  {
    "path": "test/stubs/class-buffer.11ty.cjs",
    "content": "class Test {\n  returnsBill() {\n    return \"Bill\";\n  }\n\n  static returnsTed() {\n    return \"Ted\";\n  }\n\n  render({ name }) {\n    return Buffer.from(\n      `<p>${name}${this.returnsBill()}${Test.returnsTed()}</p>`\n    );\n  }\n}\n\nmodule.exports = Test;\n"
  },
  {
    "path": "test/stubs/class-data-filter.11ty.cjs",
    "content": "class Test {\n  get data() {\n    return {\n      name: \"Ted\"\n    };\n  }\n\n  render({ name }) {\n    return `<p>${this.upper(name)}</p>`;\n  }\n}\n\nmodule.exports = Test;\n"
  },
  {
    "path": "test/stubs/class-data-fn-filter.11ty.cjs",
    "content": "class Test {\n  data() {\n    return {\n      name: \"Ted\"\n    };\n  }\n\n  render({ name }) {\n    return `<p>${this.upper(name)}</p>`;\n  }\n}\n\nmodule.exports = Test;\n"
  },
  {
    "path": "test/stubs/class-data-fn-shorthand.11ty.cjs",
    "content": "function Test() {}\n\n// this doesn’t return an object?? 🤡\n// Test.prototype.data = () => { name: \"Ted\" };\nTest.prototype.data = () => {\n  return { name: \"Ted\" };\n};\nTest.prototype.render = ({ name }) => `<p>${name}</p>`;\n\n/*\nTest.prototype.data = function() {\n  return { name: \"Ted\" };\n};\nTest.prototype.render = function(data) {\n  return `<p>${data.name}</p>`;\n}\n*/\n\n/*\n// this isn’t valid syntax?? 🤡\nclass Test {\n  data() => {\n    name: \"Ted\"\n  };\n\n  render({ name }) => `<p>${name}</p>`;\n}\n*/\n\nmodule.exports = Test;\n"
  },
  {
    "path": "test/stubs/class-data-fn.11ty.cjs",
    "content": "class Test {\n  data() {\n    return {\n      name: \"Ted\"\n    };\n  }\n\n  render({ name }) {\n    return `<p>${name}</p>`;\n  }\n}\n\nmodule.exports = Test;\n"
  },
  {
    "path": "test/stubs/class-data-permalink-async-fn.11ty.cjs",
    "content": "class Test {\n  get data() {\n    return {\n      key: \"value1\",\n      permalink: async function(data) {\n        return new Promise((resolve, reject) => {\n          setTimeout(function() {\n            resolve(`/my-permalink/${data.key}/`);\n          }, 100);\n        });\n      }\n    };\n  }\n\n  render({ name }) {\n    return `<p>${name}</p>`;\n  }\n}\n\nmodule.exports = Test;\n"
  },
  {
    "path": "test/stubs/class-data-permalink-buffer.11ty.cjs",
    "content": "class Test {\n  get data() {\n    return {\n      permalink: Buffer.from(\"/my-permalink/\")\n    };\n  }\n\n  render({ name }) {\n    return `<p>${name}</p>`;\n  }\n}\n\nmodule.exports = Test;\n"
  },
  {
    "path": "test/stubs/class-data-permalink-fn-buffer.11ty.cjs",
    "content": "class Test {\n  get data() {\n    return {\n      key: \"value1\",\n      permalink: data => Buffer.from(`/my-permalink/${data.key}/`)\n    };\n  }\n\n  render({ name }) {\n    return `<p>${name}</p>`;\n  }\n}\n\nmodule.exports = Test;\n"
  },
  {
    "path": "test/stubs/class-data-permalink-fn-filter.11ty.cjs",
    "content": "class Test {\n  get data() {\n    return {\n      title: \"My Super Cool Title\",\n      permalink: function({ title }) {\n        return `/my-permalink/${this.slugify(title)}/`;\n      }\n    };\n  }\n\n  render({ name }) {\n    return `<p>${name}</p>`;\n  }\n}\n\nmodule.exports = Test;\n"
  },
  {
    "path": "test/stubs/class-data-permalink-fn.11ty.cjs",
    "content": "class Test {\n  get data() {\n    return {\n      key: \"value1\",\n      permalink: data => `/my-permalink/${data.key}/`\n    };\n  }\n\n  render({ name }) {\n    return `<p>${name}</p>`;\n  }\n}\n\nmodule.exports = Test;\n"
  },
  {
    "path": "test/stubs/class-data-permalink.11ty.cjs",
    "content": "class Test {\n  get data() {\n    return {\n      permalink: \"/my-permalink/\"\n    };\n  }\n\n  render({ name }) {\n    return `<p>${name}</p>`;\n  }\n}\n\nmodule.exports = Test;\n"
  },
  {
    "path": "test/stubs/class-data.11ty.cjs",
    "content": "class Test {\n  get data() {\n    return {\n      name: \"Ted\"\n    };\n  }\n\n  render({ name }) {\n    return `<p>${name}</p>`;\n  }\n}\n\nmodule.exports = Test;\n"
  },
  {
    "path": "test/stubs/class-filter.11ty.cjs",
    "content": "class Test {\n  static returnsTed() {\n    return \"Ted\";\n  }\n\n  returnsBill() {\n    return \"Bill\";\n  }\n\n  render({ name }) {\n    return `<p>${this.upper(\n      name\n    )}${this.returnsBill()}${Test.returnsTed()}</p>`;\n  }\n}\n\nmodule.exports = Test;\n"
  },
  {
    "path": "test/stubs/class-fns-has-page.11ty.cjs",
    "content": "class TestWithPage {\n  get page() {\n    return \"this-is-my-page\";\n  }\n\n  render(data) {\n    data.avaTest.is(this.page, \"this-is-my-page\");\n    data.avaTest.is(data.page.url, \"/hi/\");\n  }\n}\n\nmodule.exports = TestWithPage;\n"
  },
  {
    "path": "test/stubs/class-fns.11ty.cjs",
    "content": "class Test {\n  render({ avaTest }) {\n    avaTest.truthy(this.url);\n    avaTest.truthy(this.slug);\n    avaTest.truthy(this.log);\n    avaTest.truthy(this.getPreviousCollectionItem);\n    avaTest.truthy(this.getNextCollectionItem);\n    avaTest.truthy(this.page);\n  }\n}\n\nmodule.exports = Test;\n"
  },
  {
    "path": "test/stubs/class-norender.11ty.cjs",
    "content": "class Test {\n  data() {\n    return {\n      name: \"Ted\"\n    };\n  }\n}\n\nmodule.exports = Test;\n"
  },
  {
    "path": "test/stubs/class-with-dep-upstream.js",
    "content": "module.exports = function() {};\n"
  },
  {
    "path": "test/stubs/class-with-dep.11ty.cjs",
    "content": "const Dep = require(\"./class-with-dep-upstream.js\");\n\nclass Test {\n  returnsBill() {\n    return \"Bill\";\n  }\n  static returnsTed() {\n    return \"Ted\";\n  }\n  render({ name }) {\n    return `<p>${name}${this.returnsBill()}${Test.returnsTed()}</p>`;\n  }\n}\n\nmodule.exports = Test;"
  },
  {
    "path": "test/stubs/class.11ty.cjs",
    "content": "class Test {\n  returnsBill() {\n    return \"Bill\";\n  }\n\n  static returnsTed() {\n    return \"Ted\";\n  }\n\n  render({ name }) {\n    return `<p>${name}${this.returnsBill()}${Test.returnsTed()}</p>`;\n  }\n}\n\nmodule.exports = Test;\n"
  },
  {
    "path": "test/stubs/classfields-data.11ty.cjs",
    "content": "class Test {\n  data = {\n    name: \"Ted\"\n  };\n\n  render({ name }) {\n    return `<p>${name}</p>`;\n  }\n}\n\nmodule.exports = Test;\n"
  },
  {
    "path": "test/stubs/cmd-help-processing/_data/test.js",
    "content": "console.log(\"THIS SHOULD NOT LOG TO CONSOLE\");\nmodule.exports = [];\n"
  },
  {
    "path": "test/stubs/collection/test1.md",
    "content": "---\ntitle: Test Title\ntags:\n  - post\n  - dog\n---\n\n# Test 1\n"
  },
  {
    "path": "test/stubs/collection/test10.md",
    "content": "---\ntitle: Test Title\ntags:\n  - post\n  - office\neleventyExcludeFromCollections:\n  - post\n---\n\n# Test 1\n"
  },
  {
    "path": "test/stubs/collection/test2.md",
    "content": "---\ntags: cat\n---\n\n# Test 2\n"
  },
  {
    "path": "test/stubs/collection/test3.md",
    "content": "---\ntags:\n  - post\n  - cat\n---\n\n# Test 3\n"
  },
  {
    "path": "test/stubs/collection/test4.md",
    "content": "---\ndate: 1983-01-01\n---\n\n# Test 3\n"
  },
  {
    "path": "test/stubs/collection/test5.md",
    "content": "---\ndate: 2038-01-01\n---\n\n# Test 3\n"
  },
  {
    "path": "test/stubs/collection/test6.html",
    "content": "# Test 6\n"
  },
  {
    "path": "test/stubs/collection/test7.njk",
    "content": "# Test 7"
  },
  {
    "path": "test/stubs/collection/test8.md",
    "content": "---\ntitle: Test Title\ntags:\n  - post\n  - office\neleventyExcludeFromCollections: true\n---\n\n# Test 1\n"
  },
  {
    "path": "test/stubs/collection/test9.md",
    "content": "---\ntitle: Test Title\ntags:\n  - post\n  - office\neleventyExcludeFromCollections: post\n---\n\n# Test 1\n"
  },
  {
    "path": "test/stubs/collection-layout/_includes/layout.liquid",
    "content": "Layout\n\n{{ content }}\n\nAll {{ collections.all | size }} templates\nLayout {{ collections.dog | size }} dog"
  },
  {
    "path": "test/stubs/collection-layout/dog1.liquid",
    "content": "---\ntags:\n  - dog\n---"
  },
  {
    "path": "test/stubs/collection-layout/template.liquid",
    "content": "---\nlayout: layout.liquid\n---\nTemplate"
  },
  {
    "path": "test/stubs/collection-layout-wrap.njk",
    "content": "---\ntitle: Layout Test\nlayout: layouts/div-wrapper-layout.njk\n---\n{{ title }}"
  },
  {
    "path": "test/stubs/collection-slug/dog1.njk",
    "content": "---\ntags:\n  - dog\n---"
  },
  {
    "path": "test/stubs/collection-slug/template.njk",
    "content": "fileSlug:{% for post in collections.dog %}{{ post.url }}:{{ post.fileSlug }}{% endfor %}"
  },
  {
    "path": "test/stubs/collection-template/_includes/layout.liquid",
    "content": "Layout\n\n{{ content }}"
  },
  {
    "path": "test/stubs/collection-template/dog1.liquid",
    "content": "---\ntags:\n  - dog\n---"
  },
  {
    "path": "test/stubs/collection-template/template.liquid",
    "content": "---\nlayout: layout.liquid\n---\nTemplate\n\nAll {{ collections.all | size }} templates\nTemplate {{ collections.dog | size }} dog"
  },
  {
    "path": "test/stubs/collection2/test1.md",
    "content": "---\ntitle: Test Title\ntags:\n  - post\n  - dog\ndate: 2009-01-01\n---\n\n# Test 1\n"
  },
  {
    "path": "test/stubs/collection2/test2.md",
    "content": "---\ntags:\n  - post\n  - cat\ndate: 2010-01-01\n---\n\n# Test 2\n"
  },
  {
    "path": "test/stubs/component/component.11tydata.cjs",
    "content": "const dep2 = require(\"../deps/dep2.cjs\");\n\nmodule.exports = {\n  localdatakeyfromcjs: \"common-js-howdydoody\"\n};\n"
  },
  {
    "path": "test/stubs/component/component.11tydata.js",
    "content": "import dep2 from \"../deps/dep2.cjs\";\n\nexport default {\n  localdatakeyfromjs: \"howdydoody\",\n};\n"
  },
  {
    "path": "test/stubs/component/component.11tydata.json",
    "content": "{\n  \"localdatakeyfromjs\": \"this_is_overridden\",\n  \"localdatakeyfromcjs\": \"this_is_overridden\",\n  \"localdatakeyfromjs2\": \"howdy2\"\n}\n"
  },
  {
    "path": "test/stubs/component/component.json",
    "content": "{\n  \"localdatakey1\": \"localdatavalue1\",\n  \"localdatakeyfromjs\": \"this_is_also_overridden\"\n}\n"
  },
  {
    "path": "test/stubs/component/component.njk",
    "content": "{{localdatakey1}}"
  },
  {
    "path": "test/stubs/component-async/component.11tydata.cjs",
    "content": "module.exports = async function() {\n  return new Promise(resolve => {\n    setTimeout(function() {\n      resolve({\n        localdatakeyfromcjs: \"common-js-howdydoody\"\n      });\n    }, 1);\n  });\n};\n"
  },
  {
    "path": "test/stubs/component-async/component.11tydata.js",
    "content": "export default async function () {\n  return new Promise((resolve) => {\n    setTimeout(function () {\n      resolve({\n        localdatakeyfromjs: \"howdydoody\",\n      });\n    }, 1);\n  });\n}\n"
  },
  {
    "path": "test/stubs/component-async/component.njk",
    "content": "{{localdatakey1}}"
  },
  {
    "path": "test/stubs/config-deps-upstream.cjs",
    "content": "module.exports = function() {};\n"
  },
  {
    "path": "test/stubs/config-deps.cjs",
    "content": "const pretty = require(\"pretty\");\nconst Dep = require(\"./config-deps-upstream.cjs\");\n\nmodule.exports = function(config) {\n  return {};\n};\n"
  },
  {
    "path": "test/stubs/config-empty-pathprefix.cjs",
    "content": "module.exports = function (config) {\n  return {\n    pathPrefix: \"\",\n  };\n};\n"
  },
  {
    "path": "test/stubs/config-promise.js",
    "content": "module.exports = async function() {\n  return {\n    layouts: \"promise\"\n  };\n};\n"
  },
  {
    "path": "test/stubs/config.cjs",
    "content": "const pretty = require(\"pretty\");\n\nmodule.exports = function (config) {\n  /* {\n    template,\n    inputPath,\n    outputPath,\n    url,\n    data,\n    date\n  } */\n\n  return {\n    markdownTemplateEngine: \"njk\",\n    templateFormats: [\"md\", \"njk\"],\n\n    pathPrefix: \"/testdir\",\n\n    keys: {\n      package: \"pkg2\",\n    },\n    transforms: {\n      prettyHtml: function (str, outputPath) {\n        if (outputPath.split(\".\").pop() === \"html\") {\n          return pretty(str, { ocd: true });\n        } else {\n          return str;\n        }\n      },\n    },\n    nunjucksFilters: {\n      testing: (str) => {\n        return str;\n      },\n    },\n  };\n};\n"
  },
  {
    "path": "test/stubs/custom-extension-no-permalink.txt",
    "content": "Sample content"
  },
  {
    "path": "test/stubs/custom-extension.txt",
    "content": "---\npermalink: custom-extension.lit\n---\nSample content"
  },
  {
    "path": "test/stubs/custom-frontmatter/template-excerpt-comment.njk",
    "content": "---\nfront: hello\n---\nThis is an excerpt.\n<!-- excerpt -->\nThis is content."
  },
  {
    "path": "test/stubs/custom-frontmatter/template-newline1.njk",
    "content": "---\nfront: hello\n---\nThis is an excerpt.---\nThis is content."
  },
  {
    "path": "test/stubs/custom-frontmatter/template-newline2.njk",
    "content": "---\nfront: hello\n---\nThis is an excerpt.---This is content."
  },
  {
    "path": "test/stubs/custom-frontmatter/template-newline3.njk",
    "content": "---\nfront: hello\n---\nThis is an excerpt.\n---This is content."
  },
  {
    "path": "test/stubs/custom-frontmatter/template-nonewline.njk",
    "content": "---\nfront: hello\n---\nThis is an excerpt.\n---This is content."
  },
  {
    "path": "test/stubs/custom-frontmatter/template-toml.njk",
    "content": "---toml\nfront = \"hello\"\n---\nThis is content."
  },
  {
    "path": "test/stubs/custom-frontmatter/template.njk",
    "content": "---\nfront: hello\n---\nThis is an excerpt.\n---\nThis is content."
  },
  {
    "path": "test/stubs/data-cascade/template.11tydata.cjs",
    "content": "module.exports = {\n  parent: {\n    child: 2,\n    datafile: true,\n  },\n  datafile: true,\n  tags: [\"tagC\", \"tagD\", \"tagD\"],\n};\n"
  },
  {
    "path": "test/stubs/data-cascade/template.njk",
    "content": "---\nparent:\n  child: -2\n  frontmatter: true\nfrontmatter: true\ntags:\n  - tagA\n  - tagB\n---"
  },
  {
    "path": "test/stubs/datafiledoesnotexist/template.njk",
    "content": ""
  },
  {
    "path": "test/stubs/dates/2018-01-01-file5.md",
    "content": "---\ntags: dateTestTag\n---\n"
  },
  {
    "path": "test/stubs/dates/2019-01-01-folder/2020-01-01-file.md",
    "content": "---\n---\n"
  },
  {
    "path": "test/stubs/dates/file1.md",
    "content": "---\ntags: dateTestTag\n---\n\nAssume file created time.\n"
  },
  {
    "path": "test/stubs/dates/file2.md",
    "content": "---\ntags: dateTestTag\ndate: \"2016-01-01\"\n---\n"
  },
  {
    "path": "test/stubs/dates/file2b.md",
    "content": "---\ntags: dateTestTag\ndate: 2016-01-01\n---\n"
  },
  {
    "path": "test/stubs/dates/file3.md",
    "content": "---\ntags: dateTestTag\ndate: Last Modified\n---\n"
  },
  {
    "path": "test/stubs/dates/file4.md",
    "content": "---\ntags: dateTestTag\ndate: Created\n---\n"
  },
  {
    "path": "test/stubs/default-class-export-and-others.11ty.js",
    "content": "export default class IndexPage {\n  render(data) {\n    const name = world();\n    return `<div>hello</div>`;\n  }\n}\n\nexport function world() {\n  return \"World\";\n}"
  },
  {
    "path": "test/stubs/default-export-and-others.11ty.js",
    "content": "export const foo = \"test\";\n\n// render\nexport default () => \"<h1>hello</h1>\";"
  },
  {
    "path": "test/stubs/default-frontmatter.txt",
    "content": "---\nfrontmatter: 1\n---\nhi"
  },
  {
    "path": "test/stubs/default-function-export-and-named-data.11ty.cjs",
    "content": "// render\nmodule.exports = (data) => `<h1>${data.name} World</h1>`;\nmodule.exports.data = function() {\n  return { name: \"Hello\" }\n};\n"
  },
  {
    "path": "test/stubs/default-function-export-and-named-data.11ty.js",
    "content": "export function data() {\n  return { name: \"Hello\" }\n};\n\n// render\nexport default (data) => `<h1>${data.name} World</h1>`;"
  },
  {
    "path": "test/stubs/default-no-liquid.md",
    "content": "hi\n"
  },
  {
    "path": "test/stubs/default.liquid",
    "content": "{{ \"hi\" }}"
  },
  {
    "path": "test/stubs/default.md",
    "content": "{{ \"hi\" }}\n"
  },
  {
    "path": "test/stubs/dependencies/dep1.cjs",
    "content": "module.exports = function() {};\n"
  },
  {
    "path": "test/stubs/dependencies/dep2.cjs",
    "content": "module.exports = function() {};\n"
  },
  {
    "path": "test/stubs/dependencies/two-deps.11ty.cjs",
    "content": "const dep1 = require(\"./dep1.cjs\");\nconst dep2 = require(\"./dep2.cjs\");\n\nmodule.exports = \"\";\n"
  },
  {
    "path": "test/stubs/deps/dep1.cjs",
    "content": "module.exports = function() {};\n"
  },
  {
    "path": "test/stubs/deps/dep2.cjs",
    "content": "module.exports = function() {};\n"
  },
  {
    "path": "test/stubs/dynamic-permalink/test.njk",
    "content": "---\npermalink: \"/{{justastring}}/\"\ndynamicPermalink: false\n---"
  },
  {
    "path": "test/stubs/eleventyComputed/first.njk",
    "content": "---\nkey1: value1\neleventyComputed:\n  key2: value2-{{ key1 }}.css\n---\nhi:{{ key2 }}\n"
  },
  {
    "path": "test/stubs/eleventyComputed/override-reuse.njk",
    "content": "---\nkey1: value1\neleventyComputed:\n  key1: \"over({{key1}})ride\"\n---\nhi:{{ key1 }}\n"
  },
  {
    "path": "test/stubs/eleventyComputed/override.njk",
    "content": "---\nkey1: value1\neleventyComputed:\n  key1: override\n---\nhi:{{ key1 }}\n"
  },
  {
    "path": "test/stubs/eleventyComputed/permalink-simple.njk",
    "content": "---\nkey1: value1\neleventyComputed:\n  permalink: \"haha-{{key1}}.html\"\n---\nhi:{{ key2 }}"
  },
  {
    "path": "test/stubs/eleventyComputed/permalink-slug.njk",
    "content": "---\nkey1: \"This is a string\"\neleventyComputed:\n  permalink: \"haha-{{key1 | slugify}}.html\"\n---"
  },
  {
    "path": "test/stubs/eleventyComputed/permalink.njk",
    "content": "---\nkey1: value1\neleventyComputed:\n  permalink: \"haha-{{key2}}.html\"\n  key2: \"{{key1}}\"\n  dependsOnPage: \"depends:{{page.url}}\"\n  nested:\n    key3: \"{{key1}}\"\n    key4: \"depends on computed {{key2}}\"\n---\nhi:{{ key2 }}\n"
  },
  {
    "path": "test/stubs/eleventyComputed/second.njk",
    "content": "---js\n{\n\tkey1: \"value1\",\n\televentyComputed: {\n\t\tkey3: function(data) {\n\t\t\treturn `value3-${data.key2}`;\n\t\t},\n\t\tkey2: function(data) {\n\t\t\treturn `value2-${data.key1}.css`;\n\t\t}\n\t}\n}\n---\nhi:{{ key2 }}\n"
  },
  {
    "path": "test/stubs/eleventyComputed/third.njk",
    "content": "---js\n{\n\tkey1: \"value1\",\n\televentyComputed: {\n\t\tkey1: function(data) {\n\t\t\treturn `value2-${data.key1}`;\n\t\t}\n\t}\n}\n---\nhi:{{ key1 }}\n"
  },
  {
    "path": "test/stubs/eleventyComputed/true.njk",
    "content": "---\nkey1: value1\neleventyComputed:\n  key2: true\n  key3: false\n  key4: 324\n---\n"
  },
  {
    "path": "test/stubs/eleventyComputed/use-global-data.njk",
    "content": "---js\n{\n  eleventyComputed: {\n    image: data => {\n      return data.globalData.datakey1;\n    }\n  }\n}\n---\nIssue #1043"
  },
  {
    "path": "test/stubs/eleventyExcludeFromCollections.njk",
    "content": "---\ntitle: Paged Test\neleventyExcludeFromCollections: true\ntags:\n  - post\n  - dog\n---\n{{ title }}"
  },
  {
    "path": "test/stubs/eleventyExcludeFromCollectionsPermalinkFalse.njk",
    "content": "---\ntitle: Paged Test\neleventyExcludeFromCollections: true\npermalink: false\ntags:\n  - post\n  - dog\n---\n{{ title }}"
  },
  {
    "path": "test/stubs/engine-singletons/first.njk",
    "content": ""
  },
  {
    "path": "test/stubs/engine-singletons/second.njk",
    "content": ""
  },
  {
    "path": "test/stubs/exitCode/failure.njk",
    "content": "{{ test() }}"
  },
  {
    "path": "test/stubs/exitCode_globalData/_data/test.js",
    "content": "module.exports = async function () {\n  throw new Error(\"Testing\");\n};\n"
  },
  {
    "path": "test/stubs/exitCode_globalData/test.liquid",
    "content": ""
  },
  {
    "path": "test/stubs/exitCode_success/success.njk",
    "content": "{{ \"hi\" }}"
  },
  {
    "path": "test/stubs/exports-flatdata.11ty.cjs",
    "content": "// This is invalid, data must be an object\nexports.data = \"Ted\";\n\nexports.render = function(name) {\n  return `<p>${JSON.stringify(name)}</p>`;\n};\n"
  },
  {
    "path": "test/stubs/fileslug.11ty.cjs",
    "content": "module.exports = function(data) {\n  return `<p>${data.page.fileSlug}</p>`;\n};\n"
  },
  {
    "path": "test/stubs/firstdir/seconddir/component.njk",
    "content": ""
  },
  {
    "path": "test/stubs/formatTest.liquid",
    "content": "---\nname: zach\n---\n<p>{{name | capitalize}}</p>"
  },
  {
    "path": "test/stubs/frontmatter-date/test.liquid",
    "content": "---\nmydate: 2009-04-15T12:34:34+01:00\n---\n{{ mydate | date: \"%Y-%m-%d\" }}"
  },
  {
    "path": "test/stubs/frontmatter-date/test.njk",
    "content": "---\nmydate: 2009-04-15T01:34:34+01:00\n---\n{{ mydate.toISOString() }}"
  },
  {
    "path": "test/stubs/function-arrow.11ty.cjs",
    "content": "module.exports = ({ name }) => `<p>${name}</p>`;\n"
  },
  {
    "path": "test/stubs/function-async-filter.11ty.cjs",
    "content": "module.exports = async function({ name }) {\n  return `<p>${await this.upper(name)}</p>`;\n};\n"
  },
  {
    "path": "test/stubs/function-async.11ty.cjs",
    "content": "module.exports = async function(data) {\n  return new Promise((resolve, reject) => {\n    setTimeout(function() {\n      resolve(`<p>${data.name}</p>`);\n    }, 100);\n  });\n};\n"
  },
  {
    "path": "test/stubs/function-buffer.11ty.cjs",
    "content": "module.exports = function(data) {\n  return Buffer.from(`<p>${data.name}</p>`);\n};\n"
  },
  {
    "path": "test/stubs/function-filter.11ty.cjs",
    "content": "function myFunction({ name }) {\n  return `<p>${this.upper(name)}${myFunction.staticMethod()}</p>`;\n}\n\nmyFunction.staticMethod = function() {\n  return \"T9000\";\n};\n\nmodule.exports = myFunction;\n"
  },
  {
    "path": "test/stubs/function-fns.11ty.cjs",
    "content": "module.exports = function({ avaTest }) {\n  avaTest.truthy(this.url);\n  avaTest.truthy(this.slug);\n  avaTest.truthy(this.log);\n  avaTest.truthy(this.getPreviousCollectionItem);\n  avaTest.truthy(this.getNextCollectionItem);\n  avaTest.truthy(this.page);\n\n  return \"test\";\n};\n"
  },
  {
    "path": "test/stubs/function-markdown.11ty.cjs",
    "content": "module.exports = function(data) {\n  return `# ${data.name}`;\n};\n"
  },
  {
    "path": "test/stubs/function-prototype.11ty.cjs",
    "content": "function myFunction() {}\n\nmyFunction.prototype.render = function({ name }) {\n  return `<p>${this.upper(\n    name\n  )}${this.returnsBill()}${myFunction.staticMethod()}</p>`;\n};\n\nmyFunction.prototype.returnsBill = function() {\n  return \"Bill\";\n};\n\nmyFunction.staticMethod = function() {\n  return \"T9001\";\n};\n\nmodule.exports = myFunction;\n"
  },
  {
    "path": "test/stubs/function-throws-async.11ty.cjs",
    "content": "module.exports = async function(data) {\n  return `<p>${await this.upper(data.name)}</p>`;\n};\n"
  },
  {
    "path": "test/stubs/function-throws.11ty.cjs",
    "content": "module.exports = function(data) {\n  return `<p>${this.upper(data.name)}</p>`;\n};\n"
  },
  {
    "path": "test/stubs/function.11ty.cjs",
    "content": "module.exports = function(data) {\n  return `<p>${data.name}</p>`;\n};\n"
  },
  {
    "path": "test/stubs/glob-pages/about.md",
    "content": "---\ntitle: \"About\"\n---\n\nAbout\n"
  },
  {
    "path": "test/stubs/glob-pages/contact.md",
    "content": "---\ntitle: \"Contact\"\n---\n\nContact\n"
  },
  {
    "path": "test/stubs/glob-pages/home.md",
    "content": "---\ntitle: \"Home\"\n---\n\nHome\n"
  },
  {
    "path": "test/stubs/global-dash-variable.liquid",
    "content": "---\nis-it-tasty: Yes\n---\n{{ is-it-tasty }}"
  },
  {
    "path": "test/stubs/globby/_includes/include.html",
    "content": ""
  },
  {
    "path": "test/stubs/globby/test.html",
    "content": ""
  },
  {
    "path": "test/stubs/ignore-dedupe/.gitignore",
    "content": "ignoredFolder\nignoredFolder"
  },
  {
    "path": "test/stubs/ignore1/ignoredFolder/ignored.md",
    "content": "# This should be ignored\n"
  },
  {
    "path": "test/stubs/ignore2/.gitignore",
    "content": "thisshouldnotexist12345"
  },
  {
    "path": "test/stubs/ignore2/ignoredFolder/ignored.md",
    "content": "# This should be ignored\n"
  },
  {
    "path": "test/stubs/ignore3/.eleventyignore",
    "content": "ignoredFolder\n./ignoredFolder/ignored.md\n# This is a comment\n"
  },
  {
    "path": "test/stubs/ignore3/ignoredFolder/ignored.md",
    "content": "# This should be ignored\n"
  },
  {
    "path": "test/stubs/ignore4/.eleventyignore",
    "content": "ignoredFolder\n./ignoredFolder/ignored.md\n# This is a comment\n"
  },
  {
    "path": "test/stubs/ignore4/ignoredFolder/ignored.md",
    "content": "# This should be ignored\n"
  },
  {
    "path": "test/stubs/ignore5/.gitignore",
    "content": ""
  },
  {
    "path": "test/stubs/ignore5/ignoredFolder/ignored.md",
    "content": "# This should be ignored\n"
  },
  {
    "path": "test/stubs/ignore6/.eleventyignore",
    "content": "ignoredFolder\n./ignoredFolder/ignored.md\n# This is a comment\n"
  },
  {
    "path": "test/stubs/ignore6/.gitignore",
    "content": ""
  },
  {
    "path": "test/stubs/ignore6/ignoredFolder/ignored.md",
    "content": "# This should be ignored\n"
  },
  {
    "path": "test/stubs/ignoredFolder/ignored.md",
    "content": "# This should be ignored\n"
  },
  {
    "path": "test/stubs/ignorelocalroot/.eleventyignore",
    "content": "test.md"
  },
  {
    "path": "test/stubs/ignorelocalrootgitignore/.eleventyignore",
    "content": "test.md"
  },
  {
    "path": "test/stubs/ignorelocalrootgitignore/.gitignore",
    "content": "thisshouldnotexist12345"
  },
  {
    "path": "test/stubs/img/stub.md",
    "content": "\n"
  },
  {
    "path": "test/stubs/included.liquid",
    "content": "This is not in the includes dir."
  },
  {
    "path": "test/stubs/includer.liquid",
    "content": "<p>{% include 'included' %}</p>\n"
  },
  {
    "path": "test/stubs/includesemptystring.liquid",
    "content": ""
  },
  {
    "path": "test/stubs/index.html",
    "content": "<html>\n<body>\n\tThis is an html template.\n</body>\n</html>"
  },
  {
    "path": "test/stubs/index.liquid",
    "content": ""
  },
  {
    "path": "test/stubs/issue-115/index-with-layout.liquid",
    "content": "---\nlayout: layouts/issue-115.liquid\ntitle: My index page\npagination:\n  data: collections.foos\n  size: 12\n---\n"
  },
  {
    "path": "test/stubs/issue-115/index.liquid",
    "content": "---\ntitle: My index page\npagination:\n  data: collections.foos\n  size: 12\n---\n{% for foo in pagination.items -%}\n{{ foo.data.title }}\n{% endfor -%}\n{% for bar in collections.bars -%}\n{{ bar.data.title }}\n{% endfor -%}"
  },
  {
    "path": "test/stubs/issue-115/template-bars.liquid",
    "content": "---\ntitle: This page is bars\ntags:\n  - bars\n---\nBars"
  },
  {
    "path": "test/stubs/issue-115/template-foos.liquid",
    "content": "---\ntitle: This page is foos\ntags:\n  - foos\n---\nFoos."
  },
  {
    "path": "test/stubs/issue-135/template.json",
    "content": "{\n  \"articles\":\n  [\n    {\n      \"title\": \"Do you even paginate bro?\",\n      \"author\": \"Bill Ted\",\n      \"publish_date\": \"2018-06-22\",\n      \"tags\": [\n        \"post\"\n      ],\n      \"image\": \"/url/thing\",\n      \"teaser\": \"Teaser copy\",\n      \"body\": \"<p>Raw HTML</p>\"\n    }\n  ]\n}\n"
  },
  {
    "path": "test/stubs/issue-135/template.njk",
    "content": "---\npagination:\n  data: articles\n  size: 1\n  alias: article\npermalink: blog/{{ article.title | slugify }}/index.html\n---\n{{ article.body | safe }}"
  },
  {
    "path": "test/stubs/issue-522/excluded.md",
    "content": "---\neleventyExcludeFromCollections: true\n---\n\n# Test\n\n{{ collections.all[0].templateContent }}\n"
  },
  {
    "path": "test/stubs/issue-522/template.md",
    "content": "# Test\n"
  },
  {
    "path": "test/stubs/issue-95/cat.md",
    "content": "---\ntags: \n  - cat\n---\n\n# Test 8\n"
  },
  {
    "path": "test/stubs/issue-95/notacat.md",
    "content": "---\ntags: notacat\n---\n\n# Test 8\n"
  },
  {
    "path": "test/stubs/layout-permalink-difflang/_includes/test.njk",
    "content": "---\npermalink: \"/{{ page.fileSlug }}/\"\n---\n{{ content }}"
  },
  {
    "path": "test/stubs/layout-permalink-difflang/test.md",
    "content": "---\nlayout: test.njk\ntemplateEngineOverride: md\n---\n\n# Title\n"
  },
  {
    "path": "test/stubs/layoutsemptystring.liquid",
    "content": ""
  },
  {
    "path": "test/stubs/local-data-tags/component.11tydata.cjs",
    "content": "module.exports = {\n  tags: \"tag3\"\n};\n"
  },
  {
    "path": "test/stubs/local-data-tags/component.njk",
    "content": "---\ntags:\n  - tag1\n  - tag2\n---\n{{localdatakey1}}"
  },
  {
    "path": "test/stubs/multiple-ignores/.eleventyignore",
    "content": "ignoredFolder\n./ignoredFolder/ignored.md\n# This is a comment"
  },
  {
    "path": "test/stubs/multiple-ignores/ignoredFolder/ignored.md",
    "content": ""
  },
  {
    "path": "test/stubs/multiple-ignores/subfolder/.eleventyignore",
    "content": "ignoredFolder2\n./ignoredFolder2/ignored2.md\n# This is a comment"
  },
  {
    "path": "test/stubs/multiple-ignores/subfolder/ignoredFolder2/ignored2.md",
    "content": ""
  },
  {
    "path": "test/stubs/multipleexports-promises.11ty.cjs",
    "content": "exports.data = async function() {\n  return new Promise((resolve, reject) => {\n    setTimeout(function() {\n      resolve({ name: \"Ted\" });\n    }, 100);\n  });\n};\n\nexports.render = async function({ name }) {\n  return new Promise((resolve, reject) => {\n    setTimeout(function() {\n      resolve(`<p>${name}</p>`);\n    }, 100);\n  });\n};\n"
  },
  {
    "path": "test/stubs/multipleexports.11ty.cjs",
    "content": "exports.data = {\n  name: \"Ted\"\n};\n\nexports.render = function({ name }) {\n  return `<p>${name}</p>`;\n};\n"
  },
  {
    "path": "test/stubs/njk-relative/dir/base.njk",
    "content": "<p>{% block content %}This is a parent.{% endblock %}</p>"
  },
  {
    "path": "test/stubs/njk-relative/dir/imports.njk",
    "content": "{% macro label(text) %}<label>{{ text }}</label>{% endmacro %}"
  },
  {
    "path": "test/stubs/njk-relative/dir/included.njk",
    "content": "HELLO FROM THE OTHER SIDE."
  },
  {
    "path": "test/stubs/njk-relative/dir/unique-include-123.njk",
    "content": "HELLO FROM THE OTHER SIDE."
  },
  {
    "path": "test/stubs/object-norender.11ty.cjs",
    "content": "module.exports = {\n  data: {\n    name: \"Ted\"\n  }\n};\n"
  },
  {
    "path": "test/stubs/object.11ty.cjs",
    "content": "module.exports = {\n  data: {\n    name: \"Ted\"\n  },\n  render: function({ name }) {\n    return `<p>${name}</p>`;\n  }\n};\n"
  },
  {
    "path": "test/stubs/oneinstance.11ty.cjs",
    "content": "class Test {\n  constructor() {\n    this.rand = Math.random();\n  }\n\n  get data() {\n    return {\n      name: \"Ted\",\n      rand: this.rand\n    };\n  }\n\n  render({ name }) {\n    return `<p>${name}${this.rand}</p>`;\n  }\n}\n\nmodule.exports = Test;\n"
  },
  {
    "path": "test/stubs/overrides/layout.njk",
    "content": "---\ntemplateEngineOverride: liquid\ntitle: My Title\nlayout: layouts/engineOverrides.njk\n---\n<h2>{{ title | size }}</h2>"
  },
  {
    "path": "test/stubs/overrides/layoutfalse.njk",
    "content": "---\ntemplateEngineOverride: false\ntitle: My Title\nlayout: layouts/engineOverrides.njk\n---\n<h2><%= title %></h2>"
  },
  {
    "path": "test/stubs/overrides/page-templatesyntax.md",
    "content": "---\ntemplateEngineOverride: njk,md\n---\n\n{{ page.templateSyntax }}\n"
  },
  {
    "path": "test/stubs/overrides/test-bypass.md",
    "content": "---\ntemplateEngineOverride: njk\ntitle: My Title\n---\n\n# {{ title }}\n"
  },
  {
    "path": "test/stubs/overrides/test-empty.html",
    "content": "---\ntemplateEngineOverride:\ntitle: My Title\n---\n<h2>{{ title }}</h2>\n"
  },
  {
    "path": "test/stubs/overrides/test-empty.md",
    "content": "---\ntemplateEngineOverride:\ntitle: My Title\n---\n\n# {{ title }}\n"
  },
  {
    "path": "test/stubs/overrides/test-error.njk",
    "content": "---\ntemplateEngineOverride: liquid,njk\ntitle: My Title\n---\n# {{ title }}\n"
  },
  {
    "path": "test/stubs/overrides/test-md.liquid",
    "content": "---\ntemplateEngineOverride: md\n---\n# My Title\n"
  },
  {
    "path": "test/stubs/overrides/test-multiple.md",
    "content": "---\ntemplateEngineOverride: njk,md\ntitle: My Title\n---\n\n# {{ title }}\n"
  },
  {
    "path": "test/stubs/overrides/test-multiple2.njk",
    "content": "---\ntemplateEngineOverride: liquid,md\ntitle: My Title\n---\n# {{ title }}\n"
  },
  {
    "path": "test/stubs/overrides/test-njk.liquid",
    "content": "---\ntemplateEngineOverride: njk\ntitle: My Title\n---\n{{ title }}\n"
  },
  {
    "path": "test/stubs/overrides/test.html",
    "content": "---\ntemplateEngineOverride: njk\ntitle: My Title\n---\n<h2>{{ title }}</h2>\n"
  },
  {
    "path": "test/stubs/overrides/test.liquid",
    "content": "---\ntemplateEngineOverride: njk\ntitle: My Title\n---\n{{ title }}\n"
  },
  {
    "path": "test/stubs/overrides/test.md",
    "content": "---\ntitle: My Title\nlayout: layouts/engineOverridesMd.njk\n---\n\n# {{ title }}\n"
  },
  {
    "path": "test/stubs/page-target-collections/paginateall.njk",
    "content": "---\npagination:\n  data: collections.all\n  size: 1\n  alias: entry\n  filter:\n    - all\n  addAllPagesToCollections: true\n---\nINPUT PATH:{{ entry.inputPath }}"
  },
  {
    "path": "test/stubs/page-target-collections/tagpages.njk",
    "content": "---\npagination:\n  data: collections\n  size: 1\n  alias: tag\n  filter:\n    - all\n---\n\n{{ tag }}"
  },
  {
    "path": "test/stubs/page-target-collections/tagpagesall.njk",
    "content": "---\npagination:\n  data: collections\n  size: 1\n  alias: tag\n  filter:\n    - all\n  addAllPagesToCollections: true\n---\n\n{{ tag }}"
  },
  {
    "path": "test/stubs/paged/cfg-collection-tag-cfg-collection/consumer.njk",
    "content": "{% for item in collections.pagingtag %}{{ item.templateContent | safe }}{% endfor %}\n"
  },
  {
    "path": "test/stubs/paged/cfg-collection-tag-cfg-collection/paged-downstream.njk",
    "content": "---\npagination:\n  data: collections.pagingtag\n  size: 1\n---\n<ol>{% for item in pagination.items %}<li>{{ item.url }}</li>{% endfor %}</ol>"
  },
  {
    "path": "test/stubs/paged/cfg-collection-tag-cfg-collection/paged-main.njk",
    "content": "---\npagination:\n  data: collections.tag1\n  size: 2\n  addAllPagesToCollections: true\ntags:\n  - pagingtag\n---\n<ol>{% for item in pagination.items %}<li>{{ item.url }}</li>{% endfor %}</ol>"
  },
  {
    "path": "test/stubs/paged/cfg-collection-tag-cfg-collection/test1.njk",
    "content": "---\ntitle: Testing 1\ntags:\n  - tag1\n---\n{{ title }}"
  },
  {
    "path": "test/stubs/paged/cfg-collection-tag-cfg-collection/test2.njk",
    "content": "---\ntitle: Testing 2\ntags:\n  - tag1\n---\n{{ title }}"
  },
  {
    "path": "test/stubs/paged/cfg-collection-tag-cfg-collection/test3.njk",
    "content": "---\ntitle: Testing 3\ntags:\n  - tag1\n---\n{{ title }}"
  },
  {
    "path": "test/stubs/paged/collection/consumer.njk",
    "content": "{% for item in collections.pagingtag %}{{ item.templateContent | safe }}{% endfor %}\n"
  },
  {
    "path": "test/stubs/paged/collection/main.njk",
    "content": "---\npagination:\n  data: collections.tag1\n  size: 2\ntags:\n  - pagingtag\n---\n<ol>{% for item in pagination.items %}<li>{{ item.url }}</li>{% endfor %}</ol>\n"
  },
  {
    "path": "test/stubs/paged/collection/test1.njk",
    "content": "---\ntitle: Testing 1\ntags:\n  - tag1\n---\n{{ title }}"
  },
  {
    "path": "test/stubs/paged/collection/test2.njk",
    "content": "---\ntitle: Testing 2\ntags:\n  - tag1\n---\n{{ title }}"
  },
  {
    "path": "test/stubs/paged/collection/test3.njk",
    "content": "---\ntitle: Testing 3\ntags:\n  - tag1\n---\n{{ title }}"
  },
  {
    "path": "test/stubs/paged/collection-apply-to-all/consumer.njk",
    "content": "{% for item in collections.pagingtag %}{{ item.templateContent | safe }}{% endfor %}\n"
  },
  {
    "path": "test/stubs/paged/collection-apply-to-all/main.njk",
    "content": "---\npagination:\n  data: collections.tag1\n  size: 2\n  addAllPagesToCollections: true\ntags:\n  - pagingtag\n---\n<ol>{% for item in pagination.items %}<li>{{ item.url }}</li>{% endfor %}</ol>"
  },
  {
    "path": "test/stubs/paged/collection-apply-to-all/test1.njk",
    "content": "---\ntitle: Testing 1\ntags:\n  - tag1\n---\n{{ title }}"
  },
  {
    "path": "test/stubs/paged/collection-apply-to-all/test2.njk",
    "content": "---\ntitle: Testing 2\ntags:\n  - tag1\n---\n{{ title }}"
  },
  {
    "path": "test/stubs/paged/collection-apply-to-all/test3.njk",
    "content": "---\ntitle: Testing 3\ntags:\n  - tag1\n---\n{{ title }}"
  },
  {
    "path": "test/stubs/paged/notpaged.njk",
    "content": "---\ntestdata:\n  sub:\n   - item1\n   - item2\n   - item3\n   - item4\n   - item5\n   - item6\n   - item7\n   - item8\n---\n<ol>{% for item in pagination.items %}<li>{{ item }}</li>{% endfor %}</ol>\n"
  },
  {
    "path": "test/stubs/paged/paged-before-and-reverse.njk",
    "content": "---js\n{\n  pagination: {\n    data: \"items\",\n    size: 1,\n    reverse: true,\n    before: function(data) {\n      return data.slice(0, 2);\n    }\n  },\n  items: [\"item1\", \"item2\", \"item3\", \"item4\", \"item5\", \"item6\"]\n}\n---\n<ol>{% for item in pagination.items %}<li>{{ item }}</li>{% endfor %}</ol>"
  },
  {
    "path": "test/stubs/paged/paged-before-filter.njk",
    "content": "---js\n{\n  pagination: {\n    data: \"items\",\n    size: 1,\n    before: function(data) {\n      return data.slice(0, 2).reverse();\n    },\n    alias: \"myalias\"\n  },\n  items: [\"item1\", \"item2\", \"item3\", \"item4\", \"item5\", \"item6\"]\n}\n---\n<ol>{% for item in pagination.items %}<li>{{ item }}</li>{% endfor %}</ol>"
  },
  {
    "path": "test/stubs/paged/paged-before-metadata.njk",
    "content": "---js\n{\n  keyword: \"item3\",\n  pagination: {\n    data: \"items\",\n    size: 1,\n    before: function(data, metadata) {\n      return data.filter(el => el === metadata.keyword);\n    }\n  },\n  items: [\"item1\", \"item2\", \"item3\", \"item4\", \"item5\", \"item6\"]\n}\n---\n<ol>{% for item in pagination.items %}<li>{{ item }}</li>{% endfor %}</ol>\n"
  },
  {
    "path": "test/stubs/paged/paged-before.njk",
    "content": "---js\n{\n  pagination: {\n    data: \"items\",\n    size: 1,\n    before: function(data) {\n      return data.reverse();\n    },\n    alias: 'myalias'\n  },\n  items: [\"item1\", \"item2\", \"item3\", \"item4\", \"item5\", \"item6\"]\n}\n---\n<ol>{% for item in pagination.items %}<li>{{ item }}</li>{% endfor %}</ol>"
  },
  {
    "path": "test/stubs/paged/paged-empty-pageonemptydata.njk",
    "content": "---\npagination:\n  data: items\n  size: 1\n  generatePageOnEmptyData: true\nitems: []\n---\n<ol>{% for item in pagination.items %}<li>{{ item }}</li>{% endfor %}</ol>\n"
  },
  {
    "path": "test/stubs/paged/paged-empty.njk",
    "content": "---\npagination:\n  data: items\n  size: 1\nitems: []\n---\n<ol>{% for item in pagination.items %}<li>{{ item }}</li>{% endfor %}</ol>\n"
  },
  {
    "path": "test/stubs/paged/paged.json",
    "content": "{\n  \"items\": {\n    \"sub\": [\n      \"item1\",\n      \"item2\",\n      \"item3\",\n      \"item4\",\n      \"item5\",\n      \"item6\",\n      \"item7\",\n      \"item8\"\n    ]\n  }\n}\n"
  },
  {
    "path": "test/stubs/paged/paged.njk",
    "content": "---\npagination:\n  data: items.sub\n  size: 5\n---\n<ol>{% for item in pagination.items %}<li>{{ item }}</li>{% endfor %}</ol>\n"
  },
  {
    "path": "test/stubs/paged/pagedalias.njk",
    "content": "---\npagination:\n  data: items\n  size: 1\n  alias: font.test\nitems:\n  - item1\n  - item2\npermalink: pagedalias/{{ font.test }}/index.html\n---\n{{ font.test }}\n"
  },
  {
    "path": "test/stubs/paged/pagedaliassize2.njk",
    "content": "---\npagination:\n  data: items\n  size: 2\n  alias: font.test\nitems:\n  - item1\n  - item2\n  - item3\n  - item4\npermalink: pagedalias/{{ font.test[0] }}/index.html\n---\n{{ font.test[0] }}\n"
  },
  {
    "path": "test/stubs/paged/pagedinlinedata-reverse.njk",
    "content": "---\npagination:\n  data: testdata\n  size: 4\n  reverse: true\ntestdata:\n - item1\n - item2\n - item3\n - item4\n - item5\n - item6\n - item7\n - item8\n---\n<ol>{% for item in pagination.items %}<li>{{ item }}</li>{% endfor %}</ol>"
  },
  {
    "path": "test/stubs/paged/pagedinlinedata.njk",
    "content": "---\npagination:\n  data: testdata\n  size: 4\ntestdata:\n - item1\n - item2\n - item3\n - item4\n - item5\n - item6\n - item7\n - item8\n---\n<ol>{% for item in pagination.items %}<li>{{ item }}</li>{% endfor %}</ol>\n"
  },
  {
    "path": "test/stubs/paged/pagedobject.njk",
    "content": "---\npagination:\n  data: testdata\n  size: 4\ntestdata:\n  item1: itemvalue1\n  item2: itemvalue2\n  item3: itemvalue3\n  item4: itemvalue4\n  item5: itemvalue5\n  item6: itemvalue6\n  item7: itemvalue7\n  item8: itemvalue8\n  item9: itemvalue9\n---\n<ol>{% for item in pagination.items %}<li>{{ item }}</li>{% endfor %}</ol>\n"
  },
  {
    "path": "test/stubs/paged/pagedobjectfilterarray.njk",
    "content": "---\npagination:\n  data: testdata\n  size: 4\n  filter:\n    - item4\ntestdata:\n  item1: itemvalue1\n  item2: itemvalue2\n  item3: itemvalue3\n  item4: itemvalue4\n  item5: itemvalue5\n  item6: itemvalue6\n  item7: itemvalue7\n  item8: itemvalue8\n  item9: itemvalue9\n---\n<ol>{% for item in pagination.items %}<li>{{ item }}</li>{% endfor %}</ol>\n"
  },
  {
    "path": "test/stubs/paged/pagedobjectfilterstring.njk",
    "content": "---\npagination:\n  data: testdata\n  size: 4\n  filter: item4\ntestdata:\n  item1: itemvalue1\n  item2: itemvalue2\n  item3: itemvalue3\n  item4: itemvalue4\n  item5: itemvalue5\n  item6: itemvalue6\n  item7: itemvalue7\n  item8: itemvalue8\n  item9: itemvalue9\n---\n<ol>{% for item in pagination.items %}<li>{{ item }}</li>{% endfor %}</ol>\n"
  },
  {
    "path": "test/stubs/paged/pagedobjectvalues.njk",
    "content": "---\npagination:\n  data: testdata\n  size: 4\n  resolve: values\ntestdata:\n  item1: itemvalue1\n  item2: itemvalue2\n  item3: itemvalue3\n  item4: itemvalue4\n  item5: itemvalue5\n  item6: itemvalue6\n  item7: itemvalue7\n  item8: itemvalue8\n  item9: itemvalue9\n---\n<ol>{% for item in pagination.items %}<li>{{ item }}</li>{% endfor %}</ol>\n"
  },
  {
    "path": "test/stubs/paged/pagedpermalink.njk",
    "content": "---\npagination:\n  data: items\n  size: 5\nitems:\n  - Slug CANDIDATE\n  - item2\n  - item3\n  - item4\n  - item5\n  - another-slug CandiDate\n  - item7\n  - item8\npermalink: paged/{{ pagination.items[0] | slugify }}/index.html\n---\n<ol>{% for item in pagination.items %}<li>{{ item }}</li>{% endfor %}</ol>\n"
  },
  {
    "path": "test/stubs/paged/pagedpermalinkif.liquid",
    "content": "---\npagination:\n  data: items\n  size: 2\nitems:\n  - item1\n  - item2\n  - item3\n  - item4\npermalink: paged/{% if pagination.pageNumber > 0 %}page-{{ pagination.pageNumber }}/{% endif %}index.html\n---\n<ol>{% for item in pagination.items %}<li>{{ item }}</li>{% endfor %}</ol>\n"
  },
  {
    "path": "test/stubs/paged/pagedpermalinkif.njk",
    "content": "---\npagination:\n  data: items\n  size: 2\nitems:\n  - item1\n  - item2\n  - item3\n  - item4\npermalink: paged/{% if pagination.pageNumber > 0 %}page-{{ pagination.pageNumber }}/{% endif %}index.html\n---\n<ol>{% for item in pagination.items %}<li>{{ item }}</li>{% endfor %}</ol>\n"
  },
  {
    "path": "test/stubs/paged/pagedpermalinknumeric.njk",
    "content": "---\npagination:\n  data: items\n  size: 5\nitems:\n  - Slug CANDIDATE\n  - item2\n  - item3\n  - item4\n  - item5\n  - another-slug CandiDate\n  - item7\n  - item8\npermalink: paged/page-{{ pagination.pageNumber }}/index.html\n---\n<ol>{% for item in pagination.items %}<li>{{ item }}</li>{% endfor %}</ol>\n"
  },
  {
    "path": "test/stubs/paged/pagedpermalinknumericoneindexed.njk",
    "content": "---\npagination:\n  data: items\n  size: 5\nitems:\n  - Slug CANDIDATE\n  - item2\n  - item3\n  - item4\n  - item5\n  - another-slug CandiDate\n  - item7\n  - item8\npermalink: paged/page-{{ pagination.pageNumber + 1 }}/index.html\n---\n<ol>{% for item in pagination.items %}<li>{{ item }}</li>{% endfor %}</ol>\n"
  },
  {
    "path": "test/stubs/paged/pagedresolve.njk",
    "content": "---\npagination:\n  data: testdata.sub\n  size: 4\ntestdata:\n  sub:\n   - item1\n   - item2\n   - item3\n   - item4\n   - item5\n   - item6\n   - item7\n   - item8\n---\n<ol>{% for item in pagination.items %}<li>{{ item }}</li>{% endfor %}</ol>\n"
  },
  {
    "path": "test/stubs/paged-global-data-mutable/_data/testdata.cjs",
    "content": "module.exports = [\n  {\n    key1: \"item1\",\n    key2: \"item2\",\n  },\n  {\n    key3: \"item3\",\n    key4: \"item4\",\n  },\n  {\n    key5: \"item5\",\n    key6: \"item6\",\n  },\n];\n"
  },
  {
    "path": "test/stubs/paged-global-data-mutable/paged-differing-data-set.njk",
    "content": "---\npagination:\n  data: testdata\n  size: 1\n  alias: item\n---\n1:{{ item.key1 }}\n2:{{ item.key2 }}\n3:{{ item.key3 }}\n4:{{ item.key4 }}\n5:{{ item.key5 }}\n6:{{ item.key6 }}"
  },
  {
    "path": "test/stubs/pagedate.liquid",
    "content": "{{ page.date }}"
  },
  {
    "path": "test/stubs/pagedate.njk",
    "content": "{{ page.date }}"
  },
  {
    "path": "test/stubs/pagedateutc.njk",
    "content": "{{ page.date.toUTCString() }}"
  },
  {
    "path": "test/stubs/pagination-eleventycomputed-permalink.liquid",
    "content": "---\nvenues:\n  - first\n  - second\n  - third\npagination:\n  data: venues\n  size: 1\n  alias: venue\n  addAllPagesToCollections: true\neleventyComputed:\n  permalink: \"venues/{{ venue }}/\"\n---\n"
  },
  {
    "path": "test/stubs/pagination-eleventycomputed-title.liquid",
    "content": "---\nvenues:\n  - id: 1\n    name: first\n  - id: 2\n    name: second\n  - id: 3\n    name: third\nrandommetadata: \"woopers\"\npagination:\n  data: venues\n  size: 1\n  alias: venue\neleventyComputed:\n  title: \"website - {{ venue.name }}\"\n---\n\n{{ venue.name }}\n\n{{ randommetadata }}\n"
  },
  {
    "path": "test/stubs/pagination-templatecontent/index.njk",
    "content": "---\npagination:\n  data: collections.post\n  size: 5\n  alias: posts\n---\n{%- for post in posts -%}\n{{ post.templateContent | safe }}\n{%- endfor -%}"
  },
  {
    "path": "test/stubs/pagination-templatecontent/post-1.md",
    "content": "---\ntags:\n  - post\n---\n\n# Post 1\n"
  },
  {
    "path": "test/stubs/pagination-templatecontent/post-2.md",
    "content": "---\ntags:\n  - post\n---\n\n# Post 2\n"
  },
  {
    "path": "test/stubs/permalink-build/permalink-build.md",
    "content": "---\npermalink:\n  build: /url/\n---\n\nThis should be the same as `permalink: /url/`\n"
  },
  {
    "path": "test/stubs/permalink-conflicts/test1.md",
    "content": "---\ntitle: Test Title\ntags:\n  - post\n  - dog\npermalink: /permalink-conflicts/\n---\n\n# Test 1\n"
  },
  {
    "path": "test/stubs/permalink-conflicts/test2.md",
    "content": "---\ntitle: Test Title\ntags:\n  - post\n  - dog\npermalink: /permalink-conflicts/\n---\n\n# Test 2\n"
  },
  {
    "path": "test/stubs/permalink-conflicts/test3.md",
    "content": "---\ntitle: Test Title\ntags:\n  - post\n  - dog\npermalink: permalink-conflicts/\n---\n\n# Test 3\n"
  },
  {
    "path": "test/stubs/permalink-conflicts-false/test1.md",
    "content": "---\ntitle: Test Title\ntags:\n  - post\n  - dog\npermalink: false\n---\n\n# Test 1\n"
  },
  {
    "path": "test/stubs/permalink-conflicts-false/test2.md",
    "content": "---\ntitle: Test Title\ntags:\n  - post\n  - dog\npermalink: false\n---\n\n# Test 2\n"
  },
  {
    "path": "test/stubs/permalink-data-layout/test.json",
    "content": "{\n  \"layout\": \"permalink-data-layout.njk\"\n}"
  },
  {
    "path": "test/stubs/permalink-data-layout/test.njk",
    "content": "Test 1:{{ page.fileSlug }}"
  },
  {
    "path": "test/stubs/permalink-empty-object/empty-object.md",
    "content": "---\npermalink: {}\n---\n\nThis should be the same as if permalink was not set at all.\n"
  },
  {
    "path": "test/stubs/permalink-false/test.md",
    "content": "---\npermalink: false\n---\n\nThis shouldn’t write\n"
  },
  {
    "path": "test/stubs/permalink-false-computed/test.md",
    "content": "---\neleventyComputed:\n  permalink: false\n---\n\nThis shouldn’t write\n"
  },
  {
    "path": "test/stubs/permalink-in-layout-fileslug.liquid",
    "content": "---\nlayout: permalink-in-layout/layout-fileslug.liquid\n---\nCurrent url: {{ permalink }}"
  },
  {
    "path": "test/stubs/permalink-in-layout.liquid",
    "content": "---\nlayout: permalink-in-layout/layout.liquid\n---\nCurrent url: {{ permalink }}"
  },
  {
    "path": "test/stubs/permalink-markdown-override.md",
    "content": "---\ntitle: My Title\npermalink: /news/my-test-file/index.html\ntemplateEngineOverride: html,md\n---\n\n# <%= title %>\n"
  },
  {
    "path": "test/stubs/permalink-markdown-var.md",
    "content": "---\ntitle: My Title\npermalink: /news/{{ title | slugify }}/index.html\n---\n\n# <%= title %>\n"
  },
  {
    "path": "test/stubs/permalink-markdown.md",
    "content": "---\ntitle: My Title\npermalink: /news/my-test-file/index.html\n---\n\n# <%= title %>\n"
  },
  {
    "path": "test/stubs/permalink-nobuild/permalink-nobuild.md",
    "content": "---\npermalink:\n  someotherkey: /url/\n---\n\nThis shouldn’t write to the file system or be rendered by a template engine.\n"
  },
  {
    "path": "test/stubs/permalink-true/permalink-true.md",
    "content": "---\npermalink: true\n---\n\nThis should throw an error.\n"
  },
  {
    "path": "test/stubs/permalinkdata-jsfn.njk",
    "content": "---js\n{\n\ttitle: \"slug\",\n\tpermalink: \"subdir/{{title}}/\"\n}\n---\nSlugged.\n"
  },
  {
    "path": "test/stubs/permalinkdata-jspermalinkfn.njk",
    "content": "---js\n{\n\ttitle: \"slug\",\n\tpermalink: function(data) {\n\t\treturn `subdir/${data.title}/`;\n\t}\n}\n---\nSlugged.\n"
  },
  {
    "path": "test/stubs/permalinkdata.njk",
    "content": "---\ntitle: Slug CANDIDATE\npermalink: subdir/{{ title | slugify }}/index.html\n---\nSlugged.\n"
  },
  {
    "path": "test/stubs/permalinkdate.liquid",
    "content": "---\ntitle: Date Permalink\ndate: \"2016-01-01T06:00-06:00\"\npermalink: \"/{{ page.date | date: '%Y/%m/%d' }}/index.html\"\n---\nDate Permalinks"
  },
  {
    "path": "test/stubs/permalinked.liquid",
    "content": "---\npermalink: permalinksubfolder/index.html\n---\nCurrent url: {{ permalink }}"
  },
  {
    "path": "test/stubs/posts/post1.njk",
    "content": "Post1\n"
  },
  {
    "path": "test/stubs/posts/posts.json",
    "content": "{\n  \"layout\": \"mylocallayout.njk\"\n}\n"
  },
  {
    "path": "test/stubs/posts/posts.njk",
    "content": "Posts\n"
  },
  {
    "path": "test/stubs/prematureTemplateContent/test.11ty.cjs",
    "content": "module.exports = function(data) {\n  return data.collections.all[0].templateContent;\n};\n"
  },
  {
    "path": "test/stubs/prematureTemplateContent/test.liquid",
    "content": "{{ collections.all[0].templateContent }}"
  },
  {
    "path": "test/stubs/prematureTemplateContent/test.md",
    "content": "{{ sample.templateContent }}\n"
  },
  {
    "path": "test/stubs/prematureTemplateContent/test.njk",
    "content": "{{ sample.templateContent }}"
  },
  {
    "path": "test/stubs/promise.11ty.cjs",
    "content": "module.exports = new Promise((resolve, reject) => {\n  setTimeout(function() {\n    resolve(\"<p>Zach</p>\");\n  }, 100);\n});\n"
  },
  {
    "path": "test/stubs/public/test.css",
    "content": ""
  },
  {
    "path": "test/stubs/relative-liquid/dir/included.liquid",
    "content": "TIME IS RELATIVE."
  },
  {
    "path": "test/stubs/reuse-permalink/reuse-permalink.json",
    "content": "{\n  \"permalink\": \"/{{ page.date | date: '%Y/%m/%d' }}/index.html\"\n}\n"
  },
  {
    "path": "test/stubs/reuse-permalink/test1.liquid",
    "content": "---\ntitle: Test Title\ndate: \"2016-01-01T06:00-06:00\"\n---\n\n# Test 1\n"
  },
  {
    "path": "test/stubs/script-frontmatter/test-default.njk",
    "content": "---\nimport {noopSync} from \"@zachleat/noop\";\nconst myString = \"Hi\";\n\n// export a function\nfunction myFunction() { return \"Bye\" }\n---\n<div>{{ noopSync(myString) }}</div><div>{{ myFunction() }}</div>"
  },
  {
    "path": "test/stubs/script-frontmatter/test-js.njk",
    "content": "---javascript\n{\n\tmyFunction: function() {\n\t\treturn \"HELLO!\";\n\t}\n}\n---\n<div>{{ myFunction() }}</div>"
  },
  {
    "path": "test/stubs/script-frontmatter/test.njk",
    "content": "---js\nimport {noopSync} from \"@zachleat/noop\";\nconst myString = \"Hi\";\n\n// export a function\nfunction myFunction() { return \"Bye\" }\n---\n<div>{{ noopSync(myString) }}</div><div>{{ myFunction() }}</div>"
  },
  {
    "path": "test/stubs/string.11ty.cjs",
    "content": "module.exports = \"<p>Zach</p>\";\n"
  },
  {
    "path": "test/stubs/string.11ty.custom",
    "content": "export default \"<p>Zach</p>\";\n"
  },
  {
    "path": "test/stubs/string.11ty.possum",
    "content": "export default \"<p>Possum</p>\";\n"
  },
  {
    "path": "test/stubs/stubs-1541/_includes/render-source.liquid",
    "content": "{{ page.url }} via {{ eleventy.env.source }} collections.all size: {{ collections.all | size }}"
  },
  {
    "path": "test/stubs/stubs-computed-permalink/eleventycomputed-nested-object.11ty.cjs",
    "content": "module.exports.data = {\n  lang: \"en\",\n  eleventyComputed: {\n    permalink: function (data) {\n      // console.log(\">>>>\", { data });\n      return {\n        build: `/i18n/${data.lang}/`,\n      };\n    },\n  },\n};\n"
  },
  {
    "path": "test/stubs/stubs-computed-permalink/eleventycomputed-object-replace.11ty.cjs",
    "content": "module.exports.data = {\n  lang: \"en\",\n  permalink: \"/i18n/{{lang}}/\",\n  eleventyComputed: {\n    permalink: function (data) {\n      return {\n        build: data.permalink.replace(\"{{lang}}\", \"en\"),\n      };\n    },\n  },\n};\n"
  },
  {
    "path": "test/stubs/stubs-computed-permalink/eleventycomputed-object.11ty.cjs",
    "content": "module.exports.data = {\n  lang: \"en\",\n  eleventyComputed: {\n    permalink: {\n      build: function (data) {\n        return `/i18n/${data.lang}/`;\n      },\n    },\n  },\n};\n"
  },
  {
    "path": "test/stubs/stubs-virtual-conflict/virtual.md",
    "content": "# This is on the file system"
  },
  {
    "path": "test/stubs/subdir/img/.gitkeep",
    "content": ""
  },
  {
    "path": "test/stubs/subdir/index.html",
    "content": ""
  },
  {
    "path": "test/stubs/subfolder/index.html",
    "content": ""
  },
  {
    "path": "test/stubs/subfolder/subfolder/subfolder.liquid",
    "content": ""
  },
  {
    "path": "test/stubs/subfolder/subfolder.liquid",
    "content": "subfolder"
  },
  {
    "path": "test/stubs/tagged-pagination-multiples/test.njk",
    "content": "---\ntitle: Paged Test\ntags:\n  - haha\npagination:\n  data: collections.userCollection\n  size: 1\n  alias: item\n  addAllPagesToCollections: true\n---\n\n{{ title }}"
  },
  {
    "path": "test/stubs/tagged-pagination-multiples-layout/test.njk",
    "content": "---\ntitle: Paged Test\nlayout: layouts/div-wrapper-layout.njk\ntestdata:\n  - one\n  - two\n  - three\npagination:\n  data: testdata\n  size: 1\n  alias: item\n  addAllPagesToCollections: true\n---\n{{ item }}"
  },
  {
    "path": "test/stubs/template-passthrough/.htaccess",
    "content": ""
  },
  {
    "path": "test/stubs/template-passthrough/static/nested/test-nested.css",
    "content": ""
  },
  {
    "path": "test/stubs/template-passthrough/static/test.css",
    "content": ""
  },
  {
    "path": "test/stubs/template-passthrough/static/test.js",
    "content": ""
  },
  {
    "path": "test/stubs/template-passthrough2/.htaccess",
    "content": ""
  },
  {
    "path": "test/stubs/template-passthrough2/static/nested/test-nested.css",
    "content": ""
  },
  {
    "path": "test/stubs/template-passthrough2/static/test.css",
    "content": ""
  },
  {
    "path": "test/stubs/template-passthrough2/static/test.js",
    "content": ""
  },
  {
    "path": "test/stubs/template.liquid",
    "content": ""
  },
  {
    "path": "test/stubs/templateFrontMatter.liquid",
    "content": "---\nkey1: value1\nkey2: value2\nkey3: value3\n---\nc:{{ key1 }}:{{ key2 }}:{{ key3 }}"
  },
  {
    "path": "test/stubs/templateFrontMatterJs.njk",
    "content": "---js\n{\n  key1: \"value1\",\n  key2: function(value) {\n    return value.toUpperCase();\n  },\n  key3: \"value3\"\n}\n---\nc:{{ key1 }}:{{ key2(\"value2\") }}:{{ key3 }}\n"
  },
  {
    "path": "test/stubs/templateFrontMatterJson.liquid",
    "content": "---json\n{\n  \"key1\": \"value1\",\n  \"key2\": \"value2\",\n  \"key3\": \"value3\"\n}\n---\nc:{{ key1 }}:{{ key2 }}:{{ key3 }}"
  },
  {
    "path": "test/stubs/templateLayoutCacheDuplicates/_includes/layout.njk",
    "content": "Hello A"
  },
  {
    "path": "test/stubs/templateLayoutCacheDuplicates-b/_includes/layout.njk",
    "content": "Hello B"
  },
  {
    "path": "test/stubs/templateMapCollection/paged-cfg-permalink.md",
    "content": "---\ntitle: Paged Test\npagination:\n  data: collections.userCollection\n  size: 1\n  alias: item\npermalink: /{{ item.data.title | slugify }}/hello/\n---\n\n# {{ title }}\n"
  },
  {
    "path": "test/stubs/templateMapCollection/paged-cfg-tagged-apply-to-all.md",
    "content": "---\ntitle: Paged Test\ntags:\n  - haha\npagination:\n  data: collections.userCollection\n  size: 1\n  alias: item\n  addAllPagesToCollections: true\n---\n\n# {{ title }}\n"
  },
  {
    "path": "test/stubs/templateMapCollection/paged-cfg-tagged-permalink-apply-to-all.md",
    "content": "---\ntitle: Paged Test\ntags:\n  - haha\npagination:\n  data: collections.userCollection\n  size: 1\n  alias: item\n  addAllPagesToCollections: true\npermalink: /{{ item.data.title | slugify }}/goodbye/\n---\n\n# {{ title }}\n"
  },
  {
    "path": "test/stubs/templateMapCollection/paged-cfg-tagged-permalink.md",
    "content": "---\ntitle: Paged Test\ntags:\n  - haha\npagination:\n  data: collections.userCollection\n  size: 1\n  alias: item\npermalink: /{{ item.data.title | slugify }}/goodbye/\n---\n\n# {{ title }}\n"
  },
  {
    "path": "test/stubs/templateMapCollection/paged-cfg-tagged.md",
    "content": "---\ntitle: Paged Test\ntags:\n  - haha\npagination:\n  data: collections.userCollection\n  size: 1\n  alias: item\n---\n\n# {{ title }}\n"
  },
  {
    "path": "test/stubs/templateMapCollection/paged-cfg.md",
    "content": "---\ntitle: Paged Test\npagination:\n  data: collections.userCollection\n  size: 1\n---\n\n# {{ title }}\n"
  },
  {
    "path": "test/stubs/templateMapCollection/paged-tag-dogs-templateContent-alias.md",
    "content": "---\ntitle: Paged Test\npagination:\n  data: collections.dog\n  size: 2\n  alias: dogs\n---\n\nBefore\n{% for dog in dogs %}\n{{ dog.templateContent }}\n{% endfor %}\nAfter\n"
  },
  {
    "path": "test/stubs/templateMapCollection/paged-tag-dogs-templateContent.md",
    "content": "---\ntitle: Paged Test\npagination:\n  data: collections.dog\n  size: 1\n---\n\nBefore\n{% for dog in pagination.items %}\n{{ dog.templateContent }}\n{% endfor %}\nAfter\n"
  },
  {
    "path": "test/stubs/templateMapCollection/paged-tag.md",
    "content": "---\ntitle: Paged Test\npagination:\n  data: collections.dog\n  size: 1\n---\n\n# {{ title }}\n"
  },
  {
    "path": "test/stubs/templateMapCollection/templateContent.md",
    "content": "---\ntags: circle\n---\n\n# Test\n\n{{ collections.circle[0].templateContent }}\n"
  },
  {
    "path": "test/stubs/templateMapCollection/test1.md",
    "content": "---\ntitle: Test Title\ntags:\n  - post\n  - dog\n---\n\n# Test 1\n"
  },
  {
    "path": "test/stubs/templateMapCollection/test2.md",
    "content": "---\ntags: cat\n---\n\n# Test 2\n"
  },
  {
    "path": "test/stubs/templateMapCollection/test3.md",
    "content": "---\ntags: circle\n---\n\n# {{ collections.circle.length }}, {{ collections.circle[0].url }}\n"
  },
  {
    "path": "test/stubs/templateMapCollection/test4.md",
    "content": "---\ntitle: Test Title 4\ntags:\n  - post\n  - dog\n---\n\n# Test 4\n"
  },
  {
    "path": "test/stubs/templateMapCollection/test5.md",
    "content": "---\ntitle: Test Title 5\n---\n\n# Test 5\n"
  },
  {
    "path": "test/stubs/templateMapCollection/testWithLayout.md",
    "content": "---\nlayout: layouts/templateMapCollection.njk\n---\n\n{{ upstream }}\n"
  },
  {
    "path": "test/stubs/templateTwoLayouts.liquid",
    "content": "---\nlayout: layout-a\nkey1: value1\ntitle: 'Font Aliasing, or How to Rename a Font in CSS'\npermalink: /rename-font/\npostRank: 4\ndaysPosted: 152\nyearsPosted: 0.4\n---\n\n<p>{{ upstream }}</p>"
  },
  {
    "path": "test/stubs/templateWithLayout.liquid",
    "content": "---\nlayout: layoutLiquid.liquid\nkeymain: valuemain\ntitle: 'Font Aliasing, or How to Rename a Font in CSS'\npermalink: /rename-font/\n---\n\n<p>Hello.</p>"
  },
  {
    "path": "test/stubs/templateWithLayoutContent.liquid",
    "content": "---\nlayout: defaultLayoutLayoutContent\nkeymain: valuemain\ntitle: 'Font Aliasing, or How to Rename a Font in CSS'\npermalink: /rename-font/\n---\n<p>Hello.</p>\n"
  },
  {
    "path": "test/stubs/templateWithLayoutKey.liquid",
    "content": "---\nlayout: defaultLayout\nkeymain: valuemain\ntitle: 'Font Aliasing, or How to Rename a Font in CSS'\npermalink: /rename-font/\n---\n\n<p>Hello.</p>"
  },
  {
    "path": "test/stubs/templatetest-frontmatter/multiple.njk",
    "content": "---\ntags:\n  - multi-tag\n  - multi-tag-2\n---\n{% if tags.includes(\"multi-tag-2\") %}Has multi-tag-2{% endif %}"
  },
  {
    "path": "test/stubs/templatetest-frontmatter/single.njk",
    "content": "---\ntags: single-tag\n---\n{% if tags.includes(\"single-tag\") %}Has single-tag{% endif %}"
  },
  {
    "path": "test/stubs/test-override-js-markdown.11ty.cjs",
    "content": "class Test {\n  data() {\n    return {\n      name: \"markdown\",\n      templateEngineOverride: \"11ty.js,md\"\n    };\n  }\n\n  render(data) {\n    return `# This is ${data.name}`;\n  }\n}\n\nmodule.exports = Test;\n"
  },
  {
    "path": "test/stubs/testing.html",
    "content": "<html>\n<body>\n\tThis is an html template.\n</body>\n</html>"
  },
  {
    "path": "test/stubs/transform-pages/template.njk",
    "content": "---\npagination:\n  data: testdata\n  size: 4\ntestdata:\n - item1\n - item2\n - item3\n - item4\n - item5\n - item6\n - item7\n - item8\n---\n<ol>{% for item in pagination.items %}<li>{{ item }}</li>{% endfor %}</ol>\n"
  },
  {
    "path": "test/stubs/use-collection.11ty.cjs",
    "content": "module.exports = function({ collections }) {\n  return `<ul>${collections.post\n    .map(post => `<li>${post.data.title}</li>`)\n    .join(\"\")}</ul>`;\n};\n"
  },
  {
    "path": "test/stubs/vue-layout.11ty.cjs",
    "content": "const { createSSRApp } = require(\"vue\");\nconst { renderToString } = require(\"@vue/server-renderer\");\n\nmodule.exports = async function (data) {\n  var app = createSSRApp({\n    template: \"<p>Hello {{ data.name }}, this is a Vue template.</p>\",\n    data: function () {\n      return { data };\n    },\n    // components: {\n    //   'test': ComponentA\n    // }\n  });\n\n  let content = await renderToString(app, { title: \"Test\" });\n  return `<!doctype html>\n<title>Test</title>\n${content}`;\n};\n"
  },
  {
    "path": "test/stubs/vue.11ty.cjs",
    "content": "const { createSSRApp } = require(\"vue\");\nconst { renderToString } = require(\"@vue/server-renderer\");\n\nmodule.exports = async function (templateData) {\n  var app = createSSRApp({\n    template: \"<p>Hello {{ data.name }}, this is a Vue template.</p>\",\n    data: function () {\n      return {\n        data: templateData,\n      };\n    },\n    // components: {\n    //   'test': ComponentA\n    // }\n  });\n\n  return renderToString(app);\n};\n"
  },
  {
    "path": "test/stubs/writeTest/test.md",
    "content": "# Header"
  },
  {
    "path": "test/stubs/writeTestJS/sample.cjs",
    "content": "module.exports = \"<p>Zach</p>\";\n"
  },
  {
    "path": "test/stubs/writeTestJS/test.11ty.cjs",
    "content": "module.exports = \"<p>Zach</p>\";\n"
  },
  {
    "path": "test/stubs/writeTestJS-casesensitive/sample.Js",
    "content": "module.exports = \"<p>Zach</p>\";\n"
  },
  {
    "path": "test/stubs/writeTestJS-casesensitive/test.11Ty.js",
    "content": "module.exports = \"<p>Zach</p>\";\n"
  },
  {
    "path": "test/stubs/writeTestJS-passthrough/sample.js",
    "content": "export default \"<p>Zach</p>\";\n"
  },
  {
    "path": "test/stubs/writeTestJS-passthrough/test.11ty.js",
    "content": "export default \"<p>Zach</p>\";\n"
  },
  {
    "path": "test/stubs/writeTestMarkdown/sample.md",
    "content": "module.exports = \"<p>Zach</p>\";\n"
  },
  {
    "path": "test/stubs/writeTestMarkdown/sample2.markdown",
    "content": "module.exports = \"<p>Zach</p>\";\n"
  },
  {
    "path": "test/stubs--to/test.md",
    "content": "# hi\n"
  },
  {
    "path": "test/stubs--to/test2.liquid",
    "content": "---\nhi: hello\n---\n{{ hi }}"
  },
  {
    "path": "test/stubs-1206/page1.njk",
    "content": "---\ntags: tag1\n---\nThis is the first template.{{ page.rawInput }}"
  },
  {
    "path": "test/stubs-1206/page2.njk",
    "content": "This is the second template.{{ collections.tag1[0].rawInput }}"
  },
  {
    "path": "test/stubs-1242/_data/xyz.dottest/test.json",
    "content": "{\r\n  \"abc\": 42\r\n}\r\n"
  },
  {
    "path": "test/stubs-1242/_data/xyz.dottest.json",
    "content": "{\n  \"hi\": \"bye\"\n}\n"
  },
  {
    "path": "test/stubs-1242/empty.md",
    "content": ""
  },
  {
    "path": "test/stubs-1325/test.11ty.js",
    "content": ""
  },
  {
    "path": "test/stubs-1325/test.js",
    "content": ""
  },
  {
    "path": "test/stubs-142/index.njk",
    "content": "---\ndate: git Last Modified\n---\n{{ page.date.getTime() }}"
  },
  {
    "path": "test/stubs-1691/_data/str.txt",
    "content": "Testing"
  },
  {
    "path": "test/stubs-1691/template.11tydata.txt",
    "content": "Template Data File"
  },
  {
    "path": "test/stubs-1691/template.njk",
    "content": ""
  },
  {
    "path": "test/stubs-2145/_includes/layout.njk",
    "content": "---\nLayoutData: 123\n---\nFromLayout{{ content }}"
  },
  {
    "path": "test/stubs-2145/test.njk",
    "content": "{{ layout }}"
  },
  {
    "path": "test/stubs-2167/paginated.njk",
    "content": "---\ndropdown:\n  - a\n  - b\n  - c\n  - d\n  - e\npagination:\n  data: dropdown\n  size: 1\npermalink: false\n---\n"
  },
  {
    "path": "test/stubs-2224/index.njk",
    "content": "---\ndate: git created\n---\n{{ page.date.getTime() }}"
  },
  {
    "path": "test/stubs-2258/_includes/_code.scss",
    "content": "code {\n  padding: 0.25em;\n  line-height: 0;\n}"
  },
  {
    "path": "test/stubs-2258/_includes/layout.njk",
    "content": "/* Banner */\n{{ content | safe }}"
  },
  {
    "path": "test/stubs-2258/eleventy.config.cjs",
    "content": "const path = require(\"path\");\nconst sass = require(\"sass\");\n\nmodule.exports = function (eleventyConfig) {\n  eleventyConfig.addTemplateFormats(\"scss\");\n\n  eleventyConfig.addExtension(\"scss\", {\n    outputFileExtension: \"css\", // optional, default: \"html\"\n\n    compile: function (inputContent, inputPath) {\n      let parsed = path.parse(inputPath);\n      let dirs = [\n        parsed.dir || \".\",\n        // BRITTLE TEST ALERT: DON’T REUSE THIS CODE\n        // it expects the template to be in the project root dir\n        path.join(parsed.dir, this.config.dir.includes),\n      ];\n\n      let result = sass.compileString(inputContent, {\n        loadPaths: dirs,\n      });\n\n      this.addDependencies(inputPath, result.loadedUrls);\n\n      return (data) => {\n        return result.css;\n      };\n    },\n  });\n};\n"
  },
  {
    "path": "test/stubs-2258/style.scss",
    "content": "---\nlayout: layout.njk\n---\n@use \"code.scss\";\n\n/* Comment */"
  },
  {
    "path": "test/stubs-2258-2830-skip-layouts/_includes/layout.njk",
    "content": "/* Banner */\n{{ content | safe }}"
  },
  {
    "path": "test/stubs-2258-2830-skip-layouts/eleventy.config.cjs",
    "content": "const path = require(\"path\");\nconst sass = require(\"sass\");\n\nmodule.exports = function (eleventyConfig) {\n  eleventyConfig.addTemplateFormats(\"scss\");\n\n  eleventyConfig.addExtension(\"scss\", {\n    useLayouts: false,\n\n    outputFileExtension: \"css\", // optional, default: \"html\"\n\n    compile: function (inputContent, inputPath) {\n      let parsed = path.parse(inputPath);\n      let dirs = [\n        parsed.dir || \".\",\n        // BRITTLE TEST ALERT: DON’T REUSE THIS CODE\n        // it expects the template to be in the project root dir\n        path.join(parsed.dir, this.config.dir.includes),\n      ];\n\n      let result = sass.compileString(inputContent, {\n        loadPaths: dirs,\n      });\n\n      return (data) => {\n        return result.css;\n      };\n    },\n  });\n};\n"
  },
  {
    "path": "test/stubs-2258-2830-skip-layouts/style.scss",
    "content": "---\nlayout: layout.njk\n---\ncode {\n  padding: 0.25em;\n  line-height: 0;\n}"
  },
  {
    "path": "test/stubs-2261/_includes/block.njk",
    "content": "{% macro block() %}<div>{{ caller() }}</div>{% endmacro %}"
  },
  {
    "path": "test/stubs-2261/eleventy.config.js",
    "content": "export default function(eleventyConfig) {\n  eleventyConfig.addPairedShortcode(\"sample\", function(content, firstName) {\n      return `${content} ${firstName}`\n  });\n};"
  },
  {
    "path": "test/stubs-2261/index.njk",
    "content": "{% from \"block.njk\" import block with context %}\n{% call block() %}Hello{% sample \"Manuel\" %}Hello{% endsample %}{% endcall %}"
  },
  {
    "path": "test/stubs-2367/_includes/layout.liquid",
    "content": "---\ntext: layout\nurl: \"/mylayout\"\n---\n{% simplelink text url text url text url %}\n{% simplelink text, url, text, url, text, url %}"
  },
  {
    "path": "test/stubs-2367/templateWithLiquidShortcodeMultipleArguments-template2.liquid",
    "content": "---\nlayout: layout.liquid\n---"
  },
  {
    "path": "test/stubs-2367/templateWithLiquidShortcodeMultipleArguments.liquid",
    "content": "---\nlayout: layout.liquid\n---"
  },
  {
    "path": "test/stubs-2602/index.njk",
    "content": "---\npermalink: /deep/\n---\n<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<meta name=\"description\" content=\"\">\n<title></title>\n<style>div { background-image: url(test.jpg); }</style>\n<style>div { background-image: url({{ \"/test.jpg\" | htmlBaseUrl }}); }</style>\n<link rel=\"stylesheet\" href=\"/test.css\">\n<script src=\"/test.js\"></script>\n</head>\n<body>\n<a href=\"/\">Home</a>\n<a href=\"subdir/\">Test</a>\n<a href=\"../subdir/\">Test</a>\n</body>\n</html>"
  },
  {
    "path": "test/stubs-2753/_data/global.js",
    "content": "let count = 0;\nexport default async function () {\n  return ++count;\n};\n"
  },
  {
    "path": "test/stubs-2753/page1.njk",
    "content": "{{ global }}"
  },
  {
    "path": "test/stubs-2753/page2.njk",
    "content": "{{ global }}"
  },
  {
    "path": "test/stubs-2790/page.11ty.cjs",
    "content": "module.exports = function ({ name }) {\n  return `<p>${this.jsfunction(name)}</p>`;\n};\n"
  },
  {
    "path": "test/stubs-2851/content.njk",
    "content": "---\ntags: ['tag with spaces']\n---"
  },
  {
    "path": "test/stubs-2851/paginated.njk",
    "content": "---\npagination:\n  data: \"collections['tag with spaces']\"\n  size: 1\n---\n"
  },
  {
    "path": "test/stubs-3013/html/_data/books.json",
    "content": "[\n    {\n        \"id\": 1,\n        \"name\": \"The Effervescent adventures of Paul Mescal\",\n        \"shortname\": \"paul-mescal\"\n    },\n    {\n        \"id\": 2,\n        \"name\": \"Populace and Power: A user's guide\",\n        \"shortname\": \"populace-and-power\"\n    }\n]"
  },
  {
    "path": "test/stubs-3013/html/_includes/base.html",
    "content": "---\ntitle: Books\n---\n<title>{{ title }}</title>"
  },
  {
    "path": "test/stubs-3013/html/book.html",
    "content": "---\nlayout: base\npagination:\n  data: books\n  size: 1\n  alias: book\npermalink: /{{ book.shortname }}/\neleventyComputed:\n  title: \"{{ book.name }}\"\n---\n{{ title }}"
  },
  {
    "path": "test/stubs-3013/liquid/_data/books.json",
    "content": "[\n    {\n        \"id\": 1,\n        \"name\": \"The Effervescent adventures of Paul Mescal\",\n        \"shortname\": \"paul-mescal\"\n    },\n    {\n        \"id\": 2,\n        \"name\": \"Populace and Power: A user's guide\",\n        \"shortname\": \"populace-and-power\"\n    }\n]"
  },
  {
    "path": "test/stubs-3013/liquid/_includes/base.liquid",
    "content": "---\ntitle: Books\n---\n<title>{{ title }}</title>"
  },
  {
    "path": "test/stubs-3013/liquid/book.liquid",
    "content": "---\nlayout: base\npagination:\n  data: books\n  size: 1\n  alias: book\npermalink: /{{ book.shortname }}/\neleventyComputed:\n  title: \"{{ book.name }}\"\n---\n{{ title }}"
  },
  {
    "path": "test/stubs-3013/md/_data/books.json",
    "content": "[\n    {\n        \"id\": 1,\n        \"name\": \"The Effervescent adventures of Paul Mescal\",\n        \"shortname\": \"paul-mescal\"\n    },\n    {\n        \"id\": 2,\n        \"name\": \"Populace and Power: A user's guide\",\n        \"shortname\": \"populace-and-power\"\n    }\n]"
  },
  {
    "path": "test/stubs-3013/md/_includes/base.md",
    "content": "---\ntitle: Books\n---\n\n<title>{{ title }}</title>\n"
  },
  {
    "path": "test/stubs-3013/md/book.md",
    "content": "---\nlayout: base\npagination:\n  data: books\n  size: 1\n  alias: book\npermalink: /{{ book.shortname }}/\neleventyComputed:\n  title: \"{{ book.name }}\"\n---\n\n{{ title }}\n"
  },
  {
    "path": "test/stubs-3013/njk/_data/books.json",
    "content": "[\n    {\n        \"id\": 1,\n        \"name\": \"The Effervescent adventures of Paul Mescal\",\n        \"shortname\": \"paul-mescal\"\n    },\n    {\n        \"id\": 2,\n        \"name\": \"Populace and Power: A user's guide\",\n        \"shortname\": \"populace-and-power\"\n    }\n]"
  },
  {
    "path": "test/stubs-3013/njk/_includes/base.njk",
    "content": "---\ntitle: Books\n---\n<title>{{ title }}</title>"
  },
  {
    "path": "test/stubs-3013/njk/book.njk",
    "content": "---\nlayout: base\npagination:\n  data: books\n  size: 1\n  alias: book\npermalink: /{{ book.shortname }}/\neleventyComputed:\n  title: \"{{ book.name }}\"\n---\n{{ title }}"
  },
  {
    "path": "test/stubs-3285/src/scripts/hello-world.js",
    "content": "export default function() {\n  console.log('hello world');\n};\n"
  },
  {
    "path": "test/stubs-3356/.gitkeep",
    "content": ""
  },
  {
    "path": "test/stubs-337/data/xyz.json",
    "content": "{\n  \"hi\": \"bye\"\n}"
  },
  {
    "path": "test/stubs-337/src/empty.md",
    "content": ""
  },
  {
    "path": "test/stubs-3807/Issue3807test.js",
    "content": "import test from \"ava\";\nimport fs from \"node:fs\";\nimport Eleventy from \"../../src/Eleventy.js\";\nimport { withResolvers } from \"../../src/Util/PromiseUtil.js\";\n\n// This tests Eleventy Watch and the file system!\n\ntest(\"#3807 Nunjucks cacheable should be reused when Nunjucks is the preprocessor language\", async (t) => {\n  let runs = [\n    {\n      ...withResolvers(),\n      input: `<html>first{% block main %}{{ content | safe }}{% endblock %}</html>`,\n      expected: `<html>firstHome<p>Index</p></html>`,\n    },\n    {\n      ...withResolvers(),\n      input: `<html>second{% block main %}{{ content | safe }}{% endblock %}</html>`,\n      expected: `<html>secondHome<p>Index</p></html>`,\n    },\n    {\n      ...withResolvers(),\n      input: `<html>third{% block main %}{{ content | safe }}{% endblock %}</html>`,\n      expected: `<html>thirdHome<p>Index</p></html>`,\n    }\n  ];\n\n  t.plan(runs.length + 1);\n\n  // Restore original content\n  const ORIGINAL_CONTENT = `<html>{% block main %}{{ content | safe }}{% endblock %}</html>`;\n  fs.writeFileSync(\"test/stubs-3807/_layouts/base.html\", ORIGINAL_CONTENT, \"utf8\");\n\n  let index = 0;\n  let elev = new Eleventy(\"test/stubs-3807/\", \"test/stubs-3807/_site\", {\n    configPath: \"test/stubs-3807/eleventy.config.js\",\n    config(eleventyConfig) {\n      eleventyConfig.on(\"eleventy.afterwatch\", () => {\n        let {resolve} = runs[index];\n        index++;\n        resolve();\n      });\n    }\n  });\n\n  elev.disableLogger();\n  await elev.init();\n  await elev.watch();\n\n  // Control\n  let content = fs.readFileSync(\"test/stubs-3807/_site/index.html\", \"utf8\");\n  t.is(content, `<html>Home<p>Index</p></html>`);\n\n  // Stop after all runs are complete\n  Promise.all(runs.map(entry => entry.promise)).then(async () => {\n    await elev.stopWatch();\n  });\n\n  for(let run of runs) {\n    fs.writeFileSync(\"test/stubs-3807/_layouts/base.html\", run.input, \"utf8\");\n    await run.promise;\n\n    let content = fs.readFileSync(\"test/stubs-3807/_site/index.html\", \"utf8\");\n    t.is(content, run.expected);\n  }\n\n  fs.writeFileSync(\"test/stubs-3807/_layouts/base.html\", ORIGINAL_CONTENT, \"utf8\");\n  fs.rmSync(\"test/stubs-3807/_site\", { recursive: true });\n});"
  },
  {
    "path": "test/stubs-3807/_layouts/base.html",
    "content": "<html>{% block main %}{{ content | safe }}{% endblock %}</html>"
  },
  {
    "path": "test/stubs-3807/_layouts/home.html",
    "content": "{% extends \"test/stubs-3807/_layouts/base.html\" %}{% block main %}Home{{ content | trim | safe }}{% endblock %}"
  },
  {
    "path": "test/stubs-3807/eleventy.config.js",
    "content": "export default function(eleventyConfig) {\n\televentyConfig.setLayoutsDirectory(\"_layouts\");\n}\nexport const config = {\n\tmarkdownTemplateEngine: \"njk\",\n\thtmlTemplateEngine: \"njk\",\n}"
  },
  {
    "path": "test/stubs-3807/index.md",
    "content": "---\nlayout: home.html\n---\nIndex"
  },
  {
    "path": "test/stubs-3810/_includes/promo.njk",
    "content": "<h1>Sign up for our {{ promoType }}!</h1>"
  },
  {
    "path": "test/stubs-3810/eleventy.config.js",
    "content": "import fs from 'fs';\nimport { RenderPlugin } from '../../src/Eleventy.js';\nconst { RenderManager } = RenderPlugin;\n\nexport default function(eleventyConfig) {\n\tconst rm = new RenderManager();\n\n\televentyConfig.on('eleventy.config', cfg => {\n\t\trm.templateConfig = cfg;\n\t});\n\n\televentyConfig.addAsyncShortcode('promo', async function (promoType) {\n\t\tlet content = fs.readFileSync('./test/stubs-3810/_includes/promo.njk').toString();\n\n\t\tconst fn = await rm.compile(content, 'njk');\n\n\t\treturn fn({ promoType });\n\t});\n}\n\nexport const config = {\n\tmarkdownTemplateEngine: \"njk\",\n}"
  },
  {
    "path": "test/stubs-3810/index.md",
    "content": "{% promo \"newsletter\" %}"
  },
  {
    "path": "test/stubs-403/.eleventyignore",
    "content": "./_includes/**"
  },
  {
    "path": "test/stubs-403/_includes/include.liquid",
    "content": ""
  },
  {
    "path": "test/stubs-403/template.liquid",
    "content": ""
  },
  {
    "path": "test/stubs-408-sass/_code.scss",
    "content": "code {\n  padding: 0.25em;\n  line-height: 0;\n}"
  },
  {
    "path": "test/stubs-408-sass/style.scss",
    "content": "---\nlayout: layout.njk\n---\n@use \"code.scss\";\n\n/* Comment */"
  },
  {
    "path": "test/stubs-413/date-frontmatter.md",
    "content": "---\nsubtitle: New doc page\ndate: 2019-03-13 20:18:42 +0000\ntags:\n  - docs\n---\n"
  },
  {
    "path": "test/stubs-434/_includes/macros-filter.njk",
    "content": "{% macro label(text) %}<label>{{ text }}:{{ \"all\" | getCollection | length }}</label>{% endmacro %}"
  },
  {
    "path": "test/stubs-434/_includes/macros.njk",
    "content": "{% macro label(text) %}<label>{{ text }}:{{ collections | length }}:{{ collections.all[0].data.page.url }}</label>{% endmacro %}"
  },
  {
    "path": "test/stubs-475/_includes/layout.njk",
    "content": "<html><body>{{ content | safe }}</body></html>"
  },
  {
    "path": "test/stubs-475/transform-layout/transform-layout.njk",
    "content": "---\nlayout: layout.njk\n---\nThis is content."
  },
  {
    "path": "test/stubs-630/_data/globalData0.cjs",
    "content": "module.exports = {\n  datakey1: \"datavalue0\"\n};\n"
  },
  {
    "path": "test/stubs-630/_data/globalData1.cjs",
    "content": "module.exports = {\n  datakey1: \"datavalue1\"\n};\n"
  },
  {
    "path": "test/stubs-630/_data/globalData2.json",
    "content": "{\n  \"datakey1\": \"datavalue2\",\n  \"datakey2\": \"{{pkg.name}}--json\"\n}\n"
  },
  {
    "path": "test/stubs-630/_data/globalData3.yaml",
    "content": "datakey1: datavalue3\ndatakey2: \"{{pkg.name}}--yaml\"\n"
  },
  {
    "path": "test/stubs-630/_data/globalData4.nosj",
    "content": "{\n  \"datakey1\": \"datavalue4\",\n  \"datakey2\": \"{{pkg.name}}--nosj\"\n}\n"
  },
  {
    "path": "test/stubs-630/_data/mergingGlobalData.cjs",
    "content": "module.exports = {\n  datakey0: \"cjs-value1\",\n  datakey1: \"cjs-value1\",\n  cjskey: \"cjs\"\n};\n"
  },
  {
    "path": "test/stubs-630/_data/mergingGlobalData.js",
    "content": "export default {\n  datakey0: \"js-value0\",\n  jskey: \"js\",\n};\n"
  },
  {
    "path": "test/stubs-630/_data/mergingGlobalData.json",
    "content": "{\n  \"datakey0\": \"json-value1\",\n  \"datakey1\": \"json-value1\",\n  \"datakey2\": \"json-value2\",\n  \"jsonkey\": \"json\"\n}\n"
  },
  {
    "path": "test/stubs-630/_data/mergingGlobalData.nosj",
    "content": "{\n  \"datakey0\": \"nosj-value1\",\n  \"datakey1\": \"nosj-value1\",\n  \"datakey2\": \"nosj-value2\",\n  \"datakey3\": \"nosj-value3\",\n  \"datakey4\": \"nosj-value4\",\n  \"nosjkey\": \"nosj\"\n}\n"
  },
  {
    "path": "test/stubs-630/_data/mergingGlobalData.yaml",
    "content": "datakey0: \"yaml-value1\"\ndatakey1: \"yaml-value1\"\ndatakey2: \"yaml-value2\"\ndatakey3: \"yaml-value3\"\nyamlkey: \"yaml\"\n"
  },
  {
    "path": "test/stubs-630/_data/subdir/globalDataSubdir.yaml",
    "content": "keyyaml: \"yaml\"\n"
  },
  {
    "path": "test/stubs-630/component-yaml/component.11tydata.cjs",
    "content": "module.exports = {\n  jsKey1: \"js1\"\n};\n"
  },
  {
    "path": "test/stubs-630/component-yaml/component.11tydata.json",
    "content": "{\n    \"jsonKey1\": \"json1\"\n}\n"
  },
  {
    "path": "test/stubs-630/component-yaml/component.11tydata.nosj",
    "content": "{\n  \"nosjKey1\": \"nosj1\"\n}\n"
  },
  {
    "path": "test/stubs-630/component-yaml/component.11tydata.yaml",
    "content": "yamlKey2: \"yaml2\"\nyamlKey3: \"yaml3\"\njsonKey1: \"overridden\"\njsKey1: \"overridden\"\nnosjKey1: \"overridden\"\n"
  },
  {
    "path": "test/stubs-630/component-yaml/component.json",
    "content": "{\n    \"jsonKey2\": \"json2\",\n    \"jsKey1\": \"overridden\",\n    \"yamlKey3\": \"overridden\"\n}\n"
  },
  {
    "path": "test/stubs-630/component-yaml/component.njk",
    "content": "{{localkeyOverride}}"
  },
  {
    "path": "test/stubs-630/component-yaml/component.yaml",
    "content": "yamlKey1: \"yaml1\"\nyamlKey2: \"overridden\"\njsonKey1: \"overridden\"\njsonKey2: \"overridden\"\njsKey1: \"overridden\"\n"
  },
  {
    "path": "test/stubs-670/content.njk",
    "content": "---\ntags:\n  - Cañon City\n---"
  },
  {
    "path": "test/stubs-670/index.njk",
    "content": "{{ collections | length }},{% for key,item in collections %}{{ key }},{% endfor %}"
  },
  {
    "path": "test/stubs-919/test.11tydata.cjs",
    "content": "module.exports = function () {\n  return {\n    test: Math.random(),\n  };\n};\n"
  },
  {
    "path": "test/stubs-919/test.njk",
    "content": "---\nroot:\n  - one\n  - two\n  - three\npagination:\n  data: \"root\"\n  size: 1\n---\n{{ test | log }}"
  },
  {
    "path": "test/stubs-919/test2.njk",
    "content": ""
  },
  {
    "path": "test/stubs-absolute/test.md",
    "content": ""
  },
  {
    "path": "test/stubs-addglobaldata/test.liquid",
    "content": ""
  },
  {
    "path": "test/stubs-addglobaldata-noop/test.txt",
    "content": ""
  },
  {
    "path": "test/stubs-autocopy/.gitkeep",
    "content": ""
  },
  {
    "path": "test/stubs-base/index.njk",
    "content": "---\npermalink: /deep/\n---\n<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<meta name=\"description\" content=\"\">\n<title></title>\n<style>div { background-image: url(test.jpg); }</style>\n<style>div { background-image: url({{ \"/test.jpg\" | htmlBaseUrl }}); }</style>\n<link rel=\"stylesheet\" href=\"/test.css\">\n<script src=\"/test.js\"></script>\n</head>\n<body>\n<a href=\"/\">Home</a>\n<a href=\"subdir/\">Test</a>\n<a href=\"./subdir/\">Test</a>\n<a href=\"../subdir/\">Test</a>\n</body>\n</html>"
  },
  {
    "path": "test/stubs-base-case-sens/index.njk",
    "content": "---\npermalink: /deep/\n---\n<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<meta name=\"description\" content=\"\">\n<title></title>\n<style>div { background-image: url(test.jpg); }</style>\n<style>div { background-image: url({{ \"/test.jpg\" | htmlBaseUrl }}); }</style>\n<link rel=\"stylesheet\" href=\"/test.css\">\n<script SrC=\"/test.js\"></script>\n</head>\n<body>\n<a hreF=\"/\">Home</a>\n<a HrEf=\"subdir/\">Test</a>\n<a href=\"../subdir/\">Test</a>\n</body>\n</html>"
  },
  {
    "path": "test/stubs-circular-layout/_includes/layout-cycle-a.njk",
    "content": "---\nlayout: layouts/layout-cycle-b.njk\n---"
  },
  {
    "path": "test/stubs-circular-layout/_includes/layout-cycle-b.njk",
    "content": "---\nlayout: layouts/layout-cycle-c.njk\n---"
  },
  {
    "path": "test/stubs-circular-layout/_includes/layout-cycle-c.njk",
    "content": "---\nlayout: layouts/layout-cycle-a.njk\n---"
  },
  {
    "path": "test/stubs-circular-layout/_includes/layout-cycle-self.njk",
    "content": "---\nlayout: layouts/layout-cycle-b.njk\n---"
  },
  {
    "path": "test/stubs-computed-array/test.liquid",
    "content": "---\ndynamicValue: \"test\"\neleventyComputed:\n  notArray: \"{{ dynamicValue }}\"\n  array:\n    - \"static value\"\n    - \"{{ dynamicValue }}\"\n---"
  },
  {
    "path": "test/stubs-computed-collections/collections.njk",
    "content": "---js\n{\n  eleventyComputed: {\n    test: \"hello\",\n    dogCollection: data => {\n      return data.collections.dog;\n    }\n  }\n}\n---\nIssue #1114"
  },
  {
    "path": "test/stubs-computed-collections/dog.njk",
    "content": "---\ntags: dog\n---\nHi from dog"
  },
  {
    "path": "test/stubs-computed-collections-filter/collections.njk",
    "content": "---js\n{\n  eleventyComputed: {\n    test: \"hello\",\n    dogCollection: data => {\n      return data.collections.dog.filter(entry => true);\n    }\n  }\n}\n---\nIssue #1114"
  },
  {
    "path": "test/stubs-computed-collections-filter/dog.njk",
    "content": "---\ntags: dog\n---\nHi from dog"
  },
  {
    "path": "test/stubs-computed-dirdata/dir/dir.11tydata.cjs",
    "content": "module.exports = {\n  eleventyComputed: {\n    webmentions: (data) => {\n      return data.test;\n    },\n  },\n};\n"
  },
  {
    "path": "test/stubs-computed-dirdata/dir/first.11ty.cjs",
    "content": "module.exports.data = {\n  test: \"first\",\n};\n\nmodule.exports.render = function (data) {\n  return \"first\";\n};\n"
  },
  {
    "path": "test/stubs-computed-dirdata/dir/second.11ty.cjs",
    "content": "module.exports.data = {\n  test: \"second\",\n};\n\nmodule.exports.render = function (data) {\n  return \"second\";\n};\n"
  },
  {
    "path": "test/stubs-computed-global/_data/eleventyComputed.cjs",
    "content": "module.exports = {\n  eleventyNavigation: {\n    key: data => {\n      return \"nested-first-global\";\n    }\n  },\n  image2: data => {\n    return \"second-global\";\n  },\n  image3: data => {\n    return \"third-global\";\n  }\n};\n"
  },
  {
    "path": "test/stubs-computed-global/intermix.njk",
    "content": "---js\n{\n  eleventyComputed: {\n    image: data => {\n      return \"first\";\n    },\n    image2: data => {\n      return \"second\";\n    }\n  }\n}\n---\nIssue #1043"
  },
  {
    "path": "test/stubs-computed-pagination/child.11ty.cjs",
    "content": "module.exports.data = {\n  eleventyComputed: {\n    venues: (data) => {\n      return data.collections.venue;\n    },\n  },\n};\n"
  },
  {
    "path": "test/stubs-computed-pagination/paginated.njk",
    "content": "---\nvenues:\n  - first\n  - second\npagination:\n  data: venues\n  size: 1\n  alias: venue\n  addAllPagesToCollections: true\npermalink: \"venues/{{ venue }}/\"\ntags: venue\neleventyComputed:\n  title: \"{{ venue }}\"\n---"
  },
  {
    "path": "test/stubs-computed-symbolparse/test.liquid",
    "content": "---\neleventyComputed:\n  c: \"{{ a | fail }}{{ b | fail }}\"\n  a: \"a\"\n  b: \"b\"\n---"
  },
  {
    "path": "test/stubs-computed-symbolparse/test.njk",
    "content": "---\neleventyComputed:\n  c: \"{{ a | fail }}{{ b | fail }}\"\n  a: \"a\"\n  b: \"b\"\n---"
  },
  {
    "path": "test/stubs-custom-extension/test.js1",
    "content": "<p>Paragraph</p>"
  },
  {
    "path": "test/stubs-data-cascade/global-versus-layout/_data/cascade.cjs",
    "content": "module.exports = \"from-global-data\";\n"
  },
  {
    "path": "test/stubs-data-cascade/global-versus-layout/_includes/base.njk",
    "content": "---\ncascade: \"from-layout-file\"\n---"
  },
  {
    "path": "test/stubs-data-cascade/global-versus-layout/test.njk",
    "content": "---\nlayout: \"base.njk\"\n---"
  },
  {
    "path": "test/stubs-data-cascade/layout-data-files/_includes/base.njk",
    "content": "---\nshared: layout\n---"
  },
  {
    "path": "test/stubs-data-cascade/layout-data-files/test.11tydata.cjs",
    "content": "module.exports = {\n  shared: \"datafile\"\n};\n"
  },
  {
    "path": "test/stubs-data-cascade/layout-data-files/test.njk",
    "content": "---\nlayout: base.njk\n---"
  },
  {
    "path": "test/stubs-data-cascade/layout-versus-dirdatafile/src/_includes/base.njk",
    "content": "---\ncascade: \"from-layout-file\"\n---"
  },
  {
    "path": "test/stubs-data-cascade/layout-versus-dirdatafile/src/src.11tydata.cjs",
    "content": "module.exports = {\n  cascade: \"dir-data-file\",\n};\n"
  },
  {
    "path": "test/stubs-data-cascade/layout-versus-dirdatafile/src/test.njk",
    "content": "---\nlayout: \"base.njk\"\n---"
  },
  {
    "path": "test/stubs-data-cascade/layout-versus-tmpldatafile/_includes/base.njk",
    "content": "---\ncascade: \"from-layout-file\"\n---"
  },
  {
    "path": "test/stubs-data-cascade/layout-versus-tmpldatafile/test.11tydata.cjs",
    "content": "module.exports = {\n  cascade: \"template-data-file\",\n};\n"
  },
  {
    "path": "test/stubs-data-cascade/layout-versus-tmpldatafile/test.njk",
    "content": "---\nlayout: \"base.njk\"\n---"
  },
  {
    "path": "test/stubs-data-esm/_data/commonjs.cjs",
    "content": "module.exports = \"commonjs default\";"
  },
  {
    "path": "test/stubs-data-esm/_data/module.mjs",
    "content": "export const named = \"es module named\";\n\nexport default \"es module default\";\n"
  },
  {
    "path": "test/stubs-dependency-tree/child.cjs",
    "content": "require(\"./grandchild.cjs\");\n"
  },
  {
    "path": "test/stubs-dependency-tree/grandchild.cjs",
    "content": "require(\"kleur\");\n"
  },
  {
    "path": "test/stubs-dependency-tree/index.cjs",
    "content": "require(\"@11ty/lodash-custom\");\nrequire(\"./child.cjs\");\n"
  },
  {
    "path": "test/stubs-empty/.gitkeep",
    "content": ""
  },
  {
    "path": "test/stubs-empty-json-data/_data/empty.json",
    "content": "    \n    "
  },
  {
    "path": "test/stubs-fancyjs/test.11ty.tsx",
    "content": "import React from \"react\";\n\nfunction render(data: object) {\n\treturn <div>hello world 1</div>;\n}\n\nexport { render }"
  },
  {
    "path": "test/stubs-fancyjs/test.mdx",
    "content": "export function Thing() {\n  return <>World!!!!</>\n}\n\n# Hello, <Thing />"
  },
  {
    "path": "test/stubs-freeze/eleventy/_data/eleventy.js",
    "content": "export default {\n  \"key\": \"value\", // not allowed\n};"
  },
  {
    "path": "test/stubs-freeze/page/_data/page.js",
    "content": "export default {\n  \"key\": \"value\", // allowed\n  \"url\": \"lksjdklfjlskdjf\", // not allowed\n};"
  },
  {
    "path": "test/stubs-global-data-config-api/empty.txt",
    "content": ""
  },
  {
    "path": "test/stubs-global-data-config-api-nested/_data/deep.cjs",
    "content": "module.exports = {\n  existing: true,\n};\n"
  },
  {
    "path": "test/stubs-i18n/en/index.liquid",
    "content": "{{ \"/\" | locale_url }}\n{{ \"/en-us/\" | locale_url }}\n{{ \"/es/\" | locale_url }}\n{{ \"/\" | locale_url: \"en-us\" }}\n{{ \"/non-lang-file/\" | locale_url }}\n{{ page.url | locale_links | json }}\n{{ \"\" | locale_links | json }}\n{{ page.lang }}"
  },
  {
    "path": "test/stubs-i18n/en-us/index.11ty.cjs",
    "content": "module.exports = function (data) {\n  return `${this.locale_url(\"/\")}\n${this.locale_url(\"/en-us/\")}\n${this.locale_url(\"/es/\")}\n${this.locale_url(\"/\", \"es\")}\n${this.locale_url(\"/non-lang-file/\")}\n${JSON.stringify(this.locale_links(data.page.url).sort())}\n${JSON.stringify(this.locale_links().sort())}\n${data.page.lang}`;\n};\n"
  },
  {
    "path": "test/stubs-i18n/es/index.njk",
    "content": "{{ \"/\" | locale_url }}\n{{ \"/en-us/\" | locale_url }}\n{{ \"/es/\" | locale_url }}\n{{ \"/\" | locale_url(\"en-us\") }}\n{{ \"/non-lang-file/\" | locale_url }}\n{{ page.url | locale_links | dump | safe }}\n{{ \"\" | locale_links | dump | safe }}\n{{ page.lang }}"
  },
  {
    "path": "test/stubs-i18n/non-lang-file.njk",
    "content": "{{ \"/\" | locale_url }}\n{{ \"/\" | locale_url(\"en-us\") }}\n{{ \"/non-lang-file/\" | locale_url }}\n{{ page.url | locale_links | dump | safe }}\n{{ \"\" | locale_links | dump | safe }}\n{{ page.lang }}"
  },
  {
    "path": "test/stubs-img-transform/ignored.md",
    "content": "<img src=\"./possum.png\" alt=\"it’s a possum\" loading=\"eager\" eleventy:ignore>"
  },
  {
    "path": "test/stubs-img-transform/missing-alt.md",
    "content": "<img src=\"./possum.png\">"
  },
  {
    "path": "test/stubs-img-transform/multiple.md",
    "content": "<img src=\"./possum.png\" alt=\"it’s a possum\" loading=\"eager\">\n<img src=\"./possum.png\" alt=\"it’s a possum\">"
  },
  {
    "path": "test/stubs-img-transform/single.md",
    "content": "<img src=\"./possum.png\" alt=\"it’s a possum\" loading=\"eager\">"
  },
  {
    "path": "test/stubs-incremental/layout-chain/_includes/base.njk",
    "content": "---\nlayout: parent.njk\n---"
  },
  {
    "path": "test/stubs-incremental/layout-chain/_includes/parent.njk",
    "content": ""
  },
  {
    "path": "test/stubs-incremental/layout-chain/test.njk",
    "content": "---\nlayout: base.njk\n---"
  },
  {
    "path": "test/stubs-layout-cache/_includes/layout.liquid",
    "content": "<script>{%- include 'include-script-2.js' -%}</script>"
  },
  {
    "path": "test/stubs-layout-cache/_includes/layout.njk",
    "content": "<script>{%- include \"include-script-1.js\" -%}</script>"
  },
  {
    "path": "test/stubs-layout-cache/test.liquid",
    "content": "---\nlayout: \"layout.liquid\"\n---\nContent"
  },
  {
    "path": "test/stubs-layout-cache/test.njk",
    "content": "---\nlayout: \"layout.njk\"\n---\nContent"
  },
  {
    "path": "test/stubs-layouts-event/_includes/first.liquid",
    "content": "---\nlayout: second\n---\n{{ content }}"
  },
  {
    "path": "test/stubs-layouts-event/_includes/second.liquid",
    "content": "---\nlayout: third.liquid\n---\n{{ content }}"
  },
  {
    "path": "test/stubs-layouts-event/_includes/third.liquid",
    "content": "{{ content }}"
  },
  {
    "path": "test/stubs-layouts-event/page.md",
    "content": "---\nlayout: first\n---\n"
  },
  {
    "path": "test/stubs-njk-async/_includes/loop.njk",
    "content": "included_{{item}}-{% genericshortcode item %}"
  },
  {
    "path": "test/stubs-pagination-computed-quotes/post.liquid",
    "content": "---\ntags: posts\n---\nNo"
  },
  {
    "path": "test/stubs-pagination-computed-quotes/test.liquid",
    "content": "---\npagination:\n  data: \"collections.posts\"\n  size: 1\nquotes:\n  - The person that shared this is awesome\neleventyComputed:\n  quote: \"{{ quotes | selectRandomFromArray }}\"\n---\n{{ quote }}"
  },
  {
    "path": "test/stubs-pagination-computed-quotes-njk/post.njk",
    "content": "---\ntags: posts\n---\nNo"
  },
  {
    "path": "test/stubs-pagination-computed-quotes-njk/test.njk",
    "content": "---\npagination:\n  data: \"collections.posts\"\n  size: 1\nquotes:\n  - The person that shared this is awesome\neleventyComputed:\n  quote: \"{{ quotes | selectRandomFromArray }}\"\n---\n{{ quote }}"
  },
  {
    "path": "test/stubs-pathtourl/css.njk",
    "content": "---\npermalink: output.css\n---"
  },
  {
    "path": "test/stubs-pathtourl/filter.njk",
    "content": "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<meta name=\"description\" content=\"\">\n<title></title>\n<link rel=\"stylesheet\" href=\"{{ 'css.njk' | inputPathToUrl }}\">\n<script src=\"{{ 'css.njk' | inputPathToUrl }}\"></script>\n</head>\n<body>\n<a href=\"/\">Home</a>\n<a href=\"{{ 'tmpl.njk' | inputPathToUrl }}\">Test</a>\n<a href=\"{{ 'tmpl.njk#anchor' | inputPathToUrl }}\">Anchor</a>\n<a href=\"#anchor\">Anchor</a>\n<a href=\"./#anchor\">Anchor</a>\n<a href=\"?search=1\">Search</a>\n<a href=\"./?search=1\">Search</a>\n</body>\n</html>"
  },
  {
    "path": "test/stubs-pathtourl/tmpl.njk",
    "content": ""
  },
  {
    "path": "test/stubs-pathtourl/transform.njk",
    "content": "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<meta name=\"description\" content=\"\">\n<title></title>\n<link rel=\"stylesheet\" href=\"css.njk\">\n<script src=\"css.njk\"></script>\n</head>\n<body>\n<a href=\"/\">Home</a>\n<a href=\"tmpl.njk\">Test</a>\n<a href=\"tmpl.njk#anchor\">Anchor</a>\n<a href=\"#anchor\">Anchor</a>\n<a href=\"./#anchor\">Anchor</a>\n<a href=\"?search=1\">Search</a>\n<a href=\"./?search=1\">Search</a>\n</body>\n</html>"
  },
  {
    "path": "test/stubs-render-plugin/11tyjs-file-override.njk",
    "content": "---\nhi: value\nargData:\n  hi: liquidHi\n  bye: liquidBye\n---\n{% renderFile \"./test/stubs-render-plugin/_includes/include-js.txt\", argData, \"11ty.js\" %}"
  },
  {
    "path": "test/stubs-render-plugin/11tyjs-file.njk",
    "content": "---\nhi: value\nargData:\n  hi: liquidHi\n  bye: liquidBye\n---\n{% renderFile \"./test/stubs-render-plugin/_includes/include.11ty.cjs\", argData %}"
  },
  {
    "path": "test/stubs-render-plugin/11tyjs.liquid",
    "content": "---\nhi: value\nargData:\n  hi: liquidHi\n  bye: liquidBye\n---\n{% renderTemplate \"11ty.js\" argData %}\nmodule.exports = \"TESTING\";\n{% endrenderTemplate %}\n"
  },
  {
    "path": "test/stubs-render-plugin/_includes/frontmatter.liquid",
    "content": "---\ntest: frontmatterString\n---\n{{ test }}"
  },
  {
    "path": "test/stubs-render-plugin/_includes/include-js.txt",
    "content": "module.exports = \"TESTING\";\n"
  },
  {
    "path": "test/stubs-render-plugin/_includes/include.11ty.cjs",
    "content": "module.exports = \"TESTING\";\n"
  },
  {
    "path": "test/stubs-render-plugin/_includes/include.liquid",
    "content": "TESTING\n{% renderTemplate \"liquid\" %}\nTESTING IN LIQUID\n{% assign liquidassign = 999 %}\n* {{ liquidassign }}\n{% endrenderTemplate %}"
  },
  {
    "path": "test/stubs-render-plugin/_includes/include.njk",
    "content": "TESTING\n{% renderTemplate \"liquid\" %}\nTESTING IN LIQUID\n{% assign liquidassign = 999 %}\n* {{ liquidassign }}\n{% endrenderTemplate %}"
  },
  {
    "path": "test/stubs-render-plugin/bad-data.njk",
    "content": "{% renderTemplate \"liquid\", \"string\" %}\n{{ _ }}\n{% endrenderTemplate %}\n"
  },
  {
    "path": "test/stubs-render-plugin/capture-liquid.njk",
    "content": "---\nargData:\n  num: 2\n---\n{% setAsync \"liquidOutput\" %}\n{% renderTemplate \"liquid\", argData %}\n{% assign liquidVar = num | times: 2 %}\n{{ liquidVar }}\n{% endrenderTemplate %}\n{% endsetAsync %}\n{{ liquidOutput }}"
  },
  {
    "path": "test/stubs-render-plugin/capture-njk.liquid",
    "content": "---\nargData:\n  num: 2\n---\n{% capture nunjucksOutput %}\n{% renderTemplate \"njk\", argData %}\n{% set njkVar = num * 2 %}\n{{ njkVar }}\n{% endrenderTemplate %}\n{% endcapture %}\n{{ nunjucksOutput }}"
  },
  {
    "path": "test/stubs-render-plugin/data-no-templatelang.liquid",
    "content": "---\nargData:\n  name: Bruno\n---\n{% renderTemplate argData %}\n# Hello {{ name }}\n* Testing\n{% endrenderTemplate %}\n"
  },
  {
    "path": "test/stubs-render-plugin/false.liquid",
    "content": "{% renderTemplate %}\n{% assign name = \"Bruno\" %}\n# Hello {{ name }}\n* Testing\n{% endrenderTemplate %}\n"
  },
  {
    "path": "test/stubs-render-plugin/liquid-direct.njk",
    "content": "{%- set nunjucksVar = 69 -%}\n{{ hi }}\n{{ nunjucksVar }}\n{{ \"who\" | testing }}\n{% renderTemplate \"liquid\", argData %}\n{% assign liquidVar = 138 %}\n* {{ hi }} test test {{ bye }} {{ liquidVar }}\n{% endrenderTemplate %}\n"
  },
  {
    "path": "test/stubs-render-plugin/liquid-eleventy.njk",
    "content": "---\n---\n{% renderTemplate \"liquid\" %}\n{{ eleventy.version }}\n{% endrenderTemplate %}\n"
  },
  {
    "path": "test/stubs-render-plugin/liquid-global.njk",
    "content": "---\nhi: globalHi\n---\n{% renderTemplate \"liquid\" %}\n{{ hi }}123\n{% endrenderTemplate %}\n"
  },
  {
    "path": "test/stubs-render-plugin/liquid-md.11ty.cjs",
    "content": "class Tmpl {\n  data() {\n    return {\n      argData: {\n        hi: \"javascriptHi\",\n        bye: \"javascriptBye\",\n      },\n    };\n  }\n\n  async render(data) {\n    return this.renderTemplate(\n      `\n# Markdown\n{% assign t1 = 2 %}\n* {{ t1 }}\n`,\n      \"liquid,md\",\n      data.argData\n    );\n  }\n}\nmodule.exports = Tmpl;\n"
  },
  {
    "path": "test/stubs-render-plugin/liquid-md.liquid",
    "content": "---\nhi: value\nargData:\n  hi: liquidHi\n  bye: liquidBye\n---\n{% renderTemplate \"liquid,md\" argData %}\n# Hello {{ hi }}\n* Testing\n{% endrenderTemplate %}\n"
  },
  {
    "path": "test/stubs-render-plugin/liquid-page.liquid",
    "content": "{% renderTemplate \"liquid\" %}\n{{ page.url }}\n{% endrenderTemplate %}\n"
  },
  {
    "path": "test/stubs-render-plugin/liquid-page.njk",
    "content": "{% renderTemplate \"liquid\" %}\n{{ page.url }}\n{% endrenderTemplate %}\n"
  },
  {
    "path": "test/stubs-render-plugin/liquid.njk",
    "content": "---\n# Nunjucks\nhi: nunjucksHi\nargData:\n  hi: liquidHi\n  bye: liquidBye\n---\n{%- set nunjucksVar = 69 -%}\n{{ hi }}\n{{ nunjucksVar }}\n{% renderTemplate \"liquid\", argData %}\n{% assign liquidVar = 138 %}\n* {{ hi }} test test {{ bye }} {{ liquidVar }}\n{% endrenderTemplate %}\n"
  },
  {
    "path": "test/stubs-render-plugin/md.liquid",
    "content": "---\nhi: value\nargData:\n  hi: liquidHi\n  bye: liquidBye\n---\n{% renderTemplate \"md\" argData %}\n# Hello {{ hi }}\n* Testing\n{% endrenderTemplate %}"
  },
  {
    "path": "test/stubs-render-plugin/njk-eleventy.liquid",
    "content": "---\n---\n{% renderTemplate \"njk\" %}\n{{ eleventy.version }}\n{% endrenderTemplate %}\n"
  },
  {
    "path": "test/stubs-render-plugin/njk-file-not-exist.liquid",
    "content": "{% renderFile \"./test/stubs-render-plugin/THIS_DOES_NOT_EXIST.njk\" %}\n"
  },
  {
    "path": "test/stubs-render-plugin/njk-file.liquid",
    "content": "---\nhi: value\nargData:\n  hi: liquidHi\n  bye: liquidBye\n---\n{% renderFile \"./test/stubs-render-plugin/_includes/include.njk\" argData %}\n"
  },
  {
    "path": "test/stubs-render-plugin/njk-file.njk",
    "content": "---\nhi: value\nargData:\n  hi: liquidHi\n  bye: liquidBye\n---\n{% renderFile \"./test/stubs-render-plugin/_includes/include.liquid\", argData %}\n"
  },
  {
    "path": "test/stubs-render-plugin/njk-page.liquid",
    "content": "{% renderTemplate \"njk\" %}\n{{ page.url }}\n{% endrenderTemplate %}\n"
  },
  {
    "path": "test/stubs-render-plugin/nunjucks-frontmatter.njk",
    "content": "---\nhi: \"{% test %}\"\n---\n{{ hi | renderContent(\"njk\") }}\n{# {% renderTemplate \"njk\", hi %}{{ _ }}{% endrenderTemplate %} #}\n"
  },
  {
    "path": "test/stubs-render-plugin/nunjucks-global.liquid",
    "content": "---\nhi: globalHi\n---\n{% renderTemplate \"njk\" %}\n{{ hi }}\n{% endrenderTemplate %}\n"
  },
  {
    "path": "test/stubs-render-plugin/nunjucks.11ty.cjs",
    "content": "class Tmpl {\n  data() {\n    return {\n      argData: {\n        hi: \"javascriptHi\",\n        bye: \"javascriptBye\",\n      },\n    };\n  }\n\n  async render(data) {\n    return this.renderTemplate(\n      `\n* {{ hi | reverse }}\n`,\n      \"njk\",\n      data.argData\n    );\n  }\n}\nmodule.exports = Tmpl;\n"
  },
  {
    "path": "test/stubs-render-plugin/nunjucks.liquid",
    "content": "---\nhi: value\nargData:\n  hi: liquidHi\n  bye: liquidBye\n---\n{% renderTemplate \"njk\" argData %}\n{% set bye = \"abldskjfl\" %}\n* {{ hi | reverse }}\n* {{ bye | reverse }}\n{% endrenderTemplate %}\n"
  },
  {
    "path": "test/stubs-render-plugin/using-frontmatter.liquid",
    "content": "{% renderFile \"./test/stubs-render-plugin/_includes/frontmatter.liquid\" %}\n"
  },
  {
    "path": "test/stubs-render-plugin/vue.liquid",
    "content": "---\nhi: value\nargData:\n  hi: liquidHi\n  bye: liquidBye\n---\n{% renderTemplate \"vue\" argData %}\n<div>\n  HELLO WE ARE VUEING <p v-html=\"hi\"></p>\n</div>\n{% endrenderTemplate %}"
  },
  {
    "path": "test/stubs-virtual/.gitkeep",
    "content": ""
  },
  {
    "path": "test/stubs-virtual/eleventy.config.js",
    "content": "// generic config file\nexport default function(eleventyConfig) {};"
  },
  {
    "path": "test/stubs-virtual-nowrite/.gitkeep",
    "content": ""
  },
  {
    "path": "test/views/.gitkeep",
    "content": ""
  },
  {
    "path": "test_node/3824/3824-test.js",
    "content": "// This test file is using Node’s test runner because `tsx` doesn’t work with worker threads (used by avajs)\n// See https://github.com/privatenumber/tsx/issues/354\n// See https://github.com/nodejs/node/issues/47747\nimport test from \"node:test\";\nimport fs from \"node:fs\";\nimport assert from \"node:assert\";\n\nimport Eleventy from \"../../src/Eleventy.js\";\nimport { withResolvers } from \"../../src/Util/PromiseUtil.js\";\n\n// This tests Eleventy Watch and the file system!\n\nfunction getInputContent(str = \"\") {\n\treturn `import { Page } from \"./ViewProps.js\";\n\nexport type HeadProps = {\n  page: Page\n};\n\nexport function Head(props: HeadProps): JSX.Element {\n  return <head>\n    <title>My test page${str}</title>\n  </head>;\n}`;\n}\n\nfunction getOutputContent(str = \"\") {\n\treturn `<html><head><title>My test page${str}</title></head><body><p>Hello World</p></body></html>`;\n}\n\ntest(\n\t\"#3824 TSX updates during watch\",\n\t{\n\t\ttimeout: 10000,\n\t},\n\tasync () => {\n\t\tlet comparisonStrings = [\"first\", \"second\"];\n\n\t\tlet runs = comparisonStrings.map((str) => {\n\t\t\treturn {\n\t\t\t\t...withResolvers(),\n\t\t\t\tinput: getInputContent(str),\n\t\t\t\texpected: getOutputContent(str),\n\t\t\t};\n\t\t});\n\n\t\t// Restore original content\n\t\tconst ROOT_DIR = \"./test_node/3824/\";\n\t\tconst OUTPUT_DIR = ROOT_DIR + \"_site/\";\n\n\t\tconst FILE_CHANGING = ROOT_DIR + \"_includes/head.tsx\";\n\t\tconst OUTPUT_FILE = OUTPUT_DIR + \"index.html\";\n\n\t\tfs.writeFileSync(FILE_CHANGING, getInputContent(), \"utf8\");\n\n\t\tlet index = 0;\n\t\tlet elev = new Eleventy(ROOT_DIR, OUTPUT_DIR, {\n\t\t\tconfigPath: ROOT_DIR + \"eleventy.config.js\",\n\t\t\tconfig(eleventyConfig) {\n\t\t\t\televentyConfig.on(\"eleventy.afterwatch\", () => {\n\t\t\t\t\tlet { resolve } = runs[index];\n\t\t\t\t\tindex++;\n\t\t\t\t\tresolve();\n\t\t\t\t});\n\t\t\t},\n\t\t});\n\n\t\telev.disableLogger();\n\t\tawait elev.init();\n\t\tawait elev.watch();\n\n\t\t// Control\n\t\tlet content = fs.readFileSync(OUTPUT_FILE, \"utf8\");\n\t\tassert.equal(content, getOutputContent());\n\n\t\t// Stop after all runs are complete\n\t\tPromise.all(runs.map((entry) => entry.promise)).then(async () => {\n\t\t\tawait elev.stopWatch();\n\t\t});\n\n\t\tfor (let run of runs) {\n\t\t\t// Windows/Ubuntu needed this for Chokidar reasons\n\t\t\tawait new Promise((resolve) => setTimeout(resolve, 200));\n\t\t\tfs.writeFileSync(FILE_CHANGING, run.input, \"utf8\");\n\n\t\t\tawait run.promise;\n\n\t\t\tlet content = fs.readFileSync(OUTPUT_FILE, \"utf8\");\n\t\t\tassert.equal(content, run.expected);\n\t\t}\n\n\t\tfs.writeFileSync(FILE_CHANGING, getInputContent(), \"utf8\");\n\t\tfs.rmSync(OUTPUT_DIR, { recursive: true });\n\t},\n);\n"
  },
  {
    "path": "test_node/3824/_includes/head.tsx",
    "content": "import { Page } from \"./ViewProps.js\";\n\nexport type HeadProps = {\n  page: Page\n};\n\nexport function Head(props: HeadProps): JSX.Element {\n  return <head>\n    <title>My test page</title>\n  </head>;\n}"
  },
  {
    "path": "test_node/3824/_includes/view-props.tsx",
    "content": ""
  },
  {
    "path": "test_node/3824/eleventy.config.js",
    "content": "import { register } from \"tsx/esm/api\";\n\nimport { jsxToString } from \"jsx-async-runtime\";\n// import { renderToStaticMarkup } from \"react-dom/server\";\n\nexport default async function (eleventyConfig) {\n\televentyConfig.addExtension([\"11ty.jsx\", \"11ty.ts\", \"11ty.tsx\"], {\n\t\tkey: \"11ty.js\",\n\t\tcompile: async function (inputContent, inputPath) {\n\t\t\tthis.addDependencies(inputPath, [\"./test_node/3824/_includes/head.tsx\"]);\n\n\t\t\treturn async function (data) {\n\t\t\t\tlet content = await this.defaultRenderer(data);\n\t\t\t\treturn jsxToString(content);\n\t\t\t\t// return renderToStaticMarkup(content);\n\t\t\t};\n\t\t},\n\t});\n\n\televentyConfig.addTemplateFormats([\"11ty.jsx\", \"11ty.tsx\"]);\n\n\tlet unregister;\n\televentyConfig.on(\"eleventy.before\", () => {\n\t\tunregister = register({\n\t\t\t// custom tsconfig\n\t\t\ttsconfig: \"test_node/3824/tsconfig-3824.json\",\n\t\t});\n\t});\n\televentyConfig.on(\"eleventy.after\", () => {\n\t\tunregister();\n\t});\n}\n"
  },
  {
    "path": "test_node/3824/index.11ty.tsx",
    "content": "import { Head } from \"./_includes/head.tsx\";\nimport { Page, ViewProps } from \"./_includes/viewprops.tsx\";\n\n\nexport type IndexProps = {\n  children?: JSX.Element;\n  page: Page\n};\n\nexport function Index(props: IndexProps): JSX.Element {\n  return <html>\n    <Head page={props.page} />\n    <body>\n      <p>Hello World</p>\n    </body>\n  </html>;\n}\n\nexport function render(props: ViewProps): JSX.Element {\n  return <Index page={props.page} />;\n}\n"
  },
  {
    "path": "test_node/3824/tsconfig-3824.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es2016\",\n    \"jsx\": \"react-jsx\",\n    \"jsxImportSource\": \"jsx-async-runtime\",\n    \"module\": \"NodeNext\",\n    \"outDir\": \"dist\",\n    \"esModuleInterop\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"strict\": true,\n    \"skipLibCheck\": true\n  },\n  \"exclude\": [ \"node_modules\", \"_site\" ]\n}\n"
  },
  {
    "path": "test_node/3824-incremental/3824-incremental-test.js",
    "content": "// This test file is using Node’s test runner because `tsx` doesn’t work with worker threads (used by avajs)\n// See https://github.com/privatenumber/tsx/issues/354\n// See https://github.com/nodejs/node/issues/47747\nimport test from \"node:test\";\nimport fs from \"node:fs\";\nimport assert from \"node:assert\";\n\nimport Eleventy from \"../../src/Eleventy.js\";\nimport { withResolvers } from \"../../src/Util/PromiseUtil.js\";\n\n// This tests Eleventy Watch and the file system!\n\nfunction getInputContent(str = \"\") {\n\treturn `import { Page } from \"./ViewProps.js\";\n\nexport type HeadProps = {\n  page: Page\n};\n\nexport function Head(props: HeadProps): JSX.Element {\n  return <head>\n    <title>My test page${str}</title>\n  </head>;\n}`;\n}\n\nfunction getOutputContent(str = \"\") {\n\treturn `<html><head><title>My test page${str}</title></head><body><p>Hello World</p></body></html>`;\n}\n\ntest(\n\t\"#3824 TSX updates during watch (incremental)\",\n\t{\n\t\ttimeout: 10000,\n\t},\n\tasync () => {\n\t\tlet comparisonStrings = [\"first\", \"second\"];\n\n\t\tlet runs = comparisonStrings.map((str) => {\n\t\t\treturn {\n\t\t\t\t...withResolvers(),\n\t\t\t\tinput: getInputContent(str),\n\t\t\t\texpected: getOutputContent(str),\n\t\t\t};\n\t\t});\n\n\t\t// Restore original content\n\t\tconst ROOT_DIR = \"./test_node/3824-incremental/\";\n\t\tconst OUTPUT_DIR = ROOT_DIR + \"_site/\";\n\n\t\tconst FILE_CHANGING = ROOT_DIR + \"_includes/head.tsx\";\n\t\tconst OUTPUT_FILE = OUTPUT_DIR + \"index.html\";\n\n\t\tfs.writeFileSync(FILE_CHANGING, getInputContent(), \"utf8\");\n\n\t\tlet index = 0;\n\t\tlet elev = new Eleventy(ROOT_DIR, OUTPUT_DIR, {\n\t\t\tconfigPath: ROOT_DIR + \"eleventy.config.js\",\n\t\t\tconfig(eleventyConfig) {\n\t\t\t\televentyConfig.on(\"eleventy.afterwatch\", () => {\n\t\t\t\t\tlet { resolve } = runs[index];\n\t\t\t\t\tindex++;\n\t\t\t\t\tresolve();\n\t\t\t\t});\n\t\t\t},\n\t\t});\n\n\t\t// Same as 3824-test.js except for this line\n\t\telev.setIncrementalBuild(true);\n\n\t\telev.disableLogger();\n\t\tawait elev.init();\n\t\tawait elev.watch();\n\n\t\t// Control\n\t\tlet content = fs.readFileSync(OUTPUT_FILE, \"utf8\");\n\t\tassert.equal(content, getOutputContent());\n\n\t\t// Stop after all runs are complete\n\t\tPromise.all(runs.map((entry) => entry.promise)).then(async () => {\n\t\t\tawait elev.stopWatch();\n\t\t});\n\n\t\tfor (let run of runs) {\n\t\t\t// Windows/Ubuntu needed this for Chokidar reasons\n\t\t\tawait new Promise((resolve) => setTimeout(resolve, 200));\n\n\t\t\tfs.writeFileSync(FILE_CHANGING, run.input, \"utf8\");\n\t\t\tawait run.promise;\n\n\t\t\tlet content = fs.readFileSync(OUTPUT_FILE, \"utf8\");\n\t\t\tassert.equal(content, run.expected);\n\t\t}\n\n\t\tfs.writeFileSync(FILE_CHANGING, getInputContent(), \"utf8\");\n\t\tfs.rmSync(OUTPUT_DIR, { recursive: true });\n\t},\n);\n"
  },
  {
    "path": "test_node/3824-incremental/_includes/head.tsx",
    "content": "import { Page } from \"./ViewProps.js\";\n\nexport type HeadProps = {\n  page: Page\n};\n\nexport function Head(props: HeadProps): JSX.Element {\n  return <head>\n    <title>My test page</title>\n  </head>;\n}"
  },
  {
    "path": "test_node/3824-incremental/_includes/view-props.tsx",
    "content": ""
  },
  {
    "path": "test_node/3824-incremental/eleventy.config.js",
    "content": "import { register } from \"tsx/esm/api\";\n\nimport { jsxToString } from \"jsx-async-runtime\";\n// import { renderToStaticMarkup } from \"react-dom/server\";\n\nexport default async function (eleventyConfig) {\n\televentyConfig.addExtension([\"11ty.jsx\", \"11ty.ts\", \"11ty.tsx\"], {\n\t\tkey: \"11ty.js\",\n\t\tcompile: async function (inputContent, inputPath) {\n\t\t\tthis.addDependencies(inputPath, [\"./test_node/3824-incremental/_includes/head.tsx\"]);\n\n\t\t\treturn async function (data) {\n\t\t\t\tlet content = await this.defaultRenderer(data);\n\t\t\t\treturn jsxToString(content);\n\t\t\t\t// return renderToStaticMarkup(content);\n\t\t\t};\n\t\t},\n\t});\n\n\televentyConfig.addTemplateFormats([\"11ty.jsx\", \"11ty.tsx\"]);\n\n\tlet unregister;\n\televentyConfig.on(\"eleventy.before\", () => {\n\t\tunregister = register({\n\t\t\t// custom tsconfig\n\t\t\ttsconfig: \"test_node/3824-incremental/tsconfig-3824.json\",\n\t\t});\n\t});\n\televentyConfig.on(\"eleventy.after\", () => {\n\t\tunregister();\n\t});\n}\n"
  },
  {
    "path": "test_node/3824-incremental/index.11ty.tsx",
    "content": "import { Head } from \"./_includes/head.tsx\";\nimport { Page, ViewProps } from \"./_includes/viewprops.tsx\";\n\n\nexport type IndexProps = {\n  children?: JSX.Element;\n  page: Page\n};\n\nexport function Index(props: IndexProps): JSX.Element {\n  return <html>\n    <Head page={props.page} />\n    <body>\n      <p>Hello World</p>\n    </body>\n  </html>;\n}\n\nexport function render(props: ViewProps): JSX.Element {\n  return <Index page={props.page} />;\n}\n"
  },
  {
    "path": "test_node/3824-incremental/tsconfig-3824.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es2016\",\n    \"jsx\": \"react-jsx\",\n    \"jsxImportSource\": \"jsx-async-runtime\",\n    \"module\": \"NodeNext\",\n    \"outDir\": \"dist\",\n    \"esModuleInterop\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"strict\": true,\n    \"skipLibCheck\": true\n  },\n  \"exclude\": [ \"node_modules\", \"_site\" ]\n}\n"
  },
  {
    "path": "test_node/JsxTest.js",
    "content": "// This test file is using Node’s test runner because `tsx` doesn’t work with worker threads (used by avajs)\n// See https://github.com/privatenumber/tsx/issues/354\n// See https://github.com/nodejs/node/issues/47747\nimport test from \"node:test\";\nimport assert from \"node:assert\";\nimport { renderToStaticMarkup } from \"react-dom/server\";\n\n// Typically import 'tsx/esm'; but we use register method to work with test isolation\n// import { register } from 'tsx/esm/api'\nimport \"tsx/esm\";\n// import 'tsimp';\n\nimport Eleventy from \"../src/Eleventy.js\";\n\ntest(\"Eleventy with JSX\", async () => {\n\tlet elev = new Eleventy(\"./test/stubs-fancyjs/test.11ty.tsx\", undefined, {\n\t\tconfig: (eleventyConfig) => {\n\t\t\televentyConfig.addExtension([\"11ty.jsx\", \"11ty.ts\", \"11ty.tsx\"], {\n\t\t\t\tkey: \"11ty.js\",\n\t\t\t\tcompile: function () {\n\t\t\t\t\treturn async function (data) {\n\t\t\t\t\t\tlet content = await this.defaultRenderer(data);\n\t\t\t\t\t\treturn renderToStaticMarkup(content);\n\t\t\t\t\t};\n\t\t\t\t},\n\t\t\t});\n\t\t},\n\t});\n\telev.setFormats(\"11ty.tsx\");\n\n\tlet results = await elev.toJSON();\n\tassert.strictEqual(results.length, 1);\n\n\tassert.strictEqual(results[0].content, `<div>hello world 1</div>`);\n});\n\ntest(\"Eleventy no formats\", async () => {\n\tlet elev = new Eleventy(\"./test/stubs-fancyjs/\", undefined, {\n\t\tconfig: (eleventyConfig) => {\n\t\t\televentyConfig.addExtension([\"11ty.jsx\", \"11ty.ts\", \"11ty.tsx\"], {\n\t\t\t\tkey: \"11ty.js\",\n\t\t\t\tcompile: function () {\n\t\t\t\t\treturn async function (data) {\n\t\t\t\t\t\tlet content = await this.defaultRenderer(data);\n\t\t\t\t\t\treturn renderToStaticMarkup(content);\n\t\t\t\t\t};\n\t\t\t\t},\n\t\t\t});\n\t\t},\n\t});\n\t// elev.setFormats(\"\")\n\n\tlet results = await elev.toJSON();\n\tassert.strictEqual(results.length, 0);\n});\n\ntest(\"Eleventy JSX --formats=11ty.tsx\", async () => {\n\tlet elev = new Eleventy(\"./test/stubs-fancyjs/\", undefined, {\n\t\tconfig: (eleventyConfig) => {\n\t\t\televentyConfig.addExtension([\"11ty.jsx\", \"11ty.ts\", \"11ty.tsx\"], {\n\t\t\t\tkey: \"11ty.js\",\n\t\t\t\tcompile: function () {\n\t\t\t\t\treturn async function (data) {\n\t\t\t\t\t\tlet content = await this.defaultRenderer(data);\n\t\t\t\t\t\treturn renderToStaticMarkup(content);\n\t\t\t\t\t};\n\t\t\t\t},\n\t\t\t});\n\t\t},\n\t});\n\telev.setFormats(\"11ty.tsx\");\n\n\tlet results = await elev.toJSON();\n\tassert.strictEqual(results.length, 1);\n\n\tassert.strictEqual(results[0].content, `<div>hello world 1</div>`);\n});\n\ntest(\"Eleventy JSX --formats=tsx\", async () => {\n\tlet elev = new Eleventy(\"./test/stubs-fancyjs/\", undefined, {\n\t\tconfig: (eleventyConfig) => {\n\t\t\televentyConfig.addExtension([\"11ty.jsx\", \"11ty.ts\", \"11ty.tsx\"], {\n\t\t\t\tkey: \"11ty.js\",\n\t\t\t\tcompile: function () {\n\t\t\t\t\treturn async function (data) {\n\t\t\t\t\t\tlet content = await this.defaultRenderer(data);\n\t\t\t\t\t\treturn renderToStaticMarkup(content);\n\t\t\t\t\t};\n\t\t\t\t},\n\t\t\t});\n\t\t},\n\t});\n\telev.setFormats(\"tsx\"); // should not pick up 11ty.tsx\n\n\tlet results = await elev.toJSON();\n\tassert.strictEqual(results.length, 0); // Should have no results!!\n});\n"
  },
  {
    "path": "test_node/MdxTest.js",
    "content": "// This test file is using Node’s test runner because `tsx` doesn’t work with worker threads (used by avajs)\n// See https://github.com/privatenumber/tsx/issues/354\n// See https://github.com/nodejs/node/issues/47747\nimport test from \"node:test\";\nimport assert from \"node:assert\";\nimport module from \"node:module\";\nimport { renderToStaticMarkup } from \"react-dom/server\";\n\nimport Eleventy from \"../src/Eleventy.js\";\n\nif (\"register\" in module) {\n\tmodule.register(\"@mdx-js/node-loader\", import.meta.url);\n}\n\ntest(\"Eleventy with MDX\", async () => {\n\tlet elev = new Eleventy(\"./test/stubs-fancyjs/test.mdx\", undefined, {\n\t\tconfig: (eleventyConfig) => {\n\t\t\televentyConfig.addExtension(\"mdx\", {\n\t\t\t\tkey: \"11ty.js\",\n\t\t\t\tcompile: () => {\n\t\t\t\t\treturn async function (data) {\n\t\t\t\t\t\tlet content = await this.defaultRenderer(data);\n\t\t\t\t\t\treturn renderToStaticMarkup(content);\n\t\t\t\t\t};\n\t\t\t\t},\n\t\t\t});\n\t\t},\n\t});\n\telev.disableLogger();\n\telev.setFormats(\"mdx\");\n\n\tlet results = await elev.toJSON();\n\tassert.strictEqual(results.length, 1);\n\n\tassert.strictEqual(results[0].content, `<h1>Hello, World!!!!</h1>`);\n});\n"
  },
  {
    "path": "test_node/README.md",
    "content": "# test_node Unit Tests\n\nThis folder is for tests using the [official Node Test Runner](https://nodejs.org/api/test.html). It was originally introduced to workaround issues with `tsx` and `@mdx-js/node-loader` using worker threads (not supported by the existing test runner, [ava](https://github.com/avajs/ava)). We’re using this instead of `--no-worker-threads` with a separate `ava` run.\n"
  },
  {
    "path": "test_node/tests.js",
    "content": "import \"./JsxTest.js\";\nimport \"./MdxTest.js\";\nimport \"./3824/3824-test.js\";\nimport \"./3824-incremental/3824-incremental-test.js\";\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n\t\"include\": [\n\t\t// \"src/Eleventy.js\",\n\t\t\"src/UserConfig.js\",\n\t\t\"src/Util/ConsoleLogger.js\",\n\t],\n\t\"exclude\": [],\n\n\t\"compilerOptions\": {\n\t\t/* Visit https://aka.ms/tsconfig to read more about this file */\n\n\t\t/* Projects */\n\t\t// \"incremental\": true,                              /* Save .tsbuildinfo files to allow for incremental compilation of projects. */\n\t\t// \"composite\": true,                                /* Enable constraints that allow a TypeScript project to be used with project references. */\n\t\t// \"tsBuildInfoFile\": \"./.tsbuildinfo\",              /* Specify the path to .tsbuildinfo incremental compilation file. */\n\t\t// \"disableSourceOfProjectReferenceRedirect\": true,  /* Disable preferring source files instead of declaration files when referencing composite projects. */\n\t\t// \"disableSolutionSearching\": true,                 /* Opt a project out of multi-project reference checking when editing. */\n\t\t// \"disableReferencedProjectLoad\": true,             /* Reduce the number of projects loaded automatically by TypeScript. */\n\n\t\t/* Language and Environment */\n\t\t\"target\": \"ES2021\",                                  /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */\n\t\t\"lib\": [\"ES2021\"],                                   /* Specify a set of bundled library declaration files that describe the target runtime environment. */\n\t\t// \"jsx\": \"preserve\",                                /* Specify what JSX code is generated. */\n\t\t// \"experimentalDecorators\": true,                   /* Enable experimental support for legacy experimental decorators. */\n\t\t// \"emitDecoratorMetadata\": true,                    /* Emit design-type metadata for decorated declarations in source files. */\n\t\t// \"jsxFactory\": \"\",                                 /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */\n\t\t// \"jsxFragmentFactory\": \"\",                         /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */\n\t\t// \"jsxImportSource\": \"\",                            /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */\n\t\t// \"reactNamespace\": \"\",                             /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */\n\t\t// \"noLib\": true,                                    /* Disable including any library files, including the default lib.d.ts. */\n\t\t// \"useDefineForClassFields\": true,                  /* Emit ECMAScript-standard-compliant class fields. */\n\t\t// \"moduleDetection\": \"auto\",                        /* Control what method is used to detect module-format JS files. */\n\n\t\t/* Modules */\n\t\t\"module\": \"Node16\",                                  /* Specify what module code is generated. */\n\t\t// \"rootDir\": \"./src/\",                              /* Specify the root folder within your source files. */\n\t\t\"moduleResolution\": \"Node16\",                        /* Specify how TypeScript looks up a file from a given module specifier. */\n\t\t// \"baseUrl\": \"./\",                                  /* Specify the base directory to resolve non-relative module names. */\n\t\t// \"paths\": {},                                      /* Specify a set of entries that re-map imports to additional lookup locations. */\n\t\t// \"rootDirs\": [],                                   /* Allow multiple folders to be treated as one when resolving modules. */\n\t\t\"types\": [\"node\"],                                   /* Specify type package names to be included without being referenced in a source file. */\n\t\t// \"typeRoots\": [],                                  /* Specify multiple folders that act like './node_modules/@types'. */\n\t\t// \"allowUmdGlobalAccess\": true,                     /* Allow accessing UMD globals from modules. */\n\t\t// \"moduleSuffixes\": [],                             /* List of file name suffixes to search when resolving a module. */\n\t\t// \"allowImportingTsExtensions\": true,               /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */\n\t\t// \"resolvePackageJsonExports\": true,                /* Use the package.json 'exports' field when resolving package imports. */\n\t\t// \"resolvePackageJsonImports\": true,                /* Use the package.json 'imports' field when resolving imports. */\n\t\t// \"customConditions\": [],                           /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */\n\t\t// \"resolveJsonModule\": true,                        /* Enable importing .json files. */\n\t\t// \"allowArbitraryExtensions\": true,                 /* Enable importing files with any extension, provided a declaration file is present. */\n\n\t\t// WARNING: this causes missing `node` types even with \"types\": [\"node\"]\n\t\t// \"noResolve\": true,                                   /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */\n\n\t\t/* JavaScript Support */\n\t\t\"allowJs\": true,                                  /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */\n\t\t\"checkJs\": true,                                  /* Enable error reporting in type-checked JavaScript files. */\n\t\t\"maxNodeModuleJsDepth\": 0,                        /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */\n\n\t\t/* Emit */\n\t\t// \"declaration\": true,                              /* Generate .d.ts files from TypeScript and JavaScript files in your project. */\n\t\t// \"declarationMap\": true,                           /* Create sourcemaps for d.ts files. */\n\t\t// \"emitDeclarationOnly\": true,                      /* Only output d.ts files and not JavaScript files. */\n\t\t// \"sourceMap\": true,                                /* Create source map files for emitted JavaScript files. */\n\t\t// \"inlineSourceMap\": true,                          /* Include sourcemap files inside the emitted JavaScript. */\n\t\t// \"outFile\": \"./\",                                  /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */\n\t\t// \"outDir\": \"./types/\",                                   /* Specify an output folder for all emitted files. */\n\t\t// \"removeComments\": true,                           /* Disable emitting comments. */\n\t\t\"noEmit\": true,                                   /* Disable emitting files from a compilation. */\n\t\t// \"importHelpers\": true,                            /* Allow importing helper functions from tslib once per project, instead of including them per-file. */\n\t\t// \"downlevelIteration\": true,                       /* Emit more compliant, but verbose and less performant JavaScript for iteration. */\n\t\t// \"sourceRoot\": \"\",                                 /* Specify the root path for debuggers to find the reference source code. */\n\t\t// \"mapRoot\": \"\",                                    /* Specify the location where debugger should locate map files instead of generated locations. */\n\t\t// \"inlineSources\": true,                            /* Include source code in the sourcemaps inside the emitted JavaScript. */\n\t\t// \"emitBOM\": true,                                  /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */\n\t\t// \"newLine\": \"crlf\",                                /* Set the newline character for emitting files. */\n\t\t// \"stripInternal\": true,                            /* Disable emitting declarations that have '@internal' in their JSDoc comments. */\n\t\t// \"noEmitHelpers\": true,                            /* Disable generating custom helper functions like '__extends' in compiled output. */\n\t\t// \"noEmitOnError\": true,                            /* Disable emitting files if any type checking errors are reported. */\n\t\t// \"preserveConstEnums\": true,                       /* Disable erasing 'const enum' declarations in generated code. */\n\t\t// \"declarationDir\": \"./types/\",                           /* Specify the output directory for generated declaration files. */\n\n\t\t/* Interop Constraints */\n\t\t// \"isolatedModules\": true,                          /* Ensure that each file can be safely transpiled without relying on other imports. */\n\t\t// \"verbatimModuleSyntax\": true,                     /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */\n\t\t// \"isolatedDeclarations\": true,                     /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */\n\t\t// \"allowSyntheticDefaultImports\": true,             /* Allow 'import x from y' when a module doesn't have a default export. */\n\t\t// \"esModuleInterop\": true,                             /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */\n\t\t// \"preserveSymlinks\": true,                         /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */\n\t\t\"forceConsistentCasingInFileNames\": true,            /* Ensure that casing is correct in imports. */\n\n\t\t/* Type Checking */\n\t\t\"strict\": true,                                      /* Enable all strict type-checking options. */\n\t\t\"noImplicitAny\": false,                            /* Enable error reporting for expressions and declarations with an implied 'any' type. */\n\t\t// \"strictNullChecks\": true,                         /* When type checking, take into account 'null' and 'undefined'. */\n\t\t// \"strictFunctionTypes\": true,                      /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */\n\t\t// \"strictBindCallApply\": true,                      /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */\n\t\t// \"strictPropertyInitialization\": true,             /* Check for class properties that are declared but not set in the constructor. */\n\t\t// \"noImplicitThis\": true,                           /* Enable error reporting when 'this' is given the type 'any'. */\n\t\t// \"useUnknownInCatchVariables\": true,               /* Default catch clause variables as 'unknown' instead of 'any'. */\n\t\t// \"alwaysStrict\": true,                             /* Ensure 'use strict' is always emitted. */\n\t\t// \"noUnusedLocals\": true,                           /* Enable error reporting when local variables aren't read. */\n\t\t// \"noUnusedParameters\": true,                       /* Raise an error when a function parameter isn't read. */\n\t\t// \"exactOptionalPropertyTypes\": true,               /* Interpret optional property types as written, rather than adding 'undefined'. */\n\t\t// \"noImplicitReturns\": true,                        /* Enable error reporting for codepaths that do not explicitly return in a function. */\n\t\t// \"noFallthroughCasesInSwitch\": true,               /* Enable error reporting for fallthrough cases in switch statements. */\n\t\t// \"noUncheckedIndexedAccess\": true,                 /* Add 'undefined' to a type when accessed using an index. */\n\t\t// \"noImplicitOverride\": true,                       /* Ensure overriding members in derived classes are marked with an override modifier. */\n\t\t// \"noPropertyAccessFromIndexSignature\": true,       /* Enforces using indexed accessors for keys declared using an indexed type. */\n\t\t// \"allowUnusedLabels\": true,                        /* Disable error reporting for unused labels. */\n\t\t// \"allowUnreachableCode\": true,                     /* Disable error reporting for unreachable code. */\n\n\t\t/* Completeness */\n\t\t// \"skipDefaultLibCheck\": true,                      /* Skip type checking .d.ts files that are included with TypeScript. */\n\t\t// \"skipLibCheck\": true                                 /* Skip type checking all .d.ts files. */\n\t}\n}\n"
  }
]