summaryrefslogtreecommitdiff
path: root/node_modules/@11ty/eleventy/src
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/@11ty/eleventy/src')
-rw-r--r--node_modules/@11ty/eleventy/src/Benchmark/Benchmark.js55
-rw-r--r--node_modules/@11ty/eleventy/src/Benchmark/BenchmarkGroup.js135
-rw-r--r--node_modules/@11ty/eleventy/src/Benchmark/BenchmarkManager.js73
-rw-r--r--node_modules/@11ty/eleventy/src/Data/ComputedData.js122
-rw-r--r--node_modules/@11ty/eleventy/src/Data/ComputedDataProxy.js131
-rw-r--r--node_modules/@11ty/eleventy/src/Data/ComputedDataQueue.js64
-rw-r--r--node_modules/@11ty/eleventy/src/Data/ComputedDataTemplateString.js70
-rw-r--r--node_modules/@11ty/eleventy/src/Data/TemplateData.js710
-rw-r--r--node_modules/@11ty/eleventy/src/Data/TemplateDataInitialGlobalData.js40
-rw-r--r--node_modules/@11ty/eleventy/src/Eleventy.js1565
-rw-r--r--node_modules/@11ty/eleventy/src/EleventyCommonJs.cjs43
-rw-r--r--node_modules/@11ty/eleventy/src/EleventyExtensionMap.js284
-rw-r--r--node_modules/@11ty/eleventy/src/EleventyFiles.js521
-rw-r--r--node_modules/@11ty/eleventy/src/EleventyServe.js321
-rwxr-xr-xnode_modules/@11ty/eleventy/src/EleventyWatch.js131
-rw-r--r--node_modules/@11ty/eleventy/src/EleventyWatchTargets.js164
-rw-r--r--node_modules/@11ty/eleventy/src/Engines/Custom.js339
-rw-r--r--node_modules/@11ty/eleventy/src/Engines/FrontMatter/JavaScript.js34
-rw-r--r--node_modules/@11ty/eleventy/src/Engines/Html.js33
-rw-r--r--node_modules/@11ty/eleventy/src/Engines/JavaScript.js240
-rw-r--r--node_modules/@11ty/eleventy/src/Engines/Liquid.js331
-rw-r--r--node_modules/@11ty/eleventy/src/Engines/Markdown.js100
-rwxr-xr-xnode_modules/@11ty/eleventy/src/Engines/Nunjucks.js482
-rw-r--r--node_modules/@11ty/eleventy/src/Engines/TemplateEngine.js206
-rw-r--r--node_modules/@11ty/eleventy/src/Engines/TemplateEngineManager.js193
-rw-r--r--node_modules/@11ty/eleventy/src/Engines/Util/ContextAugmenter.js67
-rw-r--r--node_modules/@11ty/eleventy/src/Errors/DuplicatePermalinkOutputError.js9
-rw-r--r--node_modules/@11ty/eleventy/src/Errors/EleventyBaseError.js24
-rw-r--r--node_modules/@11ty/eleventy/src/Errors/EleventyErrorHandler.js152
-rw-r--r--node_modules/@11ty/eleventy/src/Errors/EleventyErrorUtil.js70
-rw-r--r--node_modules/@11ty/eleventy/src/Errors/TemplateContentPrematureUseError.js5
-rw-r--r--node_modules/@11ty/eleventy/src/Errors/TemplateContentUnrenderedTemplateError.js5
-rw-r--r--node_modules/@11ty/eleventy/src/Errors/UsingCircularTemplateContentReferenceError.js5
-rw-r--r--node_modules/@11ty/eleventy/src/EventBus.js23
-rw-r--r--node_modules/@11ty/eleventy/src/FileSystemSearch.js129
-rw-r--r--node_modules/@11ty/eleventy/src/Filters/GetCollectionItem.js20
-rw-r--r--node_modules/@11ty/eleventy/src/Filters/GetCollectionItemIndex.js17
-rw-r--r--node_modules/@11ty/eleventy/src/Filters/GetLocaleCollectionItem.js47
-rw-r--r--node_modules/@11ty/eleventy/src/Filters/Slug.js14
-rw-r--r--node_modules/@11ty/eleventy/src/Filters/Slugify.js14
-rw-r--r--node_modules/@11ty/eleventy/src/Filters/Url.js35
-rw-r--r--node_modules/@11ty/eleventy/src/GlobalDependencyMap.js463
-rw-r--r--node_modules/@11ty/eleventy/src/LayoutCache.js98
-rw-r--r--node_modules/@11ty/eleventy/src/Plugins/HtmlBasePlugin.js160
-rw-r--r--node_modules/@11ty/eleventy/src/Plugins/HtmlRelativeCopyPlugin.js52
-rw-r--r--node_modules/@11ty/eleventy/src/Plugins/I18nPlugin.js317
-rw-r--r--node_modules/@11ty/eleventy/src/Plugins/IdAttributePlugin.js110
-rw-r--r--node_modules/@11ty/eleventy/src/Plugins/InputPathToUrl.js191
-rwxr-xr-xnode_modules/@11ty/eleventy/src/Plugins/Pagination.js379
-rw-r--r--node_modules/@11ty/eleventy/src/Plugins/RenderPlugin.js520
-rwxr-xr-xnode_modules/@11ty/eleventy/src/Template.js1200
-rw-r--r--node_modules/@11ty/eleventy/src/TemplateBehavior.js85
-rwxr-xr-xnode_modules/@11ty/eleventy/src/TemplateCollection.js77
-rw-r--r--node_modules/@11ty/eleventy/src/TemplateConfig.js565
-rw-r--r--node_modules/@11ty/eleventy/src/TemplateContent.js748
-rw-r--r--node_modules/@11ty/eleventy/src/TemplateFileSlug.js57
-rw-r--r--node_modules/@11ty/eleventy/src/TemplateGlob.js35
-rw-r--r--node_modules/@11ty/eleventy/src/TemplateLayout.js240
-rw-r--r--node_modules/@11ty/eleventy/src/TemplateLayoutPathResolver.js136
-rw-r--r--node_modules/@11ty/eleventy/src/TemplateMap.js684
-rw-r--r--node_modules/@11ty/eleventy/src/TemplatePassthrough.js389
-rw-r--r--node_modules/@11ty/eleventy/src/TemplatePassthroughManager.js368
-rw-r--r--node_modules/@11ty/eleventy/src/TemplatePermalink.js195
-rw-r--r--node_modules/@11ty/eleventy/src/TemplateRender.js292
-rwxr-xr-xnode_modules/@11ty/eleventy/src/TemplateWriter.js508
-rw-r--r--node_modules/@11ty/eleventy/src/UserConfig.js1339
-rw-r--r--node_modules/@11ty/eleventy/src/Util/ArrayUtil.js24
-rw-r--r--node_modules/@11ty/eleventy/src/Util/AsyncEventEmitter.js88
-rw-r--r--node_modules/@11ty/eleventy/src/Util/Compatibility.js59
-rw-r--r--node_modules/@11ty/eleventy/src/Util/ConsoleLogger.js140
-rw-r--r--node_modules/@11ty/eleventy/src/Util/DateGitFirstAdded.js23
-rw-r--r--node_modules/@11ty/eleventy/src/Util/DateGitLastUpdated.js23
-rw-r--r--node_modules/@11ty/eleventy/src/Util/DirContains.js10
-rw-r--r--node_modules/@11ty/eleventy/src/Util/EsmResolver.js53
-rw-r--r--node_modules/@11ty/eleventy/src/Util/EventBusUtil.js14
-rw-r--r--node_modules/@11ty/eleventy/src/Util/ExistsCache.js62
-rw-r--r--node_modules/@11ty/eleventy/src/Util/FilePathUtil.js19
-rw-r--r--node_modules/@11ty/eleventy/src/Util/FileSystemManager.js48
-rw-r--r--node_modules/@11ty/eleventy/src/Util/GetJavaScriptData.js30
-rw-r--r--node_modules/@11ty/eleventy/src/Util/GlobMatcher.js22
-rw-r--r--node_modules/@11ty/eleventy/src/Util/GlobRemap.js85
-rw-r--r--node_modules/@11ty/eleventy/src/Util/HtmlRelativeCopy.js149
-rw-r--r--node_modules/@11ty/eleventy/src/Util/HtmlTransformer.js172
-rw-r--r--node_modules/@11ty/eleventy/src/Util/ImportJsonSync.js77
-rw-r--r--node_modules/@11ty/eleventy/src/Util/IsAsyncFunction.js5
-rw-r--r--node_modules/@11ty/eleventy/src/Util/JavaScriptDependencies.js55
-rw-r--r--node_modules/@11ty/eleventy/src/Util/MemoizeFunction.js26
-rw-r--r--node_modules/@11ty/eleventy/src/Util/Objects/DeepFreeze.js20
-rw-r--r--node_modules/@11ty/eleventy/src/Util/Objects/ObjectFilter.js9
-rw-r--r--node_modules/@11ty/eleventy/src/Util/Objects/ProxyWrap.js118
-rw-r--r--node_modules/@11ty/eleventy/src/Util/Objects/SampleModule.mjs1
-rw-r--r--node_modules/@11ty/eleventy/src/Util/Objects/Sortable.js136
-rw-r--r--node_modules/@11ty/eleventy/src/Util/Objects/Unique.js3
-rw-r--r--node_modules/@11ty/eleventy/src/Util/PassthroughCopyBehaviorCheck.js16
-rw-r--r--node_modules/@11ty/eleventy/src/Util/PathNormalizer.js58
-rw-r--r--node_modules/@11ty/eleventy/src/Util/PathPrefixer.js21
-rw-r--r--node_modules/@11ty/eleventy/src/Util/Pluralize.js3
-rw-r--r--node_modules/@11ty/eleventy/src/Util/ProjectDirectories.js369
-rw-r--r--node_modules/@11ty/eleventy/src/Util/ProjectTemplateFormats.js134
-rw-r--r--node_modules/@11ty/eleventy/src/Util/PromiseUtil.js15
-rw-r--r--node_modules/@11ty/eleventy/src/Util/Require.js258
-rw-r--r--node_modules/@11ty/eleventy/src/Util/ReservedData.js69
-rw-r--r--node_modules/@11ty/eleventy/src/Util/SetUnion.js11
-rw-r--r--node_modules/@11ty/eleventy/src/Util/SpawnAsync.js29
-rw-r--r--node_modules/@11ty/eleventy/src/Util/TemplateDepGraph.js160
-rw-r--r--node_modules/@11ty/eleventy/src/Util/TransformsUtil.js70
-rw-r--r--node_modules/@11ty/eleventy/src/Util/ValidUrl.js9
-rw-r--r--node_modules/@11ty/eleventy/src/defaultConfig.js178
108 files changed, 19102 insertions, 0 deletions
diff --git a/node_modules/@11ty/eleventy/src/Benchmark/Benchmark.js b/node_modules/@11ty/eleventy/src/Benchmark/Benchmark.js
new file mode 100644
index 0000000..df6dea7
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Benchmark/Benchmark.js
@@ -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;
diff --git a/node_modules/@11ty/eleventy/src/Benchmark/BenchmarkGroup.js b/node_modules/@11ty/eleventy/src/Benchmark/BenchmarkGroup.js
new file mode 100644
index 0000000..ee82f6b
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Benchmark/BenchmarkGroup.js
@@ -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();
+ // // don’t 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;
diff --git a/node_modules/@11ty/eleventy/src/Benchmark/BenchmarkManager.js b/node_modules/@11ty/eleventy/src/Benchmark/BenchmarkManager.js
new file mode 100644
index 0000000..d7a8f61
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Benchmark/BenchmarkManager.js
@@ -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 don’t 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;
diff --git a/node_modules/@11ty/eleventy/src/Data/ComputedData.js b/node_modules/@11ty/eleventy/src/Data/ComputedData.js
new file mode 100644
index 0000000..5350475
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Data/ComputedData.js
@@ -0,0 +1,122 @@
+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,
+ };
+ }
+ fns.tmpl = templateInstance;
+
+ renderFn = renderFn.bind(fns);
+ }
+
+ 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;
diff --git a/node_modules/@11ty/eleventy/src/Data/ComputedDataProxy.js b/node_modules/@11ty/eleventy/src/Data/ComputedDataProxy.js
new file mode 100644
index 0000000..2415355
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Data/ComputedDataProxy.js
@@ -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;
diff --git a/node_modules/@11ty/eleventy/src/Data/ComputedDataQueue.js b/node_modules/@11ty/eleventy/src/Data/ComputedDataQueue.js
new file mode 100644
index 0000000..628b911
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Data/ComputedDataQueue.js
@@ -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;
diff --git a/node_modules/@11ty/eleventy/src/Data/ComputedDataTemplateString.js b/node_modules/@11ty/eleventy/src/Data/ComputedDataTemplateString.js
new file mode 100644
index 0000000..d5241b2
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Data/ComputedDataTemplateString.js
@@ -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 doesn’t work
+ * in some template languages that visit all available data even if
+ * it isn’t 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 won’t 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
+ // can’t use proxies here as some template languages trigger proxy for all
+ // keys in data
+ for (let key of this.computedKeys) {
+ // TODO don’t 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 shouldn’t 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;
diff --git a/node_modules/@11ty/eleventy/src/Data/TemplateData.js b/node_modules/@11ty/eleventy/src/Data/TemplateData.js
new file mode 100644
index 0000000..6942892
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Data/TemplateData.js
@@ -0,0 +1,710 @@
+import path from "node:path";
+import util from "node:util";
+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 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 TemplateDataParseError extends EleventyBaseError {}
+
+class TemplateData {
+ constructor(templateConfig) {
+ if (!templateConfig || templateConfig.constructor.name !== "TemplateConfig") {
+ throw new Error(
+ "Internal error: Missing `templateConfig` or was not an instance of `TemplateConfig`.",
+ );
+ }
+
+ this.templateConfig = templateConfig;
+ this.config = this.templateConfig.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.templateConfig);
+ }
+
+ get dirs() {
+ return this.templateConfig.directories;
+ }
+
+ get inputDir() {
+ return this.dirs.input;
+ }
+
+ // if this was set but `falsy` we would fallback to inputDir
+ get dataDir() {
+ return this.dirs.data;
+ }
+
+ get absoluteDataDir() {
+ return TemplatePath.absolutePath(this.dataDir);
+ }
+
+ // This was async in 2.0 and prior but doesn’t need to be any more.
+ getInputDir() {
+ return this.dirs.input;
+ }
+
+ getDataDir() {
+ return this.dataDir;
+ }
+
+ exists(pathname) {
+ // 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.templateConfig.existsCache.exists(pathname);
+ }
+
+ setFileSystemSearch(fileSystemSearch) {
+ this.fileSystemSearch = fileSystemSearch;
+ }
+
+ setProjectUsingEsm(isEsmProject) {
+ this.isEsm = !!isEsmProject;
+ }
+
+ get extensionMap() {
+ if (!this._extensionMap) {
+ throw new Error("Internal error: missing `extensionMap` in TemplateData.");
+ }
+ 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;
+ }
+
+ let pkgJson = getWorkingProjectPackageJson();
+ this.rawImports[this.config.keys.package] = pkgJson;
+
+ if (this.config.freezeReservedData) {
+ DeepFreeze(this.rawImports);
+ }
+
+ 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 shouldn’t 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 absoluteDataFilePath = TemplatePath.absolutePath(dataFilePath);
+ let reducedPath = TemplatePath.stripLeadingSubPath(absoluteDataFilePath, this.absoluteDataDir);
+ 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, let’s 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
+ localDataPaths = localDataPaths.filter((path) => {
+ return this.exists(path);
+ });
+
+ 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, {
+ file: path,
+ });
+ 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] = 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 require’d JSON (no preprocessing needed)
+ if (!this.exists(path)) {
+ 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` isn’t available in ESM.
+ let returnValue = await EleventyImport(path, 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);
+ }
+ // TODO use DirContains
+ 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, options = {}) {
+ 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;
+ } else if (data.tags) {
+ throw new Error(
+ `String or Array expected for \`tags\`${options.file ? ` in ${options.isVirtualTemplate ? "virtual " : ""}template: ${options.file}` : ""}. Received: ${util.inspect(data.tags)}`,
+ );
+ }
+
+ // Deduplicate tags
+ // Coerce to string #3875
+ return [...new Set(tags)].map((entry) => String(entry));
+ }
+
+ return tags;
+ }
+
+ static cleanupData(data, options = {}) {
+ if (isPlainObject(data) && "tags" in data) {
+ data.tags = this.getCleanedTagsImmutable(data, options);
+ }
+
+ 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,
+ };
+ }
+
+ static getIncludedCollectionNames(data) {
+ let tags = TemplateData.getCleanedTagsImmutable(data);
+
+ let { excludes, excludeAll } = TemplateData.getNormalizedExcludedCollections(data);
+ if (excludeAll) {
+ return [];
+ }
+
+ return ["all", ...tags].filter((tag) => !excludes.includes(tag));
+ }
+
+ static getIncludedTagNames(data) {
+ return this.getIncludedCollectionNames(data).filter((tagName) => tagName !== "all");
+ }
+}
+
+export default TemplateData;
diff --git a/node_modules/@11ty/eleventy/src/Data/TemplateDataInitialGlobalData.js b/node_modules/@11ty/eleventy/src/Data/TemplateDataInitialGlobalData.js
new file mode 100644
index 0000000..7e2a7ee
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Data/TemplateDataInitialGlobalData.js
@@ -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;
diff --git a/node_modules/@11ty/eleventy/src/Eleventy.js b/node_modules/@11ty/eleventy/src/Eleventy.js
new file mode 100644
index 0000000..0568a3e
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Eleventy.js
@@ -0,0 +1,1565 @@
+import chalk from "kleur";
+import { performance } from "node:perf_hooks";
+import debugUtil from "debug";
+import { filesize } from "filesize";
+import path from "node:path";
+
+/* Eleventy Deps */
+import { TemplatePath } from "@11ty/eleventy-utils";
+import BundlePlugin from "@11ty/eleventy-plugin-bundle";
+
+import TemplateData from "./Data/TemplateData.js";
+import TemplateWriter from "./TemplateWriter.js";
+import EleventyExtensionMap from "./EleventyExtensionMap.js";
+import { EleventyErrorHandler } from "./Errors/EleventyErrorHandler.js";
+import EleventyBaseError from "./Errors/EleventyBaseError.js";
+import EleventyServe from "./EleventyServe.js";
+import EleventyWatch from "./EleventyWatch.js";
+import EleventyWatchTargets from "./EleventyWatchTargets.js";
+import EleventyFiles from "./EleventyFiles.js";
+import TemplatePassthroughManager from "./TemplatePassthroughManager.js";
+import TemplateConfig from "./TemplateConfig.js";
+import FileSystemSearch from "./FileSystemSearch.js";
+import TemplateEngineManager from "./Engines/TemplateEngineManager.js";
+
+/* Utils */
+import ConsoleLogger from "./Util/ConsoleLogger.js";
+import PathPrefixer from "./Util/PathPrefixer.js";
+import ProjectDirectories from "./Util/ProjectDirectories.js";
+import PathNormalizer from "./Util/PathNormalizer.js";
+import { isGlobMatch } from "./Util/GlobMatcher.js";
+import simplePlural from "./Util/Pluralize.js";
+import checkPassthroughCopyBehavior from "./Util/PassthroughCopyBehaviorCheck.js";
+import eventBus from "./EventBus.js";
+import {
+ getEleventyPackageJson,
+ importJsonSync,
+ getWorkingProjectPackageJsonPath,
+} from "./Util/ImportJsonSync.js";
+import { EleventyImport } from "./Util/Require.js";
+import ProjectTemplateFormats from "./Util/ProjectTemplateFormats.js";
+import { withResolvers } from "./Util/PromiseUtil.js";
+
+/* Plugins */
+import RenderPlugin, * as RenderPluginExtras from "./Plugins/RenderPlugin.js";
+import I18nPlugin, * as I18nPluginExtras from "./Plugins/I18nPlugin.js";
+import HtmlBasePlugin, * as HtmlBasePluginExtras from "./Plugins/HtmlBasePlugin.js";
+import { TransformPlugin as InputPathToUrlTransformPlugin } from "./Plugins/InputPathToUrl.js";
+import { IdAttributePlugin } from "./Plugins/IdAttributePlugin.js";
+import FileSystemRemap from "./Util/GlobRemap.js";
+
+const pkg = getEleventyPackageJson();
+const debug = debugUtil("Eleventy");
+
+/**
+ * Eleventy’s programmatic API
+ * @module 11ty/eleventy/Eleventy
+ */
+
+class Eleventy {
+ /**
+ * Userspace package.json file contents
+ * @type {object|undefined}
+ */
+ #projectPackageJson;
+ /** @type {string} */
+ #projectPackageJsonPath;
+ /** @type {ProjectTemplateFormats|undefined} */
+ #templateFormats;
+ /** @type {ConsoleLogger|undefined} */
+ #logger;
+ /** @type {ProjectDirectories|undefined} */
+ #directories;
+ /** @type {boolean|undefined} */
+ #verboseOverride;
+ /** @type {boolean} */
+ #isVerboseMode = true;
+ /** @type {boolean|undefined} */
+ #preInitVerbose;
+ /** @type {boolean} */
+ #hasConfigInitialized = false;
+ /** @type {boolean} */
+ #needsInit = true;
+ /** @type {Promise|undefined} */
+ #initPromise;
+ /** @type {EleventyErrorHandler|undefined} */
+ #errorHandler;
+ /** @type {Map} */
+ #privateCaches = new Map();
+ /** @type {boolean} */
+ #isStopping = false;
+ /** @type {boolean|undefined} */
+ #isEsm;
+
+ /**
+ * @typedef {object} EleventyOptions
+ * @property {'cli'|'script'=} source
+ * @property {'build'|'serve'|'watch'=} runMode
+ * @property {boolean=} dryRun
+ * @property {string=} configPath
+ * @property {string=} pathPrefix
+ * @property {boolean=} quietMode
+ * @property {Function=} config
+ * @property {string=} inputDir
+
+ * @param {string} [input] - Directory or filename for input/sources files.
+ * @param {string} [output] - Directory serving as the target for writing the output files.
+ * @param {EleventyOptions} [options={}]
+ * @param {TemplateConfig} [eleventyConfig]
+ */
+ constructor(input, output, options = {}, eleventyConfig = null) {
+ /**
+ * @type {string|undefined}
+ * @description Holds the path to the input (might be a file or folder)
+ */
+ this.rawInput = input || undefined;
+
+ /**
+ * @type {string|undefined}
+ * @description holds the path to the output directory
+ */
+ this.rawOutput = output || undefined;
+
+ /**
+ * @type {module:11ty/eleventy/TemplateConfig}
+ * @description Override the config instance (for centralized config re-use)
+ */
+ this.eleventyConfig = eleventyConfig;
+
+ /**
+ * @type {EleventyOptions}
+ * @description Options object passed to the Eleventy constructor
+ * @default {}
+ */
+ this.options = options;
+
+ /**
+ * @type {'cli'|'script'}
+ * @description Called via CLI (`cli`) or Programmatically (`script`)
+ * @default "script"
+ */
+ this.source = options.source || "script";
+
+ /**
+ * @type {string}
+ * @description One of build, serve, or watch
+ * @default "build"
+ */
+ this.runMode = options.runMode || "build";
+
+ /**
+ * @type {boolean}
+ * @description Is Eleventy running in dry mode?
+ * @default false
+ */
+ this.isDryRun = options.dryRun ?? false;
+
+ /**
+ * @type {boolean}
+ * @description Is this an incremental build? (only operates on a subset of input files)
+ * @default false
+ */
+ this.isIncremental = false;
+
+ /**
+ * @type {string|undefined}
+ * @description If an incremental build, this is the file we’re operating on.
+ * @default null
+ */
+ this.programmaticApiIncrementalFile = undefined;
+
+ /**
+ * @type {boolean}
+ * @description Should we process files on first run? (The --ignore-initial feature)
+ * @default true
+ */
+ this.isRunInitialBuild = true;
+
+ /**
+ * @type {Number}
+ * @description Number of builds run on this instance.
+ * @default 0
+ */
+ this.buildCount = 0;
+
+ /**
+ * @member {String} - Force ESM or CJS mode instead of detecting from package.json. Either cjs, esm, or auto.
+ * @default "auto"
+ */
+ this.loader = this.options.loader ?? "auto";
+
+ /**
+ * @type {Number}
+ * @description The timestamp of Eleventy start.
+ */
+ this.start = this.getNewTimestamp();
+ }
+
+ /**
+ * @type {string|undefined}
+ * @description An override of Eleventy's default config file paths
+ * @default undefined
+ */
+ get configPath() {
+ return this.options.configPath;
+ }
+
+ /**
+ * @type {string}
+ * @description The top level directory the site pretends to reside in
+ * @default "/"
+ */
+ get pathPrefix() {
+ return this.options.pathPrefix || "/";
+ }
+
+ async initializeConfig(initOverrides) {
+ if (!this.eleventyConfig) {
+ this.eleventyConfig = new TemplateConfig(null, this.configPath);
+ } else if (this.configPath) {
+ await this.eleventyConfig.setProjectConfigPath(this.configPath);
+ }
+
+ this.eleventyConfig.setRunMode(this.runMode);
+ this.eleventyConfig.setProjectUsingEsm(this.isEsm);
+ this.eleventyConfig.setLogger(this.logger);
+ this.eleventyConfig.setDirectories(this.directories);
+ this.eleventyConfig.setTemplateFormats(this.templateFormats);
+
+ if (this.pathPrefix || this.pathPrefix === "") {
+ this.eleventyConfig.setPathPrefix(this.pathPrefix);
+ }
+
+ // Debug mode should always run quiet (all output goes to debug logger)
+ if (process.env.DEBUG) {
+ this.#verboseOverride = false;
+ } else if (this.options.quietMode === true || this.options.quietMode === false) {
+ this.#verboseOverride = !this.options.quietMode;
+ }
+
+ // Moved before config merges: https://github.com/11ty/eleventy/issues/3316
+ if (this.#verboseOverride === true || this.#verboseOverride === false) {
+ this.eleventyConfig.userConfig._setQuietModeOverride(!this.#verboseOverride);
+ }
+
+ this.eleventyConfig.userConfig.directories = this.directories;
+
+ /* Programmatic API config */
+ if (this.options.config && typeof this.options.config === "function") {
+ debug("Running options.config configuration callback (passed to Eleventy constructor)");
+ // TODO use return object here?
+ await this.options.config(this.eleventyConfig.userConfig);
+ }
+
+ /**
+ * @type {object}
+ * @description Initialize Eleventy environment variables
+ * @default null
+ */
+ // this.runMode need to be set before this
+ this.env = this.getEnvironmentVariableValues();
+ this.initializeEnvironmentVariables(this.env);
+
+ // Async initialization of configuration
+ await this.eleventyConfig.init(initOverrides);
+
+ /**
+ * @type {object}
+ * @description Initialize Eleventy’s configuration, including the user config file
+ */
+ this.config = this.eleventyConfig.getConfig();
+
+ /**
+ * @type {object}
+ * @description Singleton BenchmarkManager instance
+ */
+ this.bench = this.config.benchmarkManager;
+
+ if (performance) {
+ debug("Eleventy warm up time: %o (ms)", performance.now());
+ }
+
+ // Careful to make sure the previous server closes on SIGINT, issue #3873
+ if (!this.eleventyServe) {
+ /** @type {object} */
+ this.eleventyServe = new EleventyServe();
+ }
+ this.eleventyServe.eleventyConfig = this.eleventyConfig;
+
+ /** @type {object} */
+ this.watchManager = new EleventyWatch();
+
+ /** @type {object} */
+ this.watchTargets = new EleventyWatchTargets(this.eleventyConfig);
+ this.watchTargets.addAndMakeGlob(this.config.additionalWatchTargets);
+
+ /** @type {object} */
+ this.fileSystemSearch = new FileSystemSearch();
+
+ this.#hasConfigInitialized = true;
+
+ // after #hasConfigInitialized above
+ this.setIsVerbose(this.#preInitVerbose ?? !this.config.quietMode);
+ }
+
+ getNewTimestamp() {
+ if (performance) {
+ return performance.now();
+ }
+ return new Date().getTime();
+ }
+
+ /** @type {ProjectDirectories} */
+ get directories() {
+ if (!this.#directories) {
+ this.#directories = new ProjectDirectories();
+ this.#directories.setInput(this.rawInput, this.options.inputDir);
+ this.#directories.setOutput(this.rawOutput);
+
+ if (this.source == "cli" && (this.rawInput !== undefined || this.rawOutput !== undefined)) {
+ this.#directories.freeze();
+ }
+ }
+
+ return this.#directories;
+ }
+
+ /** @type {string} */
+ get input() {
+ return this.directories.inputFile || this.directories.input || this.config.dir.input;
+ }
+
+ /** @type {string} */
+ get inputFile() {
+ return this.directories.inputFile;
+ }
+
+ /** @type {string} */
+ get inputDir() {
+ return this.directories.input;
+ }
+
+ // Not used internally, removed in 3.0.
+ setInputDir() {
+ throw new Error(
+ "Eleventy->setInputDir was removed in 3.0. Use the inputDir option to the constructor",
+ );
+ }
+
+ /** @type {string} */
+ get outputDir() {
+ return this.directories.output || this.config.dir.output;
+ }
+
+ /**
+ * Updates the dry-run mode of Eleventy.
+ *
+ * @param {boolean} isDryRun - Shall Eleventy run in dry mode?
+ */
+ setDryRun(isDryRun) {
+ this.isDryRun = !!isDryRun;
+ }
+
+ /**
+ * Sets the incremental build mode.
+ *
+ * @param {boolean} isIncremental - Shall Eleventy run in incremental build mode and only write the files that trigger watch updates
+ */
+ setIncrementalBuild(isIncremental) {
+ this.isIncremental = !!isIncremental;
+
+ if (this.watchManager) {
+ this.watchManager.incremental = !!isIncremental;
+ }
+ if (this.writer) {
+ this.writer.setIncrementalBuild(this.isIncremental);
+ }
+ }
+
+ /**
+ * Set whether or not to do an initial build
+ *
+ * @param {boolean} ignoreInitialBuild - Shall Eleventy ignore the default initial build before watching in watch/serve mode?
+ * @default true
+ */
+ setIgnoreInitial(ignoreInitialBuild) {
+ this.isRunInitialBuild = !ignoreInitialBuild;
+
+ if (this.writer) {
+ this.writer.setRunInitialBuild(this.isRunInitialBuild);
+ }
+ }
+
+ /**
+ * Updates the path prefix used in the config.
+ *
+ * @param {string} pathPrefix - The new path prefix.
+ */
+ setPathPrefix(pathPrefix) {
+ if (pathPrefix || pathPrefix === "") {
+ this.eleventyConfig.setPathPrefix(pathPrefix);
+ // TODO reset config
+ // this.config = this.eleventyConfig.getConfig();
+ }
+ }
+
+ /**
+ * Restarts Eleventy.
+ */
+ async restart() {
+ debug("Restarting.");
+ this.start = this.getNewTimestamp();
+
+ this.extensionMap.reset();
+ this.bench.reset();
+ this.passthroughManager.reset();
+ this.eleventyFiles.restart();
+ }
+
+ /**
+ * Logs some statistics after a complete run of Eleventy.
+ *
+ * @returns {string} ret - The log message.
+ */
+ logFinished() {
+ if (!this.writer) {
+ throw new Error(
+ "Did you call Eleventy.init to create the TemplateWriter instance? Hint: you probably didn’t.",
+ );
+ }
+
+ let ret = [];
+
+ let {
+ copyCount,
+ copySize,
+ skipCount,
+ writeCount,
+ // renderCount, // files that render (costly) but may not write to disk
+ } = this.writer.getMetadata();
+
+ let slashRet = [];
+
+ if (copyCount) {
+ debug("Total passthrough copy aggregate size: %o", filesize(copySize));
+ slashRet.push(`Copied ${chalk.bold(copyCount)}`);
+ }
+
+ slashRet.push(
+ `Wrote ${chalk.bold(writeCount)} ${simplePlural(writeCount, "file", "files")}${
+ skipCount ? ` (skipped ${skipCount})` : ""
+ }`,
+ );
+
+ // slashRet.push(
+ // `${renderCount} rendered`
+ // )
+
+ if (slashRet.length) {
+ ret.push(slashRet.join(" "));
+ }
+
+ let time = (this.getNewTimestamp() - this.start) / 1000;
+ ret.push(
+ `in ${chalk.bold(time.toFixed(2))} ${simplePlural(time.toFixed(2), "second", "seconds")}`,
+ );
+
+ // More than 1 second total, show estimate of per-template time
+ if (time >= 1 && writeCount > 1) {
+ ret.push(`(${((time * 1000) / writeCount).toFixed(1)}ms each, v${pkg.version})`);
+ } else {
+ ret.push(`(v${pkg.version})`);
+ }
+
+ return ret.join(" ");
+ }
+
+ #cache(key, inst) {
+ if (!("caches" in inst)) {
+ throw new Error("To use #cache you need a `caches` getter object");
+ }
+
+ // Restore from cache
+ if (this.#privateCaches.has(key)) {
+ let c = this.#privateCaches.get(key);
+ for (let cacheKey in c) {
+ inst[cacheKey] = c[cacheKey];
+ }
+ } else {
+ // Set cache
+ let c = {};
+ for (let cacheKey of inst.caches || []) {
+ c[cacheKey] = inst[cacheKey];
+ }
+ this.#privateCaches.set(key, c);
+ }
+ }
+
+ /**
+ * Starts Eleventy.
+ */
+ async init(options = {}) {
+ let { viaConfigReset } = Object.assign({ viaConfigReset: false }, options);
+ if (!this.#hasConfigInitialized) {
+ await this.initializeConfig();
+ } else {
+ // Note: Global event bus is different from user config event bus
+ this.config.events.reset();
+ }
+
+ await this.config.events.emit("eleventy.config", this.eleventyConfig);
+
+ if (this.env) {
+ await this.config.events.emit("eleventy.env", this.env);
+ }
+
+ let formats = this.templateFormats.getTemplateFormats();
+ let engineManager = new TemplateEngineManager(this.eleventyConfig);
+ this.extensionMap = new EleventyExtensionMap(this.eleventyConfig);
+ this.extensionMap.setFormats(formats);
+ this.extensionMap.engineManager = engineManager;
+ await this.config.events.emit("eleventy.extensionmap", this.extensionMap);
+
+ // eleventyServe is always available, even when not in --serve mode
+ // TODO directorynorm
+ this.eleventyServe.setOutputDir(this.outputDir);
+
+ // TODO
+ // this.eleventyServe.setWatcherOptions(this.getChokidarConfig());
+
+ this.templateData = new TemplateData(this.eleventyConfig);
+ this.templateData.setProjectUsingEsm(this.isEsm);
+ this.templateData.extensionMap = this.extensionMap;
+ if (this.env) {
+ this.templateData.environmentVariables = this.env;
+ }
+ this.templateData.setFileSystemSearch(this.fileSystemSearch);
+
+ this.passthroughManager = new TemplatePassthroughManager(this.eleventyConfig);
+ this.passthroughManager.setRunMode(this.runMode);
+ this.passthroughManager.setDryRun(this.isDryRun);
+ this.passthroughManager.extensionMap = this.extensionMap;
+ this.passthroughManager.setFileSystemSearch(this.fileSystemSearch);
+
+ this.eleventyFiles = new EleventyFiles(formats, this.eleventyConfig);
+ this.eleventyFiles.setPassthroughManager(this.passthroughManager);
+ this.eleventyFiles.setFileSystemSearch(this.fileSystemSearch);
+ this.eleventyFiles.setRunMode(this.runMode);
+ this.eleventyFiles.extensionMap = this.extensionMap;
+ // This needs to be set before init or it’ll construct a new one
+ this.eleventyFiles.templateData = this.templateData;
+ this.eleventyFiles.init();
+
+ if (checkPassthroughCopyBehavior(this.config, this.runMode)) {
+ this.eleventyServe.watchPassthroughCopy(
+ this.eleventyFiles.getGlobWatcherFilesForPassthroughCopy(),
+ );
+ }
+
+ // Note these directories are all project root relative
+ this.config.events.emit("eleventy.directories", this.directories.getUserspaceInstance());
+
+ this.writer = new TemplateWriter(formats, this.templateData, this.eleventyConfig);
+
+ if (!viaConfigReset) {
+ // set or restore cache
+ this.#cache("TemplateWriter", this.writer);
+ }
+
+ this.writer.logger = this.logger;
+ this.writer.extensionMap = this.extensionMap;
+ this.writer.setEleventyFiles(this.eleventyFiles);
+ this.writer.setPassthroughManager(this.passthroughManager);
+ this.writer.setRunInitialBuild(this.isRunInitialBuild);
+ this.writer.setIncrementalBuild(this.isIncremental);
+
+ let debugStr = `Directories:
+ Input:
+ Directory: ${this.directories.input}
+ File: ${this.directories.inputFile || false}
+ Glob: ${this.directories.inputGlob || false}
+ Data: ${this.directories.data}
+ Includes: ${this.directories.includes}
+ Layouts: ${this.directories.layouts || false}
+ Output: ${this.directories.output}
+Template Formats: ${formats.join(",")}
+Verbose Output: ${this.verboseMode}`;
+ debug(debugStr);
+
+ this.writer.setVerboseOutput(this.verboseMode);
+ this.writer.setDryRun(this.isDryRun);
+
+ this.#needsInit = false;
+ }
+
+ // These are all set as initial global data under eleventy.env.* (see TemplateData->environmentVariables)
+ getEnvironmentVariableValues() {
+ let values = {
+ source: this.source,
+ runMode: this.runMode,
+ };
+
+ let configPath = this.eleventyConfig.getLocalProjectConfigFile();
+ if (configPath) {
+ values.config = TemplatePath.absolutePath(configPath);
+ }
+
+ // Fixed: instead of configuration directory, explicit root or working directory
+ values.root = TemplatePath.getWorkingDir();
+
+ values.source = this.source;
+
+ // Backwards compatibility
+ Object.defineProperty(values, "isServerless", {
+ enumerable: false,
+ value: false,
+ });
+
+ return values;
+ }
+
+ /**
+ * Set process.ENV variables for use in Eleventy projects
+ *
+ * @method
+ */
+ initializeEnvironmentVariables(env) {
+ // Recognize that global data `eleventy.version` is coerced to remove prerelease tags
+ // and this is the raw version (3.0.0 versus 3.0.0-alpha.6).
+ // `eleventy.env.version` does not yet exist (unnecessary)
+ process.env.ELEVENTY_VERSION = Eleventy.getVersion();
+
+ process.env.ELEVENTY_ROOT = env.root;
+ debug("Setting process.env.ELEVENTY_ROOT: %o", env.root);
+
+ process.env.ELEVENTY_SOURCE = env.source;
+ process.env.ELEVENTY_RUN_MODE = env.runMode;
+ }
+
+ /** @param {boolean} value */
+ set verboseMode(value) {
+ this.setIsVerbose(value);
+ }
+
+ /** @type {boolean} */
+ get verboseMode() {
+ return this.#isVerboseMode;
+ }
+
+ /** @type {ConsoleLogger} */
+ get logger() {
+ if (!this.#logger) {
+ this.#logger = new ConsoleLogger();
+ this.#logger.isVerbose = this.verboseMode;
+ }
+
+ return this.#logger;
+ }
+
+ /** @param {ConsoleLogger} logger */
+ set logger(logger) {
+ this.eleventyConfig.setLogger(logger);
+ this.#logger = logger;
+ }
+
+ disableLogger() {
+ this.logger.overrideLogger(false);
+ }
+
+ /** @type {EleventyErrorHandler} */
+ get errorHandler() {
+ if (!this.#errorHandler) {
+ this.#errorHandler = new EleventyErrorHandler();
+ this.#errorHandler.isVerbose = this.verboseMode;
+ this.#errorHandler.logger = this.logger;
+ }
+
+ return this.#errorHandler;
+ }
+
+ /**
+ * Updates the verbose mode of Eleventy.
+ *
+ * @method
+ * @param {boolean} isVerbose - Shall Eleventy run in verbose mode?
+ */
+ setIsVerbose(isVerbose) {
+ if (!this.#hasConfigInitialized) {
+ this.#preInitVerbose = !!isVerbose;
+ return;
+ }
+
+ // always defer to --quiet if override happened
+ isVerbose = this.#verboseOverride ?? !!isVerbose;
+
+ this.#isVerboseMode = isVerbose;
+
+ if (this.logger) {
+ this.logger.isVerbose = isVerbose;
+ }
+
+ this.bench.setVerboseOutput(isVerbose);
+
+ if (this.writer) {
+ this.writer.setVerboseOutput(isVerbose);
+ }
+
+ if (this.errorHandler) {
+ this.errorHandler.isVerbose = isVerbose;
+ }
+
+ // Set verbose mode in config file
+ this.eleventyConfig.verbose = isVerbose;
+ }
+
+ get templateFormats() {
+ if (!this.#templateFormats) {
+ let tf = new ProjectTemplateFormats();
+ this.#templateFormats = tf;
+ }
+
+ return this.#templateFormats;
+ }
+
+ /**
+ * Updates the template formats of Eleventy.
+ *
+ * @method
+ * @param {string} formats - The new template formats.
+ */
+ setFormats(formats) {
+ this.templateFormats.setViaCommandLine(formats);
+ }
+
+ /**
+ * Updates the run mode of Eleventy.
+ *
+ * @method
+ * @param {string} runMode - One of "build", "watch", or "serve"
+ */
+ setRunMode(runMode) {
+ this.runMode = runMode;
+ }
+
+ /**
+ * Set the file that needs to be rendered/compiled/written for an incremental build.
+ * This method is also wired up to the CLI --incremental=incrementalFile
+ *
+ * @method
+ * @param {string} incrementalFile - File path (added or modified in a project)
+ */
+ setIncrementalFile(incrementalFile) {
+ if (incrementalFile) {
+ // This used to also setIgnoreInitial(true) but was changed in 3.0.0-alpha.14
+ this.setIncrementalBuild(true);
+
+ this.programmaticApiIncrementalFile = TemplatePath.addLeadingDotSlash(incrementalFile);
+
+ this.eleventyConfig.setPreviousBuildModifiedFile(incrementalFile);
+ }
+ }
+
+ unsetIncrementalFile() {
+ // only applies to initial build, no re-runs (--watch/--serve)
+ if (this.programmaticApiIncrementalFile) {
+ // this.setIgnoreInitial(false);
+ this.programmaticApiIncrementalFile = undefined;
+ }
+
+ // reset back to false
+ this.setIgnoreInitial(false);
+ }
+
+ /**
+ * Reads the version of Eleventy.
+ *
+ * @static
+ * @returns {string} - The version of Eleventy.
+ */
+ static getVersion() {
+ return pkg.version;
+ }
+
+ /**
+ * @deprecated since 1.0.1, use static Eleventy.getVersion()
+ */
+ getVersion() {
+ return Eleventy.getVersion();
+ }
+
+ /**
+ * Shows a help message including usage.
+ *
+ * @static
+ * @returns {string} - The help message.
+ */
+ static getHelp() {
+ return `Usage: eleventy
+ eleventy --input=. --output=./_site
+ eleventy --serve
+
+Arguments:
+
+ --version
+
+ --input=.
+ Input template files (default: \`.\`)
+
+ --output=_site
+ Write HTML output to this folder (default: \`_site\`)
+
+ --serve
+ Run web server on --port (default 8080) and watch them too
+
+ --port
+ Run the --serve web server on this port (default 8080)
+
+ --watch
+ Wait for files to change and automatically rewrite (no web server)
+
+ --incremental
+ Only build the files that have changed. Best with watch/serve.
+
+ --incremental=filename.md
+ Does not require watch/serve. Run an incremental build targeting a single file.
+
+ --ignore-initial
+ Start without a build; build when files change. Works best with watch/serve/incremental.
+
+ --formats=liquid,md
+ Allow only certain template types (default: \`*\`)
+
+ --quiet
+ Don’t print all written files (off by default)
+
+ --config=filename.js
+ Override the eleventy config file path (default: \`.eleventy.js\`)
+
+ --pathprefix='/'
+ Change all url template filters to use this subdirectory.
+
+ --dryrun
+ Don’t write any files. Useful in DEBUG mode, for example: \`DEBUG=Eleventy* npx @11ty/eleventy --dryrun\`
+
+ --loader
+ Set to "esm" to force ESM mode, "cjs" to force CommonJS mode, or "auto" (default) to infer it from package.json.
+
+ --to=json
+ --to=ndjson
+ Change the output to JSON or NDJSON (default: \`fs\`)
+
+ --help`;
+ }
+
+ /**
+ * @deprecated since 1.0.1, use static Eleventy.getHelp()
+ */
+ getHelp() {
+ return Eleventy.getHelp();
+ }
+
+ /**
+ * Resets the config of Eleventy.
+ *
+ * @method
+ */
+ resetConfig() {
+ delete this.eleventyConfig;
+
+ // ensures `initializeConfig()` will run when `init()` is called next
+ this.#hasConfigInitialized = false;
+ }
+
+ /**
+ * @param {string} changedFilePath - File that triggered a re-run (added or modified)
+ * @param {boolean} [isResetConfig] - are we doing a config reset
+ */
+ async #addFileToWatchQueue(changedFilePath, isResetConfig) {
+ // Currently this is only for 11ty.js deps but should be extended with usesGraph
+ let usedByDependants = [];
+ if (this.watchTargets) {
+ usedByDependants = this.watchTargets.getDependantsOf(
+ TemplatePath.addLeadingDotSlash(changedFilePath),
+ );
+ }
+
+ let relevantLayouts = this.eleventyConfig.usesGraph.getLayoutsUsedBy(changedFilePath);
+
+ // `eleventy.templateModified` is no longer used internally, remove in a future major version.
+ eventBus.emit("eleventy.templateModified", changedFilePath, {
+ usedByDependants,
+ relevantLayouts,
+ });
+
+ // These listeners are *global*, not cleared even on config reset
+ eventBus.emit("eleventy.resourceModified", changedFilePath, usedByDependants, {
+ viaConfigReset: isResetConfig,
+ relevantLayouts,
+ });
+
+ this.config.events.emit("eleventy#templateModified", changedFilePath);
+
+ this.watchManager.addToPendingQueue(changedFilePath);
+ }
+
+ shouldTriggerConfigReset(changedFiles) {
+ let configFilePaths = new Set(this.eleventyConfig.getLocalProjectConfigFiles());
+ let resetConfigGlobs = EleventyWatchTargets.normalizeToGlobs(
+ Array.from(this.eleventyConfig.userConfig.watchTargetsConfigReset),
+ );
+ for (let filePath of changedFiles) {
+ if (configFilePaths.has(filePath)) {
+ return true;
+ }
+ if (isGlobMatch(filePath, resetConfigGlobs)) {
+ return true;
+ }
+ }
+
+ for (const configFilePath of configFilePaths) {
+ // Any dependencies of the config file changed
+ let configFileDependencies = new Set(this.watchTargets.getDependenciesOf(configFilePath));
+
+ for (let filePath of changedFiles) {
+ if (configFileDependencies.has(filePath)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ // Checks the build queue to see if any configuration related files have changed
+ #shouldResetConfig(activeQueue = []) {
+ if (!activeQueue.length) {
+ return false;
+ }
+
+ return this.shouldTriggerConfigReset(
+ activeQueue.map((path) => {
+ return PathNormalizer.normalizeSeperator(TemplatePath.addLeadingDotSlash(path));
+ }),
+ );
+ }
+
+ async #watch(isResetConfig = false) {
+ if (this.watchManager.isBuildRunning()) {
+ return;
+ }
+
+ this.watchManager.setBuildRunning();
+
+ let queue = this.watchManager.getActiveQueue();
+
+ await this.config.events.emit("beforeWatch", queue);
+ await this.config.events.emit("eleventy.beforeWatch", queue);
+
+ // Clear `import` cache for all files that triggered the rebuild (sync event)
+ this.watchTargets.clearImportCacheFor(queue);
+
+ // reset and reload global configuration
+ if (isResetConfig) {
+ // important: run this before config resets otherwise the handlers will disappear.
+ await this.config.events.emit("eleventy.reset");
+ this.resetConfig();
+ }
+
+ await this.restart();
+ await this.init({ viaConfigReset: isResetConfig });
+
+ try {
+ let [passthroughCopyResults, templateResults] = await this.write();
+
+ this.watchTargets.reset();
+
+ await this.#initWatchDependencies();
+
+ // Add new deps to chokidar
+ this.watcher.add(this.watchTargets.getNewTargetsSinceLastReset());
+
+ // Is a CSS input file and is not in the includes folder
+ // TODO check output path file extension of this template (not input path)
+ // TODO add additional API for this, maybe a config callback?
+ let onlyCssChanges = this.watchManager.hasAllQueueFiles((path) => {
+ return (
+ path.endsWith(".css") &&
+ // TODO how to make this work with relative includes?
+ !TemplatePath.startsWithSubPath(path, this.eleventyFiles.getIncludesDir())
+ );
+ });
+
+ let files = this.watchManager.getActiveQueue();
+
+ // Maps passthrough copy files to output URLs for CSS live reload
+ let stylesheetUrls = new Set();
+ for (let entry of passthroughCopyResults) {
+ for (let filepath in entry.map) {
+ if (
+ filepath.endsWith(".css") &&
+ files.includes(TemplatePath.addLeadingDotSlash(filepath))
+ ) {
+ stylesheetUrls.add(
+ "/" + TemplatePath.stripLeadingSubPath(entry.map[filepath], this.outputDir),
+ );
+ }
+ }
+ }
+
+ let normalizedPathPrefix = PathPrefixer.normalizePathPrefix(this.config.pathPrefix);
+ let matchingTemplates = templateResults
+ .flat()
+ .filter((entry) => Boolean(entry))
+ .map((entry) => {
+ // only `url`, `inputPath`, and `content` are used: https://github.com/11ty/eleventy-dev-server/blob/1c658605f75224fdc76f68aebe7a412eeb4f1bc9/client/reload-client.js#L140
+ entry.url = PathPrefixer.joinUrlParts(normalizedPathPrefix, entry.url);
+ delete entry.rawInput; // Issue #3481
+ return entry;
+ });
+
+ await this.eleventyServe.reload({
+ files,
+ subtype: onlyCssChanges ? "css" : undefined,
+ build: {
+ stylesheets: Array.from(stylesheetUrls),
+ templates: matchingTemplates,
+ },
+ });
+ } catch (error) {
+ this.eleventyServe.sendError({
+ error,
+ });
+ }
+
+ this.watchManager.setBuildFinished();
+
+ let queueSize = this.watchManager.getPendingQueueSize();
+ if (queueSize > 0) {
+ this.logger.log(
+ `You saved while Eleventy was running, let’s run again. (${queueSize} change${
+ queueSize !== 1 ? "s" : ""
+ })`,
+ );
+ await this.#watch();
+ } else {
+ this.logger.log("Watching…");
+ }
+ }
+
+ /**
+ * @returns {module:11ty/eleventy/src/Benchmark/BenchmarkGroup~BenchmarkGroup}
+ */
+ get watcherBench() {
+ return this.bench.get("Watcher");
+ }
+
+ /**
+ * Set up watchers and benchmarks.
+ *
+ * @async
+ * @method
+ */
+ async initWatch() {
+ this.watchManager = new EleventyWatch();
+ this.watchManager.incremental = this.isIncremental;
+
+ if (this.projectPackageJsonPath) {
+ this.watchTargets.add([
+ path.relative(TemplatePath.getWorkingDir(), this.projectPackageJsonPath),
+ ]);
+ }
+ this.watchTargets.add(this.eleventyFiles.getGlobWatcherFiles());
+ this.watchTargets.add(this.eleventyFiles.getIgnoreFiles());
+
+ // Watch the local project config file
+ this.watchTargets.add(this.eleventyConfig.getLocalProjectConfigFiles());
+
+ // Template and Directory Data Files
+ this.watchTargets.add(await this.eleventyFiles.getGlobWatcherTemplateDataFiles());
+
+ let benchmark = this.watcherBench.get(
+ "Watching JavaScript Dependencies (disable with `eleventyConfig.setWatchJavaScriptDependencies(false)`)",
+ );
+ benchmark.before();
+ await this.#initWatchDependencies();
+ benchmark.after();
+ }
+
+ // fetch from project’s package.json
+ get projectPackageJsonPath() {
+ if (this.#projectPackageJsonPath === undefined) {
+ this.#projectPackageJsonPath = getWorkingProjectPackageJsonPath() || false;
+ }
+ return this.#projectPackageJsonPath;
+ }
+
+ get projectPackageJson() {
+ if (!this.#projectPackageJson) {
+ let p = this.projectPackageJsonPath;
+ this.#projectPackageJson = p ? importJsonSync(p) : {};
+ }
+ return this.#projectPackageJson;
+ }
+
+ get isEsm() {
+ if (this.#isEsm !== undefined) {
+ return this.#isEsm;
+ }
+ if (this.loader == "esm") {
+ this.#isEsm = true;
+ } else if (this.loader == "cjs") {
+ this.#isEsm = false;
+ } else if (this.loader == "auto") {
+ this.#isEsm = this.projectPackageJson?.type === "module";
+ } else {
+ throw new Error("The 'loader' option must be one of 'esm', 'cjs', or 'auto'");
+ }
+ return this.#isEsm;
+ }
+
+ /**
+ * Starts watching dependencies.
+ */
+ async #initWatchDependencies() {
+ if (!this.eleventyConfig.shouldSpiderJavaScriptDependencies()) {
+ return;
+ }
+
+ // TODO use DirContains
+ let dataDir = TemplatePath.stripLeadingDotSlash(this.templateData.getDataDir());
+ function filterOutGlobalDataFiles(path) {
+ return !dataDir || !TemplatePath.stripLeadingDotSlash(path).startsWith(dataDir);
+ }
+
+ // Lazy resolve isEsm only for --watch
+ this.watchTargets.setProjectUsingEsm(this.isEsm);
+
+ // Template files .11ty.js
+ let templateFiles = await this.eleventyFiles.getWatchPathCache();
+ await this.watchTargets.addDependencies(templateFiles);
+
+ // Config file dependencies
+ await this.watchTargets.addDependencies(
+ this.eleventyConfig.getLocalProjectConfigFiles(),
+ filterOutGlobalDataFiles,
+ );
+
+ // Deps from Global Data (that aren’t in the global data directory, everything is watched there)
+ let globalDataDeps = this.templateData.getWatchPathCache();
+ await this.watchTargets.addDependencies(globalDataDeps, filterOutGlobalDataFiles);
+
+ await this.watchTargets.addDependencies(
+ await this.eleventyFiles.getWatcherTemplateJavaScriptDataFiles(),
+ );
+ }
+
+ /**
+ * Returns all watched files.
+ *
+ * @async
+ * @method
+ * @returns {Promise<Array>} targets - The watched files.
+ */
+ async getWatchedFiles() {
+ return this.watchTargets.getTargets();
+ }
+
+ getChokidarConfig() {
+ let ignores = this.eleventyFiles.getGlobWatcherIgnores();
+ debug("Ignoring watcher changes to: %o", ignores);
+
+ let configOptions = this.config.chokidarConfig;
+
+ // can’t override these yet
+ // TODO maybe if array, merge the array?
+ delete configOptions.ignored;
+
+ return Object.assign(
+ {
+ ignored: ignores,
+ ignoreInitial: true,
+ awaitWriteFinish: {
+ stabilityThreshold: 150,
+ pollInterval: 25,
+ },
+ },
+ configOptions,
+ );
+ }
+
+ /**
+ * Start the watching of files
+ *
+ * @async
+ * @method
+ */
+ async watch() {
+ this.watcherBench.setMinimumThresholdMs(500);
+ this.watcherBench.reset();
+
+ // We use a string module name and try/catch here to hide this from the zisi and esbuild serverless bundlers
+ let chokidar;
+ // eslint-disable-next-line no-useless-catch
+ try {
+ let moduleName = "chokidar";
+ let chokidarImport = await import(moduleName);
+ chokidar = chokidarImport.default;
+ } catch (e) {
+ throw e;
+ }
+
+ // Note that watching indirectly depends on this for fetching dependencies from JS files
+ // See: TemplateWriter:pathCache and EleventyWatchTargets
+ await this.write();
+
+ let initWatchBench = this.watcherBench.get("Start up --watch");
+ initWatchBench.before();
+
+ await this.initWatch();
+
+ // TODO improve unwatching if JS dependencies are removed (or files are deleted)
+ let rawFiles = await this.getWatchedFiles();
+ debug("Watching for changes to: %o", rawFiles);
+
+ let options = this.getChokidarConfig();
+
+ // Remap all paths to `cwd` if in play (Issue #3854)
+ let remapper = new FileSystemRemap(rawFiles);
+ let cwd = remapper.getCwd();
+
+ if (cwd) {
+ options.cwd = cwd;
+
+ rawFiles = remapper.getInput().map((entry) => {
+ return TemplatePath.stripLeadingDotSlash(entry);
+ });
+
+ options.ignored = remapper.getRemapped(options.ignored || []).map((entry) => {
+ return TemplatePath.stripLeadingDotSlash(entry);
+ });
+ }
+
+ let watcher = chokidar.watch(rawFiles, options);
+
+ initWatchBench.after();
+
+ this.watcherBench.finish("Watch");
+
+ this.logger.forceLog("Watching…");
+
+ this.watcher = watcher;
+
+ let watchDelay;
+ let watchRun = async (path) => {
+ path = TemplatePath.normalize(path);
+ try {
+ let isResetConfig = this.#shouldResetConfig([path]);
+ this.#addFileToWatchQueue(path, isResetConfig);
+
+ clearTimeout(watchDelay);
+
+ let { promise, resolve, reject } = withResolvers();
+
+ watchDelay = setTimeout(async () => {
+ this.#watch(isResetConfig).then(resolve, reject);
+ }, this.config.watchThrottleWaitTime);
+
+ await promise;
+ } catch (e) {
+ if (e instanceof EleventyBaseError) {
+ this.errorHandler.error(e, "Eleventy watch error");
+ this.watchManager.setBuildFinished();
+ } else {
+ this.errorHandler.fatal(e, "Eleventy fatal watch error");
+ await this.stopWatch();
+ }
+ }
+
+ this.config.events.emit("eleventy.afterwatch");
+ };
+
+ watcher.on("change", async (path) => {
+ // Emulated passthrough copy logs from the server
+ if (!this.eleventyServe.isEmulatedPassthroughCopyMatch(path)) {
+ this.logger.forceLog(`File changed: ${TemplatePath.standardizeFilePath(path)}`);
+ }
+
+ await watchRun(path);
+ });
+
+ watcher.on("add", async (path) => {
+ // Emulated passthrough copy logs from the server
+ if (!this.eleventyServe.isEmulatedPassthroughCopyMatch(path)) {
+ this.logger.forceLog(`File added: ${TemplatePath.standardizeFilePath(path)}`);
+ }
+
+ this.fileSystemSearch.add(path);
+ await watchRun(path);
+ });
+
+ watcher.on("unlink", (path) => {
+ this.logger.forceLog(`File deleted: ${TemplatePath.standardizeFilePath(path)}`);
+ this.fileSystemSearch.delete(path);
+ });
+
+ // wait for chokidar to be ready.
+ await new Promise((resolve) => {
+ watcher.on("ready", () => resolve());
+ });
+
+ // Returns for testability
+ return watchRun;
+ }
+
+ async stopWatch() {
+ // Prevent multiple invocations.
+ if (this.#isStopping) {
+ return this.#isStopping;
+ }
+
+ debug("Cleaning up chokidar and server instances, if they exist.");
+ this.#isStopping = Promise.all([this.eleventyServe.close(), this.watcher?.close()]).then(() => {
+ this.#isStopping = false;
+ });
+
+ return this.#isStopping;
+ }
+
+ /**
+ * Serve Eleventy on this port.
+ *
+ * @param {Number} port - The HTTP port to serve Eleventy from.
+ */
+ async serve(port) {
+ // Port is optional and in this case likely via --port on the command line
+ // May defer to configuration API options `port` property
+ return this.eleventyServe.serve(port);
+ }
+
+ /**
+ * Writes templates to the file system.
+ *
+ * @async
+ * @method
+ * @returns {Promise<{Array}>}
+ */
+ async write() {
+ return this.executeBuild("fs");
+ }
+
+ /**
+ * Renders templates to a JSON object.
+ *
+ * @async
+ * @method
+ * @returns {Promise<{Array}>}
+ */
+ async toJSON() {
+ return this.executeBuild("json");
+ }
+
+ /**
+ * Returns a stream of new line delimited (NDJSON) objects
+ *
+ * @async
+ * @method
+ * @returns {Promise<{ReadableStream}>}
+ */
+ async toNDJSON() {
+ return this.executeBuild("ndjson");
+ }
+
+ /**
+ * tbd.
+ *
+ * @async
+ * @method
+ * @returns {Promise<{Array,ReadableStream}>} ret - tbd.
+ */
+ async executeBuild(to = "fs") {
+ if (this.#needsInit) {
+ if (!this.#initPromise) {
+ this.#initPromise = this.init();
+ }
+ await this.#initPromise.then(() => {
+ // #needsInit also set to false at the end of `init()`
+ this.#needsInit = false;
+ this.#initPromise = undefined;
+ });
+ }
+
+ if (!this.writer) {
+ throw new Error(
+ "Internal error: Eleventy didn’t run init() properly and wasn’t able to create a TemplateWriter.",
+ );
+ }
+
+ let incrementalFile =
+ this.programmaticApiIncrementalFile || this.watchManager?.getIncrementalFile();
+ if (incrementalFile) {
+ this.writer.setIncrementalFile(incrementalFile);
+ }
+
+ let returnObj;
+ let hasError = false;
+
+ try {
+ let directories = this.directories.getUserspaceInstance();
+ let eventsArg = {
+ directories,
+
+ // v3.0.0-alpha.6, changed to use `directories` instead (this was only used by serverless plugin)
+ inputDir: directories.input,
+
+ // Deprecated (not normalized) use `directories` instead.
+ dir: this.config.dir,
+
+ runMode: this.runMode,
+ outputMode: to,
+ incremental: this.isIncremental,
+ };
+
+ await this.config.events.emit("beforeBuild", eventsArg);
+ await this.config.events.emit("eleventy.before", eventsArg);
+
+ let promise;
+ if (to === "fs") {
+ promise = this.writer.write();
+ } else if (to === "json") {
+ promise = this.writer.getJSON("json");
+ } else if (to === "ndjson") {
+ promise = this.writer.getJSON("ndjson");
+ } else {
+ throw new Error(
+ `Invalid argument for \`Eleventy->executeBuild(${to})\`, expected "json", "ndjson", or "fs".`,
+ );
+ }
+
+ let resolved = await promise;
+
+ // Passing the processed output to the eleventy.after event (2.0+)
+ eventsArg.results = resolved.templates;
+
+ if (to === "ndjson") {
+ // return a stream
+ // TODO this outputs all ndjson rows after all the templates have been written to the stream
+ returnObj = this.logger.closeStream();
+ } else if (to === "json") {
+ // Backwards compat
+ returnObj = resolved.templates;
+ } else {
+ // Backwards compat
+ returnObj = [resolved.passthroughCopy, resolved.templates];
+ }
+
+ this.unsetIncrementalFile();
+ this.writer.resetIncrementalFile();
+
+ eventsArg.uses = this.eleventyConfig.usesGraph.map;
+ await this.config.events.emit("afterBuild", eventsArg);
+ await this.config.events.emit("eleventy.after", eventsArg);
+
+ this.buildCount++;
+ } catch (error) {
+ hasError = true;
+
+ // Issue #2405: Don’t change the exitCode for programmatic scripts
+ let errorSeverity = this.source === "script" ? "error" : "fatal";
+ this.errorHandler.once(errorSeverity, error, "Problem writing Eleventy templates");
+
+ // TODO ndjson should stream the error but https://github.com/11ty/eleventy/issues/3382
+ throw error;
+ } finally {
+ this.bench.finish();
+
+ if (to === "fs") {
+ this.logger.logWithOptions({
+ message: this.logFinished(),
+ color: hasError ? "red" : "green",
+ force: true,
+ });
+ }
+
+ debug("Finished.");
+
+ debug(`
+Have a suggestion/feature request/feedback? Feeling frustrated? I want to hear it!
+Open an issue: https://github.com/11ty/eleventy/issues/new`);
+ }
+
+ return returnObj;
+ }
+}
+
+export default Eleventy;
+
+// extend for exporting to CJS
+Object.assign(RenderPlugin, RenderPluginExtras);
+Object.assign(I18nPlugin, I18nPluginExtras);
+Object.assign(HtmlBasePlugin, HtmlBasePluginExtras);
+
+// Removed plugins
+
+const EleventyServerlessBundlerPlugin = function () {
+ throw new Error(
+ "Following feedback from our Community Survey, low interest in this plugin prompted its removal from Eleventy core in 3.0 as we refocus on static sites. Learn more: https://v3.11ty.dev/docs/plugins/serverless/",
+ );
+};
+
+const EleventyEdgePlugin = function () {
+ throw new Error(
+ "Following feedback from our Community Survey, low interest in this plugin prompted its removal from Eleventy core in 3.0 as we refocus on static sites. Learn more: https://v3.11ty.dev/docs/plugins/edge/",
+ );
+};
+
+export {
+ Eleventy,
+ EleventyImport as ImportFile,
+
+ // Error messages for removed plugins
+ EleventyServerlessBundlerPlugin as EleventyServerless,
+ EleventyServerlessBundlerPlugin,
+ EleventyEdgePlugin,
+
+ /**
+ * @type {module:11ty/eleventy/Plugins/RenderPlugin}
+ */
+ RenderPlugin as EleventyRenderPlugin, // legacy name
+ /**
+ * @type {module:11ty/eleventy/Plugins/RenderPlugin}
+ */
+ RenderPlugin,
+
+ /**
+ * @type {module:11ty/eleventy/Plugins/I18nPlugin}
+ */
+ I18nPlugin as EleventyI18nPlugin, // legacy name
+ /**
+ * @type {module:11ty/eleventy/Plugins/I18nPlugin}
+ */
+ I18nPlugin,
+
+ /**
+ * @type {module:11ty/eleventy/Plugins/HtmlBasePlugin}
+ */
+ HtmlBasePlugin as EleventyHtmlBasePlugin, // legacy name
+ /**
+ * @type {module:11ty/eleventy/Plugins/HtmlBasePlugin}
+ */
+ HtmlBasePlugin,
+
+ /**
+ * @type {module:11ty/eleventy/Plugins/InputPathToUrlTransformPlugin}
+ */
+ InputPathToUrlTransformPlugin,
+
+ /**
+ * @type {module:11ty/eleventy-plugin-bundle}
+ */
+ BundlePlugin,
+
+ /**
+ * @type {module:11ty/eleventy/Plugins/IdAttributePlugin}
+ */
+ IdAttributePlugin,
+};
diff --git a/node_modules/@11ty/eleventy/src/EleventyCommonJs.cjs b/node_modules/@11ty/eleventy/src/EleventyCommonJs.cjs
new file mode 100644
index 0000000..5227265
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/EleventyCommonJs.cjs
@@ -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. Upgrade your Node version (at time of writing, v22.12 or newer) to enable this behavior. If you use a version
+ of Node older than v22.12, try the --experimental-require-module command line flag in Node. Read more:
+ https://nodejs.org/api/modules.html#loading-ecmascript-modules-using-require`);
+
+ 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;
diff --git a/node_modules/@11ty/eleventy/src/EleventyExtensionMap.js b/node_modules/@11ty/eleventy/src/EleventyExtensionMap.js
new file mode 100644
index 0000000..8f42640
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/EleventyExtensionMap.js
@@ -0,0 +1,284 @@
+import { TemplatePath } from "@11ty/eleventy-utils";
+
+class EleventyExtensionMap {
+ #engineManager;
+
+ constructor(config) {
+ this.setTemplateConfig(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));
+ }
+
+ setTemplateConfig(config) {
+ if (!config || config.constructor.name !== "TemplateConfig") {
+ throw new Error("Internal error: Missing or invalid `config` argument.");
+ }
+
+ this.templateConfig = config;
+ }
+
+ get config() {
+ return this.templateConfig.getConfig();
+ }
+
+ get engineManager() {
+ if (!this.#engineManager) {
+ throw new Error("Internal error: Missing `#engineManager` in EleventyExtensionMap.");
+ }
+
+ return this.#engineManager;
+ }
+
+ set engineManager(mgr) {
+ this.#engineManager = mgr;
+ }
+
+ 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 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);
+ }
+ }
+
+ let dir = TemplatePath.convertToRecursiveGlobSync(inputDir);
+ 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;
diff --git a/node_modules/@11ty/eleventy/src/EleventyFiles.js b/node_modules/@11ty/eleventy/src/EleventyFiles.js
new file mode 100644
index 0000000..7ec6379
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/EleventyFiles.js
@@ -0,0 +1,521 @@
+import fs from "node:fs";
+
+import { TemplatePath, isPlainObject } from "@11ty/eleventy-utils";
+import debugUtil from "debug";
+
+import DirContains from "./Util/DirContains.js";
+import TemplateData from "./Data/TemplateData.js";
+import TemplateGlob from "./TemplateGlob.js";
+import checkPassthroughCopyBehavior from "./Util/PassthroughCopyBehaviorCheck.js";
+
+const debug = debugUtil("Eleventy:EleventyFiles");
+
+class EleventyFiles {
+ #extensionMap;
+ #watcherGlobs;
+
+ constructor(formats, templateConfig) {
+ if (!templateConfig) {
+ throw new Error("Internal error: Missing `templateConfig`` argument.");
+ }
+
+ this.templateConfig = templateConfig;
+ this.config = templateConfig.getConfig();
+ this.aggregateBench = this.config.benchmarkManager.get("Aggregate");
+
+ this.formats = formats;
+ this.eleventyIgnoreContent = false;
+ }
+
+ get dirs() {
+ return this.templateConfig.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.setupGlobs();
+ }
+
+ #getWatcherGlobs() {
+ if (!this.#watcherGlobs) {
+ let globs;
+ // Input is a file
+ if (this.inputFile) {
+ globs = this.templateGlobs;
+ } else {
+ // input is a directory
+ globs = this.extensionMap.getValidGlobs(this.inputDir);
+ }
+ this.#watcherGlobs = globs;
+ }
+
+ return this.#watcherGlobs;
+ }
+
+ 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.setupGlobs();
+ this._glob = null;
+ }
+
+ /* For testing */
+ _setConfig(config) {
+ if (!config.ignores) {
+ config.ignores = new Set();
+ config.ignores.add("**/node_modules/**");
+ }
+
+ this.config = config;
+
+ this.init();
+ }
+
+ /* 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) {
+ throw new Error("Internal error: missing `extensionMap` in EleventyFiles.");
+ }
+ return this.#extensionMap;
+ }
+
+ setRunMode(runMode) {
+ this.runMode = runMode;
+ }
+
+ setPassthroughManager(mgr) {
+ this.passthroughManager = mgr;
+ }
+
+ set templateData(templateData) {
+ this._templateData = templateData;
+ }
+
+ get templateData() {
+ if (!this._templateData) {
+ this._templateData = new TemplateData(this.templateConfig);
+ }
+
+ return this._templateData;
+ }
+
+ setupGlobs() {
+ this.fileIgnores = this.getIgnores();
+ this.extraIgnores = this.getIncludesAndDataDirs();
+ this.uniqueIgnores = this.getIgnoreGlobs();
+
+ // Conditional added for tests that don’t have a config
+ if (this.config?.events) {
+ this.config.events.emit("eleventy.ignores", this.uniqueIgnores);
+ }
+
+ this.normalizedTemplateGlobs = this.templateGlobs;
+ }
+
+ normalizeIgnoreEntry(entry) {
+ if (!entry.startsWith("**/")) {
+ return TemplateGlob.normalizePath(this.localPathRoot || ".", entry);
+ }
+ return entry;
+ }
+
+ getIgnoreGlobs() {
+ let uniqueIgnores = new Set();
+ for (let ignore of this.fileIgnores) {
+ 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(this.normalizeIgnoreEntry(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;
+ }
+
+ /* Tests only */
+ _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);
+ }
+
+ // Make sure output dir isn’t in the input dir (or it will ignore all input!)
+ // input: . and output: . (skip ignore)
+ // input: ./content and output . (skip ignore)
+ // input: . and output: ./_site (add ignore)
+ let outputContainsInputDir = DirContains(this.outputDir, this.inputDir);
+ if (!outputContainsInputDir) {
+ // both are already normalized in 3.0
+ files.add(TemplateGlob.map(this.outputDir + "/**"));
+ }
+
+ 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.templateConfig.logger.warn(
+ `The virtual template at ${fullVirtualPath} is using a template format that’s not valid for your project. Your project is using: "${this.formats}". Read more about formats: https://v3.11ty.dev/docs/config/#template-formats`,
+ );
+ }
+ 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();
+
+ let globs = this.#getWatcherGlobs();
+
+ if (checkPassthroughCopyBehavior(this.config, this.runMode)) {
+ return globs.concat(directoryGlobs);
+ }
+
+ // Revert to old passthroughcopy copy files behavior
+ return globs.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 isn’t 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/**",
+ ".git/**",
+ // TODO outputDir
+ // this.outputDir,
+ ],
+ }),
+ );
+ 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(this.normalizeIgnoreEntry(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;
diff --git a/node_modules/@11ty/eleventy/src/EleventyServe.js b/node_modules/@11ty/eleventy/src/EleventyServe.js
new file mode 100644
index 0000000..65525ef
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/EleventyServe.js
@@ -0,0 +1,321 @@
+import assert from "node:assert";
+
+import debugUtil from "debug";
+import { Merge, DeepCopy, TemplatePath } from "@11ty/eleventy-utils";
+
+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() {},
+ // ready: function(server) {},
+ // logger: { info: function() {}, error: function() {} }
+};
+
+class EleventyServe {
+ #eleventyConfig;
+
+ 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("It’s 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 import("@11ty/eleventy-dev-server").then((i) => i.default);
+ }
+
+ // 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. We’re using the default server instead.\n" +
+ e.message,
+ );
+ debug("Eleventy server error %o", e);
+ return import("@11ty/eleventy-dev-server").then((i) => i.default);
+ }
+ }
+
+ 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);
+
+ if (typeof this.config.serverOptions?.ready === "function") {
+ if (typeof this.server.ready === "function") {
+ // Dev Server 2.0.7+
+ // wait for ready promise to resolve before triggering ready callback
+ await this.server.ready();
+ await this.config.serverOptions?.ready(this.server);
+ } else {
+ throw new Error(
+ "The `ready` option in Eleventy’s `setServerOptions` method requires a `ready` function on the Dev Server instance. If you’re using Eleventy Dev Server, you will need Dev Server 2.0.7+ or newer to use this feature.",
+ );
+ }
+ }
+ }
+
+ 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 don’t 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, we’re restarting the server");
+ await this.restart();
+ } else {
+ await this.server.reload(reloadEvent);
+ }
+ }
+}
+
+export default EleventyServe;
diff --git a/node_modules/@11ty/eleventy/src/EleventyWatch.js b/node_modules/@11ty/eleventy/src/EleventyWatch.js
new file mode 100755
index 0000000..22dffbe
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/EleventyWatch.js
@@ -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 don’t 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;
diff --git a/node_modules/@11ty/eleventy/src/EleventyWatchTargets.js b/node_modules/@11ty/eleventy/src/EleventyWatchTargets.js
new file mode 100644
index 0000000..aec2036
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/EleventyWatchTargets.js
@@ -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 target’s 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;
diff --git a/node_modules/@11ty/eleventy/src/Engines/Custom.js b/node_modules/@11ty/eleventy/src/Engines/Custom.js
new file mode 100644
index 0000000..17a0da1
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Engines/Custom.js
@@ -0,0 +1,339 @@
+import TemplateEngine from "./TemplateEngine.js";
+import getJavaScriptData from "../Util/GetJavaScriptData.js";
+
+export default 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.setDefaultEngine(undefined);
+ }
+
+ 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;
+ }
+
+ get cacheable() {
+ // Enable cacheability for this template
+ if (this.entry?.compileOptions?.cache !== undefined) {
+ return this.entry.compileOptions.cache;
+ } else if (this.needsToReadFileContents()) {
+ return true;
+ } else if (this._defaultEngine?.cacheable !== undefined) {
+ return this._defaultEngine.cacheable;
+ }
+
+ return super.cacheable;
+ }
+
+ async getInstanceFromInputPath(inputPath) {
+ if (
+ "getInstanceFromInputPath" in this.entry &&
+ 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 compiledFn = 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 (compiledFn) {
+ // Bind defaultRenderer to render function
+ if ("then" in compiledFn && typeof compiledFn.then === "function") {
+ // Promise, wait to bind
+ return compiledFn.then((fn) => {
+ if (typeof fn === "function") {
+ return fn.bind({
+ defaultRenderer: defaultCompilationFn,
+ });
+ }
+ return fn;
+ });
+ } else if ("bind" in compiledFn && typeof compiledFn.bind === "function") {
+ return compiledFn.bind({
+ defaultRenderer: defaultCompilationFn,
+ });
+ }
+ }
+
+ return compiledFn;
+ }
+
+ 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) {
+ let lastModifiedFile = this.eleventyConfig.getPreviousBuildModifiedFile();
+ // Return this separately so we know whether or not to use the cached version
+ // but still return a key to cache this new render for next time
+ let isRelevant = this.isFileRelevantTo(inputPath, lastModifiedFile, false);
+ let useCache = !isRelevant;
+
+ if (this.entry.compileOptions && "getCacheKey" in this.entry.compileOptions) {
+ if (typeof this.entry.compileOptions.getCacheKey !== "function") {
+ 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
+ // Note: `false` is the same as "raw" here.
+ return false;
+ }
+
+ static shouldSpiderJavaScriptDependencies(entry) {
+ if (entry.compileOptions && "spiderJavaScriptDependencies" in entry.compileOptions) {
+ return entry.compileOptions.spiderJavaScriptDependencies;
+ }
+
+ return false;
+ }
+}
diff --git a/node_modules/@11ty/eleventy/src/Engines/FrontMatter/JavaScript.js b/node_modules/@11ty/eleventy/src/Engines/FrontMatter/JavaScript.js
new file mode 100644
index 0000000..b91ba36
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Engines/FrontMatter/JavaScript.js
@@ -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 it’s handled in Eleventy upstream.
+ return vm.getGlobalContext(data, {
+ reuseGlobal: true,
+ dynamicImport: true,
+ // addRequire: true,
+ });
+}
diff --git a/node_modules/@11ty/eleventy/src/Engines/Html.js b/node_modules/@11ty/eleventy/src/Engines/Html.js
new file mode 100644
index 0000000..a0f4101
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Engines/Html.js
@@ -0,0 +1,33 @@
+import TemplateEngine from "./TemplateEngine.js";
+
+export default class Html extends TemplateEngine {
+ constructor(name, eleventyConfig) {
+ super(name, eleventyConfig);
+ }
+
+ get cacheable() {
+ return true;
+ }
+
+ async #getPreEngine(preTemplateEngine) {
+ return this.engineManager.getEngine(preTemplateEngine, this.extensionMap);
+ }
+
+ async compile(str, inputPath, preTemplateEngine) {
+ if (preTemplateEngine) {
+ let engine = await this.#getPreEngine(preTemplateEngine);
+ let fnReady = engine.compile(str, inputPath);
+
+ return async function (data) {
+ let fn = await fnReady;
+
+ return fn(data);
+ };
+ }
+
+ return function () {
+ // do nothing with data if preTemplateEngine is falsy
+ return str;
+ };
+ }
+}
diff --git a/node_modules/@11ty/eleventy/src/Engines/JavaScript.js b/node_modules/@11ty/eleventy/src/Engines/JavaScript.js
new file mode 100644
index 0000000..29b3b7c
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Engines/JavaScript.js
@@ -0,0 +1,240 @@
+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 { EleventyImport } from "../Util/Require.js";
+import { augmentFunction, augmentObject } from "./Util/ContextAugmenter.js";
+
+class JavaScriptTemplateNotDefined extends EleventyBaseError {}
+
+export default class JavaScript extends TemplateEngine {
+ constructor(name, templateConfig) {
+ super(name, templateConfig);
+ this.instances = {};
+
+ this.config.events.on("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];
+ }
+ }
+ });
+ }
+
+ get cacheable() {
+ return false;
+ }
+
+ 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();
+ let cacheBust = !this.cacheable || !this.config.useTemplateCache;
+ mod = await EleventyImport(inputPath, isEsm ? "esm" : "cjs", {
+ cacheBust,
+ });
+ }
+
+ 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 (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));
+ };
+ }
+ }
+
+ static shouldSpiderJavaScriptDependencies() {
+ return true;
+ }
+}
diff --git a/node_modules/@11ty/eleventy/src/Engines/Liquid.js b/node_modules/@11ty/eleventy/src/Engines/Liquid.js
new file mode 100644
index 0000000..44fdab4
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Engines/Liquid.js
@@ -0,0 +1,331 @@
+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");
+
+export default 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);
+ }
+
+ get cacheable() {
+ return 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, Boolean(this.config.libraryOverrides.liquid));
+
+ 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 *don’t* 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) {
+ if (!str) {
+ return [];
+ }
+
+ 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;
+ }
+
+ // Don’t 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);
+ };
+ }
+}
diff --git a/node_modules/@11ty/eleventy/src/Engines/Markdown.js b/node_modules/@11ty/eleventy/src/Engines/Markdown.js
new file mode 100644
index 0000000..ec1e1f6
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Engines/Markdown.js
@@ -0,0 +1,100 @@
+import markdownIt from "markdown-it";
+
+import TemplateEngine from "./TemplateEngine.js";
+
+export default class Markdown extends TemplateEngine {
+ constructor(name, eleventyConfig) {
+ super(name, eleventyConfig);
+
+ this.markdownOptions = {};
+
+ this.setLibrary(this.config.libraryOverrides.md);
+ }
+
+ get cacheable() {
+ return 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, Boolean(this.config.libraryOverrides.md));
+ }
+
+ 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 || {},
+ );
+ }
+
+ // TODO use preTemplateEngine to help inform this
+ // needsCompilation() {
+ // return super.needsCompilation();
+ // }
+
+ async #getPreEngine(preTemplateEngine) {
+ if (typeof preTemplateEngine === "string") {
+ return this.engineManager.getEngine(preTemplateEngine, this.extensionMap);
+ }
+
+ return preTemplateEngine;
+ }
+
+ async compile(str, inputPath, preTemplateEngine, bypassMarkdown) {
+ let mdlib = this.mdLib;
+
+ if (preTemplateEngine) {
+ let engine = await this.#getPreEngine(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);
+ };
+ }
+ }
+ }
+}
diff --git a/node_modules/@11ty/eleventy/src/Engines/Nunjucks.js b/node_modules/@11ty/eleventy/src/Engines/Nunjucks.js
new file mode 100755
index 0000000..70cca17
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Engines/Nunjucks.js
@@ -0,0 +1,482 @@
+import NunjucksLib from "nunjucks";
+import debugUtil from "debug";
+import { TemplatePath } from "@11ty/eleventy-utils";
+
+import TemplateEngine from "./TemplateEngine.js";
+import EleventyBaseError from "../Errors/EleventyBaseError.js";
+import { augmentObject } from "./Util/ContextAugmenter.js";
+import { withResolvers } from "../Util/PromiseUtil.js";
+
+const debug = debugUtil("Eleventy:Nunjucks");
+
+class EleventyNunjucksError extends EleventyBaseError {}
+
+export default 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);
+ }
+
+ // v3.1.0-alpha.1 we’ve moved to use Nunjucks’ internal cache instead of Eleventy’s
+ // get cacheable() {
+ // return false;
+ // }
+
+ #getFileSystemDirs() {
+ let paths = new Set();
+ paths.add(super.getIncludesDir());
+ paths.add(TemplatePath.getWorkingDir());
+
+ // Filter out undefined paths
+ return Array.from(paths).filter(Boolean);
+ }
+
+ #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 fsLoader = new NunjucksLib.FileSystemLoader(this.#getFileSystemDirs());
+ 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);
+
+ // Note that a new Nunjucks engine instance is created for subsequent builds
+ // Eleventy Nunjucks is set to `cacheable` false above to opt out of Eleventy cache
+ this.config.events.on("eleventy#templateModified", (templatePath) => {
+ // NunjucksEnvironment:
+ // loader.pathToNames: {'ABSOLUTE_PATH/src/_includes/components/possum-home.css': 'components/possum-home.css'}
+ // loader.cache: { 'components/possum-home.css': [Template] }
+ // Nunjucks stores these as Operating System native paths
+ let absTmplPath = TemplatePath.normalizeOperatingSystemFilePath(
+ TemplatePath.absolutePath(templatePath),
+ );
+ for (let loader of this.njkEnv.loaders) {
+ let nunjucksName = loader.pathsToNames[absTmplPath];
+ if (nunjucksName) {
+ debug(
+ "Match found in Nunjucks cache via templateModified for %o, clearing this entry",
+ templatePath,
+ );
+ delete loader.pathsToNames[absTmplPath];
+ delete loader.cache[nunjucksName];
+ }
+ }
+
+ // Behavior prior to v3.1.0-alpha.1:
+ // this.njkEnv.invalidateCache();
+ });
+
+ this.setEngineLib(this.njkEnv, Boolean(this.config.libraryOverrides.njk));
+
+ 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());
+ }
+
+ // Don’t 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) {
+ if (!str) {
+ return [];
+ }
+ const { parser, nodes } = NunjucksLib;
+ let obj = parser.parse(str, this._getParseExtensions());
+ if (!obj) {
+ return [];
+ }
+ 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) {
+ let { promise, resolve, reject } = withResolvers();
+
+ tmpl.render(data, (error, result) => {
+ if (error) {
+ reject(error);
+ } else {
+ resolve(result);
+ }
+ });
+
+ return promise;
+ };
+ }
+}
diff --git a/node_modules/@11ty/eleventy/src/Engines/TemplateEngine.js b/node_modules/@11ty/eleventy/src/Engines/TemplateEngine.js
new file mode 100644
index 0000000..234aa4e
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Engines/TemplateEngine.js
@@ -0,0 +1,206 @@
+import debugUtil from "debug";
+import EleventyBaseError from "../Errors/EleventyBaseError.js";
+
+class TemplateEngineConfigError extends EleventyBaseError {}
+
+const debug = debugUtil("Eleventy:TemplateEngine");
+
+const AMENDED_INSTANCES = new Set();
+
+export default class TemplateEngine {
+ #extensionMap;
+ #engineManager;
+ #benchmarks;
+
+ constructor(name, eleventyConfig) {
+ this.name = name;
+
+ this.engineLib = null;
+
+ if (!eleventyConfig) {
+ throw new TemplateEngineConfigError("Missing `eleventyConfig` argument.");
+ }
+ this.eleventyConfig = eleventyConfig;
+ }
+
+ get cacheable() {
+ return false;
+ }
+
+ 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) {
+ throw new Error("Internal error: missing `extensionMap` in TemplateEngine.");
+ }
+ 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, isOverrideViaSetLibrary = false) {
+ this.engineLib = engineLib;
+
+ // Run engine amendments (via issue #2438)
+ // Issue #3816: this isn’t ideal but there is no other way to reset a markdown instance if it was also overridden by addLibrary
+ if (AMENDED_INSTANCES.has(engineLib)) {
+ return;
+ }
+
+ if (isOverrideViaSetLibrary) {
+ AMENDED_INSTANCES.add(engineLib);
+ }
+ debug(
+ "Running amendLibrary for %o (number of amendments: %o)",
+ this.name,
+ this.config.libraryAmendments[this.name]?.length,
+ );
+
+ for (let amendment of this.config.libraryAmendments[this.name] || []) {
+ // TODO it’d 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);
+ }
+}
diff --git a/node_modules/@11ty/eleventy/src/Engines/TemplateEngineManager.js b/node_modules/@11ty/eleventy/src/Engines/TemplateEngineManager.js
new file mode 100644
index 0000000..913a803
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Engines/TemplateEngineManager.js
@@ -0,0 +1,193 @@
+import debugUtil from "debug";
+import EleventyBaseError from "../Errors/EleventyBaseError.js";
+
+const debug = debugUtil("Eleventy:TemplateEngineManager");
+
+class TemplateEngineManager {
+ constructor(eleventyConfig) {
+ if (!eleventyConfig || eleventyConfig.constructor.name !== "TemplateConfig") {
+ throw new EleventyBaseError("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);
+ }
+
+ 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 they’re parsed by a bundler.
+ if (extension === "md") {
+ promise = import("./Markdown.js").then((mod) => mod.default);
+ } else if (extension === "html") {
+ promise = import("./Html.js").then((mod) => mod.default);
+ } else if (extension === "njk") {
+ promise = import("./Nunjucks.js").then((mod) => mod.default);
+ } else if (extension === "liquid") {
+ promise = import("./Liquid.js").then((mod) => mod.default);
+ } else if (extension === "11ty.js") {
+ promise = import("./JavaScript.js").then((mod) => mod.default);
+ } else {
+ promise = this.getCustomEngineClass();
+ }
+
+ this.importCache[extension] = promise;
+
+ return promise;
+ }
+
+ async getCustomEngineClass() {
+ if (!this._CustomEngine) {
+ this._CustomEngine = import("./Custom.js").then((mod) => mod.default);
+ }
+ 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;
+ }
+
+ isEngineRemovedFromCore(name) {
+ return ["ejs", "hbs", "mustache", "haml", "pug"].includes(name) && !this.hasEngine(name);
+ }
+
+ async getEngine(name, extensionMap) {
+ // Bundled 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 engine 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;
diff --git a/node_modules/@11ty/eleventy/src/Engines/Util/ContextAugmenter.js b/node_modules/@11ty/eleventy/src/Engines/Util/ContextAugmenter.js
new file mode 100644
index 0000000..dd5fbc6
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Engines/Util/ContextAugmenter.js
@@ -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 };
diff --git a/node_modules/@11ty/eleventy/src/Errors/DuplicatePermalinkOutputError.js b/node_modules/@11ty/eleventy/src/Errors/DuplicatePermalinkOutputError.js
new file mode 100644
index 0000000..444195c
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Errors/DuplicatePermalinkOutputError.js
@@ -0,0 +1,9 @@
+import EleventyBaseError from "./EleventyBaseError.js";
+
+class DuplicatePermalinkOutputError extends EleventyBaseError {
+ get removeDuplicateErrorStringFromOutput() {
+ return true;
+ }
+}
+
+export default DuplicatePermalinkOutputError;
diff --git a/node_modules/@11ty/eleventy/src/Errors/EleventyBaseError.js b/node_modules/@11ty/eleventy/src/Errors/EleventyBaseError.js
new file mode 100644
index 0000000..6e76c5f
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Errors/EleventyBaseError.js
@@ -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;
diff --git a/node_modules/@11ty/eleventy/src/Errors/EleventyErrorHandler.js b/node_modules/@11ty/eleventy/src/Errors/EleventyErrorHandler.js
new file mode 100644
index 0000000..879e65c
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Errors/EleventyErrorHandler.js
@@ -0,0 +1,152 @@
+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) {
+ // Don’t show the full error stack trace
+ showStack = false;
+ }
+
+ let totalErrorCount = EleventyErrorHandler.getTotalErrorCount(e);
+ let ref = e;
+ let index = 1;
+ while (ref) {
+ let nextRef = ref.originalError;
+
+ // Unwrap cause from error and assign it to what Eleventy expects
+ if (nextRef?.cause) {
+ nextRef.originalError = nextRef.cause?.originalError ?? nextRef?.cause;
+ }
+
+ 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 };
diff --git a/node_modules/@11ty/eleventy/src/Errors/EleventyErrorUtil.js b/node_modules/@11ty/eleventy/src/Errors/EleventyErrorUtil.js
new file mode 100644
index 0000000..6b374d0
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Errors/EleventyErrorUtil.js
@@ -0,0 +1,70 @@
+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 engine’s 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?.cause instanceof TemplateContentPrematureUseError || // Custom (per Node-convention)
+ ["RenderError", "UndefinedVariableError"].includes(e?.originalError?.name) && e?.originalError?.originalError instanceof TemplateContentPrematureUseError || // Liquid
+ e?.message?.includes("TemplateContentPrematureUseError") // Nunjucks
+ );
+ }
+}
+
+export default EleventyErrorUtil;
diff --git a/node_modules/@11ty/eleventy/src/Errors/TemplateContentPrematureUseError.js b/node_modules/@11ty/eleventy/src/Errors/TemplateContentPrematureUseError.js
new file mode 100644
index 0000000..5266cd2
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Errors/TemplateContentPrematureUseError.js
@@ -0,0 +1,5 @@
+import EleventyBaseError from "./EleventyBaseError.js";
+
+class TemplateContentPrematureUseError extends EleventyBaseError {}
+
+export default TemplateContentPrematureUseError;
diff --git a/node_modules/@11ty/eleventy/src/Errors/TemplateContentUnrenderedTemplateError.js b/node_modules/@11ty/eleventy/src/Errors/TemplateContentUnrenderedTemplateError.js
new file mode 100644
index 0000000..ee270d5
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Errors/TemplateContentUnrenderedTemplateError.js
@@ -0,0 +1,5 @@
+import EleventyBaseError from "./EleventyBaseError.js";
+
+class TemplateContentUnrenderedTemplateError extends EleventyBaseError {}
+
+export default TemplateContentUnrenderedTemplateError;
diff --git a/node_modules/@11ty/eleventy/src/Errors/UsingCircularTemplateContentReferenceError.js b/node_modules/@11ty/eleventy/src/Errors/UsingCircularTemplateContentReferenceError.js
new file mode 100644
index 0000000..5608feb
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Errors/UsingCircularTemplateContentReferenceError.js
@@ -0,0 +1,5 @@
+import EleventyBaseError from "./EleventyBaseError.js";
+
+class UsingCircularTemplateContentReferenceError extends EleventyBaseError {}
+
+export default UsingCircularTemplateContentReferenceError;
diff --git a/node_modules/@11ty/eleventy/src/EventBus.js b/node_modules/@11ty/eleventy/src/EventBus.js
new file mode 100644
index 0000000..0aa4126
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/EventBus.js
@@ -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); // defaults to 10
+
+debug("EventBus max listener count: %o", bus.getMaxListeners());
+
+export default bus;
diff --git a/node_modules/@11ty/eleventy/src/FileSystemSearch.js b/node_modules/@11ty/eleventy/src/FileSystemSearch.js
new file mode 100644
index 0000000..972e80b
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/FileSystemSearch.js
@@ -0,0 +1,129 @@
+import { glob } from "tinyglobby";
+import { TemplatePath } from "@11ty/eleventy-utils";
+import debugUtil from "debug";
+
+import FileSystemRemap from "./Util/GlobRemap.js";
+import { isGlobMatch } from "./Util/GlobMatcher.js";
+
+const debug = debugUtil("Eleventy:FileSystemSearch");
+
+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));
+
+ let cwd = FileSystemRemap.getCwd(globs);
+ if (cwd) {
+ options.cwd = cwd;
+ }
+
+ if (options.ignore && Array.isArray(options.ignore)) {
+ options.ignore = options.ignore.map((entry) => {
+ entry = TemplatePath.stripLeadingDotSlash(entry);
+
+ return FileSystemRemap.remapInput(entry, cwd);
+ });
+ 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++;
+
+ globs = globs.map((entry) => {
+ if (cwd && entry.startsWith(cwd)) {
+ return FileSystemRemap.remapInput(entry, cwd);
+ }
+
+ return entry;
+ });
+
+ this.promises[cacheKey] = glob(
+ globs,
+ Object.assign(
+ {
+ caseSensitiveMatch: false, // insensitive
+ dot: true,
+ },
+ options,
+ ),
+ ).then((results) => {
+ this.outputs[cacheKey] = new Set(
+ results.map((entry) => {
+ let remapped = FileSystemRemap.remapOutput(entry, options.cwd);
+ return TemplatePath.standardizeFilePath(remapped);
+ }),
+ );
+
+ 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.standardizeFilePath(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");
+ }
+
+ // Issue #3859 get rid of chokidar globs
+ // getAllOutputFiles() {
+ // return Object.values(this.outputs).map(set => Array.from(set)).flat();
+ // }
+}
+
+export default FileSystemSearch;
diff --git a/node_modules/@11ty/eleventy/src/Filters/GetCollectionItem.js b/node_modules/@11ty/eleventy/src/Filters/GetCollectionItem.js
new file mode 100644
index 0000000..7512940
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Filters/GetCollectionItem.js
@@ -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];
+ }
+ }
+}
diff --git a/node_modules/@11ty/eleventy/src/Filters/GetCollectionItemIndex.js b/node_modules/@11ty/eleventy/src/Filters/GetCollectionItemIndex.js
new file mode 100644
index 0000000..9e12854
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Filters/GetCollectionItemIndex.js
@@ -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++;
+ }
+}
diff --git a/node_modules/@11ty/eleventy/src/Filters/GetLocaleCollectionItem.js b/node_modules/@11ty/eleventy/src/Filters/GetLocaleCollectionItem.js
new file mode 100644
index 0000000..1f96622
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Filters/GetLocaleCollectionItem.js
@@ -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;
diff --git a/node_modules/@11ty/eleventy/src/Filters/Slug.js b/node_modules/@11ty/eleventy/src/Filters/Slug.js
new file mode 100644
index 0000000..03a77cc
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Filters/Slug.js
@@ -0,0 +1,14 @@
+import slugify from "slugify";
+
+export default function (str, options = {}) {
+ return slugify(
+ "" + str,
+ Object.assign(
+ {
+ replacement: "-",
+ lower: true,
+ },
+ options,
+ ),
+ );
+}
diff --git a/node_modules/@11ty/eleventy/src/Filters/Slugify.js b/node_modules/@11ty/eleventy/src/Filters/Slugify.js
new file mode 100644
index 0000000..e84b42d
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Filters/Slugify.js
@@ -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,
+ ),
+ );
+}
diff --git a/node_modules/@11ty/eleventy/src/Filters/Url.js b/node_modules/@11ty/eleventy/src/Filters/Url.js
new file mode 100644
index 0000000..87fc1e2
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Filters/Url.js
@@ -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;
+}
diff --git a/node_modules/@11ty/eleventy/src/GlobalDependencyMap.js b/node_modules/@11ty/eleventy/src/GlobalDependencyMap.js
new file mode 100644
index 0000000..7e169da
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/GlobalDependencyMap.js
@@ -0,0 +1,463 @@
+import debugUtil from "debug";
+import { TemplatePath } from "@11ty/eleventy-utils";
+
+import JavaScriptDependencies from "./Util/JavaScriptDependencies.js";
+import PathNormalizer from "./Util/PathNormalizer.js";
+import { TemplateDepGraph } from "./Util/TemplateDepGraph.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:"; // must match TemplateDepGraph key
+
+ #map;
+ #templateConfig;
+ #cachedUserConfigurationCollectionApiNames;
+
+ static isCollection(entry) {
+ return entry.startsWith(this.COLLECTION_PREFIX);
+ }
+
+ static getTagName(entry) {
+ if (this.isCollection(entry)) {
+ return entry.slice(this.COLLECTION_PREFIX.length);
+ }
+ }
+
+ static getCollectionKeyForEntry(entry) {
+ return `${GlobalDependencyMap.COLLECTION_PREFIX}${entry}`;
+ }
+
+ reset() {
+ this.#map = undefined;
+ }
+
+ setIsEsm(isEsm) {
+ this.isEsm = isEsm;
+ }
+
+ setTemplateConfig(templateConfig) {
+ this.#templateConfig = templateConfig;
+
+ // These have leading dot slashes, but so do the paths from Eleventy
+ this.#templateConfig.userConfig.events.once("eleventy.layouts", async (layouts) => {
+ await this.addLayoutsToMap(layouts);
+ });
+ }
+
+ get userConfigurationCollectionApiNames() {
+ if (this.#cachedUserConfigurationCollectionApiNames) {
+ return this.#cachedUserConfigurationCollectionApiNames;
+ }
+ return Object.keys(this.#templateConfig.userConfig.getCollections()) || [];
+ }
+
+ initializeUserConfigurationApiCollections() {
+ this.addCollectionApiNames(this.userConfigurationCollectionApiNames);
+ }
+
+ // For Testing
+ setCollectionApiNames(names = []) {
+ this.#cachedUserConfigurationCollectionApiNames = names;
+ }
+
+ addCollectionApiNames(names = []) {
+ if (!names || names.length === 0) {
+ return;
+ }
+
+ for (let collectionName of names) {
+ this.map.addConfigCollectionName(collectionName);
+ }
+ }
+
+ filterOutLayouts(nodes = []) {
+ return nodes.filter((node) => {
+ if (GlobalDependencyMap.isLayoutNode(this.map, node)) {
+ return false;
+ }
+ return true;
+ });
+ }
+
+ filterOutCollections(nodes = []) {
+ return nodes.filter((node) => !node.startsWith(GlobalDependencyMap.COLLECTION_PREFIX));
+ }
+
+ static removeLayoutNodes(graph, nodeList) {
+ return nodeList.filter((node) => {
+ if (this.isLayoutNode(graph, node)) {
+ return false;
+ }
+ return true;
+ });
+ }
+
+ removeLayoutNodes(normalizedLayouts) {
+ let nodes = this.map.overallOrder();
+ for (let node of nodes) {
+ if (!GlobalDependencyMap.isLayoutNode(this.map, node)) {
+ 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 don’t 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 });
+ this.#map = new TemplateDepGraph();
+ }
+
+ 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 [];
+ }
+
+ node = this.normalizeNode(node);
+
+ if (!this.map.hasNode(node)) {
+ return [];
+ }
+
+ // 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)
+ .map((entry) => GlobalDependencyMap.getTagName(entry))
+ .filter(Boolean);
+
+ 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 don’t 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 collectionKey = GlobalDependencyMap.getCollectionKeyForEntry(name);
+ if (!this.map.hasNode(collectionKey)) {
+ continue;
+ }
+ for (let node of this.map.dependantsOf(collectionKey)) {
+ if (!node.startsWith(GlobalDependencyMap.COLLECTION_PREFIX)) {
+ if (!GlobalDependencyMap.isLayoutNode(this.map, node)) {
+ templates.add(node);
+ }
+ }
+ }
+ }
+ return templates;
+ }
+
+ static isLayoutNode(graph, node) {
+ if (!graph.hasNode(node)) {
+ return false;
+ }
+ return graph.getNodeData(node)?.type === GlobalDependencyMap.LAYOUT_KEY;
+ }
+
+ getLayoutsUsedBy(node) {
+ node = this.normalizeNode(node);
+
+ if (!this.map.hasNode(node)) {
+ return [];
+ }
+
+ let layouts = [];
+
+ // include self, if layout
+ if (GlobalDependencyMap.isLayoutNode(this.map, node)) {
+ layouts.push(node);
+ }
+
+ this.map.dependantsOf(node).forEach((node) => {
+ // we only want layouts
+ if (GlobalDependencyMap.isLayoutNode(this.map, node)) {
+ 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;
+ }
+
+ if (includeLayouts) {
+ return this.map.dependenciesOf(node).filter(Boolean);
+ }
+
+ return GlobalDependencyMap.removeLayoutNodes(this.map, this.map.dependenciesOf(node));
+ }
+
+ #addNode(name) {
+ if (this.map.hasNode(name)) {
+ return;
+ }
+
+ this.map.addNode(name);
+ }
+
+ // node arguments are already normalized
+ #addDependency(from, toArray = []) {
+ this.#addNode(from); // only if not already added
+
+ 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) {
+ this.#addNode(to); // only if not already added
+ if (from !== to) {
+ this.map.addDependency(from, to);
+ }
+ }
+ }
+
+ addDependency(from, toArray = []) {
+ this.#addDependency(
+ this.normalizeNode(from),
+ toArray.map((to) => this.normalizeNode(to)),
+ );
+ }
+
+ addNewNodeRelationships(from, consumes = [], publishes = []) {
+ consumes = consumes.filter(Boolean);
+ publishes = publishes.filter(Boolean);
+
+ debug("%o consumes %o and publishes to %o", from, consumes, publishes);
+ from = this.normalizeNode(from);
+
+ this.map.addTemplate(from, consumes, publishes);
+ }
+
+ // 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;
+ }
+
+ getTemplateOrder() {
+ let order = [];
+ for (let entry of this.map.overallOrder()) {
+ order.push(entry);
+ }
+
+ return order;
+ }
+
+ 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 TemplateDepGraph();
+
+ // 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;
diff --git a/node_modules/@11ty/eleventy/src/LayoutCache.js b/node_modules/@11ty/eleventy/src/LayoutCache.js
new file mode 100644
index 0000000..006f502
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/LayoutCache.js
@@ -0,0 +1,98 @@
+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 LayoutCache {
+ constructor() {
+ this.cache = {};
+ this.cacheByInputPath = {};
+ }
+
+ clear() {
+ this.cache = {};
+ this.cacheByInputPath = {};
+ }
+
+ // alias
+ removeAll() {
+ for (let layoutFilePath in this.cacheByInputPath) {
+ this.remove(layoutFilePath);
+ }
+ 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 LayoutCache->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 LayoutCache.`);
+ }
+
+ 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 LayoutCache();
+
+eventBus.on("eleventy.resourceModified", () => {
+ // https://github.com/11ty/eleventy-plugin-bundle/issues/10
+ layoutCache.removeAll();
+});
+
+// singleton
+export default layoutCache;
diff --git a/node_modules/@11ty/eleventy/src/Plugins/HtmlBasePlugin.js b/node_modules/@11ty/eleventy/src/Plugins/HtmlBasePlugin.js
new file mode 100644
index 0000000..304c0a2
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Plugins/HtmlBasePlugin.js
@@ -0,0 +1,160 @@
+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, htmlContext } = opts;
+
+ // Warning, this will not work with HtmlTransformer, as we’ll receive "false" (string) here instead of `false` (boolean)
+ if (url === false) {
+ throw new Error(
+ `Invalid url transformed in the HTML \`<base>\` plugin.${url === false ? ` Did you attempt to link to a \`permalink: false\` page?` : ""} Received: ${url}`,
+ );
+ }
+
+ // 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 `baseHref` 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, htmlContext) => {
+ return transformUrl(url.trim(), base, {
+ pathPrefix: eleventyConfig.pathPrefix,
+ pageUrl: pageUrlOverride || this.page?.url,
+ htmlContext,
+ });
+ });
+ },
+ );
+
+ // Apply to all HTML output in your project
+ eleventyConfig.htmlTransformer.addUrlTransform(
+ opts.extensions,
+
+ /** @this {object} */
+ function (urlInMarkup, htmlContext) {
+ // 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,
+ htmlContext,
+ });
+ },
+ {
+ priority: -2, // priority is descending, so this runs last (especially after AutoCopy and InputPathToUrl transform)
+ enabled: function (context) {
+ // Enabled when pathPrefix is non-default or via renderTransforms
+ return Boolean(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 };
diff --git a/node_modules/@11ty/eleventy/src/Plugins/HtmlRelativeCopyPlugin.js b/node_modules/@11ty/eleventy/src/Plugins/HtmlRelativeCopyPlugin.js
new file mode 100644
index 0000000..ac1391d
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Plugins/HtmlRelativeCopyPlugin.js
@@ -0,0 +1,52 @@
+import { HtmlRelativeCopy } from "../Util/HtmlRelativeCopy.js";
+
+// one HtmlRelativeCopy instance per entry
+function init(eleventyConfig, options) {
+ let opts = Object.assign(
+ {
+ extensions: "html",
+ match: false, // can be one glob string or an array of globs
+ paths: [], // directories to also look in for files
+ failOnError: true, // fails when a path matches (via `match`) but not found on file system
+ copyOptions: undefined,
+ },
+ options,
+ );
+
+ let htmlrel = new HtmlRelativeCopy();
+ htmlrel.setUserConfig(eleventyConfig);
+ htmlrel.addMatchingGlob(opts.match);
+ htmlrel.setFailOnError(opts.failOnError);
+ htmlrel.setCopyOptions(opts.copyOptions);
+
+ eleventyConfig.htmlTransformer.addUrlTransform(
+ opts.extensions,
+ function (targetFilepathOrUrl) {
+ // @ts-ignore
+ htmlrel.copy(targetFilepathOrUrl, this.page.inputPath, this.page.outputPath);
+
+ // TODO front matter option for manual copy
+ return targetFilepathOrUrl;
+ },
+ {
+ enabled: () => htmlrel.isEnabled(),
+ // - MUST run after other plugins but BEFORE HtmlBase plugin
+ priority: -1,
+ },
+ );
+
+ htmlrel.addPaths(opts.paths);
+}
+
+function HtmlRelativeCopyPlugin(eleventyConfig) {
+ // Important: if this is empty, no URL transforms are added
+ for (let options of eleventyConfig.passthroughCopiesHtmlRelative) {
+ init(eleventyConfig, options);
+ }
+}
+
+Object.defineProperty(HtmlRelativeCopyPlugin, "eleventyPackage", {
+ value: "@11ty/eleventy/html-relative-copy-plugin",
+});
+
+export { HtmlRelativeCopyPlugin };
diff --git a/node_modules/@11ty/eleventy/src/Plugins/I18nPlugin.js b/node_modules/@11ty/eleventy/src/Plugins/I18nPlugin.js
new file mode 100644
index 0000000..6f53825
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Plugins/I18nPlugin.js
@@ -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 won’t 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 Eleventy’s 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 page’s 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") {
+ // You’re linking to a localized file that doesn’t 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;
diff --git a/node_modules/@11ty/eleventy/src/Plugins/IdAttributePlugin.js b/node_modules/@11ty/eleventy/src/Plugins/IdAttributePlugin.js
new file mode 100644
index 0000000..a55a13e
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Plugins/IdAttributePlugin.js
@@ -0,0 +1,110 @@
+import matchHelper from "posthtml-match-helper";
+import { decodeHTML } from "entities";
+
+import slugifyFilter from "../Filters/Slugify.js";
+import MemoizeUtil from "../Util/MemoizeFunction.js";
+
+const POSTHTML_PLUGIN_NAME = "11ty/eleventy/id-attribute";
+
+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 idAttributePosthtmlPlugin(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(
+ 'You have more than one HTML `id` attribute using the same value (id="' +
+ id +
+ '") in your template (' +
+ pluginOptions.page.inputPath +
+ "). You can disable this error in the IdAttribute plugin with the `checkDuplicates: false` option.",
+ );
+ }
+ } 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
+ name: POSTHTML_PLUGIN_NAME,
+ },
+ );
+}
+
+export { IdAttributePlugin };
diff --git a/node_modules/@11ty/eleventy/src/Plugins/InputPathToUrl.js b/node_modules/@11ty/eleventy/src/Plugins/InputPathToUrl.js
new file mode 100644
index 0000000..aca148b
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Plugins/InputPathToUrl.js
@@ -0,0 +1,191 @@
+import path from "node:path";
+import { TemplatePath } from "@11ty/eleventy-utils";
+import isValidUrl from "../Util/ValidUrl.js";
+
+function getValidPath(contentMap, testPath) {
+ // if the path is coming from Markdown, it may be encoded
+ let normalized = TemplatePath.addLeadingDotSlash(decodeURIComponent(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 file’s 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) {
+ if (filepath.startsWith("#") || filepath.startsWith("?")) {
+ return [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 don’t want
+ // URL(`file:#anchor`) gives back a pathname of `/`
+ let u = new URL(`file:${filepath}`);
+ filepath = filepath.replace(u.search, ""); // includes ?
+ filepath = filepath.replace(u.hash, ""); // includes #
+
+ 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);
+ if (targetFilePath) {
+ targetFilePath = normalizeInputPath(
+ targetFilePath,
+ inputDir,
+ // @ts-ignore
+ 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);
+ if (targetFilepathOrUrl) {
+ targetFilepathOrUrl = normalizeInputPath(
+ targetFilepathOrUrl,
+ inputDir,
+ // @ts-ignore
+ this.page.inputPath,
+ contentMap,
+ );
+ }
+
+ let urls = contentMap[targetFilepathOrUrl];
+ if (!targetFilepathOrUrl || !urls || urls.length === 0) {
+ // fallback, transforms don’t 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 };
diff --git a/node_modules/@11ty/eleventy/src/Plugins/Pagination.js b/node_modules/@11ty/eleventy/src/Plugins/Pagination.js
new file mode 100755
index 0000000..8e5b1de
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Plugins/Pagination.js
@@ -0,0 +1,379 @@
+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 don’t 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]) ||
+ Boolean(this.data.eleventyComputed?.[this.config.keys.permalink]);
+
+ // Do *not* pass collections through DeepCopy, we’ll 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 let’s wait on enabling it.
+ // DeepFreeze(parentData, ["collections"]);
+
+ // TODO future improvement dea: use a light Template wrapper for paged template clones (PagedTemplate?)
+ // so that we don’t 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) {
+ 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 doesn’t 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;
diff --git a/node_modules/@11ty/eleventy/src/Plugins/RenderPlugin.js b/node_modules/@11ty/eleventy/src/Plugins/RenderPlugin.js
new file mode 100644
index 0000000..974b0e1
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Plugins/RenderPlugin.js
@@ -0,0 +1,520 @@
+import fs from "node: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 EleventyExtensionMap from "../EleventyExtensionMap.js";
+import TemplateEngineManager from "../Engines/TemplateEngineManager.js";
+import Liquid from "../Engines/Liquid.js";
+
+class EleventyNunjucksError extends EleventyBaseError {}
+
+/** @this {object} */
+async function compile(content, templateLang, options = {}) {
+ let { templateConfig, extensionMap } = options;
+ let strictMode = options.strictMode ?? false;
+
+ 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;
+ }
+
+ if (!extensionMap) {
+ if (strictMode) {
+ throw new Error("Internal error: missing `extensionMap` in RenderPlugin->compile.");
+ }
+ extensionMap = new EleventyExtensionMap(templateConfig);
+ extensionMap.engineManager = new TemplateEngineManager(templateConfig);
+ }
+ 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;
+ let strictMode = options.strictMode ?? false;
+ if (!inputPath) {
+ throw new Error("Missing file path argument passed to the `renderFile` shortcode.");
+ }
+
+ 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 normalizedPath = TemplatePath.normalizeOperatingSystemFilePath(inputPath);
+ // Prefer the exists cache, if it’s available
+ if (!templateConfig.existsCache.exists(normalizedPath)) {
+ throw new Error(
+ "Could not find render plugin file for the `renderFile` shortcode, looking for: " + inputPath,
+ );
+ }
+
+ if (!extensionMap) {
+ if (strictMode) {
+ throw new Error("Internal error: missing `extensionMap` in RenderPlugin->compileFile.");
+ }
+
+ extensionMap = new EleventyExtensionMap(templateConfig);
+ extensionMap.engineManager = new TemplateEngineManager(templateConfig);
+ }
+ 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 = {}) {
+ let templateConfig;
+ eleventyConfig.on("eleventy.config", (tmplConfigInstance) => {
+ templateConfig = tmplConfigInstance;
+ });
+
+ let extensionMap;
+ eleventyConfig.on("eleventy.extensionmap", (map) => {
+ extensionMap = map;
+ });
+
+ /**
+ * @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 page’s 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 don’t 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 {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;
+ }
+
+ // TODO Render plugin `templateLang` is feeding bad input paths to the addDependencies call in Custom.js
+ let fn = await compile.call(this, content, templateLang, {
+ templateConfig: opts.templateConfig || templateConfig,
+ extensionMap,
+ });
+
+ 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;
+ #extensionMap;
+ #templateConfig;
+
+ constructor() {
+ this.templateConfig = new TemplateConfig(null, false);
+ this.templateConfig.setDirectories(new ProjectDirectories());
+ }
+
+ get templateConfig() {
+ return this.#templateConfig;
+ }
+
+ set templateConfig(templateConfig) {
+ if (!templateConfig || templateConfig === this.#templateConfig) {
+ return;
+ }
+
+ this.#templateConfig = templateConfig;
+
+ // This is the only plugin running on the Edge
+ this.#templateConfig.userConfig.addPlugin(eleventyRenderPlugin, {
+ templateConfig: this.#templateConfig,
+ accessGlobalData: true,
+ });
+
+ this.#extensionMap = new EleventyExtensionMap(this.#templateConfig);
+ this.#extensionMap.engineManager = new TemplateEngineManager(this.#templateConfig);
+ }
+
+ 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 don’t 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();
+
+ options.templateConfig = this.templateConfig;
+ options.extensionMap = this.#extensionMap;
+ options.strictMode = true;
+
+ // We don’t need `compile.call(this)` here because the Edge always uses "liquid" as the template lang (instead of relying on this.page.templateSyntax)
+ // 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 };
diff --git a/node_modules/@11ty/eleventy/src/Template.js b/node_modules/@11ty/eleventy/src/Template.js
new file mode 100755
index 0000000..64c4709
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Template.js
@@ -0,0 +1,1200 @@
+import util from "node:util";
+import os from "node:os";
+import path from "node:path";
+import fs from "node:fs";
+
+import lodash from "@11ty/lodash-custom";
+import { DateTime } from "luxon";
+import { TemplatePath, isPlainObject } from "@11ty/eleventy-utils";
+import debugUtil from "debug";
+import chalk from "kleur";
+
+import ConsoleLogger from "./Util/ConsoleLogger.js";
+import getDateFromGitLastUpdated from "./Util/DateGitLastUpdated.js";
+import getDateFromGitFirstAdded from "./Util/DateGitFirstAdded.js";
+import TemplateData from "./Data/TemplateData.js";
+import TemplateContent from "./TemplateContent.js";
+import TemplatePermalink from "./TemplatePermalink.js";
+import TemplateLayout from "./TemplateLayout.js";
+import TemplateFileSlug from "./TemplateFileSlug.js";
+import ComputedData from "./Data/ComputedData.js";
+import Pagination from "./Plugins/Pagination.js";
+import TemplateBehavior from "./TemplateBehavior.js";
+import TemplateContentPrematureUseError from "./Errors/TemplateContentPrematureUseError.js";
+import TemplateContentUnrenderedTemplateError from "./Errors/TemplateContentUnrenderedTemplateError.js";
+import EleventyBaseError from "./Errors/EleventyBaseError.js";
+import ReservedData from "./Util/ReservedData.js";
+import TransformsUtil from "./Util/TransformsUtil.js";
+import { FileSystemManager } from "./Util/FileSystemManager.js";
+
+const { set: lodashSet, get: lodashGet } = lodash;
+const fsStat = util.promisify(fs.stat);
+
+const debug = debugUtil("Eleventy:Template");
+const debugDev = debugUtil("Dev:Eleventy:Template");
+
+class Template extends TemplateContent {
+ #logger;
+ #fsManager;
+
+ constructor(templatePath, templateData, extensionMap, config) {
+ debugDev("new Template(%o)", templatePath);
+ super(templatePath, config);
+
+ this.parsed = path.parse(templatePath);
+
+ // for pagination
+ this.extraOutputSubdirectory = "";
+
+ this.extensionMap = extensionMap;
+ this.templateData = templateData;
+ this.#initFileSlug();
+
+ this.linters = [];
+ this.transforms = {};
+
+ this.isVerbose = true;
+ this.isDryRun = false;
+ this.writeCount = 0;
+
+ this.fileSlug = new TemplateFileSlug(this.inputPath, this.extensionMap, this.eleventyConfig);
+ this.fileSlugStr = this.fileSlug.getSlug();
+ this.filePathStem = this.fileSlug.getFullPathWithoutExtension();
+
+ this.outputFormat = "fs";
+
+ this.behavior = new TemplateBehavior(this.config);
+ this.behavior.setOutputFormat(this.outputFormat);
+ }
+
+ #initFileSlug() {
+ this.fileSlug = new TemplateFileSlug(this.inputPath, this.extensionMap, this.eleventyConfig);
+ this.fileSlugStr = this.fileSlug.getSlug();
+ this.filePathStem = this.fileSlug.getFullPathWithoutExtension();
+ }
+
+ /* mimic constructor arg order */
+ resetCachedTemplate({ templateData, extensionMap, eleventyConfig }) {
+ super.resetCachedTemplate({ eleventyConfig });
+ this.templateData = templateData;
+ this.extensionMap = extensionMap;
+ // this.#fsManager = undefined;
+ this.#initFileSlug();
+ }
+
+ get fsManager() {
+ if (!this.#fsManager) {
+ this.#fsManager = new FileSystemManager(this.eleventyConfig);
+ }
+ return this.#fsManager;
+ }
+
+ get logger() {
+ if (!this.#logger) {
+ this.#logger = new ConsoleLogger();
+ this.#logger.isVerbose = this.isVerbose;
+ }
+ return this.#logger;
+ }
+
+ /* Setter for Logger */
+ set logger(logger) {
+ this.#logger = logger;
+ }
+
+ isRenderable() {
+ return this.behavior.isRenderable();
+ }
+
+ isRenderableDisabled() {
+ return this.behavior.isRenderableDisabled();
+ }
+
+ isRenderableOptional() {
+ // A template that is lazily rendered once if used by a second order dependency of another template dependency.
+ // e.g. You change firstpost.md, which is used by feed.xml, but secondpost.md (also used by feed.xml)
+ // has not yet rendered and needs to be rendered once to populate the cache.
+ return this.behavior.isRenderableOptional();
+ }
+
+ setRenderableOverride(renderableOverride) {
+ this.behavior.setRenderableOverride(renderableOverride);
+ }
+
+ reset() {
+ this.renderCount = 0;
+ this.writeCount = 0;
+ }
+
+ resetCaches(types) {
+ types = this.getResetTypes(types);
+
+ super.resetCaches(types);
+
+ if (types.data) {
+ delete this._dataCache;
+ // delete this._usePermalinkRoot;
+ // delete this._stats;
+ }
+
+ if (types.render) {
+ delete this._cacheRenderedPromise;
+ delete this._cacheRenderedTransformsAndLayoutsPromise;
+ }
+ }
+
+ setOutputFormat(to) {
+ this.outputFormat = to;
+ this.behavior.setOutputFormat(to);
+ }
+
+ setIsVerbose(isVerbose) {
+ this.isVerbose = isVerbose;
+ this.logger.isVerbose = isVerbose;
+ }
+
+ setDryRunViaIncremental(isIncremental) {
+ this.isDryRun = isIncremental;
+ this.isIncremental = isIncremental;
+ }
+
+ setDryRun(isDryRun) {
+ this.isDryRun = !!isDryRun;
+ }
+
+ setExtraOutputSubdirectory(dir) {
+ this.extraOutputSubdirectory = dir + "/";
+ }
+
+ getTemplateSubfolder() {
+ let dir = TemplatePath.absolutePath(this.parsed.dir);
+ let inputDir = TemplatePath.absolutePath(this.inputDir);
+ return TemplatePath.stripLeadingSubPath(dir, inputDir);
+ }
+
+ templateUsesLayouts(pageData) {
+ if (this.hasTemplateRender()) {
+ return pageData?.[this.config.keys.layout] && this.templateRender.engine.useLayouts();
+ }
+
+ // If `layout` prop is set, default to true when engine is unknown
+ return Boolean(pageData?.[this.config.keys.layout]);
+ }
+
+ getLayout(layoutKey) {
+ // already cached downstream in TemplateLayout -> TemplateCache
+ try {
+ return TemplateLayout.getTemplate(layoutKey, this.eleventyConfig, this.extensionMap);
+ } catch (e) {
+ throw new EleventyBaseError(
+ `Problem creating an Eleventy Layout for the "${this.inputPath}" template file.`,
+ e,
+ );
+ }
+ }
+
+ get baseFile() {
+ return this.extensionMap.removeTemplateExtension(this.parsed.base);
+ }
+
+ async _getRawPermalinkInstance(permalinkValue) {
+ let perm = new TemplatePermalink(permalinkValue, this.extraOutputSubdirectory);
+ perm.setUrlTransforms(this.config.urlTransforms);
+
+ this.behavior.setFromPermalink(perm);
+
+ return perm;
+ }
+
+ async _getLink(data) {
+ if (!data) {
+ throw new Error("Internal error: data argument missing in Template->_getLink");
+ }
+
+ let permalink =
+ data[this.config.keys.permalink] ??
+ data?.[this.config.keys.computed]?.[this.config.keys.permalink];
+ let permalinkValue;
+
+ // `permalink: false` means render but no file system write, e.g. use in collections only)
+ // `permalink: true` throws an error
+ if (typeof permalink === "boolean") {
+ debugDev("Using boolean permalink %o", permalink);
+ permalinkValue = permalink;
+ } else if (permalink && (!this.config.dynamicPermalinks || data.dynamicPermalink === false)) {
+ debugDev("Not using dynamic permalinks, using %o", permalink);
+ permalinkValue = permalink;
+ } else if (isPlainObject(permalink)) {
+ // Empty permalink {} object should act as if no permalink was set at all
+ // and inherit the default behavior
+ let isEmptyObject = Object.keys(permalink).length === 0;
+ if (!isEmptyObject) {
+ let promises = [];
+ let keys = [];
+ for (let key in permalink) {
+ keys.push(key);
+ if (key !== "build" && Array.isArray(permalink[key])) {
+ promises.push(
+ Promise.all([...permalink[key]].map((entry) => super.renderPermalink(entry, data))),
+ );
+ } else {
+ promises.push(super.renderPermalink(permalink[key], data));
+ }
+ }
+
+ let results = await Promise.all(promises);
+
+ permalinkValue = {};
+ for (let j = 0, k = keys.length; j < k; j++) {
+ let key = keys[j];
+ permalinkValue[key] = results[j];
+ debug(
+ "Rendering permalink.%o for %o: %s becomes %o",
+ key,
+ this.inputPath,
+ permalink[key],
+ results[j],
+ );
+ }
+ }
+ } else if (permalink) {
+ // render variables inside permalink front matter, bypass markdown
+ permalinkValue = await super.renderPermalink(permalink, data);
+ debug("Rendering permalink for %o: %s becomes %o", this.inputPath, permalink, permalinkValue);
+ debugDev("Permalink rendered with data: %o", data);
+ }
+
+ // Override default permalink behavior. Only do this if permalink was _not_ in the data cascade
+ if (!permalink && this.config.dynamicPermalinks && data.dynamicPermalink !== false) {
+ let tr = await this.getTemplateRender();
+ let permalinkCompilation = tr.engine.permalinkNeedsCompilation("");
+ if (typeof permalinkCompilation === "function") {
+ let ret = await this._renderFunction(permalinkCompilation, permalinkValue, this.inputPath);
+ if (ret !== undefined) {
+ if (typeof ret === "function") {
+ // function
+ permalinkValue = await this._renderFunction(ret, data);
+ } else {
+ // scalar
+ permalinkValue = ret;
+ }
+ }
+ }
+ }
+
+ if (permalinkValue !== undefined) {
+ return this._getRawPermalinkInstance(permalinkValue);
+ }
+
+ // No `permalink` specified in data cascade, do the default
+ let p = TemplatePermalink.generate(
+ this.getTemplateSubfolder(),
+ this.baseFile,
+ this.extraOutputSubdirectory,
+ this.engine.defaultTemplateFileExtension,
+ );
+ p.setUrlTransforms(this.config.urlTransforms);
+ return p;
+ }
+
+ async usePermalinkRoot() {
+ // @cachedproperty
+ if (this._usePermalinkRoot === undefined) {
+ // TODO this only works with immediate front matter and not data files
+ let { data } = await this.getFrontMatterData();
+ this._usePermalinkRoot = data[this.config.keys.permalinkRoot];
+ }
+
+ return this._usePermalinkRoot;
+ }
+
+ async getOutputLocations(data) {
+ this.bench.get("(count) getOutputLocations").incrementCount();
+ let link = await this._getLink(data);
+
+ let path;
+ if (await this.usePermalinkRoot()) {
+ path = link.toPathFromRoot();
+ } else {
+ path = link.toPath(this.outputDir);
+ }
+
+ return {
+ linkInstance: link,
+ rawPath: link.toOutputPath(),
+ href: link.toHref(),
+ path: path,
+ };
+ }
+
+ // This is likely now a test-only method
+ // Preferred to use the singular `getOutputLocations` above.
+ async getRawOutputPath(data) {
+ this.bench.get("(count) getRawOutputPath").incrementCount();
+ let link = await this._getLink(data);
+ return link.toOutputPath();
+ }
+
+ // Preferred to use the singular `getOutputLocations` above.
+ async getOutputHref(data) {
+ this.bench.get("(count) getOutputHref").incrementCount();
+ let link = await this._getLink(data);
+ return link.toHref();
+ }
+
+ // Preferred to use the singular `getOutputLocations` above.
+ async getOutputPath(data) {
+ this.bench.get("(count) getOutputPath").incrementCount();
+ let link = await this._getLink(data);
+ if (await this.usePermalinkRoot()) {
+ return link.toPathFromRoot();
+ }
+ return link.toPath(this.outputDir);
+ }
+
+ async _testGetAllLayoutFrontMatterData() {
+ let { data: frontMatterData } = await this.getFrontMatterData();
+
+ if (frontMatterData[this.config.keys.layout]) {
+ let layout = this.getLayout(frontMatterData[this.config.keys.layout]);
+ return await layout.getData();
+ }
+ return {};
+ }
+
+ async #getData() {
+ debugDev("%o getData", this.inputPath);
+ let localData = {};
+ let globalData = {};
+
+ if (this.templateData) {
+ localData = await this.templateData.getTemplateDirectoryData(this.inputPath);
+ globalData = await this.templateData.getGlobalData(this.inputPath);
+ debugDev("%o getData getTemplateDirectoryData and getGlobalData", this.inputPath);
+ }
+
+ let { data: frontMatterData } = await this.getFrontMatterData();
+
+ let mergedLayoutData = {};
+ let tr = await this.getTemplateRender();
+ if (tr.engine.useLayouts()) {
+ let layoutKey =
+ frontMatterData[this.config.keys.layout] ||
+ localData[this.config.keys.layout] ||
+ globalData[this.config.keys.layout];
+
+ // Layout front matter data
+ if (layoutKey) {
+ let layout = this.getLayout(layoutKey);
+
+ mergedLayoutData = await layout.getData();
+ debugDev("%o getData merged layout chain front matter", this.inputPath);
+ }
+ }
+
+ try {
+ let mergedData = TemplateData.mergeDeep(
+ this.config.dataDeepMerge,
+ {},
+ globalData,
+ mergedLayoutData,
+ localData,
+ frontMatterData,
+ );
+
+ if (this.config.freezeReservedData) {
+ ReservedData.check(mergedData);
+ }
+
+ await this.addPage(mergedData);
+
+ debugDev("%o getData mergedData", this.inputPath);
+
+ return mergedData;
+ } catch (e) {
+ if (
+ ReservedData.isReservedDataError(e) ||
+ (e instanceof TypeError &&
+ e.message.startsWith("Cannot add property") &&
+ e.message.endsWith("not extensible"))
+ ) {
+ throw new EleventyBaseError(
+ `You attempted to set one of Eleventy’s reserved data property names${e.reservedNames ? `: ${e.reservedNames.join(", ")}` : ""}. You can opt-out of this behavior with \`eleventyConfig.setFreezeReservedData(false)\` or rename/remove the property in your data cascade that conflicts with Eleventy’s reserved property names (e.g. \`eleventy\`, \`pkg\`, and others). Learn more: https://v3.11ty.dev/docs/data-eleventy-supplied/`,
+ e,
+ );
+ }
+
+ throw e;
+ }
+ }
+
+ async getData() {
+ if (!this._dataCache) {
+ // @cachedproperty
+ this._dataCache = this.#getData();
+ }
+
+ return this._dataCache;
+ }
+
+ async addPage(data) {
+ if (!("page" in data)) {
+ data.page = {};
+ }
+
+ // Make sure to keep these keys synchronized in src/Util/ReservedData.js
+ data.page.inputPath = this.inputPath;
+ data.page.fileSlug = this.fileSlugStr;
+ data.page.filePathStem = this.filePathStem;
+ data.page.outputFileExtension = this.engine.defaultTemplateFileExtension;
+ data.page.templateSyntax = this.templateRender.getEnginesList(
+ data[this.config.keys.engineOverride],
+ );
+
+ let newDate = await this.getMappedDate(data);
+ // Skip date assignment if custom date is falsy.
+ if (newDate) {
+ data.page.date = newDate;
+ }
+
+ // data.page.url
+ // data.page.outputPath
+ // data.page.excerpt from gray-matter and Front Matter
+ // data.page.lang from I18nPlugin
+ }
+
+ // Tests only
+ async render() {
+ throw new Error("Internal error: `Template->render` was removed in Eleventy 3.0.");
+ }
+
+ // Tests only
+ async renderLayout() {
+ throw new Error("Internal error: `Template->renderLayout` was removed in Eleventy 3.0.");
+ }
+
+ async renderDirect(str, data, bypassMarkdown) {
+ return super.render(str, data, bypassMarkdown);
+ }
+
+ // This is the primary render mechanism, called via TemplateMap->populateContentDataInMap
+ async renderPageEntryWithoutLayout(pageEntry) {
+ // @cachedproperty
+ if (!this._cacheRenderedPromise) {
+ this._cacheRenderedPromise = this.renderDirect(pageEntry.rawInput, pageEntry.data);
+ this.renderCount++;
+ }
+
+ return this._cacheRenderedPromise;
+ }
+
+ setLinters(linters) {
+ if (!isPlainObject(linters)) {
+ throw new Error("Object expected in setLinters");
+ }
+ // this acts as a reset
+ this.linters = [];
+ for (let linter of Object.values(linters).filter((l) => typeof l === "function")) {
+ this.addLinter(linter);
+ }
+ }
+
+ addLinter(callback) {
+ this.linters.push(callback);
+ }
+
+ async runLinters(str, page) {
+ let { inputPath, outputPath, url } = page;
+ let pageData = page.data.page;
+
+ for (let linter of this.linters) {
+ // these can be asynchronous but no guarantee of order when they run
+ linter.call(
+ {
+ inputPath,
+ outputPath,
+ url,
+ page: pageData,
+ },
+ str,
+ inputPath,
+ outputPath,
+ );
+ }
+ }
+
+ setTransforms(transforms) {
+ if (!isPlainObject(transforms)) {
+ throw new Error("Object expected in setTransforms");
+ }
+ this.transforms = transforms;
+ }
+
+ async runTransforms(str, pageEntry) {
+ return TransformsUtil.runAll(str, pageEntry.data.page, this.transforms, {
+ logger: this.logger,
+ });
+ }
+
+ async #renderComputedUnit(entry, data) {
+ if (typeof entry === "string") {
+ return this.renderComputedData(entry, data);
+ }
+
+ if (isPlainObject(entry)) {
+ for (let key in entry) {
+ entry[key] = await this.#renderComputedUnit(entry[key], data);
+ }
+ }
+
+ if (Array.isArray(entry)) {
+ for (let j = 0, k = entry.length; j < k; j++) {
+ entry[j] = await this.#renderComputedUnit(entry[j], data);
+ }
+ }
+
+ return entry;
+ }
+
+ _addComputedEntry(computedData, obj, parentKey, declaredDependencies) {
+ // this check must come before isPlainObject
+ if (typeof obj === "function") {
+ computedData.add(parentKey, obj, declaredDependencies);
+ } else if (Array.isArray(obj) || typeof obj === "string") {
+ // Arrays are treated as one entry in the dependency graph now, Issue #3728
+ computedData.addTemplateString(
+ parentKey,
+ async function (innerData) {
+ return this.tmpl.#renderComputedUnit(obj, innerData);
+ },
+ declaredDependencies,
+ this.getParseForSymbolsFunction(obj),
+ this,
+ );
+ } else if (isPlainObject(obj)) {
+ // Arrays used to be computed here
+ for (let key in obj) {
+ let keys = [];
+ if (parentKey) {
+ keys.push(parentKey);
+ }
+ keys.push(key);
+ this._addComputedEntry(computedData, obj[key], keys.join("."), declaredDependencies);
+ }
+ } else {
+ // Numbers, booleans, etc
+ computedData.add(parentKey, obj, declaredDependencies);
+ }
+ }
+
+ async addComputedData(data) {
+ if (isPlainObject(data?.[this.config.keys.computed])) {
+ this.computedData = new ComputedData(this.config);
+
+ // Note that `permalink` is only a thing that gets consumed—it does not go directly into generated data
+ // this allows computed entries to use page.url or page.outputPath and they’ll be resolved properly
+
+ // TODO Room for optimization here—we don’t need to recalculate `getOutputHref` and `getOutputPath`
+ // TODO Why are these using addTemplateString instead of add
+ this.computedData.addTemplateString(
+ "page.url",
+ async function (data) {
+ return this.tmpl.getOutputHref(data);
+ },
+ data.permalink ? ["permalink"] : undefined,
+ false, // skip symbol resolution
+ this,
+ );
+
+ this.computedData.addTemplateString(
+ "page.outputPath",
+ async function (data) {
+ return this.tmpl.getOutputPath(data);
+ },
+ data.permalink ? ["permalink"] : undefined,
+ false, // skip symbol resolution
+ this,
+ );
+
+ // Check for reserved properties in computed data
+ if (this.config.freezeReservedData) {
+ ReservedData.check(data[this.config.keys.computed]);
+ }
+
+ // actually add the computed data
+ this._addComputedEntry(this.computedData, data[this.config.keys.computed]);
+
+ // limited run of computed data—save the stuff that relies on collections for later.
+ debug("First round of computed data for %o", this.inputPath);
+ await this.computedData.setupData(data, function (entry) {
+ return !this.isUsesStartsWith(entry, "collections.");
+
+ // TODO possible improvement here is to only process page.url, page.outputPath, permalink
+ // instead of only punting on things that rely on collections.
+ // let firstPhaseComputedData = ["page.url", "page.outputPath", ...this.getOrderFor("page.url"), ...this.getOrderFor("page.outputPath")];
+ // return firstPhaseComputedData.indexOf(entry) > -1;
+ });
+ } else {
+ if (!("page" in data)) {
+ data.page = {};
+ }
+
+ // pagination will already have these set via Pagination->getPageTemplates
+ if (data.page.url && data.page.outputPath) {
+ return;
+ }
+
+ let { href, path } = await this.getOutputLocations(data);
+ data.page.url = href;
+ data.page.outputPath = path;
+ }
+ }
+
+ // Computed data consuming collections!
+ async resolveRemainingComputedData(data) {
+ // If it doesn’t exist, computed data is not used for this template
+ if (this.computedData) {
+ debug("Second round of computed data for %o", this.inputPath);
+ return this.computedData.processRemainingData(data);
+ }
+ }
+
+ static augmentWithTemplateContentProperty(obj) {
+ return Object.defineProperties(obj, {
+ needsCheck: {
+ enumerable: false,
+ writable: true,
+ value: true,
+ },
+ _templateContent: {
+ enumerable: false,
+ writable: true,
+ value: undefined,
+ },
+ templateContent: {
+ enumerable: true,
+ set(content) {
+ if (content === undefined) {
+ this.needsCheck = false;
+ }
+ this._templateContent = content;
+ },
+ get() {
+ if (this.needsCheck && this._templateContent === undefined) {
+ if (this.template.isRenderable()) {
+ // should at least warn here
+ throw new TemplateContentPrematureUseError(
+ `Tried to use templateContent too early on ${this.inputPath}${
+ this.pageNumber ? ` (page ${this.pageNumber})` : ""
+ }`,
+ );
+ } else {
+ throw new TemplateContentUnrenderedTemplateError(
+ `Tried to use templateContent on unrendered template: ${
+ this.inputPath
+ }${this.pageNumber ? ` (page ${this.pageNumber})` : ""}`,
+ );
+ }
+ }
+ return this._templateContent;
+ },
+ },
+ // Alias for templateContent for consistency
+ content: {
+ enumerable: true,
+ get() {
+ return this.templateContent;
+ },
+ set() {
+ throw new Error("Setter not available for `content`. Use `templateContent` instead.");
+ },
+ },
+ });
+ }
+
+ static async runPreprocessors(inputPath, content, data, preprocessors) {
+ let skippedVia = false;
+ for (let [name, preprocessor] of Object.entries(preprocessors)) {
+ let { filter, callback } = preprocessor;
+
+ let filters;
+ if (Array.isArray(filter)) {
+ filters = filter;
+ } else if (typeof filter === "string") {
+ filters = filter.split(",");
+ } else {
+ throw new Error(
+ `Expected file extensions passed to "${name}" content preprocessor to be a string or array. Received: ${filter}`,
+ );
+ }
+
+ filters = filters.map((extension) => {
+ if (extension.startsWith(".") || extension === "*") {
+ return extension;
+ }
+
+ return `.${extension}`;
+ });
+
+ if (!filters.some((extension) => extension === "*" || inputPath.endsWith(extension))) {
+ // skip
+ continue;
+ }
+
+ try {
+ let ret = await callback.call(
+ {
+ inputPath,
+ },
+ data,
+ content,
+ );
+
+ // Returning explicit false is the same as ignoring the template
+ if (ret === false) {
+ skippedVia = name;
+ continue;
+ }
+
+ // Different from transforms: returning falsy (not false) here does nothing (skips the preprocessor)
+ if (ret) {
+ content = ret;
+ }
+ } catch (e) {
+ throw new EleventyBaseError(
+ `Preprocessor \`${name}\` encountered an error when transforming ${inputPath}.`,
+ e,
+ );
+ }
+ }
+
+ return {
+ skippedVia,
+ content,
+ };
+ }
+
+ async getTemplates(data) {
+ let content = await this.getPreRender();
+ let { skippedVia, content: rawInput } = await Template.runPreprocessors(
+ this.inputPath,
+ content,
+ data,
+ this.config.preprocessors,
+ );
+
+ if (skippedVia) {
+ debug(
+ "Skipping %o, the %o preprocessor returned an explicit `false`",
+ this.inputPath,
+ skippedVia,
+ );
+ return [];
+ }
+
+ // Raw Input *includes* preprocessor modifications
+ // https://github.com/11ty/eleventy/issues/1206
+ data.page.rawInput = rawInput;
+
+ if (!Pagination.hasPagination(data)) {
+ await this.addComputedData(data);
+
+ let obj = {
+ template: this, // not on the docs but folks are relying on it
+ rawInput,
+ groupNumber: 0, // i18n plugin
+ data,
+
+ page: data.page,
+ inputPath: this.inputPath,
+ fileSlug: this.fileSlugStr,
+ filePathStem: this.filePathStem,
+ date: data.page.date,
+ outputPath: data.page.outputPath,
+ url: data.page.url,
+ };
+
+ obj = Template.augmentWithTemplateContentProperty(obj);
+
+ return [obj];
+ } else {
+ // needs collections for pagination items
+ // but individual pagination entries won’t be part of a collection
+ this.paging = new Pagination(this, data, this.config);
+
+ let pageTemplates = await this.paging.getPageTemplates();
+ let objects = [];
+
+ for (let pageEntry of pageTemplates) {
+ await pageEntry.template.addComputedData(pageEntry.data);
+
+ let obj = {
+ template: pageEntry.template, // not on the docs but folks are relying on it
+ rawInput,
+ pageNumber: pageEntry.pageNumber,
+ groupNumber: pageEntry.groupNumber || 0,
+
+ data: pageEntry.data,
+
+ inputPath: this.inputPath,
+ fileSlug: this.fileSlugStr,
+ filePathStem: this.filePathStem,
+
+ page: pageEntry.data.page,
+ date: pageEntry.data.page.date,
+ outputPath: pageEntry.data.page.outputPath,
+ url: pageEntry.data.page.url,
+ };
+
+ obj = Template.augmentWithTemplateContentProperty(obj);
+
+ objects.push(obj);
+ }
+
+ return objects;
+ }
+ }
+
+ async _write({ url, outputPath, data, rawInput }, finalContent) {
+ let lang = {
+ start: "Writing",
+ finished: "written",
+ };
+
+ if (!this.isDryRun) {
+ if (this.logger.isLoggingEnabled()) {
+ let isVirtual = this.isVirtualTemplate();
+ let tr = await this.getTemplateRender();
+ let engineList = tr.getReadableEnginesListDifferingFromFileExtension();
+ let suffix = `${isVirtual ? " (virtual)" : ""}${engineList ? ` (${engineList})` : ""}`;
+ this.logger.log(
+ `${lang.start} ${outputPath} ${chalk.gray(`from ${this.inputPath}${suffix}`)}`,
+ );
+ }
+ } else if (this.isDryRun) {
+ return;
+ }
+
+ let templateBenchmarkDir = this.bench.get("Template make parent directory");
+ templateBenchmarkDir.before();
+
+ if (this.eleventyConfig.templateHandling?.writeMode === "async") {
+ await this.fsManager.createDirectoryForFile(outputPath);
+ } else {
+ this.fsManager.createDirectoryForFileSync(outputPath);
+ }
+
+ templateBenchmarkDir.after();
+
+ if (!Buffer.isBuffer(finalContent) && typeof finalContent !== "string") {
+ throw new Error(
+ `The return value from the render function for the ${this.engine.name} template was not a String or Buffer. Received ${finalContent}`,
+ );
+ }
+
+ let templateBenchmark = this.bench.get("Template Write");
+ templateBenchmark.before();
+
+ if (this.eleventyConfig.templateHandling?.writeMode === "async") {
+ await this.fsManager.writeFile(outputPath, finalContent);
+ } else {
+ this.fsManager.writeFileSync(outputPath, finalContent);
+ }
+
+ templateBenchmark.after();
+ this.writeCount++;
+ debug(`${outputPath} ${lang.finished}.`);
+
+ let ret = {
+ inputPath: this.inputPath,
+ outputPath: outputPath,
+ url,
+ content: finalContent,
+ rawInput,
+ };
+
+ if (data && this.config.dataFilterSelectors?.size > 0) {
+ ret.data = this.retrieveDataForJsonOutput(data, this.config.dataFilterSelectors);
+ }
+
+ return ret;
+ }
+
+ async #renderPageEntryWithLayoutsAndTransforms(pageEntry) {
+ let content;
+ let layoutKey = pageEntry.data[this.config.keys.layout];
+ if (this.engine.useLayouts() && layoutKey) {
+ let layout = pageEntry.template.getLayout(layoutKey);
+ content = await layout.renderPageEntry(pageEntry);
+ } else {
+ content = pageEntry.templateContent;
+ }
+
+ await this.runLinters(content, pageEntry);
+
+ content = await this.runTransforms(content, pageEntry);
+ return content;
+ }
+
+ async renderPageEntry(pageEntry) {
+ // @cachedproperty
+ if (!pageEntry.template._cacheRenderedTransformsAndLayoutsPromise) {
+ pageEntry.template._cacheRenderedTransformsAndLayoutsPromise =
+ this.#renderPageEntryWithLayoutsAndTransforms(pageEntry);
+ }
+
+ return pageEntry.template._cacheRenderedTransformsAndLayoutsPromise;
+ }
+
+ retrieveDataForJsonOutput(data, selectors) {
+ let filtered = {};
+ for (let selector of selectors) {
+ let value = lodashGet(data, selector);
+ lodashSet(filtered, selector, value);
+ }
+ return filtered;
+ }
+
+ async generateMapEntry(mapEntry, to) {
+ let ret = [];
+
+ for (let page of mapEntry._pages) {
+ let content;
+
+ // Note that behavior.render is overridden when using json or ndjson output
+ if (page.template.isRenderable()) {
+ // this reuses page.templateContent, it doesn’t render it
+ content = await page.template.renderPageEntry(page);
+ }
+
+ if (to === "json" || to === "ndjson") {
+ let obj = {
+ url: page.url,
+ inputPath: page.inputPath,
+ outputPath: page.outputPath,
+ rawInput: page.rawInput,
+ content: content,
+ };
+
+ if (this.config.dataFilterSelectors?.size > 0) {
+ obj.data = this.retrieveDataForJsonOutput(page.data, this.config.dataFilterSelectors);
+ }
+
+ if (to === "ndjson") {
+ let jsonString = JSON.stringify(obj);
+ this.logger.toStream(jsonString + os.EOL);
+ continue;
+ }
+
+ // json
+ ret.push(obj);
+ continue;
+ }
+
+ if (!page.template.isRenderable()) {
+ debug("Template not written %o from %o.", page.outputPath, page.template.inputPath);
+ continue;
+ }
+
+ if (!page.template.behavior.isWriteable()) {
+ debug(
+ "Template not written %o from %o (via permalink: false, permalink.build: false, or a permalink object without a build property).",
+ page.outputPath,
+ page.template.inputPath,
+ );
+ continue;
+ }
+
+ // compile returned undefined
+ if (content !== undefined) {
+ ret.push(this._write(page, content));
+ }
+ }
+
+ return Promise.all(ret);
+ }
+
+ async clone() {
+ // TODO do we need to even run the constructor here or can we simplify it even more
+ let tmpl = new Template(
+ this.inputPath,
+ this.templateData,
+ this.extensionMap,
+ this.eleventyConfig,
+ );
+
+ // We use this cheap property setter below instead
+ // await tmpl.getTemplateRender();
+
+ // preserves caches too, e.g. _frontMatterDataCache
+ // Does not yet include .computedData
+ for (let key in this) {
+ tmpl[key] = this[key];
+ }
+
+ return tmpl;
+ }
+
+ getWriteCount() {
+ return this.writeCount;
+ }
+
+ getRenderCount() {
+ return this.renderCount;
+ }
+
+ async getInputFileStat() {
+ // @cachedproperty
+ if (!this._stats) {
+ this._stats = fsStat(this.inputPath);
+ }
+
+ return this._stats;
+ }
+
+ async _getDateInstance(key = "birthtimeMs") {
+ let stat = await this.getInputFileStat();
+
+ // Issue 1823: https://github.com/11ty/eleventy/issues/1823
+ // return current Date in a Lambda
+ // otherwise ctime would be "1980-01-01T00:00:00.000Z"
+ // otherwise birthtime would be "1970-01-01T00:00:00.000Z"
+ if (stat.birthtimeMs === 0) {
+ return new Date();
+ }
+
+ let newDate = new Date(stat[key]);
+
+ debug(
+ "Template date: using file’s %o for %o of %o (from %o)",
+ key,
+ this.inputPath,
+ newDate,
+ stat.birthtimeMs,
+ );
+
+ return newDate;
+ }
+
+ async getMappedDate(data) {
+ let dateValue = data?.date;
+
+ // These can return a Date object, or a string.
+ // Already type checked to be functions in UserConfig
+ for (let fn of this.config.customDateParsing) {
+ let ret = fn.call(
+ {
+ page: data.page,
+ },
+ dateValue,
+ );
+
+ if (ret) {
+ debug("getMappedDate: date value override via `addDateParsing` callback to %o", ret);
+ dateValue = ret;
+ }
+ }
+
+ if (dateValue) {
+ debug("getMappedDate: using a date in the data for %o of %o", this.inputPath, data.date);
+ if (dateValue?.constructor?.name === "DateTime") {
+ // YAML does its own date parsing
+ debug("getMappedDate: found DateTime instance: %o", dateValue);
+ return dateValue.toJSDate();
+ }
+
+ if (dateValue instanceof Date) {
+ // YAML does its own date parsing
+ debug("getMappedDate: found Date instance (maybe from YAML): %o", dateValue);
+ return dateValue;
+ }
+
+ if (typeof dateValue !== "string") {
+ throw new Error(
+ `Data cascade value for \`date\` (${dateValue}) is invalid for ${this.inputPath}. Expected a JavaScript Date instance, luxon DateTime instance, or String value.`,
+ );
+ }
+
+ // special strings
+ if (!this.isVirtualTemplate()) {
+ if (dateValue.toLowerCase() === "git last modified") {
+ let d = await getDateFromGitLastUpdated(this.inputPath);
+ if (d) {
+ return d;
+ }
+
+ // return now if this file is not yet available in `git`
+ return new Date();
+ }
+ if (dateValue.toLowerCase() === "last modified") {
+ return this._getDateInstance("ctimeMs");
+ }
+ if (dateValue.toLowerCase() === "git created") {
+ let d = await getDateFromGitFirstAdded(this.inputPath);
+ if (d) {
+ return d;
+ }
+
+ // return now if this file is not yet available in `git`
+ return new Date();
+ }
+ if (dateValue.toLowerCase() === "created") {
+ return this._getDateInstance("birthtimeMs");
+ }
+ }
+
+ // try to parse with Luxon
+ let date = DateTime.fromISO(dateValue, { zone: "utc" });
+ if (!date.isValid) {
+ throw new Error(
+ `Data cascade value for \`date\` (${dateValue}) is invalid for ${this.inputPath}`,
+ );
+ }
+ debug("getMappedDate: Luxon parsed %o: %o and %o", dateValue, date, date.toJSDate());
+
+ return date.toJSDate();
+ }
+
+ // No Date supplied in the Data Cascade, try to find the date in the file name
+ let filepathRegex = this.inputPath.match(/(\d{4}-\d{2}-\d{2})/);
+ if (filepathRegex !== null) {
+ // if multiple are found in the path, use the first one for the date
+ let dateObj = DateTime.fromISO(filepathRegex[1], {
+ zone: "utc",
+ }).toJSDate();
+ debug(
+ "getMappedDate: using filename regex time for %o of %o: %o",
+ this.inputPath,
+ filepathRegex[1],
+ dateObj,
+ );
+ return dateObj;
+ }
+
+ // No Date supplied in the Data Cascade
+ if (this.isVirtualTemplate()) {
+ return new Date();
+ }
+
+ return this._getDateInstance("birthtimeMs");
+ }
+
+ // Important reminder: Template data is first generated in TemplateMap
+ async getTemplateMapEntries(data) {
+ debugDev("%o getMapped()", this.inputPath);
+
+ this.behavior.setRenderViaDataCascade(data);
+
+ let entries = [];
+ // does not return outputPath or url, we don’t want to render permalinks yet
+ entries.push({
+ template: this,
+ inputPath: this.inputPath,
+ data,
+ });
+
+ return entries;
+ }
+}
+
+export default Template;
diff --git a/node_modules/@11ty/eleventy/src/TemplateBehavior.js b/node_modules/@11ty/eleventy/src/TemplateBehavior.js
new file mode 100644
index 0000000..8dff97c
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/TemplateBehavior.js
@@ -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 won’t 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;
diff --git a/node_modules/@11ty/eleventy/src/TemplateCollection.js b/node_modules/@11ty/eleventy/src/TemplateCollection.js
new file mode 100755
index 0000000..6e99a32
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/TemplateCollection.js
@@ -0,0 +1,77 @@
+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 = new Set(TemplateData.getIncludedTagNames(item.data));
+ return tags.every((requiredTag) => {
+ return itemTags.has(requiredTag);
+ });
+ });
+ }
+}
+
+export default TemplateCollection;
diff --git a/node_modules/@11ty/eleventy/src/TemplateConfig.js b/node_modules/@11ty/eleventy/src/TemplateConfig.js
new file mode 100644
index 0000000..e1fff8f
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/TemplateConfig.js
@@ -0,0 +1,565 @@
+import fs from "node:fs";
+import chalk from "kleur";
+import { Merge, TemplatePath, isPlainObject } from "@11ty/eleventy-utils";
+import debugUtil from "debug";
+
+import { EleventyImportRaw } 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();
+ #existsCache = new ExistsCache();
+ #usesGraph;
+ #previousBuildModifiedFile;
+
+ 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;
+
+ this.userConfig.events.on("eleventy#templateModified", (inputPath, metadata = {}) => {
+ // Might support multiple at some point
+ this.setPreviousBuildModifiedFile(inputPath, metadata);
+
+ // Issue #3569, set that this file exists in the cache
+ this.#existsCache.set(inputPath, true);
+ });
+ }
+
+ setPreviousBuildModifiedFile(inputPath, metadata = {}) {
+ this.#previousBuildModifiedFile = inputPath;
+ }
+
+ getPreviousBuildModifiedFile() {
+ return this.#previousBuildModifiedFile;
+ }
+
+ get userConfig() {
+ 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();
+
+ let configFile = configFiles.find((path) => path && fs.existsSync(path));
+ if (configFile) {
+ return configFile;
+ }
+ }
+
+ getLocalProjectConfigFiles() {
+ let paths = this.projectConfigPaths;
+ if (paths?.length > 0) {
+ return TemplatePath.addLeadingDotSlashArray(paths.filter((path) => Boolean(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();
+ this.usesGraph.reset(); // needs to be before forceReloadConfig #3711
+
+ // await this.initializeRootConfig();
+ await this.forceReloadConfig();
+
+ // 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 import("./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 user’s config file.
+ */
+ appendToRootConfig(obj) {
+ Object.assign(this.rootConfig, obj);
+ }
+
+ /*
+ * Process the userland plugins from the Config
+ *
+ * @param {object} - the return Object from the user’s 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 aren’t 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;
+
+ 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() {
+ return this.#existsCache;
+ }
+}
+
+export default TemplateConfig;
diff --git a/node_modules/@11ty/eleventy/src/TemplateContent.js b/node_modules/@11ty/eleventy/src/TemplateContent.js
new file mode 100644
index 0000000..97d440f
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/TemplateContent.js
@@ -0,0 +1,748 @@
+import os from "node:os";
+
+import fs from "node:fs";
+import matter from "gray-matter";
+import lodash from "@11ty/lodash-custom";
+import { TemplatePath } from "@11ty/eleventy-utils";
+import debugUtil from "debug";
+
+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";
+
+import { withResolvers } from "./Util/PromiseUtil.js";
+
+const { set: lodashSet } = lodash;
+const debug = debugUtil("Eleventy:TemplateContent");
+const debugDev = debugUtil("Dev:Eleventy:TemplateContent");
+
+class TemplateContentFrontMatterError extends EleventyBaseError {}
+class TemplateContentCompileError extends EleventyBaseError {}
+class TemplateContentRenderError extends EleventyBaseError {}
+
+class TemplateContent {
+ #initialized = false;
+ #config;
+ #templateRender;
+ #preprocessorEngine;
+ #extensionMap;
+ #configOptions;
+
+ constructor(inputPath, templateConfig) {
+ if (!templateConfig || templateConfig.constructor.name !== "TemplateConfig") {
+ throw new Error("Missing or invalid `templateConfig` argument");
+ }
+ this.eleventyConfig = templateConfig;
+ this.inputPath = inputPath;
+ }
+
+ async asyncTemplateInitialization() {
+ if (!this.hasTemplateRender()) {
+ await this.getTemplateRender();
+ }
+
+ if (this.#initialized) {
+ return;
+ }
+ this.#initialized = true;
+
+ let preprocessorEngineName = this.templateRender.getPreprocessorEngineName();
+ if (preprocessorEngineName && this.templateRender.engine.getName() !== preprocessorEngineName) {
+ let engine = await this.templateRender.getEngineByName(preprocessorEngineName);
+ this.#preprocessorEngine = engine;
+ }
+ }
+
+ resetCachedTemplate({ eleventyConfig }) {
+ this.eleventyConfig = eleventyConfig;
+ }
+
+ 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;
+ }
+ if (types.render) {
+ this.#templateRender = undefined;
+ }
+ }
+
+ get extensionMap() {
+ if (!this.#extensionMap) {
+ throw new Error("Internal error: Missing `extensionMap` in TemplateContent.");
+ }
+ 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 Error("Tried to get an TemplateConfig but none was found.");
+ }
+ }
+
+ get eleventyConfig() {
+ if (this.#config.constructor.name === "TemplateConfig") {
+ return this.#config;
+ }
+ throw new Error("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;
+
+ return this.#templateRender.init().then(() => {
+ return this.#templateRender;
+ });
+ }
+
+ return this.#templateRender;
+ }
+
+ // For monkey patchers
+ get frontMatter() {
+ if (this.frontMatterOverride) {
+ return this.frontMatterOverride;
+ } else {
+ throw new Error(
+ "Unfortunately you’re using code that monkey patched some Eleventy internals and it isn’t async-friendly. Change your code to use the async `read()` method on the template instead!",
+ );
+ }
+ }
+
+ // 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 === "") {
+ let tr = await this.getTemplateRender();
+ if (tr.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);
+
+ 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 isn’t async-friendly but can return a promise from custom front matter
+ if (fm.data instanceof Promise) {
+ fm.data = await fm.data;
+ }
+
+ let tr = await this.getTemplateRender();
+ let extraData = await tr.engine.getExtraDataFromFile(this.inputPath);
+
+ let virtualTemplateDefinition = this.getVirtualTemplateDefinition();
+ let virtualTemplateData;
+ if (virtualTemplateDefinition) {
+ virtualTemplateData = virtualTemplateDefinition.data;
+ }
+
+ let data = Object.assign(fm.data, extraData, virtualTemplateData);
+
+ TemplateData.cleanupData(data, {
+ file: this.inputPath,
+ isVirtualTemplate: Boolean(virtualTemplateData),
+ });
+
+ return {
+ data,
+ excerpt: fm.excerpt,
+ };
+ }
+
+ async getFrontMatterData() {
+ if (!this._frontMatterDataCache) {
+ // @cachedproperty
+ this._frontMatterDataCache = this.#getFrontMatterData();
+ }
+
+ return this._frontMatterDataCache;
+ }
+
+ async getEngineOverride() {
+ return this.getFrontMatterData().then((data) => {
+ return data[this.config.keys.engineOverride];
+ });
+ }
+
+ // checks engines
+ isTemplateCacheable() {
+ if (this.#preprocessorEngine) {
+ return this.#preprocessorEngine.cacheable;
+ }
+ return this.engine.cacheable;
+ }
+
+ _getCompileCache(str) {
+ // Caches used to be bifurcated based on engine name, now they’re based on inputPath
+ // TODO does `cacheable` need to help inform whether a cache is used here?
+ let inputPathMap = TemplateContent._compileCache.get(this.inputPath);
+ if (!inputPathMap) {
+ inputPathMap = new Map();
+ TemplateContent._compileCache.set(this.inputPath, inputPathMap);
+ }
+
+ let cacheable = this.isTemplateCacheable();
+ 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 plugin’s options)
+ key = this.eleventyConfig.userConfig._getUniqueId() + key;
+
+ return [cacheable, key, inputPathMap, useCache];
+ }
+
+ async compile(str, options = {}) {
+ let { type, bypassMarkdown, engineOverride } = options;
+
+ // Must happen before cacheable fetch below
+ // Likely only necessary for Eleventy Layouts, see TemplateMap->initDependencyMap
+ await this.asyncTemplateInitialization();
+
+ // this.templateRender is guaranteed here
+ 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
+ let withRes = withResolvers();
+ res = withRes.resolve;
+
+ cache.set(key, withRes.promise);
+ }
+ }
+
+ 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;
+
+ // Don’t use markdown as the engine to parse for symbols
+ // TODO pass in engineOverride here
+ if (this.#preprocessorEngine) {
+ engine = this.#preprocessorEngine;
+ }
+
+ if ("parseForSymbols" in engine) {
+ return () => {
+ if (Array.isArray(str)) {
+ return str
+ .filter((entry) => typeof entry === "string")
+ .map((entry) => engine.parseForSymbols(entry))
+ .flat();
+ }
+ if (typeof str === "string") {
+ return engine.parseForSymbols(str);
+ }
+ return [];
+ };
+ }
+ }
+
+ // 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 tr = await this.getTemplateRender();
+ let permalinkCompilation = tr.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 && typeof permalink !== "function") {
+ 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, {
+ type: "Content",
+ bypassMarkdown,
+ });
+ }
+
+ _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", () => {
+ TemplateContent._compileCache = new Map();
+});
+
+export default TemplateContent;
diff --git a/node_modules/@11ty/eleventy/src/TemplateFileSlug.js b/node_modules/@11ty/eleventy/src/TemplateFileSlug.js
new file mode 100644
index 0000000..03c9a29
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/TemplateFileSlug.js
@@ -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;
diff --git a/node_modules/@11ty/eleventy/src/TemplateGlob.js b/node_modules/@11ty/eleventy/src/TemplateGlob.js
new file mode 100644
index 0000000..9db85e9
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/TemplateGlob.js
@@ -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;
diff --git a/node_modules/@11ty/eleventy/src/TemplateLayout.js b/node_modules/@11ty/eleventy/src/TemplateLayout.js
new file mode 100644
index 0000000..f8526f2
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/TemplateLayout.js
@@ -0,0 +1,240 @@
+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 layoutCache from "./LayoutCache.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 (!layoutCache.has(fullKey)) {
+ let layout = new TemplateLayout(key, extensionMap, eleventyConfig);
+
+ layoutCache.add(layout);
+ debugDev("Added %o to LayoutCache", key);
+
+ return layout;
+ }
+
+ return layoutCache.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();
+ return this.compile(rawInput);
+ }
+
+ // Do only cache this layout’s 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 LayoutCache after error.");
+ layoutCache.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);
+ }
+
+ // Don’t 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;
diff --git a/node_modules/@11ty/eleventy/src/TemplateLayoutPathResolver.js b/node_modules/@11ty/eleventy/src/TemplateLayoutPathResolver.js
new file mode 100644
index 0000000..9f5a8ee
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/TemplateLayoutPathResolver.js
@@ -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, templateConfig) {
+ if (!templateConfig) {
+ throw new Error("Expected `templateConfig` in TemplateLayoutPathResolver constructor");
+ }
+
+ this.templateConfig = templateConfig;
+ 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.templateConfig.directories.getLayoutPathRelativeToInputDirectory(layoutPath);
+ return this.config.virtualTemplates[inputDirRelativePath];
+ }
+
+ get dirs() {
+ return this.templateConfig.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.templateConfig) {
+ throw new Error("Internal error: Missing this.templateConfig");
+ }
+
+ return this.templateConfig.getConfig();
+ }
+
+ exists(layoutPath) {
+ if (this.getVirtualTemplate(layoutPath)) {
+ return true;
+ }
+ let fullPath = this.templateConfig.directories.getLayoutPath(layoutPath);
+ if (this.templateConfig.existsCache.exists(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.templateConfig.directories.getLayoutPath(this.path);
+ } else if (useLayoutResolution) {
+ this.filename = this.findFileName();
+ this.fullPath = this.templateConfig.directories.getLayoutPath(this.filename || "");
+ }
+ }
+
+ addLayoutAlias(from, to) {
+ this.aliases[from] = to;
+ }
+
+ getFileName() {
+ if (!this.filename) {
+ throw new Error(
+ `You’re trying to use a layout that does not exist: ${this.originalDisplayPath}`,
+ );
+ }
+
+ return this.filename;
+ }
+
+ getFullPath() {
+ if (!this.filename) {
+ throw new Error(
+ `You’re 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;
diff --git a/node_modules/@11ty/eleventy/src/TemplateMap.js b/node_modules/@11ty/eleventy/src/TemplateMap.js
new file mode 100644
index 0000000..52cceb1
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/TemplateMap.js
@@ -0,0 +1,684 @@
+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";
+import GlobalDependencyMap from "./GlobalDependencyMap.js";
+
+const debug = debugUtil("Eleventy:TemplateMap");
+
+class EleventyMapPagesError 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
+];
+
+// must match TemplateDepGraph
+const SPECIAL_COLLECTION_NAMES = {
+ keys: "[keys]",
+ all: "all",
+};
+
+class TemplateMap {
+ #dependencyMapInitialized = false;
+
+ constructor(eleventyConfig) {
+ if (!eleventyConfig || eleventyConfig.constructor.name !== "TemplateConfig") {
+ throw new Error("Missing or invalid `eleventyConfig` 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;
+ }
+
+ 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 === "collections") {
+ // special, means targeting `collections` specifically
+ return SPECIAL_COLLECTION_NAMES.keys;
+ }
+
+ if (str.startsWith("collections.")) {
+ return str.slice("collections.".length);
+ }
+
+ // Fixes #2851
+ if (str.startsWith("collections['") || str.startsWith('collections["')) {
+ return str.slice("collections['".length, -2);
+ }
+ }
+
+ getPaginationTagTarget(entry) {
+ if (entry.data.pagination?.data) {
+ return this.getTagTarget(entry.data.pagination.data);
+ }
+ }
+
+ #addEntryToGlobalDependencyGraph(entry) {
+ let consumes = [];
+ consumes.push(this.getPaginationTagTarget(entry));
+
+ if (Array.isArray(entry.data.eleventyImport?.collections)) {
+ for (let tag of entry.data.eleventyImport.collections) {
+ consumes.push(tag);
+ }
+ }
+
+ // Important: consumers must come before publishers
+
+ // TODO it’d be nice to set the dependency relationship for addCollection here
+ // But collections are not yet populated (they populate after template order)
+ let publishes = TemplateData.getIncludedCollectionNames(entry.data);
+
+ this.config.uses.addNewNodeRelationships(entry.inputPath, consumes, publishes);
+ }
+
+ addAllToGlobalDependencyGraph() {
+ this.#dependencyMapInitialized = true;
+
+ // Should come before individual entry additions
+ this.config.uses.initializeUserConfigurationApiCollections();
+
+ 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(fullTemplateOrder) {
+ // Temporary workaround for async constructor work in templates
+ // Issue #3170 #3870
+ let inputPathSet = new Set(fullTemplateOrder);
+ await Promise.all(
+ this.map
+ .filter(({ inputPath }) => {
+ return inputPathSet.has(inputPath);
+ })
+ .map(({ template }) => {
+ // This also happens for layouts in TemplateContent->compile
+ return template.asyncTemplateInitialization();
+ }),
+ );
+
+ for (let depEntry of fullTemplateOrder) {
+ if (GlobalDependencyMap.isCollection(depEntry)) {
+ let tagName = GlobalDependencyMap.getTagName(depEntry);
+ // [keys] should initialize `all`
+ if (tagName === SPECIAL_COLLECTION_NAMES.keys) {
+ await this.setCollectionByTagName("all");
+ // [NAME] is special and implied (e.g. [keys])
+ } else if (!tagName.startsWith("[") && !tagName.endsWith("]")) {
+ // is a tag (collection) entry
+ await this.setCollectionByTagName(tagName);
+ }
+ continue;
+ }
+
+ // is a template entry
+ let map = this.getMapEntryForInputPath(depEntry);
+ await this.#initDependencyMapEntry(map);
+ }
+ }
+
+ async #initDependencyMapEntry(map) {
+ try {
+ map._pages = await map.template.getTemplates(map.data);
+ } catch (e) {
+ throw new EleventyMapPagesError(
+ "Error generating template page(s) for " + map.inputPath + ".",
+ e,
+ );
+ }
+
+ 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++;
+ }
+ }
+ }
+
+ getTemplateOrder() {
+ // 1. Templates that don’t use Pagination
+ // 2. Pagination templates that consume config API collections
+ // 3. Pagination templates consuming `collections`
+ // 4. Pagination templates consuming `collections.all`
+ let fullTemplateOrder = this.config.uses.getTemplateOrder();
+
+ return fullTemplateOrder
+ .map((entry) => {
+ if (GlobalDependencyMap.isCollection(entry)) {
+ return entry;
+ }
+
+ let inputPath = TemplatePath.addLeadingDotSlash(entry);
+ if (!this.hasMapEntryForInputPath(inputPath)) {
+ return false;
+ }
+ return inputPath;
+ })
+ .filter(Boolean);
+ }
+
+ async cache() {
+ if (!this.#dependencyMapInitialized) {
+ this.addAllToGlobalDependencyGraph();
+ }
+
+ this.collectionsData = {};
+
+ for (let entry of this.map) {
+ entry.data.collections = this.collectionsData;
+ }
+
+ let fullTemplateOrder = this.getTemplateOrder();
+ debug(
+ "Rendering templates in order (%o concurrency): %O",
+ this.userConfig.getConcurrency(),
+ fullTemplateOrder,
+ );
+
+ await this.initDependencyMap(fullTemplateOrder);
+ await this.resolveRemainingComputedData();
+
+ let orderedPaths = this.#removeTagsFromTemplateOrder(fullTemplateOrder);
+
+ 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;
+ }
+
+ hasMapEntryForInputPath(inputPath) {
+ return Boolean(this.getMapEntryForInputPath(inputPath));
+ }
+
+ // TODO(slightlyoff): hot inner loop?
+ getMapEntryForInputPath(inputPath) {
+ let absoluteInputPath = TemplatePath.absolutePath(inputPath);
+ return this.map.find((entry) => {
+ if (entry.inputPath === inputPath || entry.inputPath === absoluteInputPath) {
+ return entry;
+ }
+ });
+ }
+
+ #removeTagsFromTemplateOrder(maps) {
+ return maps.filter((dep) => !GlobalDependencyMap.isCollection(dep));
+ }
+
+ 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());
+
+ // Get concurrency level from user config
+ const concurrency = this.userConfig.getConcurrency();
+
+ // Process the templates in chunks to limit concurrency
+ // This replaces the functionality of p-map's concurrency option
+ for (let i = 0; i < filteredMap.length; i += concurrency) {
+ // Create a chunk of tasks that will run in parallel
+ const chunk = filteredMap.slice(i, i + concurrency);
+
+ // Run the chunk of tasks in parallel
+ await Promise.all(
+ chunk.map(async (map) => {
+ 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)) {
+ // Add to list of templates that need to be processed again
+ usedTemplateContentTooEarlyMap.push(map);
+
+ // Reset cached render promise
+ for (let pageEntry of map._pages) {
+ pageEntry.template.resetCaches({ render: true });
+ }
+ } else {
+ throw e;
+ }
+ }
+ }),
+ );
+ }
+
+ // Process templates that had premature template content errors
+ // This is the second pass for templates that couldn't be rendered in the first pass
+ 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)) {
+ // If we still have template content errors after the second pass,
+ // it's likely a circular reference
+ 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);
+ }
+
+ // May not return an array (can be anything)
+ // https://www.11ty.dev/docs/collections-api/#return-values
+ 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);
+
+ // May not return an array (can be anything)
+ // https://www.11ty.dev/docs/collections-api/#return-values
+ 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(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;
+ if (tmpl.templateUsesLayouts(page.data)) {
+ let layoutKey = page.data[this.config.keys.layout];
+ 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 outputPaths = {};
+ let warnings = {};
+ this.#onEachPage((page, template) => {
+ if (page.outputPath === false || page.url === false) {
+ // do nothing (also serverless)
+ } else {
+ // Make sure output doesn’t 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 (!outputPaths[page.outputPath]) {
+ outputPaths[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}
+${outputPaths[page.outputPath]
+ .map(function (inputPath, index) {
+ return ` ${index + 2}. ${inputPath}\n`;
+ })
+ .join("")}
+`;
+ outputPaths[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 won’t have a mime type) and may also cause local development issues if you later attempt to write to a subdirectory of the same name.
+
+Learn more: https://v3.11ty.dev/docs/permalinks/#trailing-slashes
+
+This is usually but not *always* an error so if you’d like to disable this error message, add \`eleventyAllowMissingExtension: true\` somewhere in the data cascade for this template or use \`eleventyConfig.configureErrorReporting({ allowMissingExtensions: true });\` to disable this feature globally.`);
+ 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;
diff --git a/node_modules/@11ty/eleventy/src/TemplatePassthrough.js b/node_modules/@11ty/eleventy/src/TemplatePassthrough.js
new file mode 100644
index 0000000..b27b634
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/TemplatePassthrough.js
@@ -0,0 +1,389 @@
+import path from "node:path";
+
+import { isDynamicPattern } from "tinyglobby";
+import { filesize } from "filesize";
+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 debug = debugUtil("Eleventy:TemplatePassthrough");
+
+class TemplatePassthroughError extends EleventyBaseError {}
+
+class TemplatePassthrough {
+ isDryRun = false;
+ #isInputPathGlob;
+ #benchmarks;
+ #isAlreadyNormalized = false;
+ #projectDirCheck = false;
+
+ // paths already guaranteed from the autocopy plugin
+ static factory(inputPath, outputPath, opts = {}) {
+ let p = new TemplatePassthrough(
+ {
+ inputPath,
+ outputPath,
+ copyOptions: opts.copyOptions,
+ },
+ opts.templateConfig,
+ );
+
+ return p;
+ }
+
+ constructor(path, templateConfig) {
+ if (!templateConfig || templateConfig.constructor.name !== "TemplateConfig") {
+ throw new Error(
+ "Internal error: Missing `templateConfig` or was not an instance of `TemplateConfig`.",
+ );
+ }
+ this.templateConfig = templateConfig;
+
+ 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.normalizeIfDirectory(path.inputPath);
+ this.#isInputPathGlob = isDynamicPattern(this.inputPath);
+
+ this.outputPath = path.outputPath;
+ this.copyOptions = path.copyOptions; // custom options for recursive-copy
+ }
+
+ get benchmarks() {
+ if (!this.#benchmarks) {
+ this.#benchmarks = {
+ aggregate: this.config.benchmarkManager.get("Aggregate"),
+ };
+ }
+
+ return this.#benchmarks;
+ }
+
+ get config() {
+ return this.templateConfig.getConfig();
+ }
+
+ get directories() {
+ return this.templateConfig.directories;
+ }
+
+ // inputDir is used when stripping from output path in `getOutputPath`
+ get inputDir() {
+ return this.templateConfig.directories.input;
+ }
+
+ get outputDir() {
+ return this.templateConfig.directories.output;
+ }
+
+ // Skips `getFiles()` normalization
+ setIsAlreadyNormalized(isNormalized) {
+ this.#isAlreadyNormalized = Boolean(isNormalized);
+ }
+
+ setCheckSourceDirectory(check) {
+ this.#projectDirCheck = Boolean(check);
+ }
+
+ /* { 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 &&
+ this.isExists(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 = Boolean(isDryRun);
+ }
+
+ setRunMode(runMode) {
+ this.runMode = runMode;
+ }
+
+ 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();
+
+ if (!this.fileSystemSearch) {
+ throw new Error("Internal error: Missing `fileSystemSearch` property.");
+ }
+
+ // TODO perf this globs once per addPassthroughCopy entry
+ let files = TemplatePath.addLeadingDotSlashArray(
+ await this.fileSystemSearch.search("passthrough", glob, {
+ ignore: [
+ // *only* ignores output dir (not node_modules!)
+ this.outputDir,
+ ],
+ }),
+ );
+ b.after();
+ return files;
+ }
+
+ isExists(filePath) {
+ return this.templateConfig.existsCache.exists(filePath);
+ }
+
+ isDirectory(filePath) {
+ return this.templateConfig.existsCache.isDirectory(filePath);
+ }
+
+ // dir is guaranteed to exist by context
+ // dir may not be a directory
+ normalizeIfDirectory(input) {
+ if (typeof input === "string") {
+ if (input.endsWith(path.sep) || input.endsWith("/")) {
+ return input;
+ }
+
+ // 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(input)) {
+ return `${input}/`;
+ }
+ }
+
+ return input;
+ }
+
+ // maps input paths to output paths
+ async getFileMap() {
+ if (this.#isAlreadyNormalized) {
+ return [
+ {
+ inputPath: this.inputPath,
+ outputPath: this.outputPath,
+ },
+ ];
+ }
+
+ // TODO VirtualFileSystem candidate
+ if (!isDynamicPattern(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 (this.#projectDirCheck && !this.directories.isFileInProjectFolder(src)) {
+ return Promise.reject(
+ new TemplatePassthroughError(
+ "Source file is not in the project directory. Check your passthrough paths.",
+ ),
+ );
+ }
+
+ if (!this.directories.isFileInOutputFolder(dest)) {
+ 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`
+ map[copyOp.src] = copyOp.dest;
+ b.before();
+ })
+ .on(copy.events.COPY_FILE_COMPLETE, (copyOp) => {
+ fileCopyCount++;
+ fileSizeCount += copyOp.stats.size;
+ if (copyOp.stats.size > 5000000) {
+ debug(`Copied %o (⚠️ large) file from %o`, filesize(copyOp.stats.size), copyOp.src);
+ } else {
+ debug(`Copied %o file from %o`, filesize(copyOp.stats.size), copyOp.src);
+ }
+ b.after();
+ })
+ .then(
+ () => {
+ return {
+ count: fileCopyCount,
+ size: fileSizeCount,
+ map,
+ };
+ },
+ (error) => {
+ if (copyOptions.overwrite === false && error.code === "EEXIST") {
+ // just ignore if the output already exists and overwrite: false
+ debug("Overwrite error ignored: %O", error);
+ return {
+ count: 0,
+ size: 0,
+ map,
+ };
+ }
+
+ return Promise.reject(error);
+ },
+ );
+ }
+
+ 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;
diff --git a/node_modules/@11ty/eleventy/src/TemplatePassthroughManager.js b/node_modules/@11ty/eleventy/src/TemplatePassthroughManager.js
new file mode 100644
index 0000000..114dfa9
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/TemplatePassthroughManager.js
@@ -0,0 +1,368 @@
+import { isDynamicPattern } from "tinyglobby";
+import { TemplatePath } from "@11ty/eleventy-utils";
+import debugUtil from "debug";
+
+import EleventyBaseError from "./Errors/EleventyBaseError.js";
+import TemplatePassthrough from "./TemplatePassthrough.js";
+import checkPassthroughCopyBehavior from "./Util/PassthroughCopyBehaviorCheck.js";
+import { isGlobMatch } from "./Util/GlobMatcher.js";
+import { withResolvers } from "./Util/PromiseUtil.js";
+
+const debug = debugUtil("Eleventy:TemplatePassthroughManager");
+
+class TemplatePassthroughManagerCopyError extends EleventyBaseError {}
+
+class TemplatePassthroughManager {
+ #isDryRun = false;
+ #afterBuild;
+ #queue = new Map();
+ #extensionMap;
+
+ constructor(templateConfig) {
+ if (!templateConfig || templateConfig.constructor.name !== "TemplateConfig") {
+ throw new Error("Internal error: Missing or invalid `templateConfig` argument.");
+ }
+
+ this.templateConfig = templateConfig;
+ this.config = templateConfig.getConfig();
+
+ // eleventy# event listeners are removed on each build
+ this.config.events.on("eleventy#copy", ({ source, target, options }) => {
+ this.enqueueCopy(source, target, options);
+ });
+
+ this.config.events.on("eleventy#beforerender", () => {
+ this.#afterBuild = withResolvers();
+ });
+
+ this.config.events.on("eleventy#render", () => {
+ let { resolve } = this.#afterBuild;
+ resolve();
+ });
+
+ this.reset();
+ }
+
+ reset() {
+ this.count = 0;
+ this.size = 0;
+ this.conflictMap = {};
+ this.incrementalFile;
+
+ this.#queue = new Map();
+ }
+
+ set extensionMap(extensionMap) {
+ this.#extensionMap = extensionMap;
+ }
+
+ get extensionMap() {
+ if (!this.#extensionMap) {
+ throw new Error("Internal error: missing `extensionMap` in TemplatePassthroughManager.");
+ }
+ return this.#extensionMap;
+ }
+
+ get inputDir() {
+ return this.templateConfig.directories.input;
+ }
+
+ get outputDir() {
+ return this.templateConfig.directories.output;
+ }
+
+ setDryRun(isDryRun) {
+ this.#isDryRun = Boolean(isDryRun);
+ }
+
+ setRunMode(runMode) {
+ this.runMode = runMode;
+ }
+
+ setIncrementalFile(path) {
+ if (path) {
+ this.incrementalFile = path;
+ }
+ }
+
+ resetIncrementalFile() {
+ this.incrementalFile = undefined;
+ }
+
+ _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;
+ }
+
+ getMetadata() {
+ return {
+ copyCount: this.getCopyCount(),
+ copySize: this.getCopySize(),
+ };
+ }
+
+ setFileSystemSearch(fileSystemSearch) {
+ this.fileSystemSearch = fileSystemSearch;
+ }
+
+ getTemplatePassthroughForPath(path) {
+ let inst = new TemplatePassthrough(path, this.templateConfig);
+
+ inst.setFileSystemSearch(this.fileSystemSearch);
+ 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 we’ll want to move all of this to use Node’s 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]) {
+ let paths = [src, this.conflictMap[dest]].sort();
+ throw new TemplatePassthroughManagerCopyError(
+ `Multiple passthrough copy files are trying to write to the same output file (${TemplatePath.standardizeFilePath(dest)}). ${paths.map((p) => TemplatePath.standardizeFilePath(p)).join(" and ")}`,
+ );
+ } 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,
+ );
+ }
+ }
+
+ this.conflictMap[dest] = src;
+ }
+
+ if (pass.isDryRun) {
+ // We don’t 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 &&
+ isDynamicPattern(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 [isPassthrough];
+ }
+
+ 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;
+ }
+
+ async #waitForTemplatesRendered() {
+ if (!this.#afterBuild) {
+ return Promise.resolve(); // immediately resolve
+ }
+
+ let { promise } = this.#afterBuild;
+ return promise;
+ }
+
+ enqueueCopy(source, target, copyOptions) {
+ let key = `${source}=>${target}`;
+
+ // light de-dupe the same source/target combo (might be in the same file, might be viaTransforms)
+ if (this.#queue.has(key)) {
+ return;
+ }
+
+ let passthrough = TemplatePassthrough.factory(source, target, {
+ templateConfig: this.templateConfig,
+ copyOptions,
+ });
+
+ passthrough.setCheckSourceDirectory(true);
+ passthrough.setIsAlreadyNormalized(true);
+ passthrough.setRunMode(this.runMode);
+ passthrough.setDryRun(this.#isDryRun);
+
+ this.#queue.set(key, this.copyPassthrough(passthrough));
+ }
+
+ async copyAll(templateExtensionPaths) {
+ debug("TemplatePassthrough copy started.");
+ let normalizedPaths = this.getAllNormalizedPaths(templateExtensionPaths);
+
+ let passthroughs = normalizedPaths.map((path) => this.getTemplatePassthroughForPath(path));
+
+ let promises = passthroughs.map((pass) => this.copyPassthrough(pass));
+
+ await this.#waitForTemplatesRendered();
+
+ for (let [key, afterBuildCopyPromises] of this.#queue) {
+ promises.push(afterBuildCopyPromises);
+ }
+
+ 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;
diff --git a/node_modules/@11ty/eleventy/src/TemplatePermalink.js b/node_modules/@11ty/eleventy/src/TemplatePermalink.js
new file mode 100644
index 0000000..9ad5111
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/TemplatePermalink.js
@@ -0,0 +1,195 @@
+import path from "node: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) {
+ if (typeof buildLink !== "string") {
+ let stringToString = "toString" in buildLink ? `:\n\n${buildLink.toString()}` : "";
+ throw new Error(
+ "Expected permalink value to be a string. Received `" +
+ typeof buildLink +
+ "`" +
+ stringToString,
+ );
+ }
+ 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.html’s 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 TemplatePath.addLeadingDotSlash(TemplatePath.normalize(outputDir + "/" + uri));
+ }
+
+ toPathFromRoot() {
+ if (!this.buildLink) {
+ return false;
+ }
+
+ let uri = this.toOutputPath();
+
+ if (uri === false) {
+ return false;
+ }
+
+ return TemplatePath.addLeadingDotSlash(TemplatePath.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;
diff --git a/node_modules/@11ty/eleventy/src/TemplateRender.js b/node_modules/@11ty/eleventy/src/TemplateRender.js
new file mode 100644
index 0000000..776f16e
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/TemplateRender.js
@@ -0,0 +1,292 @@
+import EleventyBaseError from "./Errors/EleventyBaseError.js";
+import TemplateEngineManager from "./Engines/TemplateEngineManager.js";
+
+// import debugUtil from "debug";
+// const debug = debugUtil("Eleventy:TemplateRender");
+
+class TemplateRenderUnknownEngineError extends EleventyBaseError {}
+
+// works with full path names or short engine name
+class TemplateRender {
+ #extensionMap;
+ #config;
+
+ constructor(tmplPath, config) {
+ if (!tmplPath) {
+ throw new Error(`TemplateRender requires a tmplPath argument, instead of ${tmplPath}`);
+ }
+ this.#setConfig(config);
+
+ this.engineNameOrPath = tmplPath;
+ this.parseMarkdownWith = this.config.markdownTemplateEngine;
+ this.parseHtmlWith = this.config.htmlTemplateEngine;
+ }
+
+ #setConfig(config) {
+ if (config?.constructor?.name !== "TemplateConfig") {
+ throw new Error("TemplateRender must receive a TemplateConfig instance.");
+ }
+
+ this.eleventyConfig = config;
+ this.config = config.getConfig();
+ }
+
+ 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) {
+ throw new Error("Internal error: missing `extensionMap` in TemplateRender.");
+ }
+ return this.#extensionMap;
+ }
+
+ async getEngineByName(name) {
+ // WARNING: eleventyConfig assignment removed here
+ return this.extensionMap.engineManager.getEngine(name, this.extensionMap);
+ }
+
+ // Runs once per template
+ async init(engineNameOrPath) {
+ let name = engineNameOrPath || this.engineNameOrPath;
+ this.extensionMap.setTemplateConfig(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(
+ `Don’t 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?.constructor?.name === "CustomEngine") {
+ if (
+ this.engine.entry &&
+ this.engine.entry.name &&
+ keyFromFilename !== this.engine.entry.name
+ ) {
+ return this.engine.entry.name;
+ } else {
+ // We don’t have a name for it so we return nothing so we don’t 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
+ getPreprocessorEngineName() {
+ if (this.engineName === "md" && this.parseMarkdownWith) {
+ return this.parseMarkdownWith;
+ }
+ if (this.engineName === "html" && this.parseHtmlWith) {
+ return this.parseHtmlWith;
+ }
+ // TODO do we need this?
+ return this.extensionMap.getKey(this.engineNameOrPath);
+ }
+
+ // We pass in templateEngineOverride here because it isn’t 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;
diff --git a/node_modules/@11ty/eleventy/src/TemplateWriter.js b/node_modules/@11ty/eleventy/src/TemplateWriter.js
new file mode 100755
index 0000000..79b0fee
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/TemplateWriter.js
@@ -0,0 +1,508 @@
+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 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;
+ #passthroughManager;
+ #errorHandler;
+ #extensionMap;
+
+ constructor(
+ templateFormats, // TODO remove this, see `get eleventyFiles` first
+ templateData,
+ templateConfig,
+ ) {
+ if (!templateConfig) {
+ throw new TemplateWriterMissingConfigArgError("Missing config argument.");
+ }
+ this.templateConfig = templateConfig;
+ this.config = templateConfig.getConfig();
+ this.userConfig = templateConfig.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.templateConfig.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) {
+ throw new Error("Internal error: missing `extensionMap` in TemplateWriter.");
+ }
+ return this.#extensionMap;
+ }
+
+ setPassthroughManager(mgr) {
+ this.#passthroughManager = mgr;
+ }
+
+ setEleventyFiles(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.templateConfig);
+
+ 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
+ // TODO reset other constructor things here like inputDir/outputDir
+ tmpl.resetCachedTemplate({
+ templateData: this.templateData,
+ extensionMap: this.extensionMap,
+ eleventyConfig: this.templateConfig,
+ });
+ } else {
+ tmpl = new Template(path, this.templateData, this.extensionMap, this.templateConfig);
+ tmpl.setOutputFormat(to);
+ tmpl.logger = this.logger;
+ this._templatePathCache.set(path, tmpl);
+ }
+
+ tmpl.setTransforms(this.config.transforms);
+ tmpl.setLinters(this.config.linters);
+ 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)
+ // Related: #3870, improved configuration reset
+
+ templates.push(tmpl);
+
+ // This must happen before data is generated for the incremental file only
+ if (incrementalFileShape === "template" && tmpl.inputPath === this.incrementalFile) {
+ tmpl.resetCaches();
+ } else if (
+ // Issue #3824 #3870
+ tmpl.isFileRelevantToThisTemplate(this.incrementalFile, {
+ isFullTemplate: incrementalFileShape === "template",
+ })
+ ) {
+ tmpl.resetCaches();
+ }
+
+ // IMPORTANT: This is where the data is first generated for the template
+ 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, they’re 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++;
+ }
+ }
+ }
+
+ async _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;
+ });
+
+ if (this.incrementalFile) {
+ // Top level async to get at the promises returned.
+ return await this._addToTemplateMapIncrementalBuild(incrementalFileShape, paths, to);
+ }
+
+ // Full Build
+ let ret = await this._addToTemplateMapFullBuild(paths, to);
+
+ // write new template relationships to the global dependency graph for next time
+ this.templateMap.addAllToGlobalDependencyGraph();
+
+ return ret;
+ }
+
+ async _createTemplateMap(paths, to) {
+ this.templateMap = new TemplateMap(this.templateConfig);
+
+ await this._addToTemplateMap(paths, to);
+ await this.templateMap.cache();
+
+ // Return is used by tests
+ 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) {
+ if (!this.#passthroughManager) {
+ throw new Error("Internal error: Missing `passthroughManager` instance.");
+ }
+
+ return this.#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 = [];
+ // TODO optimize await here
+ await this._createTemplateMap(paths, to);
+ 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();
+
+ // This must happen before writePassthroughCopy
+ this.templateConfig.userConfig.emit("eleventy#beforerender");
+
+ let aggregatePassthroughCopyPromise = this.writePassthroughCopy(paths);
+
+ let templatesPromise = Promise.all(await this.generateTemplates(paths)).then((results) => {
+ this.templateConfig.userConfig.emit("eleventy#render");
+
+ return results;
+ });
+
+ return Promise.all([aggregatePassthroughCopyPromise, templatesPromise]).then(
+ async ([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 = Boolean(isDryRun);
+ }
+
+ setRunInitialBuild(runInitialBuild) {
+ this.isRunInitialBuild = runInitialBuild;
+ }
+ setIncrementalBuild(isIncremental) {
+ this.isIncremental = isIncremental;
+ }
+ setIncrementalFile(incrementalFile) {
+ this.incrementalFile = incrementalFile;
+ this.#passthroughManager.setIncrementalFile(incrementalFile);
+ }
+ resetIncrementalFile() {
+ this.incrementalFile = null;
+ this.#passthroughManager.resetIncrementalFile();
+ }
+
+ getMetadata() {
+ return {
+ // copyCount, copySize
+ ...(this.#passthroughManager?.getMetadata() || {}),
+ skipCount: this.skippedCount,
+ writeCount: this.writeCount,
+ renderCount: this.renderCount,
+ };
+ }
+
+ get caches() {
+ return ["_templatePathCache"];
+ }
+}
+
+export default TemplateWriter;
diff --git a/node_modules/@11ty/eleventy/src/UserConfig.js b/node_modules/@11ty/eleventy/src/UserConfig.js
new file mode 100644
index 0000000..215327f
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/UserConfig.js
@@ -0,0 +1,1339 @@
+import chalk from "kleur";
+import { DateTime } from "luxon";
+import yaml from "js-yaml";
+import matter from "gray-matter";
+import debugUtil from "debug";
+
+import { DeepCopy, TemplatePath, isPlainObject } from "@11ty/eleventy-utils";
+
+import HtmlBasePlugin from "./Plugins/HtmlBasePlugin.js";
+import RenderPlugin from "./Plugins/RenderPlugin.js";
+import InputPathToUrlPlugin from "./Plugins/InputPathToUrl.js";
+
+import isAsyncFunction from "./Util/IsAsyncFunction.js";
+import objectFilter from "./Util/Objects/ObjectFilter.js";
+import EventEmitter from "./Util/AsyncEventEmitter.js";
+import EleventyCompatibility from "./Util/Compatibility.js";
+import EleventyBaseError from "./Errors/EleventyBaseError.js";
+import BenchmarkManager from "./Benchmark/BenchmarkManager.js";
+import JavaScriptFrontMatter from "./Engines/FrontMatter/JavaScript.js";
+import { augmentFunction } from "./Engines/Util/ContextAugmenter.js";
+
+const debug = debugUtil("Eleventy:UserConfig");
+
+class UserConfigError extends EleventyBaseError {}
+
+/**
+ * Eleventy’s user-land Configuration API
+ * @module 11ty/eleventy/UserConfig
+ */
+class UserConfig {
+ /** @type {boolean} */
+ #pluginExecution = false;
+ /** @type {boolean} */
+ #quietModeLocked = false;
+ /** @type {boolean} */
+ #dataDeepMergeModified = false;
+ /** @type {number|undefined} */
+ #uniqueId;
+ /** @type {number} */
+ #concurrency = 1;
+ // Before using os.availableParallelism(); see https://github.com/11ty/eleventy/issues/3596
+
+ constructor() {
+ // These are completely unnecessary lines to satisfy TypeScript
+ this.plugins = [];
+ this.templateFormatsAdded = [];
+ this.additionalWatchTargets = [];
+ this.watchTargetsConfigReset = new Set();
+ this.extensionMap = new Set();
+ this.dataExtensions = new Map();
+ this.urlTransforms = [];
+ this.customDateParsingCallbacks = new Set();
+ this.ignores = new Set();
+ this.events = new EventEmitter();
+
+ /** @type {object} */
+ this.directories = {};
+ /** @type {undefined} */
+ this.logger;
+ /** @type {string} */
+ this.dir;
+ /** @type {string} */
+ this.pathPrefix;
+ /** @type {object} */
+ this.errorReporting = {};
+ /** @type {object} */
+ this.templateHandling = {};
+
+ this.reset();
+ this.#uniqueId = Math.random();
+ }
+
+ // Internally used in TemplateContent for cache keys
+ _getUniqueId() {
+ return this.#uniqueId;
+ }
+
+ reset() {
+ debug("Resetting EleventyConfig to initial values.");
+
+ /** @type {EventEmitter} */
+ this.events = new EventEmitter();
+ this.events.setMaxListeners(25); // defaults to 10
+
+ /** @type {BenchmarkManager} */
+ this.benchmarkManager = new BenchmarkManager();
+
+ /** @type {object} */
+ this.benchmarks = {
+ /** @type {import('./Benchmark/BenchmarkGroup.js')} */
+ config: this.benchmarkManager.get("Configuration"),
+ /** @type {import('./Benchmark/BenchmarkGroup.js')} */
+ aggregate: this.benchmarkManager.get("Aggregate"),
+ };
+
+ /** @type {object} */
+ this.directoryAssignments = {};
+ /** @type {object} */
+ this.collections = {};
+ /** @type {object} */
+ this.precompiledCollections = {};
+ this.templateFormats = undefined;
+ this.templateFormatsAdded = [];
+
+ /** @type {object} */
+ this.universal = {
+ filters: {},
+ shortcodes: {},
+ pairedShortcodes: {},
+ };
+
+ /** @type {object} */
+ this.liquid = {
+ options: {},
+ tags: {},
+ filters: {},
+ shortcodes: {},
+ pairedShortcodes: {},
+ parameterParsing: "legacy", // or builtin
+ };
+
+ /** @type {object} */
+ this.nunjucks = {
+ // `dev: true` gives us better error messaging
+ environmentOptions: { dev: true },
+ precompiledTemplates: {},
+ filters: {},
+ asyncFilters: {},
+ tags: {},
+ globals: {},
+ shortcodes: {},
+ pairedShortcodes: {},
+ asyncShortcodes: {},
+ asyncPairedShortcodes: {},
+ };
+
+ /** @type {object} */
+ this.javascript = {
+ functions: {},
+ filters: {},
+ shortcodes: {},
+ pairedShortcodes: {},
+ };
+
+ this.markdownHighlighter = null;
+
+ /** @type {object} */
+ this.libraryOverrides = {};
+
+ /** @type {object} */
+ this.passthroughCopies = {};
+ this.passthroughCopiesHtmlRelative = new Set();
+
+ /** @type {object} */
+ this.layoutAliases = {};
+ this.layoutResolution = true; // extension-less layout files
+
+ /** @type {object} */
+ this.linters = {};
+ /** @type {object} */
+ this.transforms = {};
+ /** @type {object} */
+ this.preprocessors = {};
+
+ this.activeNamespace = "";
+ this.DateTime = DateTime;
+ this.dynamicPermalinks = true;
+
+ this.useGitIgnore = true;
+
+ let defaultIgnores = new Set();
+ defaultIgnores.add("**/node_modules/**");
+ defaultIgnores.add(".git/**"); // TODO `**/.git/**`
+ this.ignores = new Set(defaultIgnores);
+ this.watchIgnores = new Set(defaultIgnores);
+
+ this.dataDeepMerge = true;
+ this.extensionMap = new Set();
+ /** @type {object} */
+ this.extensionConflictMap = {};
+ this.watchJavaScriptDependencies = true;
+ this.additionalWatchTargets = [];
+ this.watchTargetsConfigReset = new Set();
+ /** @type {object} */
+ this.serverOptions = {};
+ /** @type {object} */
+ this.globalData = {};
+ /** @type {object} */
+ this.chokidarConfig = {};
+ this.watchThrottleWaitTime = 0; //ms
+
+ // using Map to preserve insertion order
+ this.dataExtensions = new Map();
+
+ this.quietMode = false;
+
+ this.plugins = [];
+
+ this.useTemplateCache = true;
+ this.dataFilterSelectors = new Set();
+
+ /** @type {object} */
+ this.libraryAmendments = {};
+ this.serverPassthroughCopyBehavior = "copy"; // or "passthrough"
+ this.urlTransforms = [];
+
+ // Defaults in `defaultConfig.js`
+ this.dataFileSuffixesOverride = false;
+ this.dataFileDirBaseNameOverride = false;
+
+ /** @type {object} */
+ this.frontMatterParsingOptions = {
+ // Set a project-wide default.
+ // language: "yaml",
+
+ // Supplementary engines
+ engines: {
+ yaml: yaml.load.bind(yaml),
+
+ // Backwards compatible with `js` object front matter
+ // https://github.com/11ty/eleventy/issues/2819
+ javascript: JavaScriptFrontMatter,
+
+ // Needed for fallback behavior in the new `javascript` engine
+ // @ts-ignore
+ jsLegacy: matter.engines.javascript,
+
+ node: function () {
+ throw new Error(
+ "The `node` front matter type was a 3.0.0-alpha.x only feature, removed for stable release. Rename to `js` or `javascript` instead!",
+ );
+ },
+ },
+ };
+
+ /** @type {object} */
+ this.virtualTemplates = {};
+ this.freezeReservedData = true;
+ this.customDateParsingCallbacks = new Set();
+
+ /** @type {object} */
+ this.errorReporting = {};
+ /** @type {object} */
+ this.templateHandling = {};
+
+ // Before using os.availableParallelism(); see https://github.com/11ty/eleventy/issues/3596
+ this.#concurrency = 1;
+ }
+
+ // compatibleRange is optional in 2.0.0-beta.2
+ versionCheck(compatibleRange) {
+ let compat = new EleventyCompatibility(compatibleRange);
+
+ if (!compat.isCompatible()) {
+ throw new UserConfigError(compat.getErrorMessage());
+ }
+ }
+
+ /*
+ * Events
+ */
+
+ // Duplicate event bindings are avoided with the `reset` method above.
+ // A new EventEmitter instance is created when the config is reset.
+ on(eventName, callback) {
+ return this.events.on(eventName, callback);
+ }
+
+ once(eventName, callback) {
+ return this.events.once(eventName, callback);
+ }
+
+ emit(eventName, ...args) {
+ return this.events.emit(eventName, ...args);
+ }
+
+ setEventEmitterMode(mode) {
+ this.events.setHandlerMode(mode);
+ }
+
+ /*
+ * Universal getters
+ */
+ getFilter(name) {
+ // JavaScript functions are included here for backwards compatibility https://github.com/11ty/eleventy/issues/3365
+ return this.universal.filters[name] || this.javascript.functions[name];
+ }
+
+ getFilters(options = {}) {
+ if (options.type) {
+ return objectFilter(
+ this.universal.filters,
+ (entry) => entry.__eleventyInternal?.type === options.type,
+ );
+ }
+
+ return this.universal.filters;
+ }
+
+ getShortcode(name) {
+ return this.universal.shortcodes[name];
+ }
+
+ getShortcodes(options = {}) {
+ if (options.type) {
+ return objectFilter(
+ this.universal.shortcodes,
+ (entry) => entry.__eleventyInternal?.type === options.type,
+ );
+ }
+
+ return this.universal.shortcodes;
+ }
+
+ getPairedShortcode(name) {
+ return this.universal.pairedShortcodes[name];
+ }
+
+ getPairedShortcodes(options = {}) {
+ if (options.type) {
+ return objectFilter(
+ this.universal.pairedShortcodes,
+ (entry) => entry.__eleventyInternal?.type === options.type,
+ );
+ }
+ return this.universal.pairedShortcodes;
+ }
+
+ /*
+ * Private utilities
+ */
+ #add(target, originalName, callback, options) {
+ let { description, functionName } = options;
+
+ if (typeof callback !== "function") {
+ throw new Error(`Invalid definition for "${originalName}" ${description}.`);
+ }
+
+ let name = this.getNamespacedName(originalName);
+
+ if (target[name]) {
+ debug(
+ chalk.yellow(`Warning, overwriting previous ${description} "%o" via \`%o(%o)\``),
+ name,
+ functionName,
+ originalName,
+ );
+ } else {
+ debug(`Adding new ${description} "%o" via \`%o(%o)\``, name, functionName, originalName);
+ }
+
+ target[name] = this.#decorateCallback(`"${name}" ${description}`, callback);
+ }
+
+ #decorateCallback(type, callback) {
+ return this.benchmarks.config.add(type, callback);
+ }
+
+ /*
+ * Markdown
+ */
+
+ // This is a method for plugins, probably shouldn’t use this in projects.
+ // Projects should use `setLibrary` as documented here:
+ // https://github.com/11ty/eleventy/blob/master/docs/engines/markdown.md#use-your-own-options
+ addMarkdownHighlighter(highlightFn) {
+ this.markdownHighlighter = highlightFn;
+ }
+
+ /*
+ * Filters
+ */
+
+ addLiquidFilter(name, callback) {
+ this.#add(this.liquid.filters, name, callback, {
+ description: "Liquid Filter",
+ functionName: "addLiquidFilter",
+ });
+ }
+
+ addNunjucksAsyncFilter(name, callback) {
+ this.#add(this.nunjucks.asyncFilters, name, callback, {
+ description: "Nunjucks Filter",
+ functionName: "addNunjucksAsyncFilter",
+ });
+ }
+
+ // Support the nunjucks style syntax for asynchronous filter add
+ addNunjucksFilter(name, callback, isAsync = false) {
+ if (isAsync) {
+ // namespacing happens downstream
+ this.addNunjucksAsyncFilter(name, callback);
+ } else {
+ this.#add(this.nunjucks.filters, name, callback, {
+ description: "Nunjucks Filter",
+ functionName: "addNunjucksFilter",
+ });
+ }
+ }
+
+ addJavaScriptFilter(name, callback) {
+ this.#add(this.javascript.filters, name, callback, {
+ description: "JavaScript Filter",
+ functionName: "addJavaScriptFilter",
+ });
+
+ // Backwards compat for a time before `addJavaScriptFilter` existed.
+ this.addJavaScriptFunction(name, callback);
+ }
+
+ addFilter(name, callback) {
+ // This method *requires* `async function` and will not work with `function` that returns a promise
+ if (isAsyncFunction(callback)) {
+ this.addAsyncFilter(name, callback);
+ return;
+ }
+
+ // namespacing happens downstream
+ this.#add(this.universal.filters, name, callback, {
+ description: "Universal Filter",
+ functionName: "addFilter",
+ });
+
+ this.addLiquidFilter(name, callback);
+ this.addJavaScriptFilter(name, callback);
+ this.addNunjucksFilter(
+ name,
+ /** @this {any} */
+ function (...args) {
+ // Note that `callback` is already a function as the `#add` method throws an error if not.
+ let ret = callback.call(this, ...args);
+ if (ret instanceof Promise) {
+ throw new Error(
+ `Nunjucks *is* async-friendly with \`addFilter("${name}", async function() {})\` but you need to supply an \`async function\`. You returned a promise from \`addFilter("${name}", function() {})\`. Alternatively, use the \`addAsyncFilter("${name}")\` configuration API method.`,
+ );
+ }
+ return ret;
+ },
+ );
+ }
+
+ // Liquid, Nunjucks, and JS only
+ addAsyncFilter(name, callback) {
+ // namespacing happens downstream
+ this.#add(this.universal.filters, name, callback, {
+ description: "Universal Filter",
+ functionName: "addAsyncFilter",
+ });
+
+ this.addLiquidFilter(name, callback);
+ this.addJavaScriptFilter(name, callback);
+ this.addNunjucksAsyncFilter(
+ name,
+ /** @this {any} */
+ async function (...args) {
+ let cb = args.pop();
+ // Note that `callback` is already a function as the `#add` method throws an error if not.
+ let ret = await callback.call(this, ...args);
+ cb(null, ret);
+ },
+ );
+ }
+
+ /*
+ * Shortcodes
+ */
+
+ addShortcode(name, callback) {
+ // This method *requires* `async function` and will not work with `function` that returns a promise
+ if (isAsyncFunction(callback)) {
+ this.addAsyncShortcode(name, callback);
+ return;
+ }
+
+ this.#add(this.universal.shortcodes, name, callback, {
+ description: "Universal Shortcode",
+ functionName: "addShortcode",
+ });
+
+ this.addLiquidShortcode(name, callback);
+ this.addJavaScriptShortcode(name, callback);
+ this.addNunjucksShortcode(name, callback);
+ }
+
+ addAsyncShortcode(name, callback) {
+ this.#add(this.universal.shortcodes, name, callback, {
+ description: "Universal Shortcode",
+ functionName: "addAsyncShortcode",
+ });
+
+ // Related: #498
+ this.addNunjucksAsyncShortcode(name, callback);
+ this.addLiquidShortcode(name, callback);
+ this.addJavaScriptShortcode(name, callback);
+ }
+
+ addNunjucksAsyncShortcode(name, callback) {
+ this.#add(this.nunjucks.asyncShortcodes, name, callback, {
+ description: "Nunjucks Async Shortcode",
+ functionName: "addNunjucksAsyncShortcode",
+ });
+ }
+
+ addNunjucksShortcode(name, callback, isAsync = false) {
+ if (isAsync) {
+ this.addNunjucksAsyncShortcode(name, callback);
+ } else {
+ this.#add(this.nunjucks.shortcodes, name, callback, {
+ description: "Nunjucks Shortcode",
+ functionName: "addNunjucksShortcode",
+ });
+ }
+ }
+
+ addLiquidShortcode(name, callback) {
+ this.#add(this.liquid.shortcodes, name, callback, {
+ description: "Liquid Shortcode",
+ functionName: "addLiquidShortcode",
+ });
+ }
+
+ addPairedShortcode(name, callback) {
+ // This method *requires* `async function` and will not work with `function` that returns a promise
+ if (isAsyncFunction(callback)) {
+ this.addPairedAsyncShortcode(name, callback);
+ return;
+ }
+
+ this.#add(this.universal.pairedShortcodes, name, callback, {
+ description: "Universal Paired Shortcode",
+ functionName: "addPairedShortcode",
+ });
+
+ this.addPairedNunjucksShortcode(name, callback);
+ this.addPairedLiquidShortcode(name, callback);
+ this.addPairedJavaScriptShortcode(name, callback);
+ }
+
+ // Related: #498
+ addPairedAsyncShortcode(name, callback) {
+ this.#add(this.universal.pairedShortcodes, name, callback, {
+ description: "Universal Paired Async Shortcode",
+ functionName: "addPairedAsyncShortcode",
+ });
+
+ this.addPairedNunjucksAsyncShortcode(name, callback);
+ this.addPairedLiquidShortcode(name, callback);
+ this.addPairedJavaScriptShortcode(name, callback);
+ }
+
+ addPairedNunjucksAsyncShortcode(name, callback) {
+ this.#add(this.nunjucks.asyncPairedShortcodes, name, callback, {
+ description: "Nunjucks Async Paired Shortcode",
+ functionName: "addPairedNunjucksAsyncShortcode",
+ });
+ }
+
+ addPairedNunjucksShortcode(name, callback, isAsync = false) {
+ if (isAsync) {
+ this.addPairedNunjucksAsyncShortcode(name, callback);
+ } else {
+ this.#add(this.nunjucks.pairedShortcodes, name, callback, {
+ description: "Nunjucks Paired Shortcode",
+ functionName: "addPairedNunjucksShortcode",
+ });
+ }
+ }
+
+ addPairedLiquidShortcode(name, callback) {
+ this.#add(this.liquid.pairedShortcodes, name, callback, {
+ description: "Liquid Paired Shortcode",
+ functionName: "addPairedLiquidShortcode",
+ });
+ }
+
+ addJavaScriptShortcode(name, callback) {
+ this.#add(this.javascript.shortcodes, name, callback, {
+ description: "JavaScript Shortcode",
+ functionName: "addJavaScriptShortcode",
+ });
+
+ // Backwards compat for a time before `addJavaScriptShortcode` existed.
+ this.addJavaScriptFunction(name, callback);
+ }
+
+ addPairedJavaScriptShortcode(name, callback) {
+ this.#add(this.javascript.pairedShortcodes, name, callback, {
+ description: "JavaScript Paired Shortcode",
+ functionName: "addPairedJavaScriptShortcode",
+ });
+
+ // Backwards compat for a time before `addJavaScriptShortcode` existed.
+ this.addJavaScriptFunction(name, callback);
+ }
+
+ // Both Filters and shortcodes feed into this
+ addJavaScriptFunction(name, callback) {
+ this.#add(this.javascript.functions, name, callback, {
+ description: "JavaScript Function",
+ functionName: "addJavaScriptFunction",
+ });
+ }
+
+ /*
+ * Custom Tags
+ */
+
+ // tagCallback: function(liquidEngine) { return { parse: …, render: … }} };
+ addLiquidTag(name, tagFn) {
+ if (typeof tagFn !== "function") {
+ throw new UserConfigError(
+ `EleventyConfig.addLiquidTag expects a callback function to be passed in for ${name}: addLiquidTag(name, function(liquidEngine) { return { parse: …, render: … } })`,
+ );
+ }
+
+ this.#add(this.liquid.tags, name, tagFn, {
+ description: "Liquid Custom Tag",
+ functionName: "addLiquidTag",
+ });
+ }
+
+ addNunjucksTag(name, tagFn) {
+ if (typeof tagFn !== "function") {
+ throw new UserConfigError(
+ `EleventyConfig.addNunjucksTag expects a callback function to be passed in for ${name}: addNunjucksTag(name, function(nunjucksEngine) {})`,
+ );
+ }
+
+ this.#add(this.nunjucks.tags, name, tagFn, {
+ description: "Nunjucks Custom Tag",
+ functionName: "addNunjucksTag",
+ });
+ }
+
+ /*
+ * Plugins
+ */
+
+ // Internal method
+ _enablePluginExecution() {
+ this.#pluginExecution = true;
+ }
+
+ // Internal method
+ _disablePluginExecution() {
+ this.#pluginExecution = false;
+ }
+
+ /* Config is executed in two stages and plugins are the second stage—are we in the plugins stage? */
+ isPluginExecution() {
+ return this.#pluginExecution;
+ }
+
+ /**
+ * @typedef {function|Promise<function>|object} PluginDefinition
+ * @property {Function} [configFunction]
+ * @property {string} [eleventyPackage]
+ * @property {object} [eleventyPluginOptions={}]
+ * @property {boolean} [eleventyPluginOptions.unique]
+ */
+
+ /**
+ * addPlugin: async friendly in 3.0
+ *
+ * @param {PluginDefinition} plugin
+ */
+ addPlugin(plugin, options = {}) {
+ // First addPlugin of a unique plugin wins
+ if (plugin?.eleventyPluginOptions?.unique && this.hasPlugin(plugin)) {
+ debug("Skipping duplicate unique addPlugin for %o", this._getPluginName(plugin));
+ return;
+ }
+
+ if (this.isPluginExecution() || options?.immediate) {
+ // this might return a promise
+ return this._executePlugin(plugin, options);
+ } else {
+ this.plugins.push({
+ plugin,
+ options,
+ pluginNamespace: this.activeNamespace,
+ });
+ }
+ }
+
+ /** @param {string} name */
+ resolvePlugin(name) {
+ let filenameLookup = {
+ "@11ty/eleventy/html-base-plugin": HtmlBasePlugin,
+ "@11ty/eleventy/render-plugin": RenderPlugin,
+ "@11ty/eleventy/inputpath-to-url-plugin": InputPathToUrlPlugin,
+
+ // Async plugins:
+ // requires e.g. `await resolvePlugin("@11ty/eleventy/i18n-plugin")` to avoid preloading i18n dependencies.
+ // see https://github.com/11ty/eleventy-plugin-rss/issues/52
+ "@11ty/eleventy/i18n-plugin": "./Plugins/I18nPlugin.js",
+ };
+
+ if (!filenameLookup[name]) {
+ throw new Error(
+ `Invalid name "${name}" passed to resolvePlugin. Valid options: ${Object.keys(filenameLookup).join(", ")}`,
+ );
+ }
+
+ // Future improvement: add support for any npm package name?
+ if (typeof filenameLookup[name] === "string") {
+ // returns promise
+ return import(filenameLookup[name]).then((plugin) => plugin.default);
+ }
+
+ // return reference
+ return filenameLookup[name];
+ }
+
+ /** @param {string|PluginDefinition} plugin */
+ hasPlugin(plugin) {
+ let pluginName;
+ if (typeof plugin === "string") {
+ pluginName = plugin;
+ } else {
+ pluginName = this._getPluginName(plugin);
+ }
+
+ return this.plugins.some((entry) => this._getPluginName(entry.plugin) === pluginName);
+ }
+
+ // Using Function.name https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/name#examples
+ /** @param {PluginDefinition} plugin */
+ _getPluginName(plugin) {
+ if (plugin?.eleventyPackage) {
+ return plugin.eleventyPackage;
+ }
+ if (typeof plugin === "function") {
+ return plugin.name;
+ }
+ if (plugin?.configFunction && typeof plugin.configFunction === "function") {
+ return plugin.configFunction.name;
+ }
+ }
+
+ // Starting in 3.0 the plugin callback might be asynchronous!
+ _executePlugin(plugin, options) {
+ let name = this._getPluginName(plugin);
+ let ret;
+ debug(`Adding %o plugin`, name || "anonymous");
+ let pluginBenchmark = this.benchmarks.aggregate.get("Configuration addPlugin");
+
+ if (typeof plugin === "function") {
+ pluginBenchmark.before();
+ this.benchmarks.config;
+ let configFunction = plugin;
+ ret = configFunction(this, options);
+ pluginBenchmark.after();
+ } else if (plugin?.configFunction) {
+ pluginBenchmark.before();
+
+ if (options && typeof options.init === "function") {
+ // init is not yet async-friendly but it’s also barely used
+ options.init.call(this, plugin.initArguments || {});
+ }
+
+ ret = plugin.configFunction(this, options);
+ pluginBenchmark.after();
+ } else {
+ throw new UserConfigError(
+ "Invalid EleventyConfig.addPlugin signature. Should be a function or a valid Eleventy plugin object.",
+ );
+ }
+ return ret;
+ }
+
+ /** @param {string} name */
+ getNamespacedName(name) {
+ return this.activeNamespace + name;
+ }
+
+ async namespace(pluginNamespace, callback) {
+ let validNamespace = pluginNamespace && typeof pluginNamespace === "string";
+ if (validNamespace) {
+ this.activeNamespace = pluginNamespace || "";
+ }
+
+ await callback(this);
+
+ if (validNamespace) {
+ this.activeNamespace = "";
+ }
+ }
+
+ /**
+ * Adds a path to a file or directory to the list of pass-through copies
+ * which are copied as-is to the output.
+ *
+ * @param {string|object} fileOrDir The path to the file or directory that should
+ * be copied. OR an object where the key is the input glob and the property is the output directory
+ * @param {object} copyOptions options for recursive-copy.
+ * see https://www.npmjs.com/package/recursive-copy#arguments
+ * default options are defined in TemplatePassthrough copyOptionsDefault
+ * @returns {any} a reference to the `EleventyConfig` object.
+ */
+ addPassthroughCopy(fileOrDir, copyOptions = {}) {
+ if (copyOptions.mode) {
+ if (copyOptions.mode !== "html-relative") {
+ throw new Error(
+ "Invalid `mode` option for `addPassthroughCopy`. Received: '" + copyOptions.mode + "'",
+ );
+ }
+ if (isPlainObject(fileOrDir)) {
+ throw new Error(
+ "mode: 'html-relative' does not yet support passthrough copy objects (input -> output mapping). Use a string glob or an Array of string globs.",
+ );
+ }
+
+ this.passthroughCopiesHtmlRelative?.add({
+ match: fileOrDir,
+ ...copyOptions,
+ });
+ } else if (typeof fileOrDir === "string") {
+ this.passthroughCopies[fileOrDir] = { outputPath: true, copyOptions };
+ } else {
+ for (let [inputPath, outputPath] of Object.entries(fileOrDir)) {
+ this.passthroughCopies[inputPath] = { outputPath, copyOptions };
+ }
+ }
+
+ return this;
+ }
+
+ /*
+ * Template Formats
+ */
+ _normalizeTemplateFormats() {
+ throw new Error("The internal _normalizeTemplateFormats() method was removed in Eleventy 3.0");
+ }
+
+ setTemplateFormats(templateFormats) {
+ this.templateFormats = templateFormats;
+ }
+
+ // additive, usually for plugins
+ addTemplateFormats(templateFormats) {
+ this.templateFormatsAdded.push(templateFormats);
+ }
+
+ /*
+ * Library Overrides and Options
+ */
+ setLibrary(engineName, libraryInstance) {
+ if (engineName === "liquid" && Object.keys(this.liquid.options).length) {
+ debug(
+ "WARNING: using `eleventyConfig.setLibrary` will override any configuration set using `.setLiquidOptions` via the config API. You’ll need to pass these options to the library yourself.",
+ );
+ } else if (engineName === "njk" && Object.keys(this.nunjucks.environmentOptions).length) {
+ debug(
+ "WARNING: using `eleventyConfig.setLibrary` will override any configuration set using `.setNunjucksEnvironmentOptions` via the config API. You’ll need to pass these options to the library yourself.",
+ );
+ }
+
+ this.libraryOverrides[engineName.toLowerCase()] = libraryInstance;
+ }
+
+ /* These callbacks run on both libraryOverrides and default library instances */
+ amendLibrary(engineName, callback) {
+ let name = engineName.toLowerCase();
+ if (!this.libraryAmendments[name]) {
+ this.libraryAmendments[name] = [];
+ }
+
+ this.libraryAmendments[name].push(callback);
+ }
+
+ setLiquidOptions(options) {
+ this.liquid.options = options;
+ }
+
+ setLiquidParameterParsing(behavior) {
+ if (behavior !== "legacy" && behavior !== "builtin") {
+ throw new Error(
+ `Invalid argument passed to \`setLiquidParameterParsing\`. Expected one of "legacy" or "builtin".`,
+ );
+ }
+ this.liquid.parameterParsing = behavior;
+ }
+
+ setNunjucksEnvironmentOptions(options) {
+ this.nunjucks.environmentOptions = options;
+ }
+
+ setNunjucksPrecompiledTemplates(templates) {
+ this.nunjucks.precompiledTemplates = templates;
+ }
+
+ setDynamicPermalinks(enabled) {
+ this.dynamicPermalinks = !!enabled;
+ }
+
+ setUseGitIgnore(enabled) {
+ this.useGitIgnore = !!enabled;
+ }
+
+ setDataDeepMerge(deepMerge) {
+ this.#dataDeepMergeModified = true;
+ this.dataDeepMerge = !!deepMerge;
+ }
+
+ // Used by the Upgrade Helper Plugin
+ isDataDeepMergeModified() {
+ return this.#dataDeepMergeModified;
+ }
+
+ addWatchTarget(additionalWatchTargets, options = {}) {
+ // Reset the config when the target path changes
+ if (options.resetConfig) {
+ this.watchTargetsConfigReset.add(additionalWatchTargets);
+ }
+
+ this.additionalWatchTargets.push(additionalWatchTargets);
+ }
+
+ setWatchJavaScriptDependencies(watchEnabled) {
+ this.watchJavaScriptDependencies = !!watchEnabled;
+ }
+
+ setServerOptions(options = {}, override = false) {
+ if (override) {
+ this.serverOptions = options;
+ } else {
+ this.serverOptions = DeepCopy(this.serverOptions, options);
+ }
+ }
+
+ setBrowserSyncConfig() {
+ this._attemptedBrowserSyncUse = true;
+ debug(
+ "The `setBrowserSyncConfig` method was removed in Eleventy 2.0.0. Use `setServerOptions` with the new Eleventy development server or the `@11ty/eleventy-browser-sync` plugin moving forward.",
+ );
+ }
+
+ setChokidarConfig(options = {}) {
+ this.chokidarConfig = options;
+ }
+
+ setWatchThrottleWaitTime(time = 0) {
+ this.watchThrottleWaitTime = time;
+ }
+
+ // 3.0 change: this does a top level merge instead of reset.
+ setFrontMatterParsingOptions(options = {}) {
+ DeepCopy(this.frontMatterParsingOptions, options);
+ }
+
+ /* Internal method for CLI --quiet */
+ _setQuietModeOverride(quietMode) {
+ this.setQuietMode(quietMode);
+ this.#quietModeLocked = true;
+ }
+
+ setQuietMode(quietMode) {
+ if (this.#quietModeLocked) {
+ debug(
+ "Attempt to `setQuietMode(%o)` ignored, --quiet command line argument override in place.",
+ !!quietMode,
+ );
+ // override via CLI takes precedence
+ return;
+ }
+
+ this.quietMode = !!quietMode;
+ }
+
+ addExtension(fileExtension, options = {}) {
+ let extensions;
+
+ // Array support added in 2.0.0-canary.19
+ if (Array.isArray(fileExtension)) {
+ extensions = fileExtension;
+ } else {
+ // single string
+ extensions = [fileExtension];
+ }
+
+ for (let extension of extensions) {
+ if (this.extensionConflictMap[extension]) {
+ throw new Error(
+ `An attempt was made to override the "${extension}" template syntax twice (via the \`addExtension\` configuration API). A maximum of one override is currently supported.`,
+ );
+ }
+ this.extensionConflictMap[extension] = true;
+
+ /** @type {object} */
+ let extensionOptions = Object.assign(
+ {
+ // Might be overridden for aliasing in options.key
+ key: extension,
+ extension: extension,
+ },
+ options,
+ );
+
+ if (extensionOptions.key !== extensionOptions.extension) {
+ extensionOptions.aliasKey = extensionOptions.extension;
+ }
+
+ this.extensionMap.add(extensionOptions);
+ }
+ }
+
+ addDataExtension(extensionList, parser) {
+ let options = {};
+ // second argument is an object with a `parser` callback
+ if (typeof parser !== "function") {
+ if (!("parser" in parser)) {
+ throw new Error(
+ "Expected `parser` property in second argument object to `eleventyConfig.addDataExtension`",
+ );
+ }
+
+ options = parser;
+ parser = options.parser;
+ }
+
+ let extensions = extensionList.split(",").map((s) => s.trim());
+ for (let extension of extensions) {
+ this.dataExtensions.set(extension, {
+ extension,
+ parser,
+ options,
+ });
+ }
+ }
+
+ setUseTemplateCache(bypass) {
+ this.useTemplateCache = !!bypass;
+ }
+
+ setPrecompiledCollections(collections) {
+ this.precompiledCollections = collections;
+ }
+
+ // "passthrough" is the default, no other value is explicitly required in code
+ // but opt-out via "copy" is suggested
+ setServerPassthroughCopyBehavior(behavior) {
+ this.serverPassthroughCopyBehavior = behavior;
+ }
+
+ // Url transforms change page.url and work good with server side content-negotiation (e.g. i18n plugin)
+ addUrlTransform(callback) {
+ this.urlTransforms.push(callback);
+ }
+
+ setDataFileSuffixes(suffixArray) {
+ this.dataFileSuffixesOverride = suffixArray;
+ }
+
+ setDataFileBaseName(baseName) {
+ this.dataFileDirBaseNameOverride = baseName;
+ }
+
+ addTemplate(virtualInputPath, content, data) {
+ // Lookups keys must be normalized
+ virtualInputPath = TemplatePath.stripLeadingDotSlash(
+ TemplatePath.standardizeFilePath(virtualInputPath),
+ );
+ if (this.virtualTemplates[virtualInputPath]) {
+ throw new Error(
+ "Virtual template conflict: you can’t add multiple virtual templates that have the same inputPath: " +
+ virtualInputPath,
+ );
+ }
+
+ this.virtualTemplates[virtualInputPath] = {
+ inputPath: virtualInputPath,
+ data,
+ content,
+ };
+ }
+
+ isVirtualTemplate(virtualInputPath) {
+ return Boolean(this.virtualTemplates[virtualInputPath]);
+ }
+
+ #setDirectory(key, dir) {
+ if (this.isPluginExecution()) {
+ throw new Error(
+ "The `set*Directory` configuration API methods are not yet allowed in plugins.",
+ );
+ }
+ this.directoryAssignments[key] = dir;
+ }
+
+ setInputDirectory(dir) {
+ this.#setDirectory("input", dir);
+ }
+
+ setOutputDirectory(dir) {
+ this.#setDirectory("output", dir);
+ }
+
+ setDataDirectory(dir) {
+ this.#setDirectory("data", dir);
+ }
+
+ setIncludesDirectory(dir) {
+ this.#setDirectory("includes", dir);
+ }
+
+ setLayoutsDirectory(dir) {
+ this.#setDirectory("layouts", dir);
+ }
+
+ // Some data keywords in Eleventy are reserved, throw an error if an application tries to set these.
+ setFreezeReservedData(bool) {
+ this.freezeReservedData = !!bool;
+ }
+
+ addDateParsing(callback) {
+ if (typeof callback === "function") {
+ this.customDateParsingCallbacks.add(callback);
+ } else {
+ throw new Error("addDateParsing expects a function argument.");
+ }
+ }
+
+ // 3.0.0-alpha.18 started merging conflicts here (when possible), issue #3389
+ addGlobalData(name, data) {
+ name = this.getNamespacedName(name);
+ if (this.globalData[name]) {
+ if (isPlainObject(this.globalData[name]) && isPlainObject(data)) {
+ DeepCopy(this.globalData[name], data);
+ } else {
+ debug("Warning: overwriting a previous value set with addGlobalData(%o)", name);
+ this.globalData[name] = data;
+ }
+ } else {
+ this.globalData[name] = data;
+ }
+ return this;
+ }
+
+ addNunjucksGlobal(name, globalType) {
+ name = this.getNamespacedName(name);
+
+ if (this.nunjucks.globals[name]) {
+ debug(
+ chalk.yellow("Warning, overwriting a Nunjucks global with `addNunjucksGlobal(%o)`"),
+ name,
+ );
+ }
+
+ if (typeof globalType === "function") {
+ this.nunjucks.globals[name] = this.#decorateCallback(`"${name}" Nunjucks Global`, globalType);
+ } else {
+ this.nunjucks.globals[name] = globalType;
+ }
+ }
+
+ addTransform(name, callback) {
+ name = this.getNamespacedName(name);
+
+ this.transforms[name] = this.#decorateCallback(`"${name}" Transform`, callback);
+ }
+
+ addPreprocessor(name, fileExtensions, callback) {
+ name = this.getNamespacedName(name);
+
+ this.preprocessors[name] = {
+ filter: fileExtensions,
+ callback: this.#decorateCallback(`"${name}" Preprocessor`, callback),
+ };
+ }
+
+ addLinter(name, callback) {
+ name = this.getNamespacedName(name);
+
+ this.linters[name] = this.#decorateCallback(`"${name}" Linter`, callback);
+ }
+
+ addLayoutAlias(from, to) {
+ this.layoutAliases[from] = to;
+ }
+
+ setLayoutResolution(resolution) {
+ this.layoutResolution = !!resolution;
+ }
+
+ // compat
+ enableLayoutResolution() {
+ this.layoutResolution = true;
+ }
+
+ configureErrorReporting(options = {}) {
+ // allowMissingExtensions: true
+ Object.assign(this.errorReporting, options);
+ }
+
+ configureTemplateHandling(options = {}) {
+ // writeMode: "sync" // "async"
+ Object.assign(this.templateHandling, options);
+ }
+
+ /*
+ * Collections
+ */
+
+ // get config defined collections
+ getCollections() {
+ return this.collections;
+ }
+
+ addCollection(name, callback) {
+ name = this.getNamespacedName(name);
+
+ if (this.collections[name]) {
+ throw new UserConfigError(
+ `config.addCollection(${name}) already exists. Try a different name for your collection.`,
+ );
+ }
+
+ this.collections[name] = callback;
+ }
+
+ augmentFunctionContext(fn, options) {
+ let t = typeof fn;
+ if (t !== "function") {
+ throw new UserConfigError(
+ "Invalid type passed to `augmentFunctionContext`—function was expected and received: " + t,
+ );
+ }
+
+ return augmentFunction(fn, options);
+ }
+
+ setConcurrency(number) {
+ if (typeof number !== "number") {
+ throw new UserConfigError("Argument passed to `setConcurrency` must be a number.");
+ }
+
+ this.#concurrency = number;
+ }
+
+ getConcurrency() {
+ return this.#concurrency;
+ }
+
+ getMergingConfigObject() {
+ let obj = {
+ // filters removed in 1.0 (use addTransform instead)
+ transforms: this.transforms,
+ linters: this.linters,
+ preprocessors: this.preprocessors,
+ globalData: this.globalData,
+ layoutAliases: this.layoutAliases,
+ layoutResolution: this.layoutResolution,
+ passthroughCopiesHtmlRelative: this.passthroughCopiesHtmlRelative,
+ passthroughCopies: this.passthroughCopies,
+
+ // Liquid
+ liquidOptions: this.liquid.options,
+ liquidTags: this.liquid.tags,
+ liquidFilters: this.liquid.filters,
+ liquidShortcodes: this.liquid.shortcodes,
+ liquidPairedShortcodes: this.liquid.pairedShortcodes,
+ liquidParameterParsing: this.liquid.parameterParsing,
+
+ // Nunjucks
+ nunjucksEnvironmentOptions: this.nunjucks.environmentOptions,
+ nunjucksPrecompiledTemplates: this.nunjucks.precompiledTemplates,
+ nunjucksFilters: this.nunjucks.filters,
+ nunjucksAsyncFilters: this.nunjucks.asyncFilters,
+ nunjucksTags: this.nunjucks.tags,
+ nunjucksGlobals: this.nunjucks.globals,
+ nunjucksAsyncShortcodes: this.nunjucks.asyncShortcodes,
+ nunjucksShortcodes: this.nunjucks.shortcodes,
+ nunjucksAsyncPairedShortcodes: this.nunjucks.asyncPairedShortcodes,
+ nunjucksPairedShortcodes: this.nunjucks.pairedShortcodes,
+
+ // 11ty.js
+ javascriptFunctions: this.javascript.functions, // filters and shortcodes, combined
+ javascriptShortcodes: this.javascript.shortcodes,
+ javascriptPairedShortcodes: this.javascript.pairedShortcodes,
+ javascriptFilters: this.javascript.filters,
+
+ // Markdown
+ markdownHighlighter: this.markdownHighlighter,
+
+ libraryOverrides: this.libraryOverrides,
+ dynamicPermalinks: this.dynamicPermalinks,
+ useGitIgnore: this.useGitIgnore,
+ ignores: this.ignores,
+ watchIgnores: this.watchIgnores,
+ dataDeepMerge: this.dataDeepMerge,
+ watchJavaScriptDependencies: this.watchJavaScriptDependencies,
+ additionalWatchTargets: this.additionalWatchTargets,
+ watchTargetsConfigReset: this.watchTargetsConfigReset,
+ serverOptions: this.serverOptions,
+ chokidarConfig: this.chokidarConfig,
+ watchThrottleWaitTime: this.watchThrottleWaitTime,
+ frontMatterParsingOptions: this.frontMatterParsingOptions,
+ dataExtensions: this.dataExtensions,
+ extensionMap: this.extensionMap,
+ quietMode: this.quietMode,
+ events: this.events,
+ benchmarkManager: this.benchmarkManager,
+ plugins: this.plugins,
+ useTemplateCache: this.useTemplateCache,
+ precompiledCollections: this.precompiledCollections,
+ dataFilterSelectors: this.dataFilterSelectors,
+ libraryAmendments: this.libraryAmendments,
+ serverPassthroughCopyBehavior: this.serverPassthroughCopyBehavior,
+ urlTransforms: this.urlTransforms,
+ virtualTemplates: this.virtualTemplates,
+ // `directories` and `directoryAssignments` are merged manually prior to plugin processing
+ freezeReservedData: this.freezeReservedData,
+ customDateParsing: this.customDateParsingCallbacks,
+ errorReporting: this.errorReporting,
+ templateHandling: this.templateHandling,
+ };
+
+ if (Array.isArray(this.dataFileSuffixesOverride)) {
+ // no upstream merging of this array, so we add the override: prefix
+ obj["override:dataFileSuffixes"] = this.dataFileSuffixesOverride;
+ }
+
+ if (this.dataFileDirBaseNameOverride) {
+ obj.dataFileDirBaseNameOverride = this.dataFileDirBaseNameOverride;
+ }
+
+ return obj;
+ }
+
+ // No-op functions for backwards compat
+ addHandlebarsHelper() {}
+ setPugOptions() {}
+ setEjsOptions() {}
+ addHandlebarsShortcode() {}
+ addPairedHandlebarsShortcode() {}
+}
+
+export default UserConfig;
diff --git a/node_modules/@11ty/eleventy/src/Util/ArrayUtil.js b/node_modules/@11ty/eleventy/src/Util/ArrayUtil.js
new file mode 100644
index 0000000..bcb61de
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Util/ArrayUtil.js
@@ -0,0 +1,24 @@
+export function arrayDelete(arr, match) {
+ if (!Array.isArray(arr)) {
+ return [];
+ }
+
+ if (!match) {
+ return arr;
+ }
+
+ // only mutates if found
+ if (typeof match === "function") {
+ if (arr.find(match)) {
+ return arr.filter((entry) => {
+ return !match(entry);
+ });
+ }
+ } else if (arr.includes(match)) {
+ return arr.filter((entry) => {
+ return entry !== match;
+ });
+ }
+
+ return arr;
+}
diff --git a/node_modules/@11ty/eleventy/src/Util/AsyncEventEmitter.js b/node_modules/@11ty/eleventy/src/Util/AsyncEventEmitter.js
new file mode 100644
index 0000000..0bc471f
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Util/AsyncEventEmitter.js
@@ -0,0 +1,88 @@
+import { EventEmitter } from "node:events";
+
+/**
+ * This class emits events asynchronously.
+ *
+ * Note that Eleventy has two separate event emitter instances it uses:
+ * 1. a userland one (UserConfig.js)
+ * 2. a global one for internals (EventBus.js)
+ */
+class AsyncEventEmitter extends EventEmitter {
+ #handlerMode = "parallel";
+
+ // TypeScript slop
+ constructor(...args) {
+ super(...args);
+ }
+
+ reset() {
+ // `eleventy#` event type listeners are removed at the start of each build (singletons)
+ for (let type of this.eventNames()) {
+ if (typeof type === "string" && type.startsWith("eleventy#")) {
+ this.removeAllListeners(type);
+ }
+ }
+
+ }
+
+ /**
+ * @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;
diff --git a/node_modules/@11ty/eleventy/src/Util/Compatibility.js b/node_modules/@11ty/eleventy/src/Util/Compatibility.js
new file mode 100644
index 0000000..c90a9b3
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Util/Compatibility.js
@@ -0,0 +1,59 @@
+import semver from "semver";
+
+import { getEleventyPackageJson, getWorkingProjectPackageJson } from "./ImportJsonSync.js";
+
+const pkg = getEleventyPackageJson();
+
+// Used in user config versionCheck method.
+class Compatibility {
+ static NORMALIZE_PRERELEASE_REGEX = /-canary\b/g;
+
+ static #projectPackageJson;
+
+ constructor(compatibleRange) {
+ this.compatibleRange = Compatibility.getCompatibilityValue(compatibleRange);
+ }
+
+ static get projectPackageJson() {
+ if (!this.#projectPackageJson) {
+ this.#projectPackageJson = getWorkingProjectPackageJson();
+ }
+
+ return this.#projectPackageJson;
+ }
+
+ static normalizeIdentifier(identifier) {
+ return identifier.replace(Compatibility.NORMALIZE_PRERELEASE_REGEX, "-alpha");
+ }
+
+ static getCompatibilityValue(compatibleRange) {
+ if (compatibleRange) {
+ return compatibleRange;
+ }
+
+ // fetch from project’s package.json
+ if (this.projectPackageJson?.["11ty"]?.compatibility) {
+ return this.projectPackageJson["11ty"].compatibility;
+ }
+ }
+
+ 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;
diff --git a/node_modules/@11ty/eleventy/src/Util/ConsoleLogger.js b/node_modules/@11ty/eleventy/src/Util/ConsoleLogger.js
new file mode 100644
index 0000000..ba41196
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Util/ConsoleLogger.js
@@ -0,0 +1,140 @@
+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;
+ /** @type {Readable|undefined} */
+ #outputStream;
+
+ constructor() {}
+
+ isLoggingEnabled() {
+ if (!this.isVerbose || process.env.DEBUG) {
+ return true;
+ }
+ return this.#logger !== false;
+ }
+
+ 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");
+ }
+
+ get outputStream() {
+ if (!this.#outputStream) {
+ this.#outputStream = new Readable({
+ read() {},
+ });
+ }
+ return this.#outputStream;
+ }
+
+ /** @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;
diff --git a/node_modules/@11ty/eleventy/src/Util/DateGitFirstAdded.js b/node_modules/@11ty/eleventy/src/Util/DateGitFirstAdded.js
new file mode 100644
index 0000000..0cc5959
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Util/DateGitFirstAdded.js
@@ -0,0 +1,23 @@
+import { spawnAsync } from "./SpawnAsync.js";
+
+async function getGitFirstAddedTimeStamp(filePath) {
+ try {
+ let timestamp = await spawnAsync(
+ "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],
+ );
+ return parseInt(timestamp, 10) * 1000;
+ } catch (e) {
+ // do nothing
+ }
+}
+
+// return a Date
+export default async function (inputPath) {
+ let timestamp = await getGitFirstAddedTimeStamp(inputPath);
+ if (timestamp) {
+ return new Date(timestamp);
+ }
+}
diff --git a/node_modules/@11ty/eleventy/src/Util/DateGitLastUpdated.js b/node_modules/@11ty/eleventy/src/Util/DateGitLastUpdated.js
new file mode 100644
index 0000000..1f58c11
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Util/DateGitLastUpdated.js
@@ -0,0 +1,23 @@
+import { spawnAsync } from "./SpawnAsync.js";
+
+async function getGitLastUpdatedTimeStamp(filePath) {
+ try {
+ let timestamp = await spawnAsync(
+ "git",
+ // Formats https://www.git-scm.com/docs/git-log#_pretty_formats
+ // %at author date, UNIX timestamp
+ ["log", "-1", "--format=%at", filePath],
+ );
+ return parseInt(timestamp, 10) * 1000;
+ } catch (e) {
+ // do nothing
+ }
+}
+
+// return a Date
+export default async function (inputPath) {
+ let timestamp = await getGitLastUpdatedTimeStamp(inputPath);
+ if (timestamp) {
+ return new Date(timestamp);
+ }
+}
diff --git a/node_modules/@11ty/eleventy/src/Util/DirContains.js b/node_modules/@11ty/eleventy/src/Util/DirContains.js
new file mode 100644
index 0000000..c19990c
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Util/DirContains.js
@@ -0,0 +1,10 @@
+import path from "node:path";
+
+// Returns true if subfolder is in parent (accepts absolute or relative paths for both)
+export default function (parentFolder, subFolder) {
+ // path.resolve returns an absolute path
+ if (path.resolve(subFolder).startsWith(path.resolve(parentFolder))) {
+ return true;
+ }
+ return false;
+}
diff --git a/node_modules/@11ty/eleventy/src/Util/EsmResolver.js b/node_modules/@11ty/eleventy/src/Util/EsmResolver.js
new file mode 100644
index 0000000..c098ed8
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Util/EsmResolver.js
@@ -0,0 +1,53 @@
+import debugUtil from "debug";
+import { fileURLToPath } from "node:url";
+import PathNormalizer from "./PathNormalizer.js";
+
+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 = PathNormalizer.normalizeSeperator(fileURLToPath(fileUrl));
+ // 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) {
+// }
diff --git a/node_modules/@11ty/eleventy/src/Util/EventBusUtil.js b/node_modules/@11ty/eleventy/src/Util/EventBusUtil.js
new file mode 100644
index 0000000..c749fe9
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Util/EventBusUtil.js
@@ -0,0 +1,14 @@
+import eventBus from "../EventBus.js";
+import debugUtil from "debug";
+
+const debug = debugUtil("Eleventy:EventBus");
+
+class EventBusUtil {
+ static debugCurrentListenerCounts() {
+ for (let name of eventBus.eventNames()) {
+ debug("Listeners for %o: %o", name, eventBus.listenerCount(name));
+ }
+ }
+}
+
+export default EventBusUtil;
diff --git a/node_modules/@11ty/eleventy/src/Util/ExistsCache.js b/node_modules/@11ty/eleventy/src/Util/ExistsCache.js
new file mode 100644
index 0000000..433d743
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Util/ExistsCache.js
@@ -0,0 +1,62 @@
+import fs from "node:fs";
+import { TemplatePath } from "@11ty/eleventy-utils";
+
+// Checks both files and directories
+class ExistsCache {
+ #exists = new Map();
+ #dirs = new Map();
+
+ constructor() {
+ this.lookupCount = 0;
+ }
+
+ get size() {
+ return this.#exists.size;
+ }
+
+ has(path) {
+ return this.#exists.has(path);
+ }
+
+ set(path, isExist) {
+ this.#exists.set(TemplatePath.addLeadingDotSlash(path), Boolean(isExist));
+ }
+
+ // Not yet needed
+ // setDirectory(path, isExist) {}
+
+ // Relative paths (to root directory) expected (but not enforced due to perf costs)
+ exists(path) {
+ if (!this.#exists.has(path)) {
+ let exists = fs.existsSync(path);
+ this.lookupCount++;
+
+ // mark for next time
+ this.#exists.set(path, Boolean(exists));
+
+ return exists;
+ }
+
+ return this.#exists.get(path);
+ }
+
+ isDirectory(path) {
+ if (!this.exists(path)) {
+ return false;
+ }
+
+ if (!this.#dirs.has(path)) {
+ let isDir = fs.statSync(path).isDirectory();
+ this.lookupCount++;
+
+ // mark for next time
+ this.#dirs.set(path, isDir);
+
+ return isDir;
+ }
+
+ return this.#dirs.get(path);
+ }
+}
+
+export default ExistsCache;
diff --git a/node_modules/@11ty/eleventy/src/Util/FilePathUtil.js b/node_modules/@11ty/eleventy/src/Util/FilePathUtil.js
new file mode 100644
index 0000000..1675e8e
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Util/FilePathUtil.js
@@ -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 };
diff --git a/node_modules/@11ty/eleventy/src/Util/FileSystemManager.js b/node_modules/@11ty/eleventy/src/Util/FileSystemManager.js
new file mode 100644
index 0000000..12881d7
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Util/FileSystemManager.js
@@ -0,0 +1,48 @@
+import path from "node:path";
+import fs from "node:fs";
+import { mkdir, writeFile } from "node:fs/promises";
+
+class FileSystemManager {
+ constructor(templateConfig) {
+ if (!templateConfig || templateConfig.constructor.name !== "TemplateConfig") {
+ throw new Error(
+ "Internal error: Missing `templateConfig` or was not an instance of `TemplateConfig`.",
+ );
+ }
+ this.templateConfig = templateConfig;
+ }
+
+ exists(pathname) {
+ return this.templateConfig.existsCache.exists(pathname);
+ }
+
+ async createDirectoryForFile(filePath) {
+ let dir = path.parse(filePath).dir;
+ if (!dir || this.exists(dir)) {
+ return;
+ }
+
+ return mkdir(dir, { recursive: true });
+ }
+
+ createDirectoryForFileSync(filePath) {
+ let dir = path.parse(filePath).dir;
+ if (!dir || this.exists(dir)) {
+ return;
+ }
+
+ fs.mkdirSync(dir, { recursive: true });
+ }
+
+ async writeFile(filePath, content) {
+ return writeFile(filePath, content);
+ }
+
+ writeFileSync(filePath, content) {
+ // Note: This deliberately uses the synchronous version to avoid
+ // unbounded concurrency: https://github.com/11ty/eleventy/issues/3271
+ fs.writeFileSync(filePath, content);
+ }
+}
+
+export { FileSystemManager };
diff --git a/node_modules/@11ty/eleventy/src/Util/GetJavaScriptData.js b/node_modules/@11ty/eleventy/src/Util/GetJavaScriptData.js
new file mode 100644
index 0000000..7d72a64
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Util/GetJavaScriptData.js
@@ -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;
+ }
+}
diff --git a/node_modules/@11ty/eleventy/src/Util/GlobMatcher.js b/node_modules/@11ty/eleventy/src/Util/GlobMatcher.js
new file mode 100644
index 0000000..a4a6c55
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Util/GlobMatcher.js
@@ -0,0 +1,22 @@
+import picomatch from "picomatch";
+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,
+ );
+
+ // globs: string or array of strings
+ return picomatch.isMatch(inputPath, globs, opts);
+}
+
+export { isGlobMatch };
diff --git a/node_modules/@11ty/eleventy/src/Util/GlobRemap.js b/node_modules/@11ty/eleventy/src/Util/GlobRemap.js
new file mode 100644
index 0000000..5e2bea3
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Util/GlobRemap.js
@@ -0,0 +1,85 @@
+import path from "node:path";
+import ProjectDirectories from "./ProjectDirectories.js";
+import PathNormalizer from "./PathNormalizer.js";
+
+// even on Windows (in cmd.exe) these paths are normalized to forward slashes
+// tinyglobby expects forward slashes on Windows
+const SEP = "/";
+
+class GlobRemap {
+ constructor(paths = []) {
+ this.paths = paths;
+ this.cwd = GlobRemap.getCwd(paths);
+ }
+
+ getCwd() {
+ return this.cwd;
+ }
+
+ getRemapped(paths) {
+ return paths.map((entry) => GlobRemap.remapInput(entry, this.cwd));
+ }
+
+ getInput() {
+ return this.getRemapped(this.paths);
+ }
+
+ getOutput(paths = []) {
+ return paths.map((entry) => GlobRemap.remapOutput(entry, this.cwd));
+ }
+
+ static getParentDirPrefix(filePath = "") {
+ let count = [];
+ for (let p of filePath.split(SEP)) {
+ if (p === "..") {
+ count.push("..");
+ } else {
+ break;
+ }
+ }
+
+ if (count.length > 0) {
+ // trailing slash
+ return count.join(SEP) + SEP;
+ }
+ return "";
+ }
+
+ static getLongestParentDirPrefix(filePaths) {
+ let longest = "";
+ filePaths
+ .map((entry) => {
+ return this.getParentDirPrefix(entry);
+ })
+ .filter((entry) => Boolean(entry))
+ .forEach((prefix) => {
+ if (!longest || prefix.length > longest.length) {
+ longest = prefix;
+ }
+ });
+ return longest;
+ }
+
+ // alias
+ static getCwd(filePaths) {
+ return this.getLongestParentDirPrefix(filePaths);
+ }
+
+ static remapInput(entry, cwd) {
+ if (cwd) {
+ if (!entry.startsWith("**" + SEP) && !entry.startsWith(`.git${SEP}**`)) {
+ return PathNormalizer.normalizeSeperator(ProjectDirectories.getRelativeTo(entry, cwd));
+ }
+ }
+ return entry;
+ }
+
+ static remapOutput(entry, cwd) {
+ if (cwd) {
+ return PathNormalizer.normalizeSeperator(path.join(cwd, entry));
+ }
+ return entry;
+ }
+}
+
+export default GlobRemap;
diff --git a/node_modules/@11ty/eleventy/src/Util/HtmlRelativeCopy.js b/node_modules/@11ty/eleventy/src/Util/HtmlRelativeCopy.js
new file mode 100644
index 0000000..3059014
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Util/HtmlRelativeCopy.js
@@ -0,0 +1,149 @@
+import path from "node:path";
+import { TemplatePath } from "@11ty/eleventy-utils";
+import isValidUrl from "./ValidUrl.js";
+import { isGlobMatch } from "./GlobMatcher.js";
+
+class HtmlRelativeCopy {
+ #userConfig;
+ #matchingGlobs = new Set();
+ #matchingGlobsArray;
+ #dirty = false;
+ #paths = new Set();
+ #failOnError = true;
+ #copyOptions = {
+ dot: false, // differs from standard passthrough copy
+ };
+
+ isEnabled() {
+ return this.#matchingGlobs.size > 0;
+ }
+
+ setFailOnError(failOnError) {
+ this.#failOnError = Boolean(failOnError);
+ }
+
+ setCopyOptions(opts) {
+ if (opts) {
+ Object.assign(this.#copyOptions, opts);
+ }
+ }
+
+ setUserConfig(userConfig) {
+ if (!userConfig || userConfig.constructor.name !== "UserConfig") {
+ throw new Error(
+ "Internal error: Missing `userConfig` or was not an instance of `UserConfig`.",
+ );
+ }
+ this.#userConfig = userConfig;
+ }
+
+ addPaths(paths = []) {
+ for (let path of paths) {
+ this.#paths.add(TemplatePath.getDir(path));
+ }
+ }
+
+ get matchingGlobs() {
+ if (this.#dirty || !this.#matchingGlobsArray) {
+ this.#matchingGlobsArray = Array.from(this.#matchingGlobs);
+ this.#dirty = false;
+ }
+
+ return this.#matchingGlobsArray;
+ }
+
+ addMatchingGlob(glob) {
+ if (glob) {
+ if (Array.isArray(glob)) {
+ for (let g of glob) {
+ this.#matchingGlobs.add(g);
+ }
+ } else {
+ this.#matchingGlobs.add(glob);
+ }
+ this.#dirty = true;
+ }
+ }
+
+ isSkippableHref(rawRef) {
+ if (
+ this.#matchingGlobs.size === 0 ||
+ !rawRef ||
+ path.isAbsolute(rawRef) ||
+ isValidUrl(rawRef)
+ ) {
+ return true;
+ }
+ return false;
+ }
+
+ isCopyableTarget(target) {
+ if (!isGlobMatch(target, this.matchingGlobs)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ exists(filePath) {
+ return this.#userConfig.exists(filePath);
+ }
+
+ getAliasedPath(ref) {
+ for (let dir of this.#paths) {
+ let found = TemplatePath.join(dir, ref);
+ if (this.isCopyableTarget(found) && this.exists(found)) {
+ return found;
+ }
+ }
+ }
+
+ getFilePathRelativeToProjectRoot(ref, contextFilePath) {
+ let dir = TemplatePath.getDirFromFilePath(contextFilePath);
+ return TemplatePath.join(dir, ref);
+ }
+
+ copy(fileRef, tmplInputPath, tmplOutputPath) {
+ // original ref is a full URL or no globs exist
+ if (this.isSkippableHref(fileRef)) {
+ return;
+ }
+
+ // Relative to source file’s input path
+ let source = this.getFilePathRelativeToProjectRoot(fileRef, tmplInputPath);
+ if (!this.isCopyableTarget(source)) {
+ return;
+ }
+
+ if (!this.exists(source)) {
+ // Try to alias using `options.paths`
+ let alias = this.getAliasedPath(fileRef);
+ if (!alias) {
+ if (this.#failOnError) {
+ throw new Error(
+ "Missing input file for `html-relative` Passthrough Copy file: " +
+ TemplatePath.absolutePath(source),
+ );
+ }
+
+ // don’t fail on error
+ return;
+ }
+
+ source = alias;
+ }
+
+ let target = this.getFilePathRelativeToProjectRoot(fileRef, tmplOutputPath);
+
+ // We use a Set here to allow passthrough copy manager to properly error on conflicts upstream
+ // Only errors when different inputs write to the same output
+ // Also errors if attempts to write outside the output folder.
+ this.#userConfig.emit("eleventy#copy", {
+ source,
+ target,
+ options: this.#copyOptions,
+ });
+ }
+}
+
+export { HtmlRelativeCopy };
diff --git a/node_modules/@11ty/eleventy/src/Util/HtmlTransformer.js b/node_modules/@11ty/eleventy/src/Util/HtmlTransformer.js
new file mode 100644
index 0000000..f28910f
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Util/HtmlTransformer.js
@@ -0,0 +1,172 @@
+import posthtml from "posthtml";
+import urls from "@11ty/posthtml-urls";
+import { FilePathUtil } from "./FilePathUtil.js";
+
+import { arrayDelete } from "./ArrayUtil.js";
+
+class HtmlTransformer {
+ // feature test for Eleventy Bundle Plugin
+ static SUPPORTS_PLUGINS_ENABLED_CALLBACK = true;
+
+ static TYPES = ["callbacks", "plugins"];
+
+ 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, attrName, tagName) => {
+ for (let { fn: callback } of callbacks) {
+ // already sorted by priority when added
+ url = callback.call(context, url, { attribute: attrName, tag: tagName });
+ }
+
+ 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({
+ // *could* fallback to function name, `value.name`
+ name: options.name, // for `remove` and debugging
+ fn: value, // callback or plugin
+ priority: options.priority, // sorted in descending order
+ enabled: options.enabled || (() => true),
+ options: options.pluginOptions,
+ });
+
+ target[ext].sort(HtmlTransformer.prioritySort);
+ }
+ }
+
+ addPosthtmlPlugin(extensions, plugin, options = {}) {
+ this._add(extensions, "plugins", plugin, options);
+ }
+
+ // match can be a plugin function or a filter callback(plugin => true);
+ remove(extensions, match) {
+ for (let removeType of HtmlTransformer.TYPES) {
+ for (let ext of (extensions || "").split(",")) {
+ this[removeType][ext] = arrayDelete(this[removeType][ext], match);
+ }
+ }
+ }
+
+ 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 };
diff --git a/node_modules/@11ty/eleventy/src/Util/ImportJsonSync.js b/node_modules/@11ty/eleventy/src/Util/ImportJsonSync.js
new file mode 100644
index 0000000..fa59365
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Util/ImportJsonSync.js
@@ -0,0 +1,77 @@
+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();
+ // TODO use DirContains
+ 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 || !filePath.endsWith(".json")) {
+ throw new Error(`importJsonSync expects a .json file extension (received: ${filePath})`);
+ }
+
+ return require(filePath);
+}
+
+function getEleventyPackageJson() {
+ let filePath = normalizeFilePathInEleventyPackage("package.json");
+ return importJsonSync(filePath);
+}
+
+function getModulePackageJson(dir) {
+ let filePath = findFilePathInParentDirs(TemplatePath.absolutePath(dir), "package.json");
+
+ // optional!
+ if (!filePath) {
+ return {};
+ }
+
+ return importJsonSync(filePath);
+}
+
+// This will *not* find a package.json in a parent directory above root
+function getWorkingProjectPackageJsonPath() {
+ let dir = TemplatePath.absolutePath(TemplatePath.getWorkingDir());
+ return findFilePathInParentDirs(dir, "package.json");
+}
+
+function getWorkingProjectPackageJson() {
+ let filePath = getWorkingProjectPackageJsonPath();
+
+ // optional!
+ if (!filePath) {
+ return {};
+ }
+
+ return importJsonSync(filePath);
+}
+
+export {
+ importJsonSync,
+ findFilePathInParentDirs,
+ getEleventyPackageJson,
+ getModulePackageJson,
+ getWorkingProjectPackageJson,
+ getWorkingProjectPackageJsonPath,
+};
diff --git a/node_modules/@11ty/eleventy/src/Util/IsAsyncFunction.js b/node_modules/@11ty/eleventy/src/Util/IsAsyncFunction.js
new file mode 100644
index 0000000..3c4dc65
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Util/IsAsyncFunction.js
@@ -0,0 +1,5 @@
+const ComparisonAsyncFunction = (async () => {}).constructor;
+
+export default function isAsyncFunction(fn) {
+ return fn instanceof ComparisonAsyncFunction;
+}
diff --git a/node_modules/@11ty/eleventy/src/Util/JavaScriptDependencies.js b/node_modules/@11ty/eleventy/src/Util/JavaScriptDependencies.js
new file mode 100644
index 0000000..7f6e809
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Util/JavaScriptDependencies.js
@@ -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;
diff --git a/node_modules/@11ty/eleventy/src/Util/MemoizeFunction.js b/node_modules/@11ty/eleventy/src/Util/MemoizeFunction.js
new file mode 100644
index 0000000..f66a155
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Util/MemoizeFunction.js
@@ -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);
+ };
+}
diff --git a/node_modules/@11ty/eleventy/src/Util/Objects/DeepFreeze.js b/node_modules/@11ty/eleventy/src/Util/Objects/DeepFreeze.js
new file mode 100644
index 0000000..88e2847
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Util/Objects/DeepFreeze.js
@@ -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 };
diff --git a/node_modules/@11ty/eleventy/src/Util/Objects/ObjectFilter.js b/node_modules/@11ty/eleventy/src/Util/Objects/ObjectFilter.js
new file mode 100644
index 0000000..9ce8737
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Util/Objects/ObjectFilter.js
@@ -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;
+}
diff --git a/node_modules/@11ty/eleventy/src/Util/Objects/ProxyWrap.js b/node_modules/@11ty/eleventy/src/Util/Objects/ProxyWrap.js
new file mode 100644
index 0000000..38730fd
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Util/Objects/ProxyWrap.js
@@ -0,0 +1,118 @@
+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();
+ // The fallback keys need to come first to preserve proper key order
+ // https://github.com/11ty/eleventy/issues/3849
+ if (isPlainObject(fallback)) {
+ for (let k of Reflect.ownKeys(fallback)) {
+ s.add(k);
+ }
+ }
+ for (let k of Reflect.ownKeys(target)) {
+ if (!s.has(k)) {
+ 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 (
+ (typeof fallback === "object" || typeof fallback === "function") &&
+ 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 };
diff --git a/node_modules/@11ty/eleventy/src/Util/Objects/SampleModule.mjs b/node_modules/@11ty/eleventy/src/Util/Objects/SampleModule.mjs
new file mode 100644
index 0000000..ff8b4c5
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Util/Objects/SampleModule.mjs
@@ -0,0 +1 @@
+export default {};
diff --git a/node_modules/@11ty/eleventy/src/Util/Objects/Sortable.js b/node_modules/@11ty/eleventy/src/Util/Objects/Sortable.js
new file mode 100644
index 0000000..a23d4c9
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Util/Objects/Sortable.js
@@ -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;
diff --git a/node_modules/@11ty/eleventy/src/Util/Objects/Unique.js b/node_modules/@11ty/eleventy/src/Util/Objects/Unique.js
new file mode 100644
index 0000000..8570c0c
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Util/Objects/Unique.js
@@ -0,0 +1,3 @@
+export default function Unique(arr) {
+ return Array.from(new Set(arr));
+}
diff --git a/node_modules/@11ty/eleventy/src/Util/PassthroughCopyBehaviorCheck.js b/node_modules/@11ty/eleventy/src/Util/PassthroughCopyBehaviorCheck.js
new file mode 100644
index 0000000..3dc1abb
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Util/PassthroughCopyBehaviorCheck.js
@@ -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"
+ );
+}
diff --git a/node_modules/@11ty/eleventy/src/Util/PathNormalizer.js b/node_modules/@11ty/eleventy/src/Util/PathNormalizer.js
new file mode 100644
index 0000000..cdc3253
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Util/PathNormalizer.js
@@ -0,0 +1,58 @@
+import path from "node:path";
+import { fileURLToPath } from "node:url";
+import { TemplatePath } from "@11ty/eleventy-utils";
+
+export default 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)),
+ );
+ }
+}
diff --git a/node_modules/@11ty/eleventy/src/Util/PathPrefixer.js b/node_modules/@11ty/eleventy/src/Util/PathPrefixer.js
new file mode 100644
index 0000000..abd5582
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Util/PathPrefixer.js
@@ -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;
diff --git a/node_modules/@11ty/eleventy/src/Util/Pluralize.js b/node_modules/@11ty/eleventy/src/Util/Pluralize.js
new file mode 100644
index 0000000..d35f1dd
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Util/Pluralize.js
@@ -0,0 +1,3 @@
+export default function (count, singleWord, pluralWord) {
+ return count === 1 ? singleWord : pluralWord;
+}
diff --git a/node_modules/@11ty/eleventy/src/Util/ProjectDirectories.js b/node_modules/@11ty/eleventy/src/Util/ProjectDirectories.js
new file mode 100644
index 0000000..e15e985
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Util/ProjectDirectories.js
@@ -0,0 +1,369 @@
+import fs from "node:fs";
+import path from "node:path";
+import { TemplatePath } from "@11ty/eleventy-utils";
+import { isDynamicPattern } from "tinyglobby";
+
+import DirContains from "./DirContains.js";
+
+/* Directories internally should always use *nix forward slashes */
+class ProjectDirectories {
+ static defaults = {
+ 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;
+ }
+
+ // Normalize absolute paths to relative, #3805
+ // if(path.isAbsolute(dirOrFile)) {
+ // dirOrFile = path.relative(".", dirOrFile);
+ // }
+
+ // 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 (!isDynamicPattern(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 \`tinyglobby\` 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);
+ // TODO use DirContains
+ if (this.layouts && inputPath.startsWith(this.layouts)) {
+ return false;
+ }
+
+ // if this.includes is "" (and thus is the same directory as this.input)
+ // we don’t actually know if this is a template file, so defer
+ if (this.includes && this.includes !== this.input) {
+ if (inputPath.startsWith(this.includes)) {
+ return false;
+ }
+ }
+
+ // TODO use DirContains
+ 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)),
+ );
+ }
+
+ isFileInProjectFolder(filePath) {
+ return DirContains(TemplatePath.getWorkingDir(), filePath);
+ }
+
+ isFileInOutputFolder(filePath) {
+ return DirContains(this.output, filePath);
+ }
+
+ static getRelativeTo(targetPath, cwd) {
+ return path.relative(cwd, path.join(path.resolve("."), targetPath));
+ }
+
+ // Access the data without being able to set the data.
+ 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;
diff --git a/node_modules/@11ty/eleventy/src/Util/ProjectTemplateFormats.js b/node_modules/@11ty/eleventy/src/Util/ProjectTemplateFormats.js
new file mode 100644
index 0000000..f37040e
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Util/ProjectTemplateFormats.js
@@ -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;
diff --git a/node_modules/@11ty/eleventy/src/Util/PromiseUtil.js b/node_modules/@11ty/eleventy/src/Util/PromiseUtil.js
new file mode 100644
index 0000000..fa88da0
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Util/PromiseUtil.js
@@ -0,0 +1,15 @@
+function withResolvers() {
+ if ("withResolvers" in Promise) {
+ return Promise.withResolvers();
+ }
+
+ let resolve;
+ let reject;
+ let promise = new Promise((res, rej) => {
+ resolve = res;
+ reject = rej;
+ });
+ return { promise, resolve, reject };
+}
+
+export { withResolvers };
diff --git a/node_modules/@11ty/eleventy/src/Util/Require.js b/node_modules/@11ty/eleventy/src/Util/Require.js
new file mode 100644
index 0000000..5f6412d
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Util/Require.js
@@ -0,0 +1,258 @@
+import fs from "node:fs";
+import path from "node:path";
+import { fileURLToPath } from "node:url";
+import module from "node:module";
+import { MessageChannel } from "node:worker_threads";
+
+import { TemplatePath } from "@11ty/eleventy-utils";
+
+import EleventyBaseError from "../Errors/EleventyBaseError.js";
+import eventBus from "../EventBus.js";
+
+class EleventyImportError extends EleventyBaseError {}
+
+const { port1, port2 } = new MessageChannel();
+
+// ESM Cache Buster is an enhancement that works in Node 18.19+
+// https://nodejs.org/docs/latest/api/module.html#moduleregisterspecifier-parenturl-options
+// Fixes https://github.com/11ty/eleventy/issues/3270
+// ENV variable for https://github.com/11ty/eleventy/issues/3371
+if ("register" in module && !process?.env?.ELEVENTY_SKIP_ESM_RESOLVER) {
+ module.register("./EsmResolver.js", import.meta.url, {
+ parentURL: import.meta.url,
+ data: {
+ port: port2,
+ },
+ transferList: [port2],
+ });
+}
+
+// important to clear the require.cache in CJS projects
+const require = module.createRequire(import.meta.url);
+
+const requestPromiseCache = new Map();
+
+function getImportErrorMessage(filePath, type) {
+ return `There was a problem importing '${path.relative(".", filePath)}' via ${type}`;
+}
+
+// Used for JSON imports, suffering from Node warning that import assertions experimental but also
+// throwing an error if you try to import() a JSON file without an import assertion.
+/**
+ *
+ * @returns {string|undefined}
+ */
+function loadContents(path, options = {}) {
+ let rawInput;
+ /** @type {string} */
+ let encoding = "utf8"; // JSON is utf8
+ if (options?.encoding || options?.encoding === null) {
+ encoding = options.encoding;
+ }
+
+ try {
+ // @ts-expect-error This is an error in the upstream types
+ rawInput = fs.readFileSync(path, encoding);
+ } catch (error) {
+ // @ts-expect-error Temporary
+ if (error?.code === "ENOENT") {
+ // if file does not exist, return nothing
+ return;
+ }
+
+ throw error;
+ }
+
+ // Can return a buffer, string, etc
+ if (typeof rawInput === "string") {
+ rawInput = rawInput.trim();
+ }
+
+ return rawInput;
+}
+
+let lastModifiedPaths = new Map();
+eventBus.on("eleventy.importCacheReset", (fileQueue) => {
+ for (let filePath of fileQueue) {
+ let absolutePath = TemplatePath.absolutePath(filePath);
+ let newDate = Date.now();
+ lastModifiedPaths.set(absolutePath, newDate);
+
+ // post to EsmResolver worker thread
+ if (port1) {
+ port1.postMessage({ path: absolutePath, newDate });
+ }
+
+ // ESM Eleventy when using `import()` on a CJS project file still adds to require.cache
+ if (absolutePath in (require?.cache || {})) {
+ delete require.cache[absolutePath];
+ }
+ }
+});
+
+// raw means we don’t normalize away the `default` export
+async function dynamicImportAbsolutePath(absolutePath, options = {}) {
+ let { type, returnRaw, cacheBust } = Object.assign(
+ {
+ type: undefined,
+ returnRaw: false,
+ cacheBust: false, // force cache bust
+ },
+ options,
+ );
+
+ // Short circuit for JSON files (that are optional and can be empty)
+ if (absolutePath.endsWith(".json") || type === "json") {
+ try {
+ // https://v8.dev/features/import-assertions#dynamic-import() is still experimental in Node 20
+ let rawInput = loadContents(absolutePath);
+ if (!rawInput) {
+ // should not error when file exists but is _empty_
+ return;
+ }
+ return JSON.parse(rawInput);
+ } catch (e) {
+ return Promise.reject(
+ new EleventyImportError(getImportErrorMessage(absolutePath, "fs.readFile(json)"), e),
+ );
+ }
+ }
+
+ // Removed a `require` short circuit from this piece originally added
+ // in https://github.com/11ty/eleventy/pull/3493 Was a bit faster but
+ // error messaging was worse for require(esm)
+
+ let urlPath;
+ try {
+ let u = new URL(`file:${absolutePath}`);
+
+ // Bust the import cache if this is the last modified file (or cache busting is forced)
+ if (cacheBust) {
+ lastModifiedPaths.set(absolutePath, Date.now());
+ }
+
+ if (cacheBust || lastModifiedPaths.has(absolutePath)) {
+ u.searchParams.set("_cache_bust", lastModifiedPaths.get(absolutePath));
+ }
+
+ urlPath = u.toString();
+ } catch (e) {
+ urlPath = absolutePath;
+ }
+
+ let promise;
+ if (requestPromiseCache.has(urlPath)) {
+ promise = requestPromiseCache.get(urlPath);
+ } else {
+ promise = import(urlPath);
+ requestPromiseCache.set(urlPath, promise);
+ }
+
+ return promise.then(
+ (target) => {
+ if (returnRaw) {
+ return target;
+ }
+
+ // If the only export is `default`, elevate to top (for ESM and CJS)
+ if (Object.keys(target).length === 1 && "default" in target) {
+ return target.default;
+ }
+
+ // When using import() on a CommonJS file that exports an object sometimes it
+ // returns duplicated values in `default` key, e.g. `{ default: {key: value}, key: value }`
+
+ // A few examples:
+ // module.exports = { key: false };
+ // returns `{ default: {key: false}, key: false }` as not expected.
+ // module.exports = { key: true };
+ // module.exports = { key: null };
+ // module.exports = { key: undefined };
+ // module.exports = { key: class {} };
+
+ // A few examples where it does not duplicate:
+ // module.exports = { key: 1 };
+ // returns `{ default: {key: 1} }` as expected.
+ // module.exports = { key: "value" };
+ // module.exports = { key: {} };
+ // module.exports = { key: [] };
+
+ if (type === "cjs" && "default" in target) {
+ let match = true;
+ for (let key in target) {
+ if (key === "default") {
+ continue;
+ }
+ if (key === "module.exports") {
+ continue;
+ }
+ if (target[key] !== target.default[key]) {
+ match = false;
+ }
+ }
+
+ if (match) {
+ return target.default;
+ }
+ }
+
+ // Otherwise return { default: value, named: value }
+ // Object.assign here so we can add things to it in JavaScript.js
+ return Object.assign({}, target);
+ },
+ (error) => {
+ return Promise.reject(
+ new EleventyImportError(getImportErrorMessage(absolutePath, `import(${type})`), error),
+ );
+ },
+ );
+}
+
+function normalizeFilePathInEleventyPackage(file) {
+ // Back up relative paths from ./src/Util/Require.js
+ return path.resolve(fileURLToPath(import.meta.url), "../../../", file);
+}
+
+async function dynamicImportFromEleventyPackage(file) {
+ // points to files relative to the top level Eleventy directory
+ let filePath = normalizeFilePathInEleventyPackage(file);
+
+ // Returns promise
+ return dynamicImportAbsolutePath(filePath, { type: "esm" });
+}
+
+async function dynamicImport(localPath, type, options = {}) {
+ let absolutePath = TemplatePath.absolutePath(localPath);
+ options.type = type;
+
+ // Returns promise
+ return dynamicImportAbsolutePath(absolutePath, options);
+}
+
+/* Used to import default Eleventy configuration file, raw means we don’t normalize away the `default` export */
+async function dynamicImportRawFromEleventyPackage(file) {
+ // points to files relative to the top level Eleventy directory
+ let filePath = normalizeFilePathInEleventyPackage(file);
+
+ // Returns promise
+ return dynamicImportAbsolutePath(filePath, { type: "esm", returnRaw: true });
+}
+
+/* Used to import app configuration files, raw means we don’t normalize away the `default` export */
+async function dynamicImportRaw(localPath, type) {
+ let absolutePath = TemplatePath.absolutePath(localPath);
+
+ // Returns promise
+ return dynamicImportAbsolutePath(absolutePath, { type, returnRaw: true });
+}
+
+export {
+ loadContents as EleventyLoadContent,
+ dynamicImport as EleventyImport,
+ dynamicImportRaw as EleventyImportRaw,
+ normalizeFilePathInEleventyPackage,
+
+ // no longer used in core
+ dynamicImportFromEleventyPackage as EleventyImportFromEleventy,
+ dynamicImportRawFromEleventyPackage as EleventyImportRawFromEleventy,
+};
diff --git a/node_modules/@11ty/eleventy/src/Util/ReservedData.js b/node_modules/@11ty/eleventy/src/Util/ReservedData.js
new file mode 100644
index 0000000..d726c73
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Util/ReservedData.js
@@ -0,0 +1,69 @@
+class EleventyReservedDataError extends TypeError {}
+
+class ReservedData {
+ static properties = [
+ // "pkg", // Object.freeze’d upstream
+ // "eleventy", // Object.freeze’d upstream
+ // "page" is only frozen for specific subproperties below
+ "content",
+ "collections",
+ ];
+
+ static pageProperties = [
+ "date",
+ "inputPath",
+ "fileSlug",
+ "filePathStem",
+ "outputFileExtension",
+ "templateSyntax",
+ "url",
+ "outputPath",
+ // not yet `excerpt` or `lang` set via front matter and computed data
+ ];
+
+ // Check in the data cascade for reserved data properties.
+ static getReservedKeys(data) {
+ if (!data) {
+ return [];
+ }
+
+ let keys = this.properties.filter((key) => {
+ return key in data;
+ });
+
+ if ("page" in data) {
+ if (typeof data.page === "object") {
+ for (let key of this.pageProperties) {
+ if (key in data.page) {
+ keys.push(`page.${key}`);
+ }
+ }
+ } else {
+ // fail `page` when set to non-object values.
+ keys.push("page");
+ }
+ }
+ return keys;
+ }
+
+ static check(data) {
+ let reserved = ReservedData.getReservedKeys(data);
+ if (reserved.length === 0) {
+ return;
+ }
+
+ let error = new EleventyReservedDataError(
+ `Cannot override reserved Eleventy properties: ${reserved.join(", ")}`,
+ );
+
+ error.reservedNames = reserved;
+
+ throw error;
+ }
+
+ static isReservedDataError(e) {
+ return e instanceof EleventyReservedDataError;
+ }
+}
+
+export default ReservedData;
diff --git a/node_modules/@11ty/eleventy/src/Util/SetUnion.js b/node_modules/@11ty/eleventy/src/Util/SetUnion.js
new file mode 100644
index 0000000..50e6b9c
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Util/SetUnion.js
@@ -0,0 +1,11 @@
+function setUnion(...sets) {
+ let root = new Set();
+ for (let set of sets) {
+ for (let entry of set) {
+ root.add(entry);
+ }
+ }
+ return root;
+}
+
+export { setUnion };
diff --git a/node_modules/@11ty/eleventy/src/Util/SpawnAsync.js b/node_modules/@11ty/eleventy/src/Util/SpawnAsync.js
new file mode 100644
index 0000000..5e6a20f
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Util/SpawnAsync.js
@@ -0,0 +1,29 @@
+import { spawn } from "node:child_process";
+import { withResolvers } from "./PromiseUtil.js";
+
+export function spawnAsync(command, args, options) {
+ let { promise, resolve, reject } = withResolvers();
+
+ const cmd = spawn(command, args, options);
+ let res = [];
+ cmd.stdout.on("data", (data) => {
+ res.push(data.toString("utf8"));
+ });
+
+ let err = [];
+ cmd.stderr.on("data", (data) => {
+ err.push(data.toString("utf8"));
+ });
+
+ cmd.on("close", (code) => {
+ if (err.length > 0) {
+ reject(err.join("\n"));
+ } else if (code === 1) {
+ reject("Internal error: process closed with error exit code.");
+ } else {
+ resolve(res.join("\n"));
+ }
+ });
+
+ return promise;
+}
diff --git a/node_modules/@11ty/eleventy/src/Util/TemplateDepGraph.js b/node_modules/@11ty/eleventy/src/Util/TemplateDepGraph.js
new file mode 100644
index 0000000..795453d
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Util/TemplateDepGraph.js
@@ -0,0 +1,160 @@
+import { DepGraph as DependencyGraph } from "dependency-graph";
+import debugUtil from "debug";
+
+const debug = debugUtil("Eleventy:TemplateDepGraph");
+
+const COLLECTION_PREFIX = "__collection:";
+
+export class TemplateDepGraph extends DependencyGraph {
+ static STAGES = ["[basic]", "[userconfig]", "[keys]", "all"];
+
+ #configCollectionNames = new Set();
+
+ constructor() {
+ // BREAKING TODO move this back to non-circular with errors
+ super({ circular: true });
+
+ let previous;
+ // establish stage relationships, all uses keys, keys uses userconfig, userconfig uses tags
+ for (let stageName of TemplateDepGraph.STAGES.filter(Boolean).reverse()) {
+ let stageKey = `${COLLECTION_PREFIX}${stageName}`;
+ if (previous) {
+ this.uses(previous, stageKey);
+ }
+ previous = stageKey;
+ }
+ }
+
+ uses(from, to) {
+ this.addDependency(from, to);
+ }
+
+ addTag(tagName, type) {
+ if (
+ tagName === "all" ||
+ (tagName.startsWith("[") && tagName.endsWith("]")) ||
+ this.#configCollectionNames.has(tagName)
+ ) {
+ return;
+ }
+ if (!type) {
+ throw new Error(
+ `Missing tag type for addTag. Expecting one of ${TemplateDepGraph.STAGES.map((entry) => entry.slice(1, -1)).join(" or ")}. Received: ${type}`,
+ );
+ }
+
+ debug("collection type %o uses tag %o", tagName, type);
+
+ this.uses(`${COLLECTION_PREFIX}[${type}]`, `${COLLECTION_PREFIX}${tagName}`);
+ }
+
+ addConfigCollectionName(collectionName) {
+ if (collectionName === "all") {
+ return;
+ }
+
+ this.#configCollectionNames.add(collectionName);
+ // Collection relationships to `[userconfig]` are added last, in unfilteredOrder()
+ }
+
+ cleanupCollectionNames(collectionNames = []) {
+ let s = new Set(collectionNames);
+ if (s.has("[userconfig]")) {
+ return collectionNames;
+ }
+
+ let hasAnyConfigCollections = collectionNames.find((name) => {
+ if (this.#configCollectionNames.has(name)) {
+ return true;
+ }
+ return false;
+ });
+
+ if (hasAnyConfigCollections) {
+ s.add("[userconfig]");
+ }
+
+ return Array.from(s);
+ }
+
+ addTemplate(filePath, consumes = [], publishesTo = []) {
+ // Move to the beginning if it doesn’t consume anything
+ if (consumes.length === 0) {
+ this.uses(`${COLLECTION_PREFIX}[basic]`, filePath);
+ }
+
+ consumes = this.cleanupCollectionNames(consumes);
+ publishesTo = this.cleanupCollectionNames(publishesTo);
+ // Can’t consume AND publish to `all` simultaneously
+ let consumesAll = consumes.includes("all");
+ if (consumesAll) {
+ publishesTo = publishesTo.filter((entry) => entry !== "all");
+ }
+
+ debug("%o consumes %o and publishes to %o", filePath, consumes, publishesTo);
+
+ for (let collectionName of publishesTo) {
+ if (!consumesAll) {
+ let tagType = "basic";
+
+ let consumesUserConfigCollection = consumes.includes("[userconfig]");
+ if (consumesUserConfigCollection) {
+ // must finish before [keys]
+ tagType = "keys";
+ }
+
+ this.addTag(collectionName, tagType);
+ }
+
+ this.uses(`${COLLECTION_PREFIX}${collectionName}`, filePath);
+ }
+
+ for (let collectionName of consumes) {
+ this.uses(filePath, `${COLLECTION_PREFIX}${collectionName}`);
+
+ let stageIndex = TemplateDepGraph.STAGES.indexOf(collectionName);
+ let nextStage = stageIndex > 0 ? TemplateDepGraph.STAGES[stageIndex + 1] : undefined;
+ if (nextStage) {
+ this.uses(`${COLLECTION_PREFIX}${nextStage}`, filePath);
+ }
+ }
+ }
+
+ addDependency(from, to) {
+ if (!this.hasNode(from)) {
+ this.addNode(from);
+ }
+ if (!this.hasNode(to)) {
+ this.addNode(to);
+ }
+ super.addDependency(from, to);
+ }
+
+ unfilteredOrder() {
+ // these need to be added last, after the template map has been added (see addConfigCollectionName)
+ for (let collectionName of this.#configCollectionNames) {
+ this.uses(`${COLLECTION_PREFIX}[keys]`, `${COLLECTION_PREFIX}${collectionName}`);
+ }
+
+ return super.overallOrder();
+ }
+
+ overallOrder() {
+ let unfiltered = this.unfilteredOrder();
+
+ let filtered = unfiltered.filter((entry) => {
+ if (entry === `${COLLECTION_PREFIX}[keys]`) {
+ return true;
+ }
+ return !entry.startsWith(`${COLLECTION_PREFIX}[`) && !entry.endsWith("]");
+ });
+
+ let allKey = `${COLLECTION_PREFIX}all`;
+ // Add another collections.all entry to the end (if not already the last one)
+ if (filtered[filtered.length - 1] !== allKey) {
+ filtered.push(allKey);
+ }
+
+ return filtered;
+ }
+}
diff --git a/node_modules/@11ty/eleventy/src/Util/TransformsUtil.js b/node_modules/@11ty/eleventy/src/Util/TransformsUtil.js
new file mode 100644
index 0000000..3ba8ab0
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Util/TransformsUtil.js
@@ -0,0 +1,70 @@
+import EleventyBaseError from "../Errors/EleventyBaseError.js";
+import { isPlainObject } from "@11ty/eleventy-utils";
+import debugUtil from "debug";
+
+const debug = debugUtil("Eleventy:Transforms");
+
+class EleventyTransformError extends EleventyBaseError {}
+
+class TransformsUtil {
+ static changeTransformsToArray(transformsObj) {
+ let transforms = [];
+ for (let name in transformsObj) {
+ transforms.push({
+ name: name,
+ callback: transformsObj[name],
+ });
+ }
+ return transforms;
+ }
+
+ static async runAll(content, pageData, transforms = {}, options = {}) {
+ let { baseHrefOverride, logger } = options;
+ let { inputPath, outputPath, url } = pageData;
+
+ if (!isPlainObject(transforms)) {
+ throw new Error("Object of transforms expected.");
+ }
+
+ let transformsArray = this.changeTransformsToArray(transforms);
+
+ for (let { callback, name } of transformsArray) {
+ debug("Running %o transform on %o: %o", name, inputPath, outputPath);
+
+ try {
+ let hadContentBefore = !!content;
+
+ content = await callback.call(
+ {
+ inputPath,
+ outputPath,
+ url,
+ page: pageData,
+ baseHref: baseHrefOverride,
+ },
+ content,
+ outputPath,
+ );
+
+ if (hadContentBefore && !content) {
+ if (!logger || !logger.warn) {
+ throw new Error("Internal error: missing `logger` instance.");
+ }
+
+ logger.warn(
+ `Warning: Transform \`${name}\` returned empty when writing ${outputPath} from ${inputPath}.`,
+ );
+ }
+ } catch (e) {
+ throw new EleventyTransformError(
+ `Transform \`${name}\` encountered an error when transforming ${inputPath}.`,
+ e,
+ );
+ }
+ }
+
+ return content;
+ }
+}
+
+export default TransformsUtil;
diff --git a/node_modules/@11ty/eleventy/src/Util/ValidUrl.js b/node_modules/@11ty/eleventy/src/Util/ValidUrl.js
new file mode 100644
index 0000000..9d0f59b
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Util/ValidUrl.js
@@ -0,0 +1,9 @@
+export default function isValidUrl(url) {
+ try {
+ new URL(url);
+ return true;
+ } catch (e) {
+ // invalid url OR local path
+ return false;
+ }
+}
diff --git a/node_modules/@11ty/eleventy/src/defaultConfig.js b/node_modules/@11ty/eleventy/src/defaultConfig.js
new file mode 100644
index 0000000..fb1ab5d
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/defaultConfig.js
@@ -0,0 +1,178 @@
+import bundlePlugin from "@11ty/eleventy-plugin-bundle";
+
+import urlFilter from "./Filters/Url.js";
+import slugFilter from "./Filters/Slug.js";
+import slugifyFilter from "./Filters/Slugify.js";
+import getLocaleCollectionItem from "./Filters/GetLocaleCollectionItem.js";
+import getCollectionItemIndex from "./Filters/GetCollectionItemIndex.js";
+import { FilterPlugin as InputPathToUrlFilterPlugin } from "./Plugins/InputPathToUrl.js";
+import { HtmlTransformer } from "./Util/HtmlTransformer.js";
+import TransformsUtil from "./Util/TransformsUtil.js";
+import MemoizeUtil from "./Util/MemoizeFunction.js";
+import { HtmlRelativeCopyPlugin } from "./Plugins/HtmlRelativeCopyPlugin.js";
+
+/**
+ * @module 11ty/eleventy/defaultConfig
+ */
+
+/**
+ * @callback addFilter - Register a global filter.
+ * @param {string} name - Register a template filter by this name.
+ * @param {function} callback - The filter logic.
+ */
+
+/**
+ * @typedef {object} config
+ * @property {addFilter} addFilter - Register a new global filter.
+ */
+
+/**
+ * @typedef {object} defaultConfig
+ * @property {Array<string>} templateFormats - An array of accepted template formats.
+ * @property {string} [pathPrefix='/'] - The directory under which all output files should be written to.
+ * @property {string} [markdownTemplateEngine='liquid'] - Template engine to process markdown files with.
+ * @property {string} [htmlTemplateEngine='liquid'] - Template engine to process html files with.
+ * @property {boolean} [dataTemplateEngine=false] - Changed in v1.0
+ * @property {string} [jsDataFileSuffix='.11tydata'] - File suffix for jsData files.
+ * @property {object} keys
+ * @property {string} [keys.package='pkg'] - Global data property for package.json data
+ * @property {string} [keys.layout='layout']
+ * @property {string} [keys.permalink='permalink']
+ * @property {string} [keys.permalinkRoot='permalinkBypassOutputDir']
+ * @property {string} [keys.engineOverride='templateEngineOverride']
+ * @property {string} [keys.computed='eleventyComputed']
+ * @property {object} dir
+ * @property {string} [dir.input='.']
+ * @property {string} [dir.includes='_includes']
+ * @property {string} [dir.data='_data']
+ * @property {string} [dir.output='_site']
+ * @deprecated handlebarsHelpers
+ * @deprecated nunjucksFilters
+ */
+
+/**
+ * Default configuration object factory.
+ *
+ * @param {config} config - Eleventy configuration object.
+ * @returns {defaultConfig}
+ */
+export default function (config) {
+ let templateConfig = this;
+
+ // Used for the HTML <base>, InputPathToUrl, Image transform plugins
+ let ut = new HtmlTransformer();
+ ut.setUserConfig(config);
+
+ // This needs to be assigned before bundlePlugin is added below.
+ config.htmlTransformer = ut;
+
+ config.exists = (filePath) => {
+ return this.existsCache.exists(filePath);
+ };
+
+ // Remember: the transform added here runs before the `htmlTransformer` transform
+ config.addPlugin(bundlePlugin, {
+ bundles: false, // no default bundles included—must be opt-in.
+ immediate: true,
+ });
+
+ // Filter: Maps an input path to output URL
+ config.addPlugin(InputPathToUrlFilterPlugin, {
+ immediate: true,
+ });
+
+ let memoizeBench = config.benchmarkManager.get("Configuration");
+ config.addFilter("slug", MemoizeUtil(slugFilter, { name: "slug", bench: memoizeBench }));
+ config.addFilter("slugify", MemoizeUtil(slugifyFilter, { name: "slugify", bench: memoizeBench }));
+
+ // Deprecated, use HtmlBasePlugin instead.
+ // Adds a pathPrefix manually to a URL string
+ config.addFilter("url", function addPathPrefixFilter(url, pathPrefixOverride) {
+ let pathPrefix;
+ if (pathPrefixOverride && typeof pathPrefixOverride === "string") {
+ pathPrefix = pathPrefixOverride;
+ } else {
+ pathPrefix = templateConfig.getPathPrefix();
+ }
+
+ return urlFilter.call(this, url, pathPrefix);
+ });
+
+ config.addFilter("log", (input, ...messages) => {
+ console.log(input, ...messages);
+ return input;
+ });
+
+ config.addFilter("getCollectionItemIndex", function (collection, pageOverride) {
+ return getCollectionItemIndex.call(this, collection, pageOverride);
+ });
+ config.addFilter("getCollectionItem", function (collection, pageOverride, langCode) {
+ return getLocaleCollectionItem.call(this, config, collection, pageOverride, langCode, 0);
+ });
+ config.addFilter("getPreviousCollectionItem", function (collection, pageOverride, langCode) {
+ return getLocaleCollectionItem.call(this, config, collection, pageOverride, langCode, -1);
+ });
+ config.addFilter("getNextCollectionItem", function (collection, pageOverride, langCode) {
+ return getLocaleCollectionItem.call(this, config, collection, pageOverride, langCode, 1);
+ });
+
+ // Process arbitrary content with transforms
+ config.addFilter(
+ "renderTransforms",
+ async function transformsFilter(content, pageEntryOverride, baseHrefOverride) {
+ return TransformsUtil.runAll(content, pageEntryOverride || this.page, config.transforms, {
+ baseHrefOverride,
+ logger: config.logger,
+ });
+ },
+ );
+
+ // Run the `htmlTransformer` transform
+ config.addTransform("@11ty/eleventy/html-transformer", async function (content) {
+ // Runs **AFTER** the bundle plugin transform (except: delayed bundles)
+ return ut.transformContent(this.outputPath, content, this);
+ });
+
+ // Requires user configuration, so must run as second-stage
+ config.addPlugin(HtmlRelativeCopyPlugin);
+
+ return {
+ templateFormats: ["liquid", "md", "njk", "html", "11ty.js"],
+ // if your site deploys to a subdirectory, change this
+ pathPrefix: "/",
+ markdownTemplateEngine: "liquid",
+ htmlTemplateEngine: "liquid",
+
+ // Renamed from `jsDataFileSuffix` in 2.0 (and swapped to an Array)
+ // If you remove "" we won’t look for dir/dir.json or file.json
+ dataFileSuffixes: [".11tydata", ""],
+
+ // "index" will look for `directory/index.*` directory data files instead of `directory/directory.*`
+ dataFileDirBaseNameOverride: false,
+
+ keys: {
+ // TODO breaking: use `false` by default
+ package: "pkg", // supports `false`
+ layout: "layout",
+ permalink: "permalink",
+ permalinkRoot: "permalinkBypassOutputDir",
+ engineOverride: "templateEngineOverride",
+ computed: "eleventyComputed",
+ dataSchema: "eleventyDataSchema",
+ },
+
+ // Deprecated, define using `export const directories = {}` instead.
+ // Reference values using `eleventyConfig.directories` instead.
+ dir: {
+ // These values here aren’t used internally either (except by a few tests), instead we’re using `ProjectDirectories.defaults`.
+ // These are kept in place for backwards compat with `eleventyConfig.dir` references in project config code and plugins.
+ input: ".",
+ includes: "_includes",
+ data: "_data",
+ output: "_site",
+ },
+
+ // deprecated, use config.addNunjucksFilter
+ nunjucksFilters: {},
+ };
+}