faastjs
Version:
Serverless batch computing made simple.
115 lines • 13.6 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.caches = exports.PersistentCache = void 0;
const fs_extra_1 = require("fs-extra");
const os_1 = require("os");
const path_1 = require("path");
const uuid_1 = require("uuid");
const crypto_1 = require("crypto");
/**
* A simple persistent key-value store. Used to implement {@link Limits.cache}
* for {@link throttle}.
* @remarks
* Entries can be expired, but are not actually deleted individually. The entire
* cache can be deleted at once. Hence this cache is useful for storing results
* that are expensive to compute but do not change too often (e.g. the
* node_modules folder from an 'npm install' where 'package.json' is not
* expected to change too often).
*
* By default faast.js will use the directory `~/.faastjs` as a local cache to
* store data such as pricing retrieved from cloud APIs, and garbage collection
* information. This directory can be safely deleted if no faast.js instances
* are running.
* @public
*/
class PersistentCache {
async initialize(dir) {
if (!(await (0, fs_extra_1.pathExists)(dir))) {
await (0, fs_extra_1.mkdirp)(dir);
}
}
/**
* Construct a new persistent cache, typically used with {@link Limits} as
* part of the arguments to {@link throttle}.
* @param dirRelativeToHomeDir - The directory under the user's home
* directory that will be used to store cached values. The directory will be
* created if it doesn't exist.
* @param expiration - The age (in ms) after which a cached entry is
* invalid. Default: `24*3600*1000` (1 day).
*/
constructor(
/**
* The directory under the user's home directory that will be used to
* store cached values. The directory will be created if it doesn't
* exist.
*/
dirRelativeToHomeDir,
/**
* The age (in ms) after which a cached entry is invalid. Default:
* `24*3600*1000` (1 day).
*/
expiration = 24 * 3600 * 1000) {
this.dirRelativeToHomeDir = dirRelativeToHomeDir;
this.expiration = expiration;
this.dir = (0, path_1.join)((0, os_1.homedir)(), dirRelativeToHomeDir);
this.initialized = this.initialize(this.dir);
}
hash(key) {
const hasher = (0, crypto_1.createHash)("sha256");
hasher.update(key);
return hasher.digest("hex");
}
/**
* Retrieves the value previously set for the given key, or undefined if the
* key is not found.
*/
async get(key) {
await this.initialized;
const entry = (0, path_1.join)(this.dir, this.hash(key));
const statEntry = await (0, fs_extra_1.stat)(entry).catch(_ => { });
if (statEntry) {
if (Date.now() - statEntry.mtimeMs > this.expiration) {
return undefined;
}
return (0, fs_extra_1.readFile)(entry).catch(_ => undefined);
}
return undefined;
}
/**
* Set the cache key to the given value.
* @returns a Promise that resolves when the cache entry has been persisted.
*/
async set(key, value) {
await this.initialized;
const entry = (0, path_1.join)(this.dir, this.hash(key));
const tmpEntry = (0, path_1.join)(this.dir, (0, uuid_1.v4)());
await (0, fs_extra_1.writeFile)(tmpEntry, value, { mode: 0o600, encoding: "binary" });
await (0, fs_extra_1.rename)(tmpEntry, entry);
}
/**
* Retrieve all keys stored in the cache, including expired entries.
*/
entries() {
return (0, fs_extra_1.readdir)(this.dir);
}
/**
* Deletes all cached entries from disk.
* @param leaveEmptyDir - If true, leave the cache directory in place after
* deleting its contents. If false, the cache directory will be removed.
* Default: `true`.
*/
async clear({ leaveEmptyDir = true } = {}) {
await this.initialized;
await (0, fs_extra_1.remove)(this.dir);
if (leaveEmptyDir) {
await (0, fs_extra_1.mkdirp)(this.dir);
}
}
}
exports.PersistentCache = PersistentCache;
const days = 24 * 3600 * 1000;
exports.caches = {
awsPrices: new PersistentCache(".faastjs/aws/pricing", 1 * days),
awsGc: new PersistentCache(".faastjs/aws/gc", 7 * days)
};
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2FjaGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvY2FjaGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBQUEsdUNBU2tCO0FBQ2xCLDJCQUE2QjtBQUM3QiwrQkFBNEI7QUFFNUIsK0JBQW9DO0FBQ3BDLG1DQUFvQztBQUlwQzs7Ozs7Ozs7Ozs7Ozs7O0dBZUc7QUFDSCxNQUFhLGVBQWU7SUFHaEIsS0FBSyxDQUFDLFVBQVUsQ0FBQyxHQUFXO1FBQ2hDLElBQUksQ0FBQyxDQUFDLE1BQU0sSUFBQSxxQkFBVSxFQUFDLEdBQUcsQ0FBQyxDQUFDLEVBQUUsQ0FBQztZQUMzQixNQUFNLElBQUEsaUJBQU0sRUFBQyxHQUFHLENBQUMsQ0FBQztRQUN0QixDQUFDO0lBQ0wsQ0FBQztJQU9EOzs7Ozs7OztPQVFHO0lBQ0g7SUFDSTs7OztPQUlHO0lBQ00sb0JBQTRCO0lBQ3JDOzs7T0FHRztJQUNNLGFBQXFCLEVBQUUsR0FBRyxJQUFJLEdBQUcsSUFBSTtRQUxyQyx5QkFBb0IsR0FBcEIsb0JBQW9CLENBQVE7UUFLNUIsZUFBVSxHQUFWLFVBQVUsQ0FBMkI7UUFFOUMsSUFBSSxDQUFDLEdBQUcsR0FBRyxJQUFBLFdBQUksRUFBQyxJQUFBLFlBQU8sR0FBRSxFQUFFLG9CQUFvQixDQUFDLENBQUM7UUFDakQsSUFBSSxDQUFDLFdBQVcsR0FBRyxJQUFJLENBQUMsVUFBVSxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQztJQUNqRCxDQUFDO0lBRU8sSUFBSSxDQUFDLEdBQVc7UUFDcEIsTUFBTSxNQUFNLEdBQUcsSUFBQSxtQkFBVSxFQUFDLFFBQVEsQ0FBQyxDQUFDO1FBQ3BDLE1BQU0sQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUM7UUFDbkIsT0FBTyxNQUFNLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDO0lBQ2hDLENBQUM7SUFFRDs7O09BR0c7SUFDSCxLQUFLLENBQUMsR0FBRyxDQUFDLEdBQVc7UUFDakIsTUFBTSxJQUFJLENBQUMsV0FBVyxDQUFDO1FBQ3ZCLE1BQU0sS0FBSyxHQUFHLElBQUEsV0FBSSxFQUFDLElBQUksQ0FBQyxHQUFHLEVBQUUsSUFBSSxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDO1FBQzdDLE1BQU0sU0FBUyxHQUFHLE1BQU0sSUFBQSxlQUFJLEVBQUMsS0FBSyxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxFQUFFLEdBQUUsQ0FBQyxDQUFDLENBQUM7UUFDbkQsSUFBSSxTQUFTLEVBQUUsQ0FBQztZQUNaLElBQUksSUFBSSxDQUFDLEdBQUcsRUFBRSxHQUFHLFNBQVMsQ0FBQyxPQUFPLEdBQUcsSUFBSSxDQUFDLFVBQVUsRUFBRSxDQUFDO2dCQUNuRCxPQUFPLFNBQVMsQ0FBQztZQUNyQixDQUFDO1lBQ0QsT0FBTyxJQUFBLG1CQUFRLEVBQUMsS0FBSyxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsU0FBUyxDQUFDLENBQUM7UUFDakQsQ0FBQztRQUNELE9BQU8sU0FBUyxDQUFDO0lBQ3JCLENBQUM7SUFFRDs7O09BR0c7SUFDSCxLQUFLLENBQUMsR0FBRyxDQUFDLEdBQVcsRUFBRSxLQUFtQztRQUN0RCxNQUFNLElBQUksQ0FBQyxXQUFXLENBQUM7UUFDdkIsTUFBTSxLQUFLLEdBQUcsSUFBQSxXQUFJLEVBQUMsSUFBSSxDQUFDLEdBQUcsRUFBRSxJQUFJLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUM7UUFDN0MsTUFBTSxRQUFRLEdBQUcsSUFBQSxXQUFJLEVBQUMsSUFBSSxDQUFDLEdBQUcsRUFBRSxJQUFBLFNBQU0sR0FBRSxDQUFDLENBQUM7UUFDMUMsTUFBTSxJQUFBLG9CQUFTLEVBQUMsUUFBUSxFQUFFLEtBQUssRUFBRSxFQUFFLElBQUksRUFBRSxLQUFLLEVBQUUsUUFBUSxFQUFFLFFBQVEsRUFBRSxDQUFDLENBQUM7UUFDdEUsTUFBTSxJQUFBLGlCQUFNLEVBQUMsUUFBUSxFQUFFLEtBQUssQ0FBQyxDQUFDO0lBQ2xDLENBQUM7SUFFRDs7T0FFRztJQUNILE9BQU87UUFDSCxPQUFPLElBQUEsa0JBQU8sRUFBQyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUM7SUFDN0IsQ0FBQztJQUVEOzs7OztPQUtHO0lBQ0gsS0FBSyxDQUFDLEtBQUssQ0FBQyxFQUFFLGFBQWEsR0FBRyxJQUFJLEVBQUUsR0FBRyxFQUFFO1FBQ3JDLE1BQU0sSUFBSSxDQUFDLFdBQVcsQ0FBQztRQUV2QixNQUFNLElBQUEsaUJBQU0sRUFBQyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUM7UUFFdkIsSUFBSSxhQUFhLEVBQUUsQ0FBQztZQUNoQixNQUFNLElBQUEsaUJBQU0sRUFBQyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUM7UUFDM0IsQ0FBQztJQUNMLENBQUM7Q0FDSjtBQWpHRCwwQ0FpR0M7QUFFRCxNQUFNLElBQUksR0FBRyxFQUFFLEdBQUcsSUFBSSxHQUFHLElBQUksQ0FBQztBQUVqQixRQUFBLE1BQU0sR0FBRztJQUNsQixTQUFTLEVBQUUsSUFBSSxlQUFlLENBQUMsc0JBQXNCLEVBQUUsQ0FBQyxHQUFHLElBQUksQ0FBQztJQUNoRSxLQUFLLEVBQUUsSUFBSSxlQUFlLENBQUMsaUJBQWlCLEVBQUUsQ0FBQyxHQUFHLElBQUksQ0FBQztDQUMxRCxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHtcbiAgICBta2RpcnAsXG4gICAgcGF0aEV4aXN0cyxcbiAgICByZWFkZGlyLFxuICAgIHJlYWRGaWxlLFxuICAgIHJlbW92ZSxcbiAgICByZW5hbWUsXG4gICAgc3RhdCxcbiAgICB3cml0ZUZpbGVcbn0gZnJvbSBcImZzLWV4dHJhXCI7XG5pbXBvcnQgeyBob21lZGlyIH0gZnJvbSBcIm9zXCI7XG5pbXBvcnQgeyBqb2luIH0gZnJvbSBcInBhdGhcIjtcbmltcG9ydCB7IFJlYWRhYmxlIH0gZnJvbSBcInN0cmVhbVwiO1xuaW1wb3J0IHsgdjQgYXMgdXVpZHY0IH0gZnJvbSBcInV1aWRcIjtcbmltcG9ydCB7IGNyZWF0ZUhhc2ggfSBmcm9tIFwiY3J5cHRvXCI7XG5cbmludGVyZmFjZSBCbG9iIHt9XG5cbi8qKlxuICogQSBzaW1wbGUgcGVyc2lzdGVudCBrZXktdmFsdWUgc3RvcmUuIFVzZWQgdG8gaW1wbGVtZW50IHtAbGluayBMaW1pdHMuY2FjaGV9XG4gKiBmb3Ige0BsaW5rIHRocm90dGxlfS5cbiAqIEByZW1hcmtzXG4gKiBFbnRyaWVzIGNhbiBiZSBleHBpcmVkLCBidXQgYXJlIG5vdCBhY3R1YWxseSBkZWxldGVkIGluZGl2aWR1YWxseS4gVGhlIGVudGlyZVxuICogY2FjaGUgY2FuIGJlIGRlbGV0ZWQgYXQgb25jZS4gSGVuY2UgdGhpcyBjYWNoZSBpcyB1c2VmdWwgZm9yIHN0b3JpbmcgcmVzdWx0c1xuICogdGhhdCBhcmUgZXhwZW5zaXZlIHRvIGNvbXB1dGUgYnV0IGRvIG5vdCBjaGFuZ2UgdG9vIG9mdGVuIChlLmcuIHRoZVxuICogbm9kZV9tb2R1bGVzIGZvbGRlciBmcm9tIGFuICducG0gaW5zdGFsbCcgd2hlcmUgJ3BhY2thZ2UuanNvbicgaXMgbm90XG4gKiBleHBlY3RlZCB0byBjaGFuZ2UgdG9vIG9mdGVuKS5cbiAqXG4gKiBCeSBkZWZhdWx0IGZhYXN0LmpzIHdpbGwgdXNlIHRoZSBkaXJlY3RvcnkgYH4vLmZhYXN0anNgIGFzIGEgbG9jYWwgY2FjaGUgdG9cbiAqIHN0b3JlIGRhdGEgc3VjaCBhcyBwcmljaW5nIHJldHJpZXZlZCBmcm9tIGNsb3VkIEFQSXMsIGFuZCBnYXJiYWdlIGNvbGxlY3Rpb25cbiAqIGluZm9ybWF0aW9uLiBUaGlzIGRpcmVjdG9yeSBjYW4gYmUgc2FmZWx5IGRlbGV0ZWQgaWYgbm8gZmFhc3QuanMgaW5zdGFuY2VzXG4gKiBhcmUgcnVubmluZy5cbiAqIEBwdWJsaWNcbiAqL1xuZXhwb3J0IGNsYXNzIFBlcnNpc3RlbnRDYWNoZSB7XG4gICAgcHJpdmF0ZSBpbml0aWFsaXplZDogUHJvbWlzZTx2b2lkPjtcblxuICAgIHByaXZhdGUgYXN5bmMgaW5pdGlhbGl6ZShkaXI6IHN0cmluZykge1xuICAgICAgICBpZiAoIShhd2FpdCBwYXRoRXhpc3RzKGRpcikpKSB7XG4gICAgICAgICAgICBhd2FpdCBta2RpcnAoZGlyKTtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFRoZSBkaXJlY3Rvcnkgb24gZGlzayB3aGVyZSBjYWNoZWQgdmFsdWVzIGFyZSBzdG9yZWQuXG4gICAgICovXG4gICAgcmVhZG9ubHkgZGlyOiBzdHJpbmc7XG5cbiAgICAvKipcbiAgICAgKiBDb25zdHJ1Y3QgYSBuZXcgcGVyc2lzdGVudCBjYWNoZSwgdHlwaWNhbGx5IHVzZWQgd2l0aCB7QGxpbmsgTGltaXRzfSBhc1xuICAgICAqIHBhcnQgb2YgdGhlIGFyZ3VtZW50cyB0byB7QGxpbmsgdGhyb3R0bGV9LlxuICAgICAqIEBwYXJhbSBkaXJSZWxhdGl2ZVRvSG9tZURpciAtIFRoZSBkaXJlY3RvcnkgdW5kZXIgdGhlIHVzZXIncyBob21lXG4gICAgICogZGlyZWN0b3J5IHRoYXQgd2lsbCBiZSB1c2VkIHRvIHN0b3JlIGNhY2hlZCB2YWx1ZXMuIFRoZSBkaXJlY3Rvcnkgd2lsbCBiZVxuICAgICAqIGNyZWF0ZWQgaWYgaXQgZG9lc24ndCBleGlzdC5cbiAgICAgKiBAcGFyYW0gZXhwaXJhdGlvbiAtIFRoZSBhZ2UgKGluIG1zKSBhZnRlciB3aGljaCBhIGNhY2hlZCBlbnRyeSBpc1xuICAgICAqIGludmFsaWQuIERlZmF1bHQ6IGAyNCozNjAwKjEwMDBgICgxIGRheSkuXG4gICAgICovXG4gICAgY29uc3RydWN0b3IoXG4gICAgICAgIC8qKlxuICAgICAgICAgKiBUaGUgZGlyZWN0b3J5IHVuZGVyIHRoZSB1c2VyJ3MgaG9tZSBkaXJlY3RvcnkgdGhhdCB3aWxsIGJlIHVzZWQgdG9cbiAgICAgICAgICogc3RvcmUgY2FjaGVkIHZhbHVlcy4gVGhlIGRpcmVjdG9yeSB3aWxsIGJlIGNyZWF0ZWQgaWYgaXQgZG9lc24ndFxuICAgICAgICAgKiBleGlzdC5cbiAgICAgICAgICovXG4gICAgICAgIHJlYWRvbmx5IGRpclJlbGF0aXZlVG9Ib21lRGlyOiBzdHJpbmcsXG4gICAgICAgIC8qKlxuICAgICAgICAgKiBUaGUgYWdlIChpbiBtcykgYWZ0ZXIgd2hpY2ggYSBjYWNoZWQgZW50cnkgaXMgaW52YWxpZC4gRGVmYXVsdDpcbiAgICAgICAgICogYDI0KjM2MDAqMTAwMGAgKDEgZGF5KS5cbiAgICAgICAgICovXG4gICAgICAgIHJlYWRvbmx5IGV4cGlyYXRpb246IG51bWJlciA9IDI0ICogMzYwMCAqIDEwMDBcbiAgICApIHtcbiAgICAgICAgdGhpcy5kaXIgPSBqb2luKGhvbWVkaXIoKSwgZGlyUmVsYXRpdmVUb0hvbWVEaXIpO1xuICAgICAgICB0aGlzLmluaXRpYWxpemVkID0gdGhpcy5pbml0aWFsaXplKHRoaXMuZGlyKTtcbiAgICB9XG5cbiAgICBwcml2YXRlIGhhc2goa2V5OiBzdHJpbmcpIHtcbiAgICAgICAgY29uc3QgaGFzaGVyID0gY3JlYXRlSGFzaChcInNoYTI1NlwiKTtcbiAgICAgICAgaGFzaGVyLnVwZGF0ZShrZXkpO1xuICAgICAgICByZXR1cm4gaGFzaGVyLmRpZ2VzdChcImhleFwiKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBSZXRyaWV2ZXMgdGhlIHZhbHVlIHByZXZpb3VzbHkgc2V0IGZvciB0aGUgZ2l2ZW4ga2V5LCBvciB1bmRlZmluZWQgaWYgdGhlXG4gICAgICoga2V5IGlzIG5vdCBmb3VuZC5cbiAgICAgKi9cbiAgICBhc3luYyBnZXQoa2V5OiBzdHJpbmcpIHtcbiAgICAgICAgYXdhaXQgdGhpcy5pbml0aWFsaXplZDtcbiAgICAgICAgY29uc3QgZW50cnkgPSBqb2luKHRoaXMuZGlyLCB0aGlzLmhhc2goa2V5KSk7XG4gICAgICAgIGNvbnN0IHN0YXRFbnRyeSA9IGF3YWl0IHN0YXQoZW50cnkpLmNhdGNoKF8gPT4ge30pO1xuICAgICAgICBpZiAoc3RhdEVudHJ5KSB7XG4gICAgICAgICAgICBpZiAoRGF0ZS5ub3coKSAtIHN0YXRFbnRyeS5tdGltZU1zID4gdGhpcy5leHBpcmF0aW9uKSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuIHVuZGVmaW5lZDtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIHJldHVybiByZWFkRmlsZShlbnRyeSkuY2F0Y2goXyA9PiB1bmRlZmluZWQpO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiB1bmRlZmluZWQ7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogU2V0IHRoZSBjYWNoZSBrZXkgdG8gdGhlIGdpdmVuIHZhbHVlLlxuICAgICAqIEByZXR1cm5zIGEgUHJvbWlzZSB0aGF0IHJlc29sdmVzIHdoZW4gdGhlIGNhY2hlIGVudHJ5IGhhcyBiZWVuIHBlcnNpc3RlZC5cbiAgICAgKi9cbiAgICBhc3luYyBzZXQoa2V5OiBzdHJpbmcsIHZhbHVlOiBCdWZmZXIgfCBzdHJpbmcgfCBVaW50OEFycmF5KSB7XG4gICAgICAgIGF3YWl0IHRoaXMuaW5pdGlhbGl6ZWQ7XG4gICAgICAgIGNvbnN0IGVudHJ5ID0gam9pbih0aGlzLmRpciwgdGhpcy5oYXNoKGtleSkpO1xuICAgICAgICBjb25zdCB0bXBFbnRyeSA9IGpvaW4odGhpcy5kaXIsIHV1aWR2NCgpKTtcbiAgICAgICAgYXdhaXQgd3JpdGVGaWxlKHRtcEVudHJ5LCB2YWx1ZSwgeyBtb2RlOiAwbzYwMCwgZW5jb2Rpbmc6IFwiYmluYXJ5XCIgfSk7XG4gICAgICAgIGF3YWl0IHJlbmFtZSh0bXBFbnRyeSwgZW50cnkpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFJldHJpZXZlIGFsbCBrZXlzIHN0b3JlZCBpbiB0aGUgY2FjaGUsIGluY2x1ZGluZyBleHBpcmVkIGVudHJpZXMuXG4gICAgICovXG4gICAgZW50cmllcygpIHtcbiAgICAgICAgcmV0dXJuIHJlYWRkaXIodGhpcy5kaXIpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIERlbGV0ZXMgYWxsIGNhY2hlZCBlbnRyaWVzIGZyb20gZGlzay5cbiAgICAgKiBAcGFyYW0gbGVhdmVFbXB0eURpciAtIElmIHRydWUsIGxlYXZlIHRoZSBjYWNoZSBkaXJlY3RvcnkgaW4gcGxhY2UgYWZ0ZXJcbiAgICAgKiBkZWxldGluZyBpdHMgY29udGVudHMuIElmIGZhbHNlLCB0aGUgY2FjaGUgZGlyZWN0b3J5IHdpbGwgYmUgcmVtb3ZlZC5cbiAgICAgKiBEZWZhdWx0OiBgdHJ1ZWAuXG4gICAgICovXG4gICAgYXN5bmMgY2xlYXIoeyBsZWF2ZUVtcHR5RGlyID0gdHJ1ZSB9ID0ge30pIHtcbiAgICAgICAgYXdhaXQgdGhpcy5pbml0aWFsaXplZDtcblxuICAgICAgICBhd2FpdCByZW1vdmUodGhpcy5kaXIpO1xuXG4gICAgICAgIGlmIChsZWF2ZUVtcHR5RGlyKSB7XG4gICAgICAgICAgICBhd2FpdCBta2RpcnAodGhpcy5kaXIpO1xuICAgICAgICB9XG4gICAgfVxufVxuXG5jb25zdCBkYXlzID0gMjQgKiAzNjAwICogMTAwMDtcblxuZXhwb3J0IGNvbnN0IGNhY2hlcyA9IHtcbiAgICBhd3NQcmljZXM6IG5ldyBQZXJzaXN0ZW50Q2FjaGUoXCIuZmFhc3Rqcy9hd3MvcHJpY2luZ1wiLCAxICogZGF5cyksXG4gICAgYXdzR2M6IG5ldyBQZXJzaXN0ZW50Q2FjaGUoXCIuZmFhc3Rqcy9hd3MvZ2NcIiwgNyAqIGRheXMpXG59O1xuIl19
;