summaryrefslogtreecommitdiff
path: root/node_modules/@11ty/eleventy/src/Data
diff options
context:
space:
mode:
authorShipwreckt <me@shipwreckt.co.uk>2025-10-31 20:02:14 +0000
committerShipwreckt <me@shipwreckt.co.uk>2025-10-31 20:02:14 +0000
commit7a52ddeba2a68388b544f529d2d92104420f77b0 (patch)
tree15ddd47457a2cb4a96060747437d36474e4f6b4e /node_modules/@11ty/eleventy/src/Data
parent53d6ae2b5568437afa5e4995580a3fb679b7b91b (diff)
Changed from static to 11ty!
Diffstat (limited to 'node_modules/@11ty/eleventy/src/Data')
-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
6 files changed, 1137 insertions, 0 deletions
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;