@mickdarling/dollhousemcp
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.
260 lines • 33.9 kB
JavaScript
/**
* Manage backups during updates
*/
import * as fs from 'fs/promises';
import * as fsSync from 'fs';
import * as path from 'path';
import { safeExec } from '../utils/git.js';
export class BackupManager {
rootDir;
backupsDir;
constructor(rootDir) {
// Validate rootDir parameter if provided
if (rootDir) {
// Prevent path traversal attacks first
if (rootDir.includes('../') || rootDir.includes('..\\')) {
throw new Error('rootDir cannot contain path traversal sequences');
}
// Then check if it's absolute
if (!path.isAbsolute(rootDir)) {
throw new Error('rootDir must be an absolute path');
}
}
// Allow override for testing, default to process.cwd()
this.rootDir = rootDir || process.cwd();
// Safety check: Don't allow operations on directories containing critical files
// This prevents accidental deletion of the actual project directory
if (this.hasProductionFiles() && !this.isSafeTestDirectory()) {
throw new Error('BackupManager cannot operate on production directory. Pass a safe test directory to the constructor.');
}
this.backupsDir = path.join(this.rootDir, "..", "dollhousemcp-backups");
}
/**
* Check if the directory contains production files
*/
hasProductionFiles() {
try {
const productionIndicators = [
'package.json',
'tsconfig.json',
'.git',
'src',
'LICENSE'
];
const files = fsSync.readdirSync(this.rootDir);
const hasProductionFile = productionIndicators.some(indicator => files.includes(indicator));
// Additional check: if package.json exists, check if it's a real project
if (hasProductionFile && files.includes('package.json')) {
const packageJsonPath = path.join(this.rootDir, 'package.json');
const packageJson = JSON.parse(fsSync.readFileSync(packageJsonPath, 'utf-8'));
// If it has a name and dependencies, it's likely a real project
return !!(packageJson.name && packageJson.dependencies);
}
return hasProductionFile;
}
catch {
// If we can't read the directory, assume it's safe
return false;
}
}
/**
* Check if this appears to be a safe test directory
*/
isSafeTestDirectory() {
const safePaths = ['test', 'tmp', 'temp', '.test', '__test__'];
const dirPath = this.rootDir.toLowerCase();
// Check if running in Docker container
if (this.isDockerEnvironment()) {
return true; // Docker containers are immutable, updates don't apply
}
return safePaths.some(safe => dirPath.includes(safe));
}
/**
* Check if running in a Docker container
*/
isDockerEnvironment() {
// Check common Docker indicators
if (process.env.DOLLHOUSE_DISABLE_UPDATES === 'true') {
return true;
}
// Check if running from /app directory (common Docker practice)
if (this.rootDir === '/app') {
return true;
}
// Check for Docker-specific files
try {
const hasDockerEnv = fsSync.existsSync('/.dockerenv');
if (hasDockerEnv) {
return true;
}
}
catch {
// Ignore errors
}
return false;
}
/**
* Create a backup of the current installation
*/
async createBackup(version) {
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const backupName = `backup-${timestamp}`;
const backupPath = path.join(this.backupsDir, backupName);
// Ensure backups directory exists
await fs.mkdir(this.backupsDir, { recursive: true });
// Use git to create a clean copy (respecting .gitignore)
await safeExec('git', [
'archive',
'--format=tar',
'HEAD'
], { cwd: this.rootDir }).then(async ({ stdout }) => {
// Create backup directory
await fs.mkdir(backupPath, { recursive: true });
// Extract tar to backup directory
const tarPath = path.join(backupPath, 'archive.tar');
await fs.writeFile(tarPath, stdout);
// Extract using tar command
await safeExec('tar', ['-xf', 'archive.tar'], { cwd: backupPath });
await fs.unlink(tarPath);
});
// Also backup node_modules if it exists
const nodeModulesPath = path.join(this.rootDir, 'node_modules');
try {
await fs.access(nodeModulesPath);
await safeExec('cp', ['-r', 'node_modules', backupPath], { cwd: this.rootDir });
}
catch {
// node_modules doesn't exist or copy failed, that's okay
}
// Backup all persona files (including user-created ones not in git)
const personasDir = path.join(this.rootDir, 'personas');
const backupPersonasDir = path.join(backupPath, 'personas');
try {
await fs.access(personasDir);
await fs.mkdir(backupPersonasDir, { recursive: true });
const personaFiles = await fs.readdir(personasDir);
for (const file of personaFiles) {
if (file.endsWith('.md')) {
const sourcePath = path.join(personasDir, file);
const destPath = path.join(backupPersonasDir, file);
// Copy the file, overwriting if it already exists from git archive
await fs.copyFile(sourcePath, destPath);
}
}
}
catch (error) {
// Log warning but don't fail the backup
console.error('Warning: Could not backup all personas:', error);
}
// Save backup metadata
const metadata = {
timestamp,
version,
createdAt: new Date().toISOString()
};
await fs.writeFile(path.join(backupPath, 'backup-metadata.json'), JSON.stringify(metadata, null, 2));
return {
path: backupPath,
timestamp,
version
};
}
/**
* List available backups
*/
async listBackups() {
try {
const entries = await fs.readdir(this.backupsDir, { withFileTypes: true });
const backups = [];
for (const entry of entries) {
if (entry.isDirectory() && entry.name.startsWith('backup-')) {
const backupPath = path.join(this.backupsDir, entry.name);
const timestamp = entry.name.replace('backup-', '');
// Try to read metadata
let version;
try {
const metadataPath = path.join(backupPath, 'backup-metadata.json');
const metadata = JSON.parse(await fs.readFile(metadataPath, 'utf-8'));
version = metadata.version;
}
catch {
// No metadata file, that's okay
}
backups.push({
path: backupPath,
timestamp,
version
});
}
}
// Sort by timestamp descending (newest first)
return backups.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
}
catch {
return [];
}
}
/**
* Get the most recent backup
*/
async getLatestBackup() {
const backups = await this.listBackups();
return backups.length > 0 ? backups[0] : null;
}
/**
* Restore from a backup
*/
async restoreBackup(backupPath) {
// Verify backup exists
try {
await fs.access(backupPath);
}
catch {
throw new Error(`Backup not found: ${backupPath}`);
}
// Create a temporary directory for the current state
const tempDir = path.join(this.backupsDir, 'temp-current');
await fs.mkdir(tempDir, { recursive: true });
// Move current files to temp (except .git and node_modules)
const entries = await fs.readdir(this.rootDir);
for (const entry of entries) {
if (entry !== '.git' && entry !== 'node_modules' && entry !== 'dist') {
const sourcePath = path.join(this.rootDir, entry);
const destPath = path.join(tempDir, entry);
await fs.rename(sourcePath, destPath);
}
}
// Copy backup files to root
const backupEntries = await fs.readdir(backupPath);
for (const entry of backupEntries) {
if (entry !== 'backup-metadata.json' && entry !== '.git') {
const sourcePath = path.join(backupPath, entry);
const destPath = path.join(this.rootDir, entry);
await safeExec('cp', ['-r', sourcePath, destPath]);
}
}
// Clean up temp directory
await fs.rm(tempDir, { recursive: true, force: true });
}
/**
* Clean up old backups (keep the 5 most recent)
*/
async cleanupOldBackups(keepCount = 5) {
const backups = await this.listBackups();
let deletedCount = 0;
if (backups.length > keepCount) {
const backupsToDelete = backups.slice(keepCount);
for (const backup of backupsToDelete) {
try {
await fs.rm(backup.path, { recursive: true, force: true });
deletedCount++;
}
catch (error) {
console.error(`Failed to delete backup ${backup.path}:`, error);
}
}
}
return deletedCount;
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiQmFja3VwTWFuYWdlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy91cGRhdGUvQmFja3VwTWFuYWdlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7R0FFRztBQUVILE9BQU8sS0FBSyxFQUFFLE1BQU0sYUFBYSxDQUFDO0FBQ2xDLE9BQU8sS0FBSyxNQUFNLE1BQU0sSUFBSSxDQUFDO0FBQzdCLE9BQU8sS0FBSyxJQUFJLE1BQU0sTUFBTSxDQUFDO0FBQzdCLE9BQU8sRUFBRSxRQUFRLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQVEzQyxNQUFNLE9BQU8sYUFBYTtJQUNoQixPQUFPLENBQVM7SUFDaEIsVUFBVSxDQUFTO0lBRTNCLFlBQVksT0FBZ0I7UUFDMUIseUNBQXlDO1FBQ3pDLElBQUksT0FBTyxFQUFFLENBQUM7WUFDWix1Q0FBdUM7WUFDdkMsSUFBSSxPQUFPLENBQUMsUUFBUSxDQUFDLEtBQUssQ0FBQyxJQUFJLE9BQU8sQ0FBQyxRQUFRLENBQUMsTUFBTSxDQUFDLEVBQUUsQ0FBQztnQkFDeEQsTUFBTSxJQUFJLEtBQUssQ0FBQyxpREFBaUQsQ0FBQyxDQUFDO1lBQ3JFLENBQUM7WUFDRCw4QkFBOEI7WUFDOUIsSUFBSSxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsT0FBTyxDQUFDLEVBQUUsQ0FBQztnQkFDOUIsTUFBTSxJQUFJLEtBQUssQ0FBQyxrQ0FBa0MsQ0FBQyxDQUFDO1lBQ3RELENBQUM7UUFDSCxDQUFDO1FBRUQsdURBQXVEO1FBQ3ZELElBQUksQ0FBQyxPQUFPLEdBQUcsT0FBTyxJQUFJLE9BQU8sQ0FBQyxHQUFHLEVBQUUsQ0FBQztRQUV4QyxnRkFBZ0Y7UUFDaEYsb0VBQW9FO1FBQ3BFLElBQUksSUFBSSxDQUFDLGtCQUFrQixFQUFFLElBQUksQ0FBQyxJQUFJLENBQUMsbUJBQW1CLEVBQUUsRUFBRSxDQUFDO1lBQzdELE1BQU0sSUFBSSxLQUFLLENBQUMsc0dBQXNHLENBQUMsQ0FB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LCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIE1hbmFnZSBiYWNrdXBzIGR1cmluZyB1cGRhdGVzXG4gKi9cblxuaW1wb3J0ICogYXMgZnMgZnJvbSAnZnMvcHJvbWlzZXMnO1xuaW1wb3J0ICogYXMgZnNTeW5jIGZyb20gJ2ZzJztcbmltcG9ydCAqIGFzIHBhdGggZnJvbSAncGF0aCc7XG5pbXBvcnQgeyBzYWZlRXhlYyB9IGZyb20gJy4uL3V0aWxzL2dpdC5qcyc7XG5cbmV4cG9ydCBpbnRlcmZhY2UgQmFja3VwSW5mbyB7XG4gIHBhdGg6IHN0cmluZztcbiAgdGltZXN0YW1wOiBzdHJpbmc7XG4gIHZlcnNpb24/OiBzdHJpbmc7XG59XG5cbmV4cG9ydCBjbGFzcyBCYWNrdXBNYW5hZ2VyIHtcbiAgcHJpdmF0ZSByb290RGlyOiBzdHJpbmc7XG4gIHByaXZhdGUgYmFja3Vwc0Rpcjogc3RyaW5nO1xuICBcbiAgY29uc3RydWN0b3Iocm9vdERpcj86IHN0cmluZykge1xuICAgIC8vIFZhbGlkYXRlIHJvb3REaXIgcGFyYW1ldGVyIGlmIHByb3ZpZGVkXG4gICAgaWYgKHJvb3REaXIpIHtcbiAgICAgIC8vIFByZXZlbnQgcGF0aCB0cmF2ZXJzYWwgYXR0YWNrcyBmaXJzdFxuICAgICAgaWYgKHJvb3REaXIuaW5jbHVkZXMoJy4uLycpIHx8IHJvb3REaXIuaW5jbHVkZXMoJy4uXFxcXCcpKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcigncm9vdERpciBjYW5ub3QgY29udGFpbiBwYXRoIHRyYXZlcnNhbCBzZXF1ZW5jZXMnKTtcbiAgICAgIH1cbiAgICAgIC8vIFRoZW4gY2hlY2sgaWYgaXQncyBhYnNvbHV0ZVxuICAgICAgaWYgKCFwYXRoLmlzQWJzb2x1dGUocm9vdERpcikpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKCdyb290RGlyIG11c3QgYmUgYW4gYWJzb2x1dGUgcGF0aCcpO1xuICAgICAgfVxuICAgIH1cbiAgICBcbiAgICAvLyBBbGxvdyBvdmVycmlkZSBmb3IgdGVzdGluZywgZGVmYXVsdCB0byBwcm9jZXNzLmN3ZCgpXG4gICAgdGhpcy5yb290RGlyID0gcm9vdERpciB8fCBwcm9jZXNzLmN3ZCgpO1xuICAgIFxuICAgIC8vIFNhZmV0eSBjaGVjazogRG9uJ3QgYWxsb3cgb3BlcmF0aW9ucyBvbiBkaXJlY3RvcmllcyBjb250YWluaW5nIGNyaXRpY2FsIGZpbGVzXG4gICAgLy8gVGhpcyBwcmV2ZW50cyBhY2NpZGVudGFsIGRlbGV0aW9uIG9mIHRoZSBhY3R1YWwgcHJvamVjdCBkaXJlY3RvcnlcbiAgICBpZiAodGhpcy5oYXNQcm9kdWN0aW9uRmlsZXMoKSAmJiAhdGhpcy5pc1NhZmVUZXN0RGlyZWN0b3J5KCkpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcignQmFja3VwTWFuYWdlciBjYW5ub3Qgb3BlcmF0ZSBvbiBwcm9kdWN0aW9uIGRpcmVjdG9yeS4gUGFzcyBhIHNhZmUgdGVzdCBkaXJlY3RvcnkgdG8gdGhlIGNvbnN0cnVjdG9yLicpO1xuICAgIH1cbiAgICBcbiAgICB0aGlzLmJhY2t1cHNEaXIgPSBwYXRoLmpvaW4odGhpcy5yb290RGlyLCBcIi4uXCIsIFwiZG9sbGhvdXNlbWNwLWJhY2t1cHNcIik7XG4gIH1cbiAgXG4gIC8qKlxuICAgKiBDaGVjayBpZiB0aGUgZGlyZWN0b3J5IGNvbnRhaW5zIHByb2R1Y3Rpb24gZmlsZXNcbiAgICovXG4gIHByaXZhdGUgaGFzUHJvZHVjdGlvbkZpbGVzKCk6IGJvb2xlYW4ge1xuICAgIHRyeSB7XG4gICAgICBjb25zdCBwcm9kdWN0aW9uSW5kaWNhdG9ycyA9IFtcbiAgICAgICAgJ3BhY2thZ2UuanNvbicsXG4gICAgICAgICd0c2NvbmZpZy5qc29uJyxcbiAgICAgICAgJy5naXQnLFxuICAgICAgICAnc3JjJyxcbiAgICAgICAgJ0xJQ0VOU0UnXG4gICAgICBdO1xuICAgICAgXG4gICAgICBjb25zdCBmaWxlcyA9IGZzU3luYy5yZWFkZGlyU3luYyh0aGlzLnJvb3REaXIpO1xuICAgICAgY29uc3QgaGFzUHJvZHVjdGlvbkZpbGUgPSBwcm9kdWN0aW9uSW5kaWNhdG9ycy5zb21lKGluZGljYXRvciA9PiBcbiAgICAgICAgZmlsZXMuaW5jbHVkZXMoaW5kaWNhdG9yKVxuICAgICAgKTtcbiAgICAgIFxuICAgICAgLy8gQWRkaXRpb25hbCBjaGVjazogaWYgcGFja2FnZS5qc29uIGV4aXN0cywgY2hlY2sgaWYgaXQncyBhIHJlYWwgcHJvamVjdFxuICAgICAgaWYgKGhhc1Byb2R1Y3Rpb25GaWxlICYmIGZpbGVzLmluY2x1ZGVzKCdwYWNrYWdlLmpzb24nKSkge1xuICAgICAgICBjb25zdCBwYWNrYWdlSnNvblBhdGggPSBwYXRoLmpvaW4odGhpcy5yb290RGlyLCAncGFja2FnZS5qc29uJyk7XG4gICAgICAgIGNvbnN0IHBhY2thZ2VKc29uID0gSlNPTi5wYXJzZShmc1N5bmMucmVhZEZpbGVTeW5jKHBhY2thZ2VKc29uUGF0aCwgJ3V0Zi04JykpO1xuICAgICAgICAvLyBJZiBpdCBoYXMgYSBuYW1lIGFuZCBkZXBlbmRlbmNpZXMsIGl0J3MgbGlrZWx5IGEgcmVhbCBwcm9qZWN0XG4gICAgICAgIHJldHVybiAhIShwYWNrYWdlSnNvbi5uYW1lICYmIHBhY2thZ2VKc29uLmRlcGVuZGVuY2llcyk7XG4gICAgICB9XG4gICAgICBcbiAgICAgIHJldHVybiBoYXNQcm9kdWN0aW9uRmlsZTtcbiAgICB9IGNhdGNoIHtcbiAgICAgIC8vIElmIHdlIGNhbid0IHJlYWQgdGhlIGRpcmVjdG9yeSwgYXNzdW1lIGl0J3Mgc2FmZVxuICAgICAgcmV0dXJuIGZhbHNlO1xuICAgIH1cbiAgfVxuICBcbiAgLyoqXG4gICAqIENoZWNrIGlmIHRoaXMgYXBwZWFycyB0byBiZSBhIHNhZmUgdGVzdCBkaXJlY3RvcnlcbiAgICovXG4gIHByaXZhdGUgaXNTYWZlVGVzdERpcmVjdG9yeSgpOiBib29sZWFuIHtcbiAgICBjb25zdCBzYWZlUGF0aHMgPSBbJ3Rlc3QnLCAndG1wJywgJ3RlbXAnLCAnLnRlc3QnLCAnX190ZXN0X18nXTtcbiAgICBjb25zdCBkaXJQYXRoID0gdGhpcy5yb290RGlyLnRvTG93ZXJDYXNlKCk7XG4gICAgXG4gICAgLy8gQ2hlY2sgaWYgcnVubmluZyBpbiBEb2NrZXIgY29udGFpbmVyXG4gICAgaWYgKHRoaXMuaXNEb2NrZXJFbnZpcm9ubWVudCgpKSB7XG4gICAgICByZXR1cm4gdHJ1ZTsgLy8gRG9ja2VyIGNvbnRhaW5lcnMgYXJlIGltbXV0YWJsZSwgdXBkYXRlcyBkb24ndCBhcHBseVxuICAgIH1cbiAgICBcbiAgICByZXR1cm4gc2FmZVBhdGhzLnNvbWUoc2FmZSA9PiBkaXJQYXRoLmluY2x1ZGVzKHNhZmUpKTtcbiAgfVxuICBcbiAgLyoqXG4gICAqIENoZWNrIGlmIHJ1bm5pbmcgaW4gYSBEb2NrZXIgY29udGFpbmVyXG4gICAqL1xuICBwcml2YXRlIGlzRG9ja2VyRW52aXJvbm1lbnQoKTogYm9vbGVhbiB7XG4gICAgLy8gQ2hlY2sgY29tbW9uIERvY2tlciBpbmRpY2F0b3JzXG4gICAgaWYgKHByb2Nlc3MuZW52LkRPTExIT1VTRV9ESVNBQkxFX1VQREFURVMgPT09ICd0cnVlJykge1xuICAgICAgcmV0dXJuIHRydWU7XG4gICAgfVxuICAgIFxuICAgIC8vIENoZWNrIGlmIHJ1bm5pbmcgZnJvbSAvYXBwIGRpcmVjdG9yeSAoY29tbW9uIERvY2tlciBwcmFjdGljZSlcbiAgICBpZiAodGhpcy5yb290RGlyID09PSAnL2FwcCcpIHtcbiAgICAgIHJldHVybiB0cnVlO1xuICAgIH1cbiAgICBcbiAgICAvLyBDaGVjayBmb3IgRG9ja2VyLXNwZWNpZmljIGZpbGVzXG4gICAgdHJ5IHtcbiAgICAgIGNvbnN0IGhhc0RvY2tlckVudiA9IGZzU3luYy5leGlzdHNTeW5jKCcvLmRvY2tlcmVudicpO1xuICAgICAgaWYgKGhhc0RvY2tlckVudikge1xuICAgICAgICByZXR1cm4gdHJ1ZTtcbiAgICAgIH1cbiAgICB9IGNhdGNoIHtcbiAgICAgIC8vIElnbm9yZSBlcnJvcnNcbiAgICB9XG4gICAgXG4gICAgcmV0dXJuIGZhbHNlO1xuICB9XG4gIFxuICAvKipcbiAgICogQ3JlYXRlIGEgYmFja3VwIG9mIHRoZSBjdXJyZW50IGluc3RhbGxhdGlvblxuICAgKi9cbiAgYXN5bmMgY3JlYXRlQmFja3VwKHZlcnNpb24/OiBzdHJpbmcpOiBQcm9taXNlPEJhY2t1cEluZm8+IHtcbiAgICBjb25zdCB0aW1lc3RhbXAgPSBuZXcgRGF0ZSgpLnRvSVNPU3RyaW5nKCkucmVwbGFjZSgvWzouXS9nLCAnLScpO1xuICAgIGNvbnN0IGJhY2t1cE5hbWUgPSBgYmFja3VwLSR7dGltZXN0YW1wfWA7XG4gICAgY29uc3QgYmFja3VwUGF0aCA9IHBhdGguam9pbih0aGlzLmJhY2t1cHNEaXIsIGJhY2t1cE5hbWUpO1xuICAgIFxuICAgIC8vIEVuc3VyZSBiYWNrdXBzIGRpcmVjdG9yeSBleGlzdHNcbiAgICBhd2FpdCBmcy5ta2Rpcih0aGlzLmJhY2t1cHNEaXIsIHsgcmVjdXJzaXZlOiB0cnVlIH0pO1xuICAgIFxuICAgIC8vIFVzZSBnaXQgdG8gY3JlYXRlIGEgY2xlYW4gY29weSAocmVzcGVjdGluZyAuZ2l0aWdub3JlKVxuICAgIGF3YWl0IHNhZmVFeGVjKCdnaXQnLCBbXG4gICAgICAnYXJjaGl2ZScsXG4gICAgICAnLS1mb3JtYXQ9dGFyJyxcbiAgICAgICdIRUFEJ1xuICAgIF0sIHsgY3dkOiB0aGlzLnJvb3REaXIgfSkudGhlbihhc3luYyAoeyBzdGRvdXQgfSkgPT4ge1xuICAgICAgLy8gQ3JlYXRlIGJhY2t1cCBkaXJlY3RvcnlcbiAgICAgIGF3YWl0IGZzLm1rZGlyKGJhY2t1cFBhdGgsIHsgcmVjdXJzaXZlOiB0cnVlIH0pO1xuICAgICAgXG4gICAgICAvLyBFeHRyYWN0IHRhciB0byBiYWNrdXAgZGlyZWN0b3J5XG4gICAgICBjb25zdCB0YXJQYXRoID0gcGF0aC5qb2luKGJhY2t1cFBhdGgsICdhcmNoaXZlLnRhcicpO1xuICAgICAgYXdhaXQgZnMud3JpdGVGaWxlKHRhclBhdGgsIHN0ZG91dCk7XG4gICAgICBcbiAgICAgIC8vIEV4dHJhY3QgdXNpbmcgdGFyIGNvbW1hbmRcbiAgICAgIGF3YWl0IHNhZmVFeGVjKCd0YXInLCBbJy14ZicsICdhcmNoaXZlLnRhciddLCB7IGN3ZDogYmFja3VwUGF0aCB9KTtcbiAgICAgIGF3YWl0IGZzLnVubGluayh0YXJQYXRoKTtcbiAgICB9KTtcbiAgICBcbiAgICAvLyBBbHNvIGJhY2t1cCBub2RlX21vZHVsZXMgaWYgaXQgZXhpc3RzXG4gICAgY29uc3Qgbm9kZU1vZHVsZXNQYXRoID0gcGF0aC5qb2luKHRoaXMucm9vdERpciwgJ25vZGVfbW9kdWxlcycpO1xuICAgIHRyeSB7XG4gICAgICBhd2FpdCBmcy5hY2Nlc3Mobm9kZU1vZHVsZXNQYXRoKTtcbiAgICAgIGF3YWl0IHNhZmVFeGVjKCdjcCcsIFsnLXInLCAnbm9kZV9tb2R1bGVzJywgYmFja3VwUGF0aF0sIHsgY3dkOiB0aGlzLnJvb3REaXIgfSk7XG4gICAgfSBjYXRjaCB7XG4gICAgICAvLyBub2RlX21vZHVsZXMgZG9lc24ndCBleGlzdCBvciBjb3B5IGZhaWxlZCwgdGhhdCdzIG9rYXlcbiAgICB9XG4gICAgXG4gICAgLy8gQmFja3VwIGFsbCBwZXJzb25hIGZpbGVzIChpbmNsdWRpbmcgdXNlci1jcmVhdGVkIG9uZXMgbm90IGluIGdpdClcbiAgICBjb25zdCBwZXJzb25hc0RpciA9IHBhdGguam9pbih0aGlzLnJvb3REaXIsICdwZXJzb25hcycpO1xuICAgIGNvbnN0IGJhY2t1cFBlcnNvbmFzRGlyID0gcGF0aC5qb2luKGJhY2t1cFBhdGgsICdwZXJzb25hcycpO1xuICAgIFxuICAgIHRyeSB7XG4gICAgICBhd2FpdCBmcy5hY2Nlc3MocGVyc29uYXNEaXIpO1xuICAgICAgYXdhaXQgZnMubWtkaXIoYmFja3VwUGVyc29uYXNEaXIsIHsgcmVjdXJzaXZlOiB0cnVlIH0pO1xuICAgICAgXG4gICAgICBjb25zdCBwZXJzb25hRmlsZXMgPSBhd2FpdCBmcy5yZWFkZGlyKHBlcnNvbmFzRGlyKTtcbiAgICAgIGZvciAoY29uc3QgZmlsZSBvZiBwZXJzb25hRmlsZXMpIHtcbiAgICAgICAgaWYgKGZpbGUuZW5kc1dpdGgoJy5tZCcpKSB7XG4gICAgICAgICAgY29uc3Qgc291cmNlUGF0aCA9IHBhdGguam9pbihwZXJzb25hc0RpciwgZmlsZSk7XG4gICAgICAgICAgY29uc3QgZGVzdFBhdGggPSBwYXRoLmpvaW4oYmFja3VwUGVyc29uYXNEaXIsIGZpbGUpO1xuICAgICAgICAgIFxuICAgICAgICAgIC8vIENvcHkgdGhlIGZpbGUsIG92ZXJ3cml0aW5nIGlmIGl0IGFscmVhZHkgZXhpc3RzIGZyb20gZ2l0IGFyY2hpdmVcbiAgICAgICAgICBhd2FpdCBmcy5jb3B5RmlsZShzb3VyY2VQYXRoLCBkZXN0UGF0aCk7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgLy8gTG9nIHdhcm5pbmcgYnV0IGRvbid0IGZhaWwgdGhlIGJhY2t1cFxuICAgICAgY29uc29sZS5lcnJvcignV2FybmluZzogQ291bGQgbm90IGJhY2t1cCBhbGwgcGVyc29uYXM6JywgZXJyb3IpO1xuICAgIH1cbiAgICBcbiAgICAvLyBTYXZlIGJhY2t1cCBtZXRhZGF0YVxuICAgIGNvbnN0IG1ldGFkYXRhID0ge1xuICAgICAgdGltZXN0YW1wLFxuICAgICAgdmVyc2lvbixcbiAgICAgIGNyZWF0ZWRBdDogbmV3IERhdGUoKS50b0lTT1N0cmluZygpXG4gICAgfTtcbiAgICBhd2FpdCBmcy53cml0ZUZpbGUoXG4gICAgICBwYXRoLmpvaW4oYmFja3VwUGF0aCwgJ2JhY2t1cC1tZXRhZGF0YS5qc29uJyksXG4gICAgICBKU09OLnN0cmluZ2lmeShtZXRhZGF0YSwgbnVsbCwgMilcbiAgICApO1xuICAgIFxuICAgIHJldHVybiB7XG4gICAgICBwYXRoOiBiYWNrdXBQYXRoLFxuICAgICAgdGltZXN0YW1wLFxuICAgICAgdmVyc2lvblxuICAgIH07XG4gIH1cbiAgXG4gIC8qKlxuICAgKiBMaXN0IGF2YWlsYWJsZSBiYWNrdXBzXG4gICAqL1xuICBhc3luYyBsaXN0QmFja3VwcygpOiBQcm9taXNlPEJhY2t1cEluZm9bXT4ge1xuICAgIHRyeSB7XG4gICAgICBjb25zdCBlbnRyaWVzID0gYXdhaXQgZnMucmVhZGRpcih0aGlzLmJhY2t1cHNEaXIsIHsgd2l0aEZpbGVUeXBlczogdHJ1ZSB9KTtcbiAgICAgIGNvbnN0IGJhY2t1cHM6IEJhY2t1cEluZm9bXSA9IFtdO1xuICAgICAgXG4gICAgICBmb3IgKGNvbnN0IGVudHJ5IG9mIGVudHJpZXMpIHtcbiAgICAgICAgaWYgKGVudHJ5LmlzRGlyZWN0b3J5KCkgJiYgZW50cnkubmFtZS5zdGFydHNXaXRoKCdiYWNrdXAtJykpIHtcbiAgICAgICAgICBjb25zdCBiYWNrdXBQYXRoID0gcGF0aC5qb2luKHRoaXMuYmFja3Vwc0RpciwgZW50cnkubmFtZSk7XG4gICAgICAgICAgY29uc3QgdGltZXN0YW1wID0gZW50cnkubmFtZS5yZXBsYWNlKCdiYWNrdXAtJywgJycpO1xuICAgICAgICAgIFxuICAgICAgICAgIC8vIFRyeSB0byByZWFkIG1ldGFkYXRhXG4gICAgICAgICAgbGV0IHZlcnNpb246IHN0cmluZyB8IHVuZGVmaW5lZDtcbiAgICAgICAgICB0cnkge1xuICAgICAgICAgICAgY29uc3QgbWV0YWRhdGFQYXRoID0gcGF0aC5qb2luKGJhY2t1cFBhdGgsICdiYWNrdXAtbWV0YWRhdGEuanNvbicpO1xuICAgICAgICAgICAgY29uc3QgbWV0YWRhdGEgPSBKU09OLnBhcnNlKGF3YWl0IGZzLnJlYWRGaWxlKG1ldGFkYXRhUGF0aCwgJ3V0Zi04JykpO1xuICAgICAgICAgICAgdmVyc2lvbiA9IG1ldGFkYXRhLnZlcnNpb247XG4gICAgICAgICAgfSBjYXRjaCB7XG4gICAgICAgICAgICAvLyBObyBtZXRhZGF0YSBmaWxlLCB0aGF0J3Mgb2theVxuICAgICAgICAgIH1cbiAgICAgICAgICBcbiAgICAgICAgICBiYWNrdXBzLnB1c2goe1xuICAgICAgICAgICAgcGF0aDogYmFja3VwUGF0aCxcbiAgICAgICAgICAgIHRpbWVzdGFtcCxcbiAgICAgICAgICAgIHZlcnNpb25cbiAgICAgICAgICB9KTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgICAgXG4gICAgICAvLyBTb3J0IGJ5IHRpbWVzdGFtcCBkZXNjZW5kaW5nIChuZXdlc3QgZmlyc3QpXG4gICAgICByZXR1cm4gYmFja3Vwcy5zb3J0KChhLCBiKSA9PiBiLnRpbWVzdGFtcC5sb2NhbGVDb21wYXJlKGEudGltZXN0YW1wKSk7XG4gICAgfSBjYXRjaCB7XG4gICAgICByZXR1cm4gW107XG4gICAgfVxuICB9XG4gIFxuICAvKipcbiAgICogR2V0IHRoZSBtb3N0IHJlY2VudCBiYWNrdXBcbiAgICovXG4gIGFzeW5jIGdldExhdGVzdEJhY2t1cCgpOiBQcm9taXNlPEJhY2t1cEluZm8gfCBudWxsPiB7XG4gICAgY29uc3QgYmFja3VwcyA9IGF3YWl0IHRoaXMubGlzdEJhY2t1cHMoKTtcbiAgICByZXR1cm4gYmFja3Vwcy5sZW5ndGggPiAwID8gYmFja3Vwc1swXSA6IG51bGw7XG4gIH1cbiAgXG4gIC8qKlxuICAgKiBSZXN0b3JlIGZyb20gYSBiYWNrdXBcbiAgICovXG4gIGFzeW5jIHJlc3RvcmVCYWNrdXAoYmFja3VwUGF0aDogc3RyaW5nKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgLy8gVmVyaWZ5IGJhY2t1cCBleGlzdHNcbiAgICB0cnkge1xuICAgICAgYXdhaXQgZnMuYWNjZXNzKGJhY2t1cFBhdGgpO1xuICAgIH0gY2F0Y2gge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKGBCYWNrdXAgbm90IGZvdW5kOiAke2JhY2t1cFBhdGh9YCk7XG4gICAgfVxuICAgIFxuICAgIC8vIENyZWF0ZSBhIHRlbXBvcmFyeSBkaXJlY3RvcnkgZm9yIHRoZSBjdXJyZW50IHN0YXRlXG4gICAgY29uc3QgdGVtcERpciA9IHBhdGguam9pbih0aGlzLmJhY2t1cHNEaXIsICd0ZW1wLWN1cnJlbnQnKTtcbiAgICBhd2FpdCBmcy5ta2Rpcih0ZW1wRGlyLCB7IHJlY3Vyc2l2ZTogdHJ1ZSB9KTtcbiAgICBcbiAgICAvLyBNb3ZlIGN1cnJlbnQgZmlsZXMgdG8gdGVtcCAoZXhjZXB0IC5naXQgYW5kIG5vZGVfbW9kdWxlcylcbiAgICBjb25zdCBlbnRyaWVzID0gYXdhaXQgZnMucmVhZGRpcih0aGlzLnJvb3REaXIpO1xuICAgIGZvciAoY29uc3QgZW50cnkgb2YgZW50cmllcykge1xuICAgICAgaWYgKGVudHJ5ICE9PSAnLmdpdCcgJiYgZW50cnkgIT09ICdub2RlX21vZHVsZXMnICYmIGVudHJ5ICE9PSAnZGlzdCcpIHtcbiAgICAgICAgY29uc3Qgc291cmNlUGF0aCA9IHBhdGguam9pbih0aGlzLnJvb3REaXIsIGVudHJ5KTtcbiAgICAgICAgY29uc3QgZGVzdFBhdGggPSBwYXRoLmpvaW4odGVtcERpciwgZW50cnkpO1xuICAgICAgICBhd2FpdCBmcy5yZW5hbWUoc291cmNlUGF0aCwgZGVzdFBhdGgpO1xuICAgICAgfVxuICAgIH1cbiAgICBcbiAgICAvLyBDb3B5IGJhY2t1cCBmaWxlcyB0byByb290XG4gICAgY29uc3QgYmFja3VwRW50cmllcyA9IGF3YWl0IGZzLnJlYWRkaXIoYmFja3VwUGF0aCk7XG4gICAgZm9yIChjb25zdCBlbnRyeSBvZiBiYWNrdXBFbnRyaWVzKSB7XG4gICAgICBpZiAoZW50cnkgIT09ICdiYWNrdXAtbWV0YWRhdGEuanNvbicgJiYgZW50cnkgIT09ICcuZ2l0Jykge1xuICAgICAgICBjb25zdCBzb3VyY2VQYXRoID0gcGF0aC5qb2luKGJhY2t1cFBhdGgsIGVudHJ5KTtcbiAgICAgICAgY29uc3QgZGVzdFBhdGggPSBwYXRoLmpvaW4odGhpcy5yb290RGlyLCBlbnRyeSk7XG4gICAgICAgIGF3YWl0IHNhZmVFeGVjKCdjcCcsIFsnLXInLCBzb3VyY2VQYXRoLCBkZXN0UGF0aF0pO1xuICAgICAgfVxuICAgIH1cbiAgICBcbiAgICAvLyBDbGVhbiB1cCB0ZW1wIGRpcmVjdG9yeVxuICAgIGF3YWl0IGZzLnJtKHRlbXBEaXIsIHsgcmVjdXJzaXZlOiB0cnVlLCBmb3JjZTogdHJ1ZSB9KTtcbiAgfVxuICBcbiAgLyoqXG4gICAqIENsZWFuIHVwIG9sZCBiYWNrdXBzIChrZWVwIHRoZSA1IG1vc3QgcmVjZW50KVxuICAgKi9cbiAgYXN5bmMgY2xlYW51cE9sZEJhY2t1cHMoa2VlcENvdW50OiBudW1iZXIgPSA1KTogUHJvbWlzZTxudW1iZXI+IHtcbiAgICBjb25zdCBiYWNrdXBzID0gYXdhaXQgdGhpcy5saXN0QmFja3VwcygpO1xuICAgIGxldCBkZWxldGVkQ291bnQgPSAwO1xuICAgIFxuICAgIGlmIChiYWNrdXBzLmxlbmd0aCA+IGtlZXBDb3VudCkge1xuICAgICAgY29uc3QgYmFja3Vwc1RvRGVsZXRlID0gYmFja3Vwcy5zbGljZShrZWVwQ291bnQpO1xuICAgICAgXG4gICAgICBmb3IgKGNvbnN0IGJhY2t1cCBvZiBiYWNrdXBzVG9EZWxldGUpIHtcbiAgICAgICAgdHJ5IHtcbiAgICAgICAgICBhd2FpdCBmcy5ybShiYWNrdXAucGF0aCwgeyByZWN1cnNpdmU6IHRydWUsIGZvcmNlOiB0cnVlIH0pO1xuICAgICAgICAgIGRlbGV0ZWRDb3VudCsrO1xuICAgICAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgICAgIGNvbnNvbGUuZXJyb3IoYEZhaWxlZCB0byBkZWxldGUgYmFja3VwICR7YmFja3VwLnBhdGh9OmAsIGVycm9yKTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cbiAgICBcbiAgICByZXR1cm4gZGVsZXRlZENvdW50O1xuICB9XG59Il19