150 lines
3.2 KiB
JavaScript
150 lines
3.2 KiB
JavaScript
|
|
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 };
|