This commit is contained in:
2026-03-31 16:38:22 -07:00
commit 38940436a7
2112 changed files with 376929 additions and 0 deletions

View File

@ -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 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 };