UNPKG

faastjs

Version:

Serverless batch computing made simple.

115 lines 13.6 kB
"use strict"; 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