Files
beall-11ty/node_modules/@11ty/eleventy/src/Util/HtmlRelativeCopy.js
2026-03-31 16:38:22 -07:00

150 lines
3.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 files 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),
);
}
// dont 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 };