diff --git a/src/Data/TemplateData.js b/src/Data/TemplateData.js index fbb0d9cc6..dbca18205 100644 --- a/src/Data/TemplateData.js +++ b/src/Data/TemplateData.js @@ -595,6 +595,7 @@ class TemplateData { 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; diff --git a/src/Eleventy.js b/src/Eleventy.js index e51d56332..44f8005b5 100644 --- a/src/Eleventy.js +++ b/src/Eleventy.js @@ -2,6 +2,7 @@ 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"; @@ -30,7 +31,11 @@ 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, getWorkingProjectPackageJson } from "./Util/ImportJsonSync.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"; @@ -41,6 +46,7 @@ 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"); @@ -56,6 +62,8 @@ class Eleventy { * @type {object|undefined} */ #projectPackageJson; + /** @type {string} */ + #projectPackageJsonPath; /** @type {ProjectTemplateFormats|undefined} */ #templateFormats; /** @type {ConsoleLogger|undefined} */ @@ -589,14 +597,12 @@ Verbose Output: ${this.verboseMode}`; let configPath = this.eleventyConfig.getLocalProjectConfigFile(); if (configPath) { - let absolutePathToConfig = TemplatePath.absolutePath(configPath); - values.config = absolutePathToConfig; - - // TODO(zachleat): if config is not in root (e.g. using --config=) - let root = TemplatePath.getDirFromFilePath(absolutePathToConfig); - values.root = root; + 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 @@ -1056,7 +1062,11 @@ Arguments: this.watchManager = new EleventyWatch(); this.watchManager.incremental = this.isIncremental; - this.watchTargets.add(["./package.json"]); + if (this.projectPackageJsonPath) { + this.watchTargets.add([ + path.relative(TemplatePath.getWorkingDir(), this.projectPackageJsonPath), + ]); + } this.watchTargets.add(this.eleventyFiles.getGlobWatcherFiles()); this.watchTargets.add(this.eleventyFiles.getIgnoreFiles()); @@ -1075,9 +1085,17 @@ Arguments: } // fetch from project’s package.json + get projectPackageJsonPath() { + if (this.#projectPackageJsonPath === undefined) { + this.#projectPackageJsonPath = getWorkingProjectPackageJsonPath() || false; + } + return this.#projectPackageJsonPath; + } + get projectPackageJson() { if (!this.#projectPackageJson) { - this.#projectPackageJson = getWorkingProjectPackageJson(); + let p = this.projectPackageJsonPath; + this.#projectPackageJson = p ? importJsonSync(p) : {}; } return this.#projectPackageJson; } @@ -1106,6 +1124,7 @@ Arguments: return; } + // TODO use DirContains let dataDir = TemplatePath.stripLeadingDotSlash(this.templateData.getDataDir()); function filterOutGlobalDataFiles(path) { return !dataDir || !TemplatePath.stripLeadingDotSlash(path).startsWith(dataDir); @@ -1201,7 +1220,25 @@ Arguments: let rawFiles = await this.getWatchedFiles(); debug("Watching for changes to: %o", rawFiles); - let watcher = chokidar.watch(rawFiles, this.getChokidarConfig()); + 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(); diff --git a/src/EleventyExtensionMap.js b/src/EleventyExtensionMap.js index f2a61534f..8f4264042 100644 --- a/src/EleventyExtensionMap.js +++ b/src/EleventyExtensionMap.js @@ -142,8 +142,7 @@ class EleventyExtensionMap { return this._getGlobs(this.unfilteredFormatKeys, inputDir); } - _getGlobs(formatKeys, inputDir) { - let dir = TemplatePath.convertToRecursiveGlobSync(inputDir); + _getGlobs(formatKeys, inputDir = "") { let extensions = new Set(); for (let key of formatKeys) { @@ -156,6 +155,7 @@ class EleventyExtensionMap { } } + let dir = TemplatePath.convertToRecursiveGlobSync(inputDir); if (extensions.size === 1) { return [`${dir}/*.${Array.from(extensions)[0]}`]; } else if (extensions.size > 1) { diff --git a/src/EleventyFiles.js b/src/EleventyFiles.js index 73065d706..7ec6379e5 100644 --- a/src/EleventyFiles.js +++ b/src/EleventyFiles.js @@ -3,6 +3,7 @@ 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"; @@ -11,6 +12,7 @@ const debug = debugUtil("Eleventy:EleventyFiles"); class EleventyFiles { #extensionMap; + #watcherGlobs; constructor(formats, templateConfig) { if (!templateConfig) { @@ -69,8 +71,8 @@ class EleventyFiles { this.setupGlobs(); } - get validTemplateGlobs() { - if (!this._validTemplateGlobs) { + #getWatcherGlobs() { + if (!this.#watcherGlobs) { let globs; // Input is a file if (this.inputFile) { @@ -79,9 +81,10 @@ class EleventyFiles { // input is a directory globs = this.extensionMap.getValidGlobs(this.inputDir); } - this._validTemplateGlobs = globs; + this.#watcherGlobs = globs; } - return this._validTemplateGlobs; + + return this.#watcherGlobs; } get passthroughGlobs() { @@ -154,7 +157,7 @@ class EleventyFiles { setupGlobs() { this.fileIgnores = this.getIgnores(); - this.extraIgnores = this._getIncludesAndDataDirs(); + this.extraIgnores = this.getIncludesAndDataDirs(); this.uniqueIgnores = this.getIgnoreGlobs(); // Conditional added for tests that don’t have a config @@ -165,6 +168,13 @@ class EleventyFiles { 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) { @@ -173,10 +183,12 @@ class EleventyFiles { 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)); + uniqueIgnores.add(this.normalizeIgnoreEntry(ignore)); } + return Array.from(uniqueIgnores); } @@ -261,11 +273,12 @@ class EleventyFiles { files.add(this.eleventyIgnoreContent); } - // ignore output dir (unless this excludes all input) - // input: . and output: . (skip) - // input: ./content and output . (skip) - // input: . and output: ./_site (add) - if (!this.inputDir.startsWith(this.outputDir)) { + // 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 + "/**")); } @@ -284,6 +297,7 @@ class EleventyFiles { 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")); } @@ -427,14 +441,16 @@ class EleventyFiles { /* For `eleventy --watch` */ getGlobWatcherFiles() { // TODO improvement: tie the includes and data to specific file extensions (currently using `**`) - let directoryGlobs = this._getIncludesAndDataDirs(); + let directoryGlobs = this.getIncludesAndDataDirs(); + + let globs = this.#getWatcherGlobs(); if (checkPassthroughCopyBehavior(this.config, this.runMode)) { - return this.validTemplateGlobs.concat(directoryGlobs); + return globs.concat(directoryGlobs); } // Revert to old passthroughcopy copy files behavior - return this.validTemplateGlobs.concat(this.passthroughGlobs).concat(directoryGlobs); + return globs.concat(this.passthroughGlobs).concat(directoryGlobs); } /* For `eleventy --watch` */ @@ -456,7 +472,12 @@ class EleventyFiles { bench.before(); let results = TemplatePath.addLeadingDotSlashArray( await this.fileSystemSearch.search("js-dependencies", globs, { - ignore: ["**/node_modules/**"], + ignore: [ + "**/node_modules/**", + ".git/**", + // TODO outputDir + // this.outputDir, + ], }), ); bench.after(); @@ -471,14 +492,14 @@ class EleventyFiles { ); for (let ignore of this.config.watchIgnores) { - entries.add(TemplateGlob.normalizePath(this.localPathRoot || ".", ignore)); + entries.add(this.normalizeIgnoreEntry(ignore)); } // de-duplicated return Array.from(entries); } - _getIncludesAndDataDirs() { + getIncludesAndDataDirs() { let rawPaths = new Set(); rawPaths.add(this.includesDir); if (this.layoutsDir) { diff --git a/src/FileSystemSearch.js b/src/FileSystemSearch.js index 0dfa129d0..972e80ba3 100644 --- a/src/FileSystemSearch.js +++ b/src/FileSystemSearch.js @@ -2,6 +2,7 @@ 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"); @@ -32,8 +33,17 @@ class FileSystemSearch { // 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) => TemplatePath.stripLeadingDotSlash(entry)); + options.ignore = options.ignore.map((entry) => { + entry = TemplatePath.stripLeadingDotSlash(entry); + + return FileSystemRemap.remapInput(entry, cwd); + }); debug("Glob search (%o) ignoring: %o", key, options.ignore); } @@ -52,6 +62,14 @@ class FileSystemSearch { 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( @@ -63,8 +81,12 @@ class FileSystemSearch { ), ).then((results) => { this.outputs[cacheKey] = new Set( - results.map((entry) => TemplatePath.standardizeFilePath(entry)), + results.map((entry) => { + let remapped = FileSystemRemap.remapOutput(entry, options.cwd); + return TemplatePath.standardizeFilePath(remapped); + }), ); + return Array.from(this.outputs[cacheKey]); }); } @@ -97,6 +119,11 @@ class FileSystemSearch { 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/src/TemplateConfig.js b/src/TemplateConfig.js index 9bff7fac2..48ff23283 100644 --- a/src/TemplateConfig.js +++ b/src/TemplateConfig.js @@ -150,16 +150,17 @@ class TemplateConfig { */ getLocalProjectConfigFile() { let configFiles = this.getLocalProjectConfigFiles(); - // Add the configFiles[0] in case of a test, where no file exists on the file system - let configFile = configFiles.find((path) => path && fs.existsSync(path)) || configFiles[0]; + + let configFile = configFiles.find((path) => path && fs.existsSync(path)); if (configFile) { return configFile; } } getLocalProjectConfigFiles() { - if (this.projectConfigPaths?.length > 0) { - return TemplatePath.addLeadingDotSlashArray(this.projectConfigPaths.filter((path) => path)); + let paths = this.projectConfigPaths; + if (paths?.length > 0) { + return TemplatePath.addLeadingDotSlashArray(paths.filter((path) => Boolean(path))); } return []; } diff --git a/src/TemplatePassthrough.js b/src/TemplatePassthrough.js index 32c85d407..b27b634e5 100644 --- a/src/TemplatePassthrough.js +++ b/src/TemplatePassthrough.js @@ -174,8 +174,14 @@ class TemplatePassthrough { 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), + await this.fileSystemSearch.search("passthrough", glob, { + ignore: [ + // *only* ignores output dir (not node_modules!) + this.outputDir, + ], + }), ); b.after(); return files; diff --git a/src/UserConfig.js b/src/UserConfig.js index 5a6fbcc71..215327fe5 100644 --- a/src/UserConfig.js +++ b/src/UserConfig.js @@ -170,7 +170,7 @@ class UserConfig { let defaultIgnores = new Set(); defaultIgnores.add("**/node_modules/**"); - defaultIgnores.add(".git/**"); + defaultIgnores.add(".git/**"); // TODO `**/.git/**` this.ignores = new Set(defaultIgnores); this.watchIgnores = new Set(defaultIgnores); diff --git a/src/Util/Compatibility.js b/src/Util/Compatibility.js index 18bdd701e..c90a9b331 100644 --- a/src/Util/Compatibility.js +++ b/src/Util/Compatibility.js @@ -8,10 +8,20 @@ const pkg = getEleventyPackageJson(); 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"); } @@ -22,9 +32,8 @@ class Compatibility { } // fetch from project’s package.json - let projectPackageJson = getWorkingProjectPackageJson(); - if (projectPackageJson["11ty"]?.compatibility) { - return projectPackageJson["11ty"]?.compatibility; + if (this.projectPackageJson?.["11ty"]?.compatibility) { + return this.projectPackageJson["11ty"].compatibility; } } diff --git a/src/Util/DirContains.js b/src/Util/DirContains.js index 580d330d8..c19990cbd 100644 --- a/src/Util/DirContains.js +++ b/src/Util/DirContains.js @@ -1,8 +1,9 @@ import path from "node:path"; // Returns true if subfolder is in parent (accepts absolute or relative paths for both) -export default function (parent, subfolder) { - if (path.resolve(subfolder).startsWith(path.resolve(parent))) { +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/src/Util/GlobRemap.js b/src/Util/GlobRemap.js new file mode 100644 index 000000000..5e2bea37a --- /dev/null +++ b/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/src/Util/ImportJsonSync.js b/src/Util/ImportJsonSync.js index 820a1834f..fa5936592 100644 --- a/src/Util/ImportJsonSync.js +++ b/src/Util/ImportJsonSync.js @@ -14,6 +14,7 @@ function findFilePathInParentDirs(dir, filename) { // 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) { @@ -49,9 +50,14 @@ function getModulePackageJson(dir) { return importJsonSync(filePath); } -function getWorkingProjectPackageJson() { +// This will *not* find a package.json in a parent directory above root +function getWorkingProjectPackageJsonPath() { let dir = TemplatePath.absolutePath(TemplatePath.getWorkingDir()); - let filePath = findFilePathInParentDirs(dir, "package.json"); + return findFilePathInParentDirs(dir, "package.json"); +} + +function getWorkingProjectPackageJson() { + let filePath = getWorkingProjectPackageJsonPath(); // optional! if (!filePath) { @@ -67,4 +73,5 @@ export { getEleventyPackageJson, getModulePackageJson, getWorkingProjectPackageJson, + getWorkingProjectPackageJsonPath, }; diff --git a/src/Util/ProjectDirectories.js b/src/Util/ProjectDirectories.js index c50a66aaf..e15e985af 100644 --- a/src/Util/ProjectDirectories.js +++ b/src/Util/ProjectDirectories.js @@ -3,6 +3,8 @@ 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 = { @@ -251,6 +253,7 @@ class ProjectDirectories { isTemplateFile(filePath) { let inputPath = this.getInputPath(filePath); + // TODO use DirContains if (this.layouts && inputPath.startsWith(this.layouts)) { return false; } @@ -263,6 +266,7 @@ class ProjectDirectories { } } + // TODO use DirContains return inputPath.startsWith(this.input); } @@ -309,11 +313,15 @@ class ProjectDirectories { } isFileInProjectFolder(filePath) { - return TemplatePath.absolutePath(filePath).startsWith(TemplatePath.getWorkingDir()); + return DirContains(TemplatePath.getWorkingDir(), filePath); } isFileInOutputFolder(filePath) { - return TemplatePath.absolutePath(filePath).startsWith(TemplatePath.absolutePath(this.output)); + 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. diff --git a/src/Util/SpawnAsync.js b/src/Util/SpawnAsync.js index 7f3ebe672..5e6a20f95 100644 --- a/src/Util/SpawnAsync.js +++ b/src/Util/SpawnAsync.js @@ -5,19 +5,23 @@ export function spawnAsync(command, args, options) { let { promise, resolve, reject } = withResolvers(); const cmd = spawn(command, args, options); + let res = []; cmd.stdout.on("data", (data) => { - resolve(data.toString("utf8")); + res.push(data.toString("utf8")); }); + let err = []; cmd.stderr.on("data", (data) => { - reject(data.toString("utf8")); + err.push(data.toString("utf8")); }); cmd.on("close", (code) => { - if (code === 1) { + if (err.length > 0) { + reject(err.join("\n")); + } else if (code === 1) { reject("Internal error: process closed with error exit code."); } else { - resolve(); + resolve(res.join("\n")); } }); diff --git a/test/EleventyFilesGitIgnoreEleventyIgnoreTest.js b/test/EleventyFilesGitIgnoreEleventyIgnoreTest.js index ea8e5d025..f98982823 100644 --- a/test/EleventyFilesGitIgnoreEleventyIgnoreTest.js +++ b/test/EleventyFilesGitIgnoreEleventyIgnoreTest.js @@ -22,7 +22,7 @@ test("Get ignores (no .eleventyignore no .gitignore)", async (t) => { ]); t.deepEqual(evf.getIgnoreGlobs().slice(-2), [ - "./test/stubs/ignorelocalroot/**/node_modules/**", + "**/node_modules/**", "./test/stubs/ignorelocalroot/.git/**", ]); }); @@ -45,7 +45,7 @@ test("Get ignores (no .eleventyignore)", async (t) => { ]); t.deepEqual(evf.getIgnoreGlobs().slice(-2), [ - "./test/stubs/ignorelocalrootgitignore/**/node_modules/**", + "**/node_modules/**", "./test/stubs/ignorelocalrootgitignore/.git/**", ]); }); @@ -67,7 +67,7 @@ test("Get ignores (no .eleventyignore, using setUseGitIgnore(false))", async (t) ]); t.deepEqual(evf.getIgnoreGlobs().slice(-2), [ - "./test/stubs/ignorelocalroot/**/node_modules/**", + "**/node_modules/**", "./test/stubs/ignorelocalroot/.git/**", ]); }); @@ -91,7 +91,7 @@ test("Get ignores (no .gitignore)", async (t) => { ]); t.deepEqual(evf.getIgnoreGlobs().slice(-2), [ - "./test/stubs/ignorelocalroot/**/node_modules/**", + "**/node_modules/**", "./test/stubs/ignorelocalroot/.git/**", ]); }); @@ -116,7 +116,7 @@ test("Get ignores (project .eleventyignore and root .gitignore)", async (t) => { ]); t.deepEqual(evf.getIgnoreGlobs().slice(-2), [ - "./test/stubs/ignorelocalrootgitignore/**/node_modules/**", + "**/node_modules/**", "./test/stubs/ignorelocalrootgitignore/.git/**", ]); }); @@ -141,7 +141,7 @@ test("Get ignores (project .eleventyignore and root .gitignore, using setUseGitI ]); t.deepEqual(evf.getIgnoreGlobs().slice(-2), [ - "./test/stubs/ignorelocalrootgitignore/**/node_modules/**", + "**/node_modules/**", "./test/stubs/ignorelocalrootgitignore/.git/**", ]); }); @@ -164,7 +164,7 @@ test("Get ignores (no .eleventyignore .gitignore exists but empty)", async (t) ]); t.deepEqual(evf.getIgnoreGlobs().slice(-2), [ - "./test/stubs/ignorelocalroot/**/node_modules/**", + "**/node_modules/**", "./test/stubs/ignorelocalroot/.git/**", ]); }); @@ -188,7 +188,7 @@ test("Get ignores (both .eleventyignore and .gitignore exists, but .gitignore is ]); t.deepEqual(evf.getIgnoreGlobs().slice(-2), [ - "./test/stubs/ignorelocalroot/**/node_modules/**", + "**/node_modules/**", "./test/stubs/ignorelocalroot/.git/**", ]); }); @@ -309,7 +309,7 @@ test("De-duplicated ignores", async (t) => { ]); t.deepEqual(evf.getIgnoreGlobs().slice(-2), [ - "./test/stubs/ignore-dedupe/**/node_modules/**", + "**/node_modules/**", "./test/stubs/ignore-dedupe/.git/**", ]); }); diff --git a/test/EleventyFilesTest.js b/test/EleventyFilesTest.js index 4b377bd62..56687e372 100644 --- a/test/EleventyFilesTest.js +++ b/test/EleventyFilesTest.js @@ -284,7 +284,7 @@ test("Include and Data Dirs", async (t) => { let { eleventyFiles: evf } = getEleventyFilesInstance([], eleventyConfig); evf.init(); - t.deepEqual(evf._getIncludesAndDataDirs(), [ + t.deepEqual(evf.getIncludesAndDataDirs(), [ "./test/stubs/_includes/**", "./test/stubs/_data/**", ]); diff --git a/test/GlobRemapTest.js b/test/GlobRemapTest.js new file mode 100644 index 000000000..50b67ad09 --- /dev/null +++ b/test/GlobRemapTest.js @@ -0,0 +1,125 @@ +import test from "ava"; +import path from "node:path"; +import GlobRemap from "../src/Util/GlobRemap.js"; +import { normalizeSeparatorString } from "./Util/normalizeSeparators.js"; + +test("getParentDirPrefix", (t) => { + t.is(GlobRemap.getParentDirPrefix(""), ""); + t.is(GlobRemap.getParentDirPrefix("./test/"), ""); + t.is(GlobRemap.getParentDirPrefix("../test/"), "../"); + t.is(GlobRemap.getParentDirPrefix("../test/../"), "../"); + t.is(GlobRemap.getParentDirPrefix("../../test/"), "../../"); +}); + +test("getCwd", (t) => { + t.is(GlobRemap.getCwd([]), ""); + t.is(GlobRemap.getCwd(["test.njk"]), ""); + t.is(GlobRemap.getCwd(["./test.njk"]), ""); + t.is(GlobRemap.getCwd(["../test.njk"]), "../"); + t.is(GlobRemap.getCwd(["../test.njk", "../../2.njk"]), "../../"); +}); + +test("Constructor (control)", t => { + let m = new GlobRemap([ + '**/*.{liquid,md,njk,html,11ty.js,11ty.cjs,11ty.mjs}', + '**/*.txt', // passthrough copy + '**/*.png', + '_includes/**', + '_data/**', + '.gitignore', + '.eleventyignore', + 'eleventy.config.js', + ]) + + t.deepEqual(m.getInput(), [ + '**/*.{liquid,md,njk,html,11ty.js,11ty.cjs,11ty.mjs}', + '**/*.txt', // passthrough copy + '**/*.png', + '_includes/**', + '_data/**', + '.gitignore', + '.eleventyignore', + 'eleventy.config.js', + ]) +}); + +test("Constructor (control with ./)", t => { + let m = new GlobRemap([ + './**/*.{liquid,md,njk,html,11ty.js,11ty.cjs,11ty.mjs}', + './**/*.txt', // passthrough copy + './**/*.png', + './_includes/**', + './_data/**', + './.gitignore', + './.eleventyignore', + './eleventy.config.js', + ]) + + t.deepEqual(m.getInput(), [ + './**/*.{liquid,md,njk,html,11ty.js,11ty.cjs,11ty.mjs}', + './**/*.txt', // passthrough copy + './**/*.png', + './_includes/**', + './_data/**', + './.gitignore', + './.eleventyignore', + './eleventy.config.js', + ]) +}); + +test("Constructor (up one dir)", t => { + let m = new GlobRemap([ + '../**/*.{liquid,md,njk,html,11ty.js,11ty.cjs,11ty.mjs}', + '../**/*.txt', // passthrough copy + '../**/*.png', + '../_includes/**', + '../_data/**', + './.gitignore', + './.eleventyignore', + '../.eleventyignore', + './eleventy.config.js', + ]) + + let parentDir = normalizeSeparatorString(path.resolve("./").split(path.sep).slice(-1).join(path.sep)); + + t.deepEqual(m.getInput(), [ + '**/*.{liquid,md,njk,html,11ty.js,11ty.cjs,11ty.mjs}', + '**/*.txt', // passthrough copy + '**/*.png', + '_includes/**', + '_data/**', + `${parentDir}/.gitignore`, + `${parentDir}/.eleventyignore`, + '.eleventyignore', + `${parentDir}/eleventy.config.js`, + ]) +}); + +test("Constructor (up two dirs)", t => { + let m = new GlobRemap([ + '../../**/*.{liquid,md,njk,html,11ty.js,11ty.cjs,11ty.mjs}', + '../**/*.txt', // passthrough copy + '../**/*.png', + '../_includes/**', + '../_data/**', + './.gitignore', + './.eleventyignore', + '../.eleventyignore', + './eleventy.config.js', + ]) + + let childDir = normalizeSeparatorString(path.resolve("./").split(path.sep).slice(-2).join(path.sep)); + let parentDir = normalizeSeparatorString(path.resolve("./").split(path.sep).slice(-2, -1).join(path.sep)); + + t.deepEqual(m.getInput(), [ + '**/*.{liquid,md,njk,html,11ty.js,11ty.cjs,11ty.mjs}', + `${parentDir}/**/*.txt`, // passthrough copy + `${parentDir}/**/*.png`, + `${parentDir}/_includes/**`, + `${parentDir}/_data/**`, + `${childDir}/.gitignore`, + `${childDir}/.eleventyignore`, + `${parentDir}/.eleventyignore`, + `${childDir}/eleventy.config.js`, + ]) +}); diff --git a/test/Issue3854Test.js b/test/Issue3854Test.js index 0b94b4fa0..50e90b3c8 100644 --- a/test/Issue3854Test.js +++ b/test/Issue3854Test.js @@ -2,7 +2,7 @@ import test from "ava"; import { spawnAsync } from "../src/Util/SpawnAsync.js"; -test.skip("#3854 parent directory for content, with global data files", async (t) => { +test("#3854 parent directory for content, with global data files", async (t) => { let result = await spawnAsync( "node", ["../../../../cmd.cjs", "--to=json"], diff --git a/test/Util/normalizeSeparators.js b/test/Util/normalizeSeparators.js new file mode 100644 index 000000000..22b0a0d87 --- /dev/null +++ b/test/Util/normalizeSeparators.js @@ -0,0 +1,11 @@ +import PathNormalizer from "../../src/Util/PathNormalizer.js"; + +export function normalizeSeparatorString(str) { + return PathNormalizer.normalizeSeperator(str); +} + +export function normalizeSeparatorArray(arr) { + return arr.map(entry => { + return PathNormalizer.normalizeSeperator(entry); + }) +} diff --git a/test/Util/removeNewLines.js b/test/Util/removeNewLines.js deleted file mode 100644 index eef56384e..000000000 --- a/test/Util/removeNewLines.js +++ /dev/null @@ -1,5 +0,0 @@ -function removeNewLines(str) { - return str.replace(/[\r\n]*/g, ""); -} - -module.exports = removeNewLines;