first
This commit is contained in:
270
node_modules/@11ty/eleventy-fetch/src/AssetCache.js
generated
vendored
Normal file
270
node_modules/@11ty/eleventy-fetch/src/AssetCache.js
generated
vendored
Normal file
@ -0,0 +1,270 @@
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
const { DateCompare, createHashHexSync } = require("@11ty/eleventy-utils");
|
||||
|
||||
const FileCache = require("./FileCache.js");
|
||||
const Sources = require("./Sources.js");
|
||||
|
||||
const debugUtil = require("debug");
|
||||
const debug = debugUtil("Eleventy:Fetch");
|
||||
|
||||
class AssetCache {
|
||||
#source;
|
||||
#hash;
|
||||
#customFilename;
|
||||
#cache;
|
||||
#cacheDirectory;
|
||||
#cacheLocationDirty = false;
|
||||
#directoryManager;
|
||||
|
||||
constructor(source, cacheDirectory, options = {}) {
|
||||
if(!Sources.isValidSource(source)) {
|
||||
throw Sources.getInvalidSourceError(source);
|
||||
}
|
||||
|
||||
let uniqueKey = AssetCache.getCacheKey(source, options);
|
||||
this.uniqueKey = uniqueKey;
|
||||
this.hash = AssetCache.getHash(uniqueKey, options.hashLength);
|
||||
|
||||
this.cacheDirectory = cacheDirectory || ".cache";
|
||||
this.options = options;
|
||||
|
||||
this.defaultDuration = "1d";
|
||||
this.duration = options.duration || this.defaultDuration;
|
||||
|
||||
// Compute the filename only once
|
||||
if (typeof this.options.filenameFormat === "function") {
|
||||
this.#customFilename = AssetCache.cleanFilename(this.options.filenameFormat(uniqueKey, this.hash));
|
||||
|
||||
if (typeof this.#customFilename !== "string" || this.#customFilename.length === 0) {
|
||||
throw new Error(`The provided filenameFormat callback function needs to return valid filename characters.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log(message) {
|
||||
if (this.options.verbose) {
|
||||
console.log(`[11ty/eleventy-fetch] ${message}`);
|
||||
} else {
|
||||
debug(message);
|
||||
}
|
||||
}
|
||||
|
||||
static cleanFilename(filename) {
|
||||
// Ensure no illegal characters are present (Windows or Linux: forward/backslash, chevrons, colon, double-quote, pipe, question mark, asterisk)
|
||||
if (filename.match(/([\/\\<>:"|?*]+?)/)) {
|
||||
let sanitizedFilename = filename.replace(/[\/\\<>:"|?*]+/g, "");
|
||||
debug(
|
||||
`[@11ty/eleventy-fetch] Some illegal characters were removed from the cache filename: ${filename} will be cached as ${sanitizedFilename}.`,
|
||||
);
|
||||
return sanitizedFilename;
|
||||
}
|
||||
|
||||
return filename;
|
||||
}
|
||||
|
||||
static getCacheKey(source, options) {
|
||||
// RemoteAssetCache passes in a string here, which skips this check (requestId is already used upstream)
|
||||
if (Sources.isValidComplexSource(source)) {
|
||||
if(options.requestId) {
|
||||
return options.requestId;
|
||||
}
|
||||
|
||||
if(typeof source.toString === "function") {
|
||||
// return source.toString();
|
||||
let toStr = source.toString();
|
||||
if(toStr !== "function() {}" && toStr !== "[object Object]") {
|
||||
return toStr;
|
||||
}
|
||||
}
|
||||
|
||||
throw Sources.getInvalidSourceError(source);
|
||||
}
|
||||
|
||||
return source;
|
||||
}
|
||||
|
||||
// Defult hashLength also set in global options, duplicated here for tests
|
||||
// v5.0+ key can be Array or literal
|
||||
static getHash(key, hashLength = 30) {
|
||||
if (!Array.isArray(key)) {
|
||||
key = [key];
|
||||
}
|
||||
|
||||
let result = createHashHexSync(...key);
|
||||
return result.slice(0, hashLength);
|
||||
}
|
||||
|
||||
get source() {
|
||||
return this.#source;
|
||||
}
|
||||
|
||||
set source(source) {
|
||||
this.#source = source;
|
||||
}
|
||||
|
||||
get hash() {
|
||||
return this.#hash;
|
||||
}
|
||||
|
||||
set hash(value) {
|
||||
if (value !== this.#hash) {
|
||||
this.#cacheLocationDirty = true;
|
||||
}
|
||||
|
||||
this.#hash = value;
|
||||
}
|
||||
|
||||
get cacheDirectory() {
|
||||
return this.#cacheDirectory;
|
||||
}
|
||||
|
||||
set cacheDirectory(dir) {
|
||||
if (dir !== this.#cacheDirectory) {
|
||||
this.#cacheLocationDirty = true;
|
||||
}
|
||||
|
||||
this.#cacheDirectory = dir;
|
||||
}
|
||||
|
||||
get cacheFilename() {
|
||||
if (typeof this.#customFilename === "string" && this.#customFilename.length > 0) {
|
||||
return this.#customFilename;
|
||||
}
|
||||
|
||||
return `eleventy-fetch-${this.hash}`;
|
||||
}
|
||||
|
||||
get rootDir() {
|
||||
// Work in an AWS Lambda (serverless)
|
||||
// https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html
|
||||
|
||||
// Bad: LAMBDA_TASK_ROOT is /var/task/ on AWS so we must use ELEVENTY_ROOT
|
||||
// When using ELEVENTY_ROOT, cacheDirectory must be relative
|
||||
// (we are bundling the cache files into the serverless function)
|
||||
if (
|
||||
process.env.LAMBDA_TASK_ROOT &&
|
||||
process.env.ELEVENTY_ROOT &&
|
||||
!this.cacheDirectory.startsWith("/")
|
||||
) {
|
||||
return path.resolve(process.env.ELEVENTY_ROOT, this.cacheDirectory);
|
||||
}
|
||||
|
||||
// otherwise, it is recommended to use somewhere in /tmp/ for serverless (otherwise it won’t write)
|
||||
return path.resolve(this.cacheDirectory);
|
||||
}
|
||||
|
||||
get cachePath() {
|
||||
return path.join(this.rootDir, this.cacheFilename);
|
||||
}
|
||||
|
||||
get cache() {
|
||||
if (!this.#cache || this.#cacheLocationDirty) {
|
||||
let cache = new FileCache(this.cacheFilename, {
|
||||
dir: this.rootDir,
|
||||
source: this.source,
|
||||
});
|
||||
cache.setDefaultType(this.options.type);
|
||||
cache.setDryRun(this.options.dryRun);
|
||||
cache.setDirectoryManager(this.#directoryManager);
|
||||
|
||||
this.#cache = cache;
|
||||
this.#cacheLocationDirty = false;
|
||||
}
|
||||
return this.#cache;
|
||||
}
|
||||
|
||||
getDurationMs(duration = "0s") {
|
||||
return DateCompare.getDurationMs(duration);
|
||||
}
|
||||
|
||||
setDirectoryManager(manager) {
|
||||
this.#directoryManager = manager;
|
||||
}
|
||||
|
||||
async save(contents, type = "buffer", metadata = {}) {
|
||||
if(!contents) {
|
||||
throw new Error("save(contents) expects contents (was falsy)");
|
||||
}
|
||||
|
||||
this.cache.set(type, contents, metadata);
|
||||
|
||||
// Dry-run handled downstream
|
||||
this.cache.save();
|
||||
}
|
||||
|
||||
getCachedContents() {
|
||||
return this.cache.getContents();
|
||||
}
|
||||
|
||||
getCachedValue() {
|
||||
if(this.options.returnType === "response") {
|
||||
return {
|
||||
...this.cachedObject.metadata?.response,
|
||||
body: this.getCachedContents(),
|
||||
cache: "hit",
|
||||
}
|
||||
}
|
||||
|
||||
return this.getCachedContents();
|
||||
}
|
||||
|
||||
getCachedTimestamp() {
|
||||
return this.cachedObject?.cachedAt;
|
||||
}
|
||||
|
||||
isCacheValid(duration = this.duration) {
|
||||
if(!this.cachedObject || !this.cachedObject?.cachedAt) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(this.cachedObject?.type && DateCompare.isTimestampWithinDuration(this.cachedObject?.cachedAt, duration)) {
|
||||
return this.cache.hasContents(this.cachedObject?.type); // check file system to make files haven’t been purged.
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
get cachedObject() {
|
||||
return this.cache.get();
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
needsToFetch(duration) {
|
||||
return !this.isCacheValid(duration);
|
||||
}
|
||||
|
||||
// This is only included for completenes—not on the docs.
|
||||
async fetch(optionsOverride = {}) {
|
||||
if (this.isCacheValid(optionsOverride.duration)) {
|
||||
// promise
|
||||
debug(`Using cached version of: ${this.uniqueKey}`);
|
||||
return this.getCachedValue();
|
||||
}
|
||||
|
||||
debug(`Saving ${this.uniqueKey} to ${this.cacheFilename}`);
|
||||
await this.save(this.source, optionsOverride.type);
|
||||
|
||||
return this.source;
|
||||
}
|
||||
|
||||
// for testing
|
||||
hasAnyCacheFiles() {
|
||||
for(let p of this.cache.getAllPossibleFilePaths()) {
|
||||
if(fs.existsSync(p)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// for testing
|
||||
async destroy() {
|
||||
await Promise.all(this.cache.getAllPossibleFilePaths().map(path => {
|
||||
if (fs.existsSync(path)) {
|
||||
return fs.unlinkSync(path);
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
module.exports = AssetCache;
|
||||
Reference in New Issue
Block a user