summaryrefslogtreecommitdiff
path: root/node_modules/@11ty/eleventy/src/EleventyFiles.js
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/EleventyFiles.js
parent53d6ae2b5568437afa5e4995580a3fb679b7b91b (diff)
Changed from static to 11ty!
Diffstat (limited to 'node_modules/@11ty/eleventy/src/EleventyFiles.js')
-rw-r--r--node_modules/@11ty/eleventy/src/EleventyFiles.js521
1 files changed, 521 insertions, 0 deletions
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;