diff --git a/.github/dependabot.yml b/.github/dependabot.yml index c07af46cf..f4a4264ab 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -12,5 +12,5 @@ updates: labels: - "dependency-updates" versioning-strategy: increase - ignore: - dependency-type: "development" + allow: + - dependency-type: "production" diff --git a/docs/coverage.md b/docs/coverage.md index 21886af26..4d61f7082 100644 --- a/docs/coverage.md +++ b/docs/coverage.md @@ -1,52 +1,52 @@ -# Code Coverage for Eleventy v3.1.1 +# Code Coverage for Eleventy v3.1.2 | Filename | % Lines | % Statements | % Functions | % Branches | | ---------------------------------------------------------- | ------- | ------------ | ----------- | ---------- | -| `total` | 89.09% | 89.09% | 89.46% | 89.49% | -| `cmd.cjs` | 71.61% | 71.61% | 25% | 69.56% | -| `src/Eleventy.js` | 76.64% | 76.64% | 73.33% | 89.79% | +| `total` | 90.67% | 90.67% | 90.51% | 89.18% | +| `cmd.cjs` | 71.61% | 71.61% | 25% | 68.18% | +| `src/Eleventy.js` | 91.11% | 91.11% | 87.09% | 84.15% | | `src/EleventyExtensionMap.js` | 97.18% | 97.18% | 96.15% | 94.31% | -| `src/EleventyFiles.js` | 92.8% | 92.8% | 93.47% | 90.38% | -| `src/EleventyServe.js` | 50% | 50% | 59.09% | 58.06% | -| `src/EleventyWatch.js` | 93.12% | 93.12% | 94.44% | 91.42% | -| `src/EleventyWatchTargets.js` | 79.26% | 79.26% | 80% | 100% | +| `src/EleventyFiles.js` | 95.39% | 95.39% | 95.74% | 90.99% | +| `src/EleventyServe.js` | 52.33% | 52.33% | 69.56% | 58.33% | +| `src/EleventyWatch.js` | 93.12% | 93.12% | 94.44% | 91.66% | +| `src/EleventyWatchTargets.js` | 93.29% | 93.29% | 90% | 90.47% | | `src/EventBus.js` | 100% | 100% | 100% | 100% | | `src/FileSystemSearch.js` | 100% | 100% | 100% | 100% | -| `src/GlobalDependencyMap.js` | 78.83% | 78.83% | 80% | 90.62% | -| `src/LayoutCache.js` | 92.85% | 92.85% | 100% | 89.47% | -| `src/Template.js` | 94.89% | 94.89% | 93.75% | 90.55% | +| `src/GlobalDependencyMap.js` | 81.85% | 81.85% | 85% | 88.23% | +| `src/LayoutCache.js` | 77.55% | 77.55% | 87.5% | 87.5% | +| `src/Template.js` | 94.91% | 94.91% | 93.84% | 90.73% | | `src/TemplateBehavior.js` | 90.58% | 90.58% | 100% | 84.21% | | `src/TemplateCollection.js` | 94.8% | 94.8% | 87.5% | 95.23% | -| `src/TemplateConfig.js` | 91.59% | 91.59% | 81.25% | 92.52% | -| `src/TemplateContent.js` | 90.24% | 90.24% | 91.83% | 86.95% | +| `src/TemplateConfig.js` | 91.85% | 91.85% | 82.35% | 92.66% | +| `src/TemplateContent.js` | 90.24% | 90.24% | 91.83% | 87.02% | | `src/TemplateFileSlug.js` | 100% | 100% | 100% | 100% | | `src/TemplateGlob.js` | 94.28% | 94.28% | 100% | 91.66% | -| `src/TemplateLayout.js` | 94.16% | 94.16% | 94.44% | 89.18% | +| `src/TemplateLayout.js` | 90.83% | 90.83% | 83.33% | 88.57% | | `src/TemplateLayoutPathResolver.js` | 88.97% | 88.97% | 75% | 90.9% | -| `src/TemplateMap.js` | 95.15% | 95.15% | 94.87% | 94.62% | -| `src/TemplatePassthrough.js` | 92.68% | 92.68% | 100% | 90% | +| `src/TemplateMap.js` | 95.17% | 95.17% | 94.87% | 94.65% | +| `src/TemplatePassthrough.js` | 92.8% | 92.8% | 100% | 90% | | `src/TemplatePassthroughManager.js` | 86.95% | 86.95% | 96.29% | 83.52% | -| `src/TemplatePermalink.js` | 87.81% | 87.81% | 91.66% | 92.95% | -| `src/TemplateRender.js` | 90.1% | 90.1% | 100% | 91.5% | -| `src/TemplateWriter.js` | 84.29% | 84.29% | 83.33% | 74.69% | +| `src/TemplatePermalink.js` | 87.69% | 87.69% | 91.66% | 92.95% | +| `src/TemplateRender.js` | 90.06% | 90.06% | 100% | 91.5% | +| `src/TemplateWriter.js` | 84.64% | 84.64% | 83.33% | 74.69% | | `src/UserConfig.js` | 90.96% | 90.96% | 78.57% | 87.36% | | `src/defaultConfig.js` | 96.06% | 96.06% | 100% | 66.66% | | `src/Benchmark/Benchmark.js` | 98.18% | 98.18% | 100% | 92.3% | -| `src/Benchmark/BenchmarkGroup.js` | 88.14% | 88.14% | 72.72% | 94.73% | +| `src/Benchmark/BenchmarkGroup.js` | 92.59% | 92.59% | 81.81% | 95.45% | | `src/Benchmark/BenchmarkManager.js` | 90.41% | 90.41% | 77.77% | 87.5% | | `src/Data/ComputedData.js` | 100% | 100% | 100% | 100% | | `src/Data/ComputedDataProxy.js` | 97.7% | 97.7% | 100% | 94.44% | | `src/Data/ComputedDataQueue.js` | 100% | 100% | 100% | 100% | | `src/Data/ComputedDataTemplateString.js` | 95.71% | 95.71% | 100% | 85.71% | -| `src/Data/TemplateData.js` | 93.07% | 93.07% | 94.11% | 88.05% | +| `src/Data/TemplateData.js` | 93.09% | 93.09% | 94.11% | 88.12% | | `src/Data/TemplateDataInitialGlobalData.js` | 95% | 95% | 100% | 83.33% | -| `src/Engines/Custom.js` | 88.72% | 88.72% | 100% | 87.23% | +| `src/Engines/Custom.js` | 88.2% | 88.2% | 100% | 86.59% | | `src/Engines/Html.js` | 100% | 100% | 100% | 100% | -| `src/Engines/JavaScript.js` | 76.89% | 76.89% | 93.33% | 87.27% | -| `src/Engines/Liquid.js` | 99.08% | 99.08% | 100% | 95.89% | -| `src/Engines/Markdown.js` | 95.91% | 95.91% | 83.33% | 92.3% | -| `src/Engines/Nunjucks.js` | 92.29% | 92.29% | 100% | 87.5% | -| `src/Engines/TemplateEngine.js` | 89.16% | 89.16% | 83.33% | 92.5% | +| `src/Engines/JavaScript.js` | 81.25% | 81.25% | 93.75% | 86.44% | +| `src/Engines/Liquid.js` | 99.09% | 99.09% | 100% | 95.94% | +| `src/Engines/Markdown.js` | 96% | 96% | 85.71% | 92.59% | +| `src/Engines/Nunjucks.js` | 92.32% | 92.32% | 100% | 87.5% | +| `src/Engines/TemplateEngine.js` | 89.32% | 89.32% | 83.87% | 92.68% | | `src/Engines/TemplateEngineManager.js` | 93.78% | 93.78% | 100% | 92.64% | | `src/Engines/FrontMatter/JavaScript.js` | 100% | 100% | 100% | 100% | | `src/Engines/Util/ContextAugmenter.js` | 91.04% | 91.04% | 50% | 88.23% | @@ -71,36 +71,37 @@ | `src/Plugins/Pagination.js` | 90.23% | 90.23% | 95% | 84% | | `src/Plugins/RenderPlugin.js` | 87.69% | 87.69% | 86.36% | 77.77% | | `src/Util/ArrayUtil.js` | 100% | 100% | 100% | 100% | -| `src/Util/AsyncEventEmitter.js` | 93.18% | 93.18% | 100% | 84.21% | -| `src/Util/Compatibility.js` | 86% | 86% | 85.71% | 77.77% | -| `src/Util/ConsoleLogger.js` | 100% | 100% | 94.73% | 100% | +| `src/Util/AsyncEventEmitter.js` | 95.45% | 95.45% | 100% | 89.47% | +| `src/Util/Compatibility.js` | 79.66% | 79.66% | 75% | 77.77% | +| `src/Util/ConsoleLogger.js` | 100% | 100% | 95% | 100% | | `src/Util/DateGitFirstAdded.js` | 100% | 100% | 100% | 100% | | `src/Util/DateGitLastUpdated.js` | 100% | 100% | 100% | 100% | | `src/Util/DirContains.js` | 100% | 100% | 100% | 100% | -| `src/Util/EsmResolver.js` | 82.35% | 82.35% | 100% | 84.61% | -| `src/Util/ExistsCache.js` | 96.29% | 96.29% | 83.33% | 100% | +| `src/Util/EsmResolver.js` | 84.9% | 84.9% | 100% | 85.71% | +| `src/Util/ExistsCache.js` | 96.77% | 96.77% | 85.71% | 100% | | `src/Util/FilePathUtil.js` | 47.36% | 47.36% | 50% | 100% | | `src/Util/FileSystemManager.js` | 72.91% | 72.91% | 66.66% | 87.5% | | `src/Util/GetJavaScriptData.js` | 100% | 100% | 100% | 100% | | `src/Util/GlobMatcher.js` | 90.9% | 90.9% | 100% | 66.66% | +| `src/Util/GlobRemap.js` | 97.64% | 97.64% | 90% | 100% | | `src/Util/HtmlRelativeCopy.js` | 90.6% | 90.6% | 100% | 89.18% | | `src/Util/HtmlTransformer.js` | 90.11% | 90.11% | 88.88% | 90.69% | -| `src/Util/ImportJsonSync.js` | 82.85% | 82.85% | 80% | 91.66% | +| `src/Util/ImportJsonSync.js` | 84.41% | 84.41% | 83.33% | 92.3% | | `src/Util/IsAsyncFunction.js` | 100% | 100% | 50% | 100% | | `src/Util/JavaScriptDependencies.js` | 89.09% | 89.09% | 50% | 85.71% | | `src/Util/MemoizeFunction.js` | 100% | 100% | 100% | 100% | | `src/Util/PassthroughCopyBehaviorCheck.js` | 100% | 100% | 100% | 100% | -| `src/Util/PathNormalizer.js` | 93.33% | 93.33% | 100% | 86.66% | +| `src/Util/PathNormalizer.js` | 93.1% | 93.1% | 100% | 86.66% | | `src/Util/PathPrefixer.js` | 100% | 100% | 100% | 100% | | `src/Util/Pluralize.js` | 100% | 100% | 100% | 100% | -| `src/Util/ProjectDirectories.js` | 96.67% | 96.67% | 97.22% | 96.11% | +| `src/Util/ProjectDirectories.js` | 96.74% | 96.74% | 97.29% | 96.11% | | `src/Util/ProjectTemplateFormats.js` | 94.02% | 94.02% | 90% | 94.73% | | `src/Util/PromiseUtil.js` | 46.66% | 46.66% | 100% | 66.66% | -| `src/Util/Require.js` | 77.9% | 77.9% | 75% | 86.11% | +| `src/Util/Require.js` | 82.94% | 82.94% | 75% | 82.05% | | `src/Util/ReservedData.js` | 97.1% | 97.1% | 100% | 92.85% | | `src/Util/SetUnion.js` | 100% | 100% | 100% | 100% | -| `src/Util/SpawnAsync.js` | 96% | 96% | 100% | 83.33% | -| `src/Util/TemplateDepGraph.js` | 96.12% | 96.12% | 100% | 93.02% | +| `src/Util/SpawnAsync.js` | 96.55% | 96.55% | 100% | 87.5% | +| `src/Util/TemplateDepGraph.js` | 96.25% | 96.25% | 100% | 93.61% | | `src/Util/TransformsUtil.js` | 94.28% | 94.28% | 100% | 83.33% | | `src/Util/ValidUrl.js` | 100% | 100% | 100% | 100% | | `src/Util/Objects/DeepFreeze.js` | 90% | 90% | 100% | 80% | diff --git a/docs/release-instructions.md b/docs/release-instructions.md index 937f3cf17..979f64f53 100644 --- a/docs/release-instructions.md +++ b/docs/release-instructions.md @@ -5,7 +5,7 @@ # Release Procedure 1. (Optional) Update minor dependencies in package.json - - `npx npm-check-updates --target minor -u` + - `npx npm-check-updates --target minor -u --dep prod` - or `npm outdated` + `npm update --save` 1. Stable release only: make sure there aren’t any `@11ty/*` dependencies on pre-release versions alpha/beta/canary 1. If the minimum Node version changed, make sure you update `package.json` engines property. diff --git a/package-lock.json b/package-lock.json index cd79a18a5..24bdf0169 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@11ty/eleventy", - "version": "3.1.1", + "version": "3.1.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@11ty/eleventy", - "version": "3.1.1", + "version": "3.1.2", "license": "MIT", "dependencies": { "@11ty/dependency-tree": "^4.0.0", @@ -16,13 +16,13 @@ "@11ty/eleventy-utils": "^2.0.7", "@11ty/lodash-custom": "^4.17.21", "@11ty/posthtml-urls": "^1.0.1", - "@11ty/recursive-copy": "^4.0.1", + "@11ty/recursive-copy": "^4.0.2", "@sindresorhus/slugify": "^2.2.1", "bcp-47-normalize": "^2.3.0", "chokidar": "^3.6.0", "debug": "^4.4.1", "dependency-graph": "^1.0.0", - "entities": "^6.0.0", + "entities": "^6.0.1", "filesize": "^10.1.6", "gray-matter": "^4.0.3", "iso-639-1": "^3.1.5", @@ -52,17 +52,18 @@ "@11ty/eleventy-plugin-syntaxhighlight": "^5.0.1", "@11ty/eleventy-plugin-webc": "^0.12.0-beta.3", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "^9.28.0", + "@eslint/js": "^9.29.0", "@iarna/toml": "^2.2.5", "@mdx-js/node-loader": "^3.1.0", - "@types/node": "^22.15.29", - "@vue/server-renderer": "^3.5.16", - "@zachleat/noop": "^1.0.4", - "ava": "^6.3.0", + "@types/node": "^22.15.32", + "@vue/server-renderer": "^3.5.17", + "@zachleat/noop": "^1.0.6", + "ava": "^6.4.0", "c8": "^10.1.3", - "eslint": "^9.28.0", + "eslint": "^9.29.0", "eslint-config-prettier": "^10.1.5", "globals": "^16.2.0", + "jsx-async-runtime": "^1.0.2", "markdown-it-abbr": "^2.0.0", "markdown-it-emoji": "^3.0.0", "marked": "^15.0.12", @@ -72,13 +73,13 @@ "react": "^19.1.0", "react-dom": "^19.1.0", "rimraf": "^6.0.1", - "sass": "^1.89.1", + "sass": "^1.89.2", "simple-git-hooks": "^2.13.0", - "tsx": "^4.19.4", + "tsx": "^4.20.3", "typescript": "^5.8.3", - "vue": "^3.5.16", - "zod": "^3.25.49", - "zod-validation-error": "^3.4.1" + "vue": "^3.5.17", + "zod": "^3.25.67", + "zod-validation-error": "^3.5.2" }, "engines": { "node": ">=18" @@ -292,9 +293,9 @@ } }, "node_modules/@11ty/recursive-copy": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@11ty/recursive-copy/-/recursive-copy-4.0.1.tgz", - "integrity": "sha512-Zsg1xgfdVTMKNPj9o4FZeYa73dFZRX856CL4LsmqPMvDr0TuIK4cH9CVWJyf0OkNmM8GmlibGX18fF0B75Rn1w==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@11ty/recursive-copy/-/recursive-copy-4.0.2.tgz", + "integrity": "sha512-174nFXxL/6KcYbLYpra+q3nDbfKxLxRTNVY1atq2M1pYYiPfHse++3IFNl8mjPFsd7y2qQjxLORzIjHMjL3NDQ==", "license": "ISC", "dependencies": { "errno": "^1.0.0", @@ -422,9 +423,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.27.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.4.tgz", - "integrity": "sha512-BRmLHGwpUqLFR2jzx9orBuX/ABDkj2jLKOXrHDTN2aOKL+jFDDKaRNo9nyYsIl9h/UE/7lMKdDjKQQyxKKDZ7g==", + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.5.tgz", + "integrity": "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==", "dev": true, "license": "MIT", "dependencies": { @@ -438,9 +439,9 @@ } }, "node_modules/@babel/types": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.3.tgz", - "integrity": "sha512-Y1GkI4ktrtvmawoSq+4FCVHNryea6uR+qUQy0AGxLSsjCX0nVmkYQMBLHDkXZuo5hGx7eYdnIaslsdBFm7zbUw==", + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.6.tgz", + "integrity": "sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==", "dev": true, "license": "MIT", "dependencies": { @@ -940,9 +941,9 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz", - "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.1.tgz", + "integrity": "sha512-OL0RJzC/CBzli0DrrR31qzj6d6i6Mm3HByuhflhl4LOBiWxN+3i6/t/ZQQNii4tjksXi8r2CRW1wMpWA2ULUEw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -1015,9 +1016,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.28.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.28.0.tgz", - "integrity": "sha512-fnqSjGWd/CoIp4EXIxWVK/sHA6DOHN4+8Ix2cX5ycOY7LG0UY8nHCU5pIp2eaE1Mc7Qd8kHspYNzYXT2ojPLzg==", + "version": "9.29.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.29.0.tgz", + "integrity": "sha512-3PIF4cBw/y+1u2EazflInpV+lYsSG0aByVIQzAgb1m1MhHFSbqTyNqtBKHgWf/9Ykud+DhILS9EGkmekVhbKoQ==", "dev": true, "license": "MIT", "engines": { @@ -2242,9 +2243,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.15.29", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.29.tgz", - "integrity": "sha512-LNdjOkUDlU1RZb8e1kOIUpN1qQUlzGkEtbVNo53vbrwDg5om6oduhm4SiUaPW5ASTXhAiP0jInWG8Qx9fVlOeQ==", + "version": "22.15.32", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.32.tgz", + "integrity": "sha512-3jigKqgSjsH6gYZv2nEsqdXfZqIFGAV36XYYjf9KGZ3PSG+IhLecqPnI310RvjutyMwifE2hhhNEklOUrvx/wA==", "dev": true, "license": "MIT", "dependencies": { @@ -2273,9 +2274,9 @@ "license": "ISC" }, "node_modules/@vercel/nft": { - "version": "0.29.3", - "resolved": "https://registry.npmjs.org/@vercel/nft/-/nft-0.29.3.tgz", - "integrity": "sha512-aVV0E6vJpuvImiMwU1/5QKkw2N96BRFE7mBYGS7FhXUoS6V7SarQ+8tuj33o7ofECz8JtHpmQ9JW+oVzOoB7MA==", + "version": "0.29.4", + "resolved": "https://registry.npmjs.org/@vercel/nft/-/nft-0.29.4.tgz", + "integrity": "sha512-6lLqMNX3TuycBPABycx7A9F1bHQR7kiQln6abjFbPrf5C/05qHM9M5E4PeTE59c7z8g6vHnx1Ioihb2AQl7BTA==", "dev": true, "license": "MIT", "dependencies": { @@ -2307,14 +2308,14 @@ "license": "MIT" }, "node_modules/@vue/compiler-core": { - "version": "3.5.16", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.16.tgz", - "integrity": "sha512-AOQS2eaQOaaZQoL1u+2rCJIKDruNXVBZSiUD3chnUrsoX5ZTQMaCvXlWNIfxBJuU15r1o7+mpo5223KVtIhAgQ==", + "version": "3.5.17", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.17.tgz", + "integrity": "sha512-Xe+AittLbAyV0pabcN7cP7/BenRBNcteM4aSDCtRvGw0d9OL+HG1u/XHLY/kt1q4fyMeZYXyIYrsHuPSiDPosA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.27.2", - "@vue/shared": "3.5.16", + "@babel/parser": "^7.27.5", + "@vue/shared": "3.5.17", "entities": "^4.5.0", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" @@ -2341,31 +2342,31 @@ "license": "MIT" }, "node_modules/@vue/compiler-dom": { - "version": "3.5.16", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.16.tgz", - "integrity": "sha512-SSJIhBr/teipXiXjmWOVWLnxjNGo65Oj/8wTEQz0nqwQeP75jWZ0n4sF24Zxoht1cuJoWopwj0J0exYwCJ0dCQ==", + "version": "3.5.17", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.17.tgz", + "integrity": "sha512-+2UgfLKoaNLhgfhV5Ihnk6wB4ljyW1/7wUIog2puUqajiC29Lp5R/IKDdkebh9jTbTogTbsgB+OY9cEWzG95JQ==", "dev": true, "license": "MIT", "dependencies": { - "@vue/compiler-core": "3.5.16", - "@vue/shared": "3.5.16" + "@vue/compiler-core": "3.5.17", + "@vue/shared": "3.5.17" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.5.16", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.16.tgz", - "integrity": "sha512-rQR6VSFNpiinDy/DVUE0vHoIDUF++6p910cgcZoaAUm3POxgNOOdS/xgoll3rNdKYTYPnnbARDCZOyZ+QSe6Pw==", + "version": "3.5.17", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.17.tgz", + "integrity": "sha512-rQQxbRJMgTqwRugtjw0cnyQv9cP4/4BxWfTdRBkqsTfLOHWykLzbOc3C4GGzAmdMDxhzU/1Ija5bTjMVrddqww==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.27.2", - "@vue/compiler-core": "3.5.16", - "@vue/compiler-dom": "3.5.16", - "@vue/compiler-ssr": "3.5.16", - "@vue/shared": "3.5.16", + "@babel/parser": "^7.27.5", + "@vue/compiler-core": "3.5.17", + "@vue/compiler-dom": "3.5.17", + "@vue/compiler-ssr": "3.5.17", + "@vue/shared": "3.5.17", "estree-walker": "^2.0.2", "magic-string": "^0.30.17", - "postcss": "^8.5.3", + "postcss": "^8.5.6", "source-map-js": "^1.2.1" } }, @@ -2377,75 +2378,75 @@ "license": "MIT" }, "node_modules/@vue/compiler-ssr": { - "version": "3.5.16", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.16.tgz", - "integrity": "sha512-d2V7kfxbdsjrDSGlJE7my1ZzCXViEcqN6w14DOsDrUCHEA6vbnVCpRFfrc4ryCP/lCKzX2eS1YtnLE/BuC9f/A==", + "version": "3.5.17", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.17.tgz", + "integrity": "sha512-hkDbA0Q20ZzGgpj5uZjb9rBzQtIHLS78mMilwrlpWk2Ep37DYntUz0PonQ6kr113vfOEdM+zTBuJDaceNIW0tQ==", "dev": true, "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.5.16", - "@vue/shared": "3.5.16" + "@vue/compiler-dom": "3.5.17", + "@vue/shared": "3.5.17" } }, "node_modules/@vue/reactivity": { - "version": "3.5.16", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.16.tgz", - "integrity": "sha512-FG5Q5ee/kxhIm1p2bykPpPwqiUBV3kFySsHEQha5BJvjXdZTUfmya7wP7zC39dFuZAcf/PD5S4Lni55vGLMhvA==", + "version": "3.5.17", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.17.tgz", + "integrity": "sha512-l/rmw2STIscWi7SNJp708FK4Kofs97zc/5aEPQh4bOsReD/8ICuBcEmS7KGwDj5ODQLYWVN2lNibKJL1z5b+Lw==", "dev": true, "license": "MIT", "dependencies": { - "@vue/shared": "3.5.16" + "@vue/shared": "3.5.17" } }, "node_modules/@vue/runtime-core": { - "version": "3.5.16", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.16.tgz", - "integrity": "sha512-bw5Ykq6+JFHYxrQa7Tjr+VSzw7Dj4ldR/udyBZbq73fCdJmyy5MPIFR9IX/M5Qs+TtTjuyUTCnmK3lWWwpAcFQ==", + "version": "3.5.17", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.17.tgz", + "integrity": "sha512-QQLXa20dHg1R0ri4bjKeGFKEkJA7MMBxrKo2G+gJikmumRS7PTD4BOU9FKrDQWMKowz7frJJGqBffYMgQYS96Q==", "dev": true, "license": "MIT", "dependencies": { - "@vue/reactivity": "3.5.16", - "@vue/shared": "3.5.16" + "@vue/reactivity": "3.5.17", + "@vue/shared": "3.5.17" } }, "node_modules/@vue/runtime-dom": { - "version": "3.5.16", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.16.tgz", - "integrity": "sha512-T1qqYJsG2xMGhImRUV9y/RseB9d0eCYZQ4CWca9ztCuiPj/XWNNN+lkNBuzVbia5z4/cgxdL28NoQCvC0Xcfww==", + "version": "3.5.17", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.17.tgz", + "integrity": "sha512-8El0M60TcwZ1QMz4/os2MdlQECgGoVHPuLnQBU3m9h3gdNRW9xRmI8iLS4t/22OQlOE6aJvNNlBiCzPHur4H9g==", "dev": true, "license": "MIT", "dependencies": { - "@vue/reactivity": "3.5.16", - "@vue/runtime-core": "3.5.16", - "@vue/shared": "3.5.16", + "@vue/reactivity": "3.5.17", + "@vue/runtime-core": "3.5.17", + "@vue/shared": "3.5.17", "csstype": "^3.1.3" } }, "node_modules/@vue/server-renderer": { - "version": "3.5.16", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.16.tgz", - "integrity": "sha512-BrX0qLiv/WugguGsnQUJiYOE0Fe5mZTwi6b7X/ybGB0vfrPH9z0gD/Y6WOR1sGCgX4gc25L1RYS5eYQKDMoNIg==", + "version": "3.5.17", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.17.tgz", + "integrity": "sha512-BOHhm8HalujY6lmC3DbqF6uXN/K00uWiEeF22LfEsm9Q93XeJ/plHTepGwf6tqFcF7GA5oGSSAAUock3VvzaCA==", "dev": true, "license": "MIT", "dependencies": { - "@vue/compiler-ssr": "3.5.16", - "@vue/shared": "3.5.16" + "@vue/compiler-ssr": "3.5.17", + "@vue/shared": "3.5.17" }, "peerDependencies": { - "vue": "3.5.16" + "vue": "3.5.17" } }, "node_modules/@vue/shared": { - "version": "3.5.16", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.16.tgz", - "integrity": "sha512-c/0fWy3Jw6Z8L9FmTyYfkpM5zklnqqa9+a6dz3DvONRKW2NEbh46BP0FHuLFSWi2TnQEtp91Z6zOWNrU6QiyPg==", + "version": "3.5.17", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.17.tgz", + "integrity": "sha512-CabR+UN630VnsJO/jHWYBC1YVXyMq94KKp6iF5MQgZJs5I8cmjw6oVMO1oDbtBkENSHSSn/UadWlW/OAgdmKrg==", "dev": true, "license": "MIT" }, "node_modules/@zachleat/noop": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@zachleat/noop/-/noop-1.0.4.tgz", - "integrity": "sha512-oQTAaGctz0g+s4ZokIu/RfD0aCRV7DjIUBzWmVKjMhriHQQwDfQ0ohrCrWOwBki20f+YnZpQYJ+Xl7QJqraPCQ==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@zachleat/noop/-/noop-1.0.6.tgz", + "integrity": "sha512-8l60Jv/YsCVSoNbrfObCdhyUSTEq9LRY/Ig/cuoQVOsSbYjzhOuUPQVQsC9EXsGbu9GG8gi/a5Zf70G5auPnlw==", "dev": true, "license": "MIT" }, @@ -2466,9 +2467,9 @@ } }, "node_modules/acorn": { - "version": "8.14.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", - "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -2680,13 +2681,13 @@ "license": "MIT" }, "node_modules/ava": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/ava/-/ava-6.3.0.tgz", - "integrity": "sha512-64K+xNmlgMo1D94evJlkBWmJ6CGrO6oEctGEjA3PIl5GrwZyMXM5OEycZWnKGduE1YdqMvYDl29SgnNk7kyx+A==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/ava/-/ava-6.4.0.tgz", + "integrity": "sha512-aeFapuBZtaGwVMlFFf074SZJ0bPcdmAdJdsvhHMp+XaOnC2DgeMzopb7yyYAhulNGRJQfUK/SIBYo2PoX7+gtw==", "dev": true, "license": "MIT", "dependencies": { - "@vercel/nft": "^0.29.2", + "@vercel/nft": "^0.29.4", "acorn": "^8.14.1", "acorn-walk": "^8.3.4", "ansi-styles": "^6.2.1", @@ -2703,7 +2704,7 @@ "common-path-prefix": "^3.0.0", "concordance": "^5.0.4", "currently-unhandled": "^0.4.1", - "debug": "^4.4.0", + "debug": "^4.4.1", "emittery": "^1.1.0", "figures": "^6.1.0", "globby": "^14.1.0", @@ -2731,7 +2732,7 @@ "ava": "entrypoints/cli.mjs" }, "engines": { - "node": "^18.18 || ^20.8 || ^22 || >=23" + "node": "^18.18 || ^20.8 || ^22 || ^23 || >=24" }, "peerDependencies": { "@ava/typescript": "*" @@ -2828,9 +2829,9 @@ "license": "MIT" }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -3613,9 +3614,9 @@ } }, "node_modules/editorconfig/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3684,9 +3685,9 @@ } }, "node_modules/entities": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.0.tgz", - "integrity": "sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", "license": "BSD-2-Clause", "engines": { "node": ">=0.12" @@ -3811,19 +3812,19 @@ } }, "node_modules/eslint": { - "version": "9.28.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.28.0.tgz", - "integrity": "sha512-ocgh41VhRlf9+fVpe7QKzwLj9c92fDiqOj8Y3Sd4/ZmVA4Btx4PlUYPq4pp9JDyupkf1upbEXecxL2mwNV7jPQ==", + "version": "9.29.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.29.0.tgz", + "integrity": "sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.20.0", + "@eslint/config-array": "^0.20.1", "@eslint/config-helpers": "^0.2.1", "@eslint/core": "^0.14.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.28.0", + "@eslint/js": "9.29.0", "@eslint/plugin-kit": "^0.3.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", @@ -3835,9 +3836,9 @@ "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.3.0", - "eslint-visitor-keys": "^4.2.0", - "espree": "^10.3.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -3888,9 +3889,9 @@ } }, "node_modules/eslint-scope": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", - "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -3905,9 +3906,9 @@ } }, "node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -3986,15 +3987,15 @@ } }, "node_modules/espree": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", - "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.14.0", + "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.0" + "eslint-visitor-keys": "^4.2.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4535,9 +4536,9 @@ } }, "node_modules/glob/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -5264,6 +5265,13 @@ "dev": true, "license": "MIT" }, + "node_modules/jsx-async-runtime": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/jsx-async-runtime/-/jsx-async-runtime-1.0.2.tgz", + "integrity": "sha512-KeAahyNZTfu8EvTKN0TEHE1CuNMlq8SIgPlc90WppYxEXzfJCHGe06slPYlH4jQBRvSDACNJ3ScmlfutUFzFXw==", + "dev": true, + "license": "MIT" + }, "node_modules/junk": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/junk/-/junk-3.1.0.tgz", @@ -7058,9 +7066,9 @@ } }, "node_modules/postcss": { - "version": "8.5.4", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.4.tgz", - "integrity": "sha512-QSa9EBe+uwlGTFmHsPKokv3B/oEMQZxfqW0QqNCyhpa6mB1afzulwn8hihglqAb2pOw+BJgNlmXQ8la2VeHB7w==", + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", "dev": true, "funding": [ { @@ -7541,9 +7549,9 @@ } }, "node_modules/rimraf/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -7658,9 +7666,9 @@ } }, "node_modules/sass": { - "version": "1.89.1", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.89.1.tgz", - "integrity": "sha512-eMLLkl+qz7tx/0cJ9wI+w09GQ2zodTkcE/aVfywwdlRcI3EO19xGnbmJwg/JMIm+5MxVJ6outddLZ4Von4E++Q==", + "version": "1.89.2", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.89.2.tgz", + "integrity": "sha512-xCmtksBKd/jdJ9Bt9p7nPKiuqrlBMBuuGkQlkhZjjQk3Ty48lv93k5Dq6OPkKt4XwxDJ7tvlfrTa1MPA9bf+QA==", "dev": true, "license": "MIT", "dependencies": { @@ -8263,9 +8271,9 @@ } }, "node_modules/test-exclude/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -8373,9 +8381,9 @@ "optional": true }, "node_modules/tsx": { - "version": "4.19.4", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.4.tgz", - "integrity": "sha512-gK5GVzDkJK1SI1zwHf32Mqxf2tSJkNx+eYcNly5+nHvWqXUJYUkWBQtKauoESz3ymezAI++ZwT855x5p5eop+Q==", + "version": "4.20.3", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.3.tgz", + "integrity": "sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==", "dev": true, "license": "MIT", "dependencies": { @@ -8718,17 +8726,17 @@ } }, "node_modules/vue": { - "version": "3.5.16", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.16.tgz", - "integrity": "sha512-rjOV2ecxMd5SiAmof2xzh2WxntRcigkX/He4YFJ6WdRvVUrbt6DxC1Iujh10XLl8xCDRDtGKMeO3D+pRQ1PP9w==", + "version": "3.5.17", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.17.tgz", + "integrity": "sha512-LbHV3xPN9BeljML+Xctq4lbz2lVHCR6DtbpTf5XIO6gugpXUN49j2QQPcMj086r9+AkJ0FfUT8xjulKKBkkr9g==", "dev": true, "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.5.16", - "@vue/compiler-sfc": "3.5.16", - "@vue/runtime-dom": "3.5.16", - "@vue/server-renderer": "3.5.16", - "@vue/shared": "3.5.16" + "@vue/compiler-dom": "3.5.17", + "@vue/compiler-sfc": "3.5.17", + "@vue/runtime-dom": "3.5.17", + "@vue/server-renderer": "3.5.17", + "@vue/shared": "3.5.17" }, "peerDependencies": { "typescript": "*" @@ -9036,9 +9044,9 @@ } }, "node_modules/zod": { - "version": "3.25.49", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.49.tgz", - "integrity": "sha512-JMMPMy9ZBk3XFEdbM3iL1brx4NUSejd6xr3ELrrGEfGb355gjhiAWtG3K5o+AViV/3ZfkIrCzXsZn6SbLwTR8Q==", + "version": "3.25.67", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.67.tgz", + "integrity": "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==", "dev": true, "license": "MIT", "funding": { @@ -9046,16 +9054,16 @@ } }, "node_modules/zod-validation-error": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-3.4.1.tgz", - "integrity": "sha512-1KP64yqDPQ3rupxNv7oXhf7KdhHHgaqbKuspVoiN93TT0xrBjql+Svjkdjq/Qh/7GSMmgQs3AfvBT0heE35thw==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-3.5.2.tgz", + "integrity": "sha512-mdi7YOLtram5dzJ5aDtm1AG9+mxRma1iaMrZdYIpFO7epdKBUwLHIxTF8CPDeCQ828zAXYtizrKlEJAtzgfgrw==", "dev": true, "license": "MIT", "engines": { "node": ">=18.0.0" }, "peerDependencies": { - "zod": "^3.24.4" + "zod": "^3.25.0" } }, "node_modules/zwitch": { diff --git a/package.json b/package.json index 85ee804b1..f79a2e756 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@11ty/eleventy", - "version": "3.1.1", + "version": "3.1.2", "description": "A simpler static site generator.", "publishConfig": { "access": "public", @@ -95,17 +95,18 @@ "@11ty/eleventy-plugin-syntaxhighlight": "^5.0.1", "@11ty/eleventy-plugin-webc": "^0.12.0-beta.3", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "^9.28.0", + "@eslint/js": "^9.29.0", "@iarna/toml": "^2.2.5", "@mdx-js/node-loader": "^3.1.0", - "@types/node": "^22.15.29", - "@vue/server-renderer": "^3.5.16", - "@zachleat/noop": "^1.0.4", - "ava": "^6.3.0", + "@types/node": "^22.15.32", + "@vue/server-renderer": "^3.5.17", + "@zachleat/noop": "^1.0.6", + "ava": "^6.4.0", "c8": "^10.1.3", - "eslint": "^9.28.0", + "eslint": "^9.29.0", "eslint-config-prettier": "^10.1.5", "globals": "^16.2.0", + "jsx-async-runtime": "^1.0.2", "markdown-it-abbr": "^2.0.0", "markdown-it-emoji": "^3.0.0", "marked": "^15.0.12", @@ -115,13 +116,13 @@ "react": "^19.1.0", "react-dom": "^19.1.0", "rimraf": "^6.0.1", - "sass": "^1.89.1", + "sass": "^1.89.2", "simple-git-hooks": "^2.13.0", - "tsx": "^4.19.4", + "tsx": "^4.20.3", "typescript": "^5.8.3", - "vue": "^3.5.16", - "zod": "^3.25.49", - "zod-validation-error": "^3.4.1" + "vue": "^3.5.17", + "zod": "^3.25.67", + "zod-validation-error": "^3.5.2" }, "dependencies": { "@11ty/dependency-tree": "^4.0.0", @@ -131,13 +132,13 @@ "@11ty/eleventy-utils": "^2.0.7", "@11ty/lodash-custom": "^4.17.21", "@11ty/posthtml-urls": "^1.0.1", - "@11ty/recursive-copy": "^4.0.1", + "@11ty/recursive-copy": "^4.0.2", "@sindresorhus/slugify": "^2.2.1", "bcp-47-normalize": "^2.3.0", "chokidar": "^3.6.0", "debug": "^4.4.1", "dependency-graph": "^1.0.0", - "entities": "^6.0.0", + "entities": "^6.0.1", "filesize": "^10.1.6", "gray-matter": "^4.0.3", "iso-639-1": "^3.1.5", diff --git a/src/Data/TemplateData.js b/src/Data/TemplateData.js index fbb0d9cc6..6942892c3 100644 --- a/src/Data/TemplateData.js +++ b/src/Data/TemplateData.js @@ -595,6 +595,7 @@ class TemplateData { if (inputDir) { debugDev("dirStr: %o; inputDir: %o", dir, inputDir); } + // TODO use DirContains if (!inputDir || (dir.startsWith(inputDir) && dir !== inputDir)) { if (this.config.dataFileDirBaseNameOverride) { let indexDataFile = dir + "/" + this.config.dataFileDirBaseNameOverride; @@ -657,7 +658,8 @@ class TemplateData { } // Deduplicate tags - return [...new Set(tags)]; + // Coerce to string #3875 + return [...new Set(tags)].map((entry) => String(entry)); } return tags; diff --git a/src/Eleventy.js b/src/Eleventy.js index e51d56332..0568a3ea9 100644 --- a/src/Eleventy.js +++ b/src/Eleventy.js @@ -2,6 +2,7 @@ import chalk from "kleur"; import { performance } from "node:perf_hooks"; import debugUtil from "debug"; import { filesize } from "filesize"; +import path from "node:path"; /* Eleventy Deps */ import { TemplatePath } from "@11ty/eleventy-utils"; @@ -30,7 +31,11 @@ import { isGlobMatch } from "./Util/GlobMatcher.js"; import simplePlural from "./Util/Pluralize.js"; import checkPassthroughCopyBehavior from "./Util/PassthroughCopyBehaviorCheck.js"; import eventBus from "./EventBus.js"; -import { getEleventyPackageJson, getWorkingProjectPackageJson } from "./Util/ImportJsonSync.js"; +import { + getEleventyPackageJson, + importJsonSync, + getWorkingProjectPackageJsonPath, +} from "./Util/ImportJsonSync.js"; import { EleventyImport } from "./Util/Require.js"; import ProjectTemplateFormats from "./Util/ProjectTemplateFormats.js"; import { withResolvers } from "./Util/PromiseUtil.js"; @@ -41,6 +46,7 @@ import I18nPlugin, * as I18nPluginExtras from "./Plugins/I18nPlugin.js"; import HtmlBasePlugin, * as HtmlBasePluginExtras from "./Plugins/HtmlBasePlugin.js"; import { TransformPlugin as InputPathToUrlTransformPlugin } from "./Plugins/InputPathToUrl.js"; import { IdAttributePlugin } from "./Plugins/IdAttributePlugin.js"; +import FileSystemRemap from "./Util/GlobRemap.js"; const pkg = getEleventyPackageJson(); const debug = debugUtil("Eleventy"); @@ -56,6 +62,8 @@ class Eleventy { * @type {object|undefined} */ #projectPackageJson; + /** @type {string} */ + #projectPackageJsonPath; /** @type {ProjectTemplateFormats|undefined} */ #templateFormats; /** @type {ConsoleLogger|undefined} */ @@ -271,8 +279,11 @@ class Eleventy { debug("Eleventy warm up time: %o (ms)", performance.now()); } - /** @type {object} */ - this.eleventyServe = new EleventyServe(); + // Careful to make sure the previous server closes on SIGINT, issue #3873 + if (!this.eleventyServe) { + /** @type {object} */ + this.eleventyServe = new EleventyServe(); + } this.eleventyServe.eleventyConfig = this.eleventyConfig; /** @type {object} */ @@ -287,6 +298,7 @@ class Eleventy { this.#hasConfigInitialized = true; + // after #hasConfigInitialized above this.setIsVerbose(this.#preInitVerbose ?? !this.config.quietMode); } @@ -589,14 +601,12 @@ Verbose Output: ${this.verboseMode}`; let configPath = this.eleventyConfig.getLocalProjectConfigFile(); if (configPath) { - let absolutePathToConfig = TemplatePath.absolutePath(configPath); - values.config = absolutePathToConfig; - - // TODO(zachleat): if config is not in root (e.g. using --config=) - let root = TemplatePath.getDirFromFilePath(absolutePathToConfig); - values.root = root; + values.config = TemplatePath.absolutePath(configPath); } + // Fixed: instead of configuration directory, explicit root or working directory + values.root = TemplatePath.getWorkingDir(); + values.source = this.source; // Backwards compatibility @@ -744,6 +754,8 @@ Verbose Output: ${this.verboseMode}`; this.setIncrementalBuild(true); this.programmaticApiIncrementalFile = TemplatePath.addLeadingDotSlash(incrementalFile); + + this.eleventyConfig.setPreviousBuildModifiedFile(incrementalFile); } } @@ -851,15 +863,11 @@ Arguments: * * @method */ - async resetConfig() { - this.env = this.getEnvironmentVariableValues(); - this.initializeEnvironmentVariables(this.env); - await this.eleventyConfig.reset(); - - this.config = this.eleventyConfig.getConfig(); - this.eleventyServe.eleventyConfig = this.eleventyConfig; + resetConfig() { + delete this.eleventyConfig; - this.setIsVerbose(!this.config.quietMode); + // ensures `initializeConfig()` will run when `init()` is called next + this.#hasConfigInitialized = false; } /** @@ -954,8 +962,7 @@ Arguments: if (isResetConfig) { // important: run this before config resets otherwise the handlers will disappear. await this.config.events.emit("eleventy.reset"); - - await this.resetConfig(); + this.resetConfig(); } await this.restart(); @@ -1056,7 +1063,11 @@ Arguments: this.watchManager = new EleventyWatch(); this.watchManager.incremental = this.isIncremental; - this.watchTargets.add(["./package.json"]); + if (this.projectPackageJsonPath) { + this.watchTargets.add([ + path.relative(TemplatePath.getWorkingDir(), this.projectPackageJsonPath), + ]); + } this.watchTargets.add(this.eleventyFiles.getGlobWatcherFiles()); this.watchTargets.add(this.eleventyFiles.getIgnoreFiles()); @@ -1075,9 +1086,17 @@ Arguments: } // fetch from project’s package.json + get projectPackageJsonPath() { + if (this.#projectPackageJsonPath === undefined) { + this.#projectPackageJsonPath = getWorkingProjectPackageJsonPath() || false; + } + return this.#projectPackageJsonPath; + } + get projectPackageJson() { if (!this.#projectPackageJson) { - this.#projectPackageJson = getWorkingProjectPackageJson(); + let p = this.projectPackageJsonPath; + this.#projectPackageJson = p ? importJsonSync(p) : {}; } return this.#projectPackageJson; } @@ -1106,6 +1125,7 @@ Arguments: return; } + // TODO use DirContains let dataDir = TemplatePath.stripLeadingDotSlash(this.templateData.getDataDir()); function filterOutGlobalDataFiles(path) { return !dataDir || !TemplatePath.stripLeadingDotSlash(path).startsWith(dataDir); @@ -1201,7 +1221,25 @@ Arguments: let rawFiles = await this.getWatchedFiles(); debug("Watching for changes to: %o", rawFiles); - let watcher = chokidar.watch(rawFiles, this.getChokidarConfig()); + let options = this.getChokidarConfig(); + + // Remap all paths to `cwd` if in play (Issue #3854) + let remapper = new FileSystemRemap(rawFiles); + let cwd = remapper.getCwd(); + + if (cwd) { + options.cwd = cwd; + + rawFiles = remapper.getInput().map((entry) => { + return TemplatePath.stripLeadingDotSlash(entry); + }); + + options.ignored = remapper.getRemapped(options.ignored || []).map((entry) => { + return TemplatePath.stripLeadingDotSlash(entry); + }); + } + + let watcher = chokidar.watch(rawFiles, options); initWatchBench.after(); @@ -1268,6 +1306,9 @@ Arguments: await new Promise((resolve) => { watcher.on("ready", () => resolve()); }); + + // Returns for testability + return watchRun; } async stopWatch() { diff --git a/src/EleventyExtensionMap.js b/src/EleventyExtensionMap.js index f2a61534f..8f4264042 100644 --- a/src/EleventyExtensionMap.js +++ b/src/EleventyExtensionMap.js @@ -142,8 +142,7 @@ class EleventyExtensionMap { return this._getGlobs(this.unfilteredFormatKeys, inputDir); } - _getGlobs(formatKeys, inputDir) { - let dir = TemplatePath.convertToRecursiveGlobSync(inputDir); + _getGlobs(formatKeys, inputDir = "") { let extensions = new Set(); for (let key of formatKeys) { @@ -156,6 +155,7 @@ class EleventyExtensionMap { } } + let dir = TemplatePath.convertToRecursiveGlobSync(inputDir); if (extensions.size === 1) { return [`${dir}/*.${Array.from(extensions)[0]}`]; } else if (extensions.size > 1) { diff --git a/src/EleventyFiles.js b/src/EleventyFiles.js index 73065d706..7ec6379e5 100644 --- a/src/EleventyFiles.js +++ b/src/EleventyFiles.js @@ -3,6 +3,7 @@ import fs from "node:fs"; import { TemplatePath, isPlainObject } from "@11ty/eleventy-utils"; import debugUtil from "debug"; +import DirContains from "./Util/DirContains.js"; import TemplateData from "./Data/TemplateData.js"; import TemplateGlob from "./TemplateGlob.js"; import checkPassthroughCopyBehavior from "./Util/PassthroughCopyBehaviorCheck.js"; @@ -11,6 +12,7 @@ const debug = debugUtil("Eleventy:EleventyFiles"); class EleventyFiles { #extensionMap; + #watcherGlobs; constructor(formats, templateConfig) { if (!templateConfig) { @@ -69,8 +71,8 @@ class EleventyFiles { this.setupGlobs(); } - get validTemplateGlobs() { - if (!this._validTemplateGlobs) { + #getWatcherGlobs() { + if (!this.#watcherGlobs) { let globs; // Input is a file if (this.inputFile) { @@ -79,9 +81,10 @@ class EleventyFiles { // input is a directory globs = this.extensionMap.getValidGlobs(this.inputDir); } - this._validTemplateGlobs = globs; + this.#watcherGlobs = globs; } - return this._validTemplateGlobs; + + return this.#watcherGlobs; } get passthroughGlobs() { @@ -154,7 +157,7 @@ class EleventyFiles { setupGlobs() { this.fileIgnores = this.getIgnores(); - this.extraIgnores = this._getIncludesAndDataDirs(); + this.extraIgnores = this.getIncludesAndDataDirs(); this.uniqueIgnores = this.getIgnoreGlobs(); // Conditional added for tests that don’t have a config @@ -165,6 +168,13 @@ class EleventyFiles { this.normalizedTemplateGlobs = this.templateGlobs; } + normalizeIgnoreEntry(entry) { + if (!entry.startsWith("**/")) { + return TemplateGlob.normalizePath(this.localPathRoot || ".", entry); + } + return entry; + } + getIgnoreGlobs() { let uniqueIgnores = new Set(); for (let ignore of this.fileIgnores) { @@ -173,10 +183,12 @@ class EleventyFiles { for (let ignore of this.extraIgnores) { uniqueIgnores.add(ignore); } + // Placing the config ignores last here is important to the tests for (let ignore of this.config.ignores) { - uniqueIgnores.add(TemplateGlob.normalizePath(this.localPathRoot || ".", ignore)); + uniqueIgnores.add(this.normalizeIgnoreEntry(ignore)); } + return Array.from(uniqueIgnores); } @@ -261,11 +273,12 @@ class EleventyFiles { files.add(this.eleventyIgnoreContent); } - // ignore output dir (unless this excludes all input) - // input: . and output: . (skip) - // input: ./content and output . (skip) - // input: . and output: ./_site (add) - if (!this.inputDir.startsWith(this.outputDir)) { + // Make sure output dir isn’t in the input dir (or it will ignore all input!) + // input: . and output: . (skip ignore) + // input: ./content and output . (skip ignore) + // input: . and output: ./_site (add ignore) + let outputContainsInputDir = DirContains(this.outputDir, this.inputDir); + if (!outputContainsInputDir) { // both are already normalized in 3.0 files.add(TemplateGlob.map(this.outputDir + "/**")); } @@ -284,6 +297,7 @@ class EleventyFiles { if (this.eleventyIgnoreContent === false) { let absoluteInputDir = TemplatePath.absolutePath(this.inputDir); ignoreFiles.add(TemplatePath.join(rootDirectory, ".eleventyignore")); + if (rootDirectory !== absoluteInputDir) { ignoreFiles.add(TemplatePath.join(this.inputDir, ".eleventyignore")); } @@ -427,14 +441,16 @@ class EleventyFiles { /* For `eleventy --watch` */ getGlobWatcherFiles() { // TODO improvement: tie the includes and data to specific file extensions (currently using `**`) - let directoryGlobs = this._getIncludesAndDataDirs(); + let directoryGlobs = this.getIncludesAndDataDirs(); + + let globs = this.#getWatcherGlobs(); if (checkPassthroughCopyBehavior(this.config, this.runMode)) { - return this.validTemplateGlobs.concat(directoryGlobs); + return globs.concat(directoryGlobs); } // Revert to old passthroughcopy copy files behavior - return this.validTemplateGlobs.concat(this.passthroughGlobs).concat(directoryGlobs); + return globs.concat(this.passthroughGlobs).concat(directoryGlobs); } /* For `eleventy --watch` */ @@ -456,7 +472,12 @@ class EleventyFiles { bench.before(); let results = TemplatePath.addLeadingDotSlashArray( await this.fileSystemSearch.search("js-dependencies", globs, { - ignore: ["**/node_modules/**"], + ignore: [ + "**/node_modules/**", + ".git/**", + // TODO outputDir + // this.outputDir, + ], }), ); bench.after(); @@ -471,14 +492,14 @@ class EleventyFiles { ); for (let ignore of this.config.watchIgnores) { - entries.add(TemplateGlob.normalizePath(this.localPathRoot || ".", ignore)); + entries.add(this.normalizeIgnoreEntry(ignore)); } // de-duplicated return Array.from(entries); } - _getIncludesAndDataDirs() { + getIncludesAndDataDirs() { let rawPaths = new Set(); rawPaths.add(this.includesDir); if (this.layoutsDir) { diff --git a/src/EleventyServe.js b/src/EleventyServe.js index 4676d19fb..65525ef7c 100644 --- a/src/EleventyServe.js +++ b/src/EleventyServe.js @@ -25,6 +25,8 @@ const DEFAULT_SERVER_OPTIONS = { }; class EleventyServe { + #eleventyConfig; + constructor() { this.logger = new ConsoleLogger(); this._initOptionsFetched = false; @@ -55,19 +57,20 @@ class EleventyServe { } get eleventyConfig() { - if (!this._eleventyConfig) { + if (!this.#eleventyConfig) { throw new EleventyServeConfigError( "You need to set the eleventyConfig property on EleventyServe.", ); } - return this._eleventyConfig; + return this.#eleventyConfig; } set eleventyConfig(config) { - this._eleventyConfig = config; - if (checkPassthroughCopyBehavior(this._eleventyConfig.userConfig, "serve")) { - this._eleventyConfig.userConfig.events.on("eleventy.passthrough", ({ map }) => { + this.#eleventyConfig = config; + + if (checkPassthroughCopyBehavior(this.#eleventyConfig.userConfig, "serve")) { + this.#eleventyConfig.userConfig.events.on("eleventy.passthrough", ({ map }) => { // for-free passthrough copy this.setAliases(map); }); @@ -84,7 +87,7 @@ class EleventyServe { async getServerModule(name) { try { if (!name || name === DEFAULT_SERVER_OPTIONS.module) { - return import("@11ty/eleventy-dev-server").then(i=>i.default) + return import("@11ty/eleventy-dev-server").then((i) => i.default); } // Look for peer dep in local project @@ -134,7 +137,7 @@ class EleventyServe { e.message, ); debug("Eleventy server error %o", e); - return import("@11ty/eleventy-dev-server").then(i=>i.default) + return import("@11ty/eleventy-dev-server").then((i) => i.default); } } diff --git a/src/Engines/Custom.js b/src/Engines/Custom.js index 4bd0f371f..17a0da143 100644 --- a/src/Engines/Custom.js +++ b/src/Engines/Custom.js @@ -1,11 +1,5 @@ import TemplateEngine from "./TemplateEngine.js"; import getJavaScriptData from "../Util/GetJavaScriptData.js"; -import eventBus from "../EventBus.js"; - -let lastModifiedFile = undefined; -eventBus.on("eleventy.resourceModified", (path) => { - lastModifiedFile = path; -}); export default class CustomEngine extends TemplateEngine { constructor(name, eleventyConfig) { @@ -14,14 +8,7 @@ export default class CustomEngine extends TemplateEngine { this.entry = this.getExtensionMapEntry(); this.needsInit = "init" in this.entry && typeof this.entry.init === "function"; - this._defaultEngine = undefined; - - // Enable cacheability for this template - if (this.entry?.compileOptions?.cache !== undefined) { - this.cacheable = this.entry.compileOptions.cache; - } else if (this.needsToReadFileContents()) { - this.cacheable = true; - } + this.setDefaultEngine(undefined); } getExtensionMapEntry() { @@ -45,6 +32,19 @@ export default class CustomEngine extends TemplateEngine { this._defaultEngine = defaultEngine; } + get cacheable() { + // Enable cacheability for this template + if (this.entry?.compileOptions?.cache !== undefined) { + return this.entry.compileOptions.cache; + } else if (this.needsToReadFileContents()) { + return true; + } else if (this._defaultEngine?.cacheable !== undefined) { + return this._defaultEngine.cacheable; + } + + return super.cacheable; + } + async getInstanceFromInputPath(inputPath) { if ( "getInstanceFromInputPath" in this.entry && @@ -230,7 +230,7 @@ export default class CustomEngine extends TemplateEngine { } // TODO generalize this (look at JavaScript.js) - let fn = this.entry.compile.bind({ + let compiledFn = this.entry.compile.bind({ config: this.config, addDependencies: (from, toArray = []) => { this.config.uses.addDependency(from, toArray); @@ -239,11 +239,11 @@ export default class CustomEngine extends TemplateEngine { })(str, inputPath); // Support `undefined` to skip compile/render - if (fn) { + if (compiledFn) { // Bind defaultRenderer to render function - if ("then" in fn && typeof fn.then === "function") { + if ("then" in compiledFn && typeof compiledFn.then === "function") { // Promise, wait to bind - return fn.then((fn) => { + return compiledFn.then((fn) => { if (typeof fn === "function") { return fn.bind({ defaultRenderer: defaultCompilationFn, @@ -251,14 +251,14 @@ export default class CustomEngine extends TemplateEngine { } return fn; }); - } else if ("bind" in fn && typeof fn.bind === "function") { - return fn.bind({ + } else if ("bind" in compiledFn && typeof compiledFn.bind === "function") { + return compiledFn.bind({ defaultRenderer: defaultCompilationFn, }); } } - return fn; + return compiledFn; } get defaultTemplateFileExtension() { @@ -283,9 +283,11 @@ export default class CustomEngine extends TemplateEngine { } getCompileCacheKey(str, inputPath) { + let lastModifiedFile = this.eleventyConfig.getPreviousBuildModifiedFile(); // Return this separately so we know whether or not to use the cached version // but still return a key to cache this new render for next time - let useCache = !this.isFileRelevantTo(inputPath, lastModifiedFile, false); + let isRelevant = this.isFileRelevantTo(inputPath, lastModifiedFile, false); + let useCache = !isRelevant; if (this.entry.compileOptions && "getCacheKey" in this.entry.compileOptions) { if (typeof this.entry.compileOptions.getCacheKey !== "function") { diff --git a/src/Engines/Html.js b/src/Engines/Html.js index 261594efa..a0f410190 100644 --- a/src/Engines/Html.js +++ b/src/Engines/Html.js @@ -3,7 +3,10 @@ import TemplateEngine from "./TemplateEngine.js"; export default class Html extends TemplateEngine { constructor(name, eleventyConfig) { super(name, eleventyConfig); - this.cacheable = true; + } + + get cacheable() { + return true; } async #getPreEngine(preTemplateEngine) { diff --git a/src/Engines/JavaScript.js b/src/Engines/JavaScript.js index 78b1d182d..29b3b7c54 100644 --- a/src/Engines/JavaScript.js +++ b/src/Engines/JavaScript.js @@ -13,8 +13,6 @@ export default class JavaScript extends TemplateEngine { super(name, templateConfig); this.instances = {}; - this.cacheable = false; - this.config.events.on("eleventy#templateModified", (inputPath, metadata = {}) => { let { usedByDependants, relevantLayouts } = metadata; // Remove from cached instances when modified @@ -31,6 +29,10 @@ export default class JavaScript extends TemplateEngine { }); } + get cacheable() { + return false; + } + normalize(result) { if (Buffer.isBuffer(result)) { return result.toString(); diff --git a/src/Engines/Liquid.js b/src/Engines/Liquid.js index c06a6fb58..44fdab4c0 100644 --- a/src/Engines/Liquid.js +++ b/src/Engines/Liquid.js @@ -25,7 +25,10 @@ export default class Liquid extends TemplateEngine { this.setLibrary(this.config.libraryOverrides.liquid); this.argLexer = moo.compile(Liquid.argumentLexerOptions); - this.cacheable = true; + } + + get cacheable() { + return true; } setLibrary(override) { diff --git a/src/Engines/Markdown.js b/src/Engines/Markdown.js index a7dde58c0..ec1e1f6fb 100644 --- a/src/Engines/Markdown.js +++ b/src/Engines/Markdown.js @@ -9,8 +9,10 @@ export default class Markdown extends TemplateEngine { this.markdownOptions = {}; this.setLibrary(this.config.libraryOverrides.md); + } - this.cacheable = true; + get cacheable() { + return true; } setLibrary(mdLib) { diff --git a/src/Engines/Nunjucks.js b/src/Engines/Nunjucks.js index b31f8f181..70cca174a 100755 --- a/src/Engines/Nunjucks.js +++ b/src/Engines/Nunjucks.js @@ -21,11 +21,13 @@ export default class Nunjucks extends TemplateEngine { this._usingPrecompiled = Object.keys(this.nunjucksPrecompiledTemplates).length > 0; this.setLibrary(this.config.libraryOverrides.njk); - - // v3.1.0-alpha.1 we’ve moved to use Nunjucks’ internal cache instead of Eleventy’s - // this.cacheable = false; } + // v3.1.0-alpha.1 we’ve moved to use Nunjucks’ internal cache instead of Eleventy’s + // get cacheable() { + // return false; + // } + #getFileSystemDirs() { let paths = new Set(); paths.add(super.getIncludesDir()); diff --git a/src/Engines/TemplateEngine.js b/src/Engines/TemplateEngine.js index ddf0bc534..234aa4e74 100644 --- a/src/Engines/TemplateEngine.js +++ b/src/Engines/TemplateEngine.js @@ -16,7 +16,6 @@ export default class TemplateEngine { this.name = name; this.engineLib = null; - this.cacheable = false; if (!eleventyConfig) { throw new TemplateEngineConfigError("Missing `eleventyConfig` argument."); @@ -24,6 +23,10 @@ export default class TemplateEngine { this.eleventyConfig = eleventyConfig; } + get cacheable() { + return false; + } + get dirs() { return this.eleventyConfig.directories; } diff --git a/src/FileSystemSearch.js b/src/FileSystemSearch.js index 0dfa129d0..972e80ba3 100644 --- a/src/FileSystemSearch.js +++ b/src/FileSystemSearch.js @@ -2,6 +2,7 @@ import { glob } from "tinyglobby"; import { TemplatePath } from "@11ty/eleventy-utils"; import debugUtil from "debug"; +import FileSystemRemap from "./Util/GlobRemap.js"; import { isGlobMatch } from "./Util/GlobMatcher.js"; const debug = debugUtil("Eleventy:FileSystemSearch"); @@ -32,8 +33,17 @@ class FileSystemSearch { // Strip leading slashes from everything! globs = globs.map((entry) => TemplatePath.stripLeadingDotSlash(entry)); + let cwd = FileSystemRemap.getCwd(globs); + if (cwd) { + options.cwd = cwd; + } + if (options.ignore && Array.isArray(options.ignore)) { - options.ignore = options.ignore.map((entry) => TemplatePath.stripLeadingDotSlash(entry)); + options.ignore = options.ignore.map((entry) => { + entry = TemplatePath.stripLeadingDotSlash(entry); + + return FileSystemRemap.remapInput(entry, cwd); + }); debug("Glob search (%o) ignoring: %o", key, options.ignore); } @@ -52,6 +62,14 @@ class FileSystemSearch { this.count++; + globs = globs.map((entry) => { + if (cwd && entry.startsWith(cwd)) { + return FileSystemRemap.remapInput(entry, cwd); + } + + return entry; + }); + this.promises[cacheKey] = glob( globs, Object.assign( @@ -63,8 +81,12 @@ class FileSystemSearch { ), ).then((results) => { this.outputs[cacheKey] = new Set( - results.map((entry) => TemplatePath.standardizeFilePath(entry)), + results.map((entry) => { + let remapped = FileSystemRemap.remapOutput(entry, options.cwd); + return TemplatePath.standardizeFilePath(remapped); + }), ); + return Array.from(this.outputs[cacheKey]); }); } @@ -97,6 +119,11 @@ class FileSystemSearch { delete(path) { this._modify(path, "delete"); } + + // Issue #3859 get rid of chokidar globs + // getAllOutputFiles() { + // return Object.values(this.outputs).map(set => Array.from(set)).flat(); + // } } export default FileSystemSearch; diff --git a/src/Plugins/RenderPlugin.js b/src/Plugins/RenderPlugin.js index 1ec31cf5c..974b0e1d8 100644 --- a/src/Plugins/RenderPlugin.js +++ b/src/Plugins/RenderPlugin.js @@ -362,6 +362,7 @@ function eleventyRenderPlugin(eleventyConfig, options = {}) { templateLang = false; } + // TODO Render plugin `templateLang` is feeding bad input paths to the addDependencies call in Custom.js let fn = await compile.call(this, content, templateLang, { templateConfig: opts.templateConfig || templateConfig, extensionMap, diff --git a/src/Template.js b/src/Template.js index 2542d5fc4..64c47095f 100755 --- a/src/Template.js +++ b/src/Template.js @@ -167,7 +167,9 @@ class Template extends TemplateContent { } getTemplateSubfolder() { - return TemplatePath.stripLeadingSubPath(this.parsed.dir, this.inputDir); + let dir = TemplatePath.absolutePath(this.parsed.dir); + let inputDir = TemplatePath.absolutePath(this.inputDir); + return TemplatePath.stripLeadingSubPath(dir, inputDir); } templateUsesLayouts(pageData) { @@ -534,29 +536,43 @@ class Template extends TemplateContent { }); } + async #renderComputedUnit(entry, data) { + if (typeof entry === "string") { + return this.renderComputedData(entry, data); + } + + if (isPlainObject(entry)) { + for (let key in entry) { + entry[key] = await this.#renderComputedUnit(entry[key], data); + } + } + + if (Array.isArray(entry)) { + for (let j = 0, k = entry.length; j < k; j++) { + entry[j] = await this.#renderComputedUnit(entry[j], data); + } + } + + return entry; + } + _addComputedEntry(computedData, obj, parentKey, declaredDependencies) { // this check must come before isPlainObject if (typeof obj === "function") { computedData.add(parentKey, obj, declaredDependencies); - } else if (Array.isArray(obj)) { - // Arrays are treated as one entry in the dependency graph now + } else if (Array.isArray(obj) || typeof obj === "string") { + // Arrays are treated as one entry in the dependency graph now, Issue #3728 computedData.addTemplateString( parentKey, async function (innerData) { - return Promise.all( - obj.map((entry) => { - if (typeof entry === "string") { - return this.tmpl.renderComputedData(entry, innerData); - } - return entry; - }), - ); + return this.tmpl.#renderComputedUnit(obj, innerData); }, declaredDependencies, this.getParseForSymbolsFunction(obj), this, ); } else if (isPlainObject(obj)) { + // Arrays used to be computed here for (let key in obj) { let keys = []; if (parentKey) { @@ -565,16 +581,6 @@ class Template extends TemplateContent { keys.push(key); this._addComputedEntry(computedData, obj[key], keys.join("."), declaredDependencies); } - } else if (typeof obj === "string") { - computedData.addTemplateString( - parentKey, - async function (innerData) { - return this.tmpl.renderComputedData(obj, innerData); - }, - declaredDependencies, - this.getParseForSymbolsFunction(obj), - this, - ); } else { // Numbers, booleans, etc computedData.add(parentKey, obj, declaredDependencies); diff --git a/src/TemplateConfig.js b/src/TemplateConfig.js index 9bff7fac2..e1fff8f92 100644 --- a/src/TemplateConfig.js +++ b/src/TemplateConfig.js @@ -50,6 +50,7 @@ class TemplateConfig { #userConfig = new UserConfig(); #existsCache = new ExistsCache(); #usesGraph; + #previousBuildModifiedFile; constructor(customRootConfig, projectConfigPath) { /** @type {object} */ @@ -91,6 +92,22 @@ class TemplateConfig { this.hasConfigMerged = false; this.isEsm = false; + + this.userConfig.events.on("eleventy#templateModified", (inputPath, metadata = {}) => { + // Might support multiple at some point + this.setPreviousBuildModifiedFile(inputPath, metadata); + + // Issue #3569, set that this file exists in the cache + this.#existsCache.set(inputPath, true); + }); + } + + setPreviousBuildModifiedFile(inputPath, metadata = {}) { + this.#previousBuildModifiedFile = inputPath; + } + + getPreviousBuildModifiedFile() { + return this.#previousBuildModifiedFile; } get userConfig() { @@ -150,16 +167,17 @@ class TemplateConfig { */ getLocalProjectConfigFile() { let configFiles = this.getLocalProjectConfigFiles(); - // Add the configFiles[0] in case of a test, where no file exists on the file system - let configFile = configFiles.find((path) => path && fs.existsSync(path)) || configFiles[0]; + + let configFile = configFiles.find((path) => path && fs.existsSync(path)); if (configFile) { return configFile; } } getLocalProjectConfigFiles() { - if (this.projectConfigPaths?.length > 0) { - return TemplatePath.addLeadingDotSlashArray(this.projectConfigPaths.filter((path) => path)); + let paths = this.projectConfigPaths; + if (paths?.length > 0) { + return TemplatePath.addLeadingDotSlashArray(paths.filter((path) => Boolean(path))); } return []; } diff --git a/src/TemplateMap.js b/src/TemplateMap.js index f0eefafe2..52cceb187 100644 --- a/src/TemplateMap.js +++ b/src/TemplateMap.js @@ -154,6 +154,7 @@ class TemplateMap { // TODO(slightlyoff): major bottleneck async initDependencyMap(fullTemplateOrder) { // Temporary workaround for async constructor work in templates + // Issue #3170 #3870 let inputPathSet = new Set(fullTemplateOrder); await Promise.all( this.map @@ -169,8 +170,11 @@ class TemplateMap { for (let depEntry of fullTemplateOrder) { if (GlobalDependencyMap.isCollection(depEntry)) { let tagName = GlobalDependencyMap.getTagName(depEntry); - // [NAME] is special and implied (e.g. [keys]) - if (!tagName.startsWith("[") && !tagName.endsWith("]")) { + // [keys] should initialize `all` + if (tagName === SPECIAL_COLLECTION_NAMES.keys) { + await this.setCollectionByTagName("all"); + // [NAME] is special and implied (e.g. [keys]) + } else if (!tagName.startsWith("[") && !tagName.endsWith("]")) { // is a tag (collection) entry await this.setCollectionByTagName(tagName); } @@ -232,8 +236,9 @@ class TemplateMap { // 2. Pagination templates that consume config API collections // 3. Pagination templates consuming `collections` // 4. Pagination templates consuming `collections.all` - let fullTemplateOrder = this.config.uses - .getTemplateOrder() + let fullTemplateOrder = this.config.uses.getTemplateOrder(); + + return fullTemplateOrder .map((entry) => { if (GlobalDependencyMap.isCollection(entry)) { return entry; @@ -246,8 +251,6 @@ class TemplateMap { return inputPath; }) .filter(Boolean); - - return fullTemplateOrder; } async cache() { diff --git a/src/TemplatePassthrough.js b/src/TemplatePassthrough.js index 32c85d407..b27b634e5 100644 --- a/src/TemplatePassthrough.js +++ b/src/TemplatePassthrough.js @@ -174,8 +174,14 @@ class TemplatePassthrough { throw new Error("Internal error: Missing `fileSystemSearch` property."); } + // TODO perf this globs once per addPassthroughCopy entry let files = TemplatePath.addLeadingDotSlashArray( - await this.fileSystemSearch.search("passthrough", glob), + await this.fileSystemSearch.search("passthrough", glob, { + ignore: [ + // *only* ignores output dir (not node_modules!) + this.outputDir, + ], + }), ); b.after(); return files; diff --git a/src/TemplatePermalink.js b/src/TemplatePermalink.js index c9385b8bf..9ad51119f 100644 --- a/src/TemplatePermalink.js +++ b/src/TemplatePermalink.js @@ -81,7 +81,6 @@ class TemplatePermalink { // empty or false return false; } - let cleanLink = this._addDefaultLinkFilename(this.buildLink); let parsed = path.parse(cleanLink); @@ -184,8 +183,7 @@ class TemplatePermalink { path = (dir ? dir + "/" : "") + (filenameNoExt !== "index" && !hasDupeFolder ? filenameNoExt + "/" : "") + - "index" + - ".html"; + "index.html"; } else { path = (dir ? dir + "/" : "") + filenameNoExt + "." + fileExtension; } diff --git a/src/TemplateRender.js b/src/TemplateRender.js index 8bc9096fd..776f16e99 100644 --- a/src/TemplateRender.js +++ b/src/TemplateRender.js @@ -1,6 +1,5 @@ import EleventyBaseError from "./Errors/EleventyBaseError.js"; import TemplateEngineManager from "./Engines/TemplateEngineManager.js"; -import CustomEngine from "./Engines/Custom.js"; // import debugUtil from "debug"; // const debug = debugUtil("Eleventy:TemplateRender"); @@ -167,7 +166,7 @@ class TemplateRender { getReadableEnginesListDifferingFromFileExtension() { let keyFromFilename = this.extensionMap.getKey(this.engineNameOrPath); - if (this.engine instanceof CustomEngine) { + if (this.engine?.constructor?.name === "CustomEngine") { if ( this.engine.entry && this.engine.entry.name && diff --git a/src/TemplateWriter.js b/src/TemplateWriter.js index 55c467a79..79b0fee52 100755 --- a/src/TemplateWriter.js +++ b/src/TemplateWriter.js @@ -185,13 +185,21 @@ class TemplateWriter { let { template: tmpl } = this._createTemplate(path, to); // Note: removed a fix here to fetch missing templateRender instances - // that was tested as no longer needed (Issue #3170). + // that was tested as no longer needed (Issue #3170) + // Related: #3870, improved configuration reset templates.push(tmpl); // This must happen before data is generated for the incremental file only if (incrementalFileShape === "template" && tmpl.inputPath === this.incrementalFile) { tmpl.resetCaches(); + } else if ( + // Issue #3824 #3870 + tmpl.isFileRelevantToThisTemplate(this.incrementalFile, { + isFullTemplate: incrementalFileShape === "template", + }) + ) { + tmpl.resetCaches(); } // IMPORTANT: This is where the data is first generated for the template @@ -370,11 +378,8 @@ class TemplateWriter { async generateTemplates(paths, to = "fs") { let promises = []; - - // console.time("generateTemplates:_createTemplateMap"); // TODO optimize await here await this._createTemplateMap(paths, to); - // console.timeEnd("generateTemplates:_createTemplateMap"); debug("Template map created."); let usedTemplateContentTooEarlyMap = []; diff --git a/src/UserConfig.js b/src/UserConfig.js index 5a6fbcc71..215327fe5 100644 --- a/src/UserConfig.js +++ b/src/UserConfig.js @@ -170,7 +170,7 @@ class UserConfig { let defaultIgnores = new Set(); defaultIgnores.add("**/node_modules/**"); - defaultIgnores.add(".git/**"); + defaultIgnores.add(".git/**"); // TODO `**/.git/**` this.ignores = new Set(defaultIgnores); this.watchIgnores = new Set(defaultIgnores); diff --git a/src/Util/Compatibility.js b/src/Util/Compatibility.js index 18bdd701e..c90a9b331 100644 --- a/src/Util/Compatibility.js +++ b/src/Util/Compatibility.js @@ -8,10 +8,20 @@ const pkg = getEleventyPackageJson(); class Compatibility { static NORMALIZE_PRERELEASE_REGEX = /-canary\b/g; + static #projectPackageJson; + constructor(compatibleRange) { this.compatibleRange = Compatibility.getCompatibilityValue(compatibleRange); } + static get projectPackageJson() { + if (!this.#projectPackageJson) { + this.#projectPackageJson = getWorkingProjectPackageJson(); + } + + return this.#projectPackageJson; + } + static normalizeIdentifier(identifier) { return identifier.replace(Compatibility.NORMALIZE_PRERELEASE_REGEX, "-alpha"); } @@ -22,9 +32,8 @@ class Compatibility { } // fetch from project’s package.json - let projectPackageJson = getWorkingProjectPackageJson(); - if (projectPackageJson["11ty"]?.compatibility) { - return projectPackageJson["11ty"]?.compatibility; + if (this.projectPackageJson?.["11ty"]?.compatibility) { + return this.projectPackageJson["11ty"].compatibility; } } diff --git a/src/Util/ConsoleLogger.js b/src/Util/ConsoleLogger.js index 7ac58ed6b..ba4119634 100644 --- a/src/Util/ConsoleLogger.js +++ b/src/Util/ConsoleLogger.js @@ -15,12 +15,10 @@ class ConsoleLogger { #isChalkEnabled = true; /** @type {object|boolean|undefined} */ #logger; + /** @type {Readable|undefined} */ + #outputStream; - constructor() { - this.outputStream = new Readable({ - read() {}, - }); - } + constructor() {} isLoggingEnabled() { if (!this.isVerbose || process.env.DEBUG) { @@ -91,6 +89,15 @@ class ConsoleLogger { this.message(msg, "error", "red"); } + get outputStream() { + if (!this.#outputStream) { + this.#outputStream = new Readable({ + read() {}, + }); + } + return this.#outputStream; + } + /** @param {string} msg */ toStream(msg) { this.outputStream.push(msg); diff --git a/src/Util/DirContains.js b/src/Util/DirContains.js index 580d330d8..c19990cbd 100644 --- a/src/Util/DirContains.js +++ b/src/Util/DirContains.js @@ -1,8 +1,9 @@ import path from "node:path"; // Returns true if subfolder is in parent (accepts absolute or relative paths for both) -export default function (parent, subfolder) { - if (path.resolve(subfolder).startsWith(path.resolve(parent))) { +export default function (parentFolder, subFolder) { + // path.resolve returns an absolute path + if (path.resolve(subFolder).startsWith(path.resolve(parentFolder))) { return true; } return false; diff --git a/src/Util/EsmResolver.js b/src/Util/EsmResolver.js index d42b88b49..c098ed877 100644 --- a/src/Util/EsmResolver.js +++ b/src/Util/EsmResolver.js @@ -1,4 +1,6 @@ import debugUtil from "debug"; +import { fileURLToPath } from "node:url"; +import PathNormalizer from "./PathNormalizer.js"; const debug = debugUtil("Eleventy:EsmResolver"); @@ -31,7 +33,7 @@ export async function resolve(specifier, context, nextResolve) { return nextResolve(specifier); } - let absolutePath = fileUrl.pathname; + let absolutePath = PathNormalizer.normalizeSeperator(fileURLToPath(fileUrl)); // Bust the import cache if this is a recently modified file if (lastModifiedPaths.has(absolutePath)) { fileUrl.search = ""; // delete existing searchparams diff --git a/src/Util/ExistsCache.js b/src/Util/ExistsCache.js index f54ae3fea..433d7434f 100644 --- a/src/Util/ExistsCache.js +++ b/src/Util/ExistsCache.js @@ -1,4 +1,5 @@ import fs from "node:fs"; +import { TemplatePath } from "@11ty/eleventy-utils"; // Checks both files and directories class ExistsCache { @@ -17,6 +18,13 @@ class ExistsCache { return this.#exists.has(path); } + set(path, isExist) { + this.#exists.set(TemplatePath.addLeadingDotSlash(path), Boolean(isExist)); + } + + // Not yet needed + // setDirectory(path, isExist) {} + // Relative paths (to root directory) expected (but not enforced due to perf costs) exists(path) { if (!this.#exists.has(path)) { diff --git a/src/Util/GlobRemap.js b/src/Util/GlobRemap.js new file mode 100644 index 000000000..5e2bea37a --- /dev/null +++ b/src/Util/GlobRemap.js @@ -0,0 +1,85 @@ +import path from "node:path"; +import ProjectDirectories from "./ProjectDirectories.js"; +import PathNormalizer from "./PathNormalizer.js"; + +// even on Windows (in cmd.exe) these paths are normalized to forward slashes +// tinyglobby expects forward slashes on Windows +const SEP = "/"; + +class GlobRemap { + constructor(paths = []) { + this.paths = paths; + this.cwd = GlobRemap.getCwd(paths); + } + + getCwd() { + return this.cwd; + } + + getRemapped(paths) { + return paths.map((entry) => GlobRemap.remapInput(entry, this.cwd)); + } + + getInput() { + return this.getRemapped(this.paths); + } + + getOutput(paths = []) { + return paths.map((entry) => GlobRemap.remapOutput(entry, this.cwd)); + } + + static getParentDirPrefix(filePath = "") { + let count = []; + for (let p of filePath.split(SEP)) { + if (p === "..") { + count.push(".."); + } else { + break; + } + } + + if (count.length > 0) { + // trailing slash + return count.join(SEP) + SEP; + } + return ""; + } + + static getLongestParentDirPrefix(filePaths) { + let longest = ""; + filePaths + .map((entry) => { + return this.getParentDirPrefix(entry); + }) + .filter((entry) => Boolean(entry)) + .forEach((prefix) => { + if (!longest || prefix.length > longest.length) { + longest = prefix; + } + }); + return longest; + } + + // alias + static getCwd(filePaths) { + return this.getLongestParentDirPrefix(filePaths); + } + + static remapInput(entry, cwd) { + if (cwd) { + if (!entry.startsWith("**" + SEP) && !entry.startsWith(`.git${SEP}**`)) { + return PathNormalizer.normalizeSeperator(ProjectDirectories.getRelativeTo(entry, cwd)); + } + } + return entry; + } + + static remapOutput(entry, cwd) { + if (cwd) { + return PathNormalizer.normalizeSeperator(path.join(cwd, entry)); + } + return entry; + } +} + +export default GlobRemap; diff --git a/src/Util/ImportJsonSync.js b/src/Util/ImportJsonSync.js index 820a1834f..fa5936592 100644 --- a/src/Util/ImportJsonSync.js +++ b/src/Util/ImportJsonSync.js @@ -14,6 +14,7 @@ function findFilePathInParentDirs(dir, filename) { // https://docs.npmjs.com/cli/v7/configuring-npm/folders#more-information // Fixes issue #3178, limited to working dir paths only let workingDir = TemplatePath.getWorkingDir(); + // TODO use DirContains let allDirs = TemplatePath.getAllDirs(dir).filter((entry) => entry.startsWith(workingDir)); for (let dir of allDirs) { @@ -49,9 +50,14 @@ function getModulePackageJson(dir) { return importJsonSync(filePath); } -function getWorkingProjectPackageJson() { +// This will *not* find a package.json in a parent directory above root +function getWorkingProjectPackageJsonPath() { let dir = TemplatePath.absolutePath(TemplatePath.getWorkingDir()); - let filePath = findFilePathInParentDirs(dir, "package.json"); + return findFilePathInParentDirs(dir, "package.json"); +} + +function getWorkingProjectPackageJson() { + let filePath = getWorkingProjectPackageJsonPath(); // optional! if (!filePath) { @@ -67,4 +73,5 @@ export { getEleventyPackageJson, getModulePackageJson, getWorkingProjectPackageJson, + getWorkingProjectPackageJsonPath, }; diff --git a/src/Util/PathNormalizer.js b/src/Util/PathNormalizer.js index 0e46fb972..cdc325324 100644 --- a/src/Util/PathNormalizer.js +++ b/src/Util/PathNormalizer.js @@ -2,7 +2,7 @@ import path from "node:path"; import { fileURLToPath } from "node:url"; import { TemplatePath } from "@11ty/eleventy-utils"; -class PathNormalizer { +export default class PathNormalizer { static getParts(inputPath) { if (!inputPath) { return []; @@ -56,5 +56,3 @@ class PathNormalizer { ); } } - -export default PathNormalizer; diff --git a/src/Util/ProjectDirectories.js b/src/Util/ProjectDirectories.js index c50a66aaf..e15e985af 100644 --- a/src/Util/ProjectDirectories.js +++ b/src/Util/ProjectDirectories.js @@ -3,6 +3,8 @@ import path from "node:path"; import { TemplatePath } from "@11ty/eleventy-utils"; import { isDynamicPattern } from "tinyglobby"; +import DirContains from "./DirContains.js"; + /* Directories internally should always use *nix forward slashes */ class ProjectDirectories { static defaults = { @@ -251,6 +253,7 @@ class ProjectDirectories { isTemplateFile(filePath) { let inputPath = this.getInputPath(filePath); + // TODO use DirContains if (this.layouts && inputPath.startsWith(this.layouts)) { return false; } @@ -263,6 +266,7 @@ class ProjectDirectories { } } + // TODO use DirContains return inputPath.startsWith(this.input); } @@ -309,11 +313,15 @@ class ProjectDirectories { } isFileInProjectFolder(filePath) { - return TemplatePath.absolutePath(filePath).startsWith(TemplatePath.getWorkingDir()); + return DirContains(TemplatePath.getWorkingDir(), filePath); } isFileInOutputFolder(filePath) { - return TemplatePath.absolutePath(filePath).startsWith(TemplatePath.absolutePath(this.output)); + return DirContains(this.output, filePath); + } + + static getRelativeTo(targetPath, cwd) { + return path.relative(cwd, path.join(path.resolve("."), targetPath)); } // Access the data without being able to set the data. diff --git a/src/Util/SpawnAsync.js b/src/Util/SpawnAsync.js index 7f3ebe672..5e6a20f95 100644 --- a/src/Util/SpawnAsync.js +++ b/src/Util/SpawnAsync.js @@ -5,19 +5,23 @@ export function spawnAsync(command, args, options) { let { promise, resolve, reject } = withResolvers(); const cmd = spawn(command, args, options); + let res = []; cmd.stdout.on("data", (data) => { - resolve(data.toString("utf8")); + res.push(data.toString("utf8")); }); + let err = []; cmd.stderr.on("data", (data) => { - reject(data.toString("utf8")); + err.push(data.toString("utf8")); }); cmd.on("close", (code) => { - if (code === 1) { + if (err.length > 0) { + reject(err.join("\n")); + } else if (code === 1) { reject("Internal error: process closed with error exit code."); } else { - resolve(); + resolve(res.join("\n")); } }); diff --git a/src/Util/TemplateDepGraph.js b/src/Util/TemplateDepGraph.js index b70fc8c20..795453db6 100644 --- a/src/Util/TemplateDepGraph.js +++ b/src/Util/TemplateDepGraph.js @@ -141,13 +141,18 @@ export class TemplateDepGraph extends DependencyGraph { overallOrder() { let unfiltered = this.unfilteredOrder(); + let filtered = unfiltered.filter((entry) => { - return !entry.startsWith("[") && !entry.endsWith("]"); + if (entry === `${COLLECTION_PREFIX}[keys]`) { + return true; + } + return !entry.startsWith(`${COLLECTION_PREFIX}[`) && !entry.endsWith("]"); }); - // Add another collections.all entry (if not already the last one) - if (filtered[filtered.length - 1] !== `${COLLECTION_PREFIX}all`) { - filtered.push(`${COLLECTION_PREFIX}all`); + let allKey = `${COLLECTION_PREFIX}all`; + // Add another collections.all entry to the end (if not already the last one) + if (filtered[filtered.length - 1] !== allKey) { + filtered.push(allKey); } return filtered; diff --git a/test/EleventyFilesGitIgnoreEleventyIgnoreTest.js b/test/EleventyFilesGitIgnoreEleventyIgnoreTest.js index ea8e5d025..f98982823 100644 --- a/test/EleventyFilesGitIgnoreEleventyIgnoreTest.js +++ b/test/EleventyFilesGitIgnoreEleventyIgnoreTest.js @@ -22,7 +22,7 @@ test("Get ignores (no .eleventyignore no .gitignore)", async (t) => { ]); t.deepEqual(evf.getIgnoreGlobs().slice(-2), [ - "./test/stubs/ignorelocalroot/**/node_modules/**", + "**/node_modules/**", "./test/stubs/ignorelocalroot/.git/**", ]); }); @@ -45,7 +45,7 @@ test("Get ignores (no .eleventyignore)", async (t) => { ]); t.deepEqual(evf.getIgnoreGlobs().slice(-2), [ - "./test/stubs/ignorelocalrootgitignore/**/node_modules/**", + "**/node_modules/**", "./test/stubs/ignorelocalrootgitignore/.git/**", ]); }); @@ -67,7 +67,7 @@ test("Get ignores (no .eleventyignore, using setUseGitIgnore(false))", async (t) ]); t.deepEqual(evf.getIgnoreGlobs().slice(-2), [ - "./test/stubs/ignorelocalroot/**/node_modules/**", + "**/node_modules/**", "./test/stubs/ignorelocalroot/.git/**", ]); }); @@ -91,7 +91,7 @@ test("Get ignores (no .gitignore)", async (t) => { ]); t.deepEqual(evf.getIgnoreGlobs().slice(-2), [ - "./test/stubs/ignorelocalroot/**/node_modules/**", + "**/node_modules/**", "./test/stubs/ignorelocalroot/.git/**", ]); }); @@ -116,7 +116,7 @@ test("Get ignores (project .eleventyignore and root .gitignore)", async (t) => { ]); t.deepEqual(evf.getIgnoreGlobs().slice(-2), [ - "./test/stubs/ignorelocalrootgitignore/**/node_modules/**", + "**/node_modules/**", "./test/stubs/ignorelocalrootgitignore/.git/**", ]); }); @@ -141,7 +141,7 @@ test("Get ignores (project .eleventyignore and root .gitignore, using setUseGitI ]); t.deepEqual(evf.getIgnoreGlobs().slice(-2), [ - "./test/stubs/ignorelocalrootgitignore/**/node_modules/**", + "**/node_modules/**", "./test/stubs/ignorelocalrootgitignore/.git/**", ]); }); @@ -164,7 +164,7 @@ test("Get ignores (no .eleventyignore .gitignore exists but empty)", async (t) ]); t.deepEqual(evf.getIgnoreGlobs().slice(-2), [ - "./test/stubs/ignorelocalroot/**/node_modules/**", + "**/node_modules/**", "./test/stubs/ignorelocalroot/.git/**", ]); }); @@ -188,7 +188,7 @@ test("Get ignores (both .eleventyignore and .gitignore exists, but .gitignore is ]); t.deepEqual(evf.getIgnoreGlobs().slice(-2), [ - "./test/stubs/ignorelocalroot/**/node_modules/**", + "**/node_modules/**", "./test/stubs/ignorelocalroot/.git/**", ]); }); @@ -309,7 +309,7 @@ test("De-duplicated ignores", async (t) => { ]); t.deepEqual(evf.getIgnoreGlobs().slice(-2), [ - "./test/stubs/ignore-dedupe/**/node_modules/**", + "**/node_modules/**", "./test/stubs/ignore-dedupe/.git/**", ]); }); diff --git a/test/EleventyFilesTest.js b/test/EleventyFilesTest.js index 4b377bd62..56687e372 100644 --- a/test/EleventyFilesTest.js +++ b/test/EleventyFilesTest.js @@ -284,7 +284,7 @@ test("Include and Data Dirs", async (t) => { let { eleventyFiles: evf } = getEleventyFilesInstance([], eleventyConfig); evf.init(); - t.deepEqual(evf._getIncludesAndDataDirs(), [ + t.deepEqual(evf.getIncludesAndDataDirs(), [ "./test/stubs/_includes/**", "./test/stubs/_data/**", ]); diff --git a/test/EleventyTest.js b/test/EleventyTest.js index 1c2c38abb..783c395fd 100644 --- a/test/EleventyTest.js +++ b/test/EleventyTest.js @@ -9,7 +9,6 @@ import { marked } from "marked"; import nunjucks from "nunjucks"; import * as sass from "sass"; -import eventBus from "../src/EventBus.js"; import Eleventy, { HtmlBasePlugin } from "../src/Eleventy.js"; import TemplateContent from "../src/TemplateContent.js"; import TemplateMap from "../src/TemplateMap.js"; @@ -661,9 +660,7 @@ ${previousContents} fs.writeFileSync(includeFilePath, newContents, "utf8"); - // Trigger that the file has changed - eventBus.emit("eleventy.resourceModified", includeFilePath); - + // This also triggers that the file has changed in the event bus via setPreviousBuildModifiedFile elev.setIncrementalFile(includeFilePath); let results3 = await elev.toJSON(); diff --git a/test/GlobRemapTest.js b/test/GlobRemapTest.js new file mode 100644 index 000000000..50b67ad09 --- /dev/null +++ b/test/GlobRemapTest.js @@ -0,0 +1,125 @@ +import test from "ava"; +import path from "node:path"; +import GlobRemap from "../src/Util/GlobRemap.js"; +import { normalizeSeparatorString } from "./Util/normalizeSeparators.js"; + +test("getParentDirPrefix", (t) => { + t.is(GlobRemap.getParentDirPrefix(""), ""); + t.is(GlobRemap.getParentDirPrefix("./test/"), ""); + t.is(GlobRemap.getParentDirPrefix("../test/"), "../"); + t.is(GlobRemap.getParentDirPrefix("../test/../"), "../"); + t.is(GlobRemap.getParentDirPrefix("../../test/"), "../../"); +}); + +test("getCwd", (t) => { + t.is(GlobRemap.getCwd([]), ""); + t.is(GlobRemap.getCwd(["test.njk"]), ""); + t.is(GlobRemap.getCwd(["./test.njk"]), ""); + t.is(GlobRemap.getCwd(["../test.njk"]), "../"); + t.is(GlobRemap.getCwd(["../test.njk", "../../2.njk"]), "../../"); +}); + +test("Constructor (control)", t => { + let m = new GlobRemap([ + '**/*.{liquid,md,njk,html,11ty.js,11ty.cjs,11ty.mjs}', + '**/*.txt', // passthrough copy + '**/*.png', + '_includes/**', + '_data/**', + '.gitignore', + '.eleventyignore', + 'eleventy.config.js', + ]) + + t.deepEqual(m.getInput(), [ + '**/*.{liquid,md,njk,html,11ty.js,11ty.cjs,11ty.mjs}', + '**/*.txt', // passthrough copy + '**/*.png', + '_includes/**', + '_data/**', + '.gitignore', + '.eleventyignore', + 'eleventy.config.js', + ]) +}); + +test("Constructor (control with ./)", t => { + let m = new GlobRemap([ + './**/*.{liquid,md,njk,html,11ty.js,11ty.cjs,11ty.mjs}', + './**/*.txt', // passthrough copy + './**/*.png', + './_includes/**', + './_data/**', + './.gitignore', + './.eleventyignore', + './eleventy.config.js', + ]) + + t.deepEqual(m.getInput(), [ + './**/*.{liquid,md,njk,html,11ty.js,11ty.cjs,11ty.mjs}', + './**/*.txt', // passthrough copy + './**/*.png', + './_includes/**', + './_data/**', + './.gitignore', + './.eleventyignore', + './eleventy.config.js', + ]) +}); + +test("Constructor (up one dir)", t => { + let m = new GlobRemap([ + '../**/*.{liquid,md,njk,html,11ty.js,11ty.cjs,11ty.mjs}', + '../**/*.txt', // passthrough copy + '../**/*.png', + '../_includes/**', + '../_data/**', + './.gitignore', + './.eleventyignore', + '../.eleventyignore', + './eleventy.config.js', + ]) + + let parentDir = normalizeSeparatorString(path.resolve("./").split(path.sep).slice(-1).join(path.sep)); + + t.deepEqual(m.getInput(), [ + '**/*.{liquid,md,njk,html,11ty.js,11ty.cjs,11ty.mjs}', + '**/*.txt', // passthrough copy + '**/*.png', + '_includes/**', + '_data/**', + `${parentDir}/.gitignore`, + `${parentDir}/.eleventyignore`, + '.eleventyignore', + `${parentDir}/eleventy.config.js`, + ]) +}); + +test("Constructor (up two dirs)", t => { + let m = new GlobRemap([ + '../../**/*.{liquid,md,njk,html,11ty.js,11ty.cjs,11ty.mjs}', + '../**/*.txt', // passthrough copy + '../**/*.png', + '../_includes/**', + '../_data/**', + './.gitignore', + './.eleventyignore', + '../.eleventyignore', + './eleventy.config.js', + ]) + + let childDir = normalizeSeparatorString(path.resolve("./").split(path.sep).slice(-2).join(path.sep)); + let parentDir = normalizeSeparatorString(path.resolve("./").split(path.sep).slice(-2, -1).join(path.sep)); + + t.deepEqual(m.getInput(), [ + '**/*.{liquid,md,njk,html,11ty.js,11ty.cjs,11ty.mjs}', + `${parentDir}/**/*.txt`, // passthrough copy + `${parentDir}/**/*.png`, + `${parentDir}/_includes/**`, + `${parentDir}/_data/**`, + `${childDir}/.gitignore`, + `${childDir}/.eleventyignore`, + `${parentDir}/.eleventyignore`, + `${childDir}/eleventy.config.js`, + ]) +}); diff --git a/test/GlobalDependencyMapTest.js b/test/GlobalDependencyMapTest.js index 5881435dc..9a74b54c0 100644 --- a/test/GlobalDependencyMapTest.js +++ b/test/GlobalDependencyMapTest.js @@ -80,6 +80,7 @@ test("Collection API", (t) => { t.deepEqual(map.getTemplateOrder(), [ "test.njk", + "__collection:[keys]", "__collection:articles", "feed.njk", "__collection:all", diff --git a/test/Issue3850Test.js b/test/Issue3850Test.js new file mode 100644 index 000000000..60f067d2d --- /dev/null +++ b/test/Issue3850Test.js @@ -0,0 +1,26 @@ +import test from "ava"; +import Eleventy from "../src/Eleventy.js"; + +test("#3850 Computed Data regression part 2", async (t) => { + let elev = new Eleventy("test/noop", false, { + config(eleventyConfig) { + eleventyConfig.addTemplate("index.njk", `--- +site: + download_link_mac: "http://example.com/" +eleventyComputed: + downloads: + - + links: + - + url: "{{ site.download_link_mac }}" +--- +{{ site.download_link_mac }}:::{{ downloads | dump | safe }}`); + } + }); + + let results = await elev.toJSON(); + results.sort(); + + t.is(results.length, 1); + t.is(results[0]?.content.trim(), `http://example.com/:::[{"links":[{"url":"http://example.com/"}]}]`) +}); diff --git a/test/Issue3853Test.js b/test/Issue3853Test.js new file mode 100644 index 000000000..167c5916a --- /dev/null +++ b/test/Issue3853Test.js @@ -0,0 +1,23 @@ +import test from "ava"; +import path from "node:path"; +import { TemplatePath } from "@11ty/eleventy-utils"; + +import { spawnAsync } from "../src/Util/SpawnAsync.js"; + +test("#3853 absolute path input should strip output from permalink", async (t) => { + let input = path.join(process.cwd(), "test/_issues/3853/deeper"); + let output = path.join(process.cwd(), "test/_issues/3853/public/site"); + let result = await spawnAsync( + "node", + ["../../../cmd.cjs", `--input=${input}`, `--output=${output}`, "--to=json"], + { + cwd: "test/_issues/3853/" + } + ); + + let json = JSON.parse(result); + + t.is(json.length, 1); + t.is(json[0]?.outputPath, TemplatePath.standardizeFilePath(path.join(output, "index.html"))); + t.is(json[0]?.content.trim(), "3853"); +}); diff --git a/test/Issue3854Test.js b/test/Issue3854Test.js new file mode 100644 index 000000000..50e90b3c8 --- /dev/null +++ b/test/Issue3854Test.js @@ -0,0 +1,23 @@ +import test from "ava"; + +import { spawnAsync } from "../src/Util/SpawnAsync.js"; + +test("#3854 parent directory for content, with global data files", async (t) => { + let result = await spawnAsync( + "node", + ["../../../../cmd.cjs", "--to=json"], + { + cwd: "test/_issues/3854/app/" + } + ); + + let json = JSON.parse(result); + t.is(json.length, 2); + + json.sort((a, b) => { + return a.inputPath.length - b.inputPath.length; + }) + + t.is(json[0]?.content.trim(), "3854/parent"); + t.is(json[1]?.content.trim(), "3854/child"); +}); diff --git a/test/Issue3860Test.js b/test/Issue3860Test.js new file mode 100644 index 000000000..c34c2f6d9 --- /dev/null +++ b/test/Issue3860Test.js @@ -0,0 +1,30 @@ +import test from "ava"; +import Eleventy from "../src/Eleventy.js"; + +test("#3860 addCollection consumes `collections` but is missing `collections.all`", async (t) => { + let elev = new Eleventy("test/noop", false, { + config(eleventyConfig) { + eleventyConfig.addFilter("keys", obj => Object.keys(obj)); + eleventyConfig.addTemplate("post1.md", "# Post1", { tags: ["bar"]}); + eleventyConfig.addTemplate("post2.md", "# Post2", { tags: ["foo"]}); + + eleventyConfig.addTemplate("tag.njk", "{{ collections | keys }}", { + pagination: { + data: "collections", + size: 1, + alias: "collection", + }, + permalink: "tag/{{collection}}/index.html", + eleventyExcludeFromCollections: true, + }); + } + }); + + let results = await elev.toJSON(); + + let tagPages = results.filter((entry) => entry.inputPath.endsWith("tag.njk")); + t.is(tagPages.length, 3); + t.is(tagPages[0]?.content.trim(), `bar,foo,all`) + t.is(tagPages[1]?.content.trim(), `bar,foo,all`) + t.is(tagPages[2]?.content.trim(), `bar,foo,all`) +}); diff --git a/test/Issue3870IncrementalTest.js b/test/Issue3870IncrementalTest.js new file mode 100644 index 000000000..fdeb8c66e --- /dev/null +++ b/test/Issue3870IncrementalTest.js @@ -0,0 +1,55 @@ +import test from "ava"; +import Eleventy from "../src/Eleventy.js"; + +// This tests Eleventy Watch WITHOUT using the file system! + +test("#3870 templateRender has not yet initialized (not incremental)", async (t) => { + let runs = [ + { + expected: `[]`, + }, + { + expected: `[]`, + }, + ]; + + t.plan(runs.length + 1); + + let index = 0; + let elev = new Eleventy("test/stubs-virtual/", "test/stubs-virtual/_site", { + configPath: "test/stubs-virtual/eleventy.config.js", + config(eleventyConfig) { + eleventyConfig.addTemplate("search.11ty.js", class { + data() { + return { + permalink: '/search.json', + // permalink: false, + layout: false, + eleventyExcludeFromCollections: true, + }; + } + + async render(data) { + return '[]'; + } + }); + + eleventyConfig.on("eleventy.after", ({ results }) => { + t.is(results[0]?.content, runs[index].expected); + }); + } + }); + + elev.disableLogger(); + elev.setIncrementalBuild(true); + await elev.init(); + + let asyncTriggerFn = await elev.watch(); + + for(let run of runs) { + await asyncTriggerFn("test/stubs-virtual/eleventy.config.js"); + index++; + } + + await elev.stopWatch(); +}); diff --git a/test/Issue3870Test.js b/test/Issue3870Test.js new file mode 100644 index 000000000..0535f74b2 --- /dev/null +++ b/test/Issue3870Test.js @@ -0,0 +1,54 @@ +import test from "ava"; +import Eleventy from "../src/Eleventy.js"; + +// This tests Eleventy Watch WITHOUT using the file system! + +test("#3870 templateRender has not yet initialized (not incremental)", async (t) => { + let runs = [ + { + expected: `[]`, + }, + { + expected: `[]`, + }, + ]; + + t.plan(runs.length + 1); + + let index = 0; + let elev = new Eleventy("test/stubs-virtual/", "test/stubs-virtual/_site", { + configPath: "test/stubs-virtual/eleventy.config.js", + config(eleventyConfig) { + eleventyConfig.addTemplate("search.11ty.js", class { + data() { + return { + permalink: '/search.json', + // permalink: false, + layout: false, + eleventyExcludeFromCollections: true, + }; + } + + async render(data) { + return '[]'; + } + }); + + eleventyConfig.on("eleventy.after", ({ results }) => { + t.is(results[0]?.content, runs[index].expected); + }); + } + }); + + elev.disableLogger(); + await elev.init(); + + let asyncTriggerFn = await elev.watch(); + + for(let run of runs) { + await asyncTriggerFn("test/stubs-virtual/eleventy.config.js"); + index++; + } + + await elev.stopWatch(); +}); diff --git a/test/Issue3875Test.js b/test/Issue3875Test.js new file mode 100644 index 000000000..49c272b49 --- /dev/null +++ b/test/Issue3875Test.js @@ -0,0 +1,82 @@ +import test from "ava"; +import Eleventy from "../src/Eleventy.js"; + +test("#3875 numeric tags", async (t) => { + let elev = new Eleventy("test/noop", false, { + config(eleventyConfig) { + eleventyConfig.addFilter("keys", (obj) => Object.keys(obj)); + eleventyConfig.addTemplate("index.njk", "{{ collections | keys }}", { + tags: [1,2,3] + }); + } + }); + + let results = await elev.toJSON(); + t.is(results[0].content, "1,2,3,all"); +}); + +test("#3875 numeric tags (via front matter)", async (t) => { + let elev = new Eleventy("test/noop", false, { + config(eleventyConfig) { + eleventyConfig.addFilter("keys", (obj) => Object.keys(obj)); + eleventyConfig.addTemplate("index.njk", `--- +tags: + - 1 + - 2 + - 3 +--- +{{ collections | keys }}`); + } + }); + + let results = await elev.toJSON(); + t.is(results[0].content, "1,2,3,all"); +}); + +test("#3875 consume a numeric tag collection (njk)", async (t) => { + let elev = new Eleventy("test/noop", false, { + config(eleventyConfig) { + eleventyConfig.addFilter("keyTypes", (obj) => Object.keys(obj).map(entry => typeof entry).join(",")); + eleventyConfig.addTemplate("child.njk", "", { + tags: [1] + }); + eleventyConfig.addTemplate("index.njk", `{{ collections | keyTypes }}:{{ collections[1].length }}:{{ collections['1'].length }}`); + } + }); + + let results = await elev.toJSON(); + t.is(results.filter(entry => entry.inputPath.endsWith("index.njk"))[0].content, "string,string:1:1"); +}); + +test("#3875 consume a numeric tag collection (liquid)", async (t) => { + let elev = new Eleventy("test/noop", false, { + config(eleventyConfig) { + eleventyConfig.addFilter("keyTypes", (obj) => Object.keys(obj).map(entry => typeof entry).join(",")); + eleventyConfig.addTemplate("child.njk", "", { + tags: [1] + }); + eleventyConfig.addTemplate("index.liquid", `{{ collections | keyTypes }}:{{ collections[1].length }}:{{ collections['1'].length }}`); + } + }); + + let results = await elev.toJSON(); + t.is(results.filter(entry => entry.inputPath.endsWith("index.liquid"))[0].content, "string,string:1:1"); +}); + +test("#3875 consume a numeric tag collection (11ty.js)", async (t) => { + let elev = new Eleventy("test/noop", false, { + config(eleventyConfig) { + eleventyConfig.addTemplate("child.njk", "", { + tags: [1] + }); + eleventyConfig.addTemplate("index.11ty.js", { + render(data) { + return `${Object.keys(data.collections).map(entry => typeof entry).join(",")}:${data.collections[1].length}:${data.collections['1'].length}` + } + }); + } + }); + + let results = await elev.toJSON(); + t.is(results.filter(entry => entry.inputPath.endsWith("index.11ty.js"))[0].content, "string,string:1:1"); +}); diff --git a/test/TemplateDepGraphTest.js b/test/TemplateDepGraphTest.js index 1ef95fcc6..9ed766474 100644 --- a/test/TemplateDepGraphTest.js +++ b/test/TemplateDepGraphTest.js @@ -33,6 +33,7 @@ test("Using new Template DepGraph", async (t) => { "__collection:dog", "template-paginated-over-userconfig.njk", "__collection:myCollection", + "__collection:[keys]", "template-paginated-collections.njk", "__collection:all", "template-paginated-over-all.njk", diff --git a/test/TemplateMapTest.js b/test/TemplateMapTest.js index ba368dcb1..b347a20a2 100644 --- a/test/TemplateMapTest.js +++ b/test/TemplateMapTest.js @@ -907,6 +907,7 @@ test("Dependency Map should have include orphan user config collections (in the '__collection:dog', './test/stubs/templateMapCollection/test5.md', '__collection:userCollection', + '__collection:[keys]', '__collection:all' ]); @@ -941,6 +942,7 @@ test("Dependency Map should have include orphan user config collections, mapped '__collection:dog', './test/stubs/templateMapCollection/test5.md', '__collection:userCollection', + '__collection:[keys]', '__collection:all', ]); @@ -1406,6 +1408,7 @@ test("TemplateMap circular references (map.templateContent) using eleventyExclud t.deepEqual(deps, [ "./test/stubs/issue-522/excluded.md", "./test/stubs/issue-522/template.md", + "__collection:[keys]", "__collection:all", ]); diff --git a/test/TemplateTest.js b/test/TemplateTest.js index 8a59e4cab..e67f39c4d 100644 --- a/test/TemplateTest.js +++ b/test/TemplateTest.js @@ -9,7 +9,6 @@ import EleventyExtensionMap from "../src/EleventyExtensionMap.js"; import EleventyErrorUtil from "../src/Errors/EleventyErrorUtil.js"; import TemplateContentPrematureUseError from "../src/Errors/TemplateContentPrematureUseError.js"; import { normalizeNewLines } from "./Util/normalizeNewLines.js"; -import eventBus from "../src/EventBus.js"; import getNewTemplate from "./_getNewTemplateForTests.js"; import { getRenderedTemplates as getRenderedTmpls, renderLayout, renderTemplate } from "./_getRenderedTemplates.js"; @@ -1794,7 +1793,8 @@ test("Make sure layout cache takes new changes during watch (liquid)", async (t) fs.writeFileSync(filePath, `alert("bye");`, "utf8"); - eventBus.emit("eleventy.resourceModified", filePath); + // Trigger that the file has changed + tmpl.eleventyConfig.setPreviousBuildModifiedFile(filePath); t.is((await renderTemplate(tmpl, data)).trim(), ''); }); diff --git a/test/Util/normalizeSeparators.js b/test/Util/normalizeSeparators.js new file mode 100644 index 000000000..22b0a0d87 --- /dev/null +++ b/test/Util/normalizeSeparators.js @@ -0,0 +1,11 @@ +import PathNormalizer from "../../src/Util/PathNormalizer.js"; + +export function normalizeSeparatorString(str) { + return PathNormalizer.normalizeSeperator(str); +} + +export function normalizeSeparatorArray(arr) { + return arr.map(entry => { + return PathNormalizer.normalizeSeperator(entry); + }) +} diff --git a/test/Util/removeNewLines.js b/test/Util/removeNewLines.js deleted file mode 100644 index eef56384e..000000000 --- a/test/Util/removeNewLines.js +++ /dev/null @@ -1,5 +0,0 @@ -function removeNewLines(str) { - return str.replace(/[\r\n]*/g, ""); -} - -module.exports = removeNewLines; diff --git a/test/_issues/3853/deeper/index.njk b/test/_issues/3853/deeper/index.njk new file mode 100644 index 000000000..6129a53e9 --- /dev/null +++ b/test/_issues/3853/deeper/index.njk @@ -0,0 +1 @@ +3853 diff --git a/test/_issues/3854/app/.eleventy.js b/test/_issues/3854/app/.eleventy.js new file mode 100644 index 000000000..51c7ee6e2 --- /dev/null +++ b/test/_issues/3854/app/.eleventy.js @@ -0,0 +1,5 @@ +export const config = { + dir: { + input: "../", + } +}; diff --git a/test/_issues/3854/app/index.njk b/test/_issues/3854/app/index.njk new file mode 100644 index 000000000..3db4023e3 --- /dev/null +++ b/test/_issues/3854/app/index.njk @@ -0,0 +1 @@ +3854/child diff --git a/test/_issues/3854/index.njk b/test/_issues/3854/index.njk new file mode 100644 index 000000000..045c3571d --- /dev/null +++ b/test/_issues/3854/index.njk @@ -0,0 +1 @@ +3854/parent diff --git a/test/_issues/975/975-test.js b/test/_issues/975/975-test.js index 39d24b10d..a30f605ba 100644 --- a/test/_issues/975/975-test.js +++ b/test/_issues/975/975-test.js @@ -54,6 +54,7 @@ test("Get ordered list of templates", async (t) => { "./test/_issues/975/post.md", "./test/_issues/975/another-post.md", "__collection:post", + "__collection:[keys]", "./test/_issues/975/index.md", "__collection:all", ]); @@ -105,6 +106,7 @@ test("Get ordered list of templates (reverse add)", async (t) => { "./test/_issues/975/another-post.md", "./test/_issues/975/post.md", "__collection:post", + "__collection:[keys]", "./test/_issues/975/index.md", "__collection:all", ]); diff --git a/test/stubs-virtual/eleventy.config.js b/test/stubs-virtual/eleventy.config.js new file mode 100644 index 000000000..df22a2d10 --- /dev/null +++ b/test/stubs-virtual/eleventy.config.js @@ -0,0 +1,2 @@ +// generic config file +export default function(eleventyConfig) {}; \ No newline at end of file diff --git a/test_node/3824-incremental/3824-incremental-test.js b/test_node/3824-incremental/3824-incremental-test.js new file mode 100644 index 000000000..82ca8b5a0 --- /dev/null +++ b/test_node/3824-incremental/3824-incremental-test.js @@ -0,0 +1,98 @@ +// This test file is using Node’s test runner because `tsx` doesn’t work with worker threads (used by avajs) +// See https://github.com/privatenumber/tsx/issues/354 +// See https://github.com/nodejs/node/issues/47747 +import test from "node:test"; +import fs from "node:fs"; +import assert from "node:assert"; + +import Eleventy from "../../src/Eleventy.js"; +import { withResolvers } from "../../src/Util/PromiseUtil.js"; + +// This tests Eleventy Watch and the file system! + +function getInputContent(str = "") { + return `import { Page } from "./ViewProps.js"; + +export type HeadProps = { + page: Page +}; + +export function Head(props: HeadProps): JSX.Element { + return
+Hello World
`; +} + +test( + "#3824 TSX updates during watch (incremental)", + { + timeout: 10000, + }, + async () => { + let comparisonStrings = ["first", "second"]; + + let runs = comparisonStrings.map((str) => { + return { + ...withResolvers(), + input: getInputContent(str), + expected: getOutputContent(str), + }; + }); + + // Restore original content + const ROOT_DIR = "./test_node/3824-incremental/"; + const OUTPUT_DIR = ROOT_DIR + "_site/"; + + const FILE_CHANGING = ROOT_DIR + "_includes/head.tsx"; + const OUTPUT_FILE = OUTPUT_DIR + "index.html"; + + fs.writeFileSync(FILE_CHANGING, getInputContent(), "utf8"); + + let index = 0; + let elev = new Eleventy(ROOT_DIR, OUTPUT_DIR, { + configPath: ROOT_DIR + "eleventy.config.js", + config(eleventyConfig) { + eleventyConfig.on("eleventy.afterwatch", () => { + let { resolve } = runs[index]; + index++; + resolve(); + }); + }, + }); + + // Same as 3824-test.js except for this line + elev.setIncrementalBuild(true); + + elev.disableLogger(); + await elev.init(); + await elev.watch(); + + // Control + let content = fs.readFileSync(OUTPUT_FILE, "utf8"); + assert.equal(content, getOutputContent()); + + // Stop after all runs are complete + Promise.all(runs.map((entry) => entry.promise)).then(async () => { + await elev.stopWatch(); + }); + + for (let run of runs) { + // Windows needed this for Chokidar reasons + await new Promise((resolve) => setTimeout(resolve, 200)); + + fs.writeFileSync(FILE_CHANGING, run.input, "utf8"); + await run.promise; + + let content = fs.readFileSync(OUTPUT_FILE, "utf8"); + assert.equal(content, run.expected); + } + + fs.writeFileSync(FILE_CHANGING, getInputContent(), "utf8"); + fs.rmSync(OUTPUT_DIR, { recursive: true }); + }, +); diff --git a/test_node/3824-incremental/_includes/head.tsx b/test_node/3824-incremental/_includes/head.tsx new file mode 100644 index 000000000..054c301a0 --- /dev/null +++ b/test_node/3824-incremental/_includes/head.tsx @@ -0,0 +1,11 @@ +import { Page } from "./ViewProps.js"; + +export type HeadProps = { + page: Page +}; + +export function Head(props: HeadProps): JSX.Element { + return +Hello World
+ + ; +} + +export function render(props: ViewProps): JSX.Element { + returnHello World
`; +} + +test( + "#3824 TSX updates during watch", + { + timeout: 10000, + }, + async () => { + let comparisonStrings = ["first", "second"]; + + let runs = comparisonStrings.map((str) => { + return { + ...withResolvers(), + input: getInputContent(str), + expected: getOutputContent(str), + }; + }); + + // Restore original content + const ROOT_DIR = "./test_node/3824/"; + const OUTPUT_DIR = ROOT_DIR + "_site/"; + + const FILE_CHANGING = ROOT_DIR + "_includes/head.tsx"; + const OUTPUT_FILE = OUTPUT_DIR + "index.html"; + + fs.writeFileSync(FILE_CHANGING, getInputContent(), "utf8"); + + let index = 0; + let elev = new Eleventy(ROOT_DIR, OUTPUT_DIR, { + configPath: ROOT_DIR + "eleventy.config.js", + config(eleventyConfig) { + eleventyConfig.on("eleventy.afterwatch", () => { + let { resolve } = runs[index]; + index++; + resolve(); + }); + }, + }); + + elev.disableLogger(); + await elev.init(); + await elev.watch(); + + // Control + let content = fs.readFileSync(OUTPUT_FILE, "utf8"); + assert.equal(content, getOutputContent()); + + // Stop after all runs are complete + Promise.all(runs.map((entry) => entry.promise)).then(async () => { + await elev.stopWatch(); + }); + + for (let run of runs) { + // Windows needed this for Chokidar reasons + await new Promise((resolve) => setTimeout(resolve, 200)); + fs.writeFileSync(FILE_CHANGING, run.input, "utf8"); + + await run.promise; + + let content = fs.readFileSync(OUTPUT_FILE, "utf8"); + assert.equal(content, run.expected); + } + + fs.writeFileSync(FILE_CHANGING, getInputContent(), "utf8"); + fs.rmSync(OUTPUT_DIR, { recursive: true }); + }, +); diff --git a/test_node/3824/_includes/head.tsx b/test_node/3824/_includes/head.tsx new file mode 100644 index 000000000..054c301a0 --- /dev/null +++ b/test_node/3824/_includes/head.tsx @@ -0,0 +1,11 @@ +import { Page } from "./ViewProps.js"; + +export type HeadProps = { + page: Page +}; + +export function Head(props: HeadProps): JSX.Element { + return +Hello World
+ + ; +} + +export function render(props: ViewProps): JSX.Element { + return