This commit is contained in:
2024-11-03 17:16:20 +01:00
commit fd6412d6f2
8090 changed files with 778406 additions and 0 deletions

48
node_modules/@11ty/eleventy/CODE_OF_CONDUCT.md generated vendored Normal file
View File

@@ -0,0 +1,48 @@
# Eleventy Community Code of Conduct
View the [Code of Conduct](https://www.11ty.dev/docs/code-of-conduct/) on 11ty.dev
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
- Using welcoming and inclusive language
- Being respectful of differing viewpoints and experiences
- Gracefully accepting constructive criticism
- Focusing on what is best for the community
- Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
- The use of sexualized language or imagery and unwelcome sexual attention or advances
- Trolling, insulting/derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or electronic address, without explicit permission
- Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, chat messages, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at eleventy@zachleat.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/

21
node_modules/@11ty/eleventy/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 20172024 Zach Leatherman @zachleat
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

47
node_modules/@11ty/eleventy/README.md generated vendored Normal file
View File

@@ -0,0 +1,47 @@
<p align="center"><img src="https://www.11ty.dev/img/logo-github.svg" width="200" height="200" alt="eleventy Logo"></p>
# eleventy 🕚⚡️🎈🐀
A simpler static site generator. An alternative to Jekyll. Written in JavaScript. Transforms a directory of templates (of varying types) into HTML.
Works with HTML, Markdown, JavaScript, Liquid, Nunjucks, with addons for WebC, Sass, Vue, Svelte, TypeScript, JSX, and many others!
## ➡ [Documentation](https://www.11ty.dev/docs/)
- Please star [this repo on GitHub](https://github.com/11ty/eleventy/)!
- Follow us on Mastodon [@eleventy@fosstodon.org](https://fosstodon.org/@eleventy) or Twitter [@eleven_ty](https://twitter.com/eleven_ty)
- Join us on [Discord](https://www.11ty.dev/blog/discord/)
- Support [11ty on Open Collective](https://opencollective.com/11ty)
- [11ty on npm](https://www.npmjs.com/org/11ty)
- [11ty on GitHub](https://github.com/11ty)
[![npm Version](https://img.shields.io/npm/v/@11ty/eleventy.svg?style=for-the-badge)](https://www.npmjs.com/package/@11ty/eleventy) [![GitHub issues](https://img.shields.io/github/issues/11ty/eleventy.svg?style=for-the-badge)](https://github.com/11ty/eleventy/issues) [![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=for-the-badge)](https://github.com/prettier/prettier) [![npm Downloads](https://img.shields.io/npm/dt/@11ty/eleventy.svg?style=for-the-badge)](https://www.npmjs.com/package/@11ty/eleventy)
## Installation
```
npm install @11ty/eleventy --save-dev
```
Read our [Getting Started guide](https://www.11ty.dev/docs/getting-started/).
## Tests
```
npm run test
```
- We use the [ava JavaScript test runner](https://github.com/avajs/ava) ([Assertions documentation](https://github.com/avajs/ava/blob/master/docs/03-assertions.md))
- To keep tests fast, thou shalt try to avoid writing files in tests.
- [Continuous Integration on GitHub Actions](https://github.com/11ty/eleventy/actions/workflows/ci.yml)
- [Code Coverage Statistics](https://github.com/11ty/eleventy/blob/master/docs/coverage.md)
- [Benchmark for Performance Regressions](https://github.com/11ty/eleventy-benchmark)
## Community Roadmap
- [Top Feature Requests](https://github.com/11ty/eleventy/issues?q=label%3Aneeds-votes+sort%3Areactions-%2B1-desc+label%3Aenhancement) (Add your own votes using the 👍 reaction)
- [Top Bugs 😱](https://github.com/11ty/eleventy/issues?q=is%3Aissue+is%3Aopen+label%3Abug+sort%3Areactions) (Add your own votes using the 👍 reaction)
## Plugins
See the [official docs on plugins](https://www.11ty.dev/docs/plugins/).

9
node_modules/@11ty/eleventy/SECURITY.md generated vendored Normal file
View File

@@ -0,0 +1,9 @@
# Security Policy
## Reporting a Vulnerability
Privately report a security issue by navigating to https://github.com/11ty/eleventy/security and using the “Report a vulnerability” button.
Read more at: https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability
Alternatively, you may report security issues via an email to `security@11ty.dev`.

165
node_modules/@11ty/eleventy/cmd.cjs generated vendored Executable file
View File

@@ -0,0 +1,165 @@
#!/usr/bin/env node
// This file intentionally uses older code conventions to be as friendly
// as possible with error messaging to folks on older runtimes.
const pkg = require("./package.json");
require("please-upgrade-node")(pkg, {
message: function (requiredVersion) {
return (
"Eleventy " +
pkg.version +
" requires Node " +
requiredVersion +
". You will need to upgrade Node to use Eleventy!"
);
},
});
const minimist = require("minimist");
const debug = require("debug")("Eleventy:cmd");
(async function () {
const { EleventyErrorHandler } = await import("./src/Errors/EleventyErrorHandler.js");
class SimpleError extends Error {
constructor(...args) {
super(...args);
this.skipOriginalStack = true;
}
}
try {
const argv = minimist(process.argv.slice(2), {
string: ["input", "output", "formats", "config", "pathprefix", "port", "to", "incremental", "loader"],
boolean: [
"quiet",
"version",
"watch",
"dryrun",
"help",
"serve",
"ignore-initial",
],
default: {
quiet: null,
"ignore-initial": false,
"to": "fs",
},
unknown: function (unknownArgument) {
throw new Error(
`We dont know what '${unknownArgument}' is. Use --help to see the list of supported commands.`,
);
},
});
debug("command: eleventy %o", argv);
const { Eleventy } = await import("./src/Eleventy.js");
let ErrorHandler = new EleventyErrorHandler();
process.on("unhandledRejection", (error, promise) => {
ErrorHandler.fatal(error, "Unhandled rejection in promise");
});
process.on("uncaughtException", (error) => {
ErrorHandler.fatal(error, "Uncaught exception");
});
process.on("rejectionHandled", (promise) => {
ErrorHandler.warn(promise, "A promise rejection was handled asynchronously");
});
if (argv.version) {
console.log(Eleventy.getVersion());
return;
} else if (argv.help) {
console.log(Eleventy.getHelp());
return;
}
let elev = new Eleventy(argv.input, argv.output, {
source: "cli",
// --quiet and --quiet=true both resolve to true
quietMode: argv.quiet,
configPath: argv.config,
pathPrefix: argv.pathprefix,
runMode: argv.serve ? "serve" : argv.watch ? "watch" : "build",
dryRun: argv.dryrun,
loader: argv.loader,
});
// reuse ErrorHandler instance in Eleventy
ErrorHandler = elev.errorHandler;
// Before init
elev.setFormats(argv.formats);
// careful, we cant use async/await here to error properly
// with old node versions in `please-upgrade-node` above.
elev
.init()
.then(() => {
if (argv.to === "json" || argv.to === "ndjson") {
// override logging output
elev.setIsVerbose(false);
}
// Only relevant for watch/serve
elev.setIgnoreInitial(argv["ignore-initial"]);
if(argv.incremental) {
elev.setIncrementalFile(argv.incremental);
} else if(argv.incremental !== undefined) {
elev.setIncrementalBuild(argv.incremental === "" || argv.incremental);
}
if (argv.serve || argv.watch) {
if(argv.to === "json" || argv.to === "ndjson") {
throw new SimpleError("--to=json and --to=ndjson are not compatible with --serve or --watch.");
}
elev
.watch()
.then(() => {
if (argv.serve) {
elev.serve(argv.port);
}
}, error => {
// A build error occurred and we arent going to --serve
ErrorHandler.once("error", error, "Eleventy Error (Watch CLI)");
});
process.on("SIGINT", async () => {
await elev.stopWatch();
process.exitCode = 0;
});
} else {
if (!argv.to || argv.to === "fs") {
elev.write().catch(error => {
ErrorHandler.once("fatal", error, "Eleventy Error (FS CLI)");
});
} else if (argv.to === "json") {
elev.toJSON().then(function (result) {
console.log(JSON.stringify(result, null, 2));
}, error => {
ErrorHandler.once("fatal", error, "Eleventy Error (JSON CLI)");
});
} else if (argv.to === "ndjson") {
elev.toNDJSON().then(function (stream) {
stream.pipe(process.stdout);
}, error => {
ErrorHandler.once("fatal", error, "Eleventy Error (JSON CLI)");
});
} else {
throw new SimpleError(
`Invalid --to value: ${argv.to}. Supported values: \`fs\` (default), \`json\`, and \`ndjson\`.`,
);
}
}
}).catch(error => {
ErrorHandler.fatal(error, "Eleventy Error (CLI)");
});
} catch (error) {
let ErrorHandler = new EleventyErrorHandler();
ErrorHandler.fatal(error, "Eleventy Fatal Error (CLI)");
}
})();

155
node_modules/@11ty/eleventy/package.json generated vendored Normal file
View File

@@ -0,0 +1,155 @@
{
"name": "@11ty/eleventy",
"version": "3.0.0",
"description": "A simpler static site generator.",
"publishConfig": {
"access": "public",
"provenance": true
},
"type": "module",
"main": "./src/Eleventy.js",
"exports": {
"import": "./src/Eleventy.js",
"require": "./src/EleventyCommonJs.cjs"
},
"bin": {
"eleventy": "cmd.cjs"
},
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/11ty"
},
"keywords": [
"static-site-generator",
"static-site",
"ssg",
"documentation",
"website",
"jekyll",
"blog",
"templates",
"generator",
"framework",
"eleventy",
"11ty",
"html",
"markdown",
"liquid",
"nunjucks"
],
"scripts": {
"default": "npm run test",
"test": "npm run test:node && npm run test:ava",
"test:ava": "ava --verbose --timeout 20s",
"test:node": "node --test test_node/tests.js",
"format": "prettier . --write",
"check": "eslint src",
"check-types": "tsc",
"lint-staged": "lint-staged",
"coverage": "npx c8 ava && npx c8 report --reporter=json-summary && cp coverage/coverage-summary.json docs/_data/coverage.json && node cmd.cjs --config=docs/eleventy.coverage.js",
"prepare": "husky"
},
"author": "Zach Leatherman <zachleatherman@gmail.com> (https://zachleat.com/)",
"repository": {
"type": "git",
"url": "git://github.com/11ty/eleventy.git"
},
"bugs": "https://github.com/11ty/eleventy/issues",
"homepage": "https://www.11ty.dev/",
"ava": {
"environmentVariables": {},
"failFast": true,
"files": [
"./test/*.js",
"./test/_issues/**/*test.js"
],
"watchMode": {
"ignoreChanges": [
"./test/stubs*/**/*",
".cache"
]
}
},
"lint-staged": {
"*.{js,css,md}": [
"prettier --write"
]
},
"devDependencies": {
"@11ty/eleventy-img": "5.0.0-beta.10",
"@11ty/eleventy-plugin-rss": "^2.0.2",
"@11ty/eleventy-plugin-syntaxhighlight": "^5.0.0",
"@eslint/eslintrc": "^3.1.0",
"@eslint/js": "^9.11.1",
"@iarna/toml": "^2.2.5",
"@mdx-js/node-loader": "^3.0.1",
"@types/node": "^22.7.4",
"@vue/server-renderer": "^3.5.10",
"@zachleat/noop": "^1.0.4",
"ava": "^6.1.3",
"c8": "^10.1.2",
"cross-env": "^7.0.3",
"eslint": "^9.11.1",
"eslint-config-prettier": "^9.1.0",
"globals": "^15.10.0",
"husky": "^9.1.6",
"lint-staged": "^15.2.10",
"markdown-it-emoji": "^3.0.0",
"marked": "^14.1.2",
"prettier": "^3.3.3",
"pretty": "^2.0.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"rimraf": "^6.0.1",
"sass": "^1.79.4",
"tsx": "^4.19.1",
"typescript": "^5.6.2",
"vue": "^3.5.10",
"zod": "^3.23.8",
"zod-validation-error": "^3.4.0"
},
"dependencies": {
"@11ty/dependency-tree": "^3.0.1",
"@11ty/dependency-tree-esm": "^1.0.0",
"@11ty/eleventy-dev-server": "^2.0.4",
"@11ty/eleventy-plugin-bundle": "^3.0.0",
"@11ty/eleventy-utils": "^1.0.3",
"@11ty/lodash-custom": "^4.17.21",
"@11ty/posthtml-urls": "^1.0.0",
"@11ty/recursive-copy": "^3.0.0",
"@sindresorhus/slugify": "^2.2.1",
"bcp-47-normalize": "^2.3.0",
"chardet": "^2.0.0",
"chokidar": "^3.6.0",
"cross-spawn": "^7.0.3",
"debug": "^4.3.7",
"dependency-graph": "^1.0.0",
"entities": "^5.0.0",
"fast-glob": "^3.3.2",
"filesize": "^10.1.6",
"graceful-fs": "^4.2.11",
"gray-matter": "^4.0.3",
"is-glob": "^4.0.3",
"iso-639-1": "^3.1.3",
"js-yaml": "^4.1.0",
"kleur": "^4.1.5",
"liquidjs": "^10.17.0",
"luxon": "^3.5.0",
"markdown-it": "^14.1.0",
"micromatch": "^4.0.8",
"minimist": "^1.2.8",
"moo": "^0.5.2",
"node-retrieve-globals": "^6.0.0",
"normalize-path": "^3.0.0",
"nunjucks": "^3.2.4",
"please-upgrade-node": "^3.2.0",
"posthtml": "^0.16.6",
"posthtml-match-helper": "^2.0.2",
"semver": "^7.6.3",
"slugify": "^1.6.6"
}
}

55
node_modules/@11ty/eleventy/src/Benchmark/Benchmark.js generated vendored Normal file
View File

@@ -0,0 +1,55 @@
import { performance } from "node:perf_hooks";
class Benchmark {
constructor() {
// TypeScript slop
this.timeSpent = 0;
this.timesCalled = 0;
this.beforeTimers = [];
}
reset() {
this.timeSpent = 0;
this.timesCalled = 0;
this.beforeTimers = [];
}
getNewTimestamp() {
if (performance) {
return performance.now();
}
return new Date().getTime();
}
incrementCount() {
this.timesCalled++;
}
// TODO(slightlyoff):
// disable all of these hrtime requests when not benchmarking
before() {
this.timesCalled++;
this.beforeTimers.push(this.getNewTimestamp());
}
after() {
if (!this.beforeTimers.length) {
throw new Error("You called Benchmark after() without a before().");
}
let before = this.beforeTimers.pop();
if (!this.beforeTimers.length) {
this.timeSpent += this.getNewTimestamp() - before;
}
}
getTimesCalled() {
return this.timesCalled;
}
getTotal() {
return this.timeSpent;
}
}
export default Benchmark;

View File

@@ -0,0 +1,135 @@
import debugUtil from "debug";
import ConsoleLogger from "../Util/ConsoleLogger.js";
import isAsyncFunction from "../Util/IsAsyncFunction.js";
import Benchmark from "./Benchmark.js";
const debugBenchmark = debugUtil("Eleventy:Benchmark");
class BenchmarkGroup {
constructor() {
this.benchmarks = {};
// Warning: aggregate benchmarks automatically default to false via BenchmarkManager->getBenchmarkGroup
this.isVerbose = true;
this.logger = new ConsoleLogger();
this.minimumThresholdMs = 50;
this.minimumThresholdPercent = 8;
}
setIsVerbose(isVerbose) {
this.isVerbose = isVerbose;
this.logger.isVerbose = isVerbose;
}
reset() {
for (var type in this.benchmarks) {
this.benchmarks[type].reset();
}
}
// TODO use addAsync everywhere instead
add(type, callback) {
let benchmark = (this.benchmarks[type] = new Benchmark());
/** @this {any} */
let fn = function (...args) {
benchmark.before();
let ret = callback.call(this, ...args);
benchmark.after();
return ret;
};
Object.defineProperty(fn, "__eleventyInternal", {
value: {
type: isAsyncFunction(callback) ? "async" : "sync",
callback,
},
});
return fn;
}
// callback must return a promise
// async addAsync(type, callback) {
// let benchmark = (this.benchmarks[type] = new Benchmark());
// benchmark.before();
// // dont await here.
// let promise = callback.call(this);
// promise.then(function() {
// benchmark.after();
// });
// return promise;
// }
setMinimumThresholdMs(minimumThresholdMs) {
let val = parseInt(minimumThresholdMs, 10);
if (isNaN(val)) {
throw new Error("`setMinimumThresholdMs` expects a number argument.");
}
this.minimumThresholdMs = val;
}
setMinimumThresholdPercent(minimumThresholdPercent) {
let val = parseInt(minimumThresholdPercent, 10);
if (isNaN(val)) {
throw new Error("`setMinimumThresholdPercent` expects a number argument.");
}
this.minimumThresholdPercent = val;
}
has(type) {
return !!this.benchmarks[type];
}
get(type) {
if (!this.benchmarks[type]) {
this.benchmarks[type] = new Benchmark();
}
return this.benchmarks[type];
}
padNumber(num, length) {
if (("" + num).length >= length) {
return num;
}
let prefix = new Array(length + 1).join(" ");
return (prefix + num).slice(-1 * length);
}
finish(label, totalTimeSpent) {
for (var type in this.benchmarks) {
let bench = this.benchmarks[type];
let isAbsoluteMinimumComparison = this.minimumThresholdMs > 0;
let totalForBenchmark = bench.getTotal();
let percent = Math.round((totalForBenchmark * 100) / totalTimeSpent);
let callCount = bench.getTimesCalled();
let output = {
ms: this.padNumber(totalForBenchmark.toFixed(0), 6),
percent: this.padNumber(percent, 3),
calls: this.padNumber(callCount, 5),
};
let str = `Benchmark ${output.ms}ms ${output.percent}% ${output.calls}× (${label}) ${type}`;
if (
isAbsoluteMinimumComparison &&
totalForBenchmark >= this.minimumThresholdMs &&
percent > this.minimumThresholdPercent
) {
this.logger.warn(str);
}
// Opt out of logging if low count (1× or 2×) or 0ms / 1%
if (
callCount > 1 || // called more than once
Math.round(totalForBenchmark) > 0 // more than 0.5ms
) {
debugBenchmark(str);
}
}
}
}
export default BenchmarkGroup;

View File

@@ -0,0 +1,73 @@
import { performance } from "node:perf_hooks";
import BenchmarkGroup from "./BenchmarkGroup.js";
// TODO this should not be a singleton, it belongs in the config or somewhere on the Eleventy instance.
class BenchmarkManager {
constructor() {
this.benchmarkGroups = {};
this.isVerbose = true;
this.start = this.getNewTimestamp();
}
reset() {
this.start = this.getNewTimestamp();
for (var j in this.benchmarkGroups) {
this.benchmarkGroups[j].reset();
}
}
getNewTimestamp() {
if (performance) {
return performance.now();
}
return new Date().getTime();
}
setVerboseOutput(isVerbose) {
this.isVerbose = !!isVerbose;
}
hasBenchmarkGroup(name) {
return name in this.benchmarkGroups;
}
getBenchmarkGroup(name) {
if (!this.benchmarkGroups[name]) {
this.benchmarkGroups[name] = new BenchmarkGroup();
// Special behavior for aggregate benchmarks
// so they dont console.log every time
if (name === "Aggregate") {
this.benchmarkGroups[name].setIsVerbose(false);
} else {
this.benchmarkGroups[name].setIsVerbose(this.isVerbose);
}
}
return this.benchmarkGroups[name];
}
getAll() {
return this.benchmarkGroups;
}
get(name) {
if (name) {
return this.getBenchmarkGroup(name);
}
return this.getAll();
}
finish() {
let totalTimeSpentBenchmarking = this.getNewTimestamp() - this.start;
for (var j in this.benchmarkGroups) {
this.benchmarkGroups[j].finish(j, totalTimeSpentBenchmarking);
}
}
}
export default BenchmarkManager;

121
node_modules/@11ty/eleventy/src/Data/ComputedData.js generated vendored Normal file
View File

@@ -0,0 +1,121 @@
import lodash from "@11ty/lodash-custom";
import debugUtil from "debug";
import ComputedDataQueue from "./ComputedDataQueue.js";
import ComputedDataTemplateString from "./ComputedDataTemplateString.js";
import ComputedDataProxy from "./ComputedDataProxy.js";
const { set: lodashSet, get: lodashGet } = lodash;
const debug = debugUtil("Eleventy:ComputedData");
class ComputedData {
constructor(config) {
this.computed = {};
this.symbolParseFunctions = {};
this.templateStringKeyLookup = {};
this.computedKeys = new Set();
this.declaredDependencies = {};
this.queue = new ComputedDataQueue();
this.config = config;
}
add(key, renderFn, declaredDependencies = [], symbolParseFn, templateInstance) {
this.computedKeys.add(key);
this.declaredDependencies[key] = declaredDependencies;
// bind config filters/JS functions
if (typeof renderFn === "function") {
let fns = {};
// TODO bug? no access to non-universal config things?
if (this.config) {
fns = this.config.javascriptFunctions;
}
renderFn = renderFn.bind({
...fns,
tmpl: templateInstance,
});
}
lodashSet(this.computed, key, renderFn);
if (symbolParseFn) {
lodashSet(this.symbolParseFunctions, key, symbolParseFn);
}
}
addTemplateString(key, renderFn, declaredDependencies = [], symbolParseFn, templateInstance) {
this.add(key, renderFn, declaredDependencies, symbolParseFn, templateInstance);
this.templateStringKeyLookup[key] = true;
}
async resolveVarOrder(data) {
let proxyByTemplateString = new ComputedDataTemplateString(this.computedKeys);
let proxyByProxy = new ComputedDataProxy(this.computedKeys);
for (let key of this.computedKeys) {
let computed = lodashGet(this.computed, key);
if (typeof computed !== "function") {
// add nodes for non functions (primitives like booleans, etc)
// This will not handle template strings, as they are normalized to functions
this.queue.addNode(key);
} else {
this.queue.uses(key, this.declaredDependencies[key]);
let symbolParseFn = lodashGet(this.symbolParseFunctions, key);
let varsUsed = [];
if (symbolParseFn) {
// use the parseForSymbols function in the TemplateEngine
varsUsed = symbolParseFn();
} else if (symbolParseFn !== false) {
// skip resolution is this is false (just use declaredDependencies)
let isTemplateString = !!this.templateStringKeyLookup[key];
let proxy = isTemplateString ? proxyByTemplateString : proxyByProxy;
varsUsed = await proxy.findVarsUsed(computed, data);
}
debug("%o accesses %o variables", key, varsUsed);
let filteredVarsUsed = varsUsed.filter((varUsed) => {
return (
(varUsed !== key && this.computedKeys.has(varUsed)) ||
varUsed.startsWith("collections.")
);
});
this.queue.uses(key, filteredVarsUsed);
}
}
}
async _setupDataEntry(data, order) {
debug("Computed data order of execution: %o", order);
for (let key of order) {
let computed = lodashGet(this.computed, key);
if (typeof computed === "function") {
let ret = await computed(data);
lodashSet(data, key, ret);
} else if (computed !== undefined) {
lodashSet(data, key, computed);
}
}
}
async setupData(data, orderFilter) {
await this.resolveVarOrder(data);
await this.processRemainingData(data, orderFilter);
}
async processRemainingData(data, orderFilter) {
// process all variables
let order = this.queue.getOrder();
if (orderFilter && typeof orderFilter === "function") {
order = order.filter(orderFilter.bind(this.queue));
}
await this._setupDataEntry(data, order);
this.queue.markComputed(order);
}
}
export default ComputedData;

View File

@@ -0,0 +1,131 @@
import lodash from "@11ty/lodash-custom";
import { isPlainObject } from "@11ty/eleventy-utils";
const { set: lodashSet, get: lodashGet } = lodash;
/* Calculates computed data using Proxies */
class ComputedDataProxy {
constructor(computedKeys) {
if (Array.isArray(computedKeys)) {
this.computedKeys = new Set(computedKeys);
} else {
this.computedKeys = computedKeys;
}
}
isArrayOrPlainObject(data) {
return Array.isArray(data) || isPlainObject(data);
}
getProxyData(data, keyRef) {
// WARNING: SIDE EFFECTS
// Set defaults for keys not already set on parent data
// TODO should make another effort to get rid of this,
// See the ProxyWrap util for more proxy handlers that will likely fix this
let undefinedValue = "__11TY_UNDEFINED__";
if (this.computedKeys) {
for (let key of this.computedKeys) {
if (lodashGet(data, key, undefinedValue) === undefinedValue) {
lodashSet(data, key, "");
}
}
}
let proxyData = this._getProxyData(data, keyRef);
return proxyData;
}
_getProxyForObject(dataObj, keyRef, parentKey = "") {
return new Proxy(
{},
{
get: (obj, key) => {
if (typeof key !== "string") {
return obj[key];
}
let newKey = `${parentKey ? `${parentKey}.` : ""}${key}`;
// Issue #1137
// Special case for Collections, always return an Array for collection keys
// so they it works fine with Array methods like `filter`, `map`, etc
if (newKey === "collections") {
keyRef.add(newKey);
return new Proxy(
{},
{
get: (target, key) => {
if (typeof key === "string") {
keyRef.add(`collections.${key}`);
return [];
}
return target[key];
},
},
);
}
let newData = this._getProxyData(dataObj[key], keyRef, newKey);
if (!this.isArrayOrPlainObject(newData)) {
keyRef.add(newKey);
}
return newData;
},
},
);
}
_getProxyForArray(dataArr, keyRef, parentKey = "") {
return new Proxy(new Array(dataArr.length), {
get: (obj, key) => {
if (Array.prototype.hasOwnProperty(key)) {
// remove `filter`, `constructor`, `map`, etc
keyRef.add(parentKey);
return obj[key];
}
// Hm, this needs to be better
if (key === "then") {
keyRef.add(parentKey);
return;
}
let newKey = `${parentKey}[${key}]`;
let newData = this._getProxyData(dataArr[key], keyRef, newKey);
if (!this.isArrayOrPlainObject(newData)) {
keyRef.add(newKey);
}
return newData;
},
});
}
_getProxyData(data, keyRef, parentKey = "") {
if (isPlainObject(data)) {
return this._getProxyForObject(data, keyRef, parentKey);
} else if (Array.isArray(data)) {
return this._getProxyForArray(data, keyRef, parentKey);
}
// everything else!
return data;
}
async findVarsUsed(fn, data = {}) {
let keyRef = new Set();
// careful, logging proxyData will mess with test results!
let proxyData = this.getProxyData(data, keyRef);
// squelch console logs for this fake proxy data pass 😅
// let savedLog = console.log;
// console.log = () => {};
await fn(proxyData);
// console.log = savedLog;
return Array.from(keyRef);
}
}
export default ComputedDataProxy;

View File

@@ -0,0 +1,64 @@
import { DepGraph as DependencyGraph } from "dependency-graph";
/* Keeps track of the dependency graph between computed data variables
* Removes keys from the graph when they are computed.
*/
class ComputedDataQueue {
constructor() {
this.graph = new DependencyGraph();
}
getOrder() {
return this.graph.overallOrder();
}
getOrderFor(name) {
return this.graph.dependenciesOf(name);
}
getDependsOn(name) {
return this.graph.dependantsOf(name);
}
isUsesStartsWith(name, prefix) {
if (name.startsWith(prefix)) {
return true;
}
return (
this.graph.dependenciesOf(name).filter((entry) => {
return entry.startsWith(prefix);
}).length > 0
);
}
addNode(name) {
if (!this.graph.hasNode(name)) {
this.graph.addNode(name);
}
}
_uses(graph, name, varsUsed = []) {
if (!graph.hasNode(name)) {
graph.addNode(name);
}
for (let varUsed of varsUsed) {
if (!graph.hasNode(varUsed)) {
graph.addNode(varUsed);
}
graph.addDependency(name, varUsed);
}
}
uses(name, varsUsed = []) {
this._uses(this.graph, name, varsUsed);
}
markComputed(varsComputed = []) {
for (let varComputed of varsComputed) {
this.graph.removeNode(varComputed);
}
}
}
export default ComputedDataQueue;

View File

@@ -0,0 +1,70 @@
import lodash from "@11ty/lodash-custom";
import debugUtil from "debug";
const { set: lodashSet } = lodash;
const debug = debugUtil("Eleventy:ComputedDataTemplateString");
/* Calculates computed data in Template Strings.
* Ideally we would use the Proxy approach but it doesnt work
* in some template languages that visit all available data even if
* it isnt used in the template (Nunjucks)
*/
class ComputedDataTemplateString {
constructor(computedKeys) {
if (Array.isArray(computedKeys)) {
this.computedKeys = new Set(computedKeys);
} else {
this.computedKeys = computedKeys;
}
// is this ¯\_(lisp)_/¯
// must be strings that wont be escaped by template languages
this.prefix = "(((11ty(((";
this.suffix = ")))11ty)))";
}
getProxyData() {
let proxyData = {};
// use these special strings as a workaround to check the rendered output
// cant use proxies here as some template languages trigger proxy for all
// keys in data
for (let key of this.computedKeys) {
// TODO dont allow to set eleventyComputed.page? other disallowed computed things?
lodashSet(proxyData, key, this.prefix + key + this.suffix);
}
return proxyData;
}
findVarsInOutput(output = "") {
let vars = new Set();
let splits = output.split(this.prefix);
for (let split of splits) {
let varName = split.slice(0, split.indexOf(this.suffix) < 0 ? 0 : split.indexOf(this.suffix));
if (varName) {
vars.add(varName);
}
}
return Array.from(vars);
}
async findVarsUsed(fn) {
let proxyData = this.getProxyData();
let output;
// Mitigation for #1061, errors with filters in the first pass shouldnt fail the whole thing.
try {
output = await fn(proxyData);
} catch (e) {
debug("Computed Data first pass data resolution error: %o", e);
}
// page.outputPath on serverless urls returns false.
if (typeof output === "string") {
return this.findVarsInOutput(output);
}
return [];
}
}
export default ComputedDataTemplateString;

737
node_modules/@11ty/eleventy/src/Data/TemplateData.js generated vendored Normal file
View File

@@ -0,0 +1,737 @@
import path from "node:path";
import semver from "semver";
import lodash from "@11ty/lodash-custom";
import { Merge, TemplatePath, isPlainObject } from "@11ty/eleventy-utils";
import debugUtil from "debug";
import unique from "../Util/Objects/Unique.js";
import TemplateGlob from "../TemplateGlob.js";
import EleventyExtensionMap from "../EleventyExtensionMap.js";
import EleventyBaseError from "../Errors/EleventyBaseError.js";
import TemplateDataInitialGlobalData from "./TemplateDataInitialGlobalData.js";
import { getEleventyPackageJson, getWorkingProjectPackageJson } from "../Util/ImportJsonSync.js";
import { EleventyImport, EleventyLoadContent } from "../Util/Require.js";
import { DeepFreeze } from "../Util/Objects/DeepFreeze.js";
const { set: lodashSet, get: lodashGet } = lodash;
const debugWarn = debugUtil("Eleventy:Warnings");
const debug = debugUtil("Eleventy:TemplateData");
const debugDev = debugUtil("Dev:Eleventy:TemplateData");
class TemplateDataConfigError extends EleventyBaseError {}
class TemplateDataParseError extends EleventyBaseError {}
class TemplateData {
constructor(eleventyConfig) {
if (!eleventyConfig) {
throw new TemplateDataConfigError("Missing `config`.");
}
this.eleventyConfig = eleventyConfig;
this.config = this.eleventyConfig.getConfig();
this.benchmarks = {
data: this.config.benchmarkManager.get("Data"),
aggregate: this.config.benchmarkManager.get("Aggregate"),
};
this.rawImports = {};
this.globalData = null;
this.templateDirectoryData = {};
this.isEsm = false;
this.initialGlobalData = new TemplateDataInitialGlobalData(this.eleventyConfig);
}
get dirs() {
return this.eleventyConfig.directories;
}
get inputDir() {
return this.dirs.input;
}
// if this was set but `falsy` we would fallback to inputDir
get dataDir() {
return this.dirs.data;
}
// This was async in 2.0 and prior but doesnt need to be any more.
getInputDir() {
return this.dirs.input;
}
getDataDir() {
return this.dataDir;
}
get _fsExistsCache() {
// It's common for data files not to exist, so we avoid going to the FS to
// re-check if they do via a quick-and-dirty cache.
return this.eleventyConfig.existsCache;
}
setFileSystemSearch(fileSystemSearch) {
this.fileSystemSearch = fileSystemSearch;
}
setProjectUsingEsm(isEsmProject) {
this.isEsm = !!isEsmProject;
}
get extensionMap() {
if (!this._extensionMap) {
this._extensionMap = new EleventyExtensionMap(this.eleventyConfig);
this._extensionMap.setFormats([]);
}
return this._extensionMap;
}
set extensionMap(map) {
this._extensionMap = map;
}
get environmentVariables() {
return this._env;
}
set environmentVariables(env) {
this._env = env;
}
/* Used by tests */
_setConfig(config) {
this.config = config;
}
getRawImports() {
if (!this.config.keys.package) {
debug(
"Opted-out of package.json assignment for global data with falsy value for `keys.package` configuration.",
);
return this.rawImports;
} else if (Object.keys(this.rawImports).length > 0) {
return this.rawImports;
}
try {
let pkgJson = getWorkingProjectPackageJson();
this.rawImports[this.config.keys.package] = pkgJson;
if (this.config.freezeReservedData) {
DeepFreeze(this.rawImports);
}
} catch (e) {
debug("Could not find or require package.json import for global data.");
}
return this.rawImports;
}
clearData() {
this.globalData = null;
this.configApiGlobalData = null;
this.templateDirectoryData = {};
}
_getGlobalDataGlobByExtension(extension) {
return TemplateGlob.normalizePath(this.dataDir, `/**/*.${extension}`);
}
// This is a backwards compatibility helper with the old `jsDataFileSuffix` configuration API
getDataFileSuffixes() {
// New API
if (Array.isArray(this.config.dataFileSuffixes)) {
return this.config.dataFileSuffixes;
}
// Backwards compatibility
if (this.config.jsDataFileSuffix) {
let suffixes = [];
suffixes.push(this.config.jsDataFileSuffix); // e.g. filename.11tydata.json
suffixes.push(""); // suffix-less for free with old API, e.g. filename.json
return suffixes;
}
return []; // if both of these entries are set to false, use no files
}
// This is used exclusively for --watch and --serve chokidar targets
async getTemplateDataFileGlob() {
let suffixes = this.getDataFileSuffixes();
let globSuffixesWithLeadingDot = new Set();
globSuffixesWithLeadingDot.add("json"); // covers .11tydata.json too
let globSuffixesWithoutLeadingDot = new Set();
// Typically using [ '.11tydata', '' ] suffixes to find data files
for (let suffix of suffixes) {
// TODO the `suffix` truthiness check is purely for backwards compat?
if (suffix && typeof suffix === "string") {
if (suffix.startsWith(".")) {
// .suffix.js
globSuffixesWithLeadingDot.add(`${suffix.slice(1)}.mjs`);
globSuffixesWithLeadingDot.add(`${suffix.slice(1)}.cjs`);
globSuffixesWithLeadingDot.add(`${suffix.slice(1)}.js`);
} else {
// "suffix.js" without leading dot
globSuffixesWithoutLeadingDot.add(`${suffix || ""}.mjs`);
globSuffixesWithoutLeadingDot.add(`${suffix || ""}.cjs`);
globSuffixesWithoutLeadingDot.add(`${suffix || ""}.js`);
}
}
}
// Configuration Data Extensions e.g. yaml
if (this.hasUserDataExtensions()) {
for (let extension of this.getUserDataExtensions()) {
globSuffixesWithLeadingDot.add(extension); // covers .11tydata.{extension} too
}
}
let paths = [];
if (globSuffixesWithLeadingDot.size > 0) {
paths.push(`${this.inputDir}**/*.{${Array.from(globSuffixesWithLeadingDot).join(",")}}`);
}
if (globSuffixesWithoutLeadingDot.size > 0) {
paths.push(`${this.inputDir}**/*{${Array.from(globSuffixesWithoutLeadingDot).join(",")}}`);
}
return TemplatePath.addLeadingDotSlashArray(paths);
}
// For spidering dependencies
// TODO Can we reuse getTemplateDataFileGlob instead? Maybe just filter off the .json files before scanning for dependencies
getTemplateJavaScriptDataFileGlob() {
let paths = [];
let suffixes = this.getDataFileSuffixes();
for (let suffix of suffixes) {
if (suffix) {
// TODO this check is purely for backwards compat and I kinda feel like it shouldnt be here
// paths.push(`${this.inputDir}/**/*${suffix || ""}.cjs`); // Same as above
paths.push(`${this.inputDir}**/*${suffix || ""}.js`);
}
}
return TemplatePath.addLeadingDotSlashArray(paths);
}
getGlobalDataGlob() {
let extGlob = this.getGlobalDataExtensionPriorities().join(",");
return [this._getGlobalDataGlobByExtension("{" + extGlob + "}")];
}
getWatchPathCache() {
return this.pathCache;
}
getGlobalDataExtensionPriorities() {
return this.getUserDataExtensions().concat(["json", "mjs", "cjs", "js"]);
}
static calculateExtensionPriority(path, priorities) {
for (let i = 0; i < priorities.length; i++) {
let ext = priorities[i];
if (path.endsWith(ext)) {
return i;
}
}
return priorities.length;
}
async getGlobalDataFiles() {
let priorities = this.getGlobalDataExtensionPriorities();
let fsBench = this.benchmarks.aggregate.get("Searching the file system (data)");
fsBench.before();
let globs = this.getGlobalDataGlob();
let paths = await this.fileSystemSearch.search("global-data", globs);
fsBench.after();
// sort paths according to extension priorities
// here we use reverse ordering, because paths with bigger index in array will override the first ones
// example [path/file.json, path/file.js] here js will override json
paths = paths.sort((first, second) => {
let p1 = TemplateData.calculateExtensionPriority(first, priorities);
let p2 = TemplateData.calculateExtensionPriority(second, priorities);
if (p1 < p2) {
return -1;
}
if (p1 > p2) {
return 1;
}
return 0;
});
this.pathCache = paths;
return paths;
}
getObjectPathForDataFile(dataFilePath) {
let reducedPath = TemplatePath.stripLeadingSubPath(dataFilePath, this.dataDir);
let parsed = path.parse(reducedPath);
let folders = parsed.dir ? parsed.dir.split("/") : [];
folders.push(parsed.name);
return folders;
}
async getAllGlobalData() {
let globalData = {};
let files = TemplatePath.addLeadingDotSlashArray(await this.getGlobalDataFiles());
this.config.events.emit("eleventy.globalDataFiles", files);
let dataFileConflicts = {};
for (let j = 0, k = files.length; j < k; j++) {
let data = await this.getDataValue(files[j]);
let objectPathTarget = this.getObjectPathForDataFile(files[j]);
// Since we're joining directory paths and an array is not usable as an objectkey since two identical arrays are not double equal,
// we can just join the array by a forbidden character ("/"" is chosen here, since it works on Linux, Mac and Windows).
// If at some point this isn't enough anymore, it would be possible to just use JSON.stringify(objectPathTarget) since that
// is guaranteed to work but is signifivcantly slower.
let objectPathTargetString = objectPathTarget.join(path.sep);
// if two global files have the same path (but different extensions)
// and conflict, lets merge them.
if (dataFileConflicts[objectPathTargetString]) {
debugWarn(
`merging global data from ${files[j]} with an already existing global data file (${dataFileConflicts[objectPathTargetString]}). Overriding existing keys.`,
);
let oldData = lodashGet(globalData, objectPathTarget);
data = TemplateData.mergeDeep(this.config.dataDeepMerge, oldData, data);
}
dataFileConflicts[objectPathTargetString] = files[j];
debug(`Found global data file ${files[j]} and adding as: ${objectPathTarget}`);
lodashSet(globalData, objectPathTarget, data);
}
return globalData;
}
async #getInitialGlobalData() {
let globalData = await this.initialGlobalData.getData();
if (!("eleventy" in globalData)) {
globalData.eleventy = {};
}
// #2293 for meta[name=generator]
const pkg = getEleventyPackageJson();
globalData.eleventy.version = semver.coerce(pkg.version).toString();
globalData.eleventy.generator = `Eleventy v${globalData.eleventy.version}`;
if (this.environmentVariables) {
if (!("env" in globalData.eleventy)) {
globalData.eleventy.env = {};
}
Object.assign(globalData.eleventy.env, this.environmentVariables);
}
if (this.dirs) {
if (!("directories" in globalData.eleventy)) {
globalData.eleventy.directories = {};
}
Object.assign(globalData.eleventy.directories, this.dirs.getUserspaceInstance());
}
// Reserved
if (this.config.freezeReservedData) {
DeepFreeze(globalData.eleventy);
}
return globalData;
}
async getInitialGlobalData() {
if (!this.configApiGlobalData) {
this.configApiGlobalData = this.#getInitialGlobalData();
}
return this.configApiGlobalData;
}
async #getGlobalData() {
let rawImports = this.getRawImports();
let configApiGlobalData = await this.getInitialGlobalData();
let globalJson = await this.getAllGlobalData();
let mergedGlobalData = Merge(globalJson, configApiGlobalData);
// OK: Shallow merge when combining rawImports (pkg) with global data files
return Object.assign({}, mergedGlobalData, rawImports);
}
async getGlobalData() {
if (!this.globalData) {
this.globalData = this.#getGlobalData();
}
return this.globalData;
}
/* Template and Directory data files */
async combineLocalData(localDataPaths) {
let localData = {};
if (!Array.isArray(localDataPaths)) {
localDataPaths = [localDataPaths];
}
// Filter out files we know don't exist to avoid overhead for checking
const dataPaths = await Promise.all(
localDataPaths.map((path) => {
if (this._fsExistsCache.exists(path)) {
return path;
}
return false;
}),
);
localDataPaths = dataPaths.filter((pathOrFalse) => {
return pathOrFalse === false ? false : true;
});
this.config.events.emit("eleventy.dataFiles", localDataPaths);
if (!localDataPaths.length) {
return localData;
}
let dataSource = {};
for (let path of localDataPaths) {
let dataForPath = await this.getDataValue(path);
if (!isPlainObject(dataForPath)) {
debug(
"Warning: Template and Directory data files expect an object to be returned, instead `%o` returned `%o`",
path,
dataForPath,
);
} else {
// clean up data for template/directory data files only.
let cleanedDataForPath = TemplateData.cleanupData(dataForPath);
for (let key in cleanedDataForPath) {
if (Object.prototype.hasOwnProperty.call(dataSource, key)) {
debugWarn(
"Local data files have conflicting data. Overwriting '%s' with data from '%s'. Previous data location was from '%s'",
key,
path,
dataSource[key],
);
}
dataSource[key] = path;
}
TemplateData.mergeDeep(this.config.dataDeepMerge, localData, cleanedDataForPath);
}
}
return localData;
}
async getTemplateDirectoryData(templatePath) {
if (!this.templateDirectoryData[templatePath]) {
let localDataPaths = await this.getLocalDataPaths(templatePath);
let importedData = await this.combineLocalData(localDataPaths);
this.templateDirectoryData[templatePath] = Object.assign({}, importedData);
}
return this.templateDirectoryData[templatePath];
}
getUserDataExtensions() {
if (!this.config.dataExtensions) {
return [];
}
// returning extensions in reverse order to create proper extension order
// later added formats will override first ones
return Array.from(this.config.dataExtensions.keys()).reverse();
}
getUserDataParser(extension) {
return this.config.dataExtensions.get(extension);
}
isUserDataExtension(extension) {
return this.config.dataExtensions && this.config.dataExtensions.has(extension);
}
hasUserDataExtensions() {
return this.config.dataExtensions && this.config.dataExtensions.size > 0;
}
async _parseDataFile(path, parser, options = {}) {
let readFile = !("read" in options) || options.read === true;
let rawInput;
if (readFile) {
rawInput = EleventyLoadContent(path, options);
}
if (readFile && !rawInput) {
return {};
}
try {
if (readFile) {
return parser(rawInput, path);
} else {
// path as a first argument is when `read: false`
// path as a second argument is for consistency with `read: true` API
return parser(path, path);
}
} catch (e) {
throw new TemplateDataParseError(`Having trouble parsing data file ${path}`, e);
}
}
// ignoreProcessing = false for global data files
// ignoreProcessing = true for local data files
async getDataValue(path) {
let extension = TemplatePath.getExtension(path);
if (extension === "js" || extension === "cjs" || extension === "mjs") {
// JS data file or required JSON (no preprocessing needed)
let localPath = TemplatePath.absolutePath(path);
let exists = this._fsExistsCache.exists(localPath);
// Make sure that relative lookups benefit from cache
this._fsExistsCache.markExists(path, exists);
if (!exists) {
return {};
}
let aggregateDataBench = this.benchmarks.aggregate.get("Data File");
aggregateDataBench.before();
let dataBench = this.benchmarks.data.get(`\`${path}\``);
dataBench.before();
let type = "cjs";
if (extension === "mjs" || (extension === "js" && this.isEsm)) {
type = "esm";
}
// We always need to use `import()`, as `require` isnt available in ESM.
let returnValue = await EleventyImport(localPath, type);
// TODO special exception for Global data `permalink.js`
// module.exports = (data) => `${data.page.filePathStem}/`; // Does not work
// module.exports = () => ((data) => `${data.page.filePathStem}/`); // Works
if (typeof returnValue === "function") {
let configApiGlobalData = await this.getInitialGlobalData();
returnValue = await returnValue(configApiGlobalData || {});
}
dataBench.after();
aggregateDataBench.after();
return returnValue;
} else if (this.isUserDataExtension(extension)) {
// Other extensions
let { parser, options } = this.getUserDataParser(extension);
return this._parseDataFile(path, parser, options);
} else if (extension === "json") {
// File to string, parse with JSON (preprocess)
const parser = (content) => JSON.parse(content);
return this._parseDataFile(path, parser);
} else {
throw new TemplateDataParseError(
`Could not find an appropriate data parser for ${path}. Do you need to add a plugin to your config file?`,
);
}
}
_pushExtensionsToPaths(paths, curpath, extensions) {
for (let extension of extensions) {
paths.push(curpath + "." + extension);
}
}
_addBaseToPaths(paths, base, extensions, nonEmptySuffixesOnly = false) {
let suffixes = this.getDataFileSuffixes();
for (let suffix of suffixes) {
suffix = suffix || "";
if (nonEmptySuffixesOnly && suffix === "") {
continue;
}
// data suffix
if (suffix) {
paths.push(base + suffix + ".js");
paths.push(base + suffix + ".cjs");
paths.push(base + suffix + ".mjs");
}
paths.push(base + suffix + ".json"); // default: .11tydata.json
// inject user extensions
this._pushExtensionsToPaths(paths, base + suffix, extensions);
}
}
async getLocalDataPaths(templatePath) {
let paths = [];
let parsed = path.parse(templatePath);
let inputDir = this.inputDir;
debugDev("getLocalDataPaths(%o)", templatePath);
debugDev("parsed.dir: %o", parsed.dir);
let userExtensions = this.getUserDataExtensions();
if (parsed.dir) {
let fileNameNoExt = this.extensionMap.removeTemplateExtension(parsed.base);
// default dataSuffix: .11tydata, is appended in _addBaseToPaths
debug("Using %o suffixes to find data files.", this.getDataFileSuffixes());
// Template data file paths
let filePathNoExt = parsed.dir + "/" + fileNameNoExt;
this._addBaseToPaths(paths, filePathNoExt, userExtensions);
// Directory data file paths
let allDirs = TemplatePath.getAllDirs(parsed.dir);
debugDev("allDirs: %o", allDirs);
for (let dir of allDirs) {
let lastDir = TemplatePath.getLastPathSegment(dir);
let dirPathNoExt = dir + "/" + lastDir;
if (inputDir) {
debugDev("dirStr: %o; inputDir: %o", dir, inputDir);
}
if (!inputDir || (dir.startsWith(inputDir) && dir !== inputDir)) {
if (this.config.dataFileDirBaseNameOverride) {
let indexDataFile = dir + "/" + this.config.dataFileDirBaseNameOverride;
this._addBaseToPaths(paths, indexDataFile, userExtensions, true);
} else {
this._addBaseToPaths(paths, dirPathNoExt, userExtensions);
}
}
}
// 0.11.0+ include root input dir files
// if using `docs/` as input dir, looks for docs/docs.json et al
if (inputDir) {
let lastInputDir = TemplatePath.addLeadingDotSlash(
TemplatePath.join(inputDir, TemplatePath.getLastPathSegment(inputDir)),
);
// in root input dir, search for index.11tydata.json et al
if (this.config.dataFileDirBaseNameOverride) {
let indexDataFile =
TemplatePath.getDirFromFilePath(lastInputDir) +
"/" +
this.config.dataFileDirBaseNameOverride;
this._addBaseToPaths(paths, indexDataFile, userExtensions, true);
} else if (lastInputDir !== "./") {
this._addBaseToPaths(paths, lastInputDir, userExtensions);
}
}
}
debug("getLocalDataPaths(%o): %o", templatePath, paths);
return unique(paths).reverse();
}
static mergeDeep(deepMerge, target, ...source) {
if (!deepMerge && deepMerge !== undefined) {
return Object.assign(target, ...source);
} else {
return TemplateData.merge(target, ...source);
}
}
static merge(target, ...source) {
return Merge(target, ...source);
}
/* Like cleanupData() but does not mutate */
static getCleanedTagsImmutable(data) {
let tags = [];
if (isPlainObject(data) && data.tags) {
if (typeof data.tags === "string") {
tags = (data.tags || "").split(",");
} else if (Array.isArray(data.tags)) {
tags = data.tags;
}
// Deduplicate tags
return [...new Set(tags)];
}
return tags;
}
static cleanupData(data) {
if (isPlainObject(data) && "tags" in data) {
if (typeof data.tags === "string") {
data.tags = data.tags ? [data.tags] : [];
} else if (data.tags === null) {
data.tags = [];
}
// Deduplicate tags
data.tags = [...new Set(data.tags)];
}
return data;
}
static getNormalizedExcludedCollections(data) {
let excludes = [];
let key = "eleventyExcludeFromCollections";
if (data?.[key] !== true) {
if (Array.isArray(data[key])) {
excludes = data[key];
} else if (typeof data[key] === "string") {
excludes = (data[key] || "").split(",");
}
}
return {
excludes,
excludeAll: data?.eleventyExcludeFromCollections === true,
};
}
/* Same as getIncludedTagNames() but may also include "all" */
static getIncludedCollectionNames(data) {
let tags = TemplateData.getCleanedTagsImmutable(data);
if (tags.length > 0) {
let { excludes, excludeAll } = TemplateData.getNormalizedExcludedCollections(data);
if (excludeAll) {
return [];
} else {
return ["all", ...tags].filter((tag) => !excludes.includes(tag));
}
} else {
return ["all"];
}
}
static getIncludedTagNames(data) {
let tags = TemplateData.getCleanedTagsImmutable(data);
if (tags.length > 0) {
let { excludes, excludeAll } = TemplateData.getNormalizedExcludedCollections(data);
if (excludeAll) {
return [];
} else {
return tags.filter((tag) => !excludes.includes(tag));
}
} else {
return [];
}
}
}
export default TemplateData;

View File

@@ -0,0 +1,40 @@
import lodash from "@11ty/lodash-custom";
import EleventyBaseError from "../Errors/EleventyBaseError.js";
const { set: lodashSet } = lodash;
class TemplateDataConfigError extends EleventyBaseError {}
class TemplateDataInitialGlobalData {
constructor(templateConfig) {
if (!templateConfig || templateConfig.constructor.name !== "TemplateConfig") {
throw new TemplateDataConfigError("Missing or invalid `templateConfig` (via Render plugin).");
}
this.templateConfig = templateConfig;
this.config = this.templateConfig.getConfig();
}
async getData() {
let globalData = {};
// via eleventyConfig.addGlobalData
if (this.config.globalData) {
let keys = Object.keys(this.config.globalData);
for (let key of keys) {
let returnValue = this.config.globalData[key];
// This section is problematic when used with eleventyComputed #3389
if (typeof returnValue === "function") {
returnValue = await returnValue();
}
lodashSet(globalData, key, returnValue);
}
}
return globalData;
}
}
export default TemplateDataInitialGlobalData;

1472
node_modules/@11ty/eleventy/src/Eleventy.js generated vendored Normal file

File diff suppressed because it is too large Load Diff

43
node_modules/@11ty/eleventy/src/EleventyCommonJs.cjs generated vendored Normal file
View File

@@ -0,0 +1,43 @@
function canRequireModules() {
// via --experimental-require-module or newer than Node 22 support when this flag is no longer necessary
try {
require("./Util/Objects/SampleModule.mjs");
return true;
} catch(e) {
if(e.code === "ERR_REQUIRE_ESM") {
return false;
}
// Rethrow if not an ESM require error.
throw e;
}
}
if(!canRequireModules()) {
let error = new Error(`\`require("@11ty/eleventy")\` is incompatible with Eleventy v3 and this version of Node. You have a few options:
1. (Easiest) Change the \`require\` to use a dynamic import inside of an asynchronous CommonJS configuration
callback, for example:
module.exports = async function {
const {EleventyRenderPlugin, EleventyI18nPlugin, EleventyHtmlBasePlugin} = await import("@11ty/eleventy");
}
2. (Easier) Update the JavaScript syntax in your configuration file from CommonJS to ESM (change \`require\`
to use \`import\` and rename the file to have an \`.mjs\` file extension).
3. (More work) Change your project to use ESM-first by adding \`"type": "module"\` to your package.json. Any
\`.js\` will need to be ported to use ESM syntax (or renamed to \`.cjs\`.)
4. (Short term workaround) Use the --experimental-require-module flag to enable this behavior. Read
more: https://nodejs.org/api/modules.html#loading-ecmascript-modules-using-require It is possible that the
newest version of Node has this enabled by default—you can try upgrading your version of Node.js.`);
error.skipOriginalStack = true;
throw error;
}
// If we made it here require(ESM) works fine (via --experimental-require-module or newer Node.js defaults)
let mod = require("./Eleventy.js");
module.exports = mod;

284
node_modules/@11ty/eleventy/src/EleventyExtensionMap.js generated vendored Normal file
View File

@@ -0,0 +1,284 @@
import { TemplatePath } from "@11ty/eleventy-utils";
import TemplateEngineManager from "./Engines/TemplateEngineManager.js";
import EleventyBaseError from "./Errors/EleventyBaseError.js";
class EleventyExtensionMapConfigError extends EleventyBaseError {}
class EleventyExtensionMap {
constructor(config) {
this.config = config;
this._spiderJsDepsCache = {};
/** @type {Array} */
this.validTemplateLanguageKeys;
}
setFormats(formatKeys = []) {
// raw
this.formatKeys = formatKeys;
this.unfilteredFormatKeys = formatKeys.map(function (key) {
return key.trim().toLowerCase();
});
this.validTemplateLanguageKeys = this.unfilteredFormatKeys.filter((key) =>
this.hasExtension(key),
);
this.passthroughCopyKeys = this.unfilteredFormatKeys.filter((key) => !this.hasExtension(key));
}
set config(cfg) {
if (!cfg || cfg.constructor.name !== "TemplateConfig") {
throw new EleventyExtensionMapConfigError(
"Missing or invalid `config` argument (via setter).",
);
}
this.eleventyConfig = cfg;
}
get config() {
return this.eleventyConfig.getConfig();
}
get engineManager() {
if (!this._engineManager) {
this._engineManager = new TemplateEngineManager(this.eleventyConfig);
}
return this._engineManager;
}
reset() {
this.engineManager.reset();
}
/* Used for layout path resolution */
getFileList(path, dir) {
if (!path) {
return [];
}
let files = [];
this.validTemplateLanguageKeys.forEach((key) => {
this.getExtensionsFromKey(key).forEach(function (extension) {
files.push((dir ? dir + "/" : "") + path + "." + extension);
});
});
return files;
}
// Warning: this would false positive on an include, but is only used
// on paths found from the file system glob search.
// TODO: Method name might just need to be renamed to something more accurate.
isFullTemplateFilePath(path) {
for (let extension of this.validTemplateLanguageKeys) {
if (path.endsWith(`.${extension}`)) {
return true;
}
}
return false;
}
getCustomExtensionEntry(extension) {
if (!this.config.extensionMap) {
return;
}
for (let entry of this.config.extensionMap) {
if (entry.extension === extension) {
return entry;
}
}
}
getValidExtensionsForPath(path) {
let extensions = new Set();
for (let extension in this.extensionToKeyMap) {
if (path.endsWith(`.${extension}`)) {
extensions.add(extension);
}
}
// if multiple extensions are valid, sort from longest to shortest
// e.g. .11ty.js and .js
let sorted = Array.from(extensions)
.filter((extension) => this.validTemplateLanguageKeys.includes(extension))
.sort((a, b) => b.length - a.length);
return sorted;
}
async shouldSpiderJavaScriptDependencies(path) {
let extensions = this.getValidExtensionsForPath(path);
for (let extension of extensions) {
if (extension in this._spiderJsDepsCache) {
return this._spiderJsDepsCache[extension];
}
let cls = await this.engineManager.getEngineClassByExtension(extension);
if (cls) {
let entry = this.getCustomExtensionEntry(extension);
let shouldSpider = cls.shouldSpiderJavaScriptDependencies(entry);
this._spiderJsDepsCache[extension] = shouldSpider;
return shouldSpider;
}
}
return false;
}
getPassthroughCopyGlobs(inputDir) {
return this._getGlobs(this.passthroughCopyKeys, inputDir);
}
getValidGlobs(inputDir) {
return this._getGlobs(this.validTemplateLanguageKeys, inputDir);
}
getGlobs(inputDir) {
return this._getGlobs(this.unfilteredFormatKeys, inputDir);
}
_getGlobs(formatKeys, inputDir) {
let dir = TemplatePath.convertToRecursiveGlobSync(inputDir);
let extensions = new Set();
for (let key of formatKeys) {
if (this.hasExtension(key)) {
for (let extension of this.getExtensionsFromKey(key)) {
extensions.add(extension);
}
} else {
extensions.add(key);
}
}
if (extensions.size === 1) {
return [`${dir}/*.${Array.from(extensions)[0]}`];
} else if (extensions.size > 1) {
return [
// extra curly brackets /*.{cjs,txt}
`${dir}/*.{${Array.from(extensions).join(",")}}`,
];
}
return [];
}
hasExtension(key) {
for (let extension in this.extensionToKeyMap) {
if (
this.extensionToKeyMap[extension].key === key ||
this.extensionToKeyMap[extension].aliasKey === key
) {
return true;
}
}
return false;
}
getExtensionsFromKey(key) {
let extensions = new Set();
for (let extension in this.extensionToKeyMap) {
if (this.extensionToKeyMap[extension].aliasKey) {
// only add aliased extension if explicitly referenced in formats
// overrides will not have an aliasKey (md => md)
if (this.extensionToKeyMap[extension].aliasKey === key) {
extensions.add(extension);
}
} else if (this.extensionToKeyMap[extension].key === key) {
extensions.add(extension);
}
}
return Array.from(extensions);
}
// Only `addExtension` configuration API extensions
getExtensionEntriesFromKey(key) {
let entries = new Set();
if ("extensionMap" in this.config) {
for (let entry of this.config.extensionMap) {
if (entry.key === key) {
entries.add(entry);
}
}
}
return Array.from(entries);
}
// Determines whether a path is a passthrough copy file or a template (via TemplateWriter)
hasEngine(pathOrKey) {
return !!this.getKey(pathOrKey);
}
getKey(pathOrKey) {
pathOrKey = (pathOrKey || "").toLowerCase();
for (let extension in this.extensionToKeyMap) {
if (pathOrKey === extension || pathOrKey.endsWith("." + extension)) {
let key =
this.extensionToKeyMap[extension].aliasKey || this.extensionToKeyMap[extension].key;
// must be a valid format key passed (e.g. via --formats)
if (this.validTemplateLanguageKeys.includes(key)) {
return key;
}
}
}
}
getExtensionEntry(pathOrKey) {
pathOrKey = (pathOrKey || "").toLowerCase();
for (let extension in this.extensionToKeyMap) {
if (pathOrKey === extension || pathOrKey.endsWith("." + extension)) {
return this.extensionToKeyMap[extension];
}
}
}
removeTemplateExtension(path) {
for (let extension in this.extensionToKeyMap) {
if (path === extension || path.endsWith("." + extension)) {
return path.slice(
0,
path.length - 1 - extension.length < 0 ? 0 : path.length - 1 - extension.length,
);
}
}
return path;
}
// keys are file extensions
// values are template language keys
get extensionToKeyMap() {
if (!this._extensionToKeyMap) {
this._extensionToKeyMap = {
md: { key: "md", extension: "md" },
html: { key: "html", extension: "html" },
njk: { key: "njk", extension: "njk" },
liquid: { key: "liquid", extension: "liquid" },
"11ty.js": { key: "11ty.js", extension: "11ty.js" },
"11ty.cjs": { key: "11ty.js", extension: "11ty.cjs" },
"11ty.mjs": { key: "11ty.js", extension: "11ty.mjs" },
};
if ("extensionMap" in this.config) {
for (let entry of this.config.extensionMap) {
// extension and key are only different when aliasing.
this._extensionToKeyMap[entry.extension] = entry;
}
}
}
return this._extensionToKeyMap;
}
getReadableFileExtensions() {
return Object.keys(this.extensionToKeyMap).join(" ");
}
}
export default EleventyExtensionMap;

517
node_modules/@11ty/eleventy/src/EleventyFiles.js generated vendored Normal file
View File

@@ -0,0 +1,517 @@
import fs from "node:fs";
import { TemplatePath, isPlainObject } from "@11ty/eleventy-utils";
import debugUtil from "debug";
import EleventyExtensionMap from "./EleventyExtensionMap.js";
import TemplateData from "./Data/TemplateData.js";
import TemplateGlob from "./TemplateGlob.js";
import TemplatePassthroughManager from "./TemplatePassthroughManager.js";
import EleventyBaseError from "./Errors/EleventyBaseError.js";
import checkPassthroughCopyBehavior from "./Util/PassthroughCopyBehaviorCheck.js";
const debug = debugUtil("Eleventy:EleventyFiles");
class EleventyFilesError extends EleventyBaseError {}
class EleventyFiles {
constructor(formats, eleventyConfig) {
if (!eleventyConfig) {
throw new EleventyFilesError("Missing `eleventyConfig`` argument.");
}
this.eleventyConfig = eleventyConfig;
this.config = eleventyConfig.getConfig();
this.aggregateBench = this.config.benchmarkManager.get("Aggregate");
this.formats = formats;
this.eleventyIgnoreContent = false;
}
get dirs() {
return this.eleventyConfig.directories;
}
get inputDir() {
return this.dirs.input;
}
get outputDir() {
return this.dirs.output;
}
get includesDir() {
return this.dirs.includes;
}
get layoutsDir() {
return this.dirs.layouts;
}
get dataDir() {
return this.dirs.data;
}
// Backwards compat
getDataDir() {
return this.dataDir;
}
setFileSystemSearch(fileSystemSearch) {
this.fileSystemSearch = fileSystemSearch;
}
init() {
if (this.dirs.inputFile || this.dirs.inputGlob) {
this.templateGlobs = TemplateGlob.map([this.dirs.inputFile || this.dirs.inputGlob]);
} else {
// Input is a directory
this.templateGlobs = this.extensionMap.getGlobs(this.inputDir);
}
this.initPassthroughManager();
this.setupGlobs();
}
get validTemplateGlobs() {
if (!this._validTemplateGlobs) {
let globs;
// Input is a file
if (this.inputFile) {
globs = this.templateGlobs;
} else {
// input is a directory
globs = this.extensionMap.getValidGlobs(this.inputDir);
}
this._validTemplateGlobs = globs;
}
return this._validTemplateGlobs;
}
get passthroughGlobs() {
let paths = new Set();
// stuff added in addPassthroughCopy()
for (let path of this.passthroughManager.getConfigPathGlobs()) {
paths.add(path);
}
// non-template language extensions
for (let path of this.extensionMap.getPassthroughCopyGlobs(this.inputDir)) {
paths.add(path);
}
return Array.from(paths);
}
restart() {
this.passthroughManager.reset();
this.setupGlobs();
this._glob = null;
}
/* For testing */
_setConfig(config) {
if (!config.ignores) {
config.ignores = new Set();
config.ignores.add("**/node_modules/**");
}
this.config = config;
}
/* Set command root for local project paths */
// This is only used by tests
_setLocalPathRoot(dir) {
this.localPathRoot = dir;
}
set extensionMap(extensionMap) {
this._extensionMap = extensionMap;
}
get extensionMap() {
// for tests
if (!this._extensionMap) {
this._extensionMap = new EleventyExtensionMap(this.eleventyConfig);
this._extensionMap.setFormats(this.formats);
this._extensionMap.config = this.eleventyConfig;
}
return this._extensionMap;
}
setRunMode(runMode) {
this.runMode = runMode;
}
initPassthroughManager() {
let mgr = new TemplatePassthroughManager(this.eleventyConfig);
mgr.setRunMode(this.runMode);
mgr.extensionMap = this.extensionMap;
mgr.setFileSystemSearch(this.fileSystemSearch);
this.passthroughManager = mgr;
}
getPassthroughManager() {
return this.passthroughManager;
}
setPassthroughManager(mgr) {
mgr.extensionMap = this.extensionMap;
this.passthroughManager = mgr;
}
set templateData(templateData) {
this._templateData = templateData;
}
get templateData() {
if (!this._templateData) {
this._templateData = new TemplateData(this.eleventyConfig);
}
return this._templateData;
}
setupGlobs() {
this.fileIgnores = this.getIgnores();
this.extraIgnores = this._getIncludesAndDataDirs();
this.uniqueIgnores = this.getIgnoreGlobs();
// Conditional added for tests that dont have a config
if (this.config?.events) {
this.config.events.emit("eleventy.ignores", this.uniqueIgnores);
}
this.normalizedTemplateGlobs = this.templateGlobs;
}
getIgnoreGlobs() {
let uniqueIgnores = new Set();
for (let ignore of this.fileIgnores) {
uniqueIgnores.add(ignore);
}
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));
}
return Array.from(uniqueIgnores);
}
static getFileIgnores(ignoreFiles) {
if (!Array.isArray(ignoreFiles)) {
ignoreFiles = [ignoreFiles];
}
let ignores = [];
for (let ignorePath of ignoreFiles) {
ignorePath = TemplatePath.normalize(ignorePath);
let dir = TemplatePath.getDirFromFilePath(ignorePath);
if (fs.existsSync(ignorePath) && fs.statSync(ignorePath).size > 0) {
let ignoreContent = fs.readFileSync(ignorePath, "utf8");
ignores = ignores.concat(EleventyFiles.normalizeIgnoreContent(dir, ignoreContent));
}
}
ignores.forEach((path) => debug(`${ignoreFiles} ignoring: ${path}`));
return ignores;
}
static normalizeIgnoreContent(dir, ignoreContent) {
let ignores = [];
if (ignoreContent) {
ignores = ignoreContent
.split("\n")
.map((line) => {
return line.trim();
})
.filter((line) => {
if (line.charAt(0) === "!") {
debug(
">>> When processing .gitignore/.eleventyignore, Eleventy does not currently support negative patterns but encountered one:",
);
debug(">>>", line);
debug("Follow along at https://github.com/11ty/eleventy/issues/693 to track support.");
}
// empty lines or comments get filtered out
return line.length > 0 && line.charAt(0) !== "#" && line.charAt(0) !== "!";
})
.map((line) => {
let path = TemplateGlob.normalizePath(dir, "/", line);
path = TemplatePath.addLeadingDotSlash(TemplatePath.relativePath(path));
try {
// Note these folders must exist to get /** suffix
let stat = fs.statSync(path);
if (stat.isDirectory()) {
return path + "/**";
}
return path;
} catch (e) {
return path;
}
});
}
return ignores;
}
setEleventyIgnoreContent(content) {
this.eleventyIgnoreContent = content;
}
getIgnores() {
let files = new Set();
for (let ignore of EleventyFiles.getFileIgnores(this.getIgnoreFiles())) {
files.add(ignore);
}
// testing API
if (this.eleventyIgnoreContent !== false) {
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)) {
// both are already normalized in 3.0
files.add(TemplateGlob.map(this.outputDir + "/**"));
}
return Array.from(files);
}
getIgnoreFiles() {
let ignoreFiles = new Set();
let rootDirectory = this.localPathRoot || ".";
if (this.config.useGitIgnore) {
ignoreFiles.add(TemplatePath.join(rootDirectory, ".gitignore"));
}
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"));
}
}
return Array.from(ignoreFiles);
}
/* Backwards compat */
getIncludesDir() {
return this.includesDir;
}
/* Backwards compat */
getLayoutsDir() {
return this.layoutsDir;
}
getFileGlobs() {
return this.normalizedTemplateGlobs;
}
getRawFiles() {
return this.templateGlobs;
}
async getWatchPathCache() {
// Issue #1325: make sure passthrough copy files are not included here
if (!this.pathCache) {
throw new Error("Watching requires `.getFiles()` to be called first in EleventyFiles");
}
let ret = [];
// Filter out the passthrough copy paths.
for (let path of this.pathCache) {
if (
this.extensionMap.isFullTemplateFilePath(path) &&
(await this.extensionMap.shouldSpiderJavaScriptDependencies(path))
) {
ret.push(path);
}
}
return ret;
}
_globSearch() {
let globs = this.getFileGlobs();
// returns a promise
debug("Searching for: %o", globs);
return this.fileSystemSearch.search("templates", globs, {
ignore: this.uniqueIgnores,
});
}
getPathsWithVirtualTemplates(paths) {
// Support for virtual templates added in 3.0
if (this.config.virtualTemplates && isPlainObject(this.config.virtualTemplates)) {
let virtualTemplates = Object.keys(this.config.virtualTemplates)
.filter((path) => {
// Filter out includes/layouts
return this.dirs.isTemplateFile(path);
})
.map((path) => {
let fullVirtualPath = this.dirs.getInputPath(path);
if (!this.extensionMap.getKey(fullVirtualPath)) {
this.eleventyConfig.logger.warn(
`The virtual template at ${fullVirtualPath} is using a template format thats not valid for your project. Your project is using: "${this.formats}". Read more about formats: https://v3.11ty.dev/docs/config/#template-formats`,
);
}
return fullVirtualPath;
});
paths = paths.concat(virtualTemplates);
// Virtual templates can not live at the same place as files on the file system!
if (paths.length !== new Set(paths).size) {
let conflicts = {};
for (let path of paths) {
if (conflicts[path]) {
throw new Error(
`A virtual template had the same path as a file on the file system: "${path}"`,
);
}
conflicts[path] = true;
}
}
}
return paths;
}
async getFiles() {
let bench = this.aggregateBench.get("Searching the file system (templates)");
bench.before();
let globResults = await this._globSearch();
let paths = TemplatePath.addLeadingDotSlashArray(globResults);
bench.after();
// Note 2.0.0-canary.19 removed a `filter` option for custom template syntax here that was unpublished and unused.
paths = this.getPathsWithVirtualTemplates(paths);
this.pathCache = paths;
return paths;
}
getFileShape(paths, filePath) {
if (!filePath) {
return;
}
if (this.isPassthroughCopyFile(paths, filePath)) {
return "copy";
}
if (this.isFullTemplateFile(paths, filePath)) {
return "template";
}
// include/layout/unknown
}
isPassthroughCopyFile(paths, filePath) {
return this.passthroughManager.isPassthroughCopyFile(paths, filePath);
}
// Assumption here that filePath is not a passthrough copy file
isFullTemplateFile(paths, filePath) {
if (!filePath) {
return false;
}
for (let path of paths) {
if (path === filePath) {
return true;
}
}
return false;
}
/* For `eleventy --watch` */
getGlobWatcherFiles() {
// TODO improvement: tie the includes and data to specific file extensions (currently using `**`)
let directoryGlobs = this._getIncludesAndDataDirs();
if (checkPassthroughCopyBehavior(this.config, this.runMode)) {
return this.validTemplateGlobs.concat(directoryGlobs);
}
// Revert to old passthroughcopy copy files behavior
return this.validTemplateGlobs.concat(this.passthroughGlobs).concat(directoryGlobs);
}
/* For `eleventy --watch` */
getGlobWatcherFilesForPassthroughCopy() {
return this.passthroughGlobs;
}
/* For `eleventy --watch` */
async getGlobWatcherTemplateDataFiles() {
let templateData = this.templateData;
return await templateData.getTemplateDataFileGlob();
}
/* For `eleventy --watch` */
// TODO this isnt great but reduces complexity avoiding using TemplateData:getLocalDataPaths for each template in the cache
async getWatcherTemplateJavaScriptDataFiles() {
let globs = this.templateData.getTemplateJavaScriptDataFileGlob();
let bench = this.aggregateBench.get("Searching the file system (watching)");
bench.before();
let results = TemplatePath.addLeadingDotSlashArray(
await this.fileSystemSearch.search("js-dependencies", globs, {
ignore: ["**/node_modules/**"],
}),
);
bench.after();
return results;
}
/* Ignored by `eleventy --watch` */
getGlobWatcherIgnores() {
// convert to format without ! since they are passed in as a separate argument to glob watcher
let entries = new Set(
this.fileIgnores.map((ignore) => TemplatePath.stripLeadingDotSlash(ignore)),
);
for (let ignore of this.config.watchIgnores) {
entries.add(TemplateGlob.normalizePath(this.localPathRoot || ".", ignore));
}
// de-duplicated
return Array.from(entries);
}
_getIncludesAndDataDirs() {
let rawPaths = new Set();
rawPaths.add(this.includesDir);
if (this.layoutsDir) {
rawPaths.add(this.layoutsDir);
}
rawPaths.add(this.dataDir);
return Array.from(rawPaths)
.filter((entry) => {
// never ignore the input directory (even if config file returns "" for these)
return entry && entry !== this.inputDir;
})
.map((entry) => {
return TemplateGlob.map(entry + "**");
});
}
}
export default EleventyFiles;

305
node_modules/@11ty/eleventy/src/EleventyServe.js generated vendored Normal file
View File

@@ -0,0 +1,305 @@
import assert from "node:assert";
import debugUtil from "debug";
import { Merge, DeepCopy, TemplatePath } from "@11ty/eleventy-utils";
import EleventyDevServer from "@11ty/eleventy-dev-server";
import EleventyBaseError from "./Errors/EleventyBaseError.js";
import ConsoleLogger from "./Util/ConsoleLogger.js";
import PathPrefixer from "./Util/PathPrefixer.js";
import checkPassthroughCopyBehavior from "./Util/PassthroughCopyBehaviorCheck.js";
import { getModulePackageJson } from "./Util/ImportJsonSync.js";
import { EleventyImport } from "./Util/Require.js";
import { isGlobMatch } from "./Util/GlobMatcher.js";
const debug = debugUtil("Eleventy:EleventyServe");
class EleventyServeConfigError extends EleventyBaseError {}
const DEFAULT_SERVER_OPTIONS = {
module: "@11ty/eleventy-dev-server",
port: 8080,
// pathPrefix: "/",
// setup: function() {},
// logger: { info: function() {}, error: function() {} }
};
class EleventyServe {
constructor() {
this.logger = new ConsoleLogger();
this._initOptionsFetched = false;
this._aliases = undefined;
this._watchedFiles = new Set();
}
get config() {
if (!this.eleventyConfig) {
throw new EleventyServeConfigError(
"You need to set the eleventyConfig property on EleventyServe.",
);
}
return this.eleventyConfig.getConfig();
}
set config(config) {
throw new Error("Its not allowed to set config on EleventyServe. Set eleventyConfig instead.");
}
setAliases(aliases) {
this._aliases = aliases;
if (this._server && "setAliases" in this._server) {
this._server.setAliases(aliases);
}
}
get eleventyConfig() {
if (!this._eleventyConfig) {
throw new EleventyServeConfigError(
"You need to set the eleventyConfig property on EleventyServe.",
);
}
return this._eleventyConfig;
}
set eleventyConfig(config) {
this._eleventyConfig = config;
if (checkPassthroughCopyBehavior(this._eleventyConfig.userConfig, "serve")) {
this._eleventyConfig.userConfig.events.on("eleventy.passthrough", ({ map }) => {
// for-free passthrough copy
this.setAliases(map);
});
}
}
// TODO directorynorm
setOutputDir(outputDir) {
// TODO check if this is different and if so, restart server (if already running)
// This applies if you change the output directory in your config file during watch/serve
this.outputDir = outputDir;
}
async getServerModule(name) {
try {
if (!name || name === DEFAULT_SERVER_OPTIONS.module) {
return EleventyDevServer;
}
// Look for peer dep in local project
let projectNodeModulesPath = TemplatePath.absolutePath("./node_modules/");
let serverPath = TemplatePath.absolutePath(projectNodeModulesPath, name);
// No references outside of the project node_modules are allowed
if (!serverPath.startsWith(projectNodeModulesPath)) {
throw new Error("Invalid node_modules name for Eleventy server instance, received:" + name);
}
let serverPackageJson = getModulePackageJson(serverPath);
// Normalize with `main` entry from
if (TemplatePath.isDirectorySync(serverPath)) {
if (serverPackageJson.main) {
serverPath = TemplatePath.absolutePath(
projectNodeModulesPath,
name,
serverPackageJson.main,
);
} else {
throw new Error(
`Eleventy server ${name} is missing a \`main\` entry in its package.json file. Traversed up from ${serverPath}.`,
);
}
}
let module = await EleventyImport(serverPath);
if (!("getServer" in module)) {
throw new Error(
`Eleventy server module requires a \`getServer\` static method. Could not find one on module: \`${name}\``,
);
}
if (serverPackageJson["11ty"]?.compatibility) {
try {
this.eleventyConfig.userConfig.versionCheck(serverPackageJson["11ty"].compatibility);
} catch (e) {
this.logger.warn(`Warning: \`${name}\` Plugin Compatibility: ${e.message}`);
}
}
return module;
} catch (e) {
this.logger.error(
"There was an error with your custom Eleventy server. Were using the default server instead.\n" +
e.message,
);
debug("Eleventy server error %o", e);
return EleventyDevServer;
}
}
get options() {
if (this._options) {
return this._options;
}
this._options = Object.assign(
{
pathPrefix: PathPrefixer.normalizePathPrefix(this.config.pathPrefix),
logger: this.logger,
},
DEFAULT_SERVER_OPTIONS,
this.config.serverOptions,
);
this._savedConfigOptions = DeepCopy({}, this.config.serverOptions);
if (!this._initOptionsFetched && this.getSetupCallback()) {
throw new Error(
"Init options have not yet been fetched in the setup callback. This probably means that `init()` has not yet been called.",
);
}
return this._options;
}
get server() {
if (!this._server) {
throw new Error("Missing server instance. Did you call .initServerInstance?");
}
return this._server;
}
async initServerInstance() {
if (this._server) {
return;
}
let serverModule = await this.getServerModule(this.options.module);
// Static method `getServer` was already checked in `getServerModule`
this._server = serverModule.getServer("eleventy-server", this.outputDir, this.options);
this.setAliases(this._aliases);
if (this._globsNeedWatching) {
this._server.watchFiles(this._watchedFiles);
this._globsNeedWatching = false;
}
}
getSetupCallback() {
let setupCallback = this.config.serverOptions.setup;
if (setupCallback && typeof setupCallback === "function") {
return setupCallback;
}
}
async #init() {
let setupCallback = this.getSetupCallback();
if (setupCallback) {
let opts = await setupCallback();
this._initOptionsFetched = true;
if (opts) {
Merge(this.options, opts);
}
}
}
async init() {
if (!this._initPromise) {
this._initPromise = this.#init();
}
return this._initPromise;
}
// Port comes in here from --port on the command line
async serve(port) {
this._commandLinePort = port;
await this.init();
await this.initServerInstance();
this.server.serve(port || this.options.port);
}
async close() {
if (this._server) {
await this._server.close();
this._server = undefined;
}
}
async sendError({ error }) {
if (this._server) {
await this.server.sendError({
error,
});
}
}
// Restart the server entirely
// We dont want to use a native `restart` method (e.g. restart() in Vite) so that
// we can correctly handle a `module` property change (changing the server type)
async restart() {
// Blow away cached options
delete this._options;
await this.close();
// saved --port in `serve()`
await this.serve(this._commandLinePort);
// rewatch the saved watched files (passthrough copy)
if ("watchFiles" in this.server) {
this.server.watchFiles(this._watchedFiles);
}
}
// checkPassthroughCopyBehavior check is called upstream in Eleventy.js
// TODO globs are not removed from watcher
watchPassthroughCopy(globs) {
this._watchedFiles = globs;
if (this._server && "watchFiles" in this.server) {
this.server.watchFiles(globs);
this._globsNeedWatching = false;
} else {
this._globsNeedWatching = true;
}
}
isEmulatedPassthroughCopyMatch(filepath) {
return isGlobMatch(filepath, this._watchedFiles);
}
hasOptionsChanged() {
try {
assert.deepStrictEqual(this.config.serverOptions, this._savedConfigOptions);
return false;
} catch (e) {
return true;
}
}
// Live reload the server
async reload(reloadEvent = {}) {
if (!this._server) {
return;
}
// Restart the server if the options have changed
if (this.hasOptionsChanged()) {
debug("Server options changed, were restarting the server");
await this.restart();
} else {
await this.server.reload(reloadEvent);
}
}
}
export default EleventyServe;

131
node_modules/@11ty/eleventy/src/EleventyWatch.js generated vendored Executable file
View File

@@ -0,0 +1,131 @@
import { TemplatePath } from "@11ty/eleventy-utils";
import PathNormalizer from "./Util/PathNormalizer.js";
/* Decides when to watch and in what mode to watch
* Incremental builds dont batch changes, they queue.
* Nonincremental builds batch.
*/
class EleventyWatch {
constructor() {
this.incremental = false;
this.isActive = false;
this.activeQueue = [];
}
isBuildRunning() {
return this.isActive;
}
setBuildRunning() {
this.isActive = true;
// pop waiting queue into the active queue
this.activeQueue = this.popNextActiveQueue();
}
setBuildFinished() {
this.isActive = false;
this.activeQueue = [];
}
getIncrementalFile() {
if (this.incremental) {
return this.activeQueue.length ? this.activeQueue[0] : false;
}
return false;
}
/* Returns the changed files currently being operated on in the current `watch` build
* Works with or without incremental (though in incremental only one file per time will be processed)
*/
getActiveQueue() {
if (!this.isActive) {
return [];
} else if (this.incremental && this.activeQueue.length === 0) {
return [];
} else if (this.incremental) {
return [this.activeQueue[0]];
}
return this.activeQueue;
}
_queueMatches(file) {
let filterCallback;
if (typeof file === "function") {
filterCallback = file;
} else {
filterCallback = (path) => path === file;
}
return this.activeQueue.filter(filterCallback);
}
hasAllQueueFiles(file) {
return (
this.activeQueue.length > 0 && this.activeQueue.length === this._queueMatches(file).length
);
}
hasQueuedFile(file) {
if (file) {
return this._queueMatches(file).length > 0;
}
return false;
}
hasQueuedFiles(files) {
for (const file of files) {
if (this.hasQueuedFile(file)) {
return true;
}
}
return false;
}
get pendingQueue() {
if (!this._queue) {
this._queue = [];
}
return this._queue;
}
set pendingQueue(value) {
this._queue = value;
}
addToPendingQueue(path) {
if (path) {
path = PathNormalizer.normalizeSeperator(TemplatePath.addLeadingDotSlash(path));
this.pendingQueue.push(path);
}
}
getPendingQueueSize() {
return this.pendingQueue.length;
}
getPendingQueue() {
return this.pendingQueue;
}
getActiveQueueSize() {
return this.activeQueue.length;
}
// returns array
popNextActiveQueue() {
if (this.incremental) {
return this.pendingQueue.length ? [this.pendingQueue.shift()] : [];
}
let ret = this.pendingQueue.slice();
this.pendingQueue = [];
return ret;
}
}
export default EleventyWatch;

164
node_modules/@11ty/eleventy/src/EleventyWatchTargets.js generated vendored Normal file
View File

@@ -0,0 +1,164 @@
import { TemplatePath } from "@11ty/eleventy-utils";
import { DepGraph } from "dependency-graph";
import JavaScriptDependencies from "./Util/JavaScriptDependencies.js";
import eventBus from "./EventBus.js";
class EleventyWatchTargets {
#templateConfig;
constructor(templateConfig) {
this.targets = new Set();
this.dependencies = new Set();
this.newTargets = new Set();
this.isEsm = false;
this.graph = new DepGraph();
this.#templateConfig = templateConfig;
}
setProjectUsingEsm(isEsmProject) {
this.isEsm = !!isEsmProject;
}
isJavaScriptDependency(path) {
return this.dependencies.has(path);
}
reset() {
this.newTargets = new Set();
}
isWatched(target) {
return this.targets.has(target);
}
addToDependencyGraph(parent, deps) {
if (!this.graph.hasNode(parent)) {
this.graph.addNode(parent);
}
for (let dep of deps) {
if (!this.graph.hasNode(dep)) {
this.graph.addNode(dep);
}
this.graph.addDependency(parent, dep);
}
}
uses(parent, dep) {
return this.getDependenciesOf(parent).includes(dep);
}
getDependenciesOf(parent) {
if (!this.graph.hasNode(parent)) {
return [];
}
return this.graph.dependenciesOf(parent);
}
getDependantsOf(child) {
if (!this.graph.hasNode(child)) {
return [];
}
return this.graph.dependantsOf(child);
}
addRaw(targets, isDependency) {
for (let target of targets) {
let path = TemplatePath.addLeadingDotSlash(target);
if (!this.isWatched(path)) {
this.newTargets.add(path);
}
this.targets.add(path);
if (isDependency) {
this.dependencies.add(path);
}
}
}
static normalize(targets) {
if (!targets) {
return [];
} else if (Array.isArray(targets)) {
return targets;
}
return [targets];
}
// add only a target
add(targets) {
this.addRaw(EleventyWatchTargets.normalize(targets));
}
static normalizeToGlobs(targets) {
return EleventyWatchTargets.normalize(targets).map((entry) =>
TemplatePath.convertToRecursiveGlobSync(entry),
);
}
addAndMakeGlob(targets) {
this.addRaw(EleventyWatchTargets.normalizeToGlobs(targets));
}
// add only a targets dependencies
async addDependencies(targets, filterCallback) {
if (this.#templateConfig && !this.#templateConfig.shouldSpiderJavaScriptDependencies()) {
return;
}
targets = EleventyWatchTargets.normalize(targets);
let deps = await JavaScriptDependencies.getDependencies(targets, this.isEsm);
if (filterCallback) {
deps = deps.filter(filterCallback);
}
for (let target of targets) {
this.addToDependencyGraph(target, deps);
}
this.addRaw(deps, true);
}
setWriter(templateWriter) {
this.writer = templateWriter;
}
clearImportCacheFor(filePathArray) {
let paths = new Set();
for (const filePath of filePathArray) {
paths.add(filePath);
// Delete from require cache so that updates to the module are re-required
let importsTheChangedFile = this.getDependantsOf(filePath);
for (let dep of importsTheChangedFile) {
paths.add(dep);
}
let isImportedInTheChangedFile = this.getDependenciesOf(filePath);
for (let dep of isImportedInTheChangedFile) {
paths.add(dep);
}
// Use GlobalDependencyMap
if (this.#templateConfig) {
for (let dep of this.#templateConfig.usesGraph.getDependantsFor(filePath)) {
paths.add(dep);
}
}
}
eventBus.emit("eleventy.importCacheReset", paths);
}
getNewTargetsSinceLastReset() {
return Array.from(this.newTargets);
}
getTargets() {
return Array.from(this.targets);
}
}
export default EleventyWatchTargets;

338
node_modules/@11ty/eleventy/src/Engines/Custom.js generated vendored Normal file
View File

@@ -0,0 +1,338 @@
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;
});
class CustomEngine extends TemplateEngine {
constructor(name, eleventyConfig) {
super(name, eleventyConfig);
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) {
this.cacheable = this.entry.compileOptions.cache;
} else if (this.needsToReadFileContents()) {
this.cacheable = true;
}
}
getExtensionMapEntry() {
if ("extensionMap" in this.config) {
let name = this.name.toLowerCase();
// Iterates over only the user config `addExtension` entries
for (let entry of this.config.extensionMap) {
let entryKey = (entry.aliasKey || entry.key || "").toLowerCase();
if (entryKey === name) {
return entry;
}
}
}
throw Error(
`Could not find a custom extension for ${this.name}. Did you add it to your config file?`,
);
}
setDefaultEngine(defaultEngine) {
this._defaultEngine = defaultEngine;
}
async getInstanceFromInputPath(inputPath) {
if (
"getInstanceFromInputPath" in this.entry &&
typeof this.entry.getInstanceFromInputPath === "function"
) {
// returns Promise
return this.entry.getInstanceFromInputPath(inputPath);
}
// aliased upstream type
if (
this._defaultEngine &&
"getInstanceFromInputPath" in this._defaultEngine &&
typeof this._defaultEngine.getInstanceFromInputPath === "function"
) {
// returns Promise
return this._defaultEngine.getInstanceFromInputPath(inputPath);
}
return false;
}
/**
* Whether to use the module loader directly
*
* @override
*/
useJavaScriptImport() {
if ("useJavaScriptImport" in this.entry) {
return this.entry.useJavaScriptImport;
}
if (
this._defaultEngine &&
"useJavaScriptImport" in this._defaultEngine &&
typeof this._defaultEngine.useJavaScriptImport === "function"
) {
return this._defaultEngine.useJavaScriptImport();
}
return false;
}
/**
* @override
*/
needsToReadFileContents() {
if ("read" in this.entry) {
return this.entry.read;
}
// Handle aliases to `11ty.js` templates, avoid reading files in the alias, see #2279
// Here, we are short circuiting fallback to defaultRenderer, does not account for compile
// functions that call defaultRenderer explicitly
if (this._defaultEngine && "needsToReadFileContents" in this._defaultEngine) {
return this._defaultEngine.needsToReadFileContents();
}
return true;
}
// If we init from multiple places, wait for the first init to finish before continuing on.
async _runningInit() {
if (this.needsInit) {
if (!this._initing) {
this._initBench = this.benchmarks.aggregate.get(`Engine (${this.name}) Init`);
this._initBench.before();
this._initing = this.entry.init.bind({
config: this.config,
bench: this.benchmarks.aggregate,
})();
}
await this._initing;
this.needsInit = false;
if (this._initBench) {
this._initBench.after();
this._initBench = undefined;
}
}
}
async getExtraDataFromFile(inputPath) {
if (this.entry.getData === false) {
return;
}
if (!("getData" in this.entry)) {
// Handle aliases to `11ty.js` templates, use upstream default engine data fetch, see #2279
if (this._defaultEngine && "getExtraDataFromFile" in this._defaultEngine) {
return this._defaultEngine.getExtraDataFromFile(inputPath);
}
return;
}
await this._runningInit();
if (typeof this.entry.getData === "function") {
let dataBench = this.benchmarks.aggregate.get(
`Engine (${this.name}) Get Data From File (Function)`,
);
dataBench.before();
let data = this.entry.getData(inputPath);
dataBench.after();
return data;
}
let keys = new Set();
if (this.entry.getData === true) {
keys.add("data");
} else if (Array.isArray(this.entry.getData)) {
for (let key of this.entry.getData) {
keys.add(key);
}
}
let dataBench = this.benchmarks.aggregate.get(`Engine (${this.name}) Get Data From File`);
dataBench.before();
let inst = await this.getInstanceFromInputPath(inputPath);
if (inst === false) {
dataBench.after();
return Promise.reject(
new Error(
`\`getInstanceFromInputPath\` callback missing from '${this.name}' template engine plugin. It is required when \`getData\` is in use. You can set \`getData: false\` to opt-out of this.`,
),
);
}
// override keys set at the plugin level in the individual template
if (inst.eleventyDataKey) {
keys = new Set(inst.eleventyDataKey);
}
let mixins;
if (this.config) {
// Object.assign usage: see TemplateRenderCustomTest.js: `JavaScript functions should not be mutable but not *that* mutable`
mixins = Object.assign({}, this.config.javascriptFunctions);
}
let promises = [];
for (let key of keys) {
promises.push(
getJavaScriptData(inst, inputPath, key, {
mixins,
isObjectRequired: key === "data",
}),
);
}
let results = await Promise.all(promises);
let data = {};
for (let result of results) {
Object.assign(data, result);
}
dataBench.after();
return data;
}
async compile(str, inputPath, ...args) {
await this._runningInit();
let defaultCompilationFn;
if (this._defaultEngine) {
defaultCompilationFn = async (data) => {
const renderFn = await this._defaultEngine.compile(str, inputPath, ...args);
return renderFn(data);
};
}
// Fall back to default compiler if the user does not provide their own
if (!this.entry.compile) {
if (defaultCompilationFn) {
return defaultCompilationFn;
} else {
throw new Error(
`Missing \`compile\` property for custom template syntax definition eleventyConfig.addExtension("${this.name}"). This is not necessary when aliasing to an existing template syntax.`,
);
}
}
// TODO generalize this (look at JavaScript.js)
let fn = this.entry.compile.bind({
config: this.config,
addDependencies: (from, toArray = []) => {
this.config.uses.addDependency(from, toArray);
},
defaultRenderer: defaultCompilationFn, // bind defaultRenderer to compile function
})(str, inputPath);
// Support `undefined` to skip compile/render
if (fn) {
// Bind defaultRenderer to render function
if ("then" in fn && typeof fn.then === "function") {
// Promise, wait to bind
return fn.then((fn) => {
if (typeof fn === "function") {
return fn.bind({
defaultRenderer: defaultCompilationFn,
});
}
return fn;
});
} else if ("bind" in fn && typeof fn.bind === "function") {
return fn.bind({
defaultRenderer: defaultCompilationFn,
});
}
}
return fn;
}
get defaultTemplateFileExtension() {
return this.entry.outputFileExtension ?? "html";
}
// Whether or not to wrap in Eleventy layouts
useLayouts() {
// TODO future change fallback to `this.defaultTemplateFileExtension === "html"`
return this.entry.useLayouts ?? true;
}
hasDependencies(inputPath) {
if (this.config.uses.getDependencies(inputPath) === false) {
return false;
}
return true;
}
isFileRelevantTo(inputPath, comparisonFile, includeLayouts) {
return this.config.uses.isFileRelevantTo(inputPath, comparisonFile, includeLayouts);
}
getCompileCacheKey(str, inputPath) {
// 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);
if (this.entry.compileOptions && "getCacheKey" in this.entry.compileOptions) {
if (typeof this.entry.compileOptions.getCacheKey !== "function") {
throw new Error(
`\`compileOptions.getCacheKey\` must be a function in addExtension for the ${this.name} type`,
);
}
return {
useCache,
key: this.entry.compileOptions.getCacheKey(str, inputPath),
};
}
let { key } = super.getCompileCacheKey(str, inputPath);
return {
useCache,
key,
};
}
permalinkNeedsCompilation(/*str*/) {
if (this.entry.compileOptions && "permalink" in this.entry.compileOptions) {
let p = this.entry.compileOptions.permalink;
if (p === "raw") {
return false;
}
// permalink: false is aliased to permalink: () => false
if (p === false) {
return () => false;
}
return this.entry.compileOptions.permalink;
}
// Breaking: default changed from `true` to `false` in 3.0.0-alpha.13
return false;
}
static shouldSpiderJavaScriptDependencies(entry) {
if (entry.compileOptions && "spiderJavaScriptDependencies" in entry.compileOptions) {
return entry.compileOptions.spiderJavaScriptDependencies;
}
return false;
}
}
export default CustomEngine;

View File

@@ -0,0 +1,34 @@
import { RetrieveGlobals } from "node-retrieve-globals";
// `javascript` Front Matter Type
export default function (frontMatterCode, context = {}) {
let { filePath } = context;
// context.language would be nice as a guard, but was unreliable
if (frontMatterCode.trimStart().startsWith("{")) {
return context.engines.jsLegacy.parse(frontMatterCode, context);
}
let vm = new RetrieveGlobals(frontMatterCode, {
filePath,
// ignored if vm.Module is stable (or --experimental-vm-modules)
transformEsmImports: true,
});
// Future warning until vm.Module is stable:
// If the frontMatterCode uses `import` this uses the `experimentalModuleApi`
// option in node-retrieve-globals to workaround https://github.com/zachleat/node-retrieve-globals/issues/2
let data = {
page: {
// Theoretically fileSlug and filePathStem could be added here but require extensionMap
inputPath: filePath,
},
};
// this is async, but its handled in Eleventy upstream.
return vm.getGlobalContext(data, {
reuseGlobal: true,
dynamicImport: true,
// addRequire: true,
});
}

28
node_modules/@11ty/eleventy/src/Engines/Html.js generated vendored Normal file
View File

@@ -0,0 +1,28 @@
import TemplateEngine from "./TemplateEngine.js";
class Html extends TemplateEngine {
constructor(name, eleventyConfig) {
super(name, eleventyConfig);
this.cacheable = true;
}
async compile(str, inputPath, preTemplateEngine) {
if (preTemplateEngine) {
let engine = await this.engineManager.getEngine(preTemplateEngine, this.extensionMap);
let fnReady = engine.compile(str, inputPath);
return async function (data) {
let fn = await fnReady;
return fn(data);
};
}
return function () {
// do nothing with data if parseHtmlWith is falsy
return str;
};
}
}
export default Html;

237
node_modules/@11ty/eleventy/src/Engines/JavaScript.js generated vendored Normal file
View File

@@ -0,0 +1,237 @@
import { TemplatePath, isPlainObject } from "@11ty/eleventy-utils";
import TemplateEngine from "./TemplateEngine.js";
import EleventyBaseError from "../Errors/EleventyBaseError.js";
import getJavaScriptData from "../Util/GetJavaScriptData.js";
import EventBusUtil from "../Util/EventBusUtil.js";
import { EleventyImport } from "../Util/Require.js";
import { augmentFunction, augmentObject } from "./Util/ContextAugmenter.js";
class JavaScriptTemplateNotDefined extends EleventyBaseError {}
class JavaScript extends TemplateEngine {
constructor(name, templateConfig) {
super(name, templateConfig);
this.instances = {};
this.cacheable = false;
EventBusUtil.soloOn("eleventy.templateModified", (inputPath, metadata = {}) => {
let { usedByDependants, relevantLayouts } = metadata;
// Remove from cached instances when modified
let instancesToDelete = [
inputPath,
...(usedByDependants || []),
...(relevantLayouts || []),
].map((entry) => TemplatePath.addLeadingDotSlash(entry));
for (let inputPath of instancesToDelete) {
if (inputPath in this.instances) {
delete this.instances[inputPath];
}
}
});
}
normalize(result) {
if (Buffer.isBuffer(result)) {
return result.toString();
}
return result;
}
// String, Buffer, Promise
// Function, Class
// Object
// Module
_getInstance(mod) {
let noop = function () {
return "";
};
let originalModData = mod?.data;
if (typeof mod === "object" && mod.default && this.eleventyConfig.getIsProjectUsingEsm()) {
mod = mod.default;
}
if (typeof mod === "string" || mod instanceof Buffer || mod.then) {
return { render: () => mod };
} else if (typeof mod === "function") {
if (mod.prototype?.data || mod.prototype?.render) {
if (!("render" in mod.prototype)) {
mod.prototype.render = noop;
}
if (!("data" in mod.prototype) && !mod.data && originalModData) {
mod.prototype.data = originalModData;
}
return new mod();
} else {
return {
...(originalModData ? { data: originalModData } : undefined),
render: mod,
};
}
} else if ("data" in mod || "render" in mod) {
if (!mod.render) {
mod.render = noop;
}
if (!mod.data && originalModData) {
mod.data = originalModData;
}
return mod;
}
}
async #getInstanceFromInputPath(inputPath) {
let mod;
let relativeInputPath =
this.eleventyConfig.directories.getInputPathRelativeToInputDirectory(inputPath);
if (this.eleventyConfig.userConfig.isVirtualTemplate(relativeInputPath)) {
mod = this.eleventyConfig.userConfig.virtualTemplates[relativeInputPath].content;
} else {
let isEsm = this.eleventyConfig.getIsProjectUsingEsm();
mod = await EleventyImport(inputPath, isEsm ? "esm" : "cjs");
}
let inst = this._getInstance(mod);
if (inst) {
this.instances[inputPath] = inst;
} else {
throw new JavaScriptTemplateNotDefined(
`No JavaScript template returned from ${inputPath}. Did you assign module.exports (CommonJS) or export (ESM)?`,
);
}
return inst;
}
async getInstanceFromInputPath(inputPath) {
if (!this.instances[inputPath]) {
this.instances[inputPath] = this.#getInstanceFromInputPath(inputPath);
}
return this.instances[inputPath];
}
/**
* JavaScript files defer to the module loader rather than read the files to strings
*
* @override
*/
needsToReadFileContents() {
return false;
}
/**
* Use the module loader directly
*
* @override
*/
useJavaScriptImport() {
return true;
}
async getExtraDataFromFile(inputPath) {
let inst = await this.getInstanceFromInputPath(inputPath);
return getJavaScriptData(inst, inputPath);
}
getJavaScriptFunctions(inst) {
let fns = {};
let configFns = this.config.javascriptFunctions;
for (let key in configFns) {
// prefer pre-existing `page` javascriptFunction, if one exists
fns[key] = augmentFunction(configFns[key], {
source: inst,
overwrite: false,
});
}
return fns;
}
// Backwards compat
static wrapJavaScriptFunction(inst, fn) {
return augmentFunction(fn, {
source: inst,
});
}
addExportsToBundles(inst, url) {
let cfg = this.eleventyConfig.userConfig;
if (!("getBundleManagers" in cfg)) {
return;
}
let managers = cfg.getBundleManagers();
for (let name in managers) {
let mgr = managers[name];
let key = mgr.getBundleExportKey();
if (!key) {
continue;
}
if (typeof inst[key] === "string") {
// export const css = ``;
mgr.addToPage(url, inst[key]);
} else if (isPlainObject(inst[key])) {
if (typeof inst[key][name] === "string") {
// Object with bundle names:
// export const bundle = {
// css: ``
// };
mgr.addToPage(url, inst[key][name]);
} else if (isPlainObject(inst[key][name])) {
// Object with bucket names:
// export const bundle = {
// css: {
// default: ``
// }
// };
for (let bucketName in inst[key][name]) {
mgr.addToPage(url, inst[key][name][bucketName], bucketName);
}
}
}
}
}
async compile(str, inputPath) {
let inst;
if (str) {
// When str has a value, it's being used for permalinks in data
inst = this._getInstance(str);
} else {
// For normal templates, str will be falsy.
inst = await this.getInstanceFromInputPath(inputPath);
}
if (inst?.render) {
return function (data = {}) {
// TODO does this do anything meaningful for non-classes?
// `inst` should have a normalized `render` function from _getInstance
// Map exports to bundles
if (data.page?.url) {
this.addExportsToBundles(inst, data.page.url);
}
augmentObject(inst, {
source: data,
overwrite: false,
});
Object.assign(inst, this.getJavaScriptFunctions(inst));
return this.normalize(inst.render.call(inst, data));
}.bind(this);
}
}
static shouldSpiderJavaScriptDependencies() {
return true;
}
}
export default JavaScript;

326
node_modules/@11ty/eleventy/src/Engines/Liquid.js generated vendored Normal file
View File

@@ -0,0 +1,326 @@
import moo from "moo";
import { Tokenizer, TokenKind, evalToken, Liquid as LiquidJs } from "liquidjs";
import { TemplatePath } from "@11ty/eleventy-utils";
// import debugUtil from "debug";
import TemplateEngine from "./TemplateEngine.js";
import { augmentObject } from "./Util/ContextAugmenter.js";
// const debug = debugUtil("Eleventy:Liquid");
class Liquid extends TemplateEngine {
static argumentLexerOptions = {
number: /[0-9]+\.*[0-9]*/,
doubleQuoteString: /"(?:\\["\\]|[^\n"\\])*"/,
singleQuoteString: /'(?:\\['\\]|[^\n'\\])*'/,
keyword: /[a-zA-Z0-9.\-_]+/,
"ignore:whitespace": /[, \t]+/, // includes comma separator
};
constructor(name, eleventyConfig) {
super(name, eleventyConfig);
this.liquidOptions = this.config.liquidOptions || {};
this.setLibrary(this.config.libraryOverrides.liquid);
this.argLexer = moo.compile(Liquid.argumentLexerOptions);
this.cacheable = true;
}
setLibrary(override) {
// warning, the include syntax supported here does not exactly match what Jekyll uses.
this.liquidLib = override || new LiquidJs(this.getLiquidOptions());
this.setEngineLib(this.liquidLib);
this.addFilters(this.config.liquidFilters);
// TODO these all go to the same place (addTag), add warnings for overwrites
this.addCustomTags(this.config.liquidTags);
this.addAllShortcodes(this.config.liquidShortcodes);
this.addAllPairedShortcodes(this.config.liquidPairedShortcodes);
}
getLiquidOptions() {
let defaults = {
root: [this.dirs.includes, this.dirs.input], // supplemented in compile with inputPath below
extname: ".liquid",
strictFilters: true,
// TODO?
// cache: true,
};
let options = Object.assign(defaults, this.liquidOptions || {});
// debug("Liquid constructor options: %o", options);
return options;
}
static wrapFilter(name, fn) {
/**
* @this {object}
*/
return function (...args) {
// Set this.eleventy and this.page
if (typeof this.context?.get === "function") {
augmentObject(this, {
source: this.context,
getter: (key, context) => context.get([key]),
lazy: this.context.strictVariables,
});
}
// We *dont* wrap this in an EleventyFilterError because Liquid has a better error message with line/column information in the template
return fn.call(this, ...args);
};
}
// Shortcodes
static normalizeScope(context) {
let obj = {};
if (context) {
obj.ctx = context; // Full context available on `ctx`
// Set this.eleventy and this.page
augmentObject(obj, {
source: context,
getter: (key, context) => context.get([key]),
lazy: context.strictVariables,
});
}
return obj;
}
addCustomTags(tags) {
for (let name in tags) {
this.addTag(name, tags[name]);
}
}
addFilters(filters) {
for (let name in filters) {
this.addFilter(name, filters[name]);
}
}
addFilter(name, filter) {
this.liquidLib.registerFilter(name, Liquid.wrapFilter(name, filter));
}
addTag(name, tagFn) {
let tagObj;
if (typeof tagFn === "function") {
tagObj = tagFn(this.liquidLib);
} else {
throw new Error(
"Liquid.addTag expects a callback function to be passed in: addTag(name, function(liquidEngine) { return { parse: …, render: … } })",
);
}
this.liquidLib.registerTag(name, tagObj);
}
addAllShortcodes(shortcodes) {
for (let name in shortcodes) {
this.addShortcode(name, shortcodes[name]);
}
}
addAllPairedShortcodes(shortcodes) {
for (let name in shortcodes) {
this.addPairedShortcode(name, shortcodes[name]);
}
}
static parseArguments(lexer, str) {
let argArray = [];
if (!lexer) {
lexer = moo.compile(Liquid.argumentLexerOptions);
}
if (typeof str === "string") {
lexer.reset(str);
let arg = lexer.next();
while (arg) {
/*{
type: 'doubleQuoteString',
value: '"test 2"',
text: '"test 2"',
toString: [Function: tokenToString],
offset: 0,
lineBreaks: 0,
line: 1,
col: 1 }*/
if (arg.type.indexOf("ignore:") === -1) {
// Push the promise into an array instead of awaiting it here.
// This forces the promises to run in order with the correct scope value for each arg.
// Otherwise they run out of order and can lead to undefined values for arguments in layout template shortcodes.
// console.log( arg.value, scope, engine );
argArray.push(arg.value);
}
arg = lexer.next();
}
}
return argArray;
}
static parseArgumentsBuiltin(args) {
let tokenizer = new Tokenizer(args);
let parsedArgs = [];
let value = tokenizer.readValue();
while (value) {
parsedArgs.push(value);
tokenizer.skipBlank();
if (tokenizer.peek() === ",") {
tokenizer.advance();
}
value = tokenizer.readValue();
}
tokenizer.end();
return parsedArgs;
}
addShortcode(shortcodeName, shortcodeFn) {
let _t = this;
this.addTag(shortcodeName, function (liquidEngine) {
return {
parse(tagToken) {
this.name = tagToken.name;
if (_t.config.liquidParameterParsing === "builtin") {
this.orderedArgs = Liquid.parseArgumentsBuiltin(tagToken.args);
// note that Liquid does have a Hash class for name-based argument parsing but offers no easy to support both modes in one class
} else {
this.legacyArgs = tagToken.args;
}
},
render: function* (ctx) {
let argArray = [];
if (this.legacyArgs) {
let rawArgs = Liquid.parseArguments(_t.argLexer, this.legacyArgs);
for (let arg of rawArgs) {
let b = yield liquidEngine.evalValue(arg, ctx);
argArray.push(b);
}
} else if (this.orderedArgs) {
for (let arg of this.orderedArgs) {
let b = yield evalToken(arg, ctx);
argArray.push(b);
}
}
let ret = yield shortcodeFn.call(Liquid.normalizeScope(ctx), ...argArray);
return ret;
},
};
});
}
addPairedShortcode(shortcodeName, shortcodeFn) {
let _t = this;
this.addTag(shortcodeName, function (liquidEngine) {
return {
parse(tagToken, remainTokens) {
this.name = tagToken.name;
if (_t.config.liquidParameterParsing === "builtin") {
this.orderedArgs = Liquid.parseArgumentsBuiltin(tagToken.args);
// note that Liquid does have a Hash class for name-based argument parsing but offers no easy to support both modes in one class
} else {
this.legacyArgs = tagToken.args;
}
this.templates = [];
var stream = liquidEngine.parser
.parseStream(remainTokens)
.on("template", (tpl) => this.templates.push(tpl))
.on("tag:end" + shortcodeName, () => stream.stop())
.on("end", () => {
throw new Error(`tag ${tagToken.raw} not closed`);
});
stream.start();
},
render: function* (ctx /*, emitter*/) {
let argArray = [];
if (this.legacyArgs) {
let rawArgs = Liquid.parseArguments(_t.argLexer, this.legacyArgs);
for (let arg of rawArgs) {
let b = yield liquidEngine.evalValue(arg, ctx);
argArray.push(b);
}
} else if (this.orderedArgs) {
for (let arg of this.orderedArgs) {
let b = yield evalToken(arg, ctx);
argArray.push(b);
}
}
const html = yield liquidEngine.renderer.renderTemplates(this.templates, ctx);
let ret = yield shortcodeFn.call(Liquid.normalizeScope(ctx), html, ...argArray);
return ret;
},
};
});
}
parseForSymbols(str) {
let tokenizer = new Tokenizer(str);
/** @type {Array} */
let tokens = tokenizer.readTopLevelTokens();
let symbols = tokens
.filter((token) => token.kind === TokenKind.Output)
.map((token) => {
// manually remove filters 😅
return token.content.split("|").map((entry) => entry.trim())[0];
});
return symbols;
}
// Dont return a boolean if permalink is a function (see TemplateContent->renderPermalink)
/** @returns {boolean|undefined} */
permalinkNeedsCompilation(str) {
if (typeof str === "string") {
return this.needsCompilation(str);
}
}
needsCompilation(str) {
let options = this.liquidLib.options;
return (
str.indexOf(options.tagDelimiterLeft) !== -1 ||
str.indexOf(options.outputDelimiterLeft) !== -1
);
}
async compile(str, inputPath) {
let engine = this.liquidLib;
let tmplReady = engine.parse(str, inputPath);
// Required for relative includes
let options = {};
if (!inputPath || inputPath === "liquid" || inputPath === "md") {
// do nothing
} else {
options.root = [TemplatePath.getDirFromFilePath(inputPath)];
}
return async function (data) {
let tmpl = await tmplReady;
return engine.render(tmpl, data, options);
};
}
}
export default Liquid;

93
node_modules/@11ty/eleventy/src/Engines/Markdown.js generated vendored Normal file
View File

@@ -0,0 +1,93 @@
import markdownIt from "markdown-it";
import TemplateEngine from "./TemplateEngine.js";
class Markdown extends TemplateEngine {
constructor(name, eleventyConfig) {
super(name, eleventyConfig);
this.markdownOptions = {};
this.setLibrary(this.config.libraryOverrides.md);
this.cacheable = true;
}
setLibrary(mdLib) {
this.mdLib = mdLib || markdownIt(this.getMarkdownOptions());
// Overrides a highlighter set in `markdownOptions`
// This is separate so devs can pass in a new mdLib and still use the official eleventy plugin for markdown highlighting
if (this.config.markdownHighlighter && typeof this.mdLib.set === "function") {
this.mdLib.set({
highlight: this.config.markdownHighlighter,
});
}
if (typeof this.mdLib.disable === "function") {
// Disable indented code blocks by default (Issue #2438)
this.mdLib.disable("code");
}
this.setEngineLib(this.mdLib);
}
setMarkdownOptions(options) {
this.markdownOptions = options;
}
getMarkdownOptions() {
// work with "mode" presets https://github.com/markdown-it/markdown-it#init-with-presets-and-options
if (typeof this.markdownOptions === "string") {
return this.markdownOptions;
}
return Object.assign(
{
html: true,
},
this.markdownOptions || {},
);
}
async compile(str, inputPath, preTemplateEngine, bypassMarkdown) {
let mdlib = this.mdLib;
if (preTemplateEngine) {
let engine;
if (typeof preTemplateEngine === "string") {
engine = await this.engineManager.getEngine(preTemplateEngine, this.extensionMap);
} else {
engine = preTemplateEngine;
}
let fnReady = engine.compile(str, inputPath);
if (bypassMarkdown) {
return async function (data) {
let fn = await fnReady;
return fn(data);
};
} else {
return async function (data) {
let fn = await fnReady;
let preTemplateEngineRender = await fn(data);
let finishedRender = mdlib.render(preTemplateEngineRender, data);
return finishedRender;
};
}
} else {
if (bypassMarkdown) {
return function () {
return str;
};
} else {
return function (data) {
return mdlib.render(str, data);
};
}
}
}
}
export default Markdown;

447
node_modules/@11ty/eleventy/src/Engines/Nunjucks.js generated vendored Executable file
View File

@@ -0,0 +1,447 @@
import NunjucksLib from "nunjucks";
import { TemplatePath } from "@11ty/eleventy-utils";
import TemplateEngine from "./TemplateEngine.js";
import EleventyBaseError from "../Errors/EleventyBaseError.js";
import EventBusUtil from "../Util/EventBusUtil.js";
import { augmentObject } from "./Util/ContextAugmenter.js";
class EleventyNunjucksError extends EleventyBaseError {}
class Nunjucks extends TemplateEngine {
constructor(name, eleventyConfig) {
super(name, eleventyConfig);
this.nunjucksEnvironmentOptions = this.config.nunjucksEnvironmentOptions || { dev: true };
this.nunjucksPrecompiledTemplates = this.config.nunjucksPrecompiledTemplates || {};
this._usingPrecompiled = Object.keys(this.nunjucksPrecompiledTemplates).length > 0;
this.setLibrary(this.config.libraryOverrides.njk);
this.cacheable = true;
}
_setEnv(override) {
if (override) {
this.njkEnv = override;
} else if (this._usingPrecompiled) {
// Precompiled templates to avoid eval!
const NodePrecompiledLoader = function () {};
NodePrecompiledLoader.prototype.getSource = (name) => {
// https://github.com/mozilla/nunjucks/blob/fd500902d7c88672470c87170796de52fc0f791a/nunjucks/src/precompiled-loader.js#L5
return {
src: {
type: "code",
obj: this.nunjucksPrecompiledTemplates[name],
},
// Maybe add this?
// path,
// noCache: true
};
};
this.njkEnv = new NunjucksLib.Environment(
new NodePrecompiledLoader(),
this.nunjucksEnvironmentOptions,
);
} else {
let paths = new Set();
paths.add(super.getIncludesDir());
paths.add(TemplatePath.getWorkingDir());
// Filter out undefined paths
let fsLoader = new NunjucksLib.FileSystemLoader(Array.from(paths).filter(Boolean));
this.njkEnv = new NunjucksLib.Environment(fsLoader, this.nunjucksEnvironmentOptions);
}
this.config.events.emit("eleventy.engine.njk", {
nunjucks: NunjucksLib,
environment: this.njkEnv,
});
}
setLibrary(override) {
this._setEnv(override);
// Correct, but overbroad. Better would be to evict more granularly, but
// resolution from paths isn't straightforward.
EventBusUtil.soloOn("eleventy.templateModified", (/*path*/) => {
this.njkEnv.invalidateCache();
});
this.setEngineLib(this.njkEnv);
this.addFilters(this.config.nunjucksFilters);
this.addFilters(this.config.nunjucksAsyncFilters, true);
// TODO these all go to the same place (addTag), add warnings for overwrites
// TODO(zachleat): variableName should work with quotes or without quotes (same as {% set %})
this.addPairedShortcode("setAsync", function (content, variableName) {
this.ctx[variableName] = content;
return "";
});
this.addCustomTags(this.config.nunjucksTags);
this.addAllShortcodes(this.config.nunjucksShortcodes);
this.addAllShortcodes(this.config.nunjucksAsyncShortcodes, true);
this.addAllPairedShortcodes(this.config.nunjucksPairedShortcodes);
this.addAllPairedShortcodes(this.config.nunjucksAsyncPairedShortcodes, true);
this.addGlobals(this.config.nunjucksGlobals);
}
addFilters(filters, isAsync) {
for (let name in filters) {
this.njkEnv.addFilter(name, Nunjucks.wrapFilter(name, filters[name]), isAsync);
}
}
static wrapFilter(name, fn) {
return function (...args) {
try {
augmentObject(this, {
source: this.ctx,
lazy: false, // context.env?.opts.throwOnUndefined,
});
return fn.call(this, ...args);
} catch (e) {
throw new EleventyNunjucksError(
`Error in Nunjucks Filter \`${name}\`${this.page ? ` (${this.page.inputPath})` : ""}`,
e,
);
}
};
}
// Shortcodes
static normalizeContext(context) {
let obj = {};
if (context.ctx) {
obj.ctx = context.ctx;
obj.env = context.env;
augmentObject(obj, {
source: context.ctx,
lazy: false, // context.env?.opts.throwOnUndefined,
});
}
return obj;
}
addCustomTags(tags) {
for (let name in tags) {
this.addTag(name, tags[name]);
}
}
addTag(name, tagFn) {
let tagObj;
if (typeof tagFn === "function") {
tagObj = tagFn(NunjucksLib, this.njkEnv);
} else {
throw new Error(
"Nunjucks.addTag expects a callback function to be passed in: addTag(name, function(nunjucksEngine) {})",
);
}
this.njkEnv.addExtension(name, tagObj);
}
addGlobals(globals) {
for (let name in globals) {
this.addGlobal(name, globals[name]);
}
}
addGlobal(name, globalFn) {
this.njkEnv.addGlobal(name, globalFn);
}
addAllShortcodes(shortcodes, isAsync = false) {
for (let name in shortcodes) {
this.addShortcode(name, shortcodes[name], isAsync);
}
}
addAllPairedShortcodes(shortcodes, isAsync = false) {
for (let name in shortcodes) {
this.addPairedShortcode(name, shortcodes[name], isAsync);
}
}
_getShortcodeFn(shortcodeName, shortcodeFn, isAsync = false) {
return function ShortcodeFunction() {
this.tags = [shortcodeName];
this.parse = function (parser, nodes) {
let args;
let tok = parser.nextToken();
args = parser.parseSignature(true, true);
// Nunjucks bug with non-paired custom tags bug still exists even
// though this issue is closed. Works fine for paired.
// https://github.com/mozilla/nunjucks/issues/158
if (args.children.length === 0) {
args.addChild(new nodes.Literal(0, 0, ""));
}
parser.advanceAfterBlockEnd(tok.value);
if (isAsync) {
return new nodes.CallExtensionAsync(this, "run", args);
}
return new nodes.CallExtension(this, "run", args);
};
this.run = function (...args) {
let resolve;
if (isAsync) {
resolve = args.pop();
}
let [context, ...argArray] = args;
if (isAsync) {
let ret = shortcodeFn.call(Nunjucks.normalizeContext(context), ...argArray);
// #3286 error messaging when the shortcode is not a promise
if (!ret?.then) {
resolve(
new EleventyNunjucksError(
`Error with Nunjucks shortcode \`${shortcodeName}\`: it was defined as asynchronous but was actually synchronous. This is important for Nunjucks.`,
),
);
}
ret.then(
function (returnValue) {
resolve(null, new NunjucksLib.runtime.SafeString("" + returnValue));
},
function (e) {
resolve(
new EleventyNunjucksError(`Error with Nunjucks shortcode \`${shortcodeName}\``, e),
);
},
);
} else {
try {
let ret = shortcodeFn.call(Nunjucks.normalizeContext(context), ...argArray);
return new NunjucksLib.runtime.SafeString("" + ret);
} catch (e) {
throw new EleventyNunjucksError(
`Error with Nunjucks shortcode \`${shortcodeName}\``,
e,
);
}
}
};
};
}
_getPairedShortcodeFn(shortcodeName, shortcodeFn, isAsync = false) {
return function PairedShortcodeFunction() {
this.tags = [shortcodeName];
this.parse = function (parser, nodes) {
var tok = parser.nextToken();
var args = parser.parseSignature(true, true);
parser.advanceAfterBlockEnd(tok.value);
var body = parser.parseUntilBlocks("end" + shortcodeName);
parser.advanceAfterBlockEnd();
return new nodes.CallExtensionAsync(this, "run", args, [body]);
};
this.run = function (...args) {
let resolve = args.pop();
let body = args.pop();
let [context, ...argArray] = args;
body(function (e, bodyContent) {
if (e) {
resolve(
new EleventyNunjucksError(
`Error with Nunjucks paired shortcode \`${shortcodeName}\``,
e,
),
);
}
if (isAsync) {
let ret = shortcodeFn.call(
Nunjucks.normalizeContext(context),
bodyContent,
...argArray,
);
// #3286 error messaging when the shortcode is not a promise
if (!ret?.then) {
throw new EleventyNunjucksError(
`Error with Nunjucks shortcode \`${shortcodeName}\`: it was defined as asynchronous but was actually synchronous. This is important for Nunjucks.`,
);
}
ret.then(
function (returnValue) {
resolve(null, new NunjucksLib.runtime.SafeString(returnValue));
},
function (e) {
resolve(
new EleventyNunjucksError(
`Error with Nunjucks paired shortcode \`${shortcodeName}\``,
e,
),
);
},
);
} else {
try {
resolve(
null,
new NunjucksLib.runtime.SafeString(
shortcodeFn.call(Nunjucks.normalizeContext(context), bodyContent, ...argArray),
),
);
} catch (e) {
resolve(
new EleventyNunjucksError(
`Error with Nunjucks paired shortcode \`${shortcodeName}\``,
e,
),
);
}
}
});
};
};
}
addShortcode(shortcodeName, shortcodeFn, isAsync = false) {
let fn = this._getShortcodeFn(shortcodeName, shortcodeFn, isAsync);
this.njkEnv.addExtension(shortcodeName, new fn());
}
addPairedShortcode(shortcodeName, shortcodeFn, isAsync = false) {
let fn = this._getPairedShortcodeFn(shortcodeName, shortcodeFn, isAsync);
this.njkEnv.addExtension(shortcodeName, new fn());
}
// Dont return a boolean if permalink is a function (see TemplateContent->renderPermalink)
permalinkNeedsCompilation(str) {
if (typeof str === "string") {
return this.needsCompilation(str);
}
}
needsCompilation(str) {
// Defend against syntax customisations:
// https://mozilla.github.io/nunjucks/api.html#customizing-syntax
let optsTags = this.njkEnv.opts.tags || {};
let blockStart = optsTags.blockStart || "{%";
let variableStart = optsTags.variableStart || "{{";
let commentStart = optsTags.variableStart || "{#";
return (
str.indexOf(blockStart) !== -1 ||
str.indexOf(variableStart) !== -1 ||
str.indexOf(commentStart) !== -1
);
}
_getParseExtensions() {
if (this._parseExtensions) {
return this._parseExtensions;
}
// add extensions so the parser knows about our custom tags/blocks
let ext = [];
for (let name in this.config.nunjucksTags) {
let fn = this._getShortcodeFn(name, () => {});
ext.push(new fn());
}
for (let name in this.config.nunjucksShortcodes) {
let fn = this._getShortcodeFn(name, () => {});
ext.push(new fn());
}
for (let name in this.config.nunjucksAsyncShortcodes) {
let fn = this._getShortcodeFn(name, () => {}, true);
ext.push(new fn());
}
for (let name in this.config.nunjucksPairedShortcodes) {
let fn = this._getPairedShortcodeFn(name, () => {});
ext.push(new fn());
}
for (let name in this.config.nunjucksAsyncPairedShortcodes) {
let fn = this._getPairedShortcodeFn(name, () => {}, true);
ext.push(new fn());
}
this._parseExtensions = ext;
return ext;
}
/* Outputs an Array of lodash get selectors */
parseForSymbols(str) {
const { parser, nodes } = NunjucksLib;
let obj = parser.parse(str, this._getParseExtensions());
let linesplit = str.split("\n");
let values = obj.findAll(nodes.Value);
let symbols = obj.findAll(nodes.Symbol).map((entry) => {
let name = [entry.value];
let nestedIndex = -1;
for (let val of values) {
if (nestedIndex > -1) {
/* deep.object.syntax */
if (linesplit[val.lineno].charAt(nestedIndex) === ".") {
name.push(val.value);
nestedIndex += val.value.length + 1;
} else {
nestedIndex = -1;
}
} else if (
val.lineno === entry.lineno &&
val.colno === entry.colno &&
val.value === entry.value
) {
nestedIndex = entry.colno + entry.value.length;
}
}
return name.join(".");
});
let uniqueSymbols = Array.from(new Set(symbols));
return uniqueSymbols;
}
async compile(str, inputPath) {
let tmpl;
// *All* templates are precompiled to avoid runtime eval
if (this._usingPrecompiled) {
tmpl = this.njkEnv.getTemplate(str, true);
} else if (!inputPath || inputPath === "njk" || inputPath === "md") {
tmpl = new NunjucksLib.Template(str, this.njkEnv, null, false);
} else {
tmpl = new NunjucksLib.Template(str, this.njkEnv, inputPath, false);
}
return function (data) {
return new Promise(function (resolve, reject) {
tmpl.render(data, function (err, res) {
if (err) {
reject(err);
} else {
resolve(res);
}
});
});
};
}
}
export default Nunjucks;

View File

@@ -0,0 +1,184 @@
import EleventyExtensionMap from "../EleventyExtensionMap.js";
import EleventyBaseError from "../Errors/EleventyBaseError.js";
class TemplateEngineConfigError extends EleventyBaseError {}
class TemplateEngine {
constructor(name, eleventyConfig) {
this.name = name;
this.engineLib = null;
this.cacheable = false;
if (!eleventyConfig) {
throw new TemplateEngineConfigError("Missing `eleventyConfig` argument.");
}
this.eleventyConfig = eleventyConfig;
}
get dirs() {
return this.eleventyConfig.directories;
}
get inputDir() {
return this.dirs.input;
}
get includesDir() {
return this.dirs.includes;
}
get config() {
if (this.eleventyConfig.constructor.name !== "TemplateConfig") {
throw new Error("Expecting a TemplateConfig instance.");
}
return this.eleventyConfig.getConfig();
}
get benchmarks() {
if (!this._benchmarks) {
this._benchmarks = {
aggregate: this.config.benchmarkManager.get("Aggregate"),
};
}
return this._benchmarks;
}
get engineManager() {
return this._engineManager;
}
set engineManager(manager) {
this._engineManager = manager;
}
get extensionMap() {
if (!this._extensionMap) {
this._extensionMap = new EleventyExtensionMap(this.eleventyConfig);
this._extensionMap.setFormats([]);
}
return this._extensionMap;
}
set extensionMap(map) {
this._extensionMap = map;
}
get extensions() {
if (!this._extensions) {
this._extensions = this.extensionMap.getExtensionsFromKey(this.name);
}
return this._extensions;
}
get extensionEntries() {
if (!this._extensionEntries) {
this._extensionEntries = this.extensionMap.getExtensionEntriesFromKey(this.name);
}
return this._extensionEntries;
}
getName() {
return this.name;
}
// Backwards compat
getIncludesDir() {
return this.includesDir;
}
/**
* @protected
*/
setEngineLib(engineLib) {
this.engineLib = engineLib;
// Run engine amendments (via issue #2438)
for (let amendment of this.config.libraryAmendments[this.name] || []) {
// TODO itd be nice if this were async friendly
amendment(engineLib);
}
}
getEngineLib() {
return this.engineLib;
}
async _testRender(str, data) {
// @ts-ignore
let fn = await this.compile(str);
return fn(data);
}
useJavaScriptImport() {
return false;
}
// JavaScript files defer to the module loader rather than read the files to strings
needsToReadFileContents() {
return true;
}
getExtraDataFromFile() {
return {};
}
getCompileCacheKey(str, inputPath) {
// Changing to use inputPath and contents, using only file contents (`str`) caused issues when two
// different files had identical content (2.0.0-canary.16)
// Caches are now segmented based on inputPath so using inputPath here is superfluous (2.0.0-canary.19)
// But we do want a non-falsy value here even if `str` is an empty string.
return {
useCache: true,
key: inputPath + str,
};
}
get defaultTemplateFileExtension() {
return "html";
}
// Whether or not to wrap in Eleventy layouts
useLayouts() {
return true;
}
/** @returns {boolean|undefined} */
permalinkNeedsCompilation(str) {
return this.needsCompilation();
}
// whether or not compile is needed or can we return the plaintext?
needsCompilation(str) {
return true;
}
/**
* Make sure compile is implemented downstream.
* @abstract
* @return {Promise}
*/
async compile() {
throw new Error("compile() must be implemented by engine");
}
// See https://v3.11ty.dev/docs/watch-serve/#watch-javascript-dependencies
static shouldSpiderJavaScriptDependencies() {
return false;
}
hasDependencies(inputPath) {
if (this.config.uses.getDependencies(inputPath) === false) {
return false;
}
return true;
}
isFileRelevantTo(inputPath, comparisonFile) {
return this.config.uses.isFileRelevantTo(inputPath, comparisonFile);
}
}
export default TemplateEngine;

View File

@@ -0,0 +1,197 @@
import debugUtil from "debug";
import EleventyBaseError from "../Errors/EleventyBaseError.js";
import { EleventyImportFromEleventy } from "../Util/Require.js";
const debug = debugUtil("Eleventy:TemplateEngineManager");
class TemplateEngineManagerConfigError extends EleventyBaseError {}
class TemplateEngineManager {
constructor(eleventyConfig) {
if (!eleventyConfig || eleventyConfig.constructor.name !== "TemplateConfig") {
throw new TemplateEngineManagerConfigError("Missing or invalid `config` argument.");
}
this.eleventyConfig = eleventyConfig;
this.engineCache = {};
this.importCache = {};
}
get config() {
return this.eleventyConfig.getConfig();
}
static isAlias(entry) {
if (entry.aliasKey) {
return true;
}
return entry.key !== entry.extension;
}
static isSimpleAlias(entry) {
if (!this.isAlias(entry)) {
return false;
}
// has keys other than key, extension, and aliasKey
return (
Object.keys(entry).some((key) => {
return key !== "key" && key !== "extension" && key !== "aliasKey";
}) === false
);
}
get keyToClassNameMap() {
if (!this._keyToClassNameMap) {
this._keyToClassNameMap = {
md: "Markdown",
html: "Html",
njk: "Nunjucks",
liquid: "Liquid",
"11ty.js": "JavaScript",
};
// Custom entries *can* overwrite default entries above
if ("extensionMap" in this.config) {
for (let entry of this.config.extensionMap) {
// either the key does not already exist or it is not a simple alias and is an override: https://v3.11ty.dev/docs/languages/custom/#overriding-an-existing-template-language
let existingTarget = this._keyToClassNameMap[entry.key];
let isAlias = TemplateEngineManager.isAlias(entry);
if (!existingTarget && isAlias) {
throw new Error(
`An attempt to alias ${entry.aliasKey} to ${entry.key} was made, but ${entry.key} is not a recognized template syntax.`,
);
}
if (isAlias) {
// only `key` and `extension`, not `compile` or other options
if (!TemplateEngineManager.isSimpleAlias(entry)) {
this._keyToClassNameMap[entry.aliasKey] = "Custom";
} else {
this._keyToClassNameMap[entry.aliasKey] = this._keyToClassNameMap[entry.key];
}
} else {
// not an alias, so `key` and `extension` are the same here.
// *can* override a built-in extension!
this._keyToClassNameMap[entry.key] = "Custom";
}
}
}
}
return this._keyToClassNameMap;
}
reset() {
this.engineCache = {};
}
getClassNameFromTemplateKey(key) {
return this.keyToClassNameMap[key];
}
hasEngine(name) {
return !!this.getClassNameFromTemplateKey(name);
}
isEngineRemovedFromCore(name) {
return ["ejs", "hbs", "mustache", "haml", "pug"].includes(name) && !this.hasEngine(name);
}
async getEngineClassByExtension(extension) {
if (this.importCache[extension]) {
return this.importCache[extension];
}
let promise;
// We include these as raw strings (and not more readable variables) so theyre parsed by a bundler.
if (extension === "md") {
promise = EleventyImportFromEleventy("./src/Engines/Markdown.js");
} else if (extension === "html") {
promise = EleventyImportFromEleventy("./src/Engines/Html.js");
} else if (extension === "njk") {
promise = EleventyImportFromEleventy("./src/Engines/Nunjucks.js");
} else if (extension === "liquid") {
promise = EleventyImportFromEleventy("./src/Engines/Liquid.js");
} else if (extension === "11ty.js") {
promise = EleventyImportFromEleventy("./src/Engines/JavaScript.js");
} else {
promise = this.getCustomEngineClass();
}
this.importCache[extension] = promise;
return promise;
}
async getCustomEngineClass() {
if (!this._CustomEngine) {
this._CustomEngine = EleventyImportFromEleventy("./src/Engines/Custom.js");
}
return this._CustomEngine;
}
async #getEngine(name, extensionMap) {
let cls = await this.getEngineClassByExtension(name);
let instance = new cls(name, this.eleventyConfig);
instance.extensionMap = extensionMap;
instance.engineManager = this;
let extensionEntry = extensionMap.getExtensionEntry(name);
// Override a built-in extension (md => md)
// If provided a "Custom" engine using addExtension, but that engine's instance is *not* custom,
// The user must be overriding a built-in engine i.e. addExtension('md', { ...overrideBehavior })
let className = this.getClassNameFromTemplateKey(name);
if (className === "Custom" && instance.constructor.name !== "CustomEngine") {
let CustomEngine = await this.getCustomEngineClass();
let overrideCustomEngine = new CustomEngine(name, this.eleventyConfig);
// Keep track of the "default" engine 11ty would normally use
// This allows the user to access the default engine in their override
overrideCustomEngine.setDefaultEngine(instance);
instance = overrideCustomEngine;
// Alias to a built-in extension (11ty.tsx => 11ty.js)
} else if (
instance.constructor.name === "CustomEngine" &&
TemplateEngineManager.isAlias(extensionEntry)
) {
// add defaultRenderer for complex aliases with their own compile functions.
let originalEngineInstance = await this.getEngine(extensionEntry.key, extensionMap);
instance.setDefaultEngine(originalEngineInstance);
}
return instance;
}
async getEngine(name, extensionMap) {
// Warning about engine deprecation
if (this.isEngineRemovedFromCore(name)) {
throw new Error(
`Per the 11ty Community Survey (2023), the "${name}" template language was moved from core to an officially supported plugin in v3.0. These plugins live here: https://github.com/11ty/eleventy-plugin-template-languages and are documented on their respective template language docs at https://v3.11ty.dev/docs/languages/ You are also empowered to implement *any* template language yourself using https://v3.11ty.dev/docs/languages/custom/`,
);
}
if (!this.hasEngine(name)) {
throw new Error(`Template Engine ${name} does not exist in getEngine()`);
}
// TODO these cached engines should be based on extensions not name, then we can remove the error in
// "Double override (not aliases) throws an error" test in TemplateRenderCustomTest.js
if (!this.engineCache[name]) {
debug("Engine cache miss %o (should only happen once per type)", name);
// Make sure cache key is based on name and not path
// Custom class is used for all plugins, cache once per plugin
this.engineCache[name] = this.#getEngine(name, extensionMap);
}
return this.engineCache[name];
}
}
export default TemplateEngineManager;

View File

@@ -0,0 +1,67 @@
const DATA_KEYS = ["page", "eleventy"];
function augmentFunction(fn, options = {}) {
let t = typeof fn;
if (t !== "function") {
throw new Error(
"Invalid type passed to `augmentFunction`. A function was expected and received: " + t,
);
}
/** @this {object} */
return function (...args) {
let context = augmentObject(this || {}, options);
return fn.call(context, ...args);
};
}
function augmentObject(targetObject, options = {}) {
options = Object.assign(
{
source: undefined, // where to copy from
overwrite: true,
lazy: false, // lazily fetch the property
// getter: function() {},
},
options,
);
for (let key of DATA_KEYS) {
// Skip if overwrite: false and prop already exists on target
if (!options.overwrite && targetObject[key]) {
continue;
}
if (options.lazy) {
let value;
if (typeof options.getter == "function") {
value = () => options.getter(key, options.source);
} else {
value = () => options.source?.[key];
}
// lazy getter important for Liquid strictVariables support
Object.defineProperty(targetObject, key, {
writable: true,
configurable: true,
enumerable: true,
value,
});
} else {
let value;
if (typeof options.getter == "function") {
value = options.getter(key, options.source);
} else {
value = options.source?.[key];
}
if (value) {
targetObject[key] = value;
}
}
}
return targetObject;
}
export { DATA_KEYS as augmentKeys, augmentFunction, augmentObject };

View File

@@ -0,0 +1,9 @@
import EleventyBaseError from "./EleventyBaseError.js";
class DuplicatePermalinkOutputError extends EleventyBaseError {
get removeDuplicateErrorStringFromOutput() {
return true;
}
}
export default DuplicatePermalinkOutputError;

View File

@@ -0,0 +1,24 @@
/**
* This class serves as basis for all Eleventy-specific errors.
* @ignore
*/
class EleventyBaseError extends Error {
/**
* @param {string} message - The error message to display.
* @param {unknown} [originalError] - The original error caught.
*/
constructor(message, originalError) {
super(message);
this.name = this.constructor.name;
if (Error.captureStackTrace) {
Error.captureStackTrace(this, this.constructor);
}
if (originalError) {
this.originalError = originalError;
}
}
}
export default EleventyBaseError;

View File

@@ -0,0 +1,151 @@
import util from "node:util";
import debugUtil from "debug";
import ConsoleLogger from "../Util/ConsoleLogger.js";
import EleventyErrorUtil from "./EleventyErrorUtil.js";
const debug = debugUtil("Eleventy:EleventyErrorHandler");
class EleventyErrorHandler {
constructor() {
this._isVerbose = true;
}
get isVerbose() {
return this._isVerbose;
}
set isVerbose(verbose) {
this._isVerbose = !!verbose;
this.logger.isVerbose = !!verbose;
}
get logger() {
if (!this._logger) {
this._logger = new ConsoleLogger();
this._logger.isVerbose = this.isVerbose;
}
return this._logger;
}
set logger(logger) {
this._logger = logger;
}
warn(e, msg) {
if (msg) {
this.initialMessage(msg, "warn", "yellow");
}
this.log(e, "warn");
}
fatal(e, msg) {
this.error(e, msg);
process.exitCode = 1;
}
once(type, e, msg) {
if (e.__errorAlreadyLogged) {
return;
}
this[type || "error"](e, msg);
Object.defineProperty(e, "__errorAlreadyLogged", {
value: true,
});
}
error(e, msg) {
if (msg) {
this.initialMessage(msg, "error", "red", true);
}
this.log(e, "error", undefined, true);
}
static getTotalErrorCount(e) {
let totalErrorCount = 0;
let errorCountRef = e;
while (errorCountRef) {
totalErrorCount++;
errorCountRef = errorCountRef.originalError;
}
return totalErrorCount;
}
//https://nodejs.org/api/process.html
log(e, type = "log", chalkColor = "", forceToConsole = false) {
if (process.env.DEBUG) {
debug("Full error object: %o", util.inspect(e, { showHidden: false, depth: null }));
}
let showStack = true;
if (e.skipOriginalStack) {
showStack = false;
}
let totalErrorCount = EleventyErrorHandler.getTotalErrorCount(e);
let ref = e;
let index = 1;
while (ref) {
let nextRef = ref.originalError;
// Nunjucks wraps errors and puts the original in error.cause
if (nextRef?.cause?.originalError) {
nextRef = nextRef.cause.originalError;
}
if (!nextRef && EleventyErrorUtil.hasEmbeddedError(ref.message)) {
nextRef = EleventyErrorUtil.deconvertErrorToObject(ref);
}
if (nextRef?.skipOriginalStack) {
showStack = false;
}
this.logger.message(
`${totalErrorCount > 1 ? `${index}. ` : ""}${(
EleventyErrorUtil.cleanMessage(ref.message) || "(No error message provided)"
).trim()}${ref.name !== "Error" ? ` (via ${ref.name})` : ""}`,
type,
chalkColor,
forceToConsole,
);
if (process.env.DEBUG) {
debug(`(${type} stack): ${ref.stack}`);
} else if (!nextRef) {
// last error in the loop
// remove duplicate error messages if the stack contains the original message output above
let stackStr = ref.stack || "";
if (e.removeDuplicateErrorStringFromOutput) {
stackStr = stackStr.replace(
`${ref.name}: ${ref.message}`,
"(Repeated output has been truncated…)",
);
}
if (showStack) {
this.logger.message(
"\nOriginal error stack trace: " + stackStr,
type,
chalkColor,
forceToConsole,
);
}
}
ref = nextRef;
index++;
}
}
initialMessage(message, type = "log", chalkColor = "blue", forceToConsole = false) {
if (message) {
this.logger.message(message + ":", type, chalkColor, forceToConsole);
}
}
}
export { EleventyErrorHandler };

View File

@@ -0,0 +1,72 @@
import TemplateContentPrematureUseError from "./TemplateContentPrematureUseError.js";
/* Hack to workaround the variety of error handling schemes in template languages */
class EleventyErrorUtil {
static get prefix() {
return ">>>>>11ty>>>>>";
}
static get suffix() {
return "<<<<<11ty<<<<<";
}
static hasEmbeddedError(msg) {
if (!msg) {
return false;
}
return msg.includes(EleventyErrorUtil.prefix) && msg.includes(EleventyErrorUtil.suffix);
}
static cleanMessage(msg) {
if (!msg) {
return "";
}
if (!EleventyErrorUtil.hasEmbeddedError(msg)) {
return "" + msg;
}
return msg.slice(0, Math.max(0, msg.indexOf(EleventyErrorUtil.prefix)));
}
static deconvertErrorToObject(error) {
if (!error || !error.message) {
throw new Error(`Could not convert error object from: ${error}`);
}
if (!EleventyErrorUtil.hasEmbeddedError(error.message)) {
return error;
}
let msg = error.message;
let objectString = msg.substring(
msg.indexOf(EleventyErrorUtil.prefix) + EleventyErrorUtil.prefix.length,
msg.lastIndexOf(EleventyErrorUtil.suffix),
);
let obj = JSON.parse(objectString);
obj.name = error.name;
return obj;
}
// pass an error through a random template engines error handling unscathed
static convertErrorToString(error) {
return (
EleventyErrorUtil.prefix +
JSON.stringify({ message: error.message, stack: error.stack }) +
EleventyErrorUtil.suffix
);
}
static isPrematureTemplateContentError(e) {
// TODO the rest of the template engines
return (
e instanceof TemplateContentPrematureUseError ||
(e.originalError &&
(e.originalError.name === "RenderError" ||
e.originalError.name === "UndefinedVariableError") &&
e.originalError.originalError instanceof TemplateContentPrematureUseError) || // Liquid
(e.message || "").indexOf("TemplateContentPrematureUseError") > -1
); // Nunjucks
}
}
export default EleventyErrorUtil;

View File

@@ -0,0 +1,5 @@
import EleventyBaseError from "./EleventyBaseError.js";
class TemplateContentPrematureUseError extends EleventyBaseError {}
export default TemplateContentPrematureUseError;

View File

@@ -0,0 +1,5 @@
import EleventyBaseError from "./EleventyBaseError.js";
class TemplateContentUnrenderedTemplateError extends EleventyBaseError {}
export default TemplateContentUnrenderedTemplateError;

View File

@@ -0,0 +1,5 @@
import EleventyBaseError from "./EleventyBaseError.js";
class UsingCircularTemplateContentReferenceError extends EleventyBaseError {}
export default UsingCircularTemplateContentReferenceError;

23
node_modules/@11ty/eleventy/src/EventBus.js generated vendored Normal file
View File

@@ -0,0 +1,23 @@
import debugUtil from "debug";
import EventEmitter from "./Util/AsyncEventEmitter.js";
const debug = debugUtil("Eleventy:EventBus");
/**
* @module 11ty/eleventy/EventBus
* @ignore
*/
debug("Setting up global EventBus.");
/**
* Provides a global event bus that modules deep down in the stack can
* subscribe to from a global singleton for decoupled pub/sub.
* @type {module:11ty/eleventy/Util/AsyncEventEmitter~AsyncEventEmitter}
*/
let bus = new EventEmitter();
bus.setMaxListeners(100);
debug("EventBus max listener count: %o", bus.getMaxListeners());
export default bus;

102
node_modules/@11ty/eleventy/src/FileSystemSearch.js generated vendored Normal file
View File

@@ -0,0 +1,102 @@
import fastglob from "fast-glob";
import { TemplatePath } from "@11ty/eleventy-utils";
import debugUtil from "debug";
import { isGlobMatch } from "./Util/GlobMatcher.js";
const debug = debugUtil("Eleventy:FastGlobManager");
class FileSystemSearch {
constructor() {
this.inputs = {};
this.outputs = {};
this.promises = {};
this.count = 0;
}
getCacheKey(key, globs, options) {
if (Array.isArray(globs)) {
globs = globs.sort();
}
return key + JSON.stringify(globs) + JSON.stringify(options);
}
// returns a promise
search(key, globs, options = {}) {
debug("Glob search (%o) searching for: %o", key, globs);
if (!Array.isArray(globs)) {
globs = [globs];
}
// Strip leading slashes from everything!
globs = globs.map((entry) => TemplatePath.stripLeadingDotSlash(entry));
if (options.ignore && Array.isArray(options.ignore)) {
options.ignore = options.ignore.map((entry) => TemplatePath.stripLeadingDotSlash(entry));
debug("Glob search (%o) ignoring: %o", key, options.ignore);
}
let cacheKey = this.getCacheKey(key, globs, options);
// Only after the promise has resolved
if (this.outputs[cacheKey]) {
return Array.from(this.outputs[cacheKey]);
}
if (!this.promises[cacheKey]) {
this.inputs[cacheKey] = {
input: globs,
options,
};
this.count++;
this.promises[cacheKey] = fastglob(
globs,
Object.assign(
{
caseSensitiveMatch: false, // insensitive
dot: true,
},
options,
),
).then((results) => {
this.outputs[cacheKey] = new Set(
results.map((entry) => TemplatePath.addLeadingDotSlash(entry)),
);
return Array.from(this.outputs[cacheKey]);
});
}
// may be an unresolved promise
return this.promises[cacheKey];
}
_modify(path, setOperation) {
path = TemplatePath.stripLeadingDotSlash(path);
let normalized = TemplatePath.addLeadingDotSlash(path);
for (let key in this.inputs) {
let { input, options } = this.inputs[key];
if (
isGlobMatch(path, input, {
ignore: options.ignore,
})
) {
this.outputs[key][setOperation](normalized);
}
}
}
add(path) {
this._modify(path, "add");
}
delete(path) {
this._modify(path, "delete");
}
}
export default FileSystemSearch;

View File

@@ -0,0 +1,20 @@
export default function getCollectionItem(collection, page, modifier = 0) {
let j = 0;
let index;
for (let item of collection) {
if (
item.inputPath === page.inputPath &&
(item.outputPath === page.outputPath || item.url === page.url)
) {
index = j;
break;
}
j++;
}
if (index !== undefined && collection?.length) {
if (index + modifier >= 0 && index + modifier < collection.length) {
return collection[index + modifier];
}
}
}

View File

@@ -0,0 +1,17 @@
// TODO locale-friendly, see GetLocaleCollectionItem.js)
export default function getCollectionItemIndex(collection, page) {
if (!page) {
page = this.page;
}
let j = 0;
for (let item of collection) {
if (
item.inputPath === page.inputPath &&
(item.outputPath === page.outputPath || item.url === page.url)
) {
return j;
}
j++;
}
}

View File

@@ -0,0 +1,47 @@
import getCollectionItem from "./GetCollectionItem.js";
// Work with I18n Plugin src/Plugins/I18nPlugin.js to retrieve root pages (not i18n pages)
function resolveRootPage(config, pageOverride, languageCode) {
let localeFilter = config.getFilter("locale_page");
if (!localeFilter || typeof localeFilter !== "function") {
return pageOverride;
}
// returns root/default-language `page` object
return localeFilter.call(this, pageOverride, languageCode);
}
function getLocaleCollectionItem(config, collection, pageOverride, langCode, indexModifier = 0) {
if (!langCode) {
// if page.lang exists (2.0.0-canary.14 and i18n plugin added, use page language)
if (this.page.lang) {
langCode = this.page.lang;
} else {
return getCollectionItem(collection, pageOverride || this.page, indexModifier);
}
}
let rootPage = resolveRootPage.call(this, config, pageOverride); // implied current page, default language
let modifiedRootItem = getCollectionItem(collection, rootPage, indexModifier);
if (!modifiedRootItem) {
return; // no root item exists for the previous/next page
}
// Resolve modified root `page` back to locale `page`
// This will return a non localized version of the page as a fallback
let modifiedLocalePage = resolveRootPage.call(this, config, modifiedRootItem.data.page, langCode);
// already localized (or default language)
if (!("__locale_page_resolved" in modifiedLocalePage)) {
return modifiedRootItem;
}
// find the modified locale `page` again in `collections.all`
let all =
this.collections?.all ||
this.ctx?.collections?.all ||
this.context?.environments?.collections?.all ||
[];
return getCollectionItem(all, modifiedLocalePage, 0);
}
export default getLocaleCollectionItem;

14
node_modules/@11ty/eleventy/src/Filters/Slug.js generated vendored Normal file
View File

@@ -0,0 +1,14 @@
import slugify from "slugify";
export default function (str, options = {}) {
return slugify(
"" + str,
Object.assign(
{
replacement: "-",
lower: true,
},
options,
),
);
}

14
node_modules/@11ty/eleventy/src/Filters/Slugify.js generated vendored Normal file
View File

@@ -0,0 +1,14 @@
import slugify from "@sindresorhus/slugify";
export default function (str, options = {}) {
return slugify(
"" + str,
Object.assign(
{
// lowercase: true, // default
decamelize: false,
},
options,
),
);
}

35
node_modules/@11ty/eleventy/src/Filters/Url.js generated vendored Normal file
View File

@@ -0,0 +1,35 @@
import { TemplatePath } from "@11ty/eleventy-utils";
import isValidUrl from "../Util/ValidUrl.js";
// Note: This filter is used in the Eleventy Navigation plugin in versions prior to 0.3.4
export default function (url, pathPrefix) {
// work with undefined
url = url || "";
if (isValidUrl(url) || (url.startsWith("//") && url !== "//")) {
return url;
}
if (pathPrefix === undefined || typeof pathPrefix !== "string") {
// When you retrieve this with config.getFilter("url") it
// grabs the pathPrefix argument from your config for you (see defaultConfig.js)
throw new Error("pathPrefix (String) is required in the `url` filter.");
}
let normUrl = TemplatePath.normalizeUrlPath(url);
let normRootDir = TemplatePath.normalizeUrlPath("/", pathPrefix);
let normFull = TemplatePath.normalizeUrlPath("/", pathPrefix, url);
let isRootDirTrailingSlash =
normRootDir.length && normRootDir.charAt(normRootDir.length - 1) === "/";
// minor difference with straight `normalize`, "" resolves to root dir and not "."
// minor difference with straight `normalize`, "/" resolves to root dir
if (normUrl === "/" || normUrl === normRootDir) {
return normRootDir + (!isRootDirTrailingSlash ? "/" : "");
} else if (normUrl.indexOf("/") === 0) {
return normFull;
}
return normUrl;
}

424
node_modules/@11ty/eleventy/src/GlobalDependencyMap.js generated vendored Normal file
View File

@@ -0,0 +1,424 @@
import { DepGraph } from "dependency-graph";
import debugUtil from "debug";
import { TemplatePath } from "@11ty/eleventy-utils";
import JavaScriptDependencies from "./Util/JavaScriptDependencies.js";
import PathNormalizer from "./Util/PathNormalizer.js";
const debug = debugUtil("Eleventy:Dependencies");
class GlobalDependencyMap {
// dependency-graph requires these keys to be alphabetic strings
static LAYOUT_KEY = "layout";
static COLLECTION_PREFIX = "__collection:";
#templateConfig;
reset() {
this._map = undefined;
}
setIsEsm(isEsm) {
this.isEsm = isEsm;
}
setTemplateConfig(templateConfig) {
this.#templateConfig = templateConfig;
}
setConfig(config) {
if (this.config) {
return;
}
this.config = config;
// These have leading dot slashes, but so do the paths from Eleventy
this.config.events.once("eleventy.layouts", async (layouts) => {
await this.addLayoutsToMap(layouts);
});
}
filterOutLayouts(nodes = []) {
return nodes.filter((node) => {
let data = this.map.getNodeData(node);
if (data?.type === GlobalDependencyMap.LAYOUT_KEY) {
return false;
}
return true;
});
}
filterOutCollections(nodes = []) {
return nodes.filter((node) => !node.startsWith(GlobalDependencyMap.COLLECTION_PREFIX));
}
removeLayoutNodes(normalizedLayouts) {
let nodes = this.map.overallOrder();
for (let node of nodes) {
let data = this.map.getNodeData(node);
if (!data || !data.type || data.type !== GlobalDependencyMap.LAYOUT_KEY) {
continue;
}
// previous layout is not in the new layout map (no templates are using it)
if (!normalizedLayouts[node]) {
this.map.removeNode(node);
}
// important: if the layout map changed to have different templates (but was not removed)
// this is already handled by `resetNode` called via TemplateMap
}
}
// Eleventy Layouts dont show up in the dependency graph, so we handle those separately
async addLayoutsToMap(layouts) {
let normalizedLayouts = this.normalizeLayoutsObject(layouts);
// Clear out any previous layout relationships to make way for the new ones
this.removeLayoutNodes(normalizedLayouts);
for (let layout in normalizedLayouts) {
// We add this pre-emptively to add the `layout` data
if (!this.map.hasNode(layout)) {
this.map.addNode(layout, {
type: GlobalDependencyMap.LAYOUT_KEY,
});
} else {
this.map.setNodeData(layout, {
type: GlobalDependencyMap.LAYOUT_KEY,
});
}
// Potential improvement: only add the first template in the chain for a template and manage any upstream layouts by their own relationships
for (let pageTemplate of normalizedLayouts[layout]) {
this.addDependency(pageTemplate, [layout]);
}
if (this.#templateConfig?.shouldSpiderJavaScriptDependencies()) {
let deps = await JavaScriptDependencies.getDependencies([layout], this.isEsm);
this.addDependency(layout, deps);
}
}
}
get map() {
if (!this._map) {
this._map = new DepGraph({ circular: true });
}
return this._map;
}
set map(graph) {
this._map = graph;
}
normalizeNode(node) {
if (!node) {
return;
}
// TODO tests for this
// Fix URL objects passed in (sass does this)
if (typeof node !== "string" && "toString" in node) {
node = node.toString();
}
if (typeof node !== "string") {
throw new Error("`addDependencies` files must be strings. Received:" + node);
}
return PathNormalizer.fullNormalization(node);
}
normalizeLayoutsObject(layouts) {
let o = {};
for (let rawLayout in layouts) {
let layout = this.normalizeNode(rawLayout);
o[layout] = layouts[rawLayout].map((entry) => this.normalizeNode(entry));
}
return o;
}
getDependantsFor(node) {
if (!node) {
return new Set();
}
node = this.normalizeNode(node);
if (!this.map.hasNode(node)) {
return new Set();
}
// Direct dependants and dependencies, both publish and consume from collections
return this.map.directDependantsOf(node);
}
hasNode(node) {
return this.map.hasNode(this.normalizeNode(node));
}
findCollectionsRemovedFrom(node, collectionNames) {
if (!this.hasNode(node)) {
return new Set();
}
let prevDeps = this.getDependantsFor(node)
.filter((entry) => {
return entry.startsWith(GlobalDependencyMap.COLLECTION_PREFIX);
})
.map((entry) => {
return GlobalDependencyMap.getEntryFromCollectionKey(entry);
});
let prevDepsSet = new Set(prevDeps);
let deleted = new Set();
for (let dep of prevDepsSet) {
if (!collectionNames.has(dep)) {
deleted.add(dep);
}
}
return deleted;
}
resetNode(node) {
node = this.normalizeNode(node);
if (!this.map.hasNode(node)) {
return;
}
// We dont want to remove relationships that consume this, controlled by the upstream content
// for (let dep of this.map.directDependantsOf(node)) {
// this.map.removeDependency(dep, node);
// }
for (let dep of this.map.directDependenciesOf(node)) {
this.map.removeDependency(node, dep);
}
}
getTemplatesThatConsumeCollections(collectionNames) {
let templates = new Set();
for (let name of collectionNames) {
let collectionName = GlobalDependencyMap.getCollectionKeyForEntry(name);
if (!this.map.hasNode(collectionName)) {
continue;
}
for (let node of this.map.dependantsOf(collectionName)) {
if (!node.startsWith(GlobalDependencyMap.COLLECTION_PREFIX)) {
let data = this.map.getNodeData(node);
if (!data || !data.type || data.type != GlobalDependencyMap.LAYOUT_KEY) {
templates.add(node);
}
}
}
}
return templates;
}
getLayoutsUsedBy(node) {
node = this.normalizeNode(node);
if (!this.map.hasNode(node)) {
return [];
}
let layouts = [];
// include self, if layout
if (this.map.getNodeData(node)?.type === GlobalDependencyMap.LAYOUT_KEY) {
layouts.push(node);
}
this.map.dependantsOf(node).forEach((node) => {
let data = this.map.getNodeData(node);
// we only want layouts
if (data?.type === GlobalDependencyMap.LAYOUT_KEY) {
return layouts.push(node);
}
});
return layouts;
}
// In order
// Does not include original templatePaths (unless *they* are second-order relevant)
getTemplatesRelevantToTemplateList(templatePaths) {
let overallOrder = this.map.overallOrder();
overallOrder = this.filterOutLayouts(overallOrder);
overallOrder = this.filterOutCollections(overallOrder);
let relevantLookup = {};
for (let inputPath of templatePaths) {
inputPath = TemplatePath.stripLeadingDotSlash(inputPath);
let deps = this.getDependencies(inputPath, false);
if (Array.isArray(deps)) {
let paths = this.filterOutCollections(deps);
for (let node of paths) {
relevantLookup[node] = true;
}
}
}
return overallOrder.filter((node) => {
if (relevantLookup[node]) {
return true;
}
return false;
});
}
// Layouts are not relevant to compile cache and can be ignored
getDependencies(node, includeLayouts = true) {
node = this.normalizeNode(node);
// `false` means the Node was unknown
if (!this.map.hasNode(node)) {
return false;
}
return this.map.dependenciesOf(node).filter((node) => {
if (includeLayouts) {
return true;
}
// When includeLayouts is `false` we want to filter out layouts
let data = this.map.getNodeData(node);
if (data?.type === GlobalDependencyMap.LAYOUT_KEY) {
return false;
}
return true;
});
}
// node arguments are already normalized
_addDependency(from, toArray = []) {
this.map.addNode(from);
if (!Array.isArray(toArray)) {
throw new Error("Second argument to `addDependency` must be an Array.");
}
// debug("%o depends on %o", from, toArray);
for (let to of toArray) {
if (!this.map.hasNode(to)) {
this.map.addNode(to);
}
if (from !== to) {
this.map.addDependency(from, to);
}
}
}
addDependency(from, toArray = []) {
this._addDependency(
this.normalizeNode(from),
toArray.map((to) => this.normalizeNode(to)),
);
}
static getEntryFromCollectionKey(entry) {
return entry.slice(GlobalDependencyMap.COLLECTION_PREFIX.length);
}
static getCollectionKeyForEntry(entry) {
return `${GlobalDependencyMap.COLLECTION_PREFIX}${entry}`;
}
addDependencyConsumesCollection(from, collectionName) {
let nodeName = this.normalizeNode(from);
debug("%o depends on collection: %o", nodeName, collectionName);
this._addDependency(nodeName, [GlobalDependencyMap.getCollectionKeyForEntry(collectionName)]);
}
addDependencyPublishesToCollection(from, collectionName) {
let normalizedFrom = this.normalizeNode(from);
this._addDependency(GlobalDependencyMap.getCollectionKeyForEntry(collectionName), [
normalizedFrom,
]);
}
// Layouts are not relevant to compile cache and can be ignored
hasDependency(from, to, includeLayouts) {
to = this.normalizeNode(to);
let deps = this.getDependencies(from, includeLayouts); // normalizes `from`
if (!deps) {
return false;
}
return deps.includes(to);
}
// Layouts are not relevant to compile cache and can be ignored
isFileRelevantTo(fullTemplateInputPath, comparisonFile, includeLayouts) {
fullTemplateInputPath = this.normalizeNode(fullTemplateInputPath);
comparisonFile = this.normalizeNode(comparisonFile);
// No watch/serve changed file
if (!comparisonFile) {
return false;
}
// The file that changed is the relevant file
if (fullTemplateInputPath === comparisonFile) {
return true;
}
// The file that changed is a dependency of the template
// comparisonFile is used by fullTemplateInputPath
if (this.hasDependency(fullTemplateInputPath, comparisonFile, includeLayouts)) {
return true;
}
return false;
}
isFileUsedBy(parent, child, includeLayouts) {
if (this.hasDependency(parent, child, includeLayouts)) {
// child is used by parent
return true;
}
return false;
}
stringify() {
return JSON.stringify(this.map, function replacer(key, value) {
// Serialize internal Map objects.
if (value instanceof Map) {
let obj = {};
for (let [k, v] of value) {
obj[k] = v;
}
return obj;
}
return value;
});
}
restore(persisted) {
let obj = JSON.parse(persisted);
let graph = new DepGraph({ circular: true });
// https://github.com/jriecken/dependency-graph/issues/44
// Restore top level serialized Map objects (in stringify above)
for (let key in obj) {
let map = graph[key];
for (let k in obj[key]) {
let v = obj[key][k];
map.set(k, v);
}
}
this.map = graph;
}
}
export default GlobalDependencyMap;

View File

@@ -0,0 +1,151 @@
import { DeepCopy } from "@11ty/eleventy-utils";
import urlFilter from "../Filters/Url.js";
import PathPrefixer from "../Util/PathPrefixer.js";
import { HtmlTransformer } from "../Util/HtmlTransformer.js";
import isValidUrl from "../Util/ValidUrl.js";
function addPathPrefixToUrl(url, pathPrefix, base) {
let u;
if (base) {
u = new URL(url, base);
} else {
u = new URL(url);
}
// Add pathPrefix **after** url is transformed using base
if (pathPrefix) {
u.pathname = PathPrefixer.joinUrlParts(pathPrefix, u.pathname);
}
return u.toString();
}
// pathprefix is only used when overrideBase is a full URL
function transformUrl(url, base, opts = {}) {
let { pathPrefix, pageUrl } = opts;
// full URL, return as-is
if (isValidUrl(url)) {
return url;
}
// Not a full URL, but with a full base URL
// e.g. relative urls like "subdir/", "../subdir", "./subdir"
if (isValidUrl(base)) {
// convert relative paths to absolute path first using pageUrl
if (pageUrl && !url.startsWith("/")) {
let urlObj = new URL(url, `http://example.com${pageUrl}`);
url = urlObj.pathname + (urlObj.hash || "");
}
return addPathPrefixToUrl(url, pathPrefix, base);
}
// Not a full URL, nor a full base URL (call the built-in `url` filter)
return urlFilter(url, base);
}
function eleventyHtmlBasePlugin(eleventyConfig, defaultOptions = {}) {
let opts = DeepCopy(
{
// eleventyConfig.pathPrefix is new in Eleventy 2.0.0-canary.15
// `base` can be a directory (for path prefix transformations)
// OR a full URL with origin and pathname
baseHref: eleventyConfig.pathPrefix,
extensions: "html",
},
defaultOptions,
);
// `filters` option to rename filters was removed in 3.0.0-alpha.13
// Renaming these would cause issues in other plugins (e.g. RSS)
if (opts.filters !== undefined) {
throw new Error(
"The `filters` option in the HTML Base plugin was removed to prevent future cross-plugin compatibility issues.",
);
}
if (opts.baseHref === undefined) {
throw new Error("The `base` option is required in the HTML Base plugin.");
}
eleventyConfig.addFilter("addPathPrefixToFullUrl", function (url) {
return addPathPrefixToUrl(url, eleventyConfig.pathPrefix);
});
// Apply to one URL
eleventyConfig.addFilter(
"htmlBaseUrl",
/** @this {object} */
function (url, baseOverride, pageUrlOverride) {
let base = baseOverride || opts.baseHref;
// Do nothing with a default base
if (base === "/") {
return url;
}
return transformUrl(url, base, {
pathPrefix: eleventyConfig.pathPrefix,
pageUrl: pageUrlOverride || this.page?.url,
});
},
);
// Apply to a block of HTML
eleventyConfig.addAsyncFilter(
"transformWithHtmlBase",
/** @this {object} */
function (content, baseOverride, pageUrlOverride) {
let base = baseOverride || opts.baseHref;
// Do nothing with a default base
if (base === "/") {
return content;
}
return HtmlTransformer.transformStandalone(content, (url) => {
return transformUrl(url.trim(), base, {
pathPrefix: eleventyConfig.pathPrefix,
pageUrl: pageUrlOverride || this.page?.url,
});
});
},
);
// Apply to all HTML output in your project
eleventyConfig.htmlTransformer.addUrlTransform(
opts.extensions,
/** @this {object} */
function (urlInMarkup) {
// baseHref override is via renderTransforms filter for adding the absolute URL (e.g. https://example.com/pathPrefix/) for RSS/Atom/JSON feeds
return transformUrl(urlInMarkup.trim(), this.baseHref || opts.baseHref, {
pathPrefix: eleventyConfig.pathPrefix,
pageUrl: this.url,
});
},
{
priority: -1, // run last (especially after PathToUrl transform)
enabled: function (context) {
// Enabled when pathPrefix is non-default or via renderTransforms
return context.baseHref || opts.baseHref !== "/";
},
},
);
}
Object.defineProperty(eleventyHtmlBasePlugin, "eleventyPackage", {
value: "@11ty/eleventy/html-base-plugin",
});
Object.defineProperty(eleventyHtmlBasePlugin, "eleventyPluginOptions", {
value: {
unique: true,
},
});
export default eleventyHtmlBasePlugin;
export { transformUrl as applyBaseToUrl };

317
node_modules/@11ty/eleventy/src/Plugins/I18nPlugin.js generated vendored Normal file
View File

@@ -0,0 +1,317 @@
import { bcp47Normalize } from "bcp-47-normalize";
import iso639 from "iso-639-1";
import { DeepCopy } from "@11ty/eleventy-utils";
// pathPrefix note:
// When using `locale_url` filter with the `url` filter, `locale_url` must run first like
// `| locale_url | url`. If you run `| url | locale_url` it wont match correctly.
// TODO improvement would be to throw an error if `locale_url` finds a url with the
// path prefix at the beginning? Would need a better way to know `url` has transformed a string
// rather than just raw comparison.
// e.g. --pathprefix=/en/ should return `/en/en/` for `/en/index.liquid`
class LangUtils {
static getLanguageCodeFromInputPath(filepath) {
return (filepath || "").split("/").find((entry) => Comparator.isLangCode(entry));
}
static getLanguageCodeFromUrl(url) {
let s = (url || "").split("/");
return s.length > 0 && Comparator.isLangCode(s[1]) ? s[1] : "";
}
static swapLanguageCodeNoCheck(str, langCode) {
let found = false;
return str
.split("/")
.map((entry) => {
// only match the first one
if (!found && Comparator.isLangCode(entry)) {
found = true;
return langCode;
}
return entry;
})
.join("/");
}
static swapLanguageCode(str, langCode) {
if (!Comparator.isLangCode(langCode)) {
return str;
}
return LangUtils.swapLanguageCodeNoCheck(str, langCode);
}
}
class Comparator {
// https://en.wikipedia.org/wiki/IETF_language_tag#Relation_to_other_standards
// Requires a ISO-639-1 language code at the start (2 characters before the first -)
static isLangCode(code) {
let [s] = (code || "").split("-");
if (!iso639.validate(s)) {
return false;
}
if (!bcp47Normalize(code)) {
return false;
}
return true;
}
static urlHasLangCode(url, code) {
if (!Comparator.isLangCode(code)) {
return false;
}
return url.split("/").some((entry) => entry === code);
}
}
function normalizeInputPath(inputPath, extensionMap) {
if (extensionMap) {
return extensionMap.removeTemplateExtension(inputPath);
}
return inputPath;
}
/*
* Input: {
* '/en-us/test/': './test/stubs-i18n/en-us/test.11ty.js',
* '/en/test/': './test/stubs-i18n/en/test.liquid',
* '/es/test/': './test/stubs-i18n/es/test.njk',
* '/non-lang-file/': './test/stubs-i18n/non-lang-file.njk'
* }
*
* Output: {
* '/en-us/test/': [ { url: '/en/test/' }, { url: '/es/test/' } ],
* '/en/test/': [ { url: '/en-us/test/' }, { url: '/es/test/' } ],
* '/es/test/': [ { url: '/en-us/test/' }, { url: '/en/test/' } ]
* }
*/
function getLocaleUrlsMap(urlToInputPath, extensionMap, options = {}) {
let filemap = {};
for (let url in urlToInputPath) {
// Group number comes from Pagination.js
let { inputPath: originalFilepath, groupNumber } = urlToInputPath[url];
let filepath = normalizeInputPath(originalFilepath, extensionMap);
let replaced =
LangUtils.swapLanguageCodeNoCheck(filepath, "__11ty_i18n") + `_group:${groupNumber}`;
if (!filemap[replaced]) {
filemap[replaced] = [];
}
let langCode = LangUtils.getLanguageCodeFromInputPath(originalFilepath);
if (!langCode) {
langCode = LangUtils.getLanguageCodeFromUrl(url);
}
if (!langCode) {
langCode = options.defaultLanguage;
}
if (langCode) {
filemap[replaced].push({
url,
lang: langCode,
label: iso639.getNativeName(langCode.split("-")[0]),
});
} else {
filemap[replaced].push({ url });
}
}
// Default sorted by lang code
for (let key in filemap) {
filemap[key].sort(function (a, b) {
if (a.lang < b.lang) {
return -1;
}
if (a.lang > b.lang) {
return 1;
}
return 0;
});
}
// map of input paths => array of localized urls
let urlMap = {};
for (let filepath in filemap) {
for (let entry of filemap[filepath]) {
let url = entry.url;
if (!urlMap[url]) {
urlMap[url] = filemap[filepath].filter((entry) => {
if (entry.lang) {
return true;
}
return entry.url !== url;
});
}
}
}
return urlMap;
}
function eleventyI18nPlugin(eleventyConfig, opts = {}) {
let options = DeepCopy(
{
defaultLanguage: "",
filters: {
url: "locale_url",
links: "locale_links",
},
errorMode: "strict", // allow-fallback, never
},
opts,
);
if (!options.defaultLanguage) {
throw new Error(
"You must specify a `defaultLanguage` in Eleventys Internationalization (I18N) plugin.",
);
}
let extensionMap;
eleventyConfig.on("eleventy.extensionmap", (map) => {
extensionMap = map;
});
let bench = eleventyConfig.benchmarkManager.get("Aggregate");
let contentMaps = {};
eleventyConfig.on("eleventy.contentMap", function ({ urlToInputPath, inputPathToUrl }) {
let b = bench.get("(i18n Plugin) Setting up content map.");
b.before();
contentMaps.inputPathToUrl = inputPathToUrl;
contentMaps.urlToInputPath = urlToInputPath;
contentMaps.localeUrlsMap = getLocaleUrlsMap(urlToInputPath, extensionMap, options);
b.after();
});
eleventyConfig.addGlobalData("eleventyComputed.page.lang", () => {
// if addGlobalData receives a function it will execute it immediately,
// so we return a nested function for computed data
return (data) => {
return LangUtils.getLanguageCodeFromUrl(data.page.url) || options.defaultLanguage;
};
});
// Normalize a theoretical URL based on the current pages language
// If a non-localized file exists, returns the URL without a language assigned
// Fails if no file exists (localized and not localized)
eleventyConfig.addFilter(options.filters.url, function (url, langCodeOverride) {
let langCode =
langCodeOverride ||
LangUtils.getLanguageCodeFromUrl(this.page?.url) ||
options.defaultLanguage;
// Already has a language code on it and has a relevant url with the target language code
if (
contentMaps.localeUrlsMap[url] ||
(!url.endsWith("/") && contentMaps.localeUrlsMap[`${url}/`])
) {
for (let existingUrlObj of contentMaps.localeUrlsMap[url] ||
contentMaps.localeUrlsMap[`${url}/`]) {
if (Comparator.urlHasLangCode(existingUrlObj.url, langCode)) {
return existingUrlObj.url;
}
}
}
// Needs the language code prepended to the URL
let prependedLangCodeUrl = `/${langCode}${url}`;
if (
contentMaps.localeUrlsMap[prependedLangCodeUrl] ||
(!prependedLangCodeUrl.endsWith("/") && contentMaps.localeUrlsMap[`${prependedLangCodeUrl}/`])
) {
return prependedLangCodeUrl;
}
if (
contentMaps.urlToInputPath[url] ||
(!url.endsWith("/") && contentMaps.urlToInputPath[`${url}/`])
) {
// this is not a localized file (independent of a language code)
if (options.errorMode === "strict") {
throw new Error(
`Localized file for URL ${prependedLangCodeUrl} was not found in your project. A non-localized version does exist—are you sure you meant to use the \`${options.filters.url}\` filter for this? You can bypass this error using the \`errorMode\` option in the I18N plugin (current value: "${options.errorMode}").`,
);
}
} else if (options.errorMode === "allow-fallback") {
// Youre linking to a localized file that doesnt exist!
throw new Error(
`Localized file for URL ${prependedLangCodeUrl} was not found in your project! You will need to add it if you want to link to it using the \`${options.filters.url}\` filter. You can bypass this error using the \`errorMode\` option in the I18N plugin (current value: "${options.errorMode}").`,
);
}
return url;
});
// Refactor to use url
// Find the links that are localized alternates to the inputPath argument
eleventyConfig.addFilter(options.filters.links, function (urlOverride) {
let url = urlOverride || this.page?.url;
return (contentMaps.localeUrlsMap[url] || []).filter((entry) => {
return entry.url !== url;
});
});
// Returns a `page`-esque variable for the root default language page
// If paginated, returns first result only
eleventyConfig.addFilter(
"locale_page", // This is not exposed in `options` because it is an Eleventy internals filter (used in get*CollectionItem filters)
function (pageOverride, languageCode) {
// both args here are optional
if (!languageCode) {
languageCode = options.defaultLanguage;
}
let page = pageOverride || this.page;
let url; // new url
if (contentMaps.localeUrlsMap[page.url]) {
for (let entry of contentMaps.localeUrlsMap[page.url]) {
if (entry.lang === languageCode) {
url = entry.url;
}
}
}
let inputPath = LangUtils.swapLanguageCode(page.inputPath, languageCode);
if (
!url ||
!Array.isArray(contentMaps.inputPathToUrl[inputPath]) ||
contentMaps.inputPathToUrl[inputPath].length === 0
) {
// no internationalized pages found
return page;
}
let result = {
// // note that the permalink/slug may be different for the localized file!
url,
inputPath,
filePathStem: LangUtils.swapLanguageCode(page.filePathStem, languageCode),
// outputPath is omitted here, not necessary for GetCollectionItem.js if url is provided
__locale_page_resolved: true,
};
return result;
},
);
}
export { Comparator, LangUtils };
Object.defineProperty(eleventyI18nPlugin, "eleventyPackage", {
value: "@11ty/eleventy/i18n-plugin",
});
Object.defineProperty(eleventyI18nPlugin, "eleventyPluginOptions", {
value: {
unique: true,
},
});
export default eleventyI18nPlugin;

View File

@@ -0,0 +1,103 @@
import matchHelper from "posthtml-match-helper";
import { decodeHTML } from "entities";
import slugifyFilter from "../Filters/Slugify.js";
import MemoizeUtil from "../Util/MemoizeFunction.js";
function getTextNodeContent(node) {
if (node.attrs?.["eleventy:id-ignore"] === "") {
delete node.attrs["eleventy:id-ignore"];
return "";
}
if (!node.content) {
return "";
}
return node.content
.map((entry) => {
if (typeof entry === "string") {
return entry;
}
if (Array.isArray(entry.content)) {
return getTextNodeContent(entry);
}
return "";
})
.join("");
}
function IdAttributePlugin(eleventyConfig, options = {}) {
if (!options.slugify) {
options.slugify = MemoizeUtil(slugifyFilter);
}
if (!options.selector) {
options.selector = "[id],h1,h2,h3,h4,h5,h6";
}
options.decodeEntities = options.decodeEntities ?? true;
options.checkDuplicates = options.checkDuplicates ?? "error";
eleventyConfig.htmlTransformer.addPosthtmlPlugin(
"html",
function (pluginOptions = {}) {
if (typeof options.filter === "function") {
if (options.filter(pluginOptions) === false) {
return function () {};
}
}
return function (tree) {
// One per page
let conflictCheck = {};
// Cache heading nodes for conflict resolution
let headingNodes = {};
tree.match(matchHelper(options.selector), function (node) {
if (node.attrs?.id) {
let id = node.attrs?.id;
if (conflictCheck[id]) {
conflictCheck[id]++;
if (headingNodes[id]) {
// Rename conflicting assigned heading id
let newId = `${id}-${conflictCheck[id]}`;
headingNodes[newId] = headingNodes[id];
headingNodes[newId].attrs.id = newId;
delete headingNodes[id];
} else if (options.checkDuplicates === "error") {
// Existing `id` conflicts with assigned heading id, throw error
throw new Error(
"Duplicate `id` attribute (" +
id +
") in markup on " +
pluginOptions.page.inputPath,
);
}
} else {
conflictCheck[id] = 1;
}
} else if (!node.attrs?.id && node.content) {
node.attrs = node.attrs || {};
let textContent = getTextNodeContent(node);
if (options.decodeEntities) {
textContent = decodeHTML(textContent);
}
let id = options.slugify(textContent);
if (conflictCheck[id]) {
conflictCheck[id]++;
id = `${id}-${conflictCheck[id]}`;
} else {
conflictCheck[id] = 1;
}
headingNodes[id] = node;
node.attrs.id = id;
}
return node;
});
};
} /* , {} // pluginOptions */,
);
}
export { IdAttributePlugin };

View File

@@ -0,0 +1,177 @@
import path from "node:path";
import { TemplatePath } from "@11ty/eleventy-utils";
import isValidUrl from "../Util/ValidUrl.js";
function getValidPath(contentMap, testPath) {
let normalized = TemplatePath.addLeadingDotSlash(testPath);
// it must exist in the content map to be valid
if (contentMap[normalized]) {
return normalized;
}
}
function normalizeInputPath(targetInputPath, inputDir, sourceInputPath, contentMap) {
// inputDir is optional at the beginning of the developer supplied-path
// Input directory already on the input path
if (TemplatePath.join(targetInputPath).startsWith(TemplatePath.join(inputDir))) {
let absolutePath = getValidPath(contentMap, targetInputPath);
if (absolutePath) {
return absolutePath;
}
}
// Relative to project input directory
let relativeToInputDir = getValidPath(contentMap, TemplatePath.join(inputDir, targetInputPath));
if (relativeToInputDir) {
return relativeToInputDir;
}
if (targetInputPath && !path.isAbsolute(targetInputPath)) {
// Relative to source files input path
let sourceInputDir = TemplatePath.getDirFromFilePath(sourceInputPath);
let relativeToSourceFile = getValidPath(
contentMap,
TemplatePath.join(sourceInputDir, targetInputPath),
);
if (relativeToSourceFile) {
return relativeToSourceFile;
}
}
// the transform may have sent in a URL so we just return it as-is
return targetInputPath;
}
function parseFilePath(filepath) {
try {
/* u: URL {
href: 'file:///tmpl.njk#anchor',
origin: 'null',
protocol: 'file:',
username: '',
password: '',
host: '',
hostname: '',
port: '',
pathname: '/tmpl.njk',
search: '',
searchParams: URLSearchParams {},
hash: '#anchor'
} */
// Note that `node:url` -> pathToFileURL creates an absolute path, which we dont want
// URL(`file:#anchor`) gives back a pathname of `/`
let u = new URL(`file:${filepath}`);
filepath = filepath.replace(u.search, "");
filepath = filepath.replace(u.hash, "");
return [
// search includes ?, hash includes #
u.search + u.hash,
filepath,
];
} catch (e) {
return ["", filepath];
}
}
function FilterPlugin(eleventyConfig) {
let contentMap;
eleventyConfig.on("eleventy.contentMap", function ({ inputPathToUrl }) {
contentMap = inputPathToUrl;
});
eleventyConfig.addFilter("inputPathToUrl", function (targetFilePath) {
if (!contentMap) {
throw new Error("Internal error: contentMap not available for `inputPathToUrl` filter.");
}
if (isValidUrl(targetFilePath)) {
return targetFilePath;
}
let inputDir = eleventyConfig.directories.input;
let suffix = "";
[suffix, targetFilePath] = parseFilePath(targetFilePath);
// @ts-ignore
targetFilePath = normalizeInputPath(targetFilePath, inputDir, this.page.inputPath, contentMap);
let urls = contentMap[targetFilePath];
if (!urls || urls.length === 0) {
throw new Error(
"`inputPathToUrl` filter could not find a matching target for " + targetFilePath,
);
}
return `${urls[0]}${suffix}`;
});
}
function TransformPlugin(eleventyConfig, defaultOptions = {}) {
let opts = Object.assign(
{
extensions: "html",
},
defaultOptions,
);
let contentMap = null;
eleventyConfig.on("eleventy.contentMap", function ({ inputPathToUrl }) {
contentMap = inputPathToUrl;
});
eleventyConfig.htmlTransformer.addUrlTransform(opts.extensions, function (targetFilepathOrUrl) {
if (!contentMap) {
throw new Error("Internal error: contentMap not available for the `pathToUrl` Transform.");
}
if (isValidUrl(targetFilepathOrUrl)) {
return targetFilepathOrUrl;
}
let inputDir = eleventyConfig.directories.input;
let suffix = "";
[suffix, targetFilepathOrUrl] = parseFilePath(targetFilepathOrUrl);
targetFilepathOrUrl = normalizeInputPath(
targetFilepathOrUrl,
inputDir,
// @ts-ignore
this.page.inputPath,
contentMap,
);
let urls = contentMap[targetFilepathOrUrl];
if (!targetFilepathOrUrl || !urls || urls.length === 0) {
// fallback, transforms dont error on missing paths (though the pathToUrl filter does)
return `${targetFilepathOrUrl}${suffix}`;
}
return `${urls[0]}${suffix}`;
});
}
Object.defineProperty(FilterPlugin, "eleventyPackage", {
value: "@11ty/eleventy/inputpath-to-url-filter-plugin",
});
Object.defineProperty(FilterPlugin, "eleventyPluginOptions", {
value: {
unique: true,
},
});
Object.defineProperty(TransformPlugin, "eleventyPackage", {
value: "@11ty/eleventy/inputpath-to-url-transform-plugin",
});
Object.defineProperty(TransformPlugin, "eleventyPluginOptions", {
value: {
unique: true,
},
});
export default TransformPlugin;
export { FilterPlugin, TransformPlugin };

380
node_modules/@11ty/eleventy/src/Plugins/Pagination.js generated vendored Executable file
View File

@@ -0,0 +1,380 @@
import { isPlainObject } from "@11ty/eleventy-utils";
import lodash from "@11ty/lodash-custom";
import { DeepCopy } from "@11ty/eleventy-utils";
import EleventyBaseError from "../Errors/EleventyBaseError.js";
import { ProxyWrap } from "../Util/Objects/ProxyWrap.js";
// import { DeepFreeze } from "../Util/Objects/DeepFreeze.js";
import TemplateData from "../Data/TemplateData.js";
const { set: lodashSet, get: lodashGet, chunk: lodashChunk } = lodash;
class PaginationConfigError extends EleventyBaseError {}
class PaginationError extends EleventyBaseError {}
class Pagination {
constructor(tmpl, data, config) {
if (!config) {
throw new PaginationConfigError("Expected `config` argument to Pagination class.");
}
this.config = config;
this.setTemplate(tmpl);
this.setData(data);
}
get inputPathForErrorMessages() {
if (this.template) {
return ` (${this.template.inputPath})`;
}
return "";
}
static hasPagination(data) {
return "pagination" in data;
}
hasPagination() {
if (!this.data) {
throw new Error(
`Missing \`setData\` call for Pagination object${this.inputPathForErrorMessages}`,
);
}
return Pagination.hasPagination(this.data);
}
circularReferenceCheck(data) {
let key = data.pagination.data;
let includedTags = TemplateData.getIncludedTagNames(data);
for (let tag of includedTags) {
if (`collections.${tag}` === key) {
throw new PaginationError(
`Pagination circular reference${this.inputPathForErrorMessages}, data:\`${key}\` iterates over both the \`${tag}\` collection and also supplies pages to that collection.`,
);
}
}
}
setData(data) {
this.data = data || {};
this.target = [];
if (!this.hasPagination()) {
return;
}
if (!data.pagination) {
throw new Error(
`Misconfigured pagination data in template front matter${this.inputPathForErrorMessages} (YAML front matter precaution: did you use tabs and not spaces for indentation?).`,
);
} else if (!("size" in data.pagination)) {
throw new Error(
`Missing pagination size in front matter data${this.inputPathForErrorMessages}`,
);
}
this.circularReferenceCheck(data);
this.size = data.pagination.size;
this.alias = data.pagination.alias;
this.fullDataSet = this._get(this.data, this._getDataKey());
// this returns an array
this.target = this._resolveItems();
this.chunkedItems = this.pagedItems;
}
setTemplate(tmpl) {
this.template = tmpl;
}
_getDataKey() {
return this.data.pagination.data;
}
shouldResolveDataToObjectValues() {
if ("resolve" in this.data.pagination) {
return this.data.pagination.resolve === "values";
}
return false;
}
isFiltered(value) {
if ("filter" in this.data.pagination) {
let filtered = this.data.pagination.filter;
if (Array.isArray(filtered)) {
return filtered.indexOf(value) > -1;
}
return filtered === value;
}
return false;
}
_has(target, key) {
let notFoundValue = "__NOT_FOUND_ERROR__";
let data = lodashGet(target, key, notFoundValue);
return data !== notFoundValue;
}
_get(target, key) {
let notFoundValue = "__NOT_FOUND_ERROR__";
let data = lodashGet(target, key, notFoundValue);
if (data === notFoundValue) {
throw new Error(
`Could not find pagination data${this.inputPathForErrorMessages}, went looking for: ${key}`,
);
}
return data;
}
_resolveItems() {
let keys;
if (Array.isArray(this.fullDataSet)) {
keys = this.fullDataSet;
this.paginationTargetType = "array";
} else if (isPlainObject(this.fullDataSet)) {
this.paginationTargetType = "object";
if (this.shouldResolveDataToObjectValues()) {
keys = Object.values(this.fullDataSet);
} else {
keys = Object.keys(this.fullDataSet);
}
} else {
throw new Error(
`Unexpected data found in pagination target${this.inputPathForErrorMessages}: expected an Array or an Object.`,
);
}
// keys must be an array
let result = keys.slice();
if (this.data.pagination.before && typeof this.data.pagination.before === "function") {
// we dont need to make a copy of this because we .slice() above to create a new copy
let fns = {};
if (this.config) {
fns = this.config.javascriptFunctions;
}
result = this.data.pagination.before.call(fns, result, this.data);
}
if (this.data.pagination.reverse === true) {
result = result.reverse();
}
if (this.data.pagination.filter) {
result = result.filter((value) => !this.isFiltered(value));
}
return result;
}
get pagedItems() {
if (!this.data) {
throw new Error(
`Missing \`setData\` call for Pagination object${this.inputPathForErrorMessages}`,
);
}
const chunks = lodashChunk(this.target, this.size);
if (this.data.pagination?.generatePageOnEmptyData) {
return chunks.length ? chunks : [[]];
} else {
return chunks;
}
}
getPageCount() {
if (!this.hasPagination()) {
return 0;
}
return this.chunkedItems.length;
}
getNormalizedItems(pageItems) {
return this.size === 1 ? pageItems[0] : pageItems;
}
getOverrideDataPages(items, pageNumber) {
return {
// See Issue #345 for more examples
page: {
previous: pageNumber > 0 ? this.getNormalizedItems(items[pageNumber - 1]) : null,
next: pageNumber < items.length - 1 ? this.getNormalizedItems(items[pageNumber + 1]) : null,
first: items.length ? this.getNormalizedItems(items[0]) : null,
last: items.length ? this.getNormalizedItems(items[items.length - 1]) : null,
},
pageNumber,
};
}
getOverrideDataLinks(pageNumber, templateCount, links) {
let obj = {};
// links are okay but hrefs are better
obj.previousPageLink = pageNumber > 0 ? links[pageNumber - 1] : null;
obj.previous = obj.previousPageLink;
obj.nextPageLink = pageNumber < templateCount - 1 ? links[pageNumber + 1] : null;
obj.next = obj.nextPageLink;
obj.firstPageLink = links.length > 0 ? links[0] : null;
obj.lastPageLink = links.length > 0 ? links[links.length - 1] : null;
obj.links = links;
// todo deprecated, consistency with collections and use links instead
obj.pageLinks = links;
return obj;
}
getOverrideDataHrefs(pageNumber, templateCount, hrefs) {
let obj = {};
// hrefs are better than links
obj.previousPageHref = pageNumber > 0 ? hrefs[pageNumber - 1] : null;
obj.nextPageHref = pageNumber < templateCount - 1 ? hrefs[pageNumber + 1] : null;
obj.firstPageHref = hrefs.length > 0 ? hrefs[0] : null;
obj.lastPageHref = hrefs.length > 0 ? hrefs[hrefs.length - 1] : null;
obj.hrefs = hrefs;
// better names
obj.href = {
previous: obj.previousPageHref,
next: obj.nextPageHref,
first: obj.firstPageHref,
last: obj.lastPageHref,
};
return obj;
}
async getPageTemplates() {
if (!this.data) {
throw new Error(
`Missing \`setData\` call for Pagination object${this.inputPathForErrorMessages}`,
);
}
if (!this.hasPagination()) {
return [];
}
let entries = [];
let items = this.chunkedItems;
let pages = this.size === 1 ? items.map((entry) => entry[0]) : items;
let links = [];
let hrefs = [];
let hasPermalinkField = Boolean(this.data[this.config.keys.permalink]);
let hasComputedPermalinkField = Boolean(
this.data.eleventyComputed && this.data.eleventyComputed[this.config.keys.permalink],
);
// Do *not* pass collections through DeepCopy, well re-add them back in later.
let collections = this.data.collections;
if (collections) {
delete this.data.collections;
}
let parentData = DeepCopy(
{
pagination: {
data: this.data.pagination.data,
size: this.data.pagination.size,
alias: this.alias,
pages,
},
},
this.data,
);
// Restore skipped collections
if (collections) {
this.data.collections = collections;
// Keep the original reference to the collections, no deep copy!!
parentData.collections = collections;
}
// TODO this does work fine but lets wait on enabling it.
// DeepFreeze(parentData, ["collections"]);
// TODO future improvement dea: use a light Template wrapper for paged template clones (PagedTemplate?)
// so that we dont have the memory cost of the full template (and can reuse the parent
// template for some things)
let indices = new Set();
for (let j = 0; j <= items.length - 1; j++) {
indices.add(j);
}
for (let pageNumber of indices) {
let cloned = await this.template.clone();
if (pageNumber > 0 && !hasPermalinkField && !hasComputedPermalinkField) {
cloned.setExtraOutputSubdirectory(pageNumber);
}
let paginationData = {
pagination: {
items: items[pageNumber],
},
page: {},
};
Object.assign(paginationData.pagination, this.getOverrideDataPages(items, pageNumber));
if (this.alias) {
lodashSet(paginationData, this.alias, this.getNormalizedItems(items[pageNumber]));
}
// Do *not* deep merge pagination data! See https://github.com/11ty/eleventy/issues/147#issuecomment-440802454
let clonedData = ProxyWrap(paginationData, parentData);
// Previous method:
// let clonedData = DeepCopy(paginationData, parentData);
let { /*linkInstance,*/ rawPath, path, href } = await cloned.getOutputLocations(clonedData);
// TODO subdirectory to links if the site doesnt live at /
if (rawPath) {
links.push("/" + rawPath);
}
hrefs.push(href);
// page.url and page.outputPath are used to avoid another getOutputLocations call later, see Template->addComputedData
clonedData.page.url = href;
clonedData.page.outputPath = path;
entries.push({
pageNumber,
// This is used by i18n Plugin to allow subgroups of nested pagination to be separate
groupNumber: items[pageNumber]?.[0]?.eleventyPaginationGroupNumber,
template: cloned,
data: clonedData,
});
}
// we loop twice to pass in the appropriate prev/next links (already full generated now)
let index = 0;
for (let pageEntry of entries) {
let linksObj = this.getOverrideDataLinks(index, items.length, links);
Object.assign(pageEntry.data.pagination, linksObj);
let hrefsObj = this.getOverrideDataHrefs(index, items.length, hrefs);
Object.assign(pageEntry.data.pagination, hrefsObj);
index++;
}
return entries;
}
}
export default Pagination;

481
node_modules/@11ty/eleventy/src/Plugins/RenderPlugin.js generated vendored Normal file
View File

@@ -0,0 +1,481 @@
import fs from "graceful-fs";
import { Merge, TemplatePath, isPlainObject } from "@11ty/eleventy-utils";
import { evalToken } from "liquidjs";
// TODO add a first-class Markdown component to expose this using Markdown-only syntax (will need to be synchronous for markdown-it)
import { ProxyWrap } from "../Util/Objects/ProxyWrap.js";
import TemplateDataInitialGlobalData from "../Data/TemplateDataInitialGlobalData.js";
import EleventyBaseError from "../Errors/EleventyBaseError.js";
import TemplateRender from "../TemplateRender.js";
import ProjectDirectories from "../Util/ProjectDirectories.js";
import TemplateConfig from "../TemplateConfig.js";
import Liquid from "../Engines/Liquid.js";
class EleventyNunjucksError extends EleventyBaseError {}
/** @this {object} */
async function compile(content, templateLang, options = {}) {
let { templateConfig, extensionMap } = options;
if (!templateConfig) {
templateConfig = new TemplateConfig(null, false);
templateConfig.setDirectories(new ProjectDirectories());
await templateConfig.init();
}
// Breaking change in 2.0+, previous default was `html` and now we default to the page template syntax
if (!templateLang) {
templateLang = this.page.templateSyntax;
}
let tr = new TemplateRender(templateLang, templateConfig);
tr.extensionMap = extensionMap;
if (templateLang) {
await tr.setEngineOverride(templateLang);
} else {
await tr.init();
}
// TODO tie this to the class, not the extension
if (
tr.engine.name === "11ty.js" ||
tr.engine.name === "11ty.cjs" ||
tr.engine.name === "11ty.mjs"
) {
throw new Error(
"11ty.js is not yet supported as a template engine for `renderTemplate`. Use `renderFile` instead!",
);
}
return tr.getCompiledTemplate(content);
}
// No templateLang default, it should infer from the inputPath.
async function compileFile(inputPath, options = {}, templateLang) {
let { templateConfig, extensionMap, config } = options;
if (!inputPath) {
throw new Error("Missing file path argument passed to the `renderFile` shortcode.");
}
if (!fs.existsSync(TemplatePath.normalizeOperatingSystemFilePath(inputPath))) {
throw new Error(
"Could not find render plugin file for the `renderFile` shortcode, looking for: " + inputPath,
);
}
let wasTemplateConfigMissing = false;
if (!templateConfig) {
templateConfig = new TemplateConfig(null, false);
templateConfig.setDirectories(new ProjectDirectories());
wasTemplateConfigMissing = true;
}
if (config && typeof config === "function") {
await config(templateConfig.userConfig);
}
if (wasTemplateConfigMissing) {
await templateConfig.init();
}
let tr = new TemplateRender(inputPath, templateConfig);
tr.extensionMap = extensionMap;
if (templateLang) {
await tr.setEngineOverride(templateLang);
} else {
await tr.init();
}
if (!tr.engine.needsToReadFileContents()) {
return tr.getCompiledTemplate(null);
}
// TODO we could make this work with full templates (with front matter?)
let content = fs.readFileSync(inputPath, "utf8");
return tr.getCompiledTemplate(content);
}
/** @this {object} */
async function renderShortcodeFn(fn, data) {
if (fn === undefined) {
return;
} else if (typeof fn !== "function") {
throw new Error(`The \`compile\` function did not return a function. Received ${fn}`);
}
// if the user passes a string or other literal, remap to an object.
if (!isPlainObject(data)) {
data = {
_: data,
};
}
if ("data" in this && isPlainObject(this.data)) {
// when options.accessGlobalData is true, this allows the global data
// to be accessed inside of the shortcode as a fallback
data = ProxyWrap(data, this.data);
} else {
// save `page` and `eleventy` for reuse
data.page = this.page;
data.eleventy = this.eleventy;
}
return fn(data);
}
/**
* @module 11ty/eleventy/Plugins/RenderPlugin
*/
/**
* A plugin to add shortcodes to render an Eleventy template
* string (or file) inside of another template. {@link https://v3.11ty.dev/docs/plugins/render/}
*
* @since 1.0.0
* @param {module:11ty/eleventy/UserConfig} eleventyConfig - User-land configuration instance.
* @param {object} options - Plugin options
*/
function eleventyRenderPlugin(eleventyConfig, options = {}) {
/**
* @typedef {object} options
* @property {string} [tagName] - The shortcode name to render a template string.
* @property {string} [tagNameFile] - The shortcode name to render a template file.
* @property {module:11ty/eleventy/TemplateConfig} [templateConfig] - Configuration object
* @property {boolean} [accessGlobalData] - Whether or not the template has access to the pages data.
*/
let defaultOptions = {
tagName: "renderTemplate",
tagNameFile: "renderFile",
filterName: "renderContent",
templateConfig: null,
accessGlobalData: false,
};
let opts = Object.assign(defaultOptions, options);
function liquidTemplateTag(liquidEngine, tagName) {
// via https://github.com/harttle/liquidjs/blob/b5a22fa0910c708fe7881ef170ed44d3594e18f3/src/builtin/tags/raw.ts
return {
parse: function (tagToken, remainTokens) {
this.name = tagToken.name;
if (eleventyConfig.liquid.parameterParsing === "builtin") {
this.orderedArgs = Liquid.parseArgumentsBuiltin(tagToken.args);
// note that Liquid does have a Hash class for name-based argument parsing but offers no easy to support both modes in one class
} else {
this.legacyArgs = tagToken.args;
}
this.tokens = [];
var stream = liquidEngine.parser
.parseStream(remainTokens)
.on("token", (token) => {
if (token.name === "end" + tagName) stream.stop();
else this.tokens.push(token);
})
.on("end", () => {
throw new Error(`tag ${tagToken.getText()} not closed`);
});
stream.start();
},
render: function* (ctx) {
let normalizedContext = {};
if (ctx) {
if (opts.accessGlobalData) {
// parent template data cascade
normalizedContext.data = ctx.getAll();
}
normalizedContext.page = ctx.get(["page"]);
normalizedContext.eleventy = ctx.get(["eleventy"]);
}
let argArray = [];
if (this.legacyArgs) {
let rawArgs = Liquid.parseArguments(null, this.legacyArgs);
for (let arg of rawArgs) {
let b = yield liquidEngine.evalValue(arg, ctx);
argArray.push(b);
}
} else if (this.orderedArgs) {
for (let arg of this.orderedArgs) {
let b = yield evalToken(arg, ctx);
argArray.push(b);
}
}
// plaintext paired shortcode content
let body = this.tokens.map((token) => token.getText()).join("");
let ret = _renderStringShortcodeFn.call(
normalizedContext,
body,
// templateLang, data
...argArray,
);
yield ret;
return ret;
},
};
}
// TODO I dont think this works with whitespace control, e.g. {%- endrenderTemplate %}
function nunjucksTemplateTag(NunjucksLib, tagName) {
return new (function () {
this.tags = [tagName];
this.parse = function (parser, nodes) {
var tok = parser.nextToken();
var args = parser.parseSignature(true, true);
const begun = parser.advanceAfterBlockEnd(tok.value);
// This code was ripped from the Nunjucks parser for `raw`
// https://github.com/mozilla/nunjucks/blob/fd500902d7c88672470c87170796de52fc0f791a/nunjucks/src/parser.js#L655
const endTagName = "end" + tagName;
// Look for upcoming raw blocks (ignore all other kinds of blocks)
const rawBlockRegex = new RegExp(
"([\\s\\S]*?){%\\s*(" + tagName + "|" + endTagName + ")\\s*(?=%})%}",
);
let rawLevel = 1;
let str = "";
let matches = null;
// Exit when there's nothing to match
// or when we've found the matching "endraw" block
while ((matches = parser.tokens._extractRegex(rawBlockRegex)) && rawLevel > 0) {
const all = matches[0];
const pre = matches[1];
const blockName = matches[2];
// Adjust rawlevel
if (blockName === tagName) {
rawLevel += 1;
} else if (blockName === endTagName) {
rawLevel -= 1;
}
// Add to str
if (rawLevel === 0) {
// We want to exclude the last "endraw"
str += pre;
// Move tokenizer to beginning of endraw block
parser.tokens.backN(all.length - pre.length);
} else {
str += all;
}
}
let body = new nodes.Output(begun.lineno, begun.colno, [
new nodes.TemplateData(begun.lineno, begun.colno, str),
]);
return new nodes.CallExtensionAsync(this, "run", args, [body]);
};
this.run = function (...args) {
let resolve = args.pop();
let body = args.pop();
let [context, ...argArray] = args;
let normalizedContext = {};
if (context.ctx?.page) {
normalizedContext.ctx = context.ctx;
// TODO .data
// if(opts.accessGlobalData) {
// normalizedContext.data = context.ctx;
// }
normalizedContext.page = context.ctx.page;
normalizedContext.eleventy = context.ctx.eleventy;
}
body(function (e, bodyContent) {
if (e) {
resolve(
new EleventyNunjucksError(`Error with Nunjucks paired shortcode \`${tagName}\``, e),
);
}
Promise.resolve(
_renderStringShortcodeFn.call(
normalizedContext,
bodyContent,
// templateLang, data
...argArray,
),
).then(
function (returnValue) {
resolve(null, new NunjucksLib.runtime.SafeString(returnValue));
},
function (e) {
resolve(
new EleventyNunjucksError(`Error with Nunjucks paired shortcode \`${tagName}\``, e),
null,
);
},
);
});
};
})();
}
// This will only work on 1.0.0-beta.5+ but is only necessary if you want to reuse your config inside of template shortcodes.
// Just rendering raw templates (without filters, shortcodes, etc. from your config) will work fine on old versions.
let templateConfig;
eleventyConfig.on("eleventy.config", (cfg) => {
templateConfig = cfg;
});
let extensionMap;
eleventyConfig.on("eleventy.extensionmap", (map) => {
extensionMap = map;
});
/** @this {object} */
async function _renderStringShortcodeFn(content, templateLang, data = {}) {
// Default is fn(content, templateLang, data) but we want to support fn(content, data) too
if (typeof templateLang !== "string") {
data = templateLang;
templateLang = false;
}
let fn = await compile.call(this, content, templateLang, {
templateConfig: opts.templateConfig || templateConfig,
extensionMap,
});
return renderShortcodeFn.call(this, fn, data);
}
/** @this {object} */
async function _renderFileShortcodeFn(inputPath, data = {}, templateLang) {
let options = {
templateConfig: opts.templateConfig || templateConfig,
extensionMap,
};
let fn = await compileFile.call(this, inputPath, options, templateLang);
return renderShortcodeFn.call(this, fn, data);
}
// Render strings
if (opts.tagName) {
// use falsy to opt-out
eleventyConfig.addJavaScriptFunction(opts.tagName, _renderStringShortcodeFn);
eleventyConfig.addLiquidTag(opts.tagName, function (liquidEngine) {
return liquidTemplateTag(liquidEngine, opts.tagName);
});
eleventyConfig.addNunjucksTag(opts.tagName, function (nunjucksLib) {
return nunjucksTemplateTag(nunjucksLib, opts.tagName);
});
}
// Filter for rendering strings
if (opts.filterName) {
eleventyConfig.addAsyncFilter(opts.filterName, _renderStringShortcodeFn);
}
// Render File
// use `false` to opt-out
if (opts.tagNameFile) {
eleventyConfig.addAsyncShortcode(opts.tagNameFile, _renderFileShortcodeFn);
}
}
// Will re-use the same configuration instance both at a top level and across any nested renders
class RenderManager {
/** @type {Promise|undefined} */
#hasConfigInitialized;
constructor() {
this.templateConfig = new TemplateConfig(null, false);
this.templateConfig.setDirectories(new ProjectDirectories());
// This is the only plugin running on the Edge
this.templateConfig.userConfig.addPlugin(eleventyRenderPlugin, {
templateConfig: this.templateConfig,
accessGlobalData: true,
});
}
async init() {
if (this.#hasConfigInitialized) {
return this.#hasConfigInitialized;
}
if (this.templateConfig.hasInitialized()) {
return true;
}
this.#hasConfigInitialized = this.templateConfig.init();
await this.#hasConfigInitialized;
return true;
}
// `callback` is async-friendly but requires await upstream
config(callback) {
// run an extra `function(eleventyConfig)` configuration callbacks
if (callback && typeof callback === "function") {
return callback(this.templateConfig.userConfig);
}
}
get initialGlobalData() {
if (!this._data) {
this._data = new TemplateDataInitialGlobalData(this.templateConfig);
}
return this._data;
}
// because we dont have access to the full data cascade—but
// we still want configuration data added via `addGlobalData`
async getData(...data) {
await this.init();
let globalData = await this.initialGlobalData.getData();
let merged = Merge({}, globalData, ...data);
return merged;
}
async compile(content, templateLang, options = {}) {
await this.init();
// Missing here: extensionMap
options.templateConfig = this.templateConfig;
// We dont need `compile.call(this)` here because the Edge always uses "liquid" as the template lang (instead of relying on this.page.templateSyntax)
// returns promise
return compile(content, templateLang, options);
}
async render(fn, edgeData, buildTimeData) {
await this.init();
let mergedData = await this.getData(edgeData);
// Set .data for options.accessGlobalData feature
let context = {
data: mergedData,
};
return renderShortcodeFn.call(context, fn, buildTimeData);
}
}
Object.defineProperty(eleventyRenderPlugin, "eleventyPackage", {
value: "@11ty/eleventy/render-plugin",
});
Object.defineProperty(eleventyRenderPlugin, "eleventyPluginOptions", {
value: {
unique: true,
},
});
export default eleventyRenderPlugin;
export { compileFile as File, compile as String, RenderManager };

1124
node_modules/@11ty/eleventy/src/Template.js generated vendored Executable file

File diff suppressed because it is too large Load Diff

85
node_modules/@11ty/eleventy/src/TemplateBehavior.js generated vendored Normal file
View File

@@ -0,0 +1,85 @@
import { isPlainObject } from "@11ty/eleventy-utils";
class TemplateBehavior {
#isRenderOptional;
constructor(config) {
this.render = true;
this.write = true;
this.outputFormat = null;
if (!config) {
throw new Error("Missing config argument in TemplateBehavior");
}
this.config = config;
}
// Render override set to false
isRenderableDisabled() {
return this.renderableOverride === false;
}
isRenderableOptional() {
return this.#isRenderOptional;
}
// undefined (fallback), true, false
setRenderableOverride(renderableOverride) {
if (renderableOverride === "optional") {
this.#isRenderOptional = true;
this.renderableOverride = undefined;
} else {
this.#isRenderOptional = false;
this.renderableOverride = renderableOverride;
}
}
// permalink *has* a build key or output is json/ndjson
isRenderable() {
return this.renderableOverride ?? (this.render || this.isRenderForced());
}
setOutputFormat(format) {
this.outputFormat = format;
}
isRenderForced() {
return this.outputFormat === "json" || this.outputFormat === "ndjson";
}
isWriteable() {
return this.write;
}
// Duplicate logic with TemplatePermalink constructor
setRenderViaDataCascade(data) {
// render is false *only* if `build` key does not exist in permalink objects (both in data and eleventyComputed)
// (note that permalink: false means it wont write but will still render)
let keys = new Set();
if (isPlainObject(data.permalink)) {
for (let key of Object.keys(data.permalink)) {
keys.add(key);
}
}
let computedKey = this.config.keys.computed;
if (computedKey in data && isPlainObject(data[computedKey]?.permalink)) {
for (let key of Object.keys(data[computedKey].permalink)) {
keys.add(key);
}
}
if (keys.size) {
this.render = keys.has("build");
}
}
setFromPermalink(templatePermalink) {
// this.render is duplicated between TemplatePermalink and `setRenderViaDataCascade` above
this.render = templatePermalink._isRendered;
this.write = templatePermalink._writeToFileSystem;
}
}
export default TemplateBehavior;

104
node_modules/@11ty/eleventy/src/TemplateCache.js generated vendored Normal file
View File

@@ -0,0 +1,104 @@
import { TemplatePath } from "@11ty/eleventy-utils";
import eventBus from "./EventBus.js";
// Note: this is only used for TemplateLayout right now but could be used for more
// Just be careful because right now the TemplateLayout cache keys are not directly mapped to paths
// So you may get collisions if you use this for other things.
class TemplateCache {
constructor() {
this.cache = {};
this.cacheByInputPath = {};
}
clear() {
this.cache = {};
this.cacheByInputPath = {};
}
// alias
removeAll() {
this.clear();
}
size() {
return Object.keys(this.cacheByInputPath).length;
}
add(layoutTemplate) {
let keys = new Set();
if (typeof layoutTemplate === "string") {
throw new Error(
"Invalid argument type passed to TemplateCache->add(). Should be a TemplateLayout.",
);
}
if ("getFullKey" in layoutTemplate) {
keys.add(layoutTemplate.getFullKey());
}
if ("getKey" in layoutTemplate) {
// if `key` was an alias, also set to the pathed layout value too
// e.g. `layout: "default"` and `layout: "default.liquid"` will both map to the same template.
keys.add(layoutTemplate.getKey());
}
for (let key of keys) {
this.cache[key] = layoutTemplate;
}
// also the full template input path for use with eleventy --serve/--watch e.g. `_includes/default.liquid` (see `remove` below)
let fullPath = TemplatePath.stripLeadingDotSlash(layoutTemplate.inputPath);
this.cacheByInputPath[fullPath] = layoutTemplate;
}
has(key) {
return key in this.cache;
}
get(key) {
if (!this.has(key)) {
throw new Error(`Could not find ${key} in TemplateCache.`);
}
return this.cache[key];
}
remove(layoutFilePath) {
layoutFilePath = TemplatePath.stripLeadingDotSlash(layoutFilePath);
if (!this.cacheByInputPath[layoutFilePath]) {
// not a layout file
return;
}
let layoutTemplate = this.cacheByInputPath[layoutFilePath];
layoutTemplate.resetCaches();
let keys = layoutTemplate.getCacheKeys();
for (let key of keys) {
delete this.cache[key];
}
delete this.cacheByInputPath[layoutFilePath];
}
}
let layoutCache = new TemplateCache();
eventBus.on("eleventy.resourceModified", (path, usedBy, metadata = {}) => {
// https://github.com/11ty/eleventy-plugin-bundle/issues/10
if (metadata.viaConfigReset) {
layoutCache.removeAll();
} else if (metadata.relevantLayouts?.length) {
// reset the appropriate layouts relevant to the file.
for (let layoutPath of metadata.relevantLayouts || []) {
layoutCache.remove(layoutPath);
}
} else {
layoutCache.remove(path);
}
});
// singleton
export default layoutCache;

81
node_modules/@11ty/eleventy/src/TemplateCollection.js generated vendored Executable file
View File

@@ -0,0 +1,81 @@
import { TemplatePath } from "@11ty/eleventy-utils";
import TemplateData from "./Data/TemplateData.js";
import Sortable from "./Util/Objects/Sortable.js";
import { isGlobMatch } from "./Util/GlobMatcher.js";
class TemplateCollection extends Sortable {
constructor() {
super();
this._filteredByGlobsCache = new Map();
}
getAll() {
return this.items.slice();
}
getAllSorted() {
return this.sort(Sortable.sortFunctionDateInputPath);
}
getSortedByDate() {
return this.sort(Sortable.sortFunctionDate);
}
getGlobs(globs) {
if (typeof globs === "string") {
globs = [globs];
}
globs = globs.map((glob) => TemplatePath.addLeadingDotSlash(glob));
return globs;
}
getFilteredByGlob(globs) {
globs = this.getGlobs(globs);
let key = globs.join("::");
if (!this._dirty) {
// Try to find a pre-sorted list and clone it.
if (this._filteredByGlobsCache.has(key)) {
return [...this._filteredByGlobsCache.get(key)];
}
} else if (this._filteredByGlobsCache.size) {
// Blow away cache
this._filteredByGlobsCache = new Map();
}
let filtered = this.getAllSorted().filter((item) => {
return isGlobMatch(item.inputPath, globs);
});
this._dirty = false;
this._filteredByGlobsCache.set(key, [...filtered]);
return filtered;
}
getFilteredByTag(tagName) {
return this.getAllSorted().filter((item) => {
if (!tagName || TemplateData.getIncludedTagNames(item.data).includes(tagName)) {
return true;
}
return false;
});
}
getFilteredByTags(...tags) {
return this.getAllSorted().filter((item) => {
let itemTags = TemplateData.getIncludedTagNames(item.data);
return tags.every((requiredTag) => {
if (Array.isArray(itemTags)) {
return itemTags.includes(requiredTag);
} else {
return itemTags === requiredTag;
}
});
});
}
}
export default TemplateCollection;

551
node_modules/@11ty/eleventy/src/TemplateConfig.js generated vendored Normal file
View File

@@ -0,0 +1,551 @@
import fs from "node:fs";
import chalk from "kleur";
import { Merge, TemplatePath, isPlainObject } from "@11ty/eleventy-utils";
import debugUtil from "debug";
import { EleventyImportRaw, EleventyImportRawFromEleventy } from "./Util/Require.js";
import EleventyBaseError from "./Errors/EleventyBaseError.js";
import UserConfig from "./UserConfig.js";
import GlobalDependencyMap from "./GlobalDependencyMap.js";
import ExistsCache from "./Util/ExistsCache.js";
import eventBus from "./EventBus.js";
import ProjectTemplateFormats from "./Util/ProjectTemplateFormats.js";
const debug = debugUtil("Eleventy:TemplateConfig");
const debugDev = debugUtil("Dev:Eleventy:TemplateConfig");
/**
* @module 11ty/eleventy/TemplateConfig
*/
/**
* Config as used by the template.
* @typedef {object} module:11ty/eleventy/TemplateConfig~TemplateConfig~config
* @property {String} [pathPrefix] - The path prefix.
*/
/**
* Errors in eleventy config.
* @ignore
*/
class EleventyConfigError extends EleventyBaseError {}
/**
* Errors in eleventy plugins.
* @ignore
*/
class EleventyPluginError extends EleventyBaseError {}
/**
* Config for a template.
* @ignore
* @param {{}} customRootConfig - tbd.
* @param {String} projectConfigPath - Path to local project config.
*/
class TemplateConfig {
#templateFormats;
#runMode;
#configManuallyDefined = false;
/** @type {UserConfig} */
#userConfig = new UserConfig();
constructor(customRootConfig, projectConfigPath) {
/** @type {object} */
this.overrides = {};
/**
* @type {String}
* @description Path to local project config.
* @default .eleventy.js
*/
if (projectConfigPath !== undefined) {
this.#configManuallyDefined = true;
if (!projectConfigPath) {
// falsy skips config files
this.projectConfigPaths = [];
} else {
this.projectConfigPaths = [projectConfigPath];
}
} else {
this.projectConfigPaths = [
".eleventy.js",
"eleventy.config.js",
"eleventy.config.mjs",
"eleventy.config.cjs",
];
}
if (customRootConfig) {
/**
* @type {object}
* @description Custom root config.
*/
this.customRootConfig = customRootConfig;
debug("Warning: Using custom root config!");
} else {
this.customRootConfig = null;
}
this.hasConfigMerged = false;
this.isEsm = false;
}
get userConfig() {
return this.#userConfig;
}
get aggregateBenchmark() {
return this.userConfig.benchmarks.aggregate;
}
/* Setter for Logger */
setLogger(logger) {
this.logger = logger;
this.userConfig.logger = this.logger;
}
/* Setter for Directories instance */
setDirectories(directories) {
this.directories = directories;
this.userConfig.directories = directories.getUserspaceInstance();
}
/* Setter for TemplateFormats instance */
setTemplateFormats(templateFormats) {
this.#templateFormats = templateFormats;
}
get templateFormats() {
if (!this.#templateFormats) {
this.#templateFormats = new ProjectTemplateFormats();
}
return this.#templateFormats;
}
/* Backwards compat */
get inputDir() {
return this.directories.input;
}
setRunMode(runMode) {
this.#runMode = runMode;
}
shouldSpiderJavaScriptDependencies() {
// not for a standard build
return (
(this.#runMode === "watch" || this.#runMode === "serve") &&
this.userConfig.watchJavaScriptDependencies
);
}
/**
* Normalises local project config file path.
*
* @method
* @returns {String|undefined} - The normalised local project config file path.
*/
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];
if (configFile) {
return configFile;
}
}
getLocalProjectConfigFiles() {
if (this.projectConfigPaths?.length > 0) {
return TemplatePath.addLeadingDotSlashArray(this.projectConfigPaths.filter((path) => path));
}
return [];
}
setProjectUsingEsm(isEsmProject) {
this.isEsm = !!isEsmProject;
this.usesGraph.setIsEsm(isEsmProject);
}
getIsProjectUsingEsm() {
return this.isEsm;
}
/**
* Resets the configuration.
*/
async reset() {
debugDev("Resetting configuration: TemplateConfig and UserConfig.");
this.userConfig.reset();
// await this.initializeRootConfig();
await this.forceReloadConfig();
this.usesGraph.reset();
// Clear the compile cache
eventBus.emit("eleventy.compileCacheReset");
}
/**
* Resets the configuration while in watch mode.
*
* @todo Add implementation.
*/
resetOnWatch() {
// nothing yet
}
hasInitialized() {
return this.hasConfigMerged;
}
/**
* Async-friendly init method
*/
async init(overrides) {
await this.initializeRootConfig();
if (overrides) {
this.appendToRootConfig(overrides);
}
this.config = await this.mergeConfig();
this.hasConfigMerged = true;
}
/**
* Force a reload of the configuration object.
*/
async forceReloadConfig() {
this.hasConfigMerged = false;
await this.init();
}
/**
* Returns the config object.
*
* @returns {{}} - The config object.
*/
getConfig() {
if (!this.hasConfigMerged) {
throw new Error("Invalid call to .getConfig(). Needs an .init() first.");
}
return this.config;
}
/**
* Overwrites the config path.
*
* @param {String} path - The new config path.
*/
async setProjectConfigPath(path) {
this.#configManuallyDefined = true;
if (path !== undefined) {
this.projectConfigPaths = [path];
} else {
this.projectConfigPaths = [];
}
if (this.hasConfigMerged) {
// merge it again
debugDev("Merging in getConfig again after setting the local project config path.");
await this.forceReloadConfig();
}
}
/**
* Overwrites the path prefix.
*
* @param {String} pathPrefix - The new path prefix.
*/
setPathPrefix(pathPrefix) {
if (pathPrefix && pathPrefix !== "/") {
debug("Setting pathPrefix to %o", pathPrefix);
this.overrides.pathPrefix = pathPrefix;
}
}
/**
* Gets the current path prefix denoting the root folder the output will be deployed to
*
* @returns {String} - The path prefix string
*/
getPathPrefix() {
if (this.overrides.pathPrefix) {
return this.overrides.pathPrefix;
}
if (!this.hasConfigMerged) {
throw new Error("Config has not yet merged. Needs `init()`.");
}
return this.config?.pathPrefix;
}
/**
* Bootstraps the config object.
*/
async initializeRootConfig() {
this.rootConfig = this.customRootConfig;
if (!this.rootConfig) {
let { default: cfg } = await EleventyImportRawFromEleventy("./src/defaultConfig.js");
this.rootConfig = cfg;
}
if (typeof this.rootConfig === "function") {
// Not yet using async in defaultConfig.js
this.rootConfig = this.rootConfig.call(this, this.userConfig);
}
debug("Default Eleventy config %o", this.rootConfig);
}
/*
* Add additional overrides to the root config object, used for testing
*
* @param {object} - a subset of the return Object from the users config file.
*/
appendToRootConfig(obj) {
Object.assign(this.rootConfig, obj);
}
/*
* Process the userland plugins from the Config
*
* @param {object} - the return Object from the users config file.
*/
async processPlugins({ dir, pathPrefix }) {
this.userConfig.dir = dir;
this.userConfig.pathPrefix = pathPrefix;
// for Nested addPlugin calls, Issue #1925
this.userConfig._enablePluginExecution();
let storedActiveNamespace = this.userConfig.activeNamespace;
for (let { plugin, options, pluginNamespace } of this.userConfig.plugins) {
try {
this.userConfig.activeNamespace = pluginNamespace;
await this.userConfig._executePlugin(plugin, options);
} catch (e) {
let name = this.userConfig._getPluginName(plugin);
let namespaces = [storedActiveNamespace, pluginNamespace].filter((entry) => !!entry);
let namespaceStr = "";
if (namespaces.length) {
namespaceStr = ` (namespace: ${namespaces.join(".")})`;
}
throw new EleventyPluginError(
`Error processing ${name ? `the \`${name}\`` : "a"} plugin${namespaceStr}`,
e,
);
}
}
this.userConfig.activeNamespace = storedActiveNamespace;
this.userConfig._disablePluginExecution();
}
/**
* Fetches and executes the local configuration file
*
* @returns {Promise<object>} merged - The merged config file object.
*/
async requireLocalConfigFile() {
let localConfig = {};
let exportedConfig = {};
let path = this.projectConfigPaths.filter((path) => path).find((path) => fs.existsSync(path));
if (this.projectConfigPaths.length > 0 && this.#configManuallyDefined && !path) {
throw new EleventyConfigError(
"A configuration file was specified but not found: " + this.projectConfigPaths.join(", "),
);
}
debug(`Merging default config with ${path}`);
if (path) {
try {
let { default: configDefaultReturn, config: exportedConfigObject } =
await EleventyImportRaw(path, this.isEsm ? "esm" : "cjs");
exportedConfig = exportedConfigObject || {};
if (this.directories && Object.keys(exportedConfigObject?.dir || {}).length > 0) {
debug(
"Setting directories via `config.dir` export from config file: %o",
exportedConfigObject.dir,
);
this.directories.setViaConfigObject(exportedConfigObject.dir);
}
if (typeof configDefaultReturn === "function") {
localConfig = await configDefaultReturn(this.userConfig);
} else {
localConfig = configDefaultReturn;
}
// Removed a check for `filters` in 3.0.0-alpha.6 (now using addTransform instead) https://v3.11ty.dev/docs/config/#transforms
} catch (err) {
let isModuleError =
err instanceof Error && (err?.message || "").includes("Cannot find module");
// TODO the error message here is bad and I feel bad (needs more accurate info)
return Promise.reject(
new EleventyConfigError(
`Error in your Eleventy config file '${path}'.` +
(isModuleError ? chalk.cyan(" You may need to run `npm install`.") : ""),
err,
),
);
}
} else {
debug(
"Project config file not found (not an error—skipping). Looked in: %o",
this.projectConfigPaths,
);
}
return {
localConfig,
exportedConfig,
};
}
/**
* Merges different config files together.
*
* @returns {Promise<object>} merged - The merged config file.
*/
async mergeConfig() {
let { localConfig, exportedConfig } = await this.requireLocalConfigFile();
// Merge `export const config = {}` with `return {}` in config callback
if (isPlainObject(exportedConfig)) {
localConfig = Merge(localConfig || {}, exportedConfig);
}
if (this.directories) {
if (Object.keys(this.userConfig.directoryAssignments || {}).length > 0) {
debug(
"Setting directories via set*Directory configuration APIs %o",
this.userConfig.directoryAssignments,
);
this.directories.setViaConfigObject(this.userConfig.directoryAssignments);
}
if (localConfig && Object.keys(localConfig?.dir || {}).length > 0) {
debug(
"Setting directories via `dir` object return from configuration file: %o",
localConfig.dir,
);
this.directories.setViaConfigObject(localConfig.dir);
}
}
// `templateFormats` is an override via `setTemplateFormats`
if (this.userConfig?.templateFormats) {
this.templateFormats.setViaConfig(this.userConfig.templateFormats);
} else if (localConfig?.templateFormats || this.rootConfig?.templateFormats) {
// Local project config or defaultConfig.js
this.templateFormats.setViaConfig(
localConfig.templateFormats || this.rootConfig?.templateFormats,
);
}
// `templateFormatsAdded` is additive via `addTemplateFormats`
if (this.userConfig?.templateFormatsAdded) {
this.templateFormats.addViaConfig(this.userConfig.templateFormatsAdded);
}
let mergedConfig = Merge({}, this.rootConfig, localConfig);
// Setup a few properties for plugins:
// Set frozen templateFormats
mergedConfig.templateFormats = Object.freeze(this.templateFormats.getTemplateFormats());
// Setup pathPrefix set via command line for plugin consumption
if (this.overrides.pathPrefix) {
mergedConfig.pathPrefix = this.overrides.pathPrefix;
}
// Returning a falsy value (e.g. "") from user config should reset to the default value.
if (!mergedConfig.pathPrefix) {
mergedConfig.pathPrefix = this.rootConfig.pathPrefix;
}
// This is not set in UserConfig.js so that getters arent converted to strings
// We want to error if someone attempts to use a setter there.
if (this.directories) {
mergedConfig.directories = this.directories.getUserspaceInstance();
}
// Delay processing plugins until after the result of localConfig is returned
// But BEFORE the rest of the config options are merged
// this way we can pass directories and other template information to plugins
await this.userConfig.events.emit("eleventy.beforeConfig", this.userConfig);
let pluginsBench = this.aggregateBenchmark.get("Processing plugins in config");
pluginsBench.before();
await this.processPlugins(mergedConfig);
pluginsBench.after();
// Template formats added via plugins
if (this.userConfig?.templateFormatsAdded) {
this.templateFormats.addViaConfig(this.userConfig.templateFormatsAdded);
mergedConfig.templateFormats = Object.freeze(this.templateFormats.getTemplateFormats());
}
let eleventyConfigApiMergingObject = this.userConfig.getMergingConfigObject();
if ("templateFormats" in eleventyConfigApiMergingObject) {
throw new Error(
"Internal error: templateFormats should not return from `getMergingConfigObject`",
);
}
// Overrides are only used by pathPrefix
debug("Configuration overrides: %o", this.overrides);
Merge(mergedConfig, eleventyConfigApiMergingObject, this.overrides);
debug("Current configuration: %o", mergedConfig);
// Add to the merged config too
mergedConfig.uses = this.usesGraph;
// this is used for the layouts event
this.usesGraph.setConfig(mergedConfig);
return mergedConfig;
}
get usesGraph() {
if (!this._usesGraph) {
this._usesGraph = new GlobalDependencyMap();
this._usesGraph.setIsEsm(this.isEsm);
this._usesGraph.setTemplateConfig(this);
}
return this._usesGraph;
}
get uses() {
if (!this.usesGraph) {
throw new Error("The Eleventy Global Dependency Graph has not yet been initialized.");
}
return this.usesGraph;
}
get existsCache() {
if (!this._existsCache) {
this._existsCache = new ExistsCache();
this._existsCache.setDirectoryCheck(true);
}
return this._existsCache;
}
}
export default TemplateConfig;

712
node_modules/@11ty/eleventy/src/TemplateContent.js generated vendored Normal file
View File

@@ -0,0 +1,712 @@
import os from "node:os";
import fs from "graceful-fs";
import matter from "gray-matter";
import lodash from "@11ty/lodash-custom";
import { TemplatePath } from "@11ty/eleventy-utils";
import debugUtil from "debug";
import chardet from "chardet";
import EleventyExtensionMap from "./EleventyExtensionMap.js";
import TemplateData from "./Data/TemplateData.js";
import TemplateRender from "./TemplateRender.js";
import EleventyBaseError from "./Errors/EleventyBaseError.js";
import EleventyErrorUtil from "./Errors/EleventyErrorUtil.js";
import eventBus from "./EventBus.js";
const { set: lodashSet } = lodash;
const debug = debugUtil("Eleventy:TemplateContent");
const debugDiagnostic = debugUtil("Eleventy:Diagnostics");
const debugDev = debugUtil("Dev:Eleventy:TemplateContent");
class TemplateContentConfigError extends EleventyBaseError {}
class TemplateContentFrontMatterError extends EleventyBaseError {}
class TemplateContentCompileError extends EleventyBaseError {}
class TemplateContentRenderError extends EleventyBaseError {}
class TemplateContent {
constructor(inputPath, templateConfig) {
if (!templateConfig || templateConfig.constructor.name !== "TemplateConfig") {
throw new TemplateContentConfigError(
"Missing or invalid `templateConfig` argument to TemplateContent",
);
}
this.eleventyConfig = templateConfig;
this.inputPath = inputPath;
}
get dirs() {
return this.eleventyConfig.directories;
}
get inputDir() {
return this.dirs.input;
}
get outputDir() {
return this.dirs.output;
}
getResetTypes(types) {
if (types) {
return Object.assign(
{
data: false,
read: false,
render: false,
},
types,
);
}
return {
data: true,
read: true,
render: true,
};
}
// Called during an incremental build when the template instance is cached but needs to be reset because it has changed
resetCaches(types) {
types = this.getResetTypes(types);
if (types.read) {
delete this.readingPromise;
delete this.inputContent;
delete this._frontMatterDataCache;
}
}
/* Used by tests */
get extensionMap() {
if (!this._extensionMap) {
this._extensionMap = new EleventyExtensionMap(this.eleventyConfig);
this._extensionMap.setFormats([]);
}
return this._extensionMap;
}
set extensionMap(map) {
this._extensionMap = map;
}
set eleventyConfig(config) {
this._config = config;
if (this._config.constructor.name === "TemplateConfig") {
this._configOptions = this._config.getConfig();
} else {
throw new TemplateContentConfigError("Tried to get an TemplateConfig but none was found.");
}
}
get eleventyConfig() {
if (this._config.constructor.name === "TemplateConfig") {
return this._config;
}
throw new TemplateContentConfigError("Tried to get an TemplateConfig but none was found.");
}
get config() {
if (this._config.constructor.name === "TemplateConfig" && !this._configOptions) {
this._configOptions = this._config.getConfig();
}
return this._configOptions;
}
get bench() {
return this.config.benchmarkManager.get("Aggregate");
}
get engine() {
return this.templateRender.engine;
}
get templateRender() {
if (!this.hasTemplateRender()) {
throw new Error(`\`templateRender\` has not yet initialized on ${this.inputPath}`);
}
return this._templateRender;
}
hasTemplateRender() {
return !!this._templateRender;
}
async getTemplateRender() {
if (!this._templateRender) {
this._templateRender = new TemplateRender(this.inputPath, this.eleventyConfig);
this._templateRender.extensionMap = this.extensionMap;
await this._templateRender.init();
}
return this._templateRender;
}
// For monkey patchers
get frontMatter() {
if (this.frontMatterOverride) {
return this.frontMatterOverride;
} else {
throw new Error(
"Unfortunately youre using code that monkey patched some Eleventy internals and it isnt async-friendly. Change your code to use the async `read()` method on the template instead!",
);
}
}
// For monkey patchers
set frontMatter(contentOverride) {
this.frontMatterOverride = contentOverride;
}
getInputPath() {
return this.inputPath;
}
getInputDir() {
return this.inputDir;
}
isVirtualTemplate() {
let def = this.getVirtualTemplateDefinition();
return !!def;
}
getVirtualTemplateDefinition() {
let inputDirRelativeInputPath =
this.eleventyConfig.directories.getInputPathRelativeToInputDirectory(this.inputPath);
return this.config.virtualTemplates[inputDirRelativeInputPath];
}
async #read() {
let content = await this.inputContent;
if (content || content === "") {
if (this.engine.useJavaScriptImport()) {
return {
data: {},
content,
};
}
let options = this.config.frontMatterParsingOptions || {};
let fm;
try {
// Added in 3.0, passed along to front matter engines
options.filePath = this.inputPath;
fm = matter(content, options);
} catch (e) {
throw new TemplateContentFrontMatterError(
`Having trouble reading front matter from template ${this.inputPath}`,
e,
);
}
if (options.excerpt && fm.excerpt) {
let excerptString = fm.excerpt + (options.excerpt_separator || "---");
if (fm.content.startsWith(excerptString + os.EOL)) {
// with an os-specific newline after excerpt separator
fm.content = fm.excerpt.trim() + "\n" + fm.content.slice((excerptString + os.EOL).length);
} else if (fm.content.startsWith(excerptString + "\n")) {
// with a newline (\n) after excerpt separator
// This is necessary for some git configurations on windows
fm.content = fm.excerpt.trim() + "\n" + fm.content.slice((excerptString + 1).length);
} else if (fm.content.startsWith(excerptString)) {
// no newline after excerpt separator
fm.content = fm.excerpt + fm.content.slice(excerptString.length);
}
// alias, defaults to page.excerpt
let alias = options.excerpt_alias || "page.excerpt";
lodashSet(fm.data, alias, fm.excerpt);
}
// For monkey patchers that used `frontMatter` 🤧
// https://github.com/11ty/eleventy/issues/613#issuecomment-999637109
// https://github.com/11ty/eleventy/issues/2710#issuecomment-1373854834
// Removed this._frontMatter monkey patcher help in 3.0.0-alpha.7
return fm;
} else {
return {
data: {},
content: "",
excerpt: "",
};
}
}
async read() {
if (!this.readingPromise) {
if (!this.inputContent) {
// @cachedproperty
this.inputContent = this.getInputContent();
}
// @cachedproperty
this.readingPromise = this.#read();
}
return this.readingPromise;
}
/* Incremental builds cache the Template instances (in TemplateWriter) but
* these template specific caches are important for Pagination */
static cache(path, content) {
this._inputCache.set(TemplatePath.absolutePath(path), content);
}
static getCached(path) {
return this._inputCache.get(TemplatePath.absolutePath(path));
}
static deleteFromInputCache(path) {
this._inputCache.delete(TemplatePath.absolutePath(path));
}
// Used via clone
setInputContent(content) {
this.inputContent = content;
}
async getInputContent() {
let tr = await this.getTemplateRender();
let virtualTemplateDefinition = this.getVirtualTemplateDefinition();
if (virtualTemplateDefinition) {
let { content } = virtualTemplateDefinition;
return content;
}
if (
tr.engine.useJavaScriptImport() &&
typeof tr.engine.getInstanceFromInputPath === "function"
) {
return tr.engine.getInstanceFromInputPath(this.inputPath);
}
if (!tr.engine.needsToReadFileContents()) {
return "";
}
let templateBenchmark = this.bench.get("Template Read");
templateBenchmark.before();
let content;
if (this.config.useTemplateCache) {
content = TemplateContent.getCached(this.inputPath);
}
if (!content && content !== "") {
let contentBuffer = fs.readFileSync(this.inputPath);
if (process.env.DEBUG) {
// Warning: this is slow!!
let encoding = chardet.detect(contentBuffer);
if (encoding.startsWith("UTF-16")) {
debug("Warning: %o encoding detected on %o.", encoding, this.inputPath);
debugDiagnostic(
"%o encoding detected on %o. Please re-save as UTF-8.",
encoding,
this.inputPath,
);
} else {
debug("%o encoding detected on %o.", encoding, this.inputPath);
}
}
content = contentBuffer.toString("utf8");
if (this.config.useTemplateCache) {
TemplateContent.cache(this.inputPath, content);
}
}
templateBenchmark.after();
return content;
}
async _testGetFrontMatter() {
let fm = this.frontMatterOverride ? this.frontMatterOverride : await this.read();
return fm;
}
async getPreRender() {
let fm = this.frontMatterOverride ? this.frontMatterOverride : await this.read();
return fm.content;
}
async #getFrontMatterData() {
let fm = await this.read();
// gray-matter isnt async-friendly but can return a promise from custom front matter
if (fm.data instanceof Promise) {
fm.data = await fm.data;
}
let extraData = await this.engine.getExtraDataFromFile(this.inputPath);
let virtualTemplateDefinition = this.getVirtualTemplateDefinition();
let virtualTemplateData;
if (virtualTemplateDefinition) {
virtualTemplateData = virtualTemplateDefinition.data;
}
let data = TemplateData.mergeDeep(false, fm.data, extraData, virtualTemplateData);
let cleanedData = TemplateData.cleanupData(data);
return {
data: cleanedData,
excerpt: fm.excerpt,
};
}
async getFrontMatterData() {
if (!this._frontMatterDataCache) {
// @cachedproperty
this._frontMatterDataCache = this.#getFrontMatterData();
}
return this._frontMatterDataCache;
}
async getEngineOverride() {
let { data: frontMatterData } = await this.getFrontMatterData();
return frontMatterData[this.config.keys.engineOverride];
}
_getCompileCache(str) {
// Caches used to be bifurcated based on engine name, now theyre based on inputPath
let inputPathMap = TemplateContent._compileCache.get(this.inputPath);
if (!inputPathMap) {
inputPathMap = new Map();
TemplateContent._compileCache.set(this.inputPath, inputPathMap);
}
let cacheable = this.engine.cacheable;
let { useCache, key } = this.engine.getCompileCacheKey(str, this.inputPath);
// We also tie the compile cache key to the UserConfig instance, to alleviate issues with global template cache
// Better to move the cache to the Eleventy instance instead, no?
// (This specifically failed I18nPluginTest cases with filters being cached across tests and not having access to each plugins options)
key = this.eleventyConfig.userConfig._getUniqueId() + key;
return [cacheable, key, inputPathMap, useCache];
}
async compile(str, options = {}) {
let { type, bypassMarkdown, engineOverride } = options;
let tr = await this.getTemplateRender();
if (engineOverride !== undefined) {
debugDev("%o overriding template engine to use %o", this.inputPath, engineOverride);
await tr.setEngineOverride(engineOverride, bypassMarkdown);
} else {
tr.setUseMarkdown(!bypassMarkdown);
}
if (bypassMarkdown && !this.engine.needsCompilation(str)) {
return function () {
return str;
};
}
debugDev("%o compile() using engine: %o", this.inputPath, tr.engineName);
try {
let res;
if (this.config.useTemplateCache) {
let [cacheable, key, cache, useCache] = this._getCompileCache(str);
if (cacheable && key) {
if (useCache && cache.has(key)) {
this.bench.get("(count) Template Compile Cache Hit").incrementCount();
return cache.get(key);
}
this.bench.get("(count) Template Compile Cache Miss").incrementCount();
// Compile cache is cleared when the resource is modified (below)
// Compilation is async, so we eagerly cache a Promise that eventually
// resolves to the compiled function
cache.set(
key,
new Promise((resolve) => {
res = resolve;
}),
);
}
}
let typeStr = type ? ` ${type}` : "";
let templateBenchmark = this.bench.get(`Template Compile${typeStr}`);
let inputPathBenchmark = this.bench.get(`> Compile${typeStr} > ${this.inputPath}`);
templateBenchmark.before();
inputPathBenchmark.before();
let fn = await tr.getCompiledTemplate(str);
inputPathBenchmark.after();
templateBenchmark.after();
debugDev("%o getCompiledTemplate function created", this.inputPath);
if (this.config.useTemplateCache && res) {
res(fn);
}
return fn;
} catch (e) {
let [cacheable, key, cache] = this._getCompileCache(str);
if (cacheable && key) {
cache.delete(key);
}
debug(`Having trouble compiling template ${this.inputPath}: %O`, str);
throw new TemplateContentCompileError(
`Having trouble compiling template ${this.inputPath}`,
e,
);
}
}
getParseForSymbolsFunction(str) {
let engine = this.engine;
// Dont use markdown as the engine to parse for symbols
// TODO pass in engineOverride here
let preprocessorEngine = this.templateRender.getPreprocessorEngine();
if (preprocessorEngine && engine.getName() !== preprocessorEngine) {
let replacementEngine = this.templateRender.getEngineByName(preprocessorEngine);
if (replacementEngine) {
engine = replacementEngine;
}
}
if ("parseForSymbols" in engine) {
return () => {
return engine.parseForSymbols(str);
};
}
}
// used by computed data or for permalink functions
async _renderFunction(fn, ...args) {
let mixins = Object.assign({}, this.config.javascriptFunctions);
let result = await fn.call(mixins, ...args);
// normalize Buffer away if returned from permalink
if (Buffer.isBuffer(result)) {
return result.toString();
}
return result;
}
async renderComputedData(str, data) {
if (typeof str === "function") {
return this._renderFunction(str, data);
}
return this._render(str, data, {
type: "Computed Data",
bypassMarkdown: true,
});
}
async renderPermalink(permalink, data) {
let permalinkCompilation = this.engine.permalinkNeedsCompilation(permalink);
// No string compilation:
// ({ compileOptions: { permalink: "raw" }})
// These mean `permalink: false`, which is no file system writing:
// ({ compileOptions: { permalink: false }})
// ({ compileOptions: { permalink: () => false }})
// ({ compileOptions: { permalink: () => (() = > false) }})
if (permalinkCompilation === false) {
return permalink;
}
/* Custom `compile` function for permalinks, usage:
permalink: function(permalinkString, inputPath) {
return async function(data) {
return "THIS IS MY RENDERED PERMALINK";
}
}
*/
if (permalinkCompilation && typeof permalinkCompilation === "function") {
permalink = await this._renderFunction(permalinkCompilation, permalink, this.inputPath);
}
// Raw permalink function (in the app code data cascade)
if (typeof permalink === "function") {
return this._renderFunction(permalink, data);
}
return this._render(permalink, data, {
type: "Permalink",
bypassMarkdown: true,
});
}
async render(str, data, bypassMarkdown) {
return this._render(str, data, {
bypassMarkdown,
type: "",
});
}
_getPaginationLogSuffix(data) {
let suffix = [];
if ("pagination" in data) {
suffix.push(" (");
if (data.pagination.pages) {
suffix.push(
`${data.pagination.pages.length} page${data.pagination.pages.length !== 1 ? "s" : ""}`,
);
} else {
suffix.push("Pagination");
}
suffix.push(")");
}
return suffix.join("");
}
async _render(str, data, options = {}) {
let { bypassMarkdown, type } = options;
try {
if (bypassMarkdown && !this.engine.needsCompilation(str)) {
return str;
}
let fn = await this.compile(str, {
bypassMarkdown,
engineOverride: data[this.config.keys.engineOverride],
type,
});
if (fn === undefined) {
return;
} else if (typeof fn !== "function") {
throw new Error(`The \`compile\` function did not return a function. Received ${fn}`);
}
// Benchmark
let templateBenchmark = this.bench.get("Render");
let inputPathBenchmark = this.bench.get(
`> Render${type ? ` ${type}` : ""} > ${this.inputPath}${this._getPaginationLogSuffix(data)}`,
);
templateBenchmark.before();
if (inputPathBenchmark) {
inputPathBenchmark.before();
}
let rendered = await fn(data);
if (inputPathBenchmark) {
inputPathBenchmark.after();
}
templateBenchmark.after();
debugDev("%o getCompiledTemplate called, rendered content created", this.inputPath);
return rendered;
} catch (e) {
if (EleventyErrorUtil.isPrematureTemplateContentError(e)) {
return Promise.reject(e);
} else {
let tr = await this.getTemplateRender();
let engine = tr.getReadableEnginesList();
debug(`Having trouble rendering ${engine} template ${this.inputPath}: %O`, str);
return Promise.reject(
new TemplateContentRenderError(
`Having trouble rendering ${engine} template ${this.inputPath}`,
e,
),
);
}
}
}
getExtensionEntries() {
return this.engine.extensionEntries;
}
isFileRelevantToThisTemplate(incrementalFile, metadata = {}) {
// always relevant if incremental file not set (build everything)
if (!incrementalFile) {
return true;
}
let hasDependencies = this.engine.hasDependencies(incrementalFile);
let isRelevant = this.engine.isFileRelevantTo(this.inputPath, incrementalFile);
debug(
"Test dependencies to see if %o is relevant to %o: %o",
this.inputPath,
incrementalFile,
isRelevant,
);
let extensionEntries = this.getExtensionEntries().filter((entry) => !!entry.isIncrementalMatch);
if (extensionEntries.length) {
for (let entry of extensionEntries) {
if (
entry.isIncrementalMatch.call(
{
inputPath: this.inputPath,
isFullTemplate: metadata.isFullTemplate,
isFileRelevantToInputPath: isRelevant,
doesFileHaveDependencies: hasDependencies,
},
incrementalFile,
)
) {
return true;
}
}
return false;
} else {
// Not great way of building all templates if this is a layout, include, JS dependency.
// TODO improve this for default template syntaxes
// This is the fallback way of determining if something is incremental (no isIncrementalMatch available)
// This will be true if the inputPath and incrementalFile are the same
if (isRelevant) {
return true;
}
// only return true here if dependencies are not known
if (!hasDependencies && !metadata.isFullTemplate) {
return true;
}
}
return false;
}
}
TemplateContent._inputCache = new Map();
TemplateContent._compileCache = new Map();
eventBus.on("eleventy.resourceModified", (path) => {
// delete from input cache
TemplateContent.deleteFromInputCache(path);
// delete from compile cache
let normalized = TemplatePath.addLeadingDotSlash(path);
let compileCache = TemplateContent._compileCache.get(normalized);
if (compileCache) {
compileCache.clear();
}
});
// Used when the configuration file reset https://github.com/11ty/eleventy/issues/2147
eventBus.on("eleventy.compileCacheReset", (/*path*/) => {
TemplateContent._compileCache = new Map();
});
export default TemplateContent;

57
node_modules/@11ty/eleventy/src/TemplateFileSlug.js generated vendored Normal file
View File

@@ -0,0 +1,57 @@
import path from "node:path";
import { TemplatePath } from "@11ty/eleventy-utils";
class TemplateFileSlug {
constructor(inputPath, extensionMap, eleventyConfig) {
let inputDir = eleventyConfig.directories.input;
if (inputDir) {
inputPath = TemplatePath.stripLeadingSubPath(inputPath, inputDir);
}
this.inputPath = inputPath;
this.cleanInputPath = inputPath.replace(/^.\//, "");
let dirs = this.cleanInputPath.split("/");
this.dirs = dirs;
this.dirs.pop();
this.parsed = path.parse(inputPath);
this.filenameNoExt = extensionMap.removeTemplateExtension(this.parsed.base);
}
// `page.filePathStem` see https://v3.11ty.dev/docs/data-eleventy-supplied/#page-variable
getFullPathWithoutExtension() {
return "/" + TemplatePath.join(...this.dirs, this._getRawSlug());
}
_getRawSlug() {
let slug = this.filenameNoExt;
return this._stripDateFromSlug(slug);
}
/** Removes dates in the format of YYYY-MM-DD from a given slug string candidate. */
_stripDateFromSlug(slug) {
let reg = slug.match(/\d{4}-\d{2}-\d{2}-(.*)/);
if (reg) {
return reg[1];
}
return slug;
}
// `page.fileSlug` see https://v3.11ty.dev/docs/data-eleventy-supplied/#page-variable
getSlug() {
let rawSlug = this._getRawSlug();
if (rawSlug === "index") {
if (!this.dirs.length) {
return "";
}
let lastDir = this.dirs[this.dirs.length - 1];
return this._stripDateFromSlug(lastDir);
}
return rawSlug;
}
}
export default TemplateFileSlug;

35
node_modules/@11ty/eleventy/src/TemplateGlob.js generated vendored Normal file
View File

@@ -0,0 +1,35 @@
import { TemplatePath } from "@11ty/eleventy-utils";
class TemplateGlob {
static normalizePath(...paths) {
if (paths[0].charAt(0) === "!") {
throw new Error(
`TemplateGlob.normalizePath does not accept ! glob paths like: ${paths.join("")}`,
);
}
return TemplatePath.addLeadingDotSlash(TemplatePath.join(...paths));
}
static normalize(path) {
path = path.trim();
if (path.charAt(0) === "!") {
return "!" + TemplateGlob.normalizePath(path.slice(1));
} else {
return TemplateGlob.normalizePath(path);
}
}
static map(files) {
if (typeof files === "string") {
return TemplateGlob.normalize(files);
} else if (Array.isArray(files)) {
return files.map(function (path) {
return TemplateGlob.normalize(path);
});
} else {
return files;
}
}
}
export default TemplateGlob;

242
node_modules/@11ty/eleventy/src/TemplateLayout.js generated vendored Normal file
View File

@@ -0,0 +1,242 @@
import { TemplatePath } from "@11ty/eleventy-utils";
import debugUtil from "debug";
import TemplateLayoutPathResolver from "./TemplateLayoutPathResolver.js";
import TemplateContent from "./TemplateContent.js";
import TemplateData from "./Data/TemplateData.js";
import templateCache from "./TemplateCache.js";
// const debug = debugUtil("Eleventy:TemplateLayout");
const debugDev = debugUtil("Dev:Eleventy:TemplateLayout");
class TemplateLayout extends TemplateContent {
constructor(key, extensionMap, eleventyConfig) {
if (!eleventyConfig || eleventyConfig.constructor.name !== "TemplateConfig") {
throw new Error("Expected `eleventyConfig` in TemplateLayout constructor.");
}
let resolver = new TemplateLayoutPathResolver(key, extensionMap, eleventyConfig);
let resolvedPath = resolver.getFullPath();
super(resolvedPath, eleventyConfig);
if (!extensionMap) {
throw new Error("Expected `extensionMap` in TemplateLayout constructor.");
}
this.extensionMap = extensionMap;
this.key = resolver.getNormalizedLayoutKey();
this.dataKeyLayoutPath = key;
this.inputPath = resolvedPath;
}
getKey() {
return this.key;
}
getFullKey() {
return TemplateLayout.resolveFullKey(this.dataKeyLayoutPath, this.inputDir);
}
getCacheKeys() {
return new Set([this.dataKeyLayoutPath, this.getFullKey(), this.key]);
}
static resolveFullKey(key, inputDir) {
return TemplatePath.join(inputDir, key);
}
static getTemplate(key, eleventyConfig, extensionMap) {
let config = eleventyConfig.getConfig();
if (!config.useTemplateCache) {
return new TemplateLayout(key, extensionMap, eleventyConfig);
}
let inputDir = eleventyConfig.directories.input;
let fullKey = TemplateLayout.resolveFullKey(key, inputDir);
if (!templateCache.has(fullKey)) {
let layout = new TemplateLayout(key, extensionMap, eleventyConfig);
templateCache.add(layout);
debugDev("Added %o to TemplateCache", key);
return layout;
}
return templateCache.get(fullKey);
}
async getTemplateLayoutMapEntry() {
let { data: frontMatterData } = await this.getFrontMatterData();
return {
// Used by `TemplateLayout.getTemplate()`
key: this.dataKeyLayoutPath,
// used by `this.getData()`
frontMatterData,
};
}
async #getTemplateLayoutMap() {
// For both the eleventy.layouts event and cyclical layout chain checking (e.g., a => b => c => a)
let layoutChain = new Set();
layoutChain.add(this.inputPath);
let cfgKey = this.config.keys.layout;
let map = [];
let mapEntry = await this.getTemplateLayoutMapEntry();
map.push(mapEntry);
while (mapEntry.frontMatterData && cfgKey in mapEntry.frontMatterData) {
// Layout of the current layout
let parentLayoutKey = mapEntry.frontMatterData[cfgKey];
let layout = TemplateLayout.getTemplate(
parentLayoutKey,
this.eleventyConfig,
this.extensionMap,
);
// Abort if a circular layout chain is detected. Otherwise, we'll time out and run out of memory.
if (layoutChain.has(layout.inputPath)) {
throw new Error(
`Your layouts have a circular reference, starting at ${map[0].key}! The layout at ${layout.inputPath} was specified twice in this layout chain.`,
);
}
// Keep track of this layout so we can detect duplicates in subsequent iterations
layoutChain.add(layout.inputPath);
// reassign for next loop
mapEntry = await layout.getTemplateLayoutMapEntry();
map.push(mapEntry);
}
this.layoutChain = Array.from(layoutChain);
return map;
}
async getTemplateLayoutMap() {
if (!this.cachedLayoutMap) {
this.cachedLayoutMap = this.#getTemplateLayoutMap();
}
return this.cachedLayoutMap;
}
async getLayoutChain() {
if (!Array.isArray(this.layoutChain)) {
await this.getTemplateLayoutMap();
}
return this.layoutChain;
}
async #getData() {
let map = await this.getTemplateLayoutMap();
let dataToMerge = [];
for (let j = map.length - 1; j >= 0; j--) {
dataToMerge.push(map[j].frontMatterData);
}
// Deep merge of layout front matter
let data = TemplateData.mergeDeep(this.config.dataDeepMerge, {}, ...dataToMerge);
delete data[this.config.keys.layout];
return data;
}
async getData() {
if (!this.dataCache) {
this.dataCache = this.#getData();
}
return this.dataCache;
}
async #getCachedCompiledLayoutFunction() {
let rawInput = await this.getPreRender();
let renderFunction = await this.compile(rawInput);
return renderFunction;
}
// Do only cache this layouts render function and delegate the rest to the other templates.
async getCachedCompiledLayoutFunction() {
if (!this.cachedCompiledLayoutFunction) {
this.cachedCompiledLayoutFunction = this.#getCachedCompiledLayoutFunction();
}
return this.cachedCompiledLayoutFunction;
}
async getCompiledLayoutFunctions() {
let layoutMap = await this.getTemplateLayoutMap();
let fns = [];
try {
fns.push({
render: await this.getCachedCompiledLayoutFunction(),
});
if (layoutMap.length > 1) {
let [, /*currentLayout*/ parentLayout] = layoutMap;
let { key } = parentLayout;
let layoutTemplate = TemplateLayout.getTemplate(
key,
this.eleventyConfig,
this.extensionMap,
);
// The parent already includes the rest of the layout chain
let upstreamFns = await layoutTemplate.getCompiledLayoutFunctions();
for (let j = 0, k = upstreamFns.length; j < k; j++) {
fns.push(upstreamFns[j]);
}
}
return fns;
} catch (e) {
debugDev("Clearing TemplateCache after error.");
templateCache.clear();
throw e;
}
}
async render() {
throw new Error("Internal error: `render` was removed from TemplateLayout.js in Eleventy 3.0.");
}
// Inefficient? We want to compile all the templatelayouts into a single reusable callback?
// Trouble: layouts may need data variables present downstream/upstream
// This is called from Template->renderPageEntry
async renderPageEntry(pageEntry) {
let templateContent = pageEntry.templateContent;
let compiledFunctions = await this.getCompiledLayoutFunctions();
for (let { render } of compiledFunctions) {
let data = {
content: templateContent,
...pageEntry.data,
};
templateContent = await render(data);
}
// Dont set `templateContent` on pageEntry because collection items should not have layout markup
return templateContent;
}
resetCaches(types) {
super.resetCaches(types);
delete this.dataCache;
delete this.layoutChain;
delete this.cachedLayoutMap;
delete this.cachedCompiledLayoutFunction;
}
}
export default TemplateLayout;

View File

@@ -0,0 +1,136 @@
import fs from "node:fs";
import { TemplatePath } from "@11ty/eleventy-utils";
// import debugUtil from "debug";
// const debug = debugUtil("Eleventy:TemplateLayoutPathResolver");
class TemplateLayoutPathResolver {
constructor(path, extensionMap, eleventyConfig) {
if (!eleventyConfig) {
throw new Error("Expected `eleventyConfig` in TemplateLayoutPathResolver constructor");
}
this.eleventyConfig = eleventyConfig;
this.originalPath = path;
this.originalDisplayPath =
TemplatePath.join(this.layoutsDir, this.originalPath) +
` (via \`layout: ${this.originalPath}\`)`; // for error messaging
this.path = path;
this.aliases = {};
this.extensionMap = extensionMap;
if (!extensionMap) {
throw new Error("Expected `extensionMap` in TemplateLayoutPathResolver constructor.");
}
this.init();
}
getVirtualTemplate(layoutPath) {
let inputDirRelativePath =
this.eleventyConfig.directories.getLayoutPathRelativeToInputDirectory(layoutPath);
return this.config.virtualTemplates[inputDirRelativePath];
}
get dirs() {
return this.eleventyConfig.directories;
}
get inputDir() {
return this.dirs.input;
}
get layoutsDir() {
return this.dirs.layouts || this.dirs.includes;
}
/* Backwards compat */
getLayoutsDir() {
return this.layoutsDir;
}
setAliases() {
this.aliases = Object.assign({}, this.config.layoutAliases, this.aliases);
}
// for testing
set config(cfg) {
this._config = cfg;
this.init();
}
get config() {
if (this.eleventyConfig) {
return this.eleventyConfig.getConfig();
} else {
throw new Error("Missing this.eleventyConfig");
}
}
exists(layoutPath) {
if (this.getVirtualTemplate(layoutPath)) {
return true;
}
let fullPath = this.eleventyConfig.directories.getLayoutPath(layoutPath);
if (fs.existsSync(fullPath)) {
return true;
}
return false;
}
init() {
// we might be able to move this into the constructor?
this.aliases = Object.assign({}, this.config.layoutAliases, this.aliases);
if (this.aliases[this.path]) {
this.path = this.aliases[this.path];
}
let useLayoutResolution = this.config.layoutResolution;
if (this.path.split(".").length > 0 && this.exists(this.path)) {
this.filename = this.path;
this.fullPath = this.eleventyConfig.directories.getLayoutPath(this.path);
} else if (useLayoutResolution) {
this.filename = this.findFileName();
this.fullPath = this.eleventyConfig.directories.getLayoutPath(this.filename || "");
}
}
addLayoutAlias(from, to) {
this.aliases[from] = to;
}
getFileName() {
if (!this.filename) {
throw new Error(
`Youre trying to use a layout that does not exist: ${this.originalDisplayPath}`,
);
}
return this.filename;
}
getFullPath() {
if (!this.filename) {
throw new Error(
`Youre trying to use a layout that does not exist: ${this.originalDisplayPath}`,
);
}
return this.fullPath;
}
findFileName() {
for (let filename of this.extensionMap.getFileList(this.path)) {
if (this.exists(filename)) {
return filename;
}
}
}
getNormalizedLayoutKey() {
return TemplatePath.stripLeadingSubPath(this.fullPath, this.layoutsDir);
}
}
export default TemplateLayoutPathResolver;

828
node_modules/@11ty/eleventy/src/TemplateMap.js generated vendored Normal file
View File

@@ -0,0 +1,828 @@
import { DepGraph as DependencyGraph } from "dependency-graph";
import { isPlainObject, TemplatePath } from "@11ty/eleventy-utils";
import debugUtil from "debug";
import TemplateCollection from "./TemplateCollection.js";
import EleventyErrorUtil from "./Errors/EleventyErrorUtil.js";
import UsingCircularTemplateContentReferenceError from "./Errors/UsingCircularTemplateContentReferenceError.js";
import EleventyBaseError from "./Errors/EleventyBaseError.js";
import DuplicatePermalinkOutputError from "./Errors/DuplicatePermalinkOutputError.js";
import TemplateData from "./Data/TemplateData.js";
const debug = debugUtil("Eleventy:TemplateMap");
const debugDev = debugUtil("Dev:Eleventy:TemplateMap");
class TemplateMapConfigError extends EleventyBaseError {}
class EleventyDataSchemaError extends EleventyBaseError {}
// These template URL filenames are allowed to exclude file extensions
const EXTENSIONLESS_URL_ALLOWLIST = [
"/_redirects", // Netlify specific
"/.htaccess", // Apache
"/_headers", // Cloudflare
];
class TemplateMap {
constructor(eleventyConfig) {
if (!eleventyConfig) {
throw new TemplateMapConfigError("Missing config argument.");
}
this.eleventyConfig = eleventyConfig;
this.map = [];
this.collectionsData = null;
this.cached = false;
this.verboseOutput = true;
this.collection = new TemplateCollection();
}
set userConfig(config) {
this._userConfig = config;
}
get userConfig() {
if (!this._userConfig) {
// TODO use this.config for this, need to add collections to mergeable props in userconfig
this._userConfig = this.eleventyConfig.userConfig;
}
return this._userConfig;
}
get config() {
if (!this._config) {
this._config = this.eleventyConfig.getConfig();
}
return this._config;
}
static get tagPrefix() {
return "___TAG___";
}
async add(template) {
if (!template) {
return;
}
let data = await template.getData();
let entries = await template.getTemplateMapEntries(data);
for (let map of entries) {
this.map.push(map);
}
}
getMap() {
return this.map;
}
getTagTarget(str) {
if (str.startsWith("collections.")) {
return str.slice("collections.".length);
}
// Fixes #2851
if (str.startsWith("collections['") || str.startsWith('collections["')) {
return str.slice("collections['".length, -2);
}
}
/* ---
* pagination:
* data: collections
* ---
*/
isPaginationOverAllCollections(entry) {
if (entry.data.pagination?.data) {
return (
entry.data.pagination.data === "collections" ||
entry.data.pagination.data === "collections.all"
);
}
}
getPaginationTagTarget(entry) {
if (entry.data.pagination?.data) {
return this.getTagTarget(entry.data.pagination.data);
}
}
addTagsToGraph(graph, inputPath, tags) {
if (!Array.isArray(tags)) {
return;
}
for (let tag of tags) {
let tagWithPrefix = TemplateMap.tagPrefix + tag;
if (!graph.hasNode(tagWithPrefix)) {
graph.addNode(tagWithPrefix);
}
// Populates to collections.tagName
// Dependency from tag to inputPath
graph.addDependency(tagWithPrefix, inputPath);
}
}
addDeclaredDependenciesToGraph(graph, inputPath, deps) {
if (!Array.isArray(deps)) {
return;
}
for (let tag of deps) {
let tagWithPrefix = TemplateMap.tagPrefix + tag;
if (!graph.hasNode(tagWithPrefix)) {
graph.addNode(tagWithPrefix);
}
// Dependency from inputPath to collection/tag
graph.addDependency(inputPath, tagWithPrefix);
}
}
// Exclude: Pagination templates consuming `collections` or `collections.all`
// Exclude: Pagination templates that consume config API collections
// Include: Pagination templates that dont consume config API collections
// Include: Templates that dont use Pagination
getMappedDependencies() {
let graph = new DependencyGraph();
let tagPrefix = TemplateMap.tagPrefix;
graph.addNode(tagPrefix + "all");
for (let entry of this.map) {
if (this.isPaginationOverAllCollections(entry)) {
continue;
}
// using Pagination (but not targeting a user config collection)
let paginationTagTarget = this.getPaginationTagTarget(entry);
if (paginationTagTarget) {
if (this.isUserConfigCollectionName(paginationTagTarget)) {
// delay this one to the second stage
continue;
} else {
// using pagination but over a tagged collection
graph.addNode(entry.inputPath);
if (!graph.hasNode(tagPrefix + paginationTagTarget)) {
graph.addNode(tagPrefix + paginationTagTarget);
}
graph.addDependency(entry.inputPath, tagPrefix + paginationTagTarget);
}
} else {
// not using pagination
graph.addNode(entry.inputPath);
}
let collections = TemplateData.getIncludedCollectionNames(entry.data);
this.addTagsToGraph(graph, entry.inputPath, collections);
this.addDeclaredDependenciesToGraph(
graph,
entry.inputPath,
entry.data.eleventyImport?.collections,
);
}
return graph;
}
// Exclude: Pagination templates consuming `collections` or `collections.all`
// Include: Pagination templates that consume config API collections
getDelayedMappedDependencies() {
let graph = new DependencyGraph();
let tagPrefix = TemplateMap.tagPrefix;
graph.addNode(tagPrefix + "all");
let userConfigCollections = this.getUserConfigCollectionNames();
// Add tags from named user config collections
for (let tag of userConfigCollections) {
graph.addNode(tagPrefix + tag);
}
for (let entry of this.map) {
if (this.isPaginationOverAllCollections(entry)) {
continue;
}
let paginationTagTarget = this.getPaginationTagTarget(entry);
if (paginationTagTarget && this.isUserConfigCollectionName(paginationTagTarget)) {
if (!graph.hasNode(entry.inputPath)) {
graph.addNode(entry.inputPath);
}
graph.addDependency(entry.inputPath, tagPrefix + paginationTagTarget);
let collections = TemplateData.getIncludedCollectionNames(entry.data);
this.addTagsToGraph(graph, entry.inputPath, collections);
this.addDeclaredDependenciesToGraph(
graph,
entry.inputPath,
entry.data.eleventyImport?.collections,
);
}
}
return graph;
}
// Exclude: Pagination templates consuming `collections.all`
// Include: Pagination templates consuming `collections`
getPaginatedOverCollectionsMappedDependencies() {
let graph = new DependencyGraph();
let tagPrefix = TemplateMap.tagPrefix;
let allNodeAdded = false;
for (let entry of this.map) {
if (this.isPaginationOverAllCollections(entry) && !this.getPaginationTagTarget(entry)) {
if (!allNodeAdded) {
graph.addNode(tagPrefix + "all");
allNodeAdded = true;
}
if (!graph.hasNode(entry.inputPath)) {
graph.addNode(entry.inputPath);
}
let collectionNames = TemplateData.getIncludedCollectionNames(entry.data);
if (collectionNames.includes("all")) {
// collections.all
graph.addDependency(tagPrefix + "all", entry.inputPath);
// Note that `tags` are otherwise ignored here
}
this.addDeclaredDependenciesToGraph(
graph,
entry.inputPath,
entry.data.eleventyImport?.collections,
);
}
}
return graph;
}
// Include: Pagination templates consuming `collections.all`
getPaginatedOverAllCollectionMappedDependencies() {
let graph = new DependencyGraph();
let tagPrefix = TemplateMap.tagPrefix;
let allNodeAdded = false;
for (let entry of this.map) {
if (
this.isPaginationOverAllCollections(entry) &&
this.getPaginationTagTarget(entry) === "all"
) {
if (!allNodeAdded) {
graph.addNode(tagPrefix + "all");
allNodeAdded = true;
}
if (!graph.hasNode(entry.inputPath)) {
graph.addNode(entry.inputPath);
}
let collectionNames = TemplateData.getIncludedCollectionNames(entry.data);
if (collectionNames.includes("all")) {
// Populates into collections.all
// This is circular!
graph.addDependency(tagPrefix + "all", entry.inputPath);
// Note that `tags` are otherwise ignored here
}
this.addDeclaredDependenciesToGraph(
graph,
entry.inputPath,
entry.data.eleventyImport?.collections,
);
}
}
return graph;
}
getTemplateMapDependencyGraph() {
return [
this.getMappedDependencies(),
this.getDelayedMappedDependencies(),
this.getPaginatedOverCollectionsMappedDependencies(),
this.getPaginatedOverAllCollectionMappedDependencies(),
];
}
getFullTemplateMapOrder() {
// convert dependency graphs to ordered arrays
return this.getTemplateMapDependencyGraph().map((entry) => entry.overallOrder());
}
#addEntryToGlobalDependencyGraph(entry) {
let paginationTagTarget = this.getPaginationTagTarget(entry);
if (paginationTagTarget) {
this.config.uses.addDependencyConsumesCollection(entry.inputPath, paginationTagTarget);
}
let collectionNames = TemplateData.getIncludedCollectionNames(entry.data);
for (let name of collectionNames) {
this.config.uses.addDependencyPublishesToCollection(entry.inputPath, name);
}
if (Array.isArray(entry.data.eleventyImport?.collections)) {
for (let tag of entry.data.eleventyImport.collections) {
this.config.uses.addDependencyConsumesCollection(entry.inputPath, tag);
}
}
}
addAllToGlobalDependencyGraph() {
for (let entry of this.map) {
this.#addEntryToGlobalDependencyGraph(entry);
}
}
async setCollectionByTagName(tagName) {
if (this.isUserConfigCollectionName(tagName)) {
// async
this.collectionsData[tagName] = await this.getUserConfigCollection(tagName);
} else {
this.collectionsData[tagName] = this.getTaggedCollection(tagName);
}
let precompiled = this.config.precompiledCollections;
if (precompiled?.[tagName]) {
if (
tagName === "all" ||
!Array.isArray(this.collectionsData[tagName]) ||
this.collectionsData[tagName].length === 0
) {
this.collectionsData[tagName] = precompiled[tagName];
}
}
}
// TODO(slightlyoff): major bottleneck
async initDependencyMap(dependencyMap) {
let tagPrefix = TemplateMap.tagPrefix;
for (let depEntry of dependencyMap) {
if (depEntry.startsWith(tagPrefix)) {
// is a tag (collection) entry
let tagName = depEntry.slice(tagPrefix.length);
await this.setCollectionByTagName(tagName);
} else {
// is a template entry
let map = this.getMapEntryForInputPath(depEntry);
map._pages = await map.template.getTemplates(map.data);
if (map._pages.length === 0) {
// Reminder: a serverless code path was removed here.
} else {
let counter = 0;
for (let page of map._pages) {
// Copy outputPath to map entry
// This is no longer used internally, just for backwards compatibility
// Error added in v3 for https://github.com/11ty/eleventy/issues/3183
if (map.data.pagination) {
if (!Object.prototype.hasOwnProperty.call(map, "outputPath")) {
Object.defineProperty(map, "outputPath", {
get() {
throw new Error(
"Internal error: `.outputPath` on a paginated map entry is not consistent. Use `_pages[…].outputPath` instead.",
);
},
});
}
} else if (!map.outputPath) {
map.outputPath = page.outputPath;
}
if (counter === 0 || map.data.pagination?.addAllPagesToCollections) {
if (map.data.eleventyExcludeFromCollections !== true) {
// is in *some* collections
this.collection.add(page);
}
}
counter++;
}
}
}
}
}
async cache() {
debug("Caching collections objects.");
this.collectionsData = {};
for (let entry of this.map) {
entry.data.collections = this.collectionsData;
}
let [dependencyMap, delayedDependencyMap, firstPaginatedDepMap, secondPaginatedDepMap] =
this.getFullTemplateMapOrder();
await this.initDependencyMap(dependencyMap);
await this.initDependencyMap(delayedDependencyMap);
await this.initDependencyMap(firstPaginatedDepMap);
await this.initDependencyMap(secondPaginatedDepMap);
await this.resolveRemainingComputedData();
let orderedPaths = this.getOrderedInputPaths(
dependencyMap,
delayedDependencyMap,
firstPaginatedDepMap,
secondPaginatedDepMap,
);
let orderedMap = orderedPaths.map((inputPath) => {
return this.getMapEntryForInputPath(inputPath);
});
await this.config.events.emitLazy("eleventy.contentMap", () => {
return {
inputPathToUrl: this.generateInputUrlContentMap(orderedMap),
urlToInputPath: this.generateUrlMap(orderedMap),
};
});
await this.runDataSchemas(orderedMap);
await this.populateContentDataInMap(orderedMap);
this.populateCollectionsWithContent();
this.cached = true;
this.checkForDuplicatePermalinks();
this.checkForMissingFileExtensions();
await this.config.events.emitLazy("eleventy.layouts", () => this.generateLayoutsMap());
}
generateInputUrlContentMap(orderedMap) {
let entries = {};
for (let entry of orderedMap) {
entries[entry.inputPath] = entry._pages.map((entry) => entry.url);
}
return entries;
}
generateUrlMap(orderedMap) {
let entries = {};
for (let entry of orderedMap) {
for (let page of entry._pages) {
// duplicate urls throw an error, so we can return non array here
entries[page.url] = {
inputPath: entry.inputPath,
groupNumber: page.groupNumber,
};
}
}
return entries;
}
// TODO(slightlyoff): hot inner loop?
getMapEntryForInputPath(inputPath) {
for (let map of this.map) {
if (map.inputPath === inputPath) {
return map;
}
}
}
// Filter out any tag nodes
getOrderedInputPaths(...maps) {
let orderedMap = [];
let tagPrefix = TemplateMap.tagPrefix;
for (let map of maps) {
for (let dep of map) {
if (!dep.startsWith(tagPrefix)) {
orderedMap.push(dep);
}
}
}
return orderedMap;
}
async runDataSchemas(orderedMap) {
for (let map of orderedMap) {
if (!map._pages) {
continue;
}
for (let pageEntry of map._pages) {
// Data Schema callback #879
if (typeof pageEntry.data[this.config.keys.dataSchema] === "function") {
try {
await pageEntry.data[this.config.keys.dataSchema](pageEntry.data);
} catch (e) {
throw new EleventyDataSchemaError(
`Error in the data schema for: ${map.inputPath} (via \`eleventyDataSchema\`)`,
e,
);
}
}
}
}
}
async populateContentDataInMap(orderedMap) {
let usedTemplateContentTooEarlyMap = [];
// Note that empty pagination templates will be skipped here as not renderable
let filteredMap = orderedMap.filter((entry) => entry.template.isRenderable());
for (let map of filteredMap) {
if (!map._pages) {
throw new Error(`Internal error: _pages not found for ${map.inputPath}`);
}
// IMPORTANT: this is where template content is rendered
try {
for (let pageEntry of map._pages) {
pageEntry.templateContent =
await pageEntry.template.renderPageEntryWithoutLayout(pageEntry);
}
} catch (e) {
if (EleventyErrorUtil.isPrematureTemplateContentError(e)) {
usedTemplateContentTooEarlyMap.push(map);
// Reset cached render promise
for (let pageEntry of map._pages) {
pageEntry.template.resetCaches({ render: true });
}
} else {
throw e;
}
}
debugDev("Added this.map[...].templateContent, outputPath, et al for one map entry");
}
for (let map of usedTemplateContentTooEarlyMap) {
try {
for (let pageEntry of map._pages) {
pageEntry.templateContent =
await pageEntry.template.renderPageEntryWithoutLayout(pageEntry);
}
} catch (e) {
if (EleventyErrorUtil.isPrematureTemplateContentError(e)) {
throw new UsingCircularTemplateContentReferenceError(
`${map.inputPath} contains a circular reference (using collections) to its own templateContent.`,
);
} else {
// rethrow?
throw e;
}
}
}
}
getTaggedCollection(tag) {
let result;
if (!tag || tag === "all") {
result = this.collection.getAllSorted();
} else {
result = this.collection.getFilteredByTag(tag);
}
debug(`Collection: collections.${tag || "all"} size: ${result.length}`);
return result;
}
/* 3.0.0-alpha.1: setUserConfigCollections method removed (was only used for testing) */
isUserConfigCollectionName(name) {
let collections = this.userConfig.getCollections();
return name && !!collections[name];
}
getUserConfigCollectionNames() {
return Object.keys(this.userConfig.getCollections());
}
async getUserConfigCollection(name) {
let configCollections = this.userConfig.getCollections();
// This works with async now
let result = await configCollections[name](this.collection);
debug(`Collection: collections.${name} size: ${result.length}`);
return result;
}
populateCollectionsWithContent() {
for (let collectionName in this.collectionsData) {
// skip custom collections set in configuration files that have arbitrary types
if (!Array.isArray(this.collectionsData[collectionName])) {
continue;
}
for (let item of this.collectionsData[collectionName]) {
// skip custom collections set in configuration files that have arbitrary types
if (!isPlainObject(item) || !("inputPath" in item)) {
continue;
}
let entry = this.getMapEntryForInputPath(item.inputPath);
// This check skips precompiled collections
if (entry) {
let index = item.pageNumber || 0;
let content = entry._pages[index]._templateContent;
if (content !== undefined) {
item.templateContent = content;
}
}
}
}
}
async resolveRemainingComputedData() {
let promises = [];
for (let entry of this.map) {
for (let pageEntry of entry._pages) {
if (this.config.keys.computed in pageEntry.data) {
promises.push(await pageEntry.template.resolveRemainingComputedData(pageEntry.data));
}
}
}
return Promise.all(promises);
}
async generateLayoutsMap() {
let layouts = {};
for (let entry of this.map) {
for (let page of entry._pages) {
let tmpl = page.template;
let layoutKey = page.data[this.config.keys.layout];
if (layoutKey) {
let layout = tmpl.getLayout(layoutKey);
let layoutChain = await layout.getLayoutChain();
let priors = [];
for (let filepath of layoutChain) {
if (!layouts[filepath]) {
layouts[filepath] = new Set();
}
layouts[filepath].add(page.inputPath);
for (let prior of priors) {
layouts[filepath].add(prior);
}
priors.push(filepath);
}
}
}
}
for (let key in layouts) {
layouts[key] = Array.from(layouts[key]);
}
return layouts;
}
#onEachPage(callback) {
for (let template of this.map) {
for (let page of template._pages) {
callback(page, template);
}
}
}
checkForDuplicatePermalinks() {
let inputs = {};
let permalinks = {};
let warnings = {};
this.#onEachPage((page, template) => {
if (page.outputPath === false || page.url === false) {
// do nothing (also serverless)
} else {
// Make sure output doesnt overwrite input (e.g. --input=. --output=.)
// Related to https://github.com/11ty/eleventy/issues/3327
if (page.outputPath === page.inputPath) {
throw new DuplicatePermalinkOutputError(
`The template at "${page.inputPath}" attempted to overwrite itself.`,
);
} else if (inputs[page.outputPath]) {
throw new DuplicatePermalinkOutputError(
`The template at "${page.inputPath}" attempted to overwrite an existing template at "${page.outputPath}".`,
);
}
inputs[page.inputPath] = true;
if (!permalinks[page.outputPath]) {
permalinks[page.outputPath] = [template.inputPath];
} else {
warnings[page.outputPath] = `Output conflict: multiple input files are writing to \`${
page.outputPath
}\`. Use distinct \`permalink\` values to resolve this conflict.
1. ${template.inputPath}
${permalinks[page.outputPath]
.map(function (inputPath, index) {
return ` ${index + 2}. ${inputPath}\n`;
})
.join("")}
`;
permalinks[page.outputPath].push(template.inputPath);
}
}
});
let warningList = Object.values(warnings);
if (warningList.length) {
// throw one at a time
throw new DuplicatePermalinkOutputError(warningList[0]);
}
}
checkForMissingFileExtensions() {
// disabled in config
if (this.userConfig?.errorReporting?.allowMissingExtensions === true) {
return;
}
this.#onEachPage((page) => {
if (
page.outputPath === false ||
page.url === false ||
page.data.eleventyAllowMissingExtension ||
EXTENSIONLESS_URL_ALLOWLIST.some((url) => page.url.endsWith(url))
) {
// do nothing (also serverless)
} else {
if (TemplatePath.getExtension(page.outputPath) === "") {
let e =
new Error(`The template at '${page.inputPath}' attempted to write to '${page.outputPath}'${page.data.permalink ? ` (via \`permalink\` value: '${page.data.permalink}')` : ""}, which is a target on the file system that does not include a file extension.
You *probably* want to add a file extension to your permalink so that hosts will know how to correctly serve this file to web browsers. Without a file extension, this file may not be reliably deployed without additional hosting configuration (it wont have a mime type) and may also cause local development issues if you later attempt to write to a subdirectory of the same name.
Learn more: https://v3.11ty.dev/docs/permalinks/#trailing-slashes
This is usually but not *always* an error so if youd like to disable this error message, add \`eleventyAllowMissingExtension: true\` somewhere in the data cascade for this template or use \`eleventyConfig.configureErrorReporting({ allowMissingExtensions: true });\` to disable this feature globally.`);
e.skipOriginalStack = true;
throw e;
}
}
});
}
// TODO move these into TemplateMapTest.js
_testGetAllTags() {
let allTags = {};
for (let map of this.map) {
let tags = map.data.tags;
if (Array.isArray(tags)) {
for (let tag of tags) {
allTags[tag] = true;
}
}
}
return Object.keys(allTags);
}
async _testGetUserConfigCollectionsData() {
let collections = {};
let configCollections = this.userConfig.getCollections();
for (let name in configCollections) {
collections[name] = configCollections[name](this.collection);
debug(`Collection: collections.${name} size: ${collections[name].length}`);
}
return collections;
}
async _testGetTaggedCollectionsData() {
let collections = {};
collections.all = this.collection.getAllSorted();
debug(`Collection: collections.all size: ${collections.all.length}`);
let tags = this._testGetAllTags();
for (let tag of tags) {
collections[tag] = this.collection.getFilteredByTag(tag);
debug(`Collection: collections.${tag} size: ${collections[tag].length}`);
}
return collections;
}
async _testGetAllCollectionsData() {
let collections = {};
let taggedCollections = await this._testGetTaggedCollectionsData();
Object.assign(collections, taggedCollections);
let userConfigCollections = await this._testGetUserConfigCollectionsData();
Object.assign(collections, userConfigCollections);
return collections;
}
async _testGetCollectionsData() {
if (!this.cached) {
await this.cache();
}
return this.collectionsData;
}
}
export default TemplateMap;

339
node_modules/@11ty/eleventy/src/TemplatePassthrough.js generated vendored Normal file
View File

@@ -0,0 +1,339 @@
import util from "node:util";
import path from "node:path";
import fs from "graceful-fs";
import isGlob from "is-glob";
import copy from "@11ty/recursive-copy";
import { TemplatePath } from "@11ty/eleventy-utils";
import debugUtil from "debug";
import EleventyBaseError from "./Errors/EleventyBaseError.js";
import checkPassthroughCopyBehavior from "./Util/PassthroughCopyBehaviorCheck.js";
import ProjectDirectories from "./Util/ProjectDirectories.js";
const fsExists = util.promisify(fs.exists);
const debug = debugUtil("Eleventy:TemplatePassthrough");
class TemplatePassthroughError extends EleventyBaseError {}
class TemplatePassthrough {
#isExistsCache = {};
#isDirectoryCache = {};
constructor(path, eleventyConfig) {
if (!eleventyConfig || eleventyConfig.constructor.name !== "TemplateConfig") {
throw new TemplatePassthroughError(
"Missing `eleventyConfig` or was not an instance of `TemplateConfig`.",
);
}
this.eleventyConfig = eleventyConfig;
this.benchmarks = {
aggregate: this.config.benchmarkManager.get("Aggregate"),
};
this.rawPath = path;
// inputPath is relative to the root of your project and not your Eleventy input directory.
// TODO normalize these with forward slashes
this.inputPath = this.normalizeDirectory(path.inputPath);
this.isInputPathGlob = isGlob(this.inputPath);
this.outputPath = path.outputPath;
this.copyOptions = path.copyOptions; // custom options for recursive-copy
this.isDryRun = false;
this.isIncremental = false;
}
get config() {
return this.eleventyConfig.getConfig();
}
get dirs() {
return this.eleventyConfig.directories;
}
// inputDir is used when stripping from output path in `getOutputPath`
get inputDir() {
return this.dirs.input;
}
get outputDir() {
return this.dirs.output;
}
/* { inputPath, outputPath } though outputPath is *not* the full path: just the output directory */
getPath() {
return this.rawPath;
}
async getOutputPath(inputFileFromGlob) {
let { inputDir, outputDir, outputPath, inputPath } = this;
if (outputPath === true) {
// no explicit target, implied target
if (this.isDirectory(inputPath)) {
let inputRelativePath = TemplatePath.stripLeadingSubPath(
inputFileFromGlob || inputPath,
inputDir,
);
return ProjectDirectories.normalizeDirectory(
TemplatePath.join(outputDir, inputRelativePath),
);
}
return TemplatePath.normalize(
TemplatePath.join(
outputDir,
TemplatePath.stripLeadingSubPath(inputFileFromGlob || inputPath, inputDir),
),
);
}
if (inputFileFromGlob) {
return this.getOutputPathForGlobFile(inputFileFromGlob);
}
// Has explicit target
// Bug when copying incremental file overwriting output directory (and making it a file)
// e.g. public/test.css -> _site
// https://github.com/11ty/eleventy/issues/2278
let fullOutputPath = TemplatePath.normalize(TemplatePath.join(outputDir, outputPath));
if (outputPath === "" || this.isDirectory(inputPath)) {
fullOutputPath = ProjectDirectories.normalizeDirectory(fullOutputPath);
}
// TODO room for improvement here:
if (
!this.isInputPathGlob &&
(await fsExists(inputPath)) &&
!this.isDirectory(inputPath) &&
this.isDirectory(fullOutputPath)
) {
let filename = path.parse(inputPath).base;
return TemplatePath.normalize(TemplatePath.join(fullOutputPath, filename));
}
return fullOutputPath;
}
async getOutputPathForGlobFile(inputFileFromGlob) {
return TemplatePath.join(
await this.getOutputPath(),
TemplatePath.getLastPathSegment(inputFileFromGlob),
);
}
setDryRun(isDryRun) {
this.isDryRun = !!isDryRun;
}
setRunMode(runMode) {
this.runMode = runMode;
}
setIsIncremental(isIncremental) {
this.isIncremental = isIncremental;
}
setFileSystemSearch(fileSystemSearch) {
this.fileSystemSearch = fileSystemSearch;
}
async getFiles(glob) {
debug("Searching for: %o", glob);
let b = this.benchmarks.aggregate.get("Searching the file system (passthrough)");
b.before();
let files = TemplatePath.addLeadingDotSlashArray(
await this.fileSystemSearch.search("passthrough", glob),
);
b.after();
return files;
}
isExists(dir) {
if (this.#isExistsCache[dir] === undefined) {
this.#isExistsCache[dir] = fs.existsSync(dir);
}
return this.#isExistsCache[dir];
}
isDirectory(dir) {
if (this.#isDirectoryCache[dir] === undefined) {
if (isGlob(this.inputPath)) {
this.#isDirectoryCache[dir] = false;
} else if (!this.isExists(dir)) {
this.#isDirectoryCache[dir] = false;
} else if (fs.statSync(dir).isDirectory()) {
this.#isDirectoryCache[dir] = true;
} else {
this.#isDirectoryCache[dir] = false;
}
}
return this.#isDirectoryCache[dir];
}
// dir is guaranteed to exist by context
// dir may not be a directory
normalizeDirectory(dir) {
if (dir && typeof dir === "string") {
if (dir.endsWith(path.sep) || dir.endsWith("/")) {
return dir;
}
// When inputPath is a directory, make sure it has a slash for passthrough copy aliasing
// https://github.com/11ty/eleventy/issues/2709
if (this.isDirectory(dir)) {
return `${dir}/`;
}
}
return dir;
}
// maps input paths to output paths
async getFileMap() {
// TODO VirtualFileSystem candidate
if (!isGlob(this.inputPath) && this.isExists(this.inputPath)) {
return [
{
inputPath: this.inputPath,
outputPath: await this.getOutputPath(),
},
];
}
let paths = [];
// If not directory or file, attempt to get globs
let files = await this.getFiles(this.inputPath);
for (let filePathFromGlob of files) {
paths.push({
inputPath: filePathFromGlob,
outputPath: await this.getOutputPath(filePathFromGlob),
});
}
return paths;
}
/* Types:
* 1. via glob, individual files found
* 2. directory, triggers an event for each file
* 3. individual file
*/
async copy(src, dest, copyOptions) {
if (
!TemplatePath.stripLeadingDotSlash(dest).startsWith(
TemplatePath.stripLeadingDotSlash(this.outputDir),
)
) {
return Promise.reject(
new TemplatePassthroughError(
"Destination is not in the site output directory. Check your passthrough paths.",
),
);
}
let fileCopyCount = 0;
let fileSizeCount = 0;
let map = {};
let b = this.benchmarks.aggregate.get("Passthrough Copy File");
// returns a promise
return copy(src, dest, copyOptions)
.on(copy.events.COPY_FILE_START, (copyOp) => {
// Access to individual files at `copyOp.src`
debug("Copying individual file %o", copyOp.src);
map[copyOp.src] = copyOp.dest;
b.before();
})
.on(copy.events.COPY_FILE_COMPLETE, (copyOp) => {
fileCopyCount++;
fileSizeCount += copyOp.stats.size;
b.after();
})
.then(() => {
return {
count: fileCopyCount,
size: fileSizeCount,
map,
};
});
}
async write() {
if (this.isDryRun) {
return Promise.resolve({
count: 0,
map: {},
});
}
debug("Copying %o", this.inputPath);
let fileMap = await this.getFileMap();
// default options for recursive-copy
// see https://www.npmjs.com/package/recursive-copy#arguments
let copyOptionsDefault = {
overwrite: true, // overwrite output. fails when input is directory (mkdir) and output is file
dot: true, // copy dotfiles
junk: false, // copy cache files like Thumbs.db
results: false,
expand: false, // follow symlinks (matches recursive-copy default)
debug: false, // (matches recursive-copy default)
// Note: `filter` callback function only passes in a relative path, which is unreliable
// See https://github.com/timkendrick/recursive-copy/blob/4c9a8b8a4bf573285e9c4a649a30a2b59ccf441c/lib/copy.js#L59
// e.g. `{ filePaths: [ './img/coolkid.jpg' ], relativePaths: [ '' ] }`
};
let copyOptions = Object.assign(copyOptionsDefault, this.copyOptions);
let promises = fileMap.map((entry) => {
// For-free passthrough copy
if (checkPassthroughCopyBehavior(this.config, this.runMode)) {
let aliasMap = {};
aliasMap[entry.inputPath] = entry.outputPath;
return Promise.resolve({
count: 0,
map: aliasMap,
});
}
// Copy the files (only in build mode)
return this.copy(entry.inputPath, entry.outputPath, copyOptions);
});
// IMPORTANT: this returns an array of promises, does not await for promise to finish
return Promise.all(promises).then(
(results) => {
// collate the count and input/output map results from the array.
let count = 0;
let size = 0;
let map = {};
for (let result of results) {
count += result.count;
size += result.size;
Object.assign(map, result.map);
}
return {
count,
size,
map,
};
},
(err) => {
throw new TemplatePassthroughError(`Error copying passthrough files: ${err.message}`, err);
},
);
}
}
export default TemplatePassthrough;

View File

@@ -0,0 +1,311 @@
import isGlob from "is-glob";
import { TemplatePath } from "@11ty/eleventy-utils";
import debugUtil from "debug";
import EleventyExtensionMap from "./EleventyExtensionMap.js";
import EleventyBaseError from "./Errors/EleventyBaseError.js";
import TemplatePassthrough from "./TemplatePassthrough.js";
import checkPassthroughCopyBehavior from "./Util/PassthroughCopyBehaviorCheck.js";
import { isGlobMatch } from "./Util/GlobMatcher.js";
const debug = debugUtil("Eleventy:TemplatePassthroughManager");
const debugDev = debugUtil("Dev:Eleventy:TemplatePassthroughManager");
class TemplatePassthroughManagerConfigError extends EleventyBaseError {}
class TemplatePassthroughManagerCopyError extends EleventyBaseError {}
class TemplatePassthroughManager {
constructor(eleventyConfig) {
if (!eleventyConfig || eleventyConfig.constructor.name !== "TemplateConfig") {
throw new TemplatePassthroughManagerConfigError("Missing or invalid `config` argument.");
}
this.eleventyConfig = eleventyConfig;
this.config = eleventyConfig.getConfig();
this.reset();
}
reset() {
this.count = 0;
this.size = 0;
this.conflictMap = {};
this.incrementalFile = null;
debug("Resetting counts to 0");
}
set extensionMap(extensionMap) {
this._extensionMap = extensionMap;
}
get extensionMap() {
if (!this._extensionMap) {
this._extensionMap = new EleventyExtensionMap(this.eleventyConfig);
this._extensionMap.setFormats([]);
}
return this._extensionMap;
}
get dirs() {
return this.eleventyConfig.directories;
}
get inputDir() {
return this.dirs.input;
}
get outputDir() {
return this.dirs.output;
}
setDryRun(isDryRun) {
this.isDryRun = !!isDryRun;
}
setRunMode(runMode) {
this.runMode = runMode;
}
setIncrementalFile(path) {
if (path) {
this.incrementalFile = path;
}
}
_normalizePaths(path, outputPath, copyOptions = {}) {
return {
inputPath: TemplatePath.addLeadingDotSlash(path),
outputPath: outputPath ? TemplatePath.stripLeadingDotSlash(outputPath) : true,
copyOptions,
};
}
getConfigPaths() {
let paths = [];
let pathsRaw = this.config.passthroughCopies || {};
debug("`addPassthroughCopy` config API paths: %o", pathsRaw);
for (let [inputPath, { outputPath, copyOptions }] of Object.entries(pathsRaw)) {
paths.push(this._normalizePaths(inputPath, outputPath, copyOptions));
}
debug("`addPassthroughCopy` config API normalized paths: %o", paths);
return paths;
}
getConfigPathGlobs() {
return this.getConfigPaths().map((path) => {
return TemplatePath.convertToRecursiveGlobSync(path.inputPath);
});
}
getNonTemplatePaths(paths) {
let matches = [];
for (let path of paths) {
if (!this.extensionMap.hasEngine(path)) {
matches.push(path);
}
}
return matches;
}
getCopyCount() {
return this.count;
}
getCopySize() {
return this.size;
}
setFileSystemSearch(fileSystemSearch) {
this.fileSystemSearch = fileSystemSearch;
}
getTemplatePassthroughForPath(path, isIncremental = false) {
let inst = new TemplatePassthrough(path, this.eleventyConfig);
inst.setFileSystemSearch(this.fileSystemSearch);
inst.setIsIncremental(isIncremental);
inst.setDryRun(this.isDryRun);
inst.setRunMode(this.runMode);
return inst;
}
async copyPassthrough(pass) {
if (!(pass instanceof TemplatePassthrough)) {
throw new TemplatePassthroughManagerCopyError(
"copyPassthrough expects an instance of TemplatePassthrough",
);
}
let { inputPath } = pass.getPath();
// TODO https://github.com/11ty/eleventy/issues/2452
// De-dupe both the input and output paired together to avoid the case
// where an input/output pair has been added via multiple passthrough methods (glob, file suffix, etc)
// Probably start with the `filter` callback in recursive-copy but it only passes relative paths
// See the note in TemplatePassthrough.js->write()
// Also note that `recursive-copy` handles repeated overwrite copy to the same destination just fine.
// e.g. `for(let j=0, k=1000; j<k; j++) { copy("coolkid.jpg", "_site/coolkid.jpg"); }`
// Eventually well want to move all of this to use Nodes fs.cp, which is experimental and only on Node 16+
return pass.write().then(
({ size, count, map }) => {
for (let src in map) {
let dest = map[src];
if (this.conflictMap[dest]) {
if (src !== this.conflictMap[dest]) {
throw new TemplatePassthroughManagerCopyError(
`Multiple passthrough copy files are trying to write to the same output file (${dest}). ${src} and ${this.conflictMap[dest]}`,
);
} else {
// Multiple entries from the same source
debug(
"A passthrough copy entry (%o) caused the same file (%o) to be copied more than once to the output (%o). This is atomically safe but a waste of build resources.",
inputPath,
src,
dest,
);
}
}
debugDev("Adding %o to passthrough copy conflict map, from %o", dest, src);
this.conflictMap[dest] = src;
}
if (pass.isDryRun) {
// We dont count the skipped files as we need to iterate over them
debug(
"Skipped %o (either from --dryrun or --incremental or for-free passthrough copy)",
inputPath,
);
} else {
if (count) {
this.count += count;
this.size += size;
debug("Copied %o (%d files, %d size)", inputPath, count || 0, size || 0);
} else {
debug("Skipped copying %o (emulated passthrough copy)", inputPath);
}
}
return {
count,
map,
};
},
function (e) {
return Promise.reject(
new TemplatePassthroughManagerCopyError(`Having trouble copying '${inputPath}'`, e),
);
},
);
}
isPassthroughCopyFile(paths, changedFile) {
if (!changedFile) {
return false;
}
// passthrough copy by non-matching engine extension (via templateFormats)
for (let path of paths) {
if (path === changedFile && !this.extensionMap.hasEngine(path)) {
return true;
}
}
for (let path of this.getConfigPaths()) {
if (TemplatePath.startsWithSubPath(changedFile, path.inputPath)) {
return path;
}
if (changedFile && isGlob(path.inputPath) && isGlobMatch(changedFile, [path.inputPath])) {
return path;
}
}
return false;
}
getAllNormalizedPaths(paths) {
if (this.incrementalFile) {
let isPassthrough = this.isPassthroughCopyFile(paths, this.incrementalFile);
if (isPassthrough) {
if (isPassthrough.outputPath) {
return [this._normalizePaths(this.incrementalFile, isPassthrough.outputPath)];
}
return [this._normalizePaths(this.incrementalFile)];
}
// Fixes https://github.com/11ty/eleventy/issues/2491
if (!checkPassthroughCopyBehavior(this.config, this.runMode)) {
return [];
}
}
let normalizedPaths = this.getConfigPaths();
if (debug.enabled) {
for (let path of normalizedPaths) {
debug("TemplatePassthrough copying from config: %o", path);
}
}
if (paths?.length) {
let passthroughPaths = this.getNonTemplatePaths(paths);
for (let path of passthroughPaths) {
let normalizedPath = this._normalizePaths(path);
debug(
`TemplatePassthrough copying from non-matching file extension: ${normalizedPath.inputPath}`,
);
normalizedPaths.push(normalizedPath);
}
}
return normalizedPaths;
}
// keys: output
// values: input
getAliasesFromPassthroughResults(result) {
let entries = {};
for (let entry of result) {
for (let src in entry.map) {
let dest = TemplatePath.stripLeadingSubPath(entry.map[src], this.outputDir);
entries["/" + encodeURI(dest)] = src;
}
}
return entries;
}
// Performance note: these can actually take a fair bit of time, but arent a
// bottleneck to eleventy. The copies are performed asynchronously and dont affect eleventy
// write times in a significant way.
async copyAll(templateExtensionPaths) {
debug("TemplatePassthrough copy started.");
let normalizedPaths = this.getAllNormalizedPaths(templateExtensionPaths);
let passthroughs = normalizedPaths.map((path) => {
// if incrementalFile is set but it isnt a passthrough copy, normalizedPaths will be an empty array
let isIncremental = !!this.incrementalFile;
return this.getTemplatePassthroughForPath(path, isIncremental);
});
let promises = passthroughs.map((pass) => this.copyPassthrough(pass));
return Promise.all(promises).then(async (results) => {
let aliases = this.getAliasesFromPassthroughResults(results);
await this.config.events.emit("eleventy.passthrough", {
map: aliases,
});
debug(`TemplatePassthrough copy finished. Current count: ${this.count} (size: ${this.size})`);
return results;
});
}
}
export default TemplatePassthroughManager;

189
node_modules/@11ty/eleventy/src/TemplatePermalink.js generated vendored Normal file
View File

@@ -0,0 +1,189 @@
import path from "node:path";
import normalize from "normalize-path";
import { TemplatePath, isPlainObject } from "@11ty/eleventy-utils";
class TemplatePermalink {
// `link` with template syntax should have already been rendered in Template.js
constructor(link, extraSubdir) {
let isLinkAnObject = isPlainObject(link);
this._isRendered = true;
this._writeToFileSystem = true;
let buildLink;
if (isLinkAnObject) {
if ("build" in link) {
buildLink = link.build;
}
// find the first string key
for (let key in link) {
if (typeof key !== "string") {
continue;
}
break;
}
} else {
buildLink = link;
}
// permalink: false and permalink: build: false
if (typeof buildLink === "boolean") {
if (buildLink === false) {
this._writeToFileSystem = false;
} else {
throw new Error(
`\`permalink: ${
isLinkAnObject ? "build: " : ""
}true\` is not a supported feature in Eleventy. Did you mean \`permalink: ${
isLinkAnObject ? "build: " : ""
}false\`?`,
);
}
} else if (buildLink) {
this.buildLink = buildLink;
}
if (isLinkAnObject) {
// default if permalink is an Object but does not have a `build` prop
if (!("build" in link)) {
this._writeToFileSystem = false;
this._isRendered = false;
}
}
this.extraPaginationSubdir = extraSubdir || "";
}
setUrlTransforms(transforms) {
this._urlTransforms = transforms;
}
get urlTransforms() {
return this._urlTransforms || [];
}
_addDefaultLinkFilename(link) {
return link + (link.slice(-1) === "/" ? "index.html" : "");
}
toOutputPath() {
if (!this.buildLink) {
// empty or false
return false;
}
let cleanLink = this._addDefaultLinkFilename(this.buildLink);
let parsed = path.parse(cleanLink);
return TemplatePath.join(parsed.dir, this.extraPaginationSubdir, parsed.base);
}
// Used in url transforms feature
static getUrlStem(original) {
let subject = original;
if (original.endsWith(".html")) {
subject = original.slice(0, -1 * ".html".length);
}
return TemplatePermalink.normalizePathToUrl(subject);
}
static normalizePathToUrl(original) {
let compare = original || "";
let needleHtml = "/index.html";
let needleBareTrailingSlash = "/index/";
let needleBare = "/index";
if (compare.endsWith(needleHtml)) {
return compare.slice(0, compare.length - needleHtml.length) + "/";
} else if (compare.endsWith(needleBareTrailingSlash)) {
return compare.slice(0, compare.length - needleBareTrailingSlash.length) + "/";
} else if (compare.endsWith(needleBare)) {
return compare.slice(0, compare.length - needleBare.length) + "/";
}
return original;
}
// This method is used to generate the `page.url` variable.
// remove all index.htmls from links
// index.html becomes /
// test/index.html becomes test/
toHref() {
if (!this.buildLink) {
// empty or false
return false;
}
let transformedLink = this.toOutputPath();
let original = (transformedLink.charAt(0) !== "/" ? "/" : "") + transformedLink;
let normalized = TemplatePermalink.normalizePathToUrl(original) || "";
for (let transform of this.urlTransforms) {
original =
transform({
url: normalized,
urlStem: TemplatePermalink.getUrlStem(original),
}) ?? original;
}
return TemplatePermalink.normalizePathToUrl(original);
}
toPath(outputDir) {
if (!this.buildLink) {
return false;
}
let uri = this.toOutputPath();
if (uri === false) {
return false;
}
return normalize(outputDir + "/" + uri);
}
toPathFromRoot() {
if (!this.buildLink) {
return false;
}
let uri = this.toOutputPath();
if (uri === false) {
return false;
}
return normalize(uri);
}
static _hasDuplicateFolder(dir, base) {
let folders = dir.split("/");
if (!folders[folders.length - 1]) {
folders.pop();
}
return folders[folders.length - 1] === base;
}
static generate(dir, filenameNoExt, extraSubdir, fileExtension = "html") {
let path;
if (fileExtension === "html") {
let hasDupeFolder = TemplatePermalink._hasDuplicateFolder(dir, filenameNoExt);
path =
(dir ? dir + "/" : "") +
(filenameNoExt !== "index" && !hasDupeFolder ? filenameNoExt + "/" : "") +
"index" +
".html";
} else {
path = (dir ? dir + "/" : "") + filenameNoExt + "." + fileExtension;
}
return new TemplatePermalink(path, extraSubdir);
}
}
export default TemplatePermalink;

294
node_modules/@11ty/eleventy/src/TemplateRender.js generated vendored Normal file
View File

@@ -0,0 +1,294 @@
import EleventyBaseError from "./Errors/EleventyBaseError.js";
import EleventyExtensionMap from "./EleventyExtensionMap.js";
import TemplateEngineManager from "./Engines/TemplateEngineManager.js";
import CustomEngine from "./Engines/Custom.js";
// import debugUtil from "debug";
// const debug = debugUtil("Eleventy:TemplateRender");
class TemplateRenderConfigError extends EleventyBaseError {}
class TemplateRenderUnknownEngineError extends EleventyBaseError {}
// works with full path names or short engine name
class TemplateRender {
constructor(tmplPath, config) {
if (!tmplPath) {
throw new Error(`TemplateRender requires a tmplPath argument, instead of ${tmplPath}`);
}
if (!config) {
throw new TemplateRenderConfigError("Missing `config` argument.");
}
if (config.constructor.name === "TemplateConfig") {
this.eleventyConfig = config;
this.config = config.getConfig();
} else {
throw new Error("Third argument to TemplateRender must be a TemplateConfig instance.");
}
this.engineNameOrPath = tmplPath;
this.parseMarkdownWith = this.config.markdownTemplateEngine;
this.parseHtmlWith = this.config.htmlTemplateEngine;
}
get dirs() {
return this.eleventyConfig.directories;
}
get inputDir() {
return this.dirs.input;
}
get includesDir() {
return this.dirs.includes;
}
/* Backwards compat */
getIncludesDir() {
return this.includesDir;
}
get config() {
return this._config;
}
set config(config) {
this._config = config;
}
set extensionMap(extensionMap) {
this._extensionMap = extensionMap;
}
get extensionMap() {
if (!this._extensionMap) {
this._extensionMap = new EleventyExtensionMap(this.eleventyConfig);
this._extensionMap.setFormats([]);
}
return this._extensionMap;
}
async getEngineByName(name) {
let engine = await this.extensionMap.engineManager.getEngine(name, this.extensionMap);
engine.eleventyConfig = this.eleventyConfig;
return engine;
}
// Runs once per template
async init(engineNameOrPath) {
let name = engineNameOrPath || this.engineNameOrPath;
this.extensionMap.config = this.eleventyConfig;
let extensionEntry = this.extensionMap.getExtensionEntry(name);
let engineName = extensionEntry?.aliasKey || extensionEntry?.key;
if (TemplateEngineManager.isSimpleAlias(extensionEntry)) {
engineName = extensionEntry?.key;
}
this._engineName = engineName;
if (!extensionEntry || !this._engineName) {
throw new TemplateRenderUnknownEngineError(
`Unknown engine for ${name} (supported extensions: ${this.extensionMap.getReadableFileExtensions()})`,
);
}
this._engine = await this.getEngineByName(this._engineName);
if (this.useMarkdown === undefined) {
this.setUseMarkdown(this._engineName === "md");
}
}
get engineName() {
if (!this._engineName) {
throw new Error("TemplateRender needs a call to the init() method.");
}
return this._engineName;
}
get engine() {
if (!this._engine) {
throw new Error("TemplateRender needs a call to the init() method.");
}
return this._engine;
}
static parseEngineOverrides(engineName) {
if (typeof (engineName || "") !== "string") {
throw new Error("Expected String passed to parseEngineOverrides. Received: " + engineName);
}
let overlappingEngineWarningCount = 0;
let engines = [];
let uniqueLookup = {};
let usingMarkdown = false;
(engineName || "")
.split(",")
.map((name) => {
return name.toLowerCase().trim();
})
.forEach((name) => {
// html is assumed (treated as plaintext by the system)
if (!name || name === "html") {
return;
}
if (name === "md") {
usingMarkdown = true;
return;
}
if (!uniqueLookup[name]) {
engines.push(name);
uniqueLookup[name] = true;
// we already short circuit md and html types above
overlappingEngineWarningCount++;
}
});
if (overlappingEngineWarningCount > 1) {
throw new Error(
`Dont mix multiple templating engines in your front matter overrides (exceptions for HTML and Markdown). You used: ${engineName}`,
);
}
// markdown should always be first
if (usingMarkdown) {
engines.unshift("md");
}
return engines;
}
// used for error logging and console output.
getReadableEnginesList() {
return this.getReadableEnginesListDifferingFromFileExtension() || this.engineName;
}
getReadableEnginesListDifferingFromFileExtension() {
let keyFromFilename = this.extensionMap.getKey(this.engineNameOrPath);
if (this.engine instanceof CustomEngine) {
if (
this.engine.entry &&
this.engine.entry.name &&
keyFromFilename !== this.engine.entry.name
) {
return this.engine.entry.name;
} else {
// We dont have a name for it so we return nothing so we dont misreport (per #2386)
return;
}
}
if (this.engineName === "md" && this.useMarkdown && this.parseMarkdownWith) {
return this.parseMarkdownWith;
}
if (this.engineName === "html" && this.parseHtmlWith) {
return this.parseHtmlWith;
}
// templateEngineOverride in play and template language differs from file extension
if (keyFromFilename !== this.engineName) {
return this.engineName;
}
}
// TODO templateEngineOverride
getPreprocessorEngine() {
if (this.engineName === "md" && this.parseMarkdownWith) {
return this.parseMarkdownWith;
}
if (this.engineName === "html" && this.parseHtmlWith) {
return this.parseHtmlWith;
}
return this.extensionMap.getKey(this.engineNameOrPath);
}
// We pass in templateEngineOverride here because it isnt yet applied to templateRender
getEnginesList(engineOverride) {
if (engineOverride) {
let engines = TemplateRender.parseEngineOverrides(engineOverride).reverse();
return engines.join(",");
}
if (this.engineName === "md" && this.useMarkdown && this.parseMarkdownWith) {
return `${this.parseMarkdownWith},md`;
}
if (this.engineName === "html" && this.parseHtmlWith) {
return this.parseHtmlWith;
}
// templateEngineOverride in play
return this.extensionMap.getKey(this.engineNameOrPath);
}
async setEngineOverride(engineName, bypassMarkdown) {
let engines = TemplateRender.parseEngineOverrides(engineName);
// when overriding, Template Engines with HTML will instead use the Template Engine as primary and output HTML
// So any HTML engine usage here will never use a preprocessor templating engine.
this.setHtmlEngine(false);
if (!engines.length) {
await this.init("html");
return;
}
await this.init(engines[0]);
let usingMarkdown = engines[0] === "md" && !bypassMarkdown;
this.setUseMarkdown(usingMarkdown);
if (usingMarkdown) {
// false means only parse markdown and not with a preprocessor template engine
this.setMarkdownEngine(engines.length > 1 ? engines[1] : false);
}
}
getEngineName() {
return this.engineName;
}
isEngine(engine) {
return this.engineName === engine;
}
setUseMarkdown(useMarkdown) {
this.useMarkdown = !!useMarkdown;
}
// this is only called for templateEngineOverride
setMarkdownEngine(markdownEngine) {
this.parseMarkdownWith = markdownEngine;
}
// this is only called for templateEngineOverride
setHtmlEngine(htmlEngineName) {
this.parseHtmlWith = htmlEngineName;
}
async _testRender(str, data) {
return this.engine._testRender(str, data);
}
async getCompiledTemplate(str) {
// TODO refactor better, move into TemplateEngine logic
if (this.engineName === "md") {
return this.engine.compile(
str,
this.engineNameOrPath,
this.parseMarkdownWith,
!this.useMarkdown,
);
} else if (this.engineName === "html") {
return this.engine.compile(str, this.engineNameOrPath, this.parseHtmlWith);
} else {
return this.engine.compile(str, this.engineNameOrPath);
}
}
}
export default TemplateRender;

518
node_modules/@11ty/eleventy/src/TemplateWriter.js generated vendored Executable file
View File

@@ -0,0 +1,518 @@
import { TemplatePath } from "@11ty/eleventy-utils";
import debugUtil from "debug";
import Template from "./Template.js";
import TemplateMap from "./TemplateMap.js";
import EleventyFiles from "./EleventyFiles.js";
import EleventyExtensionMap from "./EleventyExtensionMap.js";
import EleventyBaseError from "./Errors/EleventyBaseError.js";
import { EleventyErrorHandler } from "./Errors/EleventyErrorHandler.js";
import EleventyErrorUtil from "./Errors/EleventyErrorUtil.js";
import FileSystemSearch from "./FileSystemSearch.js";
import ConsoleLogger from "./Util/ConsoleLogger.js";
const debug = debugUtil("Eleventy:TemplateWriter");
class TemplateWriterMissingConfigArgError extends EleventyBaseError {}
class EleventyPassthroughCopyError extends EleventyBaseError {}
class EleventyTemplateError extends EleventyBaseError {}
class TemplateWriter {
#eleventyFiles;
constructor(
templateFormats, // TODO remove this, see `get eleventyFiles` first
templateData,
eleventyConfig,
) {
if (!eleventyConfig) {
throw new TemplateWriterMissingConfigArgError("Missing config argument.");
}
this.eleventyConfig = eleventyConfig;
this.config = eleventyConfig.getConfig();
this.userConfig = eleventyConfig.userConfig;
this.templateFormats = templateFormats;
this.templateData = templateData;
this.isVerbose = true;
this.isDryRun = false;
this.writeCount = 0;
this.renderCount = 0;
this.skippedCount = 0;
this.isRunInitialBuild = true;
this._templatePathCache = new Map();
}
get dirs() {
return this.eleventyConfig.directories;
}
get inputDir() {
return this.dirs.input;
}
get outputDir() {
return this.dirs.output;
}
get templateFormats() {
return this._templateFormats;
}
set templateFormats(value) {
this._templateFormats = value;
}
/* Getter for error handler */
get errorHandler() {
if (!this._errorHandler) {
this._errorHandler = new EleventyErrorHandler();
this._errorHandler.isVerbose = this.verboseMode;
this._errorHandler.logger = this.logger;
}
return this._errorHandler;
}
/* Getter for Logger */
get logger() {
if (!this._logger) {
this._logger = new ConsoleLogger();
this._logger.isVerbose = this.verboseMode;
}
return this._logger;
}
/* Setter for Logger */
set logger(logger) {
this._logger = logger;
}
/* For testing */
overrideConfig(config) {
this.config = config;
}
restart() {
this.writeCount = 0;
this.renderCount = 0;
this.skippedCount = 0;
}
set extensionMap(extensionMap) {
this._extensionMap = extensionMap;
}
get extensionMap() {
if (!this._extensionMap) {
this._extensionMap = new EleventyExtensionMap(this.eleventyConfig);
this._extensionMap.setFormats(this.templateFormats);
}
return this._extensionMap;
}
setEleventyFiles(eleventyFiles) {
this.#eleventyFiles = eleventyFiles;
}
set eleventyFiles(eleventyFiles) {
this.#eleventyFiles = eleventyFiles;
}
get eleventyFiles() {
// usually Eleventy.js will setEleventyFiles with the EleventyFiles manager
if (!this.#eleventyFiles) {
// if not, we can create one (used only by tests)
this.#eleventyFiles = new EleventyFiles(this.templateFormats, this.eleventyConfig);
this.#eleventyFiles.setFileSystemSearch(new FileSystemSearch());
this.#eleventyFiles.init();
}
return this.#eleventyFiles;
}
async _getAllPaths() {
// this is now cached upstream by FileSystemSearch
return this.eleventyFiles.getFiles();
}
_createTemplate(path, to = "fs") {
let tmpl = this._templatePathCache.get(path);
let wasCached = false;
if (tmpl) {
wasCached = true;
// Update config for https://github.com/11ty/eleventy/issues/3468
tmpl.eleventyConfig = this.eleventyConfig;
// TODO reset other constructor things here like inputDir/outputDir/extensionMap/
tmpl.setTemplateData(this.templateData);
} else {
tmpl = new Template(path, this.templateData, this.extensionMap, this.eleventyConfig);
tmpl.setOutputFormat(to);
tmpl.logger = this.logger;
this._templatePathCache.set(path, tmpl);
/*
* Sample filter: arg str, return pretty HTML string
* function(str) {
* return pretty(str, { ocd: true });
* }
*/
tmpl.setTransforms(this.config.transforms);
for (let linterName in this.config.linters) {
let linter = this.config.linters[linterName];
if (typeof linter === "function") {
tmpl.addLinter(linter);
}
}
}
tmpl.setDryRun(this.isDryRun);
tmpl.setIsVerbose(this.isVerbose);
tmpl.reset();
return {
template: tmpl,
wasCached,
};
}
// incrementalFileShape is `template` or `copy` (for passthrough file copy)
async _addToTemplateMapIncrementalBuild(incrementalFileShape, paths, to = "fs") {
// Render overrides are only used when `--ignore-initial` is in play and an initial build is not run
let ignoreInitialBuild = !this.isRunInitialBuild;
let secondOrderRelevantLookup = {};
let templates = [];
let promises = [];
for (let path of paths) {
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).
templates.push(tmpl);
// This must happen before data is generated for the incremental file only
if (incrementalFileShape === "template" && tmpl.inputPath === this.incrementalFile) {
tmpl.resetCaches();
}
// IMPORTANT: This is where the data is first generated for the template
promises.push(this.templateMap.add(tmpl));
}
// Important to set up template dependency relationships first
await Promise.all(promises);
// Delete incremental file from the dependency graph so we get fresh entries!
// This _must_ happen before any additions, the other ones are in Custom.js and GlobalDependencyMap.js (from the eleventy.layouts Event)
this.config.uses.resetNode(this.incrementalFile);
// write new template relationships to the global dependency graph for next time
this.templateMap.addAllToGlobalDependencyGraph();
// Always disable render for --ignore-initial
if (ignoreInitialBuild) {
for (let tmpl of templates) {
tmpl.setRenderableOverride(false); // disable render
}
return;
}
for (let tmpl of templates) {
if (incrementalFileShape === "template" && tmpl.inputPath === this.incrementalFile) {
tmpl.setRenderableOverride(undefined); // unset, probably render
} else if (
tmpl.isFileRelevantToThisTemplate(this.incrementalFile, {
isFullTemplate: incrementalFileShape === "template",
})
) {
// changed file is used by template
// template uses the changed file
tmpl.setRenderableOverride(undefined); // unset, probably render
secondOrderRelevantLookup[tmpl.inputPath] = true;
} else if (this.config.uses.isFileUsedBy(this.incrementalFile, tmpl.inputPath)) {
// changed file uses this template
tmpl.setRenderableOverride("optional");
} else {
// For incremental, always disable render on irrelevant templates
tmpl.setRenderableOverride(false); // disable render
}
}
let secondOrderRelevantArray = this.config.uses
.getTemplatesRelevantToTemplateList(Object.keys(secondOrderRelevantLookup))
.map((entry) => TemplatePath.addLeadingDotSlash(entry));
let secondOrderTemplates = Object.fromEntries(
Object.entries(secondOrderRelevantArray).map(([index, value]) => [value, true]),
);
for (let tmpl of templates) {
// second order templates must also be rendered if not yet already rendered at least once and available in cache.
if (secondOrderTemplates[tmpl.inputPath]) {
if (tmpl.isRenderableDisabled()) {
tmpl.setRenderableOverride("optional");
}
}
}
// Order of templates does not matter here, theyre reordered later based on dependencies in TemplateMap.js
for (let tmpl of templates) {
if (incrementalFileShape === "template" && tmpl.inputPath === this.incrementalFile) {
// Cache is reset above (to invalidate data cache at the right time)
tmpl.setDryRunViaIncremental(false);
} else if (!tmpl.isRenderableDisabled() && !tmpl.isRenderableOptional()) {
// Related to the template but not the template (reset the render cache, not the read cache)
tmpl.resetCaches({
data: true,
render: true,
});
tmpl.setDryRunViaIncremental(false);
} else {
// During incremental we only reset the data cache for non-matching templates, see https://github.com/11ty/eleventy/issues/2710
// Keep caches for read/render
tmpl.resetCaches({
data: true,
});
tmpl.setDryRunViaIncremental(true);
this.skippedCount++;
}
}
}
_addToTemplateMapFullBuild(paths, to = "fs") {
if (this.incrementalFile) {
return [];
}
let ignoreInitialBuild = !this.isRunInitialBuild;
let promises = [];
for (let path of paths) {
let { template: tmpl, wasCached } = this._createTemplate(path, to);
// Render overrides are only used when `--ignore-initial` is in play and an initial build is not run
if (ignoreInitialBuild) {
tmpl.setRenderableOverride(false); // disable render
} else {
tmpl.setRenderableOverride(undefined); // unset, render
}
if (wasCached) {
tmpl.resetCaches();
}
// IMPORTANT: This is where the data is first generated for the template
promises.push(this.templateMap.add(tmpl));
}
return Promise.all(promises);
}
async _addToTemplateMap(paths, to = "fs") {
let incrementalFileShape = this.eleventyFiles.getFileShape(paths, this.incrementalFile);
// Filter out passthrough copy files
paths = paths.filter((path) => {
if (!this.extensionMap.hasEngine(path)) {
return false;
}
if (incrementalFileShape === "copy") {
this.skippedCount++;
// Filters out templates if the incremental file is a passthrough copy file
return false;
}
return true;
});
// Full Build
if (!this.incrementalFile) {
let ret = await this._addToTemplateMapFullBuild(paths, to);
// write new template relationships to the global dependency graph for next time
this.templateMap.addAllToGlobalDependencyGraph();
return ret;
}
// Top level async to get at the promises returned.
return await this._addToTemplateMapIncrementalBuild(incrementalFileShape, paths, to);
}
async _createTemplateMap(paths, to) {
this.templateMap = new TemplateMap(this.eleventyConfig);
await this._addToTemplateMap(paths, to);
await this.templateMap.cache();
return this.templateMap;
}
async _generateTemplate(mapEntry, to) {
let tmpl = mapEntry.template;
return tmpl.generateMapEntry(mapEntry, to).then((pages) => {
this.renderCount += tmpl.getRenderCount();
this.writeCount += tmpl.getWriteCount();
return pages;
});
}
async writePassthroughCopy(templateExtensionPaths) {
let passthroughManager = this.eleventyFiles.getPassthroughManager();
passthroughManager.setIncrementalFile(this.incrementalFile);
return passthroughManager.copyAll(templateExtensionPaths).catch((e) => {
this.errorHandler.warn(e, "Error with passthrough copy");
return Promise.reject(new EleventyPassthroughCopyError("Having trouble copying", e));
});
}
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 = [];
for (let mapEntry of this.templateMap.getMap()) {
promises.push(
this._generateTemplate(mapEntry, to).catch(function (e) {
// Premature templateContent in layout render, this also happens in
// TemplateMap.populateContentDataInMap for non-layout content
if (EleventyErrorUtil.isPrematureTemplateContentError(e)) {
usedTemplateContentTooEarlyMap.push(mapEntry);
} else {
let outputPaths = `"${mapEntry._pages.map((page) => page.outputPath).join(`", "`)}"`;
return Promise.reject(
new EleventyTemplateError(
`Having trouble writing to ${outputPaths} from "${mapEntry.inputPath}"`,
e,
),
);
}
}),
);
}
for (let mapEntry of usedTemplateContentTooEarlyMap) {
promises.push(
this._generateTemplate(mapEntry, to).catch(function (e) {
return Promise.reject(
new EleventyTemplateError(
`Having trouble writing to (second pass) "${mapEntry.outputPath}" from "${mapEntry.inputPath}"`,
e,
),
);
}),
);
}
return promises;
}
async write() {
let paths = await this._getAllPaths();
let promises = [];
// The ordering here is important to destructuring in Eleventy->_watch
promises.push(this.writePassthroughCopy(paths));
promises.push(...(await this.generateTemplates(paths)));
return Promise.all(promises).then(
([passthroughCopyResults, ...templateResults]) => {
return {
passthroughCopy: passthroughCopyResults,
// New in 3.0: flatten and filter out falsy templates
templates: templateResults.flat().filter(Boolean),
};
},
(e) => {
return Promise.reject(e);
},
);
}
// Passthrough copy not supported in JSON output.
// --incremental not supported in JSON output.
async getJSON(to = "json") {
let paths = await this._getAllPaths();
let promises = await this.generateTemplates(paths, to);
return Promise.all(promises).then(
(templateResults) => {
return {
// New in 3.0: flatten and filter out falsy templates
templates: templateResults.flat().filter(Boolean),
};
},
(e) => {
return Promise.reject(e);
},
);
}
setVerboseOutput(isVerbose) {
this.isVerbose = isVerbose;
this.errorHandler.isVerbose = isVerbose;
}
setDryRun(isDryRun) {
this.isDryRun = !!isDryRun;
this.eleventyFiles.getPassthroughManager().setDryRun(this.isDryRun);
}
setRunInitialBuild(runInitialBuild) {
this.isRunInitialBuild = runInitialBuild;
}
setIncrementalBuild(isIncremental) {
this.isIncremental = isIncremental;
}
setIncrementalFile(incrementalFile) {
this.incrementalFile = incrementalFile;
}
resetIncrementalFile() {
this.incrementalFile = null;
}
getCopyCount() {
return this.eleventyFiles.getPassthroughManager().getCopyCount();
}
getCopySize() {
return this.eleventyFiles.getPassthroughManager().getCopySize();
}
getRenderCount() {
return this.renderCount;
}
getWriteCount() {
return this.writeCount;
}
getSkippedCount() {
return this.skippedCount;
}
get caches() {
return ["_templatePathCache"];
}
}
export default TemplateWriter;

1292
node_modules/@11ty/eleventy/src/UserConfig.js generated vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,75 @@
import { EventEmitter } from "node:events";
/**
* This class emits events asynchronously.
* It can be used for time measurements during a build.
*/
class AsyncEventEmitter extends EventEmitter {
#handlerMode = "parallel";
// TypeScript slop
constructor(...args) {
super(...args);
}
/**
* @param {string} type - The event name to emit.
* @param {...*} args - Additional arguments that get passed to listeners.
* @returns {Promise} - Promise resolves once all listeners were invoked
*/
/** @ts-expect-error */
async emit(type, ...args) {
let listeners = this.listeners(type);
if (listeners.length === 0) {
return [];
}
if (this.#handlerMode == "sequential") {
const result = [];
for (const listener of listeners) {
const returnValue = await listener.apply(this, args);
result.push(returnValue);
}
return result;
} else {
return Promise.all(
listeners.map((listener) => {
return listener.apply(this, args);
}),
);
}
}
/**
* @param {string} type - The event name to emit.
* @param {...*} args - Additional lazy-executed function arguments that get passed to listeners.
* @returns {Promise} - Promise resolves once all listeners were invoked
*/
async emitLazy(type, ...args) {
let listeners = this.listeners(type);
if (listeners.length === 0) {
return [];
}
let argsMap = [];
for (let arg of args) {
if (typeof arg === "function") {
let r = arg();
if (r instanceof Promise) {
r = await r;
}
argsMap.push(r);
} else {
argsMap.push(arg);
}
}
return this.emit.call(this, type, ...argsMap);
}
setHandlerMode(mode) {
this.#handlerMode = mode;
}
}
export default AsyncEventEmitter;

55
node_modules/@11ty/eleventy/src/Util/Compatibility.js generated vendored Normal file
View File

@@ -0,0 +1,55 @@
import semver from "semver";
import debugUtil from "debug";
import { getEleventyPackageJson, getWorkingProjectPackageJson } from "./ImportJsonSync.js";
const pkg = getEleventyPackageJson();
const debug = debugUtil("Eleventy:Compatibility");
// Used in user config versionCheck method.
class Compatibility {
static NORMALIZE_PRERELEASE_REGEX = /-canary\b/g;
constructor(compatibleRange) {
this.compatibleRange = Compatibility.getCompatibilityValue(compatibleRange);
}
static normalizeIdentifier(identifier) {
return identifier.replace(Compatibility.NORMALIZE_PRERELEASE_REGEX, "-alpha");
}
static getCompatibilityValue(compatibleRange) {
if (compatibleRange) {
return compatibleRange;
}
try {
// fetch from projects package.json
let projectPackageJson = getWorkingProjectPackageJson();
return projectPackageJson["11ty"]?.compatibility;
} catch (e) {
debug("Could not find a project package.json for compatibility version check: %O", e);
return; // do nothing, no compatibility information to check
}
}
isCompatible() {
return Compatibility.satisfies(pkg.version, this.compatibleRange);
}
static satisfies(version, compatibleRange) {
return semver.satisfies(
Compatibility.normalizeIdentifier(version),
Compatibility.normalizeIdentifier(compatibleRange),
{
includePrerelease: true,
},
);
}
getErrorMessage() {
return `We found Eleventy version '${pkg.version}' which does not meet the required version range: '${this.compatibleRange}'. Use \`npm install @11ty/eleventy\` to upgrade your local project to the latest Eleventy version (or \`npm install @11ty/eleventy -g\` to upgrade the globally installed version).`;
}
}
export default Compatibility;

126
node_modules/@11ty/eleventy/src/Util/ConsoleLogger.js generated vendored Normal file
View File

@@ -0,0 +1,126 @@
import { Readable } from "node:stream";
import chalk from "kleur";
import debugUtil from "debug";
const debug = debugUtil("Eleventy:Logger");
/**
* Logger implementation that logs to STDOUT.
* @typedef {'error'|'log'|'warn'|'info'} LogType
*/
class ConsoleLogger {
/** @type {boolean} */
#isVerbose = true;
/** @type {boolean} */
#isChalkEnabled = true;
/** @type {object|boolean|undefined} */
#logger;
constructor() {
this.outputStream = new Readable({
read() {},
});
}
get isVerbose() {
return this.#isVerbose;
}
set isVerbose(verbose) {
this.#isVerbose = !!verbose;
}
get isChalkEnabled() {
return this.#isChalkEnabled;
}
set isChalkEnabled(enabled) {
this.#isChalkEnabled = !!enabled;
}
overrideLogger(logger) {
this.#logger = logger;
}
get logger() {
return this.#logger || console;
}
/** @param {string} msg */
log(msg) {
this.message(msg);
}
/**
* @typedef LogOptions
* @property {string} message
* @property {string=} prefix
* @property {LogType=} type
* @property {string=} color
* @property {boolean=} force
* @param {LogOptions} options
*/
logWithOptions({ message, type, prefix, color, force }) {
this.message(message, type, color, force, prefix);
}
/** @param {string} msg */
forceLog(msg) {
this.message(msg, undefined, undefined, true);
}
/** @param {string} msg */
info(msg) {
this.message(msg, "warn", "blue");
}
/** @param {string} msg */
warn(msg) {
this.message(msg, "warn", "yellow");
}
/** @param {string} msg */
error(msg) {
this.message(msg, "error", "red");
}
/** @param {string} msg */
toStream(msg) {
this.outputStream.push(msg);
}
closeStream() {
this.outputStream.push(null);
return this.outputStream;
}
/**
* Formats the message to log.
*
* @param {string} message - The raw message to log.
* @param {LogType} [type='log'] - The error level to log.
* @param {string|undefined} [chalkColor=undefined] - Color name or falsy to disable
* @param {boolean} [forceToConsole=false] - Enforce a log on console instead of specified target.
*/
message(
message,
type = "log",
chalkColor = undefined,
forceToConsole = false,
prefix = "[11ty]",
) {
if (!forceToConsole && (!this.isVerbose || process.env.DEBUG)) {
debug(message);
} else if (this.#logger !== false) {
message = `${chalk.gray(prefix)} ${message.split("\n").join(`\n${chalk.gray(prefix)} `)}`;
if (chalkColor && this.isChalkEnabled) {
this.logger[type](chalk[chalkColor](message));
} else {
this.logger[type](message);
}
}
}
}
export default ConsoleLogger;

View File

@@ -0,0 +1,24 @@
import spawn from "cross-spawn";
function getGitFirstAddedTimeStamp(filePath) {
return (
parseInt(
spawn
.sync(
"git",
// Formats https://www.git-scm.com/docs/git-log#_pretty_formats
// %at author date, UNIX timestamp
["log", "--diff-filter=A", "--follow", "-1", "--format=%at", filePath],
)
.stdout.toString("utf-8"),
) * 1000
);
}
// return a Date
export default function (inputPath) {
let timestamp = getGitFirstAddedTimeStamp(inputPath);
if (timestamp) {
return new Date(timestamp);
}
}

View File

@@ -0,0 +1,28 @@
import spawn from "cross-spawn";
/* Thank you to Vuepress!
* https://github.com/vuejs/vuepress/blob/89440ce552675859189ed4ab254ce19c4bba5447/packages/%40vuepress/plugin-last-updated/index.js
* MIT licensed: https://github.com/vuejs/vuepress/blob/89440ce552675859189ed4ab254ce19c4bba5447/LICENSE
*/
function getGitLastUpdatedTimeStamp(filePath) {
return (
parseInt(
spawn
.sync(
"git",
// Formats https://www.git-scm.com/docs/git-log#_pretty_formats
// %at author date, UNIX timestamp
["log", "-1", "--format=%at", filePath],
)
.stdout.toString("utf-8"),
) * 1000
);
}
// return a Date
export default function (inputPath) {
let timestamp = getGitLastUpdatedTimeStamp(inputPath);
if (timestamp) {
return new Date(timestamp);
}
}

9
node_modules/@11ty/eleventy/src/Util/DirContains.js generated vendored Normal file
View File

@@ -0,0 +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))) {
return true;
}
return false;
}

51
node_modules/@11ty/eleventy/src/Util/EsmResolver.js generated vendored Normal file
View File

@@ -0,0 +1,51 @@
import debugUtil from "debug";
const debug = debugUtil("Eleventy:EsmResolver");
let lastModifiedPaths = new Map();
export async function initialize({ port }) {
// From `eleventy.importCacheReset` event in Require.js
port.on("message", ({ path, newDate }) => {
lastModifiedPaths.set(path, newDate);
});
}
// Fixes issue https://github.com/11ty/eleventy/issues/3270
// Docs: https://nodejs.org/docs/latest/api/module.html#resolvespecifier-context-nextresolve
export async function resolve(specifier, context, nextResolve) {
try {
// Not a relative import and not a file import
// Or from node_modules (perhaps better to check if the specifier is in the project directory instead)
if (
(!specifier.startsWith("../") &&
!specifier.startsWith("./") &&
!specifier.startsWith("file:")) ||
context.parentURL.includes("/node_modules/")
) {
return nextResolve(specifier);
}
let fileUrl = new URL(specifier, context.parentURL);
if (fileUrl.searchParams.has("_cache_bust")) {
// already is cache busted outside resolver (wider compat, url was changed prior to import, probably in Require.js)
return nextResolve(specifier);
}
let absolutePath = fileUrl.pathname;
// Bust the import cache if this is a recently modified file
if (lastModifiedPaths.has(absolutePath)) {
fileUrl.search = ""; // delete existing searchparams
fileUrl.searchParams.set("_cache_bust", lastModifiedPaths.get(absolutePath));
debug("Cache busting %o to %o", specifier, fileUrl.toString());
return nextResolve(fileUrl.toString());
}
} catch (e) {
debug("EsmResolver Error parsing specifier (%o): %o", specifier, e);
}
return nextResolve(specifier);
}
// export async function load(url, context, nextLoad) {
// }

26
node_modules/@11ty/eleventy/src/Util/EventBusUtil.js generated vendored Normal file
View File

@@ -0,0 +1,26 @@
import eventBus from "../EventBus.js";
import debugUtil from "debug";
const debug = debugUtil("Eleventy:EventBus");
class EventBusUtil {
// Used for non-global subscriptions that will blow away the previous listener
static soloOn(name, callback) {
eventBus.off(name, callback);
eventBus.on(name, callback);
}
static resetForConfig() {
this.debug();
debug("Config reset (removing eleventy.templateModified listeners).");
eventBus.removeAllListeners("eleventy.templateModified");
}
static debug() {
for (let name of eventBus.eventNames()) {
debug("Listeners for %o: %o", name, eventBus.listenerCount(name));
}
}
}
export default EventBusUtil;

82
node_modules/@11ty/eleventy/src/Util/ExistsCache.js generated vendored Normal file
View File

@@ -0,0 +1,82 @@
import fs from "graceful-fs";
import PathNormalizer from "./PathNormalizer.js";
// Checks both files and directories
class ExistsCache {
constructor() {
this._cache = new Map();
this.lookupCount = 0;
}
setDirectoryCheck(check) {
this.cacheDirectories = !!check;
}
get size() {
return this._cache.size;
}
parentsDoNotExist(path) {
if (!this.cacheDirectories) {
return false;
}
let allPaths = PathNormalizer.getAllPaths(path).filter((entry) => entry !== path);
for (let parentPath of allPaths) {
if (this._cache.has(parentPath)) {
if (this._cache.get(parentPath) === false) {
return true; // we know this parent doesnt exist
}
}
}
// if youve made it here: we dont know if the parents exist or not
return false;
}
has(path) {
return this._cache.has(path);
}
exists(path) {
path = PathNormalizer.fullNormalization(path);
let exists = this._cache.get(path);
if (this.parentsDoNotExist(path)) {
// we dont need to check if a parent directory does not exist
exists = false;
} else if (!this.has(path)) {
exists = fs.existsSync(path);
this.markExistsWithParentDirectories(path, exists);
this.lookupCount++;
}
return exists;
}
// if a file exists, we can mark the parent directories as existing also
// if a file does not exist, we dont know if the parent directories exist or not (yet)
markExistsWithParentDirectories(path, exists = true) {
path = PathNormalizer.fullNormalization(path);
if (!this.cacheDirectories || !exists) {
this.markExists(path, false, true);
return;
}
let paths = PathNormalizer.getAllPaths(path);
for (let fullpath of paths) {
this.markExists(fullpath, true, true);
}
}
markExists(path, exists = true, alreadyNormalized = false) {
if (!alreadyNormalized) {
path = PathNormalizer.fullNormalization(path);
}
this._cache.set(path, !!exists);
}
}
export default ExistsCache;

19
node_modules/@11ty/eleventy/src/Util/FilePathUtil.js generated vendored Normal file
View File

@@ -0,0 +1,19 @@
class FilePathUtil {
static isMatchingExtension(filepath, fileExtension) {
if (!fileExtension) {
return false;
}
if (!(fileExtension || "").startsWith(".")) {
fileExtension = "." + fileExtension;
}
return filepath.endsWith(fileExtension);
}
static getFileExtension(filepath) {
return (filepath || "").split(".").pop();
}
}
export { FilePathUtil };

View File

@@ -0,0 +1,30 @@
import EleventyBaseError from "../Errors/EleventyBaseError.js";
class JavaScriptInvalidDataFormatError extends EleventyBaseError {}
export default async function (inst, inputPath, key = "data", options = {}) {
let { mixins, isObjectRequired } = Object.assign(
{
mixins: {},
isObjectRequired: true,
},
options,
);
if (inst && key in inst) {
// get extra data from `data` method,
// either as a function or getter or object literal
let result = await (typeof inst[key] === "function"
? Object.keys(mixins).length > 0
? inst[key].call(mixins)
: inst[key]()
: inst[key]);
if (isObjectRequired && typeof result !== "object") {
throw new JavaScriptInvalidDataFormatError(
`Invalid data format returned from ${inputPath}: typeof ${typeof result}`,
);
}
return result;
}
}

21
node_modules/@11ty/eleventy/src/Util/GlobMatcher.js generated vendored Normal file
View File

@@ -0,0 +1,21 @@
import micromatch from "micromatch";
import { TemplatePath } from "@11ty/eleventy-utils";
function isGlobMatch(filepath, globs = [], options = undefined) {
if (!filepath || !Array.isArray(globs) || globs.length === 0) {
return false;
}
let inputPath = TemplatePath.stripLeadingDotSlash(filepath);
let opts = Object.assign(
{
dot: true,
nocase: true, // insensitive
},
options,
);
return micromatch.isMatch(inputPath, globs, opts);
}
export { isGlobMatch };

158
node_modules/@11ty/eleventy/src/Util/HtmlTransformer.js generated vendored Normal file
View File

@@ -0,0 +1,158 @@
import posthtml from "posthtml";
import urls from "@11ty/posthtml-urls";
import { FilePathUtil } from "./FilePathUtil.js";
class HtmlTransformer {
// feature test for Eleventy Bundle Plugin
static SUPPORTS_PLUGINS_ENABLED_CALLBACK = true;
constructor() {
// execution order is important (not order of addition/object key order)
this.callbacks = {};
this.posthtmlProcessOptions = {};
this.plugins = {};
}
get aggregateBench() {
if (!this.userConfig) {
throw new Error("Internal error: Missing `userConfig` in HtmlTransformer.");
}
return this.userConfig.benchmarkManager.get("Aggregate");
}
setUserConfig(config) {
this.userConfig = config;
}
static prioritySort(a, b) {
if (b.priority > a.priority) {
return 1;
}
if (a.priority > b.priority) {
return -1;
}
return 0;
}
// context is important as it is used in html base plugin for page specific URL
static _getPosthtmlInstance(callbacks = [], plugins = [], context = {}) {
let inst = posthtml();
// already sorted by priority when added
for (let { fn: plugin, options } of plugins) {
inst.use(plugin(Object.assign({}, context, options)));
}
// Run the built-ins last
if (callbacks.length > 0) {
inst.use(
urls({
eachURL: (url) => {
// and: attrName, tagName
for (let { fn: callback } of callbacks) {
// already sorted by priority when added
url = callback.call(context, url);
}
return url;
},
}),
);
}
return inst;
}
_add(extensions, addType, value, options = {}) {
options = Object.assign(
{
priority: 0,
},
options,
);
let extensionsArray = (extensions || "").split(",");
for (let ext of extensionsArray) {
let target = this[addType];
if (!target[ext]) {
target[ext] = [];
}
target[ext].push({
fn: value, // callback or plugin
priority: options.priority,
enabled: options.enabled || (() => true),
options: options.pluginOptions,
});
target[ext].sort(HtmlTransformer.prioritySort);
}
}
addPosthtmlPlugin(extensions, plugin, options = {}) {
this._add(extensions, "plugins", plugin, options);
}
addUrlTransform(extensions, callback, options = {}) {
this._add(extensions, "callbacks", callback, options);
}
setPosthtmlProcessOptions(options) {
Object.assign(this.posthtmlProcessOptions, options);
}
isTransformable(extension, context) {
return (
this.getCallbacks(extension, context).length > 0 || this.getPlugins(extension).length > 0
);
}
getCallbacks(extension, context) {
let callbacks = this.callbacks[extension] || [];
return callbacks.filter(({ enabled }) => {
if (!enabled || typeof enabled !== "function") {
return true;
}
return enabled(context);
});
}
getPlugins(extension) {
let plugins = this.plugins[extension] || [];
return plugins.filter(({ enabled }) => {
if (!enabled || typeof enabled !== "function") {
return true;
}
return enabled();
});
}
static async transformStandalone(content, callback, posthtmlProcessOptions = {}) {
let posthtmlInstance = this._getPosthtmlInstance([
{
fn: callback,
enabled: () => true,
},
]);
let result = await posthtmlInstance.process(content, posthtmlProcessOptions);
return result.html;
}
async transformContent(outputPath, content, context) {
let extension = FilePathUtil.getFileExtension(outputPath);
if (!this.isTransformable(extension, context)) {
return content;
}
let bench = this.aggregateBench.get(`Transforming \`${extension}\` with posthtml`);
bench.before();
let callbacks = this.getCallbacks(extension, context);
let plugins = this.getPlugins(extension);
let posthtmlInstance = HtmlTransformer._getPosthtmlInstance(callbacks, plugins, context);
let result = await posthtmlInstance.process(content, this.posthtmlProcessOptions);
bench.after();
return result.html;
}
}
export { HtmlTransformer };

64
node_modules/@11ty/eleventy/src/Util/ImportJsonSync.js generated vendored Normal file
View File

@@ -0,0 +1,64 @@
import fs from "node:fs";
import { createRequire } from "node:module";
import debugUtil from "debug";
import { TemplatePath } from "@11ty/eleventy-utils";
import { normalizeFilePathInEleventyPackage } from "./Require.js";
const debug = debugUtil("Eleventy:ImportJsonSync");
const require = createRequire(import.meta.url);
function findFilePathInParentDirs(dir, filename) {
// `package.json` searches look in parent dirs:
// https://docs.npmjs.com/cli/v7/configuring-npm/folders#more-information
// Fixes issue #3178, limited to working dir paths only
let workingDir = TemplatePath.getWorkingDir();
let allDirs = TemplatePath.getAllDirs(dir).filter((entry) => entry.startsWith(workingDir));
for (let dir of allDirs) {
let newPath = TemplatePath.join(dir, filename);
if (fs.existsSync(newPath)) {
debug("Found %o searching parent directories at: %o", filename, dir);
return newPath;
}
}
}
function importJsonSync(filePath) {
if (!filePath.endsWith(".json")) {
throw new Error(`importJsonSync expects a .json file extension (received: ${filePath})`);
}
try {
// TODO clear require.cache when these files change
return require(filePath);
} catch (e) {
debug("Attempted to import %o, received this error: %o", filePath, e);
// if file does not exist, return nothing
}
}
function getEleventyPackageJson() {
let filePath = normalizeFilePathInEleventyPackage("package.json");
return importJsonSync(filePath);
}
function getModulePackageJson(dir) {
let filePath = findFilePathInParentDirs(TemplatePath.absolutePath(dir), "package.json");
return importJsonSync(filePath);
}
function getWorkingProjectPackageJson() {
let dir = TemplatePath.absolutePath(TemplatePath.getWorkingDir());
let filePath = findFilePathInParentDirs(dir, "package.json");
return importJsonSync(filePath);
}
export {
importJsonSync,
findFilePathInParentDirs,
getEleventyPackageJson,
getModulePackageJson,
getWorkingProjectPackageJson,
};

View File

@@ -0,0 +1,5 @@
const ComparisonAsyncFunction = (async () => {}).constructor;
export default function isAsyncFunction(fn) {
return fn instanceof ComparisonAsyncFunction;
}

View File

@@ -0,0 +1,55 @@
import dependencyTree from "@11ty/dependency-tree";
import { find } from "@11ty/dependency-tree-esm";
import { TemplatePath } from "@11ty/eleventy-utils";
import EleventyBaseError from "../Errors/EleventyBaseError.js";
class JavaScriptDependencies {
static getErrorMessage(file, type) {
return `A problem was encountered looking for JavaScript dependencies in ${type} file: ${file}. This only affects --watch and --serve behavior and does not affect your build.`;
}
static async getDependencies(inputFiles, isProjectUsingEsm) {
let depSet = new Set();
// TODO does this need to work with aliasing? what other JS extensions will have deps?
let commonJsFiles = inputFiles.filter(
(file) => (!isProjectUsingEsm && file.endsWith(".js")) || file.endsWith(".cjs"),
);
for (let file of commonJsFiles) {
try {
let modules = dependencyTree(file, {
nodeModuleNames: "exclude",
allowNotFound: true,
}).map((dependency) => {
return TemplatePath.addLeadingDotSlash(TemplatePath.relativePath(dependency));
});
for (let dep of modules) {
depSet.add(dep);
}
} catch (e) {
throw new EleventyBaseError(this.getErrorMessage(file, "CommonJS"), e);
}
}
let esmFiles = inputFiles.filter(
(file) => (isProjectUsingEsm && file.endsWith(".js")) || file.endsWith(".mjs"),
);
for (let file of esmFiles) {
try {
let modules = await find(file);
for (let dep of modules) {
depSet.add(dep);
}
} catch (e) {
throw new EleventyBaseError(this.getErrorMessage(file, "ESM"), e);
}
}
return Array.from(depSet).sort();
}
}
export default JavaScriptDependencies;

View File

@@ -0,0 +1,26 @@
export default function (callback, options = {}) {
let { bench, name } = options;
let cache = new Map();
return (...args) => {
// Only supports single-arg functions for now.
if (args.filter(Boolean).length > 1) {
bench?.get(`(count) ${name} Not valid for memoize`).incrementCount();
return callback(...args);
}
let [cacheKey] = args;
if (!cache.has(cacheKey)) {
cache.set(cacheKey, callback(...args));
bench?.get(`(count) ${name} memoize miss`).incrementCount();
return cache.get(cacheKey);
}
bench?.get(`(count) ${name} memoize hit`).incrementCount();
return cache.get(cacheKey);
};
}

View File

@@ -0,0 +1,20 @@
import { isPlainObject } from "@11ty/eleventy-utils";
// via https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze
function DeepFreeze(obj, topLevelExceptions) {
for (let name of Reflect.ownKeys(obj)) {
if ((topLevelExceptions || []).find((key) => key === name)) {
continue;
}
const value = obj[name];
if (isPlainObject(value)) {
DeepFreeze(value);
}
}
return Object.freeze(obj);
}
export { DeepFreeze };

View File

@@ -0,0 +1,9 @@
export default function objectFilter(obj, callback) {
let newObject = {};
for (let [key, value] of Object.entries(obj || {})) {
if (callback(value, key)) {
newObject[key] = value;
}
}
return newObject;
}

View File

@@ -0,0 +1,111 @@
import types from "node:util/types";
import debugUtil from "debug";
import { isPlainObject } from "@11ty/eleventy-utils";
const debug = debugUtil("Dev:Eleventy:Proxy");
function wrapObject(target, fallback) {
if (Object.isFrozen(target)) {
return target;
}
return new Proxy(target, {
getOwnPropertyDescriptor(target, prop) {
let ret;
if (Reflect.has(target, prop)) {
ret = Reflect.getOwnPropertyDescriptor(target, prop);
} else if (Reflect.has(fallback, prop)) {
ret = Reflect.getOwnPropertyDescriptor(fallback, prop);
}
return ret;
},
has(target, prop) {
if (Reflect.has(target, prop)) {
return true;
}
return Reflect.has(fallback, prop);
},
ownKeys(target) {
let s = new Set();
for (let k of Reflect.ownKeys(target)) {
s.add(k);
}
if (isPlainObject(fallback)) {
for (let k of Reflect.ownKeys(fallback)) {
s.add(k);
}
}
return Array.from(s);
},
get(target, prop) {
debug("handler:get", prop);
let value = Reflect.get(target, prop);
if (Reflect.has(target, prop)) {
// Already proxied
if (types.isProxy(value)) {
return value;
}
if (isPlainObject(value) && Reflect.has(fallback, prop)) {
if (Object.isFrozen(value)) {
return value;
}
let ret = wrapObject(value, Reflect.get(fallback, prop));
debug("handler:get (primary, object)", prop);
return ret;
}
debug("handler:get (primary)", prop);
return value;
}
// Does not exist in primary
if (Reflect.has(fallback, prop)) {
// fallback has prop
let fallbackValue = Reflect.get(fallback, prop);
if (isPlainObject(fallbackValue)) {
if (Object.isFrozen(fallbackValue)) {
return fallbackValue;
}
debug("handler:get (fallback, object)", prop);
// set empty object on primary
let emptyObject = {};
Reflect.set(target, prop, emptyObject);
return wrapObject(emptyObject, fallbackValue);
}
debug("handler:get (fallback)", prop);
return fallbackValue;
}
// primary *and* fallback do _not_ have prop
debug("handler:get (not on primary or fallback)", prop);
return value;
},
set(target, prop, value) {
debug("handler:set", prop);
return Reflect.set(target, prop, value);
},
});
}
function ProxyWrap(target, fallback) {
if (!isPlainObject(target) || !isPlainObject(fallback)) {
throw new Error("ProxyWrap expects objects for both the target and fallback");
}
return wrapObject(target, fallback);
}
export { ProxyWrap };

View File

@@ -0,0 +1 @@
export default {};

View File

@@ -0,0 +1,136 @@
class Sortable {
constructor() {
this.isSortAscending = true;
this.isSortNumeric = false;
this.items = [];
this._dirty = true;
this.sortFunctionStringMap = {
"A-Z": "sortFunctionAscending",
"Z-A": "sortFunctionDescending",
"0-9": "sortFunctionNumericAscending",
"9-0": "sortFunctionNumericDescending",
};
}
get length() {
return this.items.length;
}
add(item) {
this._dirty = true;
this.items.push(item);
}
sort(sortFunction) {
if (!sortFunction) {
sortFunction = this.getSortFunction();
} else if (typeof sortFunction === "string") {
let key = sortFunction;
let name;
if (key in this.sortFunctionStringMap) {
name = this.sortFunctionStringMap[key];
}
if (Sortable[name]) {
sortFunction = Sortable[name];
} else {
throw new Error(
`Invalid String argument for sort(). Received \`${key}\`. Valid values: ${Object.keys(
this.sortFunctionStringMap,
)}`,
);
}
}
return this.items.slice().sort(sortFunction);
}
sortAscending() {
return this.sort(this.getSortFunctionAscending());
}
sortDescending() {
return this.sort(this.getSortFunctionDescending());
}
setSortDescending(isDescending = true) {
this.isSortAscending = !isDescending;
}
setSortAscending(isAscending = true) {
this.isSortAscending = isAscending;
}
setSortNumeric(isNumeric) {
this.isSortNumeric = isNumeric;
}
/* Sort functions */
static sortFunctionNumericAscending(a, b) {
return a - b;
}
static sortFunctionNumericDescending(a, b) {
return b - a;
}
static sortFunctionAscending(a, b) {
if (a > b) {
return 1;
} else if (a < b) {
return -1;
}
return 0;
}
static sortFunctionDescending(a, b) {
return Sortable.sortFunctionAscending(b, a);
}
static sortFunctionAlphabeticAscending(a, b) {
return Sortable.sortFunctionAscending(a, b);
}
static sortFunctionAlphabeticDescending(a, b) {
return Sortable.sortFunctionAscending(b, a);
}
static sortFunctionDate(mapA, mapB) {
return Sortable.sortFunctionNumericAscending(mapA.date.getTime(), mapB.date.getTime());
}
static sortFunctionDateInputPath(mapA, mapB) {
let sortDate = Sortable.sortFunctionNumericAscending(mapA.date.getTime(), mapB.date.getTime());
if (sortDate === 0) {
return Sortable.sortFunctionAlphabeticAscending(mapA.inputPath, mapB.inputPath);
}
return sortDate;
}
/* End sort functions */
getSortFunction() {
if (this.isSortAscending) {
return this.getSortFunctionAscending();
} else {
return this.getSortFunctionDescending();
}
}
getSortFunctionAscending() {
if (this.isSortNumeric) {
return Sortable.sortFunctionNumericAscending;
} else {
return Sortable.sortFunctionAlphabeticAscending;
}
}
getSortFunctionDescending() {
if (this.isSortNumeric) {
return Sortable.sortFunctionNumericDescending;
} else {
return Sortable.sortFunctionAlphabeticDescending;
}
}
}
export default Sortable;

View File

@@ -0,0 +1,3 @@
export default function Unique(arr) {
return Array.from(new Set(arr));
}

View File

@@ -0,0 +1,16 @@
function isUsingEleventyDevServer(config) {
return (
!config.serverOptions.module || config.serverOptions.module === "@11ty/eleventy-dev-server"
);
}
// Config opt-out via serverPassthroughCopyBehavior
// False when other server is used
// False when runMode is "build" or "watch"
export default function (config, runMode) {
return (
config.serverPassthroughCopyBehavior === "passthrough" &&
isUsingEleventyDevServer(config) &&
runMode === "serve"
);
}

60
node_modules/@11ty/eleventy/src/Util/PathNormalizer.js generated vendored Normal file
View File

@@ -0,0 +1,60 @@
import path from "node:path";
import { fileURLToPath } from "node:url";
import { TemplatePath } from "@11ty/eleventy-utils";
class PathNormalizer {
static getParts(inputPath) {
if (!inputPath) {
return [];
}
let separator = "/";
if (inputPath.includes(path.sep)) {
separator = path.sep;
}
return inputPath.split(separator).filter((entry) => entry !== ".");
}
// order is important here: the top-most directory returns first
// array of file and all parent directories
static getAllPaths(inputPath) {
let parts = this.getParts(inputPath);
let allPaths = [];
let fullpath = "";
for (let part of parts) {
fullpath += (fullpath.length > 0 ? "/" : "") + part;
allPaths.push(fullpath);
}
return allPaths;
}
static normalizeSeperator(inputPath) {
if (!inputPath) {
return inputPath;
}
return inputPath.split(path.sep).join("/");
}
static fullNormalization(inputPath) {
if (typeof inputPath !== "string") {
return inputPath;
}
// Fix file:///Users/ or file:///C:/ paths passed in
if (inputPath.startsWith("file://")) {
inputPath = fileURLToPath(inputPath);
}
// Paths should not be absolute (we convert absolute paths to relative)
// Paths should not have a leading dot slash
// Paths should always be `/` independent of OS path separator
return TemplatePath.stripLeadingDotSlash(
this.normalizeSeperator(TemplatePath.relativePath(inputPath)),
);
}
}
export default PathNormalizer;

21
node_modules/@11ty/eleventy/src/Util/PathPrefixer.js generated vendored Normal file
View File

@@ -0,0 +1,21 @@
import path from "node:path";
import PathNormalizer from "./PathNormalizer.js";
class PathPrefixer {
static normalizePathPrefix(pathPrefix) {
if (pathPrefix) {
// add leading / (for browsersync), see #1454
// path.join uses \\ for Windows so we split and rejoin
return PathPrefixer.joinUrlParts("/", pathPrefix);
}
return "/";
}
static joinUrlParts(...parts) {
return PathNormalizer.normalizeSeperator(path.join(...parts));
}
}
export default PathPrefixer;

3
node_modules/@11ty/eleventy/src/Util/Pluralize.js generated vendored Normal file
View File

@@ -0,0 +1,3 @@
export default function (count, singleWord, pluralWord) {
return count === 1 ? singleWord : pluralWord;
}

View File

@@ -0,0 +1,344 @@
import fs from "node:fs";
import path from "node:path";
import { TemplatePath } from "@11ty/eleventy-utils";
import isGlob from "is-glob";
/* Directories internally should always use *nix forward slashes */
class ProjectDirectories {
static defaults = {
input: "./",
data: "./_data/", // Relative to input directory
includes: "./_includes/", // Relative to input directory
layouts: "./_layouts/", // Relative to input directory
output: "./_site/",
};
// no updates allowed, input/output set via CLI
#frozen = false;
#raw = {};
#dirs = {};
inputFile = undefined;
inputGlob = undefined;
// Add leading dot slash
// Use forward slashes
static normalizePath(fileOrDir) {
return TemplatePath.standardizeFilePath(fileOrDir);
}
// Must be a directory
// Always include a trailing slash
static normalizeDirectory(dir) {
return this.addTrailingSlash(this.normalizePath(dir));
}
normalizeDirectoryPathRelativeToInputDirectory(filePath) {
return ProjectDirectories.normalizeDirectory(path.join(this.input, filePath));
}
static addTrailingSlash(path) {
if (path.slice(-1) === "/") {
return path;
}
return path + "/";
}
// If input/output are set via CLI, they take precedence over all other configuration values.
freeze() {
this.#frozen = true;
}
setViaConfigObject(configDirs = {}) {
// input must come last
let inputChanged = false;
if (
configDirs.input &&
ProjectDirectories.normalizeDirectory(configDirs.input) !== this.input
) {
this.#setInputRaw(configDirs.input);
inputChanged = true;
}
// If falsy or an empty string, the current directory is used.
if (configDirs.output !== undefined) {
if (ProjectDirectories.normalizeDirectory(configDirs.output) !== this.output) {
this.setOutput(configDirs.output);
}
}
// Input relative directory, if falsy or an empty string, inputDir is used!
// Always set if input changed, e.g. input is `src` and data is `../_data` (resulting in `./_data`) we still want to set data to this new value
if (configDirs.data !== undefined) {
if (
inputChanged ||
this.normalizeDirectoryPathRelativeToInputDirectory(configDirs.data || "") !== this.data
) {
this.setData(configDirs.data);
}
}
// Input relative directory, if falsy or an empty string, inputDir is used!
if (configDirs.includes !== undefined) {
if (
inputChanged ||
this.normalizeDirectoryPathRelativeToInputDirectory(configDirs.includes || "") !==
this.includes
) {
this.setIncludes(configDirs.includes);
}
}
// Input relative directory, if falsy or an empty string, inputDir is used!
if (configDirs.layouts !== undefined) {
if (
inputChanged ||
this.normalizeDirectoryPathRelativeToInputDirectory(configDirs.layouts || "") !==
this.layouts
) {
this.setLayouts(configDirs.layouts);
}
}
if (inputChanged) {
this.updateInputDependencies();
}
}
updateInputDependencies() {
// raw first, fall back to Eleventy defaults if not yet set
this.setData(this.#raw.data ?? ProjectDirectories.defaults.data);
this.setIncludes(this.#raw.includes ?? ProjectDirectories.defaults.includes);
// Should not include this if not explicitly opted-in
if (this.#raw.layouts !== undefined) {
this.setLayouts(this.#raw.layouts ?? ProjectDirectories.defaults.layouts);
}
}
/* Relative to project root, must exist */
#setInputRaw(dirOrFile, inputDir = undefined) {
// is frozen and was defined previously
if (this.#frozen && this.#raw.input !== undefined) {
return;
}
this.#raw.input = dirOrFile;
if (!dirOrFile) {
// input must exist if inputDir is not set.
return;
}
// Input has to exist (assumed glob if it does not exist)
let inputExists = fs.existsSync(dirOrFile);
let inputExistsAndIsDirectory = inputExists && fs.statSync(dirOrFile).isDirectory();
if (inputExistsAndIsDirectory) {
// is not a file or glob
this.#dirs.input = ProjectDirectories.normalizeDirectory(dirOrFile);
} else {
if (inputExists) {
this.inputFile = ProjectDirectories.normalizePath(dirOrFile);
} else {
if (!isGlob(dirOrFile)) {
throw new Error(
`The "${dirOrFile}" \`input\` parameter (directory or file path) must exist on the file system (unless detected as a glob by the \`is-glob\` package)`,
);
}
this.inputGlob = dirOrFile;
}
// Explicit Eleventy option for inputDir
if (inputDir) {
// Changed in 3.0: must exist
if (!fs.existsSync(inputDir)) {
throw new Error("Directory must exist (via inputDir option to Eleventy constructor).");
}
this.#dirs.input = ProjectDirectories.normalizeDirectory(inputDir);
} else {
// the input directory is implied to be the parent directory of the
// file, unless inputDir is explicitly specified (via Eleventy constructor `options`)
this.#dirs.input = ProjectDirectories.normalizeDirectory(
TemplatePath.getDirFromFilePath(dirOrFile), // works with globs
);
}
}
}
setInput(dirOrFile, inputDir = undefined) {
this.#setInputRaw(dirOrFile, inputDir); // does not update
this.updateInputDependencies();
}
/* Relative to input dir */
setIncludes(dir) {
if (dir !== undefined) {
// falsy or an empty string is valid (falls back to input dir)
this.#raw.includes = dir;
this.#dirs.includes = ProjectDirectories.normalizeDirectory(
TemplatePath.join(this.input, dir || ""),
);
}
}
/* Relative to input dir */
/* Optional */
setLayouts(dir) {
if (dir !== undefined) {
// falsy or an empty string is valid (falls back to input dir)
this.#raw.layouts = dir;
this.#dirs.layouts = ProjectDirectories.normalizeDirectory(
TemplatePath.join(this.input, dir || ""),
);
}
}
/* Relative to input dir */
setData(dir) {
if (dir !== undefined) {
// falsy or an empty string is valid (falls back to input dir)
// TODO must exist if specified
this.#raw.data = dir;
this.#dirs.data = ProjectDirectories.normalizeDirectory(
TemplatePath.join(this.input, dir || ""),
);
}
}
/* Relative to project root */
setOutput(dir) {
// is frozen and was defined previously
if (this.#frozen && this.#raw.output !== undefined) {
return;
}
if (dir !== undefined) {
this.#raw.output = dir;
this.#dirs.output = ProjectDirectories.normalizeDirectory(dir || "");
}
}
get input() {
return this.#dirs.input || ProjectDirectories.defaults.input;
}
get data() {
return this.#dirs.data || ProjectDirectories.defaults.data;
}
get includes() {
return this.#dirs.includes || ProjectDirectories.defaults.includes;
}
get layouts() {
// explicit opt-in, no fallback.
return this.#dirs.layouts;
}
get output() {
return this.#dirs.output || ProjectDirectories.defaults.output;
}
isTemplateFile(filePath) {
let inputPath = this.getInputPath(filePath);
if (this.layouts && inputPath.startsWith(this.layouts)) {
return false;
}
if (inputPath.startsWith(this.includes)) {
return false;
}
return inputPath.startsWith(this.input);
}
// for a hypothetical template file
getInputPath(filePathRelativeToInputDir) {
// TODO change ~/ to project root dir
return TemplatePath.addLeadingDotSlash(
TemplatePath.join(this.input, TemplatePath.standardizeFilePath(filePathRelativeToInputDir)),
);
}
// Inverse of getInputPath
// Removes input dir from path
getInputPathRelativeToInputDirectory(filePathRelativeToInputDir) {
let inputDir = TemplatePath.addLeadingDotSlash(TemplatePath.join(this.input));
// No leading dot slash
return TemplatePath.stripLeadingSubPath(filePathRelativeToInputDir, inputDir);
}
// for a hypothetical Eleventy layout file
getLayoutPath(filePathRelativeToLayoutDir) {
return TemplatePath.addLeadingDotSlash(
TemplatePath.join(
this.layouts || this.includes,
TemplatePath.standardizeFilePath(filePathRelativeToLayoutDir),
),
);
}
// Removes layout dir from path
getLayoutPathRelativeToInputDirectory(filePathRelativeToLayoutDir) {
let layoutPath = this.getLayoutPath(filePathRelativeToLayoutDir);
let inputDir = TemplatePath.addLeadingDotSlash(TemplatePath.join(this.input));
// No leading dot slash
return TemplatePath.stripLeadingSubPath(layoutPath, inputDir);
}
getProjectPath(filePath) {
return TemplatePath.addLeadingDotSlash(
TemplatePath.join(".", TemplatePath.standardizeFilePath(filePath)),
);
}
// Access the data without being able to set the data.
getUserspaceInstance() {
let d = this;
return {
get input() {
return d.input;
},
get inputFile() {
return d.inputFile;
},
get inputGlob() {
return d.inputGlob;
},
get data() {
return d.data;
},
get includes() {
return d.includes;
},
get layouts() {
return d.layouts;
},
get output() {
return d.output;
},
};
}
toString() {
return {
input: this.input,
inputFile: this.inputFile,
inputGlob: this.inputGlob,
data: this.data,
includes: this.includes,
layouts: this.layouts,
output: this.output,
};
}
}
export default ProjectDirectories;

View File

@@ -0,0 +1,134 @@
import debugUtil from "debug";
const debug = debugUtil("Eleventy:Util:ProjectTemplateFormats");
class ProjectTemplateFormats {
#useAll = {};
#raw = {};
#values = {}; // Set objects
static union(...sets) {
let s = new Set();
for (let set of sets) {
if (!set || typeof set[Symbol.iterator] !== "function") {
continue;
}
for (let v of set) {
s.add(v);
}
}
return s;
}
#normalize(formats) {
if (Array.isArray(formats)) {
formats = "" + formats.join(",");
}
if (typeof formats !== "string") {
throw new Error(
`Invalid formats (expect String, Array) passed to ProjectTemplateFormats->normalize: ${formats}`,
);
}
let final = new Set();
for (let format of formats.split(",")) {
format = format.trim();
if (format && format !== "*") {
final.add(format);
}
}
return final;
}
isWildcard() {
return this.#useAll.cli || this.#useAll.config || false;
}
/** @returns {boolean} */
#isUseAll(rawFormats) {
if (rawFormats === "") {
return false;
}
if (typeof rawFormats === "string") {
rawFormats = rawFormats.split(",");
}
if (Array.isArray(rawFormats)) {
return rawFormats.find((entry) => entry === "*") !== undefined;
}
return false;
}
// 3.x Breaking: "" now means no formats. In 2.x and prior it meant "*"
setViaCommandLine(formats) {
if (formats === undefined) {
return;
}
this.#useAll.cli = this.#isUseAll(formats);
this.#raw.cli = formats;
this.#values.cli = this.#normalize(formats);
}
// 3.x Breaking: "" now means no formats—in 2.x and prior it meant "*"
// 3.x Adds support for comma separated string—in 2.x this required an Array
setViaConfig(formats) {
if (formats === undefined) {
return;
}
// "*" is supported
this.#useAll.config = this.#isUseAll(formats);
this.#raw.config = formats;
this.#values.config = this.#normalize(formats);
}
addViaConfig(formats) {
if (!formats) {
return;
}
if (this.#isUseAll(formats)) {
throw new Error(
`\`addTemplateFormats("*")\` is not supported for project template syntaxes.`,
);
}
// "*" not supported here
this.#raw.configAdd = formats;
this.#values.configAdd = this.#normalize(formats);
}
getAllTemplateFormats() {
return Array.from(ProjectTemplateFormats.union(this.#values.config, this.#values.configAdd));
}
getTemplateFormats() {
if (this.#useAll.cli) {
let v = this.getAllTemplateFormats();
debug("Using CLI --formats='*': %o", v);
return v;
}
if (this.#raw.cli !== undefined) {
let v = Array.from(this.#values.cli);
debug("Using CLI --formats: %o", v);
return v;
}
let v = this.getAllTemplateFormats();
debug(
"Using configuration `templateFormats`, `setTemplateFormats()`, `addTemplateFormats()`: %o",
v,
);
return v;
}
}
export default ProjectTemplateFormats;

Some files were not shown because too many files have changed in this diff Show More