@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.
369 lines • 56.2 kB
JavaScript
/**
* Server startup utilities including migration
*
* ARCHITECTURE NOTES - Memory Auto-Load Implementation:
*
* There are two valid architectural approaches for memory auto-load:
*
* 1. MemoryManager.loadAndActivateAutoLoadMemories() (CURRENT IMPLEMENTATION)
* - Auto-load logic lives in MemoryManager
* - Called directly from Container.preparePortfolio()
* - Follows DI pattern: managers own all operations for their element type
* - Pros: Better encapsulation, clearer ownership, no duplicated responsibility
* - Used by: Container.preparePortfolio() (current production approach)
*
* 2. ServerStartup.initializeAutoLoadMemories() (ALTERNATIVE APPROACH)
* - Auto-load orchestrated by ServerStartup
* - ServerStartup delegates to MemoryManager for actual operations
* - Useful for: Complex startup sequences with multiple coordinated steps
* - Pros: Centralizes startup concerns, easier to add cross-cutting features
* - Used by: This class (kept for future use and alternative workflows)
*
* Both approaches are valid and maintained. Choose based on your needs:
* - Use MemoryManager directly for simple, focused auto-load
* - Use ServerStartup for complex orchestration with multiple startup phases
*
* See docs/architecture/memory-autoload-architectures.md for detailed comparison.
*/
import { ElementType } from '../portfolio/PortfolioManager.js';
import { PACKAGE_VERSION as VERSION } from '../generated/version.js';
import { UnicodeValidator } from '../security/validators/unicodeValidator.js';
import { SecurityMonitor } from '../security/securityMonitor.js';
import { AutoLoadError } from '../errors/AutoLoadError.js';
import { logger } from '../utils/logger.js';
export class ServerStartup {
portfolioManager;
migrationManager;
fileLockManager;
memoryManager;
configManager;
operationalTelemetry;
constructor(portfolioManager, fileLockManager, configManager, migrationManager, memoryManager, operationalTelemetry) {
this.portfolioManager = portfolioManager;
this.fileLockManager = fileLockManager;
this.configManager = configManager;
this.migrationManager = migrationManager;
this.memoryManager = memoryManager;
this.operationalTelemetry = operationalTelemetry;
}
/**
* Initialize server with migration check
*/
async initialize(options = {}) {
logger.info('[ServerStartup] Initializing server...');
// Check if migration is needed
if (!options.skipMigration) {
const needsMigration = await this.migrationManager.needsMigration();
if (needsMigration) {
logger.info('[ServerStartup] Legacy personas detected. Starting migration...');
const result = await this.migrationManager.migrate({
backup: options.autoBackup !== false // Default to true
});
if (result.success) {
logger.info(`[ServerStartup] Successfully migrated ${result.migratedCount} personas`);
if (result.backedUp && result.backupPath) {
logger.info(`[ServerStartup] Backup created at: ${result.backupPath}`);
}
}
else {
logger.error('[ServerStartup] Migration completed with errors:');
result.errors.forEach(err => logger.error(`[ServerStartup] - ${err}`));
}
}
}
// Ensure portfolio structure exists
const portfolioExists = await this.portfolioManager.exists();
if (!portfolioExists) {
logger.info('[ServerStartup] Creating portfolio directory structure...');
await this.portfolioManager.initialize();
}
// Log portfolio statistics
const stats = await this.portfolioManager.getStatistics();
logger.info('[ServerStartup] Portfolio statistics:');
Object.entries(stats).forEach(([type, count]) => {
if (count > 0) {
logger.info(`[ServerStartup] - ${type}: ${count} elements`);
}
});
// Initialize auto-load memories
await this.initializeAutoLoadMemories();
}
/**
* Process a single auto-load memory
* FIX (SonarCloud): Extracted to reduce cognitive complexity
* FIX (SonarCloud): Reduced parameter count by using options object
* @private
*/
async processAutoLoadMemory(memory, memoryManager, options) {
try {
// FIX: DMCP-SEC-004 - Normalize Unicode in user input to prevent homograph attacks
const normalizedName = UnicodeValidator.normalize(memory.metadata.name);
const memoryName = normalizedName.normalizedContent;
// PR #1436: Validate memory before loading
const validation = memory.validate();
if (!validation.valid) {
throw AutoLoadError.validationFailed(memoryName, validation.errors?.map((e) => e.message).join(', ') || 'Unknown validation error');
}
const estimatedTokens = memoryManager.estimateTokens(memory.content || '');
// Check for size warnings
const warnings = this.checkMemorySizeWarnings(memoryName, estimatedTokens, options.suppressWarnings);
// Check if memory should be skipped
const skipCheck = this.shouldSkipMemory(memoryName, estimatedTokens, options.totalTokens, options.singleLimit, options.totalBudget);
if (skipCheck.skip) {
if (skipCheck.reason === 'budget_exceeded') {
const remaining = options.totalMemories - options.loadedCount;
logger.info(`[ServerStartup] Token budget reached (${options.totalTokens}/${options.totalBudget} tokens). ` +
`Loaded ${options.loadedCount} memories, skipping remaining ${remaining}.`);
return { skip: false, breakLoop: true, skippedCount: remaining, estimatedTokens: 0, warnings: 0 };
}
return { skip: true, breakLoop: false, skippedCount: 0, estimatedTokens: 0, warnings: 0 };
}
// FIX #1430: Activate the memory so it's available for use
logger.info(`[ServerStartup] 🔄 Activating memory: ${memoryName}...`);
await memory.activate();
logger.info(`[ServerStartup] ✅ Memory activated: ${memoryName}`);
// DMCP-SEC-006: Per-memory audit downgraded from security event to debug.
// Was generating O(n) security events per startup where n = auto-load count,
// causing 25x security buffer turnover. Completion summary kept as security event.
logger.debug(`[ServerStartup] Auto-loaded: ${memoryName} (~${estimatedTokens} tokens, priority: ${memory.metadata.priority})`);
return { skip: false, breakLoop: false, skippedCount: 0, estimatedTokens, warnings };
}
catch (error) {
// PR #1436: Structured error handling with AutoLoadError
this.handleAutoLoadMemoryError(error, memory);
return { skip: true, breakLoop: false, skippedCount: 0, estimatedTokens: 0, warnings: 0 };
}
}
/**
* Handle errors during auto-load memory processing
* FIX (SonarCloud): Extracted to reduce cognitive complexity
* @private
*/
handleAutoLoadMemoryError(error, memory) {
if (error instanceof AutoLoadError) {
logger.info(`[ServerStartup] Skipping '${error.memoryName}' - ` +
`${error.phase} phase failed: ${error.message}`);
}
else {
const memoryName = memory.metadata.name || 'unknown';
logger.warn(`[ServerStartup] Unexpected error loading '${memoryName}': ${error}`);
}
}
/**
* Check and log size warnings for a memory
* @private
*/
checkMemorySizeWarnings(memoryName, estimatedTokens, suppressWarnings) {
const LARGE_MEMORY_WARN = 5000;
const VERY_LARGE_MEMORY_WARN = 10000;
if (suppressWarnings) {
return 0;
}
if (estimatedTokens > VERY_LARGE_MEMORY_WARN) {
logger.warn(`[ServerStartup] Memory '${memoryName}' is very large ` +
`(~${estimatedTokens} tokens, recommended: ${VERY_LARGE_MEMORY_WARN}). ` +
`This may impact startup time.`);
return 1;
}
if (estimatedTokens > LARGE_MEMORY_WARN) {
logger.info(`[ServerStartup] Memory '${memoryName}' is large (~${estimatedTokens} tokens).`);
return 1;
}
return 0;
}
/**
* Check if memory should be skipped due to budget limits
* @private
*/
shouldSkipMemory(memoryName, estimatedTokens, totalTokens, singleLimit, totalBudget) {
// Check single memory limit
if (singleLimit !== undefined && estimatedTokens > singleLimit) {
logger.info(`[ServerStartup] Skipping '${memoryName}' - ` +
`exceeds configured single memory limit (${estimatedTokens} > ${singleLimit} tokens)`);
return { skip: true, reason: 'single_limit' };
}
// Check total budget
if (totalTokens + estimatedTokens > totalBudget) {
return { skip: true, reason: 'budget_exceeded' };
}
return { skip: false };
}
/**
* Log error recovery suggestions based on error type
* @private
*/
logAutoLoadErrorSuggestions(errorMessage) {
if (errorMessage.includes('ENOENT') || errorMessage.includes('not found')) {
logger.info('[ServerStartup] Tip: Memory files may not exist yet. They will be created on first use.');
}
else if (errorMessage.includes('EACCES') || errorMessage.includes('permission')) {
logger.warn('[ServerStartup] Tip: Check file permissions for ~/.dollhouse/portfolio/memories/');
}
else if (errorMessage.includes('YAML') || errorMessage.includes('parse')) {
logger.warn('[ServerStartup] Tip: Check YAML syntax in memory files. Use dollhouse validate to diagnose.');
}
}
/**
* Initialize auto-load memories
* Issue #1430: Automatically load baseline memories on server startup
* @private
*/
async initializeAutoLoadMemories() {
const startTime = Date.now();
let totalTokens = 0;
let loadedCount = 0;
let skippedCount = 0;
let warningCount = 0;
const emergencyDisabled = process.env.DOLLHOUSE_DISABLE_AUTOLOAD === 'true';
try {
// FIX: DMCP-SEC-006 - Add audit logging for security operations
// Check for emergency disable
if (emergencyDisabled) {
logger.info('[ServerStartup] Auto-load disabled via DOLLHOUSE_DISABLE_AUTOLOAD');
SecurityMonitor.logSecurityEvent({
type: 'MEMORY_LOADED',
severity: 'LOW',
source: 'ServerStartup.initializeAutoLoadMemories',
details: 'Auto-load memories disabled via emergency environment variable',
additionalData: { reason: 'DOLLHOUSE_DISABLE_AUTOLOAD=true' }
});
return;
}
// Check if auto-load is enabled in config (DI: use this.configManager)
await this.configManager.initialize();
const config = this.configManager.getConfig();
if (!config.autoLoad.enabled) {
logger.debug('[ServerStartup] Auto-load memories disabled in configuration');
return;
}
// DI: use this.memoryManager instead of new MemoryManager()
const memoryManager = this.memoryManager;
// Issue #1430: Install seed memories before loading auto-load memories
// This ensures baseline knowledge is available on first run
logger.info('[ServerStartup] 🌱 Installing seed memories...');
await memoryManager.installSeedMemories();
logger.info('[ServerStartup] ✅ Seed installation complete');
logger.info('[ServerStartup] 🔍 Fetching auto-load memories...');
const autoLoadMemories = await memoryManager.getAutoLoadMemories();
logger.info(`[ServerStartup] 📋 Auto-load memories found: ${autoLoadMemories.length}`);
if (autoLoadMemories.length === 0) {
logger.warn('[ServerStartup] ⚠️ No auto-load memories configured - baseline knowledge may not be available');
return;
}
// User-configured limits (hard enforcement if set)
// PR #1436: Validate maxTokenBudget with bounds and user warning
// Minimum: 100 tokens (enough for minimal baseline knowledge)
// Maximum: 50,000 tokens (prevents excessive startup time and memory usage)
// Default: 5,000 tokens (balanced for typical use cases)
const configuredBudget = config.autoLoad.maxTokenBudget || 5000;
const totalBudget = Math.max(100, Math.min(50000, configuredBudget));
// PR #1436: Warn if configured budget was clamped to valid range
if (configuredBudget !== totalBudget) {
logger.warn(`[ServerStartup] Configured maxTokenBudget (${configuredBudget}) ` +
`was adjusted to ${totalBudget} (valid range: 100-50,000)`);
}
const singleLimit = config.autoLoad.maxSingleMemoryTokens; // undefined = no limit
const suppressWarnings = config.autoLoad.suppressLargeMemoryWarnings || false;
for (const memory of autoLoadMemories) {
const result = await this.processAutoLoadMemory(memory, memoryManager, {
totalTokens,
singleLimit,
totalBudget,
suppressWarnings,
totalMemories: autoLoadMemories.length,
loadedCount
});
if (result.breakLoop) {
skippedCount += result.skippedCount;
break;
}
if (result.skip) {
skippedCount++;
continue;
}
totalTokens += result.estimatedTokens;
loadedCount++;
warningCount += result.warnings;
}
logger.info(`[ServerStartup] Auto-load complete: ${loadedCount} memories loaded ` +
`(~${totalTokens} tokens), ${skippedCount} skipped, ${warningCount} warnings`);
// Summary event — fires once per startup, keep as security event
SecurityMonitor.logSecurityEvent({
type: 'MEMORY_LOADED',
severity: 'LOW',
source: 'ServerStartup.initializeAutoLoadMemories',
details: `Auto-load complete: ${loadedCount} loaded, ${skippedCount} skipped`,
additionalData: {
loadedCount,
skippedCount,
warningCount,
totalTokens,
loadTimeMs: Date.now() - startTime
}
});
}
catch (error) {
// ENHANCED ERROR HANDLING: Issue #1430
// Don't fail startup if auto-load fails, but provide detailed diagnostics
const errorMessage = error instanceof Error ? error.message : String(error);
const errorType = error instanceof Error ? error.constructor.name : 'Unknown';
logger.warn(`[ServerStartup] Failed to load auto-load memories (${errorType}): ${errorMessage}`);
// Provide helpful recovery suggestions based on error type
this.logAutoLoadErrorSuggestions(errorMessage);
// Record error in telemetry for diagnostics
loadedCount = 0;
totalTokens = 0;
// FIX: DMCP-SEC-006 - Audit log auto-load failure
SecurityMonitor.logSecurityEvent({
type: 'MEMORY_LOAD_FAILED',
severity: 'MEDIUM',
source: 'ServerStartup.initializeAutoLoadMemories',
details: `Auto-load memories failed: ${errorType} - ${errorMessage}`,
additionalData: {
errorType,
errorMessage,
loadTimeMs: Date.now() - startTime
}
});
}
finally {
// Record telemetry
const metrics = {
timestamp: new Date().toISOString(),
version: VERSION,
memoryCount: loadedCount,
totalTokens,
loadTimeMs: Date.now() - startTime,
skippedCount,
warningCount,
budgetExceeded: skippedCount > 0,
emergencyDisabled
};
await this.operationalTelemetry.recordAutoLoadMetrics(metrics);
}
}
/**
* Get migration status without performing migration
*/
async getMigrationStatus() {
return await this.migrationManager.getMigrationStatus();
}
/**
* Get the personas directory path for legacy compatibility
*/
getPersonasDir() {
return this.portfolioManager.getElementDir(ElementType.PERSONA);
}
/**
* Dispose of resources and cleanup
* Cleans up managers and telemetry to prevent open handles
*/
async dispose() {
// Dispose MemoryManager (cleans up file watchers)
this.memoryManager.dispose();
// Shutdown telemetry (flushes PostHog events)
await this.operationalTelemetry.shutdown();
logger.debug('[ServerStartup] Disposed and cleaned up resources');
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3RhcnR1cC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9zZXJ2ZXIvc3RhcnR1cC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0EwQkc7QUFFSCxPQUFPLEVBQW9CLFdBQVcsRUFBRSxNQUFNLGtDQUFrQyxDQUFDO0FBTWpGLE9BQU8sRUFBRSxlQUFlLElBQUksT0FBTyxFQUFFLE1BQU0seUJBQXlCLENBQUM7QUFFckUsT0FBTyxFQUFFLGdCQUFnQixFQUFFLE1BQU0sNENBQTRDLENBQUM7QUFDOUUsT0FBTyxFQUFFLGVBQWUsRUFBRSxNQUFNLGdDQUFnQyxDQUFDO0FBQ2pFLE9BQU8sRUFBRSxhQUFhLEVBQUUsTUFBTSw0QkFBNEIsQ0FBQztBQUMzRCxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFPNUMsTUFBTSxPQUFPLGFBQWE7SUFDaEIsZ0JBQWdCLENBQW1CO0lBQ25DLGdCQUFnQixDQUFtQjtJQUNuQyxlQUFlLENBQWtCO0lBQ2pDLGFBQWEsQ0FBZ0I7SUFDN0IsYUFBYSxDQUFnQjtJQUM3QixvQkFBb0IsQ0FBdUI7SUFFbkQsWUFDRSxnQkFBa0MsRUFDbEMsZUFBZ0MsRUFDaEMsYUFBNEIsRUFDNUIsZ0JBQWtDLEVBQ2xDLGFBQTRCLEVBQzVCLG9CQUEwQztRQUUxQyxJQUFJLENBQUMsZ0JBQWdCLEdBQUcsZ0JBQWdCLENBQUM7UUFDekMsSUFBSSxDQUFDLGVBQWUsR0FBRyxlQUFlLENBQUM7UUFDdkMsSUFBSSxDQUFDLGFBQWEsR0FBRyxhQUFhLENBQUM7UUFDbkMsSUFBSSxDQUFDLGdCQUFnQixHQUFHLGdCQUFnQixDQUFDO1FBQ3pDLElBQUksQ0FBQyxhQUFhLEdBQUcsYUFBYSxDQUFDO1FBQ25DLElBQUksQ0FBQyxvQkFBb0IsR0FBRyxvQkFBb0IsQ0FBQztJQUNuRCxDQUFDO0lBRUQ7O09BRUc7SUFDSCxLQUFLLENBQUMsVUFBVSxD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SCxDQUFDO0lBRUQ7O09BRUc7SUFDSCxLQUFLLENBQUMsa0JBQWtCO1FBQ3RCLE9BQU8sTUFBTSxJQUFJLENBQUMsZ0JBQWdCLENBQUMsa0JBQWtCLEVBQUUsQ0FBQztJQUMxRCxDQUFDO0lBRUQ7O09BRUc7SUFDSCxjQUFjO1FBQ1osT0FBTyxJQUFJLENBQUMsZ0JBQWdCLENBQUMsYUFBYSxDQUFDLFdBQVcsQ0FBQyxPQUFPLENBQUMsQ0FBQztJQUNsRSxDQUFDO0lBRUQ7OztPQUdHO0lBQ0gsS0FBSyxDQUFDLE9BQU87UUFDWCxrREFBa0Q7UUFDbEQsSUFBSSxDQUFDLGFBQWEsQ0FBQyxPQUFPLEVBQUUsQ0FBQztRQUU3Qiw4Q0FBOEM7UUFDOUMsTUFBTSxJQUFJLENBQUMsb0JBQW9CLENBQUMsUUFBUSxFQUFFLENBQUM7UUFFM0MsTUFBTSxDQUFDLEtBQUssQ0FBQyxtREFBbUQsQ0FBQyxDQUFDO0lBQ3BFLENBQUM7Q0FDRiIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogU2VydmVyIHN0YXJ0dXAgdXRpbGl0aWVzIGluY2x1ZGluZyBtaWdyYXRpb25cbiAqXG4gKiBBUkNISVRFQ1RVUkUgTk9URVMgLSBNZW1vcnkgQXV0by1Mb2FkIEltcGxlbWVudGF0aW9uOlxuICpcbiAqIFRoZXJlIGFyZSB0d28gdmFsaWQgYXJjaGl0ZWN0dXJhbCBhcHByb2FjaGVzIGZvciBtZW1vcnkgYXV0by1sb2FkOlxuICpcbiAqIDEuIE1lbW9yeU1hbmFnZXIubG9hZEFuZEFjdGl2YXRlQXV0b0xvYWRNZW1vcmllcygpIChDVVJSRU5UIElNUExFTUVOVEFUSU9OKVxuICogICAgLSBBdXRvLWxvYWQgbG9naWMgbGl2ZXMgaW4gTWVtb3J5TWFuYWdlclxuICogICAgLSBDYWxsZWQgZGlyZWN0bHkgZnJvbSBDb250YWluZXIucHJlcGFyZVBvcnRmb2xpbygpXG4gKiAgICAtIEZvbGxvd3MgREkgcGF0dGVybjogbWFuYWdlcnMgb3duIGFsbCBvcGVyYXRpb25zIGZvciB0aGVpciBlbGVtZW50IHR5cGVcbiAqICAgIC0gUHJvczogQmV0dGVyIGVuY2Fwc3VsYXRpb24sIGNsZWFyZXIgb3duZXJzaGlwLCBubyBkdXBsaWNhdGVkIHJlc3BvbnNpYmlsaXR5XG4gKiAgICAtIFVzZWQgYnk6IENvbnRhaW5lci5wcmVwYXJlUG9ydGZvbGlvKCkgKGN1cnJlbnQgcHJvZHVjdGlvbiBhcHByb2FjaClcbiAqXG4gKiAyLiBTZXJ2ZXJTdGFydHVwLmluaXRpYWxpemVBdXRvTG9hZE1lbW9yaWVzKCkgKEFMVEVSTkFUSVZFIEFQUFJPQUNIKVxuICogICAgLSBBdXRvLWxvYWQgb3JjaGVzdHJhdGVkIGJ5IFNlcnZlclN0YXJ0dXBcbiAqICAgIC0gU2VydmVyU3RhcnR1cCBkZWxlZ2F0ZXMgdG8gTWVtb3J5TWFuYWdlciBmb3IgYWN0dWFsIG9wZXJhdGlvbnNcbiAqICAgIC0gVXNlZnVsIGZvcjogQ29tcGxleCBzdGFydHVwIHNlcXVlbmNlcyB3aXRoIG11bHRpcGxlIGNvb3JkaW5hdGVkIHN0ZXBzXG4gKiAgICAtIFByb3M6IENlbnRyYWxpemVzIHN0YXJ0dXAgY29uY2VybnMsIGVhc2llciB0byBhZGQgY3Jvc3MtY3V0dGluZyBmZWF0dXJlc1xuICogICAgLSBVc2VkIGJ5OiBUaGlzIGNsYXNzIChrZXB0IGZvciBmdXR1cmUgdXNlIGFuZCBhbHRlcm5hdGl2ZSB3b3JrZmxvd3MpXG4gKlxuICogQm90aCBhcHByb2FjaGVzIGFyZSB2YWxpZCBhbmQgbWFpbnRhaW5lZC4gQ2hvb3NlIGJhc2VkIG9uIHlvdXIgbmVlZHM6XG4gKiAtIFVzZSBNZW1vcnlNYW5hZ2VyIGRpcmVjdGx5IGZvciBzaW1wbGUsIGZvY3VzZWQgYXV0by1sb2FkXG4gKiAtIFVzZSBTZXJ2ZXJTdGFydHVwIGZvciBjb21wbGV4IG9yY2hlc3RyYXRpb24gd2l0aCBtdWx0aXBsZSBzdGFydHVwIHBoYXNlc1xuICpcbiAqIFNlZSBkb2NzL2FyY2hpdGVjdHVyZS9tZW1vcnktYXV0b2xvYWQtYXJjaGl0ZWN0dXJlcy5tZCBmb3IgZGV0YWlsZWQgY29tcGFyaXNvbi5cbiAqL1xuXG5pbXBvcnQgeyBQb3J0Zm9saW9NYW5hZ2VyLCBFbGVtZW50VHlwZSB9IGZyb20gJy4uL3BvcnRmb2xpby9Qb3J0Zm9saW9NYW5hZ2VyLmpzJztcbmltcG9ydCB7IE1pZ3JhdGlvbk1hbmFnZXIgfSBmcm9tICcuLi9wb3J0Zm9saW8vTWlncmF0aW9uTWFuYWdlci5qcyc7XG5pbXBvcnQgeyBGaWxlTG9ja01hbmFnZXIgfSBmcm9tICcuLi9zZWN1cml0eS9maWxlTG9ja01hbmFnZXIuanMnO1xuaW1wb3J0IHsgTWVtb3J5TWFuYWdlciB9IGZyb20gJy4uL2VsZW1lbnRzL21lbW9yaWVzL01lbW9yeU1hbmFnZXIuanMnO1xuaW1wb3J0IHsgQ29uZmlnTWFuYWdlciB9IGZyb20gJy4uL2NvbmZpZy9Db25maWdNYW5hZ2VyLmpzJztcbmltcG9ydCB7IE9wZXJhdGlvbmFsVGVsZW1ldHJ5IH0gZnJvbSAnLi4vdGVsZW1ldHJ5L09wZXJhdGlvbmFsVGVsZW1ldHJ5LmpzJztcbmltcG9ydCB7IFBBQ0tBR0VfVkVSU0lPTiBhcyBWRVJTSU9OIH0gZnJvbSAnLi4vZ2VuZXJhdGVkL3ZlcnNpb24uanMnO1xuaW1wb3J0IHR5cGUgeyBBdXRvTG9hZE1ldHJpY3MgfSBmcm9tICcuLi90ZWxlbWV0cnkvdHlwZXMuanMnO1xuaW1wb3J0IHsgVW5pY29kZVZhbGlkYXRvciB9IGZyb20gJy4uL3NlY3VyaXR5L3ZhbGlkYXRvcnMvdW5pY29kZVZhbGlkYXRvci5qcyc7XG5pbXBvcnQgeyBTZWN1cml0eU1vbml0b3IgfSBmcm9tICcuLi9zZWN1cml0eS9zZWN1cml0eU1vbml0b3IuanMnO1xuaW1wb3J0IHsgQXV0b0xvYWRFcnJvciB9IGZyb20gJy4uL2Vycm9ycy9BdXRvTG9hZEVycm9yLmpzJztcbmltcG9ydCB7IGxvZ2dlciB9IGZyb20gJy4uL3V0aWxzL2xvZ2dlci5qcyc7XG5cbmV4cG9ydCBpbnRlcmZhY2UgU3RhcnR1cE9wdGlvbnMge1xuICBza2lwTWlncmF0aW9uPzogYm9vbGVhbjtcbiAgYXV0b0JhY2t1cD86IGJvb2xlYW47XG59XG5cbmV4cG9ydCBjbGFzcyBTZXJ2ZXJTdGFydHVwIHtcbiAgcHJpdmF0ZSBwb3J0Zm9saW9NYW5hZ2VyOiBQb3J0Zm9saW9NYW5hZ2VyO1xuICBwcml2YXRlIG1pZ3JhdGlvbk1hbmFnZXI6IE1pZ3JhdGlvbk1hbmFnZXI7XG4gIHByaXZhdGUgZmlsZUxvY2tNYW5hZ2VyOiBGaWxlTG9ja01hbmFnZXI7XG4gIHByaXZhdGUgbWVtb3J5TWFuYWdlcjogTWVtb3J5TWFuYWdlcjtcbiAgcHJpdmF0ZSBjb25maWdNYW5hZ2VyOiBDb25maWdNYW5hZ2VyO1xuICBwcml2YXRlIG9wZXJhdGlvbmFsVGVsZW1ldHJ5OiBPcGVyYXRpb25hbFRlbGVtZXRyeTtcblxuICBjb25zdHJ1Y3RvcihcbiAgICBwb3J0Zm9saW9NYW5hZ2VyOiBQb3J0Zm9saW9NYW5hZ2VyLFxuICAgIGZpbGVMb2NrTWFuYWdlcjogRmlsZUxvY2tNYW5hZ2VyLFxuICAgIGNvbmZpZ01hbmFnZXI6IENvbmZpZ01hbmFnZXIsXG4gICAgbWlncmF0aW9uTWFuYWdlcjogTWlncmF0aW9uTWFuYWdlcixcbiAgICBtZW1vcnlNYW5hZ2VyOiBNZW1vcnlNYW5hZ2VyLFxuICAgIG9wZXJhdGlvbmFsVGVsZW1ldHJ5OiBPcGVyYXRpb25hbFRlbGVtZXRyeVxuICApIHtcbiAgICB0aGlzLnBvcnRmb2xpb01hbmFnZXIgPSBwb3J0Zm9saW9NYW5hZ2VyO1xuICAgIHRoaXMuZmlsZUxvY2tNYW5hZ2VyID0gZmlsZUxvY2tNYW5hZ2VyO1xuICAgIHRoaXMuY29uZmlnTWFuYWdlciA9IGNvbmZpZ01hbmFnZXI7XG4gICAgdGhpcy5taWdyYXRpb25NYW5hZ2VyID0gbWlncmF0aW9uTWFuYWdlcjtcbiAgICB0aGlzLm1lbW9yeU1hbmFnZXIgPSBtZW1vcnlNYW5hZ2VyO1xuICAgIHRoaXMub3BlcmF0aW9uYWxUZWxlbWV0cnkgPSBvcGVyYXRpb25hbFRlbGVtZXRyeTtcbiAgfVxuICBcbiAgLyoqXG4gICAqIEluaXRpYWxpemUgc2VydmVyIHdpdGggbWlncmF0aW9uIGNoZWNrXG4gICAqL1xuICBhc3luYyBpbml0aWFsaXplKG9wdGlvbnM6IFN0YXJ0dXBPcHRpb25zID0ge30pOiBQcm9taXNlPHZvaWQ+IHtcbiAgICBsb2dnZXIuaW5mbygnW1NlcnZlclN0YXJ0dXBdIEluaXRpYWxpemluZyBzZXJ2ZXIuLi4nKTtcbiAgICBcbiAgICAvLyBDaGVjayBpZiBtaWdyYXRpb24gaXMgbmVlZGVkXG4gICAgaWYgKCFvcHRpb25zLnNraXBNaWdyYXRpb24pIHtcbiAgICAgIGNvbnN0IG5lZWRzTWlncmF0aW9uID0gYXdhaXQgdGhpcy5taWdyYXRpb25NYW5hZ2VyLm5lZWRzTWlncmF0aW9uKCk7XG4gICAgICBcbiAgICAgIGlmIChuZWVkc01pZ3JhdGlvbikge1xuICAgICAgICBsb2dnZXIuaW5mbygnW1NlcnZlclN0YXJ0dXBdIExlZ2FjeSBwZXJzb25hcyBkZXRlY3RlZC4gU3RhcnRpbmcgbWlncmF0aW9uLi4uJyk7XG4gICAgICAgIFxuICAgICAgICBjb25zdCByZXN1bHQgPSBhd2FpdCB0aGlzLm1pZ3JhdGlvbk1hbmFnZXIubWlncmF0ZSh7IFxuICAgICAgICAgIGJhY2t1cDogb3B0aW9ucy5hdXRvQmFja3VwICE9PSBmYWxzZSAvLyBEZWZhdWx0IHRvIHRydWVcbiAgICAgICAgfSk7XG4gICAgICAgIFxuICAgICAgICBpZiAocmVzdWx0LnN1Y2Nlc3MpIHtcbiAgICAgICAgICBsb2dnZXIuaW5mbyhgW1NlcnZlclN0YXJ0dXBdIFN1Y2Nlc3NmdWxseSBtaWdyYXRlZCAke3Jlc3VsdC5taWdyYXRlZENvdW50fSBwZXJzb25hc2ApO1xuICAgICAgICAgIGlmIChyZXN1bHQuYmFja2VkVXAgJiYgcmVzdWx0LmJhY2t1cFBhdGgpIHtcbiAgICAgICAgICAgIGxvZ2dlci5pbmZvKGBbU2VydmVyU3RhcnR1cF0gQmFja3VwIGNyZWF0ZWQgYXQ6ICR7cmVzdWx0LmJhY2t1cFBhdGh9YCk7XG4gICAgICAgICAgfVxuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIGxvZ2dlci5lcnJvcignW1NlcnZlclN0YXJ0dXBdIE1pZ3JhdGlvbiBjb21wbGV0ZWQgd2l0aCBlcnJvcnM6Jyk7XG4gICAgICAgICAgcmVzdWx0LmVycm9ycy5mb3JFYWNoKGVyciA9PiBsb2dnZXIuZXJyb3IoYFtTZXJ2ZXJTdGFydHVwXSAgIC0gJHtlcnJ9YCkpO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuICAgIFxuICAgIC8vIEVuc3VyZSBwb3J0Zm9saW8gc3RydWN0dXJlIGV4aXN0c1xuICAgIGNvbnN0IHBvcnRmb2xpb0V4aXN0cyA9IGF3YWl0IHRoaXMucG9ydGZvbGlvTWFuYWdlci5leGlzdHMoKTtcbiAgICBpZiAoIXBvcnRmb2xpb0V4aXN0cykge1xuICAgICAgbG9nZ2VyLmluZm8oJ1tTZXJ2ZXJTdGFydHVwXSBDcmVhdGluZyBwb3J0Zm9saW8gZGlyZWN0b3J5IHN0cnVjdHVyZS4uLicpO1xuICAgICAgYXdhaXQgdGhpcy5wb3J0Zm9saW9NYW5hZ2VyLmluaXRpYWxpemUoKTtcbiAgICB9XG4gICAgXG4gICAgLy8gTG9nIHBvcnRmb2xpbyBzdGF0aXN0aWNzXG4gICAgY29uc3Qgc3RhdHMgPSBhd2FpdCB0aGlzLnBvcnRmb2xpb01hbmFnZXIuZ2V0U3RhdGlzdGljcygpO1xuICAgIGxvZ2dlci5pbmZvKCdbU2VydmVyU3RhcnR1cF0gUG9ydGZvbGlvIHN0YXRpc3RpY3M6Jyk7XG4gICAgT2JqZWN0LmVudHJpZXMoc3RhdHMpLmZvckVhY2goKFt0eXBlLCBjb3VudF0pID0+IHtcbiAgICAgIGlmIChjb3VudCA+IDApIHtcbiAgICAgICAgbG9nZ2VyLmluZm8oYFtTZXJ2ZXJTdGFydHVwXSAgIC0gJHt0eXBlfTogJHtjb3VudH0gZWxlbWVudHNgKTtcbiAgICAgIH1cbiAgICB9KTtcblxuICAgIC8vIEluaXRpYWxpemUgYXV0by1sb2FkIG1lbW9yaWVzXG4gICAgYXdhaXQgdGhpcy5pbml0aWFsaXplQXV0b0xvYWRNZW1vcmllcygpO1xuICB9XG5cbiAgLyoqXG4gICAqIFByb2Nlc3MgYSBzaW5nbGUgYXV0by1sb2FkIG1lbW9yeVxuICAgKiBGSVggKFNvbmFyQ2xvdWQpOiBFeHRyYWN0ZWQgdG8gcmVkdWNlIGNvZ25pdGl2ZSBjb21wbGV4aXR5XG4gICAqIEZJWCAoU29uYXJDbG91ZCk6IFJlZHVjZWQgcGFyYW1ldGVyIGNvdW50IGJ5IHVzaW5nIG9wdGlvbnMgb2JqZWN0XG4gICAqIEBwcml2YXRlXG4gICAqL1xuICBwcml2YXRlIGFzeW5jIHByb2Nlc3NBdXRvTG9hZE1lbW9yeShcbiAgICBtZW1vcnk6IGFueSxcbiAgICBtZW1vcnlNYW5hZ2VyOiBhbnksXG4gICAgb3B0aW9uczoge1xuICAgICAgdG90YWxUb2tlbnM6IG51bWJlcjtcbiAgICAgIHNpbmdsZUxpbWl0OiBudW1iZXIgfCB1bmRlZmluZWQ7XG4gICAgICB0b3RhbEJ1ZGdldDogbnVtYmVyO1xuICAgICAgc3VwcHJlc3NXYXJuaW5nczogYm9vbGVhbjtcbiAgICAgIHRvdGFsTWVtb3JpZXM6IG51bWJlcjtcbiAgICAgIGxvYWRlZENvdW50OiBudW1iZXI7XG4gICAgfVxuICApOiBQcm9taXNlPHtcbiAgICBza2lwOiBib29sZWFuO1xuICAgIGJyZWFrTG9vcDogYm9vbGVhbjtcbiAgICBza2lwcGVkQ291bnQ6IG51bWJlcjtcbiAgICBlc3RpbWF0ZWRUb2tlbnM6IG51bWJlcjtcbiAgICB3YXJuaW5nczogbnVtYmVyO1xuICB9PiB7XG4gICAgdHJ5IHtcbiAgICAgIC8vIEZJWDogRE1DUC1TRUMtMDA0IC0gTm9ybWFsaXplIFVuaWNvZGUgaW4gdXNlciBpbnB1dCB0byBwcmV2ZW50IGhvbW9ncmFwaCBhdHRhY2tzXG4gICAgICBjb25zdCBub3JtYWxpemVkTmFtZSA9IFVuaWNvZGVWYWxpZGF0b3Iubm9ybWFsaXplKG1lbW9yeS5tZXRhZGF0YS5uYW1lKTtcbiAgICAgIGNvbnN0IG1lbW9yeU5hbWUgPSBub3JtYWxpemVkTmFtZS5ub3JtYWxpemVkQ29udGVudDtcblxuICAgICAgLy8gUFIgIzE0MzY6IFZhbGlkYXRlIG1lbW9yeSBiZWZvcmUgbG9hZGluZ1xuICAgICAgY29uc3QgdmFsaWRhdGlvbiA9IG1lbW9yeS52YWxpZGF0ZSgpO1xuICAgICAgaWYgKCF2YWxpZGF0aW9uLnZhbGlkKSB7XG4gICAgICAgIHRocm93IEF1dG9Mb2FkRXJyb3IudmFsaWRhdGlvbkZhaWxlZChcbiAgICAgICAgICBtZW1vcnlOYW1lLFxuICAgICAgICAgIHZhbGlkYXRpb24uZXJyb3JzPy5tYXAoKGU6IHsgbWVzc2FnZTogc3RyaW5nIH0pID0+IGUubWVzc2FnZSkuam9pbignLCAnKSB8fCAnVW5rbm93biB2YWxpZGF0aW9uIGVycm9yJ1xuICAgICAgICApO1xuICAgICAgfVxuXG4gICAgICBjb25zdCBlc3RpbWF0ZWRUb2tlbnMgPSBtZW1vcnlNYW5hZ2VyLmVzdGltYXRlVG9rZW5zKG1lbW9yeS5jb250ZW50IHx8ICcnKTtcblxuICAgICAgLy8gQ2hlY2sgZm9yIHNpemUgd2FybmluZ3NcbiAgICAgIGNvbnN0IHdhcm5pbmdzID0gdGhpcy5jaGVja01lbW9yeVNpemVXYXJuaW5ncyhtZW1vcnlOYW1lLCBlc3RpbWF0ZWRUb2tlbnMsIG9wdGlvbnMuc3VwcHJlc3NXYXJuaW5ncyk7XG5cbiAgICAgIC8vIENoZWNrIGlmIG1lbW9yeSBzaG91bGQgYmUgc2tpcHBlZFxuICAgICAgY29uc3Qgc2tpcENoZWNrID0gdGhpcy5zaG91bGRTa2lwTWVtb3J5KG1lbW9yeU5hbWUsIGVzdGltYXRlZFRva2Vucywgb3B0aW9ucy50b3RhbFRva2Vucywgb3B0aW9ucy5zaW5nbGVMaW1pdCwgb3B0aW9ucy50b3RhbEJ1ZGdldCk7XG4gICAgICBpZiAoc2tpcENoZWNrLnNraXApIHtcbiAgICAgICAgaWYgKHNraXBDaGVjay5yZWFzb24gPT09ICdidWRnZXRfZXhjZWVkZWQnKSB7XG4gICAgICAgICAgY29uc3QgcmVtYWluaW5nID0gb3B0aW9ucy50b3RhbE1lbW9yaWVzIC0gb3B0aW9ucy5sb2FkZWRDb3VudDtcbiAgICAgICAgICBsb2dnZXIuaW5mbyhcbiAgICAgICAgICAgIGBbU2VydmVyU3RhcnR1cF0gVG9rZW4gYnVkZ2V0IHJlYWNoZWQgKCR7b3B0aW9ucy50b3RhbFRva2Vuc30vJHtvcHRpb25zLnRvdGFsQnVkZ2V0fSB0b2tlbnMpLiBgICtcbiAgICAgICAgICAgIGBMb2FkZWQgJHtvcHRpb25zLmxvYWRlZENvdW50fSBtZW1vcmllcywgc2tpcHBpbmcgcmVtYWluaW5nICR7cmVtYWluaW5nfS5gXG4gICAgICAgICAgKTtcbiAgICAgICAgICByZXR1cm4geyBza2lwOiBmYWxzZSwgYnJlYWtMb29wOiB0cnVlLCBza2lwcGVkQ291bnQ6IHJlbWFpbmluZywgZXN0aW1hdGVkVG9rZW5zOiAwLCB3YXJuaW5nczogMCB9O1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiB7IHNraXA6IHRydWUsIGJyZWFrTG9vcDogZmFsc2UsIHNraXBwZWRDb3VudDogMCwgZXN0aW1hdGVkVG9rZW5zOiAwLCB3YXJuaW5nczogMCB9O1xuICAgICAgfVxuXG4gICAgICAvLyBGSVggIzE0MzA6IEFjdGl2YXRlIHRoZSBtZW1vcnkgc28gaXQncyBhdmFpbGFibGUgZm9yIHVzZVxuICAgICAgbG9nZ2VyLmluZm8oYFtTZXJ2ZXJTdGFydHVwXSDwn5SEIEFjdGl2YXRpbmcgbWVtb3J5OiAke21lbW9yeU5hbWV9Li4uYCk7XG4gICAgICBhd2FpdCBtZW1vcnkuYWN0aXZhdGUoKTtcbiAgICAgIGxvZ2dlci5pbmZvKGBbU2VydmVyU3RhcnR1cF0g4pyFIE1lbW9yeSBhY3RpdmF0ZWQ6ICR7bWVtb3J5TmFtZX1gKTtcblxuICAgICAgLy8gRE1DUC1TRUMtMDA2OiBQZXItbWVtb3J5IGF1ZGl0IGRvd25ncmFkZWQgZnJvbSBzZWN1cml0eSBldmVudCB0byBkZWJ1Zy5cbiAgICAgIC8vIFdhcyBnZW5lcmF0aW5nIE8obikgc2VjdXJpdHkgZXZlbnRzIHBlciBzdGFydHVwIHdoZXJlIG4gPSBhdXRvLWxvYWQgY291bnQsXG4gICAgICAvLyBjYXVzaW5nIDI1eCBzZWN1cml0eSBidWZmZXIgdHVybm92ZXIuIENvbXBsZXRpb24gc3VtbWFyeSBrZXB0IGFzIHNlY3VyaXR5IGV2ZW50LlxuICAgICAgbG9nZ2VyLmRlYnVnKGBbU2VydmVyU3RhcnR1cF0gQXV0by1sb2FkZWQ6ICR7bWVtb3J5TmFtZX0gKH4ke2VzdGltYXRlZFRva2Vuc30gdG9rZW5zLCBwcmlvcml0eTogJHttZW1vcnkubWV0YWRhdGEucHJpb3JpdHl9KWApO1xuXG4gICAgICByZXR1cm4geyBza2lwOiBmYWxzZSwgYnJlYWtMb29wOiBmYWxzZSwgc2tpcHBlZENvdW50OiAwLCBlc3RpbWF0ZWRUb2tlbnMsIHdhcm5pbmdzIH07XG4gICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgIC8vIFBSICMxNDM2OiBTdHJ1Y3R1cmVkIGVycm9yIGhhbmRsaW5nIHdpdGggQXV0b0xvYWRFcnJvclxuICAgICAgdGhpcy5oYW5kbGVBdXRvTG9hZE1lbW9yeUVycm9yKGVycm9yLCBtZW1vcnkpO1xuICAgICAgcmV0dXJuIHsgc2tpcDogdHJ1ZSwgYnJlYWtMb29wOiBmYWxzZSwgc2tpcHBlZENvdW50OiAwLCBlc3RpbWF0ZWRUb2tlbnM6IDAsIHdhcm5pbmdzOiAwIH07XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIEhhbmRsZSBlcnJvcnMgZHVyaW5nIGF1dG8tbG9hZCBtZW1vcnkgcHJvY2Vzc2luZ1xuICAgKiBGSVggKFNvbmFyQ2xvdWQpOiBFeHRyYWN0ZWQgdG8gcmVkdWNlIGNvZ25pdGl2ZSBjb21wbGV4aXR5XG4gICAqIEBwcml2YXRlXG4gICAqL1xuICBwcml2YXRlIGhhbmRsZUF1dG9Mb2FkTWVtb3J5RXJyb3IoZXJyb3I6IHVua25vd24sIG1lbW9yeTogYW55KTogdm9pZCB7XG4gICAgaWYgKGVycm9yIGluc3RhbmNlb2YgQXV0b0xvYWRFcnJvcikge1xuICAgICAgbG9nZ2VyLmluZm8oXG4gICAgICAgIGBbU2VydmVyU3RhcnR1cF0gU2tpcHBpbmcgJyR7ZXJyb3IubWVtb3J5TmFtZX0nIC0gYCArXG4gICAgICAgIGAke2Vycm9yLnBoYXNlfSBwaGFzZSBmYWlsZWQ6ICR7ZXJyb3IubWVzc2FnZX1gXG4gICAgICApO1xuICAgIH0gZWxzZSB7XG4gICAgICBjb25zdCBtZW1vcnlOYW1lID0gbWVtb3J5Lm1ldGFkYXRhLm5hbWUgfHwgJ3Vua25vd24nO1xuICAgICAgbG9nZ2VyLndhcm4oYFtTZXJ2ZXJTdGFydHVwXSBVbmV4cGVjdGVkIGVycm9yIGxvYWRpbmcgJyR7bWVtb3J5TmFtZX0nOiAke2Vycm9yfWApO1xuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBDaGVjayBhbmQgbG9nIHNpemUgd2FybmluZ3MgZm9yIGEgbWVtb3J5XG4gICAqIEBwcml2YXRlXG4gICAqL1xuICBwcml2YXRlIGNoZWNrTWVtb3J5U2l6ZVdhcm5pbmdzKFxuICAgIG1lbW9yeU5hbWU6IHN0cmluZyxcbiAgICBlc3RpbWF0ZWRUb2tlbnM6IG51bWJlcixcbiAgICBzdXBwcmVzc1dhcm5pbmdzOiBib29sZWFuXG4gICk6IG51bWJlciB7XG4gICAgY29uc3QgTEFSR0VfTUVNT1JZX1dBUk4gPSA1MDAwO1xuICAgIGNvbnN0IFZFUllfTEFSR0VfTUVNT1JZX1dBUk4gPSAxMDAwMDtcblxuICAgIGlmIChzdXBwcmVzc1dhcm5pbmdzKSB7XG4gICAgICByZXR1cm4gMDtcbiAgICB9XG5cbiAgICBpZiAoZXN0aW1hdGVkVG9rZW5zID4gVkVSWV9MQVJHRV9NRU1PUllfV0FSTikge1xuICAgICAgbG9nZ2VyLndhcm4oXG4gICAgICAgIGBbU2VydmVyU3RhcnR1cF0gTWVtb3J5ICcke21lbW9yeU5hbWV9JyBpcyB2ZXJ5IGxhcmdlIGAgK1xuICAgICAgICBgKH4ke2VzdGltYXRlZFRva2Vuc30gdG9rZW5zLCByZWNvbW1lbmRlZDogJHtWRVJZX0xBUkdFX01FTU9SWV9XQVJOfSkuIGAgK1xuICAgICAgICBgVGhpcyBtYXkgaW1wYWN0IHN0YXJ0dXAgdGltZS5gXG4gICAgICApO1xuICAgICAgcmV0dXJuIDE7XG4gICAgfVxuXG4gICAgaWYgKGVzdGltYXRlZFRva2VucyA+IExBUkdFX01FTU9SWV9XQVJOKSB7XG4gICAgICBsb2dnZXIuaW5mbyhgW1NlcnZlclN0YXJ0dXBdIE1lbW9yeSAnJHttZW1vcnlOYW1lfScgaXMgbGFyZ2UgKH4ke2VzdGltYXRlZFRva2Vuc30gdG9rZW5zKS5gKTtcbiAgICAgIHJldHVybiAxO1xuICAgIH1cblxuICAgIHJldHVybiAwO1xuICB9XG5cbiAgLyoqXG4gICAqIENoZWNrIGlmIG1lbW9yeSBzaG91bGQgYmUgc2tpcHBlZCBkdWUgdG8gYnVkZ2V0IGxpbWl0c1xuICAgKiBAcHJpdmF0ZVxuICAgKi9cbiAgcHJpdmF0ZSBzaG91bGRTa2lwTWVtb3J5KFxuICAgIG1lbW9yeU5hbWU6IHN0cmluZyxcbiAgICBlc3RpbWF0ZWRUb2tlbnM6IG51bWJlcixcbiAgICB0b3RhbFRva2VuczogbnVtYmVyLFxuICAgIHNpbmdsZUxpbWl0OiBudW1iZXIgfCB1bmRlZmluZWQsXG4gICAgdG90YWxCdWRnZXQ6IG51bWJlclxuICApOiB7IHNraXA6IGJvb2xlYW47IHJlYXNvbj86IHN0cmluZyB9IHtcbiAgICAvLyBDaGVjayBzaW5nbGUgbWVtb3J5IGxpbWl0XG4gICAgaWYgKHNpbmdsZUxpbWl0ICE9PSB1bmRlZmluZWQgJiYgZXN0aW1hdGVkVG9rZW5zID4gc2luZ2xlTGltaXQpIHtcbiAgICAgIGxvZ2dlci5pbmZvKFxuICAgICAgICBgW1NlcnZlclN0YXJ0dXBdIFNraXBwaW5nICcke21lbW9yeU5hbWV9JyAtIGAgK1xuICAgICAgICBgZXhjZWVkcyBjb25maWd1cmVkIHNpbmdsZSBtZW1vcnkgbGltaXQgKCR7ZXN0aW1hdGVkVG9rZW5zfSA+ICR7c2luZ2xlTGltaXR9IHRva2VucylgXG4gICAgICApO1xuICAgICAgcmV0dXJuIHsgc2tpcDogdHJ1ZSwgcmVhc29uOiAnc2luZ2xlX2xpbWl0JyB9O1xuICAgIH1cblxuICAgIC8vIENoZWNrIHRvdGFsIGJ1ZGdldFxuICAgIGlmICh0b3RhbFRva2VucyArIGVzdGltYXRlZFRva2VucyA+IHRvdGFsQnVkZ2V0KSB7XG4gICAgICByZXR1cm4geyBza2lwOiB0cnVlLCByZWFzb246ICdidWRnZXRfZXhjZWVkZWQnIH07XG4gICAgfVxuXG4gICAgcmV0dXJuIHsgc2tpcDogZmFsc2UgfTtcbiAgfVxuXG4gIC8qKlxuICAgKiBMb2cgZXJyb3IgcmVjb3Zlcnkgc3VnZ2VzdGlvbnMgYmFzZWQgb24gZXJyb3IgdHlwZVxuICAgKiBAcHJpdmF0ZVxuICAgKi9cbiAgcHJpdmF0ZSBsb2dBdXRvTG9hZEVycm9yU3VnZ2VzdGlvbnMoZXJyb3JNZXNzYWdlOiBzdHJpbmcpOiB2b2lkIHtcbiAgICBpZiAoZXJyb3JNZXNzYWdlLmluY2x1ZGVzKCdFTk9FTlQnKSB8fCBlcnJvck1lc3NhZ2UuaW5jbHVkZXMoJ25vdCBmb3VuZCcpKSB7XG4gICAgICBsb2dnZXIuaW5mbygnW1NlcnZlclN0YXJ0dXBdIFRpcDogTWVtb3J5IGZpbGVzIG1heSBub3QgZXhpc3QgeWV0LiBUaGV5IHdpbGwgYmUgY3JlYXRlZCBvbiBmaXJzdCB1c2UuJyk7XG4gICAgfSBlbHNlIGlmIChlcnJvck1lc3NhZ2UuaW5jbHVkZXMoJ0VBQ0NFUycpIHx8IGVycm9yTWVzc2FnZS5pbmNsdWRlcygncGVybWlzc2lvbicpKSB7XG4gICAgICBsb2dnZXIud2FybignW1NlcnZlclN0YXJ0dXBdIFRpcDogQ2hlY2sgZmlsZSBwZXJtaXNzaW9ucyBmb3Igfi8uZG9sbGhvdXNlL3BvcnRmb2xpby9tZW1vcmllcy8nKTtcbiAgICB9IGVsc2UgaWYgKGVycm9yTWVzc2FnZS5pbmNsdWRlcygnWUFNTCcpIHx8IGVycm9yTWVzc2FnZS5pbmNsdWRlcygncGFyc2UnKSkge1xuICAgICAgbG9nZ2VyLndhcm4oJ1tTZXJ2ZXJTdGFydHVwXSBUaXA6IENoZWNrIFlBTUwgc3ludGF4IGluIG1lbW9yeSBmaWxlcy4gVXNlIGRvbGxob3VzZSB2YWxpZGF0ZSB0byBkaWFnbm9zZS4nKTtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogSW5pdGlhbGl6ZSBhdXRvLWxvYWQgbWVtb3JpZXNcbiAgICogSXNzdWUgIzE0MzA6IEF1dG9tYXRpY2FsbHkgbG9hZCBiYXNlbGluZSBtZW1vcmllcyBvbiBzZXJ2ZXIgc3RhcnR1cFxuICAgKiBAcHJpdmF0ZVxuICAgKi9cbiAgcHJpdmF0ZSBhc3luYyBpbml0aWFsaXplQXV0b0xvYWRNZW1vcmllcygpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICBjb25zdCBzdGFydFRpbWUgPSBEYXRlLm5vdygpO1xuICAgIGxldCB0b3RhbFRva2VucyA9IDA7XG4gICAgbGV0IGxvYWRlZENvdW50ID0gMDtcbiAgICBsZXQgc2tpcHBlZENvdW50ID0gMDtcbiAgICBsZXQgd2FybmluZ0NvdW50ID0gMDtcbiAgICBjb25zdCBlbWVyZ2VuY3lEaXNhYmxlZCA9IHByb2Nlc3MuZW52LkRPTExIT1VTRV9ESVNBQkxFX0FVVE9MT0FEID09PSAndHJ1ZSc7XG5cbiAgICB0cnkge1xuICAgICAgLy8gRklYOiBETUNQLVNFQy0wMDYgLSBBZGQgYXVkaXQgbG9nZ2luZyBmb3Igc2VjdXJpdHkgb3BlcmF0aW9uc1xuICAgICAgLy8gQ2hlY2sgZm9yIGVtZXJnZW5jeSBkaXNhYmxlXG4gICAgICBpZiAoZW1lcmdlbmN5RGlzYWJsZWQpIHtcbiAgICAgICAgbG9nZ2VyLmluZm8oJ1tTZXJ2ZXJTdGFydHVwXSBBdXRvLWxvYWQgZGlzYWJsZWQgdmlhIERPTExIT1VTRV9ESVNBQkxFX0FVVE9MT0FEJyk7XG4gICAgICAgIFNlY3VyaXR5TW9uaXRvci5sb2dTZWN1cml0eUV2ZW50KHtcbiAgICAgICAgICB0eXBlOiAnTUVNT1JZX0xPQURFRCcsXG4gICAgICAgICAgc2V2ZXJpdHk6ICdMT1cnLFxuICAgICAgICAgIHNvdXJjZTogJ1NlcnZlclN0YXJ0dXAuaW5pdGlhbGl6ZUF1dG9Mb2FkTWVtb3JpZXMnLFxuICAgICAgICAgIGRldGFpbHM6ICdBdXRvLWxvYWQgbWVtb3JpZXMgZGlzYWJsZWQgdmlhIGVtZXJnZW5jeSBlbnZpcm9ubWVudCB2YXJpYWJsZScsXG4gICAgICAgICAgYWRkaXRpb25hbERhdGE6IHsgcmVhc29uOiAnRE9MTEhPVVNFX0RJU0FCTEVfQVVUT0xPQUQ9dHJ1ZScgfVxuICAgICAgICB9KTtcbiAgICAgICAgcmV0dXJuO1xuICAgICAgfVxuXG4gICAgICAvLyBDaGVjayBpZiBhdXRvLWxvYWQgaXMgZW5hYmxlZCBpbiBjb25maWcgKERJOiB1c2UgdGhpcy5jb25maWdNYW5hZ2VyKVxuICAgICAgYXdhaXQgdGhpcy5jb25maWdNYW5hZ2VyLmluaXRpYWxpemUoKTtcbiAgICAgIGNvbnN0IGNvbmZpZyA9IHRoaXMuY29uZmlnTWFuYWdlci5nZXRDb25maWcoKTtcblxuICAgICAgaWYgKCFjb25maWcuYXV0b0xvYWQuZW5hYmxlZCkge1xuICAgICAgICBsb2dnZXIuZGVidWcoJ1tTZXJ2ZXJTdGFydHVwXSBBdXRvLWxvYWQgbWVtb3JpZXMgZGlzYWJsZWQgaW4gY29uZmlndXJhdGlvbicpO1xuICAgICAgICByZXR1cm47XG4gICAgICB9XG5cbiAgICAgIC8vIERJOiB1c2UgdGhpcy5tZW1vcnlNYW5hZ2VyIGluc3RlYWQgb2YgbmV3IE1lbW9yeU1hbmFnZXIoKVxuICAgICAgY29uc3QgbWVtb3J5TWFuYWdlciA9IHRoaXMubWVtb3J5TWFuYWdlcjtcblxuICAgICAgLy8gSXNzdWUgIzE0MzA6IEluc3RhbGwgc2VlZCBtZW1vcmllcyBiZWZvcmUgbG9hZGluZyBhdXRvLWxvYWQgbWVtb3JpZXNcbiAgICAgIC8vIFRoaXMgZW5zdXJlcyBiYXNlbGluZSBrbm93bGVkZ2UgaXMgYXZhaWxhYmxlIG9uIGZpcnN0IHJ1blxuICAgICAgbG9nZ2VyLmluZm8oJ1tTZXJ2ZXJTdGFydHVwXSDwn4yxIEluc3RhbGxpbmcgc2VlZCBtZW1vcmllcy4uLicpO1xuICAgICAgYXdhaXQgbWVtb3J5TWFuYWdlci5pbnN0YWxsU2VlZE1lbW9yaWVzKCk7XG4gICAgICBsb2dnZXIuaW5mbygnW1NlcnZlclN0YXJ0dXBdIOKchSBTZWVkIGluc3RhbGxhdGlvbiBjb21wbGV0ZScpO1xuXG4gICAgICBsb2dnZXIuaW5mbygnW1NlcnZlclN0YXJ0dXBdIPCflI0gRmV0Y2hpbmcgYXV0by1sb2FkIG1lbW9yaWVzLi4uJyk7XG4gICAgICBjb25zdCBhdXRvTG9hZE1lbW9yaWVzID0gYXdhaXQgbWVtb3J5TWFuYWdlci5nZXRBdXRvTG9hZE1lbW9yaWVzKCk7XG4gICAgICBsb2dnZXIuaW5mbyhgW1NlcnZlclN0YXJ0dXBdIPCfk4sgQXV0by1sb2FkIG1lbW9yaWVzIGZvdW5kOiAke2F1dG9Mb2FkTWVtb3JpZXMubGVuZ3RofWApO1xuXG4gICAgICBpZiAoYXV0b0xvYWRNZW1vcmllcy5sZW5ndGggPT09IDApIHtcbiAgICAgICAgbG9nZ2VyLndhcm4oJ1tTZXJ2ZXJTdGFydHVwXSDimqDvuI8gIE5vIGF1dG8tbG9hZCBtZW1vcmllcyBjb25maWd1cmVkIC0gYmFzZWxpbmUga25vd2xlZGdlIG1heSBub3QgYmUgYXZhaWxhYmxlJyk7XG4gICAgICAgIHJldHVybjtcbiAgICAgIH1cblxuICAgICAgLy8gVXNlci1jb25maWd1cmVkIGxpbWl0cyAoaGFyZCBlbmZvcmNlbWVudCBpZiBzZXQpXG4gICAgICAvLyBQUiAjMTQzNjogVmFsaWRhdGUgbWF4VG9rZW5CdWRnZXQgd2l0aCBib3VuZHMgYW5kIHVzZXIgd2FybmluZ1xuICAgICAgLy8gT