@dollhousemcp/mcp-server
Version:
DollhouseMCP - A Model Context Protocol (MCP) server that enables dynamic AI persona management from markdown files, allowing Claude and other compatible AI assistants to activate and switch between different behavioral personas.
160 lines • 24.7 kB
JavaScript
/**
* BackupService - Universal pre-save and pre-delete backups for all element types
*
* Provides automatic backup creation before destructive operations (edit/delete)
* with bounded retention (max N backups per element per date folder).
*
* Design principles:
* - Non-fatal: backup failures never block the primary operation
* - Bounded: pruning prevents unbounded disk growth
* - Configurable: enabled/disabled via env var, max backups configurable
*
* Backup directory structure:
* {backupRootDir}/{elementType}/YYYY-MM-DD/{name}.backup-{ISO-timestamp}.{ext}
*
* @module BackupService
*/
import * as path from 'path';
import { logger } from '../utils/logger.js';
export class BackupService {
fileOperations;
config;
constructor(fileOperations, config) {
this.fileOperations = fileOperations;
this.config = config;
}
/**
* Create a backup copy of a file before it is overwritten (save/edit).
* No-op if backups are disabled or the file doesn't exist yet (new element).
* Non-fatal: catches all errors and returns a result instead of throwing.
*/
async backupBeforeSave(absolutePath, elementType) {
if (!this.config.enabled) {
return { success: false, error: 'backups disabled' };
}
try {
const exists = await this.fileOperations.exists(absolutePath);
if (!exists) {
return { success: false, error: 'source file does not exist (new element)' };
}
const backupPath = await this.createBackup(absolutePath, elementType);
return { success: true, backupPath };
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
logger.warn(`[BackupService] backupBeforeSave failed for ${path.basename(absolutePath)}: ${message}`);
return { success: false, error: message };
}
}
/**
* Create a backup of a file before it is deleted.
* Moves the file to the backup directory (rename) so the caller can skip deleteFile().
* Falls back to copy if rename fails (cross-device).
* Non-fatal: catches all errors and returns a result instead of throwing.
*/
async backupBeforeDelete(absolutePath, elementType) {
if (!this.config.enabled) {
return { success: false, error: 'backups disabled' };
}
try {
const exists = await this.fileOperations.exists(absolutePath);
if (!exists) {
return { success: false, error: 'source file does not exist' };
}
const backupDir = this.getDateBackupDir(elementType);
await this.fileOperations.createDirectory(backupDir);
const originalBasename = path.basename(absolutePath);
const backupFilename = this.generateBackupFilename(originalBasename);
const backupPath = path.join(backupDir, backupFilename);
try {
await this.fileOperations.renameFile(absolutePath, backupPath);
// Rename succeeded — original is gone, safe to prune old backups
await this.pruneBackups(backupDir, originalBasename);
return { success: true, backupPath, movedOriginal: true };
}
catch {
// Cross-device rename fails — fall back to copy.
// Original file still exists; caller must delete it.
// Do NOT prune here: the backup we just created is the safety net
// for the original that hasn't been deleted yet. Pruning could
// evict it before the caller deletes the original, losing the
// only backup. Pruning will happen on the next backupBeforeSave
// or successful rename.
await this.fileOperations.copyFile(absolutePath, backupPath);
return { success: true, backupPath, movedOriginal: false };
}
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
logger.warn(`[BackupService] backupBeforeDelete failed for ${path.basename(absolutePath)}: ${message}`);
return { success: false, error: message };
}
}
/**
* Prune old backups for a given element, keeping only maxBackupsPerElement newest.
*/
async pruneBackups(backupDir, originalBasename) {
try {
const entries = await this.fileOperations.listDirectory(backupDir);
// Match backup files for this specific element
const baseName = this.stripExtension(originalBasename);
const ext = path.extname(originalBasename);
const pattern = `${baseName}.backup-`;
const matching = entries
.filter(e => e.startsWith(pattern) && e.endsWith(ext))
.sort((a, b) => a.localeCompare(b)); // ISO timestamps sort lexicographically
if (matching.length <= this.config.maxBackupsPerElement) {
return;
}
// Delete oldest (sorted ascending, so oldest first)
const toDelete = matching.slice(0, matching.length - this.config.maxBackupsPerElement);
for (const filename of toDelete) {
try {
await this.fileOperations.deleteFile(path.join(backupDir, filename));
}
catch (err) {
logger.debug(`[BackupService] Failed to prune backup ${filename}: ${err}`);
}
}
}
catch (error) {
logger.debug(`[BackupService] pruneBackups failed: ${error}`);
}
}
/**
* Build the date-partitioned backup directory path.
* e.g., {backupRootDir}/personas/2026-03-04/
*/
getDateBackupDir(elementType) {
const today = new Date().toISOString().split('T')[0];
return path.join(this.config.backupRootDir, elementType, today);
}
/**
* Generate a backup filename from the original basename.
* e.g., "creative-writer.md" → "creative-writer.backup-2026-03-04T14-30-00-000Z.md"
*/
generateBackupFilename(originalBasename) {
const ext = path.extname(originalBasename);
const baseName = this.stripExtension(originalBasename);
const timestamp = new Date().toISOString().replace(/:/g, '-');
return `${baseName}.backup-${timestamp}${ext}`;
}
stripExtension(filename) {
const ext = path.extname(filename);
return ext ? filename.slice(0, -ext.length) : filename;
}
/**
* Internal helper used by backupBeforeSave — copies file to backup dir then prunes.
*/
async createBackup(absolutePath, elementType) {
const backupDir = this.getDateBackupDir(elementType);
await this.fileOperations.createDirectory(backupDir);
const originalBasename = path.basename(absolutePath);
const backupFilename = this.generateBackupFilename(originalBasename);
const backupPath = path.join(backupDir, backupFilename);
await this.fileOperations.copyFile(absolutePath, backupPath);
await this.pruneBackups(backupDir, originalBasename);
return backupPath;
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiQmFja3VwU2VydmljZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9zZXJ2aWNlcy9CYWNrdXBTZXJ2aWNlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7Ozs7Ozs7Ozs7R0FlRztBQUVILE9BQU8sS0FBSyxJQUFJLE1BQU0sTUFBTSxDQUFDO0FBRTdCLE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQXNCNUMsTUFBTSxPQUFPLGFBQWE7SUFDUCxjQUFjLENBQXlCO0lBQ3ZDLE1BQU0sQ0FBZTtJQUV0QyxZQUFZLGNBQXNDLEVBQUUsTUFBb0I7UUFDdEUsSUFBSSxDQUFDLGNBQWMsR0FBRyxjQUFjLENBQUM7UUFDckMsSUFBSSxDQUFDLE1BQU0sR0FBRyxNQUFNLENBQUM7SUFDdkIsQ0FBQztJQUVEOzs7O09BSUc7SUFDSCxLQUFLLENBQUMsZ0JBQWdCLENBQUMsWUFBb0IsRUFBRSxXQUFtQjtRQUM5RCxJQUFJLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxPQUFPLEVBQUUsQ0FBQztZQUN6QixPQUFPLEVBQUUsT0FBTyxFQUFFLEtBQUssRUFBRSxLQUFLLEVBQUUsa0JBQWtCLEVBQUUsQ0FBQztRQUN2RCxDQUFDO1FBRUQsSUFBSSxDQUFDO1lBQ0gsTUFBTSxNQUFNLEdBQUcsTUFBTSxJQUFJLENBQUMsY0FBYyxDQUFDLE1BQU0sQ0FBQyxZQUFZLENBQUMsQ0FBQztZQUM5RCxJQUFJLENBQUMsTUFBTSxFQUFFLENBQUM7Z0JBQ1osT0FBTyxFQUFFLE9BQU8sRUFBRSxLQUFLLEVBQUUsS0FBSyxFQUFFLDBDQUEwQyxFQUFFLENBQUM7WUFDL0UsQ0FBQztZQUVELE1BQU0sVUFBVSxHQUFHLE1BQU0sSUFBSSxDQUFDLFlBQVksQ0FBQyxZQUFZLEVBQUUsV0FBVyxDQUFDLENBQUM7WUFDdEUsT0FBTyxFQUFFLE9BQU8sRUFBRSxJQUFJLEVBQUUsVUFBVSxFQUFFLENBQUM7UUFDdkMsQ0FBQztR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QyxNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUMsUUFBUSxDQUFDO0lBQ3pELENBQUM7SUFFRDs7T0FFRztJQUNLLEtBQUssQ0FBQyxZQUFZLENBQUMsWUFBb0IsRUFBRSxXQUFtQjtRQUNsRSxNQUFNLFNBQVMsR0FBRyxJQUFJLENBQUMsZ0JBQWdCLENBQUMsV0FBVyxDQUFDLENBQUM7UUFDckQsTUFBTSxJQUFJLENBQUMsY0FBYyxDQUFDLGVBQWUsQ0FBQyxTQUFTLENBQUMsQ0FBQztRQUVyRCxNQUFNLGdCQUFnQixHQUFHLElBQUksQ0FBQyxRQUFRLENBQUMsWUFBWSxDQUFDLENBQUM7UUFDckQsTUFBTSxjQUFjLEdBQUcsSUFBSSxDQUFDLHNCQUFzQixDQUFDLGdCQUFnQixDQUFDLENBQUM7UUFDckUsTUFBTSxVQUFVLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQyxTQUFTLEVBQUUsY0FBYyxDQUFDLENBQUM7UUFFeEQsTUFBTSxJQUFJLENBQUMsY0FBYyxDQUFDLFFBQVEsQ0FBQyxZQUFZLEVBQUUsVUFBVSxDQUFDLENBQUM7UUFDN0QsTUFBTSxJQUFJLENBQUMsWUFBWSxDQUFDLFNBQVMsRUFBRSxnQkFBZ0IsQ0FBQyxDQUFDO1FBRXJELE9BQU8sVUFBVSxDQUFDO0lBQ3BCLENBQUM7Q0FDRiIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogQmFja3VwU2VydmljZSAtIFVuaXZlcnNhbCBwcmUtc2F2ZSBhbmQgcHJlLWRlbGV0ZSBiYWNrdXBzIGZvciBhbGwgZWxlbWVudCB0eXBlc1xuICpcbiAqIFByb3ZpZGVzIGF1dG9tYXRpYyBiYWNrdXAgY3JlYXRpb24gYmVmb3JlIGRlc3RydWN0aXZlIG9wZXJhdGlvbnMgKGVkaXQvZGVsZXRlKVxuICogd2l0aCBib3VuZGVkIHJldGVudGlvbiAobWF4IE4gYmFja3VwcyBwZXIgZWxlbWVudCBwZXIgZGF0ZSBmb2xkZXIpLlxuICpcbiAqIERlc2lnbiBwcmluY2lwbGVzOlxuICogLSBOb24tZmF0YWw6IGJhY2t1cCBmYWlsdXJlcyBuZXZlciBibG9jayB0aGUgcHJpbWFyeSBvcGVyYXRpb25cbiAqIC0gQm91bmRlZDogcHJ1bmluZyBwcmV2ZW50cyB1bmJvdW5kZWQgZGlzayBncm93dGhcbiAqIC0gQ29uZmlndXJhYmxlOiBlbmFibGVkL2Rpc2FibGVkIHZpYSBlbnYgdmFyLCBtYXggYmFja3VwcyBjb25maWd1cmFibGVcbiAqXG4gKiBCYWNrdXAgZGlyZWN0b3J5IHN0cnVjdHVyZTpcbiAqICAge2JhY2t1cFJvb3REaXJ9L3tlbGVtZW50VHlwZX0vWVlZWS1NTS1ERC97bmFtZX0uYmFja3VwLXtJU08tdGltZXN0YW1wfS57ZXh0fVxuICpcbiAqIEBtb2R1bGUgQmFja3VwU2VydmljZVxuICovXG5cbmltcG9ydCAqIGFzIHBhdGggZnJvbSAncGF0aCc7XG5pbXBvcnQgdHlwZSB7IElGaWxlT3BlcmF0aW9uc1NlcnZpY2UgfSBmcm9tICcuL0ZpbGVPcGVyYXRpb25zU2VydmljZS5qcyc7XG5pbXBvcnQgeyBsb2dnZXIgfSBmcm9tICcuLi91dGlscy9sb2dnZXIuanMnO1xuXG5leHBvcnQgaW50ZXJmYWNlIEJhY2t1cENvbmZpZyB7XG4gIC8qKiBSb290IGRpcmVjdG9yeSBmb3IgYWxsIGJhY2t1cHMgKGUuZy4sIH4vLmRvbGxob3VzZS9wb3J0Zm9saW8vLmJhY2t1cHMpICovXG4gIGJhY2t1cFJvb3REaXI6IHN0cmluZztcbiAgLyoqIE1heGltdW0gYmFja3VwIGZpbGVzIHRvIGtlZXAgcGVyIGVsZW1lbnQgcGVyIGRhdGUgZm9sZGVyICovXG4gIG1heEJhY2t1cHNQZXJFbGVtZW50OiBudW1iZXI7XG4gIC8qKiBXaGV0aGVyIGJhY2t1cHMgYXJlIGVuYWJsZWQgKi9cbiAgZW5hYmxlZDogYm9vbGVhbjtcbn1cblxuZXhwb3J0IGludGVyZmFjZSBCYWNrdXBSZXN1bHQge1xuICAvKiogV2hldGhlciBhIGJhY2t1cCB3YXMgc3VjY2Vzc2Z1bGx5IGNyZWF0ZWQgKGJ5IHJlbmFtZSBvciBjb3B5KSAqL1xuICBzdWNjZXNzOiBib29sZWFuO1xuICAvKiogUGF0aCB0byB0aGUgYmFja3VwIGZpbGUsIGlmIGNyZWF0ZWQgKi9cbiAgYmFja3VwUGF0aD86IHN0cmluZztcbiAgLyoqIFdoZXRoZXIgdGhlIG9yaWdpbmFsIGZpbGUgd2FzIG1vdmVkIChyZW5hbWVkKSB0byB0aGUgYmFja3VwIGxvY2F0aW9uLlxuICAgKiAgV2hlbiB0cnVlLCB0aGUgY2FsbGVyIHNob3VsZCBza2lwIGRlbGV0aW5nIHRoZSBvcmlnaW5hbCBmaWxlLiAqL1xuICBtb3ZlZE9yaWdpbmFsPzogYm9vbGVhbjtcbiAgZXJyb3I/OiBzdHJpbmc7XG59XG5cbmV4cG9ydCBjbGFzcyBCYWNrdXBTZXJ2aWNlIHtcbiAgcHJpdmF0ZSByZWFkb25seSBmaWxlT3BlcmF0aW9uczogSUZpbGVPcGVyYXRpb25zU2VydmljZTtcbiAgcHJpdmF0ZSByZWFkb25seSBjb25maWc6IEJhY2t1cENvbmZpZztcblxuICBjb25zdHJ1Y3RvcihmaWxlT3BlcmF0aW9uczogSUZpbGVPcGVyYXRpb25zU2VydmljZSwgY29uZmlnOiBCYWNrdXBDb25maWcpIHtcbiAgICB0aGlzLmZpbGVPcGVyYXRpb25zID0gZmlsZU9wZXJhdGlvbnM7XG4gICAgdGhpcy5jb25maWcgPSBjb25maWc7XG4gIH1cblxuICAvKipcbiAgICogQ3JlYXRlIGEgYmFja3VwIGNvcHkgb2YgYSBmaWxlIGJlZm9yZSBpdCBpcyBvdmVyd3JpdHRlbiAoc2F2ZS9lZGl0KS5cbiAgICogTm8tb3AgaWYgYmFja3VwcyBhcmUgZGlzYWJsZWQgb3IgdGhlIGZpbGUgZG9lc24ndCBleGlzdCB5ZXQgKG5ldyBlbGVtZW50KS5cbiAgICogTm9uLWZhdGFsOiBjYXRjaGVzIGFsbCBlcnJvcnMgYW5kIHJldHVybnMgYSByZXN1bHQgaW5zdGVhZCBvZiB0aHJvd2luZy5cbiAgICovXG4gIGFzeW5jIGJhY2t1cEJlZm9yZVNhdmUoYWJzb2x1dGVQYXRoOiBzdHJpbmcsIGVsZW1lbnRUeXBlOiBzdHJpbmcpOiBQcm9taXNlPEJhY2t1cFJlc3VsdD4ge1xuICAgIGlmICghdGhpcy5jb25maWcuZW5hYmxlZCkge1xuICAgICAgcmV0dXJuIHsgc3VjY2VzczogZmFsc2UsIGVycm9yOiAnYmFja3VwcyBkaXNhYmxlZCcgfTtcbiAgICB9XG5cbiAgICB0cnkge1xuICAgICAgY29uc3QgZXhpc3RzID0gYXdhaXQgdGhpcy5maWxlT3BlcmF0aW9ucy5leGlzdHMoYWJzb2x1dGVQYXRoKTtcbiAgICAgIGlmICghZXhpc3RzKSB7XG4gICAgICAgIHJldHVybiB7IHN1Y2Nlc3M6IGZhbHNlLCBlcnJvcjogJ3NvdXJjZSBmaWxlIGRvZXMgbm90IGV4aXN0IChuZXcgZWxlbWVudCknIH07XG4gICAgICB9XG5cbiAgICAgIGNvbnN0IGJhY2t1cFBhdGggPSBhd2FpdCB0aGlzLmNyZWF0ZUJhY2t1cChhYnNvbHV0ZVBhdGgsIGVsZW1lbnRUeXBlKTtcbiAgICAgIHJldHVybiB7IHN1Y2Nlc3M6IHRydWUsIGJhY2t1cFBhdGggfTtcbiAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgY29uc3QgbWVzc2FnZSA9IGVycm9yIGluc3RhbmNlb2YgRXJyb3IgPyBlcnJvci5tZXNzYWdlIDogU3RyaW5nKGVycm9yKTtcbiAgICAgIGxvZ2dlci53YXJuKGBbQmFja3VwU2VydmljZV0gYmFja3VwQmVmb3JlU2F2ZSBmYWlsZWQgZm9yICR7cGF0aC5iYXNlbmFtZShhYnNvbHV0ZVBhdGgpfTogJHttZXNzYWdlfWApO1xuICAgICAgcmV0dXJuIHsgc3VjY2VzczogZmFsc2UsIGVycm9yOiBtZXNzYWdlIH07XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIENyZWF0ZSBhIGJhY2t1cCBvZiBhIGZpbGUgYmVmb3JlIGl0IGlzIGRlbGV0ZWQuXG4gICAqIE1vdmVzIHRoZSBmaWxlIHRvIHRoZSBiYWNrdXAgZGlyZWN0b3J5IChyZW5hbWUpIHNvIHRoZSBjYWxsZXIgY2FuIHNraXAgZGVsZXRlRmlsZSgpLlxuICAgKiBGYWxscyBiYWNrIHRvIGNvcHkgaWYgcmVuYW1lIGZhaWxzIChjcm9zcy1kZXZpY2UpLlxuICAgKiBOb24tZmF0YWw6IGNhdGNoZXMgYWxsIGVycm9ycyBhbmQgcmV0dXJucyBhIHJlc3VsdCBpbnN0ZWFkIG9mIHRocm93aW5nLlxuICAgKi9cbiAgYXN5bmMgYmFja3VwQmVmb3JlRGVsZXRlKGFic29sdXRlUGF0aDogc3RyaW5nLCBlbGVtZW50VHlwZTogc3RyaW5nKTogUHJvbWlzZTxCYWNrdXBSZXN1bHQ+IHtcbiAgICBpZiAoIXRoaXMuY29uZmlnLmVuYWJsZWQpIHtcbiAgICAgIHJldHVybiB7IHN1Y2Nlc3M6IGZhbHNlLCBlcnJvcjogJ2JhY2t1cHMgZGlzYWJsZWQnIH07XG4gICAgfVxuXG4gICAgdHJ5IHtcbiAgICAgIGNvbnN0IGV4aXN0cyA9IGF3YWl0IHRoaXMuZmlsZU9wZXJhdGlvbnMuZXhpc3RzKGFic29sdXRlUGF0aCk7XG4gICAgICBpZiAoIWV4aXN0cykge1xuICAgICAgICByZXR1cm4geyBzdWNjZXNzOiBmYWxzZSwgZXJyb3I6ICdzb3VyY2UgZmlsZSBkb2VzIG5vdCBleGlzdCcgfTtcbiAgICAgIH1cblxuICAgICAgY29uc3QgYmFja3VwRGlyID0gdGhpcy5nZXREYXRlQmFja3VwRGlyKGVsZW1lbnRUeXBlKTtcbiAgICAgIGF3YWl0IHRoaXMuZmlsZU9wZXJhdGlvbnMuY3JlYXRlRGlyZWN0b3J5KGJhY2t1cERpcik7XG5cbiAgICAgIGNvbnN0IG9yaWdpbmFsQmFzZW5hbWUgPSBwYXRoLmJhc2VuYW1lKGFic29sdXRlUGF0aCk7XG4gICAgICBjb25zdCBiYWNrdXBGaWxlbmFtZSA9IHRoaXMuZ2VuZXJhdGVCYWNrdXBGaWxlbmFtZShvcmlnaW5hbEJhc2VuYW1lKTtcbiAgICAgIGNvbnN0IGJhY2t1cFBhdGggPSBwYXRoLmpvaW4oYmFja3VwRGlyLCBiYWNrdXBGaWxlbmFtZSk7XG5cbiAgICAgIHRyeSB7XG4gICAgICAgIGF3YWl0IHRoaXMuZmlsZU9wZXJhdGlvbnMucmVuYW1lRmlsZShhYnNvbHV0ZVBhdGgsIGJhY2t1cFBhdGgpO1xuICAgICAgICAvLyBSZW5hbWUgc3VjY2VlZGVkIOKAlCBvcmlnaW5hbCBpcyBnb25lLCBzYWZlIHRvIHBydW5lIG9sZCBiYWNrdXBzXG4gICAgICAgIGF3YWl0IHRoaXMucHJ1bmVCYWNrdXBzKGJhY2t1cERpciwgb3JpZ2luYWxCYXNlbmFtZSk7XG4gICAgICAgIHJldHVybiB7IHN1Y2Nlc3M6IHRydWUsIGJhY2t1cFBhdGgsIG1vdmVkT3JpZ2luYWw6IHRydWUgfTtcbiAgICAgIH0gY2F0Y2gge1xuICAgICAgICAvLyBDcm9zcy1kZXZpY2UgcmVuYW1lIGZhaWxzIOKAlCBmYWxsIGJhY2sgdG8gY29weS5cbiAgICAgICAgLy8gT3JpZ2luYWwgZmlsZSBzdGlsbCBleGlzdHM7IGNhbGxlciBtdXN0IGRlbGV0ZSBpdC5cbiAgICAgICAgLy8gRG8gTk9UIHBydW5lIGhlcmU6IHRoZSBiYWNrdXAgd2UganVzdCBjcmVhdGVkIGlzIHRoZSBzYWZldHkgbmV0XG4gICAgICAgIC8vIGZvciB0aGUgb3JpZ2luYWwgdGhhdCBoYXNuJ3QgYmVlbiBkZWxldGVkIHlldC4gUHJ1bmluZyBjb3VsZFxuICAgICAgICAvLyBldmljdCBpdCBiZWZvcmUgdGhlIGNhbGxlciBkZWxldGVzIHRoZSBvcmlnaW5hbCwgbG9zaW5nIHRoZVxuICAgICAgICAvLyBvbmx5IGJhY2t1cC4gUHJ1bmluZyB3aWxsIGhhcHBlbiBvbiB0aGUgbmV4dCBiYWNrdXBCZWZvcmVTYXZlXG4gICAgICAgIC8vIG9yIHN1Y2Nlc3NmdWwgcmVuYW1lLlxuICAgICAgICBhd2FpdCB0aGlzLmZpbGVPcGVyYXRpb25zLmNvcHlGaWxlKGFic29sdXRlUGF0aCwgYmFja3VwUGF0aCk7XG4gICAgICAgIHJldHVybiB7IHN1Y2Nlc3M6IHRydWUsIGJhY2t1cFBhdGgsIG1vdmVkT3JpZ2luYWw6IGZhbHNlIH07XG4gICAgICB9XG4gICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgIGNvbnN0IG1lc3NhZ2UgPSBlcnJvciBpbnN0YW5jZW9mIEVycm9yID8gZXJyb3IubWVzc2FnZSA6IFN0cmluZyhlcnJvcik7XG4gICAgICBsb2dnZXIud2FybihgW0JhY2t1cFNlcnZpY2VdIGJhY2t1cEJlZm9yZURlbGV0ZSBmYWlsZWQgZm9yICR7cGF0aC5iYXNlbmFtZShhYnNvbHV0ZVBhdGgpfTogJHttZXNzYWdlfWApO1xuICAgICAgcmV0dXJuIHsgc3VjY2VzczogZmFsc2UsIGVycm9yOiBtZXNzYWdlIH07XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIFBydW5lIG9sZCBiYWNrdXBzIGZvciBhIGdpdmVuIGVsZW1lbnQsIGtlZXBpbmcgb25seSBtYXhCYWNrdXBzUGVyRWxlbWVudCBuZXdlc3QuXG4gICAqL1xuICBhc3luYyBwcnVuZUJhY2t1cHMoYmFja3VwRGlyOiBzdHJpbmcsIG9yaWdpbmFsQmFzZW5hbWU6IHN0cmluZyk6IFByb21pc2U8dm9pZD4ge1xuICAgIHRyeSB7XG4gICAgICBjb25zdCBlbnRyaWVzID0gYXdhaXQgdGhpcy5maWxlT3BlcmF0aW9ucy5saXN0RGlyZWN0b3J5KGJhY2t1cERpcik7XG5cbiAgICAgIC8vIE1hdGNoIGJhY2t1cCBmaWxlcyBmb3IgdGhpcyBzcGVjaWZpYyBlbGVtZW50XG4gICAgICBjb25zdCBiYXNlTmFtZSA9IHRoaXMuc3RyaXBFeHRlbnNpb24ob3JpZ2luYWxCYXNlbmFtZSk7XG4gICAgICBjb25zdCBleHQgPSBwYXRoLmV4dG5hbWUob3JpZ2luYWxCYXNlbmFtZSk7XG4gICAgICBjb25zdCBwYXR0ZXJuID0gYCR7YmFzZU5hbWV9LmJhY2t1cC1gO1xuXG4gICAgICBjb25zdCBtYXRjaGluZyA9IGVudHJpZXNcbiAgICAgICAgLmZpbHRlcihlID0+IGUuc3RhcnRzV2l0aChwYXR0ZXJuKSAmJiBlLmVuZHNXaXRoKGV4dCkpXG4gICAgICAgIC5zb3J0KChhLCBiKSA9PiBhLmxvY2FsZUNvbXBhcmUoYikpOyAvLyBJU08gdGltZXN0YW1wcyBzb3J0IGxleGljb2dyYXBoaWNhbGx5XG5cbiAgICAgIGlmIChtYXRjaGluZy5sZW5ndGggPD0gdGhpcy5jb25maWcubWF4QmFja3Vwc1BlckVsZW1lbnQpIHtcbiAgICAgICAgcmV0dXJuO1xuICAgICAgfVxuXG4gICAgICAvLyBEZWxldGUgb2xkZXN0IChzb3J0ZWQgYXNjZW5kaW5nLCBzbyBvbGRlc3QgZmlyc3QpXG4gICAgICBjb25zdCB0b0RlbGV0ZSA9IG1hdGNoaW5nLnNsaWNlKDAsIG1hdGNoaW5nLmxlbmd0aCAtIHRoaXMuY29uZmlnLm1heEJhY2t1cHNQZXJFbGVtZW50KTtcbiAgICAgIGZvciAoY29uc3QgZmlsZW5hbWUgb2YgdG9EZWxldGUpIHtcbiAgICAgICAgdHJ5IHtcbiAgICAgICAgICBhd2FpdCB0aGlzLmZpbGVPcGVyYXRpb25zLmRlbGV0ZUZpbGUocGF0aC5qb2luKGJhY2t1cERpciwgZmlsZW5hbWUpKTtcbiAgICAgICAgfSBjYXRjaCAoZXJyKSB7XG4gICAgICAgICAgbG9nZ2VyLmRlYnVnKGBbQmFja3VwU2VydmljZV0gRmFpbGVkIHRvIHBydW5lIGJhY2t1cCAke2ZpbGVuYW1lfTogJHtlcnJ9YCk7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgbG9nZ2VyLmRlYnVnKGBbQmFja3VwU2VydmljZV0gcHJ1bmVCYWNrdXBzIGZhaWxlZDogJHtlcnJvcn1gKTtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogQnVpbGQgdGhlIGRhdGUtcGFydGl0aW9uZWQgYmFja3VwIGRpcmVjdG9yeSBwYXRoLlxuICAgKiBlLmcuLCB7YmFja3VwUm9vdERpcn0vcGVyc29uYXMvMjAyNi0wMy0wNC9cbiAgICovXG4gIHByaXZhdGUgZ2V0RGF0ZUJhY2t1cERpcihlbGVtZW50VHlwZTogc3RyaW5nKTogc3RyaW5nIHtcbiAgICBjb25zdCB0b2RheSA9IG5ldyBEYXRlKCkudG9JU09TdHJpbmcoKS5zcGxpdCgnVCcpWzBdO1xuICAgIHJldHVybiBwYXRoLmpvaW4odGhpcy5jb25maWcuYmFja3VwUm9vdERpciwgZWxlbWVudFR5cGUsIHRvZGF5KTtcbiAgfVxuXG4gIC8qKlxuICAgKiBHZW5lcmF0ZSBhIGJhY2t1cCBmaWxlbmFtZSBmcm9tIHRoZSBvcmlnaW5hbCBiYXNlbmFtZS5cbiAgICogZS5nLiwgXCJjcmVhdGl2ZS13cml0ZXIubWRcIiDihpIgXCJjcmVhdGl2ZS13cml0ZXIuYmFja3VwLTIwMjYtMDMtMDRUMTQtMzAtMDAtMDAwWi5tZFwiXG4gICAqL1xuICBwcml2YXRlIGdlbmVyYXRlQmFja3VwRmlsZW5hbWUob3JpZ2luYWxCYXNlbmFtZTogc3RyaW5nKTogc3RyaW5nIHtcbiAgICBjb25zdCBleHQgPSBwYXRoLmV4dG5hbWUob3JpZ2luYWxCYXNlbmFtZSk7XG4gICAgY29uc3QgYmFzZU5hbWUgPSB0aGlzLnN0cmlwRXh0ZW5zaW9uKG9yaWdpbmFsQmFzZW5hbWUpO1xuICAgIGNvbnN0IHRpbWVzdGFtcCA9IG5ldyBEYXRlKCkudG9JU09TdHJpbmcoKS5yZXBsYWNlKC86L2csICctJyk7XG4gICAgcmV0dXJuIGAke2Jhc2VOYW1lfS5iYWNrdXAtJHt0aW1lc3RhbXB9JHtleHR9YDtcbiAgfVxuXG4gIHByaXZhdGUgc3RyaXBFeHRlbnNpb24oZmlsZW5hbWU6IHN0cmluZyk6IHN0cmluZyB7XG4gICAgY29uc3QgZXh0ID0gcGF0aC5leHRuYW1lKGZpbGVuYW1lKTtcbiAgICByZXR1cm4gZXh0ID8gZmlsZW5hbWUuc2xpY2UoMCwgLWV4dC5sZW5ndGgpIDogZmlsZW5hbWU7XG4gIH1cblxuICAvKipcbiAgICogSW50ZXJuYWwgaGVscGVyIHVzZWQgYnkgYmFja3VwQmVmb3JlU2F2ZSDigJQgY29waWVzIGZpbGUgdG8gYmFja3VwIGRpciB0aGVuIHBydW5lcy5cbiAgICovXG4gIHByaXZhdGUgYXN5bmMgY3JlYXRlQmFja3VwKGFic29sdXRlUGF0aDogc3RyaW5nLCBlbGVtZW50VHlwZTogc3RyaW5nKTogUHJvbWlzZTxzdHJpbmc+IHtcbiAgICBjb25zdCBiYWNrdXBEaXIgPSB0aGlzLmdldERhdGVCYWNrdXBEaXIoZWxlbWVudFR5cGUpO1xuICAgIGF3YWl0IHRoaXMuZmlsZU9wZXJhdGlvbnMuY3JlYXRlRGlyZWN0b3J5KGJhY2t1cERpcik7XG5cbiAgICBjb25zdCBvcmlnaW5hbEJhc2VuYW1lID0gcGF0aC5iYXNlbmFtZShhYnNvbHV0ZVBhdGgpO1xuICAgIGNvbnN0IGJhY2t1cEZpbGVuYW1lID0gdGhpcy5nZW5lcmF0ZUJhY2t1cEZpbGVuYW1lKG9yaWdpbmFsQmFzZW5hbWUpO1xuICAgIGNvbnN0IGJhY2t1cFBhdGggPSBwYXRoLmpvaW4oYmFja3VwRGlyLCBiYWNrdXBGaWxlbmFtZSk7XG5cbiAgICBhd2FpdCB0aGlzLmZpbGVPcGVyYXRpb25zLmNvcHlGaWxlKGFic29sdXRlUGF0aCwgYmFja3VwUGF0aCk7XG4gICAgYXdhaXQgdGhpcy5wcnVuZUJhY2t1cHMoYmFja3VwRGlyLCBvcmlnaW5hbEJhc2VuYW1lKTtcblxuICAgIHJldHVybiBiYWNrdXBQYXRoO1xuICB9XG59XG4iXX0=