diff options
Diffstat (limited to 'node_modules/@11ty/eleventy/src/Util')
41 files changed, 2693 insertions, 0 deletions
diff --git a/node_modules/@11ty/eleventy/src/Util/ArrayUtil.js b/node_modules/@11ty/eleventy/src/Util/ArrayUtil.js new file mode 100644 index 0000000..bcb61de --- /dev/null +++ b/node_modules/@11ty/eleventy/src/Util/ArrayUtil.js @@ -0,0 +1,24 @@ +export function arrayDelete(arr, match) { + if (!Array.isArray(arr)) { + return []; + } + + if (!match) { + return arr; + } + + // only mutates if found + if (typeof match === "function") { + if (arr.find(match)) { + return arr.filter((entry) => { + return !match(entry); + }); + } + } else if (arr.includes(match)) { + return arr.filter((entry) => { + return entry !== match; + }); + } + + return arr; +} diff --git a/node_modules/@11ty/eleventy/src/Util/AsyncEventEmitter.js b/node_modules/@11ty/eleventy/src/Util/AsyncEventEmitter.js new file mode 100644 index 0000000..0bc471f --- /dev/null +++ b/node_modules/@11ty/eleventy/src/Util/AsyncEventEmitter.js @@ -0,0 +1,88 @@ +import { EventEmitter } from "node:events"; + +/** + * This class emits events asynchronously. + * + * Note that Eleventy has two separate event emitter instances it uses: + * 1. a userland one (UserConfig.js) + * 2. a global one for internals (EventBus.js) + */ +class AsyncEventEmitter extends EventEmitter { + #handlerMode = "parallel"; + + // TypeScript slop + constructor(...args) { + super(...args); + } + + reset() { + // `eleventy#` event type listeners are removed at the start of each build (singletons) + for (let type of this.eventNames()) { + if (typeof type === "string" && type.startsWith("eleventy#")) { + this.removeAllListeners(type); + } + } + + } + + /** + * @param {string} type - The event name to emit. + * @param {...*} args - Additional arguments that get passed to listeners. + * @returns {Promise} - Promise resolves once all listeners were invoked + */ + /** @ts-expect-error */ + async emit(type, ...args) { + let listeners = this.listeners(type); + if (listeners.length === 0) { + return []; + } + + if (this.#handlerMode == "sequential") { + const result = []; + for (const listener of listeners) { + const returnValue = await listener.apply(this, args); + result.push(returnValue); + } + return result; + } else { + return Promise.all( + listeners.map((listener) => { + return listener.apply(this, args); + }), + ); + } + } + + /** + * @param {string} type - The event name to emit. + * @param {...*} args - Additional lazy-executed function arguments that get passed to listeners. + * @returns {Promise} - Promise resolves once all listeners were invoked + */ + async emitLazy(type, ...args) { + let listeners = this.listeners(type); + if (listeners.length === 0) { + return []; + } + + let argsMap = []; + for (let arg of args) { + if (typeof arg === "function") { + let r = arg(); + if (r instanceof Promise) { + r = await r; + } + argsMap.push(r); + } else { + argsMap.push(arg); + } + } + + return this.emit.call(this, type, ...argsMap); + } + + setHandlerMode(mode) { + this.#handlerMode = mode; + } +} + +export default AsyncEventEmitter; diff --git a/node_modules/@11ty/eleventy/src/Util/Compatibility.js b/node_modules/@11ty/eleventy/src/Util/Compatibility.js new file mode 100644 index 0000000..c90a9b3 --- /dev/null +++ b/node_modules/@11ty/eleventy/src/Util/Compatibility.js @@ -0,0 +1,59 @@ +import semver from "semver"; + +import { getEleventyPackageJson, getWorkingProjectPackageJson } from "./ImportJsonSync.js"; + +const pkg = getEleventyPackageJson(); + +// Used in user config versionCheck method. +class Compatibility { + static NORMALIZE_PRERELEASE_REGEX = /-canary\b/g; + + static #projectPackageJson; + + constructor(compatibleRange) { + this.compatibleRange = Compatibility.getCompatibilityValue(compatibleRange); + } + + static get projectPackageJson() { + if (!this.#projectPackageJson) { + this.#projectPackageJson = getWorkingProjectPackageJson(); + } + + return this.#projectPackageJson; + } + + static normalizeIdentifier(identifier) { + return identifier.replace(Compatibility.NORMALIZE_PRERELEASE_REGEX, "-alpha"); + } + + static getCompatibilityValue(compatibleRange) { + if (compatibleRange) { + return compatibleRange; + } + + // fetch from project’s package.json + if (this.projectPackageJson?.["11ty"]?.compatibility) { + return this.projectPackageJson["11ty"].compatibility; + } + } + + isCompatible() { + return Compatibility.satisfies(pkg.version, this.compatibleRange); + } + + static satisfies(version, compatibleRange) { + return semver.satisfies( + Compatibility.normalizeIdentifier(version), + Compatibility.normalizeIdentifier(compatibleRange), + { + includePrerelease: true, + }, + ); + } + + getErrorMessage() { + return `We found Eleventy version '${pkg.version}' which does not meet the required version range: '${this.compatibleRange}'. Use \`npm install @11ty/eleventy\` to upgrade your local project to the latest Eleventy version (or \`npm install @11ty/eleventy -g\` to upgrade the globally installed version).`; + } +} + +export default Compatibility; diff --git a/node_modules/@11ty/eleventy/src/Util/ConsoleLogger.js b/node_modules/@11ty/eleventy/src/Util/ConsoleLogger.js new file mode 100644 index 0000000..ba41196 --- /dev/null +++ b/node_modules/@11ty/eleventy/src/Util/ConsoleLogger.js @@ -0,0 +1,140 @@ +import { Readable } from "node:stream"; +import chalk from "kleur"; +import debugUtil from "debug"; + +const debug = debugUtil("Eleventy:Logger"); + +/** + * Logger implementation that logs to STDOUT. + * @typedef {'error'|'log'|'warn'|'info'} LogType + */ +class ConsoleLogger { + /** @type {boolean} */ + #isVerbose = true; + /** @type {boolean} */ + #isChalkEnabled = true; + /** @type {object|boolean|undefined} */ + #logger; + /** @type {Readable|undefined} */ + #outputStream; + + constructor() {} + + isLoggingEnabled() { + if (!this.isVerbose || process.env.DEBUG) { + return true; + } + return this.#logger !== false; + } + + get isVerbose() { + return this.#isVerbose; + } + + set isVerbose(verbose) { + this.#isVerbose = !!verbose; + } + + get isChalkEnabled() { + return this.#isChalkEnabled; + } + + set isChalkEnabled(enabled) { + this.#isChalkEnabled = !!enabled; + } + + overrideLogger(logger) { + this.#logger = logger; + } + + get logger() { + return this.#logger || console; + } + + /** @param {string} msg */ + log(msg) { + this.message(msg); + } + + /** + * @typedef LogOptions + * @property {string} message + * @property {string=} prefix + * @property {LogType=} type + * @property {string=} color + * @property {boolean=} force + * @param {LogOptions} options + */ + logWithOptions({ message, type, prefix, color, force }) { + this.message(message, type, color, force, prefix); + } + + /** @param {string} msg */ + forceLog(msg) { + this.message(msg, undefined, undefined, true); + } + + /** @param {string} msg */ + info(msg) { + this.message(msg, "warn", "blue"); + } + + /** @param {string} msg */ + warn(msg) { + this.message(msg, "warn", "yellow"); + } + + /** @param {string} msg */ + error(msg) { + this.message(msg, "error", "red"); + } + + get outputStream() { + if (!this.#outputStream) { + this.#outputStream = new Readable({ + read() {}, + }); + } + return this.#outputStream; + } + + /** @param {string} msg */ + toStream(msg) { + this.outputStream.push(msg); + } + + closeStream() { + this.outputStream.push(null); + return this.outputStream; + } + + /** + * Formats the message to log. + * + * @param {string} message - The raw message to log. + * @param {LogType} [type='log'] - The error level to log. + * @param {string|undefined} [chalkColor=undefined] - Color name or falsy to disable + * @param {boolean} [forceToConsole=false] - Enforce a log on console instead of specified target. + */ + message( + message, + type = "log", + chalkColor = undefined, + forceToConsole = false, + prefix = "[11ty]", + ) { + if (!forceToConsole && (!this.isVerbose || process.env.DEBUG)) { + debug(message); + } else if (this.#logger !== false) { + message = `${chalk.gray(prefix)} ${message.split("\n").join(`\n${chalk.gray(prefix)} `)}`; + + if (chalkColor && this.isChalkEnabled) { + this.logger[type](chalk[chalkColor](message)); + } else { + this.logger[type](message); + } + } + } +} + +export default ConsoleLogger; diff --git a/node_modules/@11ty/eleventy/src/Util/DateGitFirstAdded.js b/node_modules/@11ty/eleventy/src/Util/DateGitFirstAdded.js new file mode 100644 index 0000000..0cc5959 --- /dev/null +++ b/node_modules/@11ty/eleventy/src/Util/DateGitFirstAdded.js @@ -0,0 +1,23 @@ +import { spawnAsync } from "./SpawnAsync.js"; + +async function getGitFirstAddedTimeStamp(filePath) { + try { + let timestamp = await spawnAsync( + "git", + // Formats https://www.git-scm.com/docs/git-log#_pretty_formats + // %at author date, UNIX timestamp + ["log", "--diff-filter=A", "--follow", "-1", "--format=%at", filePath], + ); + return parseInt(timestamp, 10) * 1000; + } catch (e) { + // do nothing + } +} + +// return a Date +export default async function (inputPath) { + let timestamp = await getGitFirstAddedTimeStamp(inputPath); + if (timestamp) { + return new Date(timestamp); + } +} diff --git a/node_modules/@11ty/eleventy/src/Util/DateGitLastUpdated.js b/node_modules/@11ty/eleventy/src/Util/DateGitLastUpdated.js new file mode 100644 index 0000000..1f58c11 --- /dev/null +++ b/node_modules/@11ty/eleventy/src/Util/DateGitLastUpdated.js @@ -0,0 +1,23 @@ +import { spawnAsync } from "./SpawnAsync.js"; + +async function getGitLastUpdatedTimeStamp(filePath) { + try { + let timestamp = await spawnAsync( + "git", + // Formats https://www.git-scm.com/docs/git-log#_pretty_formats + // %at author date, UNIX timestamp + ["log", "-1", "--format=%at", filePath], + ); + return parseInt(timestamp, 10) * 1000; + } catch (e) { + // do nothing + } +} + +// return a Date +export default async function (inputPath) { + let timestamp = await getGitLastUpdatedTimeStamp(inputPath); + if (timestamp) { + return new Date(timestamp); + } +} diff --git a/node_modules/@11ty/eleventy/src/Util/DirContains.js b/node_modules/@11ty/eleventy/src/Util/DirContains.js new file mode 100644 index 0000000..c19990c --- /dev/null +++ b/node_modules/@11ty/eleventy/src/Util/DirContains.js @@ -0,0 +1,10 @@ +import path from "node:path"; + +// Returns true if subfolder is in parent (accepts absolute or relative paths for both) +export default function (parentFolder, subFolder) { + // path.resolve returns an absolute path + if (path.resolve(subFolder).startsWith(path.resolve(parentFolder))) { + return true; + } + return false; +} diff --git a/node_modules/@11ty/eleventy/src/Util/EsmResolver.js b/node_modules/@11ty/eleventy/src/Util/EsmResolver.js new file mode 100644 index 0000000..c098ed8 --- /dev/null +++ b/node_modules/@11ty/eleventy/src/Util/EsmResolver.js @@ -0,0 +1,53 @@ +import debugUtil from "debug"; +import { fileURLToPath } from "node:url"; +import PathNormalizer from "./PathNormalizer.js"; + +const debug = debugUtil("Eleventy:EsmResolver"); + +let lastModifiedPaths = new Map(); +export async function initialize({ port }) { + // From `eleventy.importCacheReset` event in Require.js + port.on("message", ({ path, newDate }) => { + lastModifiedPaths.set(path, newDate); + }); +} + +// Fixes issue https://github.com/11ty/eleventy/issues/3270 +// Docs: https://nodejs.org/docs/latest/api/module.html#resolvespecifier-context-nextresolve +export async function resolve(specifier, context, nextResolve) { + try { + // Not a relative import and not a file import + // Or from node_modules (perhaps better to check if the specifier is in the project directory instead) + if ( + (!specifier.startsWith("../") && + !specifier.startsWith("./") && + !specifier.startsWith("file:")) || + context.parentURL.includes("/node_modules/") + ) { + return nextResolve(specifier); + } + + let fileUrl = new URL(specifier, context.parentURL); + if (fileUrl.searchParams.has("_cache_bust")) { + // already is cache busted outside resolver (wider compat, url was changed prior to import, probably in Require.js) + return nextResolve(specifier); + } + + let absolutePath = PathNormalizer.normalizeSeperator(fileURLToPath(fileUrl)); + // Bust the import cache if this is a recently modified file + if (lastModifiedPaths.has(absolutePath)) { + fileUrl.search = ""; // delete existing searchparams + fileUrl.searchParams.set("_cache_bust", lastModifiedPaths.get(absolutePath)); + debug("Cache busting %o to %o", specifier, fileUrl.toString()); + + return nextResolve(fileUrl.toString()); + } + } catch (e) { + debug("EsmResolver Error parsing specifier (%o): %o", specifier, e); + } + + return nextResolve(specifier); +} + +// export async function load(url, context, nextLoad) { +// } diff --git a/node_modules/@11ty/eleventy/src/Util/EventBusUtil.js b/node_modules/@11ty/eleventy/src/Util/EventBusUtil.js new file mode 100644 index 0000000..c749fe9 --- /dev/null +++ b/node_modules/@11ty/eleventy/src/Util/EventBusUtil.js @@ -0,0 +1,14 @@ +import eventBus from "../EventBus.js"; +import debugUtil from "debug"; + +const debug = debugUtil("Eleventy:EventBus"); + +class EventBusUtil { + static debugCurrentListenerCounts() { + for (let name of eventBus.eventNames()) { + debug("Listeners for %o: %o", name, eventBus.listenerCount(name)); + } + } +} + +export default EventBusUtil; diff --git a/node_modules/@11ty/eleventy/src/Util/ExistsCache.js b/node_modules/@11ty/eleventy/src/Util/ExistsCache.js new file mode 100644 index 0000000..433d743 --- /dev/null +++ b/node_modules/@11ty/eleventy/src/Util/ExistsCache.js @@ -0,0 +1,62 @@ +import fs from "node:fs"; +import { TemplatePath } from "@11ty/eleventy-utils"; + +// Checks both files and directories +class ExistsCache { + #exists = new Map(); + #dirs = new Map(); + + constructor() { + this.lookupCount = 0; + } + + get size() { + return this.#exists.size; + } + + has(path) { + return this.#exists.has(path); + } + + set(path, isExist) { + this.#exists.set(TemplatePath.addLeadingDotSlash(path), Boolean(isExist)); + } + + // Not yet needed + // setDirectory(path, isExist) {} + + // Relative paths (to root directory) expected (but not enforced due to perf costs) + exists(path) { + if (!this.#exists.has(path)) { + let exists = fs.existsSync(path); + this.lookupCount++; + + // mark for next time + this.#exists.set(path, Boolean(exists)); + + return exists; + } + + return this.#exists.get(path); + } + + isDirectory(path) { + if (!this.exists(path)) { + return false; + } + + if (!this.#dirs.has(path)) { + let isDir = fs.statSync(path).isDirectory(); + this.lookupCount++; + + // mark for next time + this.#dirs.set(path, isDir); + + return isDir; + } + + return this.#dirs.get(path); + } +} + +export default ExistsCache; diff --git a/node_modules/@11ty/eleventy/src/Util/FilePathUtil.js b/node_modules/@11ty/eleventy/src/Util/FilePathUtil.js new file mode 100644 index 0000000..1675e8e --- /dev/null +++ b/node_modules/@11ty/eleventy/src/Util/FilePathUtil.js @@ -0,0 +1,19 @@ +class FilePathUtil { + static isMatchingExtension(filepath, fileExtension) { + if (!fileExtension) { + return false; + } + + if (!(fileExtension || "").startsWith(".")) { + fileExtension = "." + fileExtension; + } + + return filepath.endsWith(fileExtension); + } + + static getFileExtension(filepath) { + return (filepath || "").split(".").pop(); + } +} + +export { FilePathUtil }; diff --git a/node_modules/@11ty/eleventy/src/Util/FileSystemManager.js b/node_modules/@11ty/eleventy/src/Util/FileSystemManager.js new file mode 100644 index 0000000..12881d7 --- /dev/null +++ b/node_modules/@11ty/eleventy/src/Util/FileSystemManager.js @@ -0,0 +1,48 @@ +import path from "node:path"; +import fs from "node:fs"; +import { mkdir, writeFile } from "node:fs/promises"; + +class FileSystemManager { + constructor(templateConfig) { + if (!templateConfig || templateConfig.constructor.name !== "TemplateConfig") { + throw new Error( + "Internal error: Missing `templateConfig` or was not an instance of `TemplateConfig`.", + ); + } + this.templateConfig = templateConfig; + } + + exists(pathname) { + return this.templateConfig.existsCache.exists(pathname); + } + + async createDirectoryForFile(filePath) { + let dir = path.parse(filePath).dir; + if (!dir || this.exists(dir)) { + return; + } + + return mkdir(dir, { recursive: true }); + } + + createDirectoryForFileSync(filePath) { + let dir = path.parse(filePath).dir; + if (!dir || this.exists(dir)) { + return; + } + + fs.mkdirSync(dir, { recursive: true }); + } + + async writeFile(filePath, content) { + return writeFile(filePath, content); + } + + writeFileSync(filePath, content) { + // Note: This deliberately uses the synchronous version to avoid + // unbounded concurrency: https://github.com/11ty/eleventy/issues/3271 + fs.writeFileSync(filePath, content); + } +} + +export { FileSystemManager }; diff --git a/node_modules/@11ty/eleventy/src/Util/GetJavaScriptData.js b/node_modules/@11ty/eleventy/src/Util/GetJavaScriptData.js new file mode 100644 index 0000000..7d72a64 --- /dev/null +++ b/node_modules/@11ty/eleventy/src/Util/GetJavaScriptData.js @@ -0,0 +1,30 @@ +import EleventyBaseError from "../Errors/EleventyBaseError.js"; + +class JavaScriptInvalidDataFormatError extends EleventyBaseError {} + +export default async function (inst, inputPath, key = "data", options = {}) { + let { mixins, isObjectRequired } = Object.assign( + { + mixins: {}, + isObjectRequired: true, + }, + options, + ); + + if (inst && key in inst) { + // get extra data from `data` method, + // either as a function or getter or object literal + let result = await (typeof inst[key] === "function" + ? Object.keys(mixins).length > 0 + ? inst[key].call(mixins) + : inst[key]() + : inst[key]); + + if (isObjectRequired && typeof result !== "object") { + throw new JavaScriptInvalidDataFormatError( + `Invalid data format returned from ${inputPath}: typeof ${typeof result}`, + ); + } + return result; + } +} diff --git a/node_modules/@11ty/eleventy/src/Util/GlobMatcher.js b/node_modules/@11ty/eleventy/src/Util/GlobMatcher.js new file mode 100644 index 0000000..a4a6c55 --- /dev/null +++ b/node_modules/@11ty/eleventy/src/Util/GlobMatcher.js @@ -0,0 +1,22 @@ +import picomatch from "picomatch"; +import { TemplatePath } from "@11ty/eleventy-utils"; + +function isGlobMatch(filepath, globs = [], options = undefined) { + if (!filepath || !Array.isArray(globs) || globs.length === 0) { + return false; + } + + let inputPath = TemplatePath.stripLeadingDotSlash(filepath); + let opts = Object.assign( + { + dot: true, + nocase: true, // insensitive + }, + options, + ); + + // globs: string or array of strings + return picomatch.isMatch(inputPath, globs, opts); +} + +export { isGlobMatch }; diff --git a/node_modules/@11ty/eleventy/src/Util/GlobRemap.js b/node_modules/@11ty/eleventy/src/Util/GlobRemap.js new file mode 100644 index 0000000..5e2bea3 --- /dev/null +++ b/node_modules/@11ty/eleventy/src/Util/GlobRemap.js @@ -0,0 +1,85 @@ +import path from "node:path"; +import ProjectDirectories from "./ProjectDirectories.js"; +import PathNormalizer from "./PathNormalizer.js"; + +// even on Windows (in cmd.exe) these paths are normalized to forward slashes +// tinyglobby expects forward slashes on Windows +const SEP = "/"; + +class GlobRemap { + constructor(paths = []) { + this.paths = paths; + this.cwd = GlobRemap.getCwd(paths); + } + + getCwd() { + return this.cwd; + } + + getRemapped(paths) { + return paths.map((entry) => GlobRemap.remapInput(entry, this.cwd)); + } + + getInput() { + return this.getRemapped(this.paths); + } + + getOutput(paths = []) { + return paths.map((entry) => GlobRemap.remapOutput(entry, this.cwd)); + } + + static getParentDirPrefix(filePath = "") { + let count = []; + for (let p of filePath.split(SEP)) { + if (p === "..") { + count.push(".."); + } else { + break; + } + } + + if (count.length > 0) { + // trailing slash + return count.join(SEP) + SEP; + } + return ""; + } + + static getLongestParentDirPrefix(filePaths) { + let longest = ""; + filePaths + .map((entry) => { + return this.getParentDirPrefix(entry); + }) + .filter((entry) => Boolean(entry)) + .forEach((prefix) => { + if (!longest || prefix.length > longest.length) { + longest = prefix; + } + }); + return longest; + } + + // alias + static getCwd(filePaths) { + return this.getLongestParentDirPrefix(filePaths); + } + + static remapInput(entry, cwd) { + if (cwd) { + if (!entry.startsWith("**" + SEP) && !entry.startsWith(`.git${SEP}**`)) { + return PathNormalizer.normalizeSeperator(ProjectDirectories.getRelativeTo(entry, cwd)); + } + } + return entry; + } + + static remapOutput(entry, cwd) { + if (cwd) { + return PathNormalizer.normalizeSeperator(path.join(cwd, entry)); + } + return entry; + } +} + +export default GlobRemap; diff --git a/node_modules/@11ty/eleventy/src/Util/HtmlRelativeCopy.js b/node_modules/@11ty/eleventy/src/Util/HtmlRelativeCopy.js new file mode 100644 index 0000000..3059014 --- /dev/null +++ b/node_modules/@11ty/eleventy/src/Util/HtmlRelativeCopy.js @@ -0,0 +1,149 @@ +import path from "node:path"; +import { TemplatePath } from "@11ty/eleventy-utils"; +import isValidUrl from "./ValidUrl.js"; +import { isGlobMatch } from "./GlobMatcher.js"; + +class HtmlRelativeCopy { + #userConfig; + #matchingGlobs = new Set(); + #matchingGlobsArray; + #dirty = false; + #paths = new Set(); + #failOnError = true; + #copyOptions = { + dot: false, // differs from standard passthrough copy + }; + + isEnabled() { + return this.#matchingGlobs.size > 0; + } + + setFailOnError(failOnError) { + this.#failOnError = Boolean(failOnError); + } + + setCopyOptions(opts) { + if (opts) { + Object.assign(this.#copyOptions, opts); + } + } + + setUserConfig(userConfig) { + if (!userConfig || userConfig.constructor.name !== "UserConfig") { + throw new Error( + "Internal error: Missing `userConfig` or was not an instance of `UserConfig`.", + ); + } + this.#userConfig = userConfig; + } + + addPaths(paths = []) { + for (let path of paths) { + this.#paths.add(TemplatePath.getDir(path)); + } + } + + get matchingGlobs() { + if (this.#dirty || !this.#matchingGlobsArray) { + this.#matchingGlobsArray = Array.from(this.#matchingGlobs); + this.#dirty = false; + } + + return this.#matchingGlobsArray; + } + + addMatchingGlob(glob) { + if (glob) { + if (Array.isArray(glob)) { + for (let g of glob) { + this.#matchingGlobs.add(g); + } + } else { + this.#matchingGlobs.add(glob); + } + this.#dirty = true; + } + } + + isSkippableHref(rawRef) { + if ( + this.#matchingGlobs.size === 0 || + !rawRef || + path.isAbsolute(rawRef) || + isValidUrl(rawRef) + ) { + return true; + } + return false; + } + + isCopyableTarget(target) { + if (!isGlobMatch(target, this.matchingGlobs)) { + return false; + } + + return true; + } + + exists(filePath) { + return this.#userConfig.exists(filePath); + } + + getAliasedPath(ref) { + for (let dir of this.#paths) { + let found = TemplatePath.join(dir, ref); + if (this.isCopyableTarget(found) && this.exists(found)) { + return found; + } + } + } + + getFilePathRelativeToProjectRoot(ref, contextFilePath) { + let dir = TemplatePath.getDirFromFilePath(contextFilePath); + return TemplatePath.join(dir, ref); + } + + copy(fileRef, tmplInputPath, tmplOutputPath) { + // original ref is a full URL or no globs exist + if (this.isSkippableHref(fileRef)) { + return; + } + + // Relative to source file’s input path + let source = this.getFilePathRelativeToProjectRoot(fileRef, tmplInputPath); + if (!this.isCopyableTarget(source)) { + return; + } + + if (!this.exists(source)) { + // Try to alias using `options.paths` + let alias = this.getAliasedPath(fileRef); + if (!alias) { + if (this.#failOnError) { + throw new Error( + "Missing input file for `html-relative` Passthrough Copy file: " + + TemplatePath.absolutePath(source), + ); + } + + // don’t fail on error + return; + } + + source = alias; + } + + let target = this.getFilePathRelativeToProjectRoot(fileRef, tmplOutputPath); + + // We use a Set here to allow passthrough copy manager to properly error on conflicts upstream + // Only errors when different inputs write to the same output + // Also errors if attempts to write outside the output folder. + this.#userConfig.emit("eleventy#copy", { + source, + target, + options: this.#copyOptions, + }); + } +} + +export { HtmlRelativeCopy }; diff --git a/node_modules/@11ty/eleventy/src/Util/HtmlTransformer.js b/node_modules/@11ty/eleventy/src/Util/HtmlTransformer.js new file mode 100644 index 0000000..f28910f --- /dev/null +++ b/node_modules/@11ty/eleventy/src/Util/HtmlTransformer.js @@ -0,0 +1,172 @@ +import posthtml from "posthtml"; +import urls from "@11ty/posthtml-urls"; +import { FilePathUtil } from "./FilePathUtil.js"; + +import { arrayDelete } from "./ArrayUtil.js"; + +class HtmlTransformer { + // feature test for Eleventy Bundle Plugin + static SUPPORTS_PLUGINS_ENABLED_CALLBACK = true; + + static TYPES = ["callbacks", "plugins"]; + + constructor() { + // execution order is important (not order of addition/object key order) + this.callbacks = {}; + this.posthtmlProcessOptions = {}; + this.plugins = {}; + } + + get aggregateBench() { + if (!this.userConfig) { + throw new Error("Internal error: Missing `userConfig` in HtmlTransformer."); + } + return this.userConfig.benchmarkManager.get("Aggregate"); + } + + setUserConfig(config) { + this.userConfig = config; + } + + static prioritySort(a, b) { + if (b.priority > a.priority) { + return 1; + } + if (a.priority > b.priority) { + return -1; + } + return 0; + } + + // context is important as it is used in html base plugin for page specific URL + static _getPosthtmlInstance(callbacks = [], plugins = [], context = {}) { + let inst = posthtml(); + + // already sorted by priority when added + for (let { fn: plugin, options } of plugins) { + inst.use(plugin(Object.assign({}, context, options))); + } + + // Run the built-ins last + if (callbacks.length > 0) { + inst.use( + urls({ + eachURL: (url, attrName, tagName) => { + for (let { fn: callback } of callbacks) { + // already sorted by priority when added + url = callback.call(context, url, { attribute: attrName, tag: tagName }); + } + + return url; + }, + }), + ); + } + + return inst; + } + + _add(extensions, addType, value, options = {}) { + options = Object.assign( + { + priority: 0, + }, + options, + ); + + let extensionsArray = (extensions || "").split(","); + for (let ext of extensionsArray) { + let target = this[addType]; + if (!target[ext]) { + target[ext] = []; + } + + target[ext].push({ + // *could* fallback to function name, `value.name` + name: options.name, // for `remove` and debugging + fn: value, // callback or plugin + priority: options.priority, // sorted in descending order + enabled: options.enabled || (() => true), + options: options.pluginOptions, + }); + + target[ext].sort(HtmlTransformer.prioritySort); + } + } + + addPosthtmlPlugin(extensions, plugin, options = {}) { + this._add(extensions, "plugins", plugin, options); + } + + // match can be a plugin function or a filter callback(plugin => true); + remove(extensions, match) { + for (let removeType of HtmlTransformer.TYPES) { + for (let ext of (extensions || "").split(",")) { + this[removeType][ext] = arrayDelete(this[removeType][ext], match); + } + } + } + + addUrlTransform(extensions, callback, options = {}) { + this._add(extensions, "callbacks", callback, options); + } + + setPosthtmlProcessOptions(options) { + Object.assign(this.posthtmlProcessOptions, options); + } + + isTransformable(extension, context) { + return ( + this.getCallbacks(extension, context).length > 0 || this.getPlugins(extension).length > 0 + ); + } + + getCallbacks(extension, context) { + let callbacks = this.callbacks[extension] || []; + return callbacks.filter(({ enabled }) => { + if (!enabled || typeof enabled !== "function") { + return true; + } + return enabled(context); + }); + } + + getPlugins(extension) { + let plugins = this.plugins[extension] || []; + return plugins.filter(({ enabled }) => { + if (!enabled || typeof enabled !== "function") { + return true; + } + return enabled(); + }); + } + + static async transformStandalone(content, callback, posthtmlProcessOptions = {}) { + let posthtmlInstance = this._getPosthtmlInstance([ + { + fn: callback, + enabled: () => true, + }, + ]); + let result = await posthtmlInstance.process(content, posthtmlProcessOptions); + return result.html; + } + + async transformContent(outputPath, content, context) { + let extension = FilePathUtil.getFileExtension(outputPath); + if (!this.isTransformable(extension, context)) { + return content; + } + + let bench = this.aggregateBench.get(`Transforming \`${extension}\` with posthtml`); + bench.before(); + let callbacks = this.getCallbacks(extension, context); + let plugins = this.getPlugins(extension); + let posthtmlInstance = HtmlTransformer._getPosthtmlInstance(callbacks, plugins, context); + let result = await posthtmlInstance.process(content, this.posthtmlProcessOptions); + bench.after(); + return result.html; + } +} + +export { HtmlTransformer }; diff --git a/node_modules/@11ty/eleventy/src/Util/ImportJsonSync.js b/node_modules/@11ty/eleventy/src/Util/ImportJsonSync.js new file mode 100644 index 0000000..fa59365 --- /dev/null +++ b/node_modules/@11ty/eleventy/src/Util/ImportJsonSync.js @@ -0,0 +1,77 @@ +import fs from "node:fs"; +import { createRequire } from "node:module"; +import debugUtil from "debug"; + +import { TemplatePath } from "@11ty/eleventy-utils"; + +import { normalizeFilePathInEleventyPackage } from "./Require.js"; + +const debug = debugUtil("Eleventy:ImportJsonSync"); +const require = createRequire(import.meta.url); + +function findFilePathInParentDirs(dir, filename) { + // `package.json` searches look in parent dirs: + // https://docs.npmjs.com/cli/v7/configuring-npm/folders#more-information + // Fixes issue #3178, limited to working dir paths only + let workingDir = TemplatePath.getWorkingDir(); + // TODO use DirContains + let allDirs = TemplatePath.getAllDirs(dir).filter((entry) => entry.startsWith(workingDir)); + + for (let dir of allDirs) { + let newPath = TemplatePath.join(dir, filename); + if (fs.existsSync(newPath)) { + debug("Found %o searching parent directories at: %o", filename, dir); + return newPath; + } + } +} + +function importJsonSync(filePath) { + if (!filePath || !filePath.endsWith(".json")) { + throw new Error(`importJsonSync expects a .json file extension (received: ${filePath})`); + } + + return require(filePath); +} + +function getEleventyPackageJson() { + let filePath = normalizeFilePathInEleventyPackage("package.json"); + return importJsonSync(filePath); +} + +function getModulePackageJson(dir) { + let filePath = findFilePathInParentDirs(TemplatePath.absolutePath(dir), "package.json"); + + // optional! + if (!filePath) { + return {}; + } + + return importJsonSync(filePath); +} + +// This will *not* find a package.json in a parent directory above root +function getWorkingProjectPackageJsonPath() { + let dir = TemplatePath.absolutePath(TemplatePath.getWorkingDir()); + return findFilePathInParentDirs(dir, "package.json"); +} + +function getWorkingProjectPackageJson() { + let filePath = getWorkingProjectPackageJsonPath(); + + // optional! + if (!filePath) { + return {}; + } + + return importJsonSync(filePath); +} + +export { + importJsonSync, + findFilePathInParentDirs, + getEleventyPackageJson, + getModulePackageJson, + getWorkingProjectPackageJson, + getWorkingProjectPackageJsonPath, +}; diff --git a/node_modules/@11ty/eleventy/src/Util/IsAsyncFunction.js b/node_modules/@11ty/eleventy/src/Util/IsAsyncFunction.js new file mode 100644 index 0000000..3c4dc65 --- /dev/null +++ b/node_modules/@11ty/eleventy/src/Util/IsAsyncFunction.js @@ -0,0 +1,5 @@ +const ComparisonAsyncFunction = (async () => {}).constructor; + +export default function isAsyncFunction(fn) { + return fn instanceof ComparisonAsyncFunction; +} diff --git a/node_modules/@11ty/eleventy/src/Util/JavaScriptDependencies.js b/node_modules/@11ty/eleventy/src/Util/JavaScriptDependencies.js new file mode 100644 index 0000000..7f6e809 --- /dev/null +++ b/node_modules/@11ty/eleventy/src/Util/JavaScriptDependencies.js @@ -0,0 +1,55 @@ +import dependencyTree from "@11ty/dependency-tree"; +import { find } from "@11ty/dependency-tree-esm"; +import { TemplatePath } from "@11ty/eleventy-utils"; + +import EleventyBaseError from "../Errors/EleventyBaseError.js"; + +class JavaScriptDependencies { + static getErrorMessage(file, type) { + return `A problem was encountered looking for JavaScript dependencies in ${type} file: ${file}. This only affects --watch and --serve behavior and does not affect your build.`; + } + + static async getDependencies(inputFiles, isProjectUsingEsm) { + let depSet = new Set(); + + // TODO does this need to work with aliasing? what other JS extensions will have deps? + let commonJsFiles = inputFiles.filter( + (file) => (!isProjectUsingEsm && file.endsWith(".js")) || file.endsWith(".cjs"), + ); + + for (let file of commonJsFiles) { + try { + let modules = dependencyTree(file, { + nodeModuleNames: "exclude", + allowNotFound: true, + }).map((dependency) => { + return TemplatePath.addLeadingDotSlash(TemplatePath.relativePath(dependency)); + }); + + for (let dep of modules) { + depSet.add(dep); + } + } catch (e) { + throw new EleventyBaseError(this.getErrorMessage(file, "CommonJS"), e); + } + } + + let esmFiles = inputFiles.filter( + (file) => (isProjectUsingEsm && file.endsWith(".js")) || file.endsWith(".mjs"), + ); + for (let file of esmFiles) { + try { + let modules = await find(file); + for (let dep of modules) { + depSet.add(dep); + } + } catch (e) { + throw new EleventyBaseError(this.getErrorMessage(file, "ESM"), e); + } + } + + return Array.from(depSet).sort(); + } +} + +export default JavaScriptDependencies; diff --git a/node_modules/@11ty/eleventy/src/Util/MemoizeFunction.js b/node_modules/@11ty/eleventy/src/Util/MemoizeFunction.js new file mode 100644 index 0000000..f66a155 --- /dev/null +++ b/node_modules/@11ty/eleventy/src/Util/MemoizeFunction.js @@ -0,0 +1,26 @@ +export default function (callback, options = {}) { + let { bench, name } = options; + let cache = new Map(); + + return (...args) => { + // Only supports single-arg functions for now. + if (args.filter(Boolean).length > 1) { + bench?.get(`(count) ${name} Not valid for memoize`).incrementCount(); + return callback(...args); + } + + let [cacheKey] = args; + + if (!cache.has(cacheKey)) { + cache.set(cacheKey, callback(...args)); + + bench?.get(`(count) ${name} memoize miss`).incrementCount(); + + return cache.get(cacheKey); + } + + bench?.get(`(count) ${name} memoize hit`).incrementCount(); + + return cache.get(cacheKey); + }; +} diff --git a/node_modules/@11ty/eleventy/src/Util/Objects/DeepFreeze.js b/node_modules/@11ty/eleventy/src/Util/Objects/DeepFreeze.js new file mode 100644 index 0000000..88e2847 --- /dev/null +++ b/node_modules/@11ty/eleventy/src/Util/Objects/DeepFreeze.js @@ -0,0 +1,20 @@ +import { isPlainObject } from "@11ty/eleventy-utils"; + +// via https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze + +function DeepFreeze(obj, topLevelExceptions) { + for (let name of Reflect.ownKeys(obj)) { + if ((topLevelExceptions || []).find((key) => key === name)) { + continue; + } + + const value = obj[name]; + if (isPlainObject(value)) { + DeepFreeze(value); + } + } + + return Object.freeze(obj); +} + +export { DeepFreeze }; diff --git a/node_modules/@11ty/eleventy/src/Util/Objects/ObjectFilter.js b/node_modules/@11ty/eleventy/src/Util/Objects/ObjectFilter.js new file mode 100644 index 0000000..9ce8737 --- /dev/null +++ b/node_modules/@11ty/eleventy/src/Util/Objects/ObjectFilter.js @@ -0,0 +1,9 @@ +export default function objectFilter(obj, callback) { + let newObject = {}; + for (let [key, value] of Object.entries(obj || {})) { + if (callback(value, key)) { + newObject[key] = value; + } + } + return newObject; +} diff --git a/node_modules/@11ty/eleventy/src/Util/Objects/ProxyWrap.js b/node_modules/@11ty/eleventy/src/Util/Objects/ProxyWrap.js new file mode 100644 index 0000000..38730fd --- /dev/null +++ b/node_modules/@11ty/eleventy/src/Util/Objects/ProxyWrap.js @@ -0,0 +1,118 @@ +import types from "node:util/types"; +import debugUtil from "debug"; +import { isPlainObject } from "@11ty/eleventy-utils"; + +const debug = debugUtil("Dev:Eleventy:Proxy"); + +function wrapObject(target, fallback) { + if (Object.isFrozen(target)) { + return target; + } + + return new Proxy(target, { + getOwnPropertyDescriptor(target, prop) { + let ret; + + if (Reflect.has(target, prop)) { + ret = Reflect.getOwnPropertyDescriptor(target, prop); + } else if (Reflect.has(fallback, prop)) { + ret = Reflect.getOwnPropertyDescriptor(fallback, prop); + } + + return ret; + }, + has(target, prop) { + if (Reflect.has(target, prop)) { + return true; + } + + return Reflect.has(fallback, prop); + }, + ownKeys(target) { + let s = new Set(); + // The fallback keys need to come first to preserve proper key order + // https://github.com/11ty/eleventy/issues/3849 + if (isPlainObject(fallback)) { + for (let k of Reflect.ownKeys(fallback)) { + s.add(k); + } + } + for (let k of Reflect.ownKeys(target)) { + if (!s.has(k)) { + s.add(k); + } + } + return Array.from(s); + }, + get(target, prop) { + debug("handler:get", prop); + + let value = Reflect.get(target, prop); + + if (Reflect.has(target, prop)) { + // Already proxied + if (types.isProxy(value)) { + return value; + } + + if (isPlainObject(value) && Reflect.has(fallback, prop)) { + if (Object.isFrozen(value)) { + return value; + } + + let ret = wrapObject(value, Reflect.get(fallback, prop)); + debug("handler:get (primary, object)", prop); + return ret; + } + + debug("handler:get (primary)", prop); + return value; + } + + // Does not exist in primary + if ( + (typeof fallback === "object" || typeof fallback === "function") && + Reflect.has(fallback, prop) + ) { + // fallback has prop + let fallbackValue = Reflect.get(fallback, prop); + + if (isPlainObject(fallbackValue)) { + if (Object.isFrozen(fallbackValue)) { + return fallbackValue; + } + + debug("handler:get (fallback, object)", prop); + // set empty object on primary + let emptyObject = {}; + Reflect.set(target, prop, emptyObject); + + return wrapObject(emptyObject, fallbackValue); + } + + debug("handler:get (fallback)", prop); + return fallbackValue; + } + + // primary *and* fallback do _not_ have prop + debug("handler:get (not on primary or fallback)", prop); + + return value; + }, + set(target, prop, value) { + debug("handler:set", prop); + + return Reflect.set(target, prop, value); + }, + }); +} + +function ProxyWrap(target, fallback) { + if (!isPlainObject(target) || !isPlainObject(fallback)) { + throw new Error("ProxyWrap expects objects for both the target and fallback"); + } + + return wrapObject(target, fallback); +} + +export { ProxyWrap }; diff --git a/node_modules/@11ty/eleventy/src/Util/Objects/SampleModule.mjs b/node_modules/@11ty/eleventy/src/Util/Objects/SampleModule.mjs new file mode 100644 index 0000000..ff8b4c5 --- /dev/null +++ b/node_modules/@11ty/eleventy/src/Util/Objects/SampleModule.mjs @@ -0,0 +1 @@ +export default {}; diff --git a/node_modules/@11ty/eleventy/src/Util/Objects/Sortable.js b/node_modules/@11ty/eleventy/src/Util/Objects/Sortable.js new file mode 100644 index 0000000..a23d4c9 --- /dev/null +++ b/node_modules/@11ty/eleventy/src/Util/Objects/Sortable.js @@ -0,0 +1,136 @@ +class Sortable { + constructor() { + this.isSortAscending = true; + this.isSortNumeric = false; + this.items = []; + this._dirty = true; + + this.sortFunctionStringMap = { + "A-Z": "sortFunctionAscending", + "Z-A": "sortFunctionDescending", + "0-9": "sortFunctionNumericAscending", + "9-0": "sortFunctionNumericDescending", + }; + } + + get length() { + return this.items.length; + } + + add(item) { + this._dirty = true; + this.items.push(item); + } + + sort(sortFunction) { + if (!sortFunction) { + sortFunction = this.getSortFunction(); + } else if (typeof sortFunction === "string") { + let key = sortFunction; + let name; + if (key in this.sortFunctionStringMap) { + name = this.sortFunctionStringMap[key]; + } + if (Sortable[name]) { + sortFunction = Sortable[name]; + } else { + throw new Error( + `Invalid String argument for sort(). Received \`${key}\`. Valid values: ${Object.keys( + this.sortFunctionStringMap, + )}`, + ); + } + } + + return this.items.slice().sort(sortFunction); + } + + sortAscending() { + return this.sort(this.getSortFunctionAscending()); + } + + sortDescending() { + return this.sort(this.getSortFunctionDescending()); + } + + setSortDescending(isDescending = true) { + this.isSortAscending = !isDescending; + } + + setSortAscending(isAscending = true) { + this.isSortAscending = isAscending; + } + + setSortNumeric(isNumeric) { + this.isSortNumeric = isNumeric; + } + + /* Sort functions */ + static sortFunctionNumericAscending(a, b) { + return a - b; + } + + static sortFunctionNumericDescending(a, b) { + return b - a; + } + + static sortFunctionAscending(a, b) { + if (a > b) { + return 1; + } else if (a < b) { + return -1; + } + return 0; + } + + static sortFunctionDescending(a, b) { + return Sortable.sortFunctionAscending(b, a); + } + + static sortFunctionAlphabeticAscending(a, b) { + return Sortable.sortFunctionAscending(a, b); + } + + static sortFunctionAlphabeticDescending(a, b) { + return Sortable.sortFunctionAscending(b, a); + } + + static sortFunctionDate(mapA, mapB) { + return Sortable.sortFunctionNumericAscending(mapA.date.getTime(), mapB.date.getTime()); + } + + static sortFunctionDateInputPath(mapA, mapB) { + let sortDate = Sortable.sortFunctionNumericAscending(mapA.date.getTime(), mapB.date.getTime()); + if (sortDate === 0) { + return Sortable.sortFunctionAlphabeticAscending(mapA.inputPath, mapB.inputPath); + } + return sortDate; + } + /* End sort functions */ + + getSortFunction() { + if (this.isSortAscending) { + return this.getSortFunctionAscending(); + } else { + return this.getSortFunctionDescending(); + } + } + + getSortFunctionAscending() { + if (this.isSortNumeric) { + return Sortable.sortFunctionNumericAscending; + } else { + return Sortable.sortFunctionAlphabeticAscending; + } + } + + getSortFunctionDescending() { + if (this.isSortNumeric) { + return Sortable.sortFunctionNumericDescending; + } else { + return Sortable.sortFunctionAlphabeticDescending; + } + } +} + +export default Sortable; diff --git a/node_modules/@11ty/eleventy/src/Util/Objects/Unique.js b/node_modules/@11ty/eleventy/src/Util/Objects/Unique.js new file mode 100644 index 0000000..8570c0c --- /dev/null +++ b/node_modules/@11ty/eleventy/src/Util/Objects/Unique.js @@ -0,0 +1,3 @@ +export default function Unique(arr) { + return Array.from(new Set(arr)); +} diff --git a/node_modules/@11ty/eleventy/src/Util/PassthroughCopyBehaviorCheck.js b/node_modules/@11ty/eleventy/src/Util/PassthroughCopyBehaviorCheck.js new file mode 100644 index 0000000..3dc1abb --- /dev/null +++ b/node_modules/@11ty/eleventy/src/Util/PassthroughCopyBehaviorCheck.js @@ -0,0 +1,16 @@ +function isUsingEleventyDevServer(config) { + return ( + !config.serverOptions.module || config.serverOptions.module === "@11ty/eleventy-dev-server" + ); +} + +// Config opt-out via serverPassthroughCopyBehavior +// False when other server is used +// False when runMode is "build" or "watch" +export default function (config, runMode) { + return ( + config.serverPassthroughCopyBehavior === "passthrough" && + isUsingEleventyDevServer(config) && + runMode === "serve" + ); +} diff --git a/node_modules/@11ty/eleventy/src/Util/PathNormalizer.js b/node_modules/@11ty/eleventy/src/Util/PathNormalizer.js new file mode 100644 index 0000000..cdc3253 --- /dev/null +++ b/node_modules/@11ty/eleventy/src/Util/PathNormalizer.js @@ -0,0 +1,58 @@ +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { TemplatePath } from "@11ty/eleventy-utils"; + +export default class PathNormalizer { + static getParts(inputPath) { + if (!inputPath) { + return []; + } + + let separator = "/"; + if (inputPath.includes(path.sep)) { + separator = path.sep; + } + + return inputPath.split(separator).filter((entry) => entry !== "."); + } + + // order is important here: the top-most directory returns first + // array of file and all parent directories + static getAllPaths(inputPath) { + let parts = this.getParts(inputPath); + let allPaths = []; + + let fullpath = ""; + for (let part of parts) { + fullpath += (fullpath.length > 0 ? "/" : "") + part; + allPaths.push(fullpath); + } + + return allPaths; + } + + static normalizeSeperator(inputPath) { + if (!inputPath) { + return inputPath; + } + return inputPath.split(path.sep).join("/"); + } + + static fullNormalization(inputPath) { + if (typeof inputPath !== "string") { + return inputPath; + } + + // Fix file:///Users/ or file:///C:/ paths passed in + if (inputPath.startsWith("file://")) { + inputPath = fileURLToPath(inputPath); + } + + // Paths should not be absolute (we convert absolute paths to relative) + // Paths should not have a leading dot slash + // Paths should always be `/` independent of OS path separator + return TemplatePath.stripLeadingDotSlash( + this.normalizeSeperator(TemplatePath.relativePath(inputPath)), + ); + } +} diff --git a/node_modules/@11ty/eleventy/src/Util/PathPrefixer.js b/node_modules/@11ty/eleventy/src/Util/PathPrefixer.js new file mode 100644 index 0000000..abd5582 --- /dev/null +++ b/node_modules/@11ty/eleventy/src/Util/PathPrefixer.js @@ -0,0 +1,21 @@ +import path from "node:path"; + +import PathNormalizer from "./PathNormalizer.js"; + +class PathPrefixer { + static normalizePathPrefix(pathPrefix) { + if (pathPrefix) { + // add leading / (for browsersync), see #1454 + // path.join uses \\ for Windows so we split and rejoin + return PathPrefixer.joinUrlParts("/", pathPrefix); + } + + return "/"; + } + + static joinUrlParts(...parts) { + return PathNormalizer.normalizeSeperator(path.join(...parts)); + } +} + +export default PathPrefixer; diff --git a/node_modules/@11ty/eleventy/src/Util/Pluralize.js b/node_modules/@11ty/eleventy/src/Util/Pluralize.js new file mode 100644 index 0000000..d35f1dd --- /dev/null +++ b/node_modules/@11ty/eleventy/src/Util/Pluralize.js @@ -0,0 +1,3 @@ +export default function (count, singleWord, pluralWord) { + return count === 1 ? singleWord : pluralWord; +} diff --git a/node_modules/@11ty/eleventy/src/Util/ProjectDirectories.js b/node_modules/@11ty/eleventy/src/Util/ProjectDirectories.js new file mode 100644 index 0000000..e15e985 --- /dev/null +++ b/node_modules/@11ty/eleventy/src/Util/ProjectDirectories.js @@ -0,0 +1,369 @@ +import fs from "node:fs"; +import path from "node:path"; +import { TemplatePath } from "@11ty/eleventy-utils"; +import { isDynamicPattern } from "tinyglobby"; + +import DirContains from "./DirContains.js"; + +/* Directories internally should always use *nix forward slashes */ +class ProjectDirectories { + static defaults = { + input: "./", + data: "./_data/", // Relative to input directory + includes: "./_includes/", // Relative to input directory + layouts: "./_layouts/", // Relative to input directory + output: "./_site/", + }; + + // no updates allowed, input/output set via CLI + #frozen = false; + + #raw = {}; + + #dirs = {}; + + inputFile = undefined; + inputGlob = undefined; + + // Add leading dot slash + // Use forward slashes + static normalizePath(fileOrDir) { + return TemplatePath.standardizeFilePath(fileOrDir); + } + + // Must be a directory + // Always include a trailing slash + static normalizeDirectory(dir) { + return this.addTrailingSlash(this.normalizePath(dir)); + } + + normalizeDirectoryPathRelativeToInputDirectory(filePath) { + return ProjectDirectories.normalizeDirectory(path.join(this.input, filePath)); + } + + static addTrailingSlash(path) { + if (path.slice(-1) === "/") { + return path; + } + return path + "/"; + } + + // If input/output are set via CLI, they take precedence over all other configuration values. + freeze() { + this.#frozen = true; + } + + setViaConfigObject(configDirs = {}) { + // input must come last + let inputChanged = false; + if ( + configDirs.input && + ProjectDirectories.normalizeDirectory(configDirs.input) !== this.input + ) { + this.#setInputRaw(configDirs.input); + inputChanged = true; + } + + // If falsy or an empty string, the current directory is used. + if (configDirs.output !== undefined) { + if (ProjectDirectories.normalizeDirectory(configDirs.output) !== this.output) { + this.setOutput(configDirs.output); + } + } + + // Input relative directory, if falsy or an empty string, inputDir is used! + // Always set if input changed, e.g. input is `src` and data is `../_data` (resulting in `./_data`) we still want to set data to this new value + if (configDirs.data !== undefined) { + if ( + inputChanged || + this.normalizeDirectoryPathRelativeToInputDirectory(configDirs.data || "") !== this.data + ) { + this.setData(configDirs.data); + } + } + + // Input relative directory, if falsy or an empty string, inputDir is used! + if (configDirs.includes !== undefined) { + if ( + inputChanged || + this.normalizeDirectoryPathRelativeToInputDirectory(configDirs.includes || "") !== + this.includes + ) { + this.setIncludes(configDirs.includes); + } + } + + // Input relative directory, if falsy or an empty string, inputDir is used! + if (configDirs.layouts !== undefined) { + if ( + inputChanged || + this.normalizeDirectoryPathRelativeToInputDirectory(configDirs.layouts || "") !== + this.layouts + ) { + this.setLayouts(configDirs.layouts); + } + } + + if (inputChanged) { + this.updateInputDependencies(); + } + } + + updateInputDependencies() { + // raw first, fall back to Eleventy defaults if not yet set + this.setData(this.#raw.data ?? ProjectDirectories.defaults.data); + this.setIncludes(this.#raw.includes ?? ProjectDirectories.defaults.includes); + + // Should not include this if not explicitly opted-in + if (this.#raw.layouts !== undefined) { + this.setLayouts(this.#raw.layouts ?? ProjectDirectories.defaults.layouts); + } + } + + /* Relative to project root, must exist */ + #setInputRaw(dirOrFile, inputDir = undefined) { + // is frozen and was defined previously + if (this.#frozen && this.#raw.input !== undefined) { + return; + } + + this.#raw.input = dirOrFile; + + if (!dirOrFile) { + // input must exist if inputDir is not set. + return; + } + + // Normalize absolute paths to relative, #3805 + // if(path.isAbsolute(dirOrFile)) { + // dirOrFile = path.relative(".", dirOrFile); + // } + + // Input has to exist (assumed glob if it does not exist) + let inputExists = fs.existsSync(dirOrFile); + let inputExistsAndIsDirectory = inputExists && fs.statSync(dirOrFile).isDirectory(); + + if (inputExistsAndIsDirectory) { + // is not a file or glob + this.#dirs.input = ProjectDirectories.normalizeDirectory(dirOrFile); + } else { + if (inputExists) { + this.inputFile = ProjectDirectories.normalizePath(dirOrFile); + } else { + if (!isDynamicPattern(dirOrFile)) { + throw new Error( + `The "${dirOrFile}" \`input\` parameter (directory or file path) must exist on the file system (unless detected as a glob by the \`tinyglobby\` package)`, + ); + } + + this.inputGlob = dirOrFile; + } + + // Explicit Eleventy option for inputDir + if (inputDir) { + // Changed in 3.0: must exist + if (!fs.existsSync(inputDir)) { + throw new Error("Directory must exist (via inputDir option to Eleventy constructor)."); + } + + this.#dirs.input = ProjectDirectories.normalizeDirectory(inputDir); + } else { + // the input directory is implied to be the parent directory of the + // file, unless inputDir is explicitly specified (via Eleventy constructor `options`) + this.#dirs.input = ProjectDirectories.normalizeDirectory( + TemplatePath.getDirFromFilePath(dirOrFile), // works with globs + ); + } + } + } + + setInput(dirOrFile, inputDir = undefined) { + this.#setInputRaw(dirOrFile, inputDir); // does not update + this.updateInputDependencies(); + } + + /* Relative to input dir */ + setIncludes(dir) { + if (dir !== undefined) { + // falsy or an empty string is valid (falls back to input dir) + this.#raw.includes = dir; + this.#dirs.includes = ProjectDirectories.normalizeDirectory( + TemplatePath.join(this.input, dir || ""), + ); + } + } + + /* Relative to input dir */ + /* Optional */ + setLayouts(dir) { + if (dir !== undefined) { + // falsy or an empty string is valid (falls back to input dir) + this.#raw.layouts = dir; + this.#dirs.layouts = ProjectDirectories.normalizeDirectory( + TemplatePath.join(this.input, dir || ""), + ); + } + } + + /* Relative to input dir */ + setData(dir) { + if (dir !== undefined) { + // falsy or an empty string is valid (falls back to input dir) + // TODO must exist if specified + this.#raw.data = dir; + this.#dirs.data = ProjectDirectories.normalizeDirectory( + TemplatePath.join(this.input, dir || ""), + ); + } + } + + /* Relative to project root */ + setOutput(dir) { + // is frozen and was defined previously + if (this.#frozen && this.#raw.output !== undefined) { + return; + } + + if (dir !== undefined) { + this.#raw.output = dir; + this.#dirs.output = ProjectDirectories.normalizeDirectory(dir || ""); + } + } + + get input() { + return this.#dirs.input || ProjectDirectories.defaults.input; + } + + get data() { + return this.#dirs.data || ProjectDirectories.defaults.data; + } + + get includes() { + return this.#dirs.includes || ProjectDirectories.defaults.includes; + } + + get layouts() { + // explicit opt-in, no fallback. + return this.#dirs.layouts; + } + + get output() { + return this.#dirs.output || ProjectDirectories.defaults.output; + } + + isTemplateFile(filePath) { + let inputPath = this.getInputPath(filePath); + // TODO use DirContains + if (this.layouts && inputPath.startsWith(this.layouts)) { + return false; + } + + // if this.includes is "" (and thus is the same directory as this.input) + // we don’t actually know if this is a template file, so defer + if (this.includes && this.includes !== this.input) { + if (inputPath.startsWith(this.includes)) { + return false; + } + } + + // TODO use DirContains + return inputPath.startsWith(this.input); + } + + // for a hypothetical template file + getInputPath(filePathRelativeToInputDir) { + // TODO change ~/ to project root dir + return TemplatePath.addLeadingDotSlash( + TemplatePath.join(this.input, TemplatePath.standardizeFilePath(filePathRelativeToInputDir)), + ); + } + + // Inverse of getInputPath + // Removes input dir from path + getInputPathRelativeToInputDirectory(filePathRelativeToInputDir) { + let inputDir = TemplatePath.addLeadingDotSlash(TemplatePath.join(this.input)); + + // No leading dot slash + return TemplatePath.stripLeadingSubPath(filePathRelativeToInputDir, inputDir); + } + + // for a hypothetical Eleventy layout file + getLayoutPath(filePathRelativeToLayoutDir) { + return TemplatePath.addLeadingDotSlash( + TemplatePath.join( + this.layouts || this.includes, + TemplatePath.standardizeFilePath(filePathRelativeToLayoutDir), + ), + ); + } + + // Removes layout dir from path + getLayoutPathRelativeToInputDirectory(filePathRelativeToLayoutDir) { + let layoutPath = this.getLayoutPath(filePathRelativeToLayoutDir); + let inputDir = TemplatePath.addLeadingDotSlash(TemplatePath.join(this.input)); + + // No leading dot slash + return TemplatePath.stripLeadingSubPath(layoutPath, inputDir); + } + + getProjectPath(filePath) { + return TemplatePath.addLeadingDotSlash( + TemplatePath.join(".", TemplatePath.standardizeFilePath(filePath)), + ); + } + + isFileInProjectFolder(filePath) { + return DirContains(TemplatePath.getWorkingDir(), filePath); + } + + isFileInOutputFolder(filePath) { + return DirContains(this.output, filePath); + } + + static getRelativeTo(targetPath, cwd) { + return path.relative(cwd, path.join(path.resolve("."), targetPath)); + } + + // Access the data without being able to set the data. + getUserspaceInstance() { + let d = this; + + return { + get input() { + return d.input; + }, + get inputFile() { + return d.inputFile; + }, + get inputGlob() { + return d.inputGlob; + }, + get data() { + return d.data; + }, + get includes() { + return d.includes; + }, + get layouts() { + return d.layouts; + }, + get output() { + return d.output; + }, + }; + } + + toString() { + return { + input: this.input, + inputFile: this.inputFile, + inputGlob: this.inputGlob, + data: this.data, + includes: this.includes, + layouts: this.layouts, + output: this.output, + }; + } +} + +export default ProjectDirectories; diff --git a/node_modules/@11ty/eleventy/src/Util/ProjectTemplateFormats.js b/node_modules/@11ty/eleventy/src/Util/ProjectTemplateFormats.js new file mode 100644 index 0000000..f37040e --- /dev/null +++ b/node_modules/@11ty/eleventy/src/Util/ProjectTemplateFormats.js @@ -0,0 +1,134 @@ +import debugUtil from "debug"; +const debug = debugUtil("Eleventy:Util:ProjectTemplateFormats"); + +class ProjectTemplateFormats { + #useAll = {}; + #raw = {}; + + #values = {}; // Set objects + + static union(...sets) { + let s = new Set(); + + for (let set of sets) { + if (!set || typeof set[Symbol.iterator] !== "function") { + continue; + } + for (let v of set) { + s.add(v); + } + } + + return s; + } + + #normalize(formats) { + if (Array.isArray(formats)) { + formats = "" + formats.join(","); + } + + if (typeof formats !== "string") { + throw new Error( + `Invalid formats (expect String, Array) passed to ProjectTemplateFormats->normalize: ${formats}`, + ); + } + + let final = new Set(); + for (let format of formats.split(",")) { + format = format.trim(); + if (format && format !== "*") { + final.add(format); + } + } + + return final; + } + + isWildcard() { + return this.#useAll.cli || this.#useAll.config || false; + } + + /** @returns {boolean} */ + #isUseAll(rawFormats) { + if (rawFormats === "") { + return false; + } + + if (typeof rawFormats === "string") { + rawFormats = rawFormats.split(","); + } + + if (Array.isArray(rawFormats)) { + return rawFormats.find((entry) => entry === "*") !== undefined; + } + + return false; + } + + // 3.x Breaking: "" now means no formats. In 2.x and prior it meant "*" + setViaCommandLine(formats) { + if (formats === undefined) { + return; + } + + this.#useAll.cli = this.#isUseAll(formats); + this.#raw.cli = formats; + this.#values.cli = this.#normalize(formats); + } + + // 3.x Breaking: "" now means no formats—in 2.x and prior it meant "*" + // 3.x Adds support for comma separated string—in 2.x this required an Array + setViaConfig(formats) { + if (formats === undefined) { + return; + } + + // "*" is supported + this.#useAll.config = this.#isUseAll(formats); + this.#raw.config = formats; + this.#values.config = this.#normalize(formats); + } + + addViaConfig(formats) { + if (!formats) { + return; + } + + if (this.#isUseAll(formats)) { + throw new Error( + `\`addTemplateFormats("*")\` is not supported for project template syntaxes.`, + ); + } + + // "*" not supported here + this.#raw.configAdd = formats; + this.#values.configAdd = this.#normalize(formats); + } + + getAllTemplateFormats() { + return Array.from(ProjectTemplateFormats.union(this.#values.config, this.#values.configAdd)); + } + + getTemplateFormats() { + if (this.#useAll.cli) { + let v = this.getAllTemplateFormats(); + debug("Using CLI --formats='*': %o", v); + return v; + } + + if (this.#raw.cli !== undefined) { + let v = Array.from(this.#values.cli); + debug("Using CLI --formats: %o", v); + return v; + } + + let v = this.getAllTemplateFormats(); + debug( + "Using configuration `templateFormats`, `setTemplateFormats()`, `addTemplateFormats()`: %o", + v, + ); + return v; + } +} + +export default ProjectTemplateFormats; diff --git a/node_modules/@11ty/eleventy/src/Util/PromiseUtil.js b/node_modules/@11ty/eleventy/src/Util/PromiseUtil.js new file mode 100644 index 0000000..fa88da0 --- /dev/null +++ b/node_modules/@11ty/eleventy/src/Util/PromiseUtil.js @@ -0,0 +1,15 @@ +function withResolvers() { + if ("withResolvers" in Promise) { + return Promise.withResolvers(); + } + + let resolve; + let reject; + let promise = new Promise((res, rej) => { + resolve = res; + reject = rej; + }); + return { promise, resolve, reject }; +} + +export { withResolvers }; diff --git a/node_modules/@11ty/eleventy/src/Util/Require.js b/node_modules/@11ty/eleventy/src/Util/Require.js new file mode 100644 index 0000000..5f6412d --- /dev/null +++ b/node_modules/@11ty/eleventy/src/Util/Require.js @@ -0,0 +1,258 @@ +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import module from "node:module"; +import { MessageChannel } from "node:worker_threads"; + +import { TemplatePath } from "@11ty/eleventy-utils"; + +import EleventyBaseError from "../Errors/EleventyBaseError.js"; +import eventBus from "../EventBus.js"; + +class EleventyImportError extends EleventyBaseError {} + +const { port1, port2 } = new MessageChannel(); + +// ESM Cache Buster is an enhancement that works in Node 18.19+ +// https://nodejs.org/docs/latest/api/module.html#moduleregisterspecifier-parenturl-options +// Fixes https://github.com/11ty/eleventy/issues/3270 +// ENV variable for https://github.com/11ty/eleventy/issues/3371 +if ("register" in module && !process?.env?.ELEVENTY_SKIP_ESM_RESOLVER) { + module.register("./EsmResolver.js", import.meta.url, { + parentURL: import.meta.url, + data: { + port: port2, + }, + transferList: [port2], + }); +} + +// important to clear the require.cache in CJS projects +const require = module.createRequire(import.meta.url); + +const requestPromiseCache = new Map(); + +function getImportErrorMessage(filePath, type) { + return `There was a problem importing '${path.relative(".", filePath)}' via ${type}`; +} + +// Used for JSON imports, suffering from Node warning that import assertions experimental but also +// throwing an error if you try to import() a JSON file without an import assertion. +/** + * + * @returns {string|undefined} + */ +function loadContents(path, options = {}) { + let rawInput; + /** @type {string} */ + let encoding = "utf8"; // JSON is utf8 + if (options?.encoding || options?.encoding === null) { + encoding = options.encoding; + } + + try { + // @ts-expect-error This is an error in the upstream types + rawInput = fs.readFileSync(path, encoding); + } catch (error) { + // @ts-expect-error Temporary + if (error?.code === "ENOENT") { + // if file does not exist, return nothing + return; + } + + throw error; + } + + // Can return a buffer, string, etc + if (typeof rawInput === "string") { + rawInput = rawInput.trim(); + } + + return rawInput; +} + +let lastModifiedPaths = new Map(); +eventBus.on("eleventy.importCacheReset", (fileQueue) => { + for (let filePath of fileQueue) { + let absolutePath = TemplatePath.absolutePath(filePath); + let newDate = Date.now(); + lastModifiedPaths.set(absolutePath, newDate); + + // post to EsmResolver worker thread + if (port1) { + port1.postMessage({ path: absolutePath, newDate }); + } + + // ESM Eleventy when using `import()` on a CJS project file still adds to require.cache + if (absolutePath in (require?.cache || {})) { + delete require.cache[absolutePath]; + } + } +}); + +// raw means we don’t normalize away the `default` export +async function dynamicImportAbsolutePath(absolutePath, options = {}) { + let { type, returnRaw, cacheBust } = Object.assign( + { + type: undefined, + returnRaw: false, + cacheBust: false, // force cache bust + }, + options, + ); + + // Short circuit for JSON files (that are optional and can be empty) + if (absolutePath.endsWith(".json") || type === "json") { + try { + // https://v8.dev/features/import-assertions#dynamic-import() is still experimental in Node 20 + let rawInput = loadContents(absolutePath); + if (!rawInput) { + // should not error when file exists but is _empty_ + return; + } + return JSON.parse(rawInput); + } catch (e) { + return Promise.reject( + new EleventyImportError(getImportErrorMessage(absolutePath, "fs.readFile(json)"), e), + ); + } + } + + // Removed a `require` short circuit from this piece originally added + // in https://github.com/11ty/eleventy/pull/3493 Was a bit faster but + // error messaging was worse for require(esm) + + let urlPath; + try { + let u = new URL(`file:${absolutePath}`); + + // Bust the import cache if this is the last modified file (or cache busting is forced) + if (cacheBust) { + lastModifiedPaths.set(absolutePath, Date.now()); + } + + if (cacheBust || lastModifiedPaths.has(absolutePath)) { + u.searchParams.set("_cache_bust", lastModifiedPaths.get(absolutePath)); + } + + urlPath = u.toString(); + } catch (e) { + urlPath = absolutePath; + } + + let promise; + if (requestPromiseCache.has(urlPath)) { + promise = requestPromiseCache.get(urlPath); + } else { + promise = import(urlPath); + requestPromiseCache.set(urlPath, promise); + } + + return promise.then( + (target) => { + if (returnRaw) { + return target; + } + + // If the only export is `default`, elevate to top (for ESM and CJS) + if (Object.keys(target).length === 1 && "default" in target) { + return target.default; + } + + // When using import() on a CommonJS file that exports an object sometimes it + // returns duplicated values in `default` key, e.g. `{ default: {key: value}, key: value }` + + // A few examples: + // module.exports = { key: false }; + // returns `{ default: {key: false}, key: false }` as not expected. + // module.exports = { key: true }; + // module.exports = { key: null }; + // module.exports = { key: undefined }; + // module.exports = { key: class {} }; + + // A few examples where it does not duplicate: + // module.exports = { key: 1 }; + // returns `{ default: {key: 1} }` as expected. + // module.exports = { key: "value" }; + // module.exports = { key: {} }; + // module.exports = { key: [] }; + + if (type === "cjs" && "default" in target) { + let match = true; + for (let key in target) { + if (key === "default") { + continue; + } + if (key === "module.exports") { + continue; + } + if (target[key] !== target.default[key]) { + match = false; + } + } + + if (match) { + return target.default; + } + } + + // Otherwise return { default: value, named: value } + // Object.assign here so we can add things to it in JavaScript.js + return Object.assign({}, target); + }, + (error) => { + return Promise.reject( + new EleventyImportError(getImportErrorMessage(absolutePath, `import(${type})`), error), + ); + }, + ); +} + +function normalizeFilePathInEleventyPackage(file) { + // Back up relative paths from ./src/Util/Require.js + return path.resolve(fileURLToPath(import.meta.url), "../../../", file); +} + +async function dynamicImportFromEleventyPackage(file) { + // points to files relative to the top level Eleventy directory + let filePath = normalizeFilePathInEleventyPackage(file); + + // Returns promise + return dynamicImportAbsolutePath(filePath, { type: "esm" }); +} + +async function dynamicImport(localPath, type, options = {}) { + let absolutePath = TemplatePath.absolutePath(localPath); + options.type = type; + + // Returns promise + return dynamicImportAbsolutePath(absolutePath, options); +} + +/* Used to import default Eleventy configuration file, raw means we don’t normalize away the `default` export */ +async function dynamicImportRawFromEleventyPackage(file) { + // points to files relative to the top level Eleventy directory + let filePath = normalizeFilePathInEleventyPackage(file); + + // Returns promise + return dynamicImportAbsolutePath(filePath, { type: "esm", returnRaw: true }); +} + +/* Used to import app configuration files, raw means we don’t normalize away the `default` export */ +async function dynamicImportRaw(localPath, type) { + let absolutePath = TemplatePath.absolutePath(localPath); + + // Returns promise + return dynamicImportAbsolutePath(absolutePath, { type, returnRaw: true }); +} + +export { + loadContents as EleventyLoadContent, + dynamicImport as EleventyImport, + dynamicImportRaw as EleventyImportRaw, + normalizeFilePathInEleventyPackage, + + // no longer used in core + dynamicImportFromEleventyPackage as EleventyImportFromEleventy, + dynamicImportRawFromEleventyPackage as EleventyImportRawFromEleventy, +}; diff --git a/node_modules/@11ty/eleventy/src/Util/ReservedData.js b/node_modules/@11ty/eleventy/src/Util/ReservedData.js new file mode 100644 index 0000000..d726c73 --- /dev/null +++ b/node_modules/@11ty/eleventy/src/Util/ReservedData.js @@ -0,0 +1,69 @@ +class EleventyReservedDataError extends TypeError {} + +class ReservedData { + static properties = [ + // "pkg", // Object.freeze’d upstream + // "eleventy", // Object.freeze’d upstream + // "page" is only frozen for specific subproperties below + "content", + "collections", + ]; + + static pageProperties = [ + "date", + "inputPath", + "fileSlug", + "filePathStem", + "outputFileExtension", + "templateSyntax", + "url", + "outputPath", + // not yet `excerpt` or `lang` set via front matter and computed data + ]; + + // Check in the data cascade for reserved data properties. + static getReservedKeys(data) { + if (!data) { + return []; + } + + let keys = this.properties.filter((key) => { + return key in data; + }); + + if ("page" in data) { + if (typeof data.page === "object") { + for (let key of this.pageProperties) { + if (key in data.page) { + keys.push(`page.${key}`); + } + } + } else { + // fail `page` when set to non-object values. + keys.push("page"); + } + } + return keys; + } + + static check(data) { + let reserved = ReservedData.getReservedKeys(data); + if (reserved.length === 0) { + return; + } + + let error = new EleventyReservedDataError( + `Cannot override reserved Eleventy properties: ${reserved.join(", ")}`, + ); + + error.reservedNames = reserved; + + throw error; + } + + static isReservedDataError(e) { + return e instanceof EleventyReservedDataError; + } +} + +export default ReservedData; diff --git a/node_modules/@11ty/eleventy/src/Util/SetUnion.js b/node_modules/@11ty/eleventy/src/Util/SetUnion.js new file mode 100644 index 0000000..50e6b9c --- /dev/null +++ b/node_modules/@11ty/eleventy/src/Util/SetUnion.js @@ -0,0 +1,11 @@ +function setUnion(...sets) { + let root = new Set(); + for (let set of sets) { + for (let entry of set) { + root.add(entry); + } + } + return root; +} + +export { setUnion }; diff --git a/node_modules/@11ty/eleventy/src/Util/SpawnAsync.js b/node_modules/@11ty/eleventy/src/Util/SpawnAsync.js new file mode 100644 index 0000000..5e6a20f --- /dev/null +++ b/node_modules/@11ty/eleventy/src/Util/SpawnAsync.js @@ -0,0 +1,29 @@ +import { spawn } from "node:child_process"; +import { withResolvers } from "./PromiseUtil.js"; + +export function spawnAsync(command, args, options) { + let { promise, resolve, reject } = withResolvers(); + + const cmd = spawn(command, args, options); + let res = []; + cmd.stdout.on("data", (data) => { + res.push(data.toString("utf8")); + }); + + let err = []; + cmd.stderr.on("data", (data) => { + err.push(data.toString("utf8")); + }); + + cmd.on("close", (code) => { + if (err.length > 0) { + reject(err.join("\n")); + } else if (code === 1) { + reject("Internal error: process closed with error exit code."); + } else { + resolve(res.join("\n")); + } + }); + + return promise; +} diff --git a/node_modules/@11ty/eleventy/src/Util/TemplateDepGraph.js b/node_modules/@11ty/eleventy/src/Util/TemplateDepGraph.js new file mode 100644 index 0000000..795453d --- /dev/null +++ b/node_modules/@11ty/eleventy/src/Util/TemplateDepGraph.js @@ -0,0 +1,160 @@ +import { DepGraph as DependencyGraph } from "dependency-graph"; +import debugUtil from "debug"; + +const debug = debugUtil("Eleventy:TemplateDepGraph"); + +const COLLECTION_PREFIX = "__collection:"; + +export class TemplateDepGraph extends DependencyGraph { + static STAGES = ["[basic]", "[userconfig]", "[keys]", "all"]; + + #configCollectionNames = new Set(); + + constructor() { + // BREAKING TODO move this back to non-circular with errors + super({ circular: true }); + + let previous; + // establish stage relationships, all uses keys, keys uses userconfig, userconfig uses tags + for (let stageName of TemplateDepGraph.STAGES.filter(Boolean).reverse()) { + let stageKey = `${COLLECTION_PREFIX}${stageName}`; + if (previous) { + this.uses(previous, stageKey); + } + previous = stageKey; + } + } + + uses(from, to) { + this.addDependency(from, to); + } + + addTag(tagName, type) { + if ( + tagName === "all" || + (tagName.startsWith("[") && tagName.endsWith("]")) || + this.#configCollectionNames.has(tagName) + ) { + return; + } + if (!type) { + throw new Error( + `Missing tag type for addTag. Expecting one of ${TemplateDepGraph.STAGES.map((entry) => entry.slice(1, -1)).join(" or ")}. Received: ${type}`, + ); + } + + debug("collection type %o uses tag %o", tagName, type); + + this.uses(`${COLLECTION_PREFIX}[${type}]`, `${COLLECTION_PREFIX}${tagName}`); + } + + addConfigCollectionName(collectionName) { + if (collectionName === "all") { + return; + } + + this.#configCollectionNames.add(collectionName); + // Collection relationships to `[userconfig]` are added last, in unfilteredOrder() + } + + cleanupCollectionNames(collectionNames = []) { + let s = new Set(collectionNames); + if (s.has("[userconfig]")) { + return collectionNames; + } + + let hasAnyConfigCollections = collectionNames.find((name) => { + if (this.#configCollectionNames.has(name)) { + return true; + } + return false; + }); + + if (hasAnyConfigCollections) { + s.add("[userconfig]"); + } + + return Array.from(s); + } + + addTemplate(filePath, consumes = [], publishesTo = []) { + // Move to the beginning if it doesn’t consume anything + if (consumes.length === 0) { + this.uses(`${COLLECTION_PREFIX}[basic]`, filePath); + } + + consumes = this.cleanupCollectionNames(consumes); + publishesTo = this.cleanupCollectionNames(publishesTo); + // Can’t consume AND publish to `all` simultaneously + let consumesAll = consumes.includes("all"); + if (consumesAll) { + publishesTo = publishesTo.filter((entry) => entry !== "all"); + } + + debug("%o consumes %o and publishes to %o", filePath, consumes, publishesTo); + + for (let collectionName of publishesTo) { + if (!consumesAll) { + let tagType = "basic"; + + let consumesUserConfigCollection = consumes.includes("[userconfig]"); + if (consumesUserConfigCollection) { + // must finish before [keys] + tagType = "keys"; + } + + this.addTag(collectionName, tagType); + } + + this.uses(`${COLLECTION_PREFIX}${collectionName}`, filePath); + } + + for (let collectionName of consumes) { + this.uses(filePath, `${COLLECTION_PREFIX}${collectionName}`); + + let stageIndex = TemplateDepGraph.STAGES.indexOf(collectionName); + let nextStage = stageIndex > 0 ? TemplateDepGraph.STAGES[stageIndex + 1] : undefined; + if (nextStage) { + this.uses(`${COLLECTION_PREFIX}${nextStage}`, filePath); + } + } + } + + addDependency(from, to) { + if (!this.hasNode(from)) { + this.addNode(from); + } + if (!this.hasNode(to)) { + this.addNode(to); + } + super.addDependency(from, to); + } + + unfilteredOrder() { + // these need to be added last, after the template map has been added (see addConfigCollectionName) + for (let collectionName of this.#configCollectionNames) { + this.uses(`${COLLECTION_PREFIX}[keys]`, `${COLLECTION_PREFIX}${collectionName}`); + } + + return super.overallOrder(); + } + + overallOrder() { + let unfiltered = this.unfilteredOrder(); + + let filtered = unfiltered.filter((entry) => { + if (entry === `${COLLECTION_PREFIX}[keys]`) { + return true; + } + return !entry.startsWith(`${COLLECTION_PREFIX}[`) && !entry.endsWith("]"); + }); + + let allKey = `${COLLECTION_PREFIX}all`; + // Add another collections.all entry to the end (if not already the last one) + if (filtered[filtered.length - 1] !== allKey) { + filtered.push(allKey); + } + + return filtered; + } +} diff --git a/node_modules/@11ty/eleventy/src/Util/TransformsUtil.js b/node_modules/@11ty/eleventy/src/Util/TransformsUtil.js new file mode 100644 index 0000000..3ba8ab0 --- /dev/null +++ b/node_modules/@11ty/eleventy/src/Util/TransformsUtil.js @@ -0,0 +1,70 @@ +import EleventyBaseError from "../Errors/EleventyBaseError.js"; +import { isPlainObject } from "@11ty/eleventy-utils"; +import debugUtil from "debug"; + +const debug = debugUtil("Eleventy:Transforms"); + +class EleventyTransformError extends EleventyBaseError {} + +class TransformsUtil { + static changeTransformsToArray(transformsObj) { + let transforms = []; + for (let name in transformsObj) { + transforms.push({ + name: name, + callback: transformsObj[name], + }); + } + return transforms; + } + + static async runAll(content, pageData, transforms = {}, options = {}) { + let { baseHrefOverride, logger } = options; + let { inputPath, outputPath, url } = pageData; + + if (!isPlainObject(transforms)) { + throw new Error("Object of transforms expected."); + } + + let transformsArray = this.changeTransformsToArray(transforms); + + for (let { callback, name } of transformsArray) { + debug("Running %o transform on %o: %o", name, inputPath, outputPath); + + try { + let hadContentBefore = !!content; + + content = await callback.call( + { + inputPath, + outputPath, + url, + page: pageData, + baseHref: baseHrefOverride, + }, + content, + outputPath, + ); + + if (hadContentBefore && !content) { + if (!logger || !logger.warn) { + throw new Error("Internal error: missing `logger` instance."); + } + + logger.warn( + `Warning: Transform \`${name}\` returned empty when writing ${outputPath} from ${inputPath}.`, + ); + } + } catch (e) { + throw new EleventyTransformError( + `Transform \`${name}\` encountered an error when transforming ${inputPath}.`, + e, + ); + } + } + + return content; + } +} + +export default TransformsUtil; diff --git a/node_modules/@11ty/eleventy/src/Util/ValidUrl.js b/node_modules/@11ty/eleventy/src/Util/ValidUrl.js new file mode 100644 index 0000000..9d0f59b --- /dev/null +++ b/node_modules/@11ty/eleventy/src/Util/ValidUrl.js @@ -0,0 +1,9 @@ +export default function isValidUrl(url) { + try { + new URL(url); + return true; + } catch (e) { + // invalid url OR local path + return false; + } +} |
