464 lines
13 KiB
JavaScript
464 lines
13 KiB
JavaScript
|
const fs = require("fs");
|
|||
|
|
|||
|
const { TemplatePath } = require("@11ty/eleventy-utils");
|
|||
|
|
|||
|
const EleventyExtensionMap = require("./EleventyExtensionMap");
|
|||
|
const TemplateData = require("./TemplateData");
|
|||
|
const TemplateGlob = require("./TemplateGlob");
|
|||
|
const TemplatePassthroughManager = require("./TemplatePassthroughManager");
|
|||
|
const EleventyBaseError = require("./EleventyBaseError");
|
|||
|
const checkPassthroughCopyBehavior = require("./Util/PassthroughCopyBehaviorCheck");
|
|||
|
|
|||
|
class EleventyFilesError extends EleventyBaseError {}
|
|||
|
|
|||
|
const debug = require("debug")("Eleventy:EleventyFiles");
|
|||
|
// const debugDev = require("debug")("Dev:Eleventy:EleventyFiles");
|
|||
|
|
|||
|
class EleventyFiles {
|
|||
|
constructor(input, outputDir, formats, eleventyConfig) {
|
|||
|
if (!eleventyConfig) {
|
|||
|
throw new EleventyFilesError("Missing `eleventyConfig`` argument.");
|
|||
|
}
|
|||
|
|
|||
|
this.eleventyConfig = eleventyConfig;
|
|||
|
this.config = eleventyConfig.getConfig();
|
|||
|
this.aggregateBench = this.config.benchmarkManager.get("Aggregate");
|
|||
|
|
|||
|
this.input = input;
|
|||
|
this.inputDir = TemplatePath.getDir(this.input);
|
|||
|
this.outputDir = outputDir;
|
|||
|
|
|||
|
this.initConfig();
|
|||
|
|
|||
|
this.formats = formats;
|
|||
|
this.eleventyIgnoreContent = false;
|
|||
|
|
|||
|
// init has not yet been called()
|
|||
|
this.alreadyInit = false;
|
|||
|
}
|
|||
|
|
|||
|
setFileSystemSearch(fileSystemSearch) {
|
|||
|
this.fileSystemSearch = fileSystemSearch;
|
|||
|
}
|
|||
|
|
|||
|
/* Overrides this.input and this.inputDir,
|
|||
|
* Useful when input is a file and inputDir is not its direct parent */
|
|||
|
setInput(inputDir, input) {
|
|||
|
this.inputDir = inputDir;
|
|||
|
this.input = input;
|
|||
|
|
|||
|
this.initConfig();
|
|||
|
|
|||
|
if (this.alreadyInit) {
|
|||
|
this.init();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
initConfig() {
|
|||
|
this.includesDir = TemplatePath.join(this.inputDir, this.config.dir.includes);
|
|||
|
|
|||
|
if ("layouts" in this.config.dir) {
|
|||
|
this.layoutsDir = TemplatePath.join(this.inputDir, this.config.dir.layouts);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
init() {
|
|||
|
this.alreadyInit = true;
|
|||
|
|
|||
|
// Input is a directory
|
|||
|
if (this.input === this.inputDir) {
|
|||
|
this.templateGlobs = this.extensionMap.getGlobs(this.inputDir);
|
|||
|
} else {
|
|||
|
// input is not a directory
|
|||
|
this.templateGlobs = TemplateGlob.map([this.input]);
|
|||
|
}
|
|||
|
|
|||
|
this.initPassthroughManager();
|
|||
|
this.setupGlobs();
|
|||
|
}
|
|||
|
|
|||
|
get validTemplateGlobs() {
|
|||
|
if (!this._validTemplateGlobs) {
|
|||
|
let globs;
|
|||
|
// Input is a directory
|
|||
|
if (this.input === this.inputDir) {
|
|||
|
globs = this.extensionMap.getValidGlobs(this.inputDir);
|
|||
|
} else {
|
|||
|
globs = this.templateGlobs;
|
|||
|
}
|
|||
|
this._validTemplateGlobs = globs;
|
|||
|
}
|
|||
|
return this._validTemplateGlobs;
|
|||
|
}
|
|||
|
|
|||
|
get passthroughGlobs() {
|
|||
|
let paths = new Set();
|
|||
|
// stuff added in addPassthroughCopy()
|
|||
|
for (let path of this.passthroughManager.getConfigPathGlobs()) {
|
|||
|
paths.add(path);
|
|||
|
}
|
|||
|
// non-template language extensions
|
|||
|
for (let path of this.extensionMap.getPassthroughCopyGlobs(this.inputDir)) {
|
|||
|
paths.add(path);
|
|||
|
}
|
|||
|
return Array.from(paths);
|
|||
|
}
|
|||
|
|
|||
|
restart() {
|
|||
|
this.passthroughManager.reset();
|
|||
|
this.setupGlobs();
|
|||
|
this._glob = null;
|
|||
|
}
|
|||
|
|
|||
|
/* For testing */
|
|||
|
_setConfig(config) {
|
|||
|
if (!config.ignores) {
|
|||
|
config.ignores = new Set();
|
|||
|
config.ignores.add("**/node_modules/**");
|
|||
|
}
|
|||
|
this.config = config;
|
|||
|
this.initConfig();
|
|||
|
}
|
|||
|
|
|||
|
/* Set command root for local project paths */
|
|||
|
// This is only used by tests
|
|||
|
_setLocalPathRoot(dir) {
|
|||
|
this.localPathRoot = dir;
|
|||
|
}
|
|||
|
|
|||
|
set extensionMap(extensionMap) {
|
|||
|
this._extensionMap = extensionMap;
|
|||
|
}
|
|||
|
|
|||
|
get extensionMap() {
|
|||
|
// for tests
|
|||
|
if (!this._extensionMap) {
|
|||
|
this._extensionMap = new EleventyExtensionMap(this.formats, this.eleventyConfig);
|
|||
|
this._extensionMap.config = this.config;
|
|||
|
}
|
|||
|
return this._extensionMap;
|
|||
|
}
|
|||
|
|
|||
|
setRunMode(runMode) {
|
|||
|
this.runMode = runMode;
|
|||
|
}
|
|||
|
|
|||
|
initPassthroughManager() {
|
|||
|
let mgr = new TemplatePassthroughManager(this.eleventyConfig);
|
|||
|
mgr.setInputDir(this.inputDir);
|
|||
|
mgr.setOutputDir(this.outputDir);
|
|||
|
mgr.setRunMode(this.runMode);
|
|||
|
mgr.extensionMap = this.extensionMap;
|
|||
|
mgr.setFileSystemSearch(this.fileSystemSearch);
|
|||
|
this.passthroughManager = mgr;
|
|||
|
}
|
|||
|
|
|||
|
getPassthroughManager() {
|
|||
|
return this.passthroughManager;
|
|||
|
}
|
|||
|
|
|||
|
setPassthroughManager(mgr) {
|
|||
|
mgr.extensionMap = this.extensionMap;
|
|||
|
this.passthroughManager = mgr;
|
|||
|
}
|
|||
|
|
|||
|
set templateData(templateData) {
|
|||
|
this._templateData = templateData;
|
|||
|
}
|
|||
|
|
|||
|
get templateData() {
|
|||
|
if (!this._templateData) {
|
|||
|
this._templateData = new TemplateData(this.inputDir, this.eleventyConfig);
|
|||
|
}
|
|||
|
return this._templateData;
|
|||
|
}
|
|||
|
|
|||
|
getDataDir() {
|
|||
|
let data = this.templateData;
|
|||
|
|
|||
|
return data.getDataDir();
|
|||
|
}
|
|||
|
|
|||
|
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 && this.config.events) {
|
|||
|
this.config.events.emit("eleventy.ignores", this.uniqueIgnores);
|
|||
|
}
|
|||
|
|
|||
|
this.normalizedTemplateGlobs = this.templateGlobs;
|
|||
|
}
|
|||
|
|
|||
|
getIgnoreGlobs() {
|
|||
|
let uniqueIgnores = new Set();
|
|||
|
for (let ignore of this.fileIgnores) {
|
|||
|
uniqueIgnores.add(ignore);
|
|||
|
}
|
|||
|
for (let ignore of this.extraIgnores) {
|
|||
|
uniqueIgnores.add(ignore);
|
|||
|
}
|
|||
|
// Placing the config ignores last here is important to the tests
|
|||
|
for (let ignore of this.config.ignores) {
|
|||
|
uniqueIgnores.add(TemplateGlob.normalizePath(this.localPathRoot || ".", ignore));
|
|||
|
}
|
|||
|
return Array.from(uniqueIgnores);
|
|||
|
}
|
|||
|
|
|||
|
static getFileIgnores(ignoreFiles) {
|
|||
|
if (!Array.isArray(ignoreFiles)) {
|
|||
|
ignoreFiles = [ignoreFiles];
|
|||
|
}
|
|||
|
|
|||
|
let ignores = [];
|
|||
|
for (let ignorePath of ignoreFiles) {
|
|||
|
ignorePath = TemplatePath.normalize(ignorePath);
|
|||
|
|
|||
|
let dir = TemplatePath.getDirFromFilePath(ignorePath);
|
|||
|
|
|||
|
if (fs.existsSync(ignorePath) && fs.statSync(ignorePath).size > 0) {
|
|||
|
let ignoreContent = fs.readFileSync(ignorePath, "utf8");
|
|||
|
|
|||
|
ignores = ignores.concat(EleventyFiles.normalizeIgnoreContent(dir, ignoreContent));
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
ignores.forEach((path) => debug(`${ignoreFiles} ignoring: ${path}`));
|
|||
|
|
|||
|
return ignores;
|
|||
|
}
|
|||
|
|
|||
|
static normalizeIgnoreContent(dir, ignoreContent) {
|
|||
|
let ignores = [];
|
|||
|
|
|||
|
if (ignoreContent) {
|
|||
|
ignores = ignoreContent
|
|||
|
.split("\n")
|
|||
|
.map((line) => {
|
|||
|
return line.trim();
|
|||
|
})
|
|||
|
.filter((line) => {
|
|||
|
if (line.charAt(0) === "!") {
|
|||
|
debug(
|
|||
|
">>> When processing .gitignore/.eleventyignore, Eleventy does not currently support negative patterns but encountered one:"
|
|||
|
);
|
|||
|
debug(">>>", line);
|
|||
|
debug("Follow along at https://github.com/11ty/eleventy/issues/693 to track support.");
|
|||
|
}
|
|||
|
|
|||
|
// empty lines or comments get filtered out
|
|||
|
return line.length > 0 && line.charAt(0) !== "#" && line.charAt(0) !== "!";
|
|||
|
})
|
|||
|
.map((line) => {
|
|||
|
let path = TemplateGlob.normalizePath(dir, "/", line);
|
|||
|
path = TemplatePath.addLeadingDotSlash(TemplatePath.relativePath(path));
|
|||
|
|
|||
|
try {
|
|||
|
// Note these folders must exist to get /** suffix
|
|||
|
let stat = fs.statSync(path);
|
|||
|
if (stat.isDirectory()) {
|
|||
|
return path + "/**";
|
|||
|
}
|
|||
|
return path;
|
|||
|
} catch (e) {
|
|||
|
return path;
|
|||
|
}
|
|||
|
});
|
|||
|
}
|
|||
|
|
|||
|
return ignores;
|
|||
|
}
|
|||
|
|
|||
|
setEleventyIgnoreContent(content) {
|
|||
|
this.eleventyIgnoreContent = content;
|
|||
|
}
|
|||
|
|
|||
|
getIgnores() {
|
|||
|
let files = new Set();
|
|||
|
|
|||
|
for (let ignore of EleventyFiles.getFileIgnores(this.getIgnoreFiles())) {
|
|||
|
files.add(ignore);
|
|||
|
}
|
|||
|
|
|||
|
// testing API
|
|||
|
if (this.eleventyIgnoreContent !== false) {
|
|||
|
files.add(this.eleventyIgnoreContent);
|
|||
|
}
|
|||
|
|
|||
|
// ignore output dir unless that would exclude all input
|
|||
|
if (!TemplatePath.startsWithSubPath(this.inputDir, this.outputDir)) {
|
|||
|
files.add(TemplateGlob.map(this.outputDir + "/**"));
|
|||
|
}
|
|||
|
|
|||
|
return Array.from(files);
|
|||
|
}
|
|||
|
|
|||
|
getIgnoreFiles() {
|
|||
|
let ignoreFiles = [];
|
|||
|
let rootDirectory = this.localPathRoot || ".";
|
|||
|
|
|||
|
if (this.config.useGitIgnore) {
|
|||
|
ignoreFiles.push(TemplatePath.join(rootDirectory, ".gitignore"));
|
|||
|
}
|
|||
|
|
|||
|
if (this.eleventyIgnoreContent === false) {
|
|||
|
let absoluteInputDir = TemplatePath.absolutePath(this.inputDir);
|
|||
|
ignoreFiles.push(TemplatePath.join(rootDirectory, ".eleventyignore"));
|
|||
|
if (rootDirectory !== absoluteInputDir) {
|
|||
|
ignoreFiles.push(TemplatePath.join(this.inputDir, ".eleventyignore"));
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return ignoreFiles;
|
|||
|
}
|
|||
|
|
|||
|
getIncludesDir() {
|
|||
|
return this.includesDir;
|
|||
|
}
|
|||
|
|
|||
|
getLayoutsDir() {
|
|||
|
return this.layoutsDir;
|
|||
|
}
|
|||
|
|
|||
|
getFileGlobs() {
|
|||
|
return this.normalizedTemplateGlobs;
|
|||
|
}
|
|||
|
|
|||
|
getRawFiles() {
|
|||
|
return this.templateGlobs;
|
|||
|
}
|
|||
|
|
|||
|
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");
|
|||
|
}
|
|||
|
|
|||
|
// Filter out the passthrough copy paths.
|
|||
|
return this.pathCache.filter((path) => {
|
|||
|
return (
|
|||
|
this.extensionMap.isFullTemplateFilePath(path) &&
|
|||
|
this.extensionMap.shouldSpiderJavaScriptDependencies(path)
|
|||
|
);
|
|||
|
});
|
|||
|
}
|
|||
|
|
|||
|
_globSearch() {
|
|||
|
let globs = this.getFileGlobs();
|
|||
|
|
|||
|
// returns a promise
|
|||
|
debug("Searching for: %o", globs);
|
|||
|
return this.fileSystemSearch.search("templates", globs, {
|
|||
|
ignore: this.uniqueIgnores,
|
|||
|
});
|
|||
|
}
|
|||
|
|
|||
|
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.
|
|||
|
|
|||
|
this.pathCache = paths;
|
|||
|
return paths;
|
|||
|
}
|
|||
|
|
|||
|
// Assumption here that filePath is not a passthrough copy file
|
|||
|
isFullTemplateFile(paths, filePath) {
|
|||
|
if (!filePath) {
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
for (let path of paths) {
|
|||
|
if (path === filePath) {
|
|||
|
return true;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
/* For `eleventy --watch` */
|
|||
|
getGlobWatcherFiles() {
|
|||
|
// TODO improvement: tie the includes and data to specific file extensions (currently using `**`)
|
|||
|
let directoryGlobs = this._getIncludesAndDataDirs();
|
|||
|
|
|||
|
if (checkPassthroughCopyBehavior(this.config, this.runMode)) {
|
|||
|
return this.validTemplateGlobs.concat(directoryGlobs);
|
|||
|
}
|
|||
|
|
|||
|
// Revert to old passthroughcopy copy files behavior
|
|||
|
return this.validTemplateGlobs.concat(this.passthroughGlobs).concat(directoryGlobs);
|
|||
|
}
|
|||
|
|
|||
|
/* For `eleventy --watch` */
|
|||
|
getGlobWatcherFilesForPassthroughCopy() {
|
|||
|
return this.passthroughGlobs;
|
|||
|
}
|
|||
|
|
|||
|
/* For `eleventy --watch` */
|
|||
|
async getGlobWatcherTemplateDataFiles() {
|
|||
|
let templateData = this.templateData;
|
|||
|
return await templateData.getTemplateDataFileGlob();
|
|||
|
}
|
|||
|
|
|||
|
/* For `eleventy --watch` */
|
|||
|
// TODO this isn’t great but reduces complexity avoiding using TemplateData:getLocalDataPaths for each template in the cache
|
|||
|
async getWatcherTemplateJavaScriptDataFiles() {
|
|||
|
let globs = await this.templateData.getTemplateJavaScriptDataFileGlob();
|
|||
|
let bench = this.aggregateBench.get("Searching the file system (watching)");
|
|||
|
bench.before();
|
|||
|
let results = TemplatePath.addLeadingDotSlashArray(
|
|||
|
await this.fileSystemSearch.search("js-dependencies", globs, {
|
|||
|
ignore: ["**/node_modules/**"],
|
|||
|
})
|
|||
|
);
|
|||
|
bench.after();
|
|||
|
return results;
|
|||
|
}
|
|||
|
|
|||
|
/* Ignored by `eleventy --watch` */
|
|||
|
getGlobWatcherIgnores() {
|
|||
|
// convert to format without ! since they are passed in as a separate argument to glob watcher
|
|||
|
let entries = new Set(
|
|||
|
this.fileIgnores.map((ignore) => TemplatePath.stripLeadingDotSlash(ignore))
|
|||
|
);
|
|||
|
|
|||
|
for (let ignore of this.config.watchIgnores) {
|
|||
|
entries.add(TemplateGlob.normalizePath(this.localPathRoot || ".", ignore));
|
|||
|
}
|
|||
|
|
|||
|
// de-duplicated
|
|||
|
return Array.from(entries);
|
|||
|
}
|
|||
|
|
|||
|
_getIncludesAndDataDirs() {
|
|||
|
let files = [];
|
|||
|
// we want this to fail on "" because we don’t want to ignore the
|
|||
|
// entire input directory when using ""
|
|||
|
if (this.config.dir.includes) {
|
|||
|
files = files.concat(TemplateGlob.map(this.includesDir + "/**"));
|
|||
|
}
|
|||
|
|
|||
|
// we want this to fail on "" because we don’t want to ignore the
|
|||
|
// entire input directory when using ""
|
|||
|
if (this.config.dir.layouts) {
|
|||
|
files = files.concat(TemplateGlob.map(this.layoutsDir + "/**"));
|
|||
|
}
|
|||
|
|
|||
|
if (this.config.dir.data && this.config.dir.data !== ".") {
|
|||
|
let dataDir = this.getDataDir();
|
|||
|
files = files.concat(TemplateGlob.map(dataDir + "/**"));
|
|||
|
}
|
|||
|
|
|||
|
return files;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
module.exports = EleventyFiles;
|