1 | ;
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | exports.caches = exports.PersistentCache = void 0;
|
4 | const fs_extra_1 = require("fs-extra");
|
5 | const os_1 = require("os");
|
6 | const path_1 = require("path");
|
7 | const uuid_1 = require("uuid");
|
8 | /**
|
9 | * A simple persistent key-value store. Used to implement {@link Limits.cache}
|
10 | * for {@link throttle}.
|
11 | * @remarks
|
12 | * Entries can be expired, but are not actually deleted individually. The entire
|
13 | * cache can be deleted at once. Hence this cache is useful for storing results
|
14 | * that are expensive to compute but do not change too often (e.g. the
|
15 | * node_modules folder from an 'npm install' where 'package.json' is not
|
16 | * expected to change too often).
|
17 | *
|
18 | * By default faast.js will use the directory `~/.faastjs` as a local cache to
|
19 | * store data such as pricing retrieved from cloud APIs, and garbage collection
|
20 | * information. This directory can be safely deleted if no faast.js instances
|
21 | * are running.
|
22 | * @public
|
23 | */
|
24 | class PersistentCache {
|
25 | /**
|
26 | * Construct a new persistent cache, typically used with {@link Limits} as
|
27 | * part of the arguments to {@link throttle}.
|
28 | * @param dirRelativeToHomeDir - The directory under the user's home
|
29 | * directory that will be used to store cached values. The directory will be
|
30 | * created if it doesn't exist.
|
31 | * @param expiration - The age (in ms) after which a cached entry is
|
32 | * invalid. Default: `24*3600*1000` (1 day).
|
33 | */
|
34 | constructor(
|
35 | /**
|
36 | * The directory under the user's home directory that will be used to
|
37 | * store cached values. The directory will be created if it doesn't
|
38 | * exist.
|
39 | */
|
40 | dirRelativeToHomeDir,
|
41 | /**
|
42 | * The age (in ms) after which a cached entry is invalid. Default:
|
43 | * `24*3600*1000` (1 day).
|
44 | */
|
45 | expiration = 24 * 3600 * 1000) {
|
46 | this.dirRelativeToHomeDir = dirRelativeToHomeDir;
|
47 | this.expiration = expiration;
|
48 | this.dir = (0, path_1.join)((0, os_1.homedir)(), dirRelativeToHomeDir);
|
49 | this.initialized = this.initialize(this.dir);
|
50 | }
|
51 | async initialize(dir) {
|
52 | if (!(await (0, fs_extra_1.pathExists)(dir))) {
|
53 | await (0, fs_extra_1.mkdirp)(dir);
|
54 | }
|
55 | }
|
56 | /**
|
57 | * Retrieves the value previously set for the given key, or undefined if the
|
58 | * key is not found.
|
59 | */
|
60 | async get(key) {
|
61 | await this.initialized;
|
62 | const entry = (0, path_1.join)(this.dir, key);
|
63 | const statEntry = await (0, fs_extra_1.stat)(entry).catch(_ => { });
|
64 | if (statEntry) {
|
65 | if (Date.now() - statEntry.mtimeMs > this.expiration) {
|
66 | return undefined;
|
67 | }
|
68 | return (0, fs_extra_1.readFile)(entry).catch(_ => undefined);
|
69 | }
|
70 | return undefined;
|
71 | }
|
72 | /**
|
73 | * Set the cache key to the given value.
|
74 | * @returns a Promise that resolves when the cache entry has been persisted.
|
75 | */
|
76 | async set(key, value) {
|
77 | await this.initialized;
|
78 | const entry = (0, path_1.join)(this.dir, key);
|
79 | const tmpEntry = (0, path_1.join)(this.dir, (0, uuid_1.v4)());
|
80 | await (0, fs_extra_1.writeFile)(tmpEntry, value, { mode: 0o600, encoding: "binary" });
|
81 | await (0, fs_extra_1.rename)(tmpEntry, entry);
|
82 | }
|
83 | /**
|
84 | * Retrieve all keys stored in the cache, including expired entries.
|
85 | */
|
86 | entries() {
|
87 | return (0, fs_extra_1.readdir)(this.dir);
|
88 | }
|
89 | /**
|
90 | * Deletes all cached entries from disk.
|
91 | * @param leaveEmptyDir - If true, leave the cache directory in place after
|
92 | * deleting its contents. If false, the cache directory will be removed.
|
93 | * Default: `true`.
|
94 | */
|
95 | async clear({ leaveEmptyDir = true } = {}) {
|
96 | await this.initialized;
|
97 | await (0, fs_extra_1.remove)(this.dir);
|
98 | if (leaveEmptyDir) {
|
99 | await (0, fs_extra_1.mkdirp)(this.dir);
|
100 | }
|
101 | }
|
102 | }
|
103 | exports.PersistentCache = PersistentCache;
|
104 | const days = 24 * 3600 * 1000;
|
105 | exports.caches = {
|
106 | awsPrices: new PersistentCache(".faastjs/aws/pricing", 1 * days),
|
107 | googlePrices: new PersistentCache(".faastjs/google/pricing", 1 * days),
|
108 | awsGc: new PersistentCache(".faastjs/aws/gc", 7 * days)
|
109 | };
|
110 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2FjaGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvY2FjaGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBQUEsdUNBU2tCO0FBQ2xCLDJCQUE2QjtBQUM3QiwrQkFBNEI7QUFFNUIsK0JBQW9DO0FBSXBDOzs7Ozs7Ozs7Ozs7Ozs7R0FlRztBQUNILE1BQWEsZUFBZTtJQWN4Qjs7Ozs7Ozs7T0FRRztJQUNIO0lBQ0k7Ozs7T0FJRztJQUNNLG9CQUE0QjtJQUNyQzs7O09BR0c7SUFDTSxhQUFxQixFQUFFLEdBQUcsSUFBSSxHQUFHLElBQUk7UUFMckMseUJBQW9CLEdBQXBCLG9CQUFvQixDQUFRO1FBSzVCLGVBQVUsR0FBVixVQUFVLENBQTJCO1FBRTlDLElBQUksQ0FBQyxHQUFHLEdBQUcsSUFBQSxXQUFJLEVBQUMsSUFBQSxZQUFPLEdBQUUsRUFBRSxvQkFBb0IsQ0FBQyxDQUFDO1FBQ2pELElBQUksQ0FBQyxXQUFXLEdBQUcsSUFBSSxDQUFDLFVBQVUsQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUM7SUFDakQsQ0FBQztJQW5DTyxLQUFLLENBQUMsVUFBVSxDQUFDLEdBQVc7UUFDaEMsSUFBSSxDQUFDLENBQUMsTUFBTSxJQUFBLHFCQUFVLEVBQUMsR0FBRyxDQUFDLENBQUMsRUFBRTtZQUMxQixNQUFNLElBQUEsaUJBQU0sRUFBQyxHQUFHLENBQUMsQ0FBQztTQUNyQjtJQUNMLENBQUM7SUFpQ0Q7OztPQUdHO0lBQ0gsS0FBSyxDQUFDLEdBQUcsQ0FBQyxHQUFXO1FBQ2pCLE1BQU0sSUFBSSxDQUFDLFdBQVcsQ0FBQztRQUN2QixNQUFNLEtBQUssR0FBRyxJQUFBLFdBQUksRUFBQyxJQUFJLENBQUMsR0FBRyxFQUFFLEdBQUcsQ0FBQyxDQUFDO1FBQ2xDLE1BQU0sU0FBUyxHQUFHLE1BQU0sSUFBQSxlQUFJLEVBQUMsS0FBSyxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxFQUFFLEdBQUUsQ0FBQyxDQUFDLENBQUM7UUFDbkQsSUFBSSxTQUFTLEVBQUU7WUFDWCxJQUFJLElBQUksQ0FBQyxHQUFHLEVBQUUsR0FBRyxTQUFTLENBQUMsT0FBTyxHQUFHLElBQUksQ0FBQyxVQUFVLEVBQUU7Z0JBQ2xELE9BQU8sU0FBUyxDQUFDO2FBQ3BCO1lBQ0QsT0FBTyxJQUFBLG1CQUFRLEVBQUMsS0FBSyxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsU0FBUyxDQUFDLENBQUM7U0FDaEQ7UUFDRCxPQUFPLFNBQVMsQ0FBQztJQUNyQixDQUFDO0lBRUQ7OztPQUdHO0lBQ0gsS0FBSyxDQUFDLEdBQUcsQ0FBQyxHQUFXLEVBQUUsS0FBcUQ7UUFDeEUsTUFBTSxJQUFJLENBQUMsV0FBVyxDQUFDO1FBQ3ZCLE1BQU0sS0FBSyxHQUFHLElBQUEsV0FBSSxFQUFDLElBQUksQ0FBQyxHQUFHLEVBQUUsR0FBRyxDQUFDLENBQUM7UUFDbEMsTUFBTSxRQUFRLEdBQUcsSUFBQSxXQUFJLEVBQUMsSUFBSSxDQUFDLEdBQUcsRUFBRSxJQUFBLFNBQU0sR0FBRSxDQUFDLENBQUM7UUFDMUMsTUFBTSxJQUFBLG9CQUFTLEVBQUMsUUFBUSxFQUFFLEtBQUssRUFBRSxFQUFFLElBQUksRUFBRSxLQUFLLEVBQUUsUUFBUSxFQUFFLFFBQVEsRUFBRSxDQUFDLENBQUM7UUFDdEUsTUFBTSxJQUFBLGlCQUFNLEVBQUMsUUFBUSxFQUFFLEtBQUssQ0FBQyxDQUFDO0lBQ2xDLENBQUM7SUFFRDs7T0FFRztJQUNILE9BQU87UUFDSCxPQUFPLElBQUEsa0JBQU8sRUFBQyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUM7SUFDN0IsQ0FBQztJQUVEOzs7OztPQUtHO0lBQ0gsS0FBSyxDQUFDLEtBQUssQ0FBQyxFQUFFLGFBQWEsR0FBRyxJQUFJLEVBQUUsR0FBRyxFQUFFO1FBQ3JDLE1BQU0sSUFBSSxDQUFDLFdBQVcsQ0FBQztRQUV2QixNQUFNLElBQUEsaUJBQU0sRUFBQyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUM7UUFFdkIsSUFBSSxhQUFhLEVBQUU7WUFDZixNQUFNLElBQUEsaUJBQU0sRUFBQyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUM7U0FDMUI7SUFDTCxDQUFDO0NBQ0o7QUEzRkQsMENBMkZDO0FBRUQsTUFBTSxJQUFJLEdBQUcsRUFBRSxHQUFHLElBQUksR0FBRyxJQUFJLENBQUM7QUFFakIsUUFBQSxNQUFNLEdBQUc7SUFDbEIsU0FBUyxFQUFFLElBQUksZUFBZSxDQUFDLHNCQUFzQixFQUFFLENBQUMsR0FBRyxJQUFJLENBQUM7SUFDaEUsWUFBWSxFQUFFLElBQUksZUFBZSxDQUFDLHlCQUF5QixFQUFFLENBQUMsR0FBRyxJQUFJLENBQUM7SUFDdEUsS0FBSyxFQUFFLElBQUksZUFBZSxDQUFDLGlCQUFpQixFQUFFLENBQUMsR0FBRyxJQUFJLENBQUM7Q0FDMUQsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7XG4gICAgbWtkaXJwLFxuICAgIHBhdGhFeGlzdHMsXG4gICAgcmVhZGRpcixcbiAgICByZWFkRmlsZSxcbiAgICByZW1vdmUsXG4gICAgcmVuYW1lLFxuICAgIHN0YXQsXG4gICAgd3JpdGVGaWxlXG59IGZyb20gXCJmcy1leHRyYVwiO1xuaW1wb3J0IHsgaG9tZWRpciB9IGZyb20gXCJvc1wiO1xuaW1wb3J0IHsgam9pbiB9IGZyb20gXCJwYXRoXCI7XG5pbXBvcnQgeyBSZWFkYWJsZSB9IGZyb20gXCJzdHJlYW1cIjtcbmltcG9ydCB7IHY0IGFzIHV1aWR2NCB9IGZyb20gXCJ1dWlkXCI7XG5cbmludGVyZmFjZSBCbG9iIHt9XG5cbi8qKlxuICogQSBzaW1wbGUgcGVyc2lzdGVudCBrZXktdmFsdWUgc3RvcmUuIFVzZWQgdG8gaW1wbGVtZW50IHtAbGluayBMaW1pdHMuY2FjaGV9XG4gKiBmb3Ige0BsaW5rIHRocm90dGxlfS5cbiAqIEByZW1hcmtzXG4gKiBFbnRyaWVzIGNhbiBiZSBleHBpcmVkLCBidXQgYXJlIG5vdCBhY3R1YWxseSBkZWxldGVkIGluZGl2aWR1YWxseS4gVGhlIGVudGlyZVxuICogY2FjaGUgY2FuIGJlIGRlbGV0ZWQgYXQgb25jZS4gSGVuY2UgdGhpcyBjYWNoZSBpcyB1c2VmdWwgZm9yIHN0b3JpbmcgcmVzdWx0c1xuICogdGhhdCBhcmUgZXhwZW5zaXZlIHRvIGNvbXB1dGUgYnV0IGRvIG5vdCBjaGFuZ2UgdG9vIG9mdGVuIChlLmcuIHRoZVxuICogbm9kZV9tb2R1bGVzIGZvbGRlciBmcm9tIGFuICducG0gaW5zdGFsbCcgd2hlcmUgJ3BhY2thZ2UuanNvbicgaXMgbm90XG4gKiBleHBlY3RlZCB0byBjaGFuZ2UgdG9vIG9mdGVuKS5cbiAqXG4gKiBCeSBkZWZhdWx0IGZhYXN0LmpzIHdpbGwgdXNlIHRoZSBkaXJlY3RvcnkgYH4vLmZhYXN0anNgIGFzIGEgbG9jYWwgY2FjaGUgdG9cbiAqIHN0b3JlIGRhdGEgc3VjaCBhcyBwcmljaW5nIHJldHJpZXZlZCBmcm9tIGNsb3VkIEFQSXMsIGFuZCBnYXJiYWdlIGNvbGxlY3Rpb25cbiAqIGluZm9ybWF0aW9uLiBUaGlzIGRpcmVjdG9yeSBjYW4gYmUgc2FmZWx5IGRlbGV0ZWQgaWYgbm8gZmFhc3QuanMgaW5zdGFuY2VzXG4gKiBhcmUgcnVubmluZy5cbiAqIEBwdWJsaWNcbiAqL1xuZXhwb3J0IGNsYXNzIFBlcnNpc3RlbnRDYWNoZSB7XG4gICAgcHJpdmF0ZSBpbml0aWFsaXplZDogUHJvbWlzZTx2b2lkPjtcblxuICAgIHByaXZhdGUgYXN5bmMgaW5pdGlhbGl6ZShkaXI6IHN0cmluZykge1xuICAgICAgICBpZiAoIShhd2FpdCBwYXRoRXhpc3RzKGRpcikpKSB7XG4gICAgICAgICAgICBhd2FpdCBta2RpcnAoZGlyKTtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFRoZSBkaXJlY3Rvcnkgb24gZGlzayB3aGVyZSBjYWNoZWQgdmFsdWVzIGFyZSBzdG9yZWQuXG4gICAgICovXG4gICAgcmVhZG9ubHkgZGlyOiBzdHJpbmc7XG5cbiAgICAvKipcbiAgICAgKiBDb25zdHJ1Y3QgYSBuZXcgcGVyc2lzdGVudCBjYWNoZSwgdHlwaWNhbGx5IHVzZWQgd2l0aCB7QGxpbmsgTGltaXRzfSBhc1xuICAgICAqIHBhcnQgb2YgdGhlIGFyZ3VtZW50cyB0byB7QGxpbmsgdGhyb3R0bGV9LlxuICAgICAqIEBwYXJhbSBkaXJSZWxhdGl2ZVRvSG9tZURpciAtIFRoZSBkaXJlY3RvcnkgdW5kZXIgdGhlIHVzZXIncyBob21lXG4gICAgICogZGlyZWN0b3J5IHRoYXQgd2lsbCBiZSB1c2VkIHRvIHN0b3JlIGNhY2hlZCB2YWx1ZXMuIFRoZSBkaXJlY3Rvcnkgd2lsbCBiZVxuICAgICAqIGNyZWF0ZWQgaWYgaXQgZG9lc24ndCBleGlzdC5cbiAgICAgKiBAcGFyYW0gZXhwaXJhdGlvbiAtIFRoZSBhZ2UgKGluIG1zKSBhZnRlciB3aGljaCBhIGNhY2hlZCBlbnRyeSBpc1xuICAgICAqIGludmFsaWQuIERlZmF1bHQ6IGAyNCozNjAwKjEwMDBgICgxIGRheSkuXG4gICAgICovXG4gICAgY29uc3RydWN0b3IoXG4gICAgICAgIC8qKlxuICAgICAgICAgKiBUaGUgZGlyZWN0b3J5IHVuZGVyIHRoZSB1c2VyJ3MgaG9tZSBkaXJlY3RvcnkgdGhhdCB3aWxsIGJlIHVzZWQgdG9cbiAgICAgICAgICogc3RvcmUgY2FjaGVkIHZhbHVlcy4gVGhlIGRpcmVjdG9yeSB3aWxsIGJlIGNyZWF0ZWQgaWYgaXQgZG9lc24ndFxuICAgICAgICAgKiBleGlzdC5cbiAgICAgICAgICovXG4gICAgICAgIHJlYWRvbmx5IGRpclJlbGF0aXZlVG9Ib21lRGlyOiBzdHJpbmcsXG4gICAgICAgIC8qKlxuICAgICAgICAgKiBUaGUgYWdlIChpbiBtcykgYWZ0ZXIgd2hpY2ggYSBjYWNoZWQgZW50cnkgaXMgaW52YWxpZC4gRGVmYXVsdDpcbiAgICAgICAgICogYDI0KjM2MDAqMTAwMGAgKDEgZGF5KS5cbiAgICAgICAgICovXG4gICAgICAgIHJlYWRvbmx5IGV4cGlyYXRpb246IG51bWJlciA9IDI0ICogMzYwMCAqIDEwMDBcbiAgICApIHtcbiAgICAgICAgdGhpcy5kaXIgPSBqb2luKGhvbWVkaXIoKSwgZGlyUmVsYXRpdmVUb0hvbWVEaXIpO1xuICAgICAgICB0aGlzLmluaXRpYWxpemVkID0gdGhpcy5pbml0aWFsaXplKHRoaXMuZGlyKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBSZXRyaWV2ZXMgdGhlIHZhbHVlIHByZXZpb3VzbHkgc2V0IGZvciB0aGUgZ2l2ZW4ga2V5LCBvciB1bmRlZmluZWQgaWYgdGhlXG4gICAgICoga2V5IGlzIG5vdCBmb3VuZC5cbiAgICAgKi9cbiAgICBhc3luYyBnZXQoa2V5OiBzdHJpbmcpIHtcbiAgICAgICAgYXdhaXQgdGhpcy5pbml0aWFsaXplZDtcbiAgICAgICAgY29uc3QgZW50cnkgPSBqb2luKHRoaXMuZGlyLCBrZXkpO1xuICAgICAgICBjb25zdCBzdGF0RW50cnkgPSBhd2FpdCBzdGF0KGVudHJ5KS5jYXRjaChfID0+IHt9KTtcbiAgICAgICAgaWYgKHN0YXRFbnRyeSkge1xuICAgICAgICAgICAgaWYgKERhdGUubm93KCkgLSBzdGF0RW50cnkubXRpbWVNcyA+IHRoaXMuZXhwaXJhdGlvbikge1xuICAgICAgICAgICAgICAgIHJldHVybiB1bmRlZmluZWQ7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICByZXR1cm4gcmVhZEZpbGUoZW50cnkpLmNhdGNoKF8gPT4gdW5kZWZpbmVkKTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gdW5kZWZpbmVkO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFNldCB0aGUgY2FjaGUga2V5IHRvIHRoZSBnaXZlbiB2YWx1ZS5cbiAgICAgKiBAcmV0dXJucyBhIFByb21pc2UgdGhhdCByZXNvbHZlcyB3aGVuIHRoZSBjYWNoZSBlbnRyeSBoYXMgYmVlbiBwZXJzaXN0ZWQuXG4gICAgICovXG4gICAgYXN5bmMgc2V0KGtleTogc3RyaW5nLCB2YWx1ZTogQnVmZmVyIHwgc3RyaW5nIHwgVWludDhBcnJheSB8IFJlYWRhYmxlIHwgQmxvYikge1xuICAgICAgICBhd2FpdCB0aGlzLmluaXRpYWxpemVkO1xuICAgICAgICBjb25zdCBlbnRyeSA9IGpvaW4odGhpcy5kaXIsIGtleSk7XG4gICAgICAgIGNvbnN0IHRtcEVudHJ5ID0gam9pbih0aGlzLmRpciwgdXVpZHY0KCkpO1xuICAgICAgICBhd2FpdCB3cml0ZUZpbGUodG1wRW50cnksIHZhbHVlLCB7IG1vZGU6IDBvNjAwLCBlbmNvZGluZzogXCJiaW5hcnlcIiB9KTtcbiAgICAgICAgYXdhaXQgcmVuYW1lKHRtcEVudHJ5LCBlbnRyeSk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogUmV0cmlldmUgYWxsIGtleXMgc3RvcmVkIGluIHRoZSBjYWNoZSwgaW5jbHVkaW5nIGV4cGlyZWQgZW50cmllcy5cbiAgICAgKi9cbiAgICBlbnRyaWVzKCkge1xuICAgICAgICByZXR1cm4gcmVhZGRpcih0aGlzLmRpcik7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogRGVsZXRlcyBhbGwgY2FjaGVkIGVudHJpZXMgZnJvbSBkaXNrLlxuICAgICAqIEBwYXJhbSBsZWF2ZUVtcHR5RGlyIC0gSWYgdHJ1ZSwgbGVhdmUgdGhlIGNhY2hlIGRpcmVjdG9yeSBpbiBwbGFjZSBhZnRlclxuICAgICAqIGRlbGV0aW5nIGl0cyBjb250ZW50cy4gSWYgZmFsc2UsIHRoZSBjYWNoZSBkaXJlY3Rvcnkgd2lsbCBiZSByZW1vdmVkLlxuICAgICAqIERlZmF1bHQ6IGB0cnVlYC5cbiAgICAgKi9cbiAgICBhc3luYyBjbGVhcih7IGxlYXZlRW1wdHlEaXIgPSB0cnVlIH0gPSB7fSkge1xuICAgICAgICBhd2FpdCB0aGlzLmluaXRpYWxpemVkO1xuXG4gICAgICAgIGF3YWl0IHJlbW92ZSh0aGlzLmRpcik7XG5cbiAgICAgICAgaWYgKGxlYXZlRW1wdHlEaXIpIHtcbiAgICAgICAgICAgIGF3YWl0IG1rZGlycCh0aGlzLmRpcik7XG4gICAgICAgIH1cbiAgICB9XG59XG5cbmNvbnN0IGRheXMgPSAyNCAqIDM2MDAgKiAxMDAwO1xuXG5leHBvcnQgY29uc3QgY2FjaGVzID0ge1xuICAgIGF3c1ByaWNlczogbmV3IFBlcnNpc3RlbnRDYWNoZShcIi5mYWFzdGpzL2F3cy9wcmljaW5nXCIsIDEgKiBkYXlzKSxcbiAgICBnb29nbGVQcmljZXM6IG5ldyBQZXJzaXN0ZW50Q2FjaGUoXCIuZmFhc3Rqcy9nb29nbGUvcHJpY2luZ1wiLCAxICogZGF5cyksXG4gICAgYXdzR2M6IG5ldyBQZXJzaXN0ZW50Q2FjaGUoXCIuZmFhc3Rqcy9hd3MvZ2NcIiwgNyAqIGRheXMpXG59O1xuIl19 |
\ | No newline at end of file |