@dollhousemcp/mcp-server 1.5.2 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +56 -0
- package/README.md +494 -111
- package/data/agents/code-reviewer.md +8 -1
- package/data/agents/research-assistant.md +8 -1
- package/data/agents/task-manager.md +8 -1
- package/data/ensembles/business-advisor.md +8 -1
- package/data/ensembles/creative-studio.md +8 -1
- package/data/ensembles/development-team.md +8 -1
- package/data/ensembles/security-analysis-team.md +8 -1
- package/data/memories/conversation-history.md +8 -1
- package/data/memories/learning-progress.md +8 -1
- package/data/memories/project-context.md +8 -1
- package/data/personas/business-consultant.md +8 -1
- package/data/personas/creative-writer.md +8 -1
- package/data/personas/debug-detective.md +8 -1
- package/data/personas/eli5-explainer.md +8 -1
- package/data/personas/security-analyst.md +8 -1
- package/data/personas/technical-analyst.md +8 -1
- package/data/skills/code-review.md +8 -1
- package/data/skills/creative-writing.md +8 -1
- package/data/skills/data-analysis.md +8 -1
- package/data/skills/penetration-testing.md +8 -1
- package/data/skills/research.md +8 -1
- package/data/skills/threat-modeling.md +8 -1
- package/data/skills/translation.md +8 -1
- package/data/templates/code-documentation.md +8 -1
- package/data/templates/email-professional.md +8 -1
- package/data/templates/meeting-notes.md +8 -1
- package/data/templates/penetration-test-report.md +8 -1
- package/data/templates/project-brief.md +8 -1
- package/data/templates/report-executive.md +8 -1
- package/data/templates/security-vulnerability-report.md +8 -1
- package/data/templates/threat-assessment-report.md +8 -1
- package/dist/auth/GitHubAuthManager.d.ts +6 -1
- package/dist/auth/GitHubAuthManager.d.ts.map +1 -1
- package/dist/auth/GitHubAuthManager.js +45 -18
- package/dist/benchmarks/IndexPerformanceBenchmark.d.ts +98 -0
- package/dist/benchmarks/IndexPerformanceBenchmark.d.ts.map +1 -0
- package/dist/benchmarks/IndexPerformanceBenchmark.js +531 -0
- package/dist/cache/CollectionCache.d.ts.map +1 -1
- package/dist/cache/CollectionCache.js +13 -3
- package/dist/cache/CollectionIndexCache.d.ts +77 -0
- package/dist/cache/CollectionIndexCache.d.ts.map +1 -0
- package/dist/cache/CollectionIndexCache.js +349 -0
- package/dist/cache/LRUCache.d.ts +93 -0
- package/dist/cache/LRUCache.d.ts.map +1 -0
- package/dist/cache/LRUCache.js +299 -0
- package/dist/cache/index.d.ts +1 -0
- package/dist/cache/index.d.ts.map +1 -1
- package/dist/cache/index.js +2 -1
- package/dist/collection/CollectionBrowser.d.ts +21 -1
- package/dist/collection/CollectionBrowser.d.ts.map +1 -1
- package/dist/collection/CollectionBrowser.js +130 -10
- package/dist/collection/CollectionIndexManager.d.ts +151 -0
- package/dist/collection/CollectionIndexManager.d.ts.map +1 -0
- package/dist/collection/CollectionIndexManager.js +499 -0
- package/dist/collection/CollectionSearch.d.ts +55 -0
- package/dist/collection/CollectionSearch.d.ts.map +1 -1
- package/dist/collection/CollectionSearch.js +338 -13
- package/dist/collection/CollectionSeeder.d.ts.map +1 -1
- package/dist/collection/CollectionSeeder.js +38 -1
- package/dist/collection/ElementInstaller.d.ts +31 -0
- package/dist/collection/ElementInstaller.d.ts.map +1 -1
- package/dist/collection/ElementInstaller.js +77 -15
- package/dist/collection/PersonaSubmitter.d.ts +1 -1
- package/dist/collection/PersonaSubmitter.d.ts.map +1 -1
- package/dist/collection/PersonaSubmitter.js +2 -2
- package/dist/collection/index.d.ts +1 -0
- package/dist/collection/index.d.ts.map +1 -1
- package/dist/collection/index.js +2 -1
- package/dist/config/ConfigManager.d.ts +78 -0
- package/dist/config/ConfigManager.d.ts.map +1 -0
- package/dist/config/ConfigManager.js +216 -0
- package/dist/config/element-types.d.ts +135 -0
- package/dist/config/element-types.d.ts.map +1 -0
- package/dist/config/element-types.js +108 -0
- package/dist/config/index.d.ts +2 -0
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +3 -1
- package/dist/config/portfolio-constants.d.ts +83 -0
- package/dist/config/portfolio-constants.d.ts.map +1 -0
- package/dist/config/portfolio-constants.js +99 -0
- package/dist/elements/BaseElement.d.ts +14 -2
- package/dist/elements/BaseElement.d.ts.map +1 -1
- package/dist/elements/BaseElement.js +88 -6
- package/dist/elements/agents/Agent.d.ts +10 -1
- package/dist/elements/agents/Agent.d.ts.map +1 -1
- package/dist/elements/agents/Agent.js +66 -19
- package/dist/elements/agents/AgentManager.d.ts +2 -0
- package/dist/elements/agents/AgentManager.d.ts.map +1 -1
- package/dist/elements/agents/AgentManager.js +12 -10
- package/dist/elements/skills/Skill.d.ts +10 -1
- package/dist/elements/skills/Skill.d.ts.map +1 -1
- package/dist/elements/skills/Skill.js +40 -3
- package/dist/elements/skills/SkillManager.d.ts +1 -0
- package/dist/elements/skills/SkillManager.d.ts.map +1 -1
- package/dist/elements/skills/SkillManager.js +10 -4
- package/dist/elements/templates/Template.d.ts +10 -1
- package/dist/elements/templates/Template.d.ts.map +1 -1
- package/dist/elements/templates/Template.js +35 -18
- package/dist/elements/templates/TemplateManager.d.ts +1 -1
- package/dist/elements/templates/TemplateManager.d.ts.map +1 -1
- package/dist/elements/templates/TemplateManager.js +6 -5
- package/dist/generated/version.d.ts +2 -2
- package/dist/generated/version.js +3 -3
- package/dist/index.barrel.d.ts +1 -2
- package/dist/index.barrel.d.ts.map +1 -1
- package/dist/index.barrel.js +2 -4
- package/dist/index.d.ts +143 -25
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1883 -310
- package/dist/persona/PersonaElement.d.ts +10 -0
- package/dist/persona/PersonaElement.d.ts.map +1 -1
- package/dist/persona/PersonaElement.js +55 -32
- package/dist/persona/PersonaElementManager.d.ts.map +1 -1
- package/dist/persona/PersonaElementManager.js +13 -11
- package/dist/persona/PersonaLoader.d.ts.map +1 -1
- package/dist/persona/PersonaLoader.js +8 -2
- package/dist/persona/export-import/PersonaImporter.d.ts.map +1 -1
- package/dist/persona/export-import/PersonaImporter.js +24 -5
- package/dist/persona/export-import/PersonaSharer.d.ts +21 -0
- package/dist/persona/export-import/PersonaSharer.d.ts.map +1 -1
- package/dist/persona/export-import/PersonaSharer.js +198 -22
- package/dist/portfolio/DefaultElementProvider.d.ts +90 -0
- package/dist/portfolio/DefaultElementProvider.d.ts.map +1 -1
- package/dist/portfolio/DefaultElementProvider.js +499 -7
- package/dist/portfolio/GitHubPortfolioIndexer.d.ts +129 -0
- package/dist/portfolio/GitHubPortfolioIndexer.d.ts.map +1 -0
- package/dist/portfolio/GitHubPortfolioIndexer.js +475 -0
- package/dist/portfolio/MigrationManager.d.ts.map +1 -1
- package/dist/portfolio/MigrationManager.js +136 -3
- package/dist/portfolio/PortfolioIndexManager.d.ts +130 -0
- package/dist/portfolio/PortfolioIndexManager.d.ts.map +1 -0
- package/dist/portfolio/PortfolioIndexManager.js +478 -0
- package/dist/portfolio/PortfolioManager.d.ts +5 -0
- package/dist/portfolio/PortfolioManager.d.ts.map +1 -1
- package/dist/portfolio/PortfolioManager.js +61 -20
- package/dist/portfolio/PortfolioRepoManager.d.ts +75 -0
- package/dist/portfolio/PortfolioRepoManager.d.ts.map +1 -0
- package/dist/portfolio/PortfolioRepoManager.js +337 -0
- package/dist/portfolio/UnifiedIndexManager.d.ts +388 -0
- package/dist/portfolio/UnifiedIndexManager.d.ts.map +1 -0
- package/dist/portfolio/UnifiedIndexManager.js +1434 -0
- package/dist/portfolio/index.d.ts +15 -0
- package/dist/portfolio/index.d.ts.map +1 -0
- package/dist/portfolio/index.js +15 -0
- package/dist/portfolio/types.d.ts +7 -0
- package/dist/portfolio/types.d.ts.map +1 -1
- package/dist/portfolio/types.js +6 -1
- package/dist/security/InputValidator.d.ts.map +1 -1
- package/dist/security/InputValidator.js +50 -48
- package/dist/security/audit/SecurityAuditor.d.ts.map +1 -1
- package/dist/security/audit/SecurityAuditor.js +17 -9
- package/dist/security/audit/config/suppressions.d.ts.map +1 -1
- package/dist/security/audit/config/suppressions.js +19 -3
- package/dist/security/contentValidator.d.ts +2 -0
- package/dist/security/contentValidator.d.ts.map +1 -1
- package/dist/security/contentValidator.js +115 -4
- package/dist/security/secureYamlParser.d.ts +1 -0
- package/dist/security/secureYamlParser.d.ts.map +1 -1
- package/dist/security/secureYamlParser.js +29 -7
- package/dist/security/securityMonitor.d.ts +1 -1
- package/dist/security/securityMonitor.d.ts.map +1 -1
- package/dist/security/securityMonitor.js +1 -1
- package/dist/security/tokenManager.d.ts +1 -1
- package/dist/security/tokenManager.d.ts.map +1 -1
- package/dist/security/tokenManager.js +30 -10
- package/dist/server/ServerSetup.d.ts +22 -2
- package/dist/server/ServerSetup.d.ts.map +1 -1
- package/dist/server/ServerSetup.js +77 -12
- package/dist/server/tools/AuthTools.d.ts.map +1 -1
- package/dist/server/tools/AuthTools.js +33 -1
- package/dist/server/tools/BuildInfoTools.d.ts +25 -0
- package/dist/server/tools/BuildInfoTools.d.ts.map +1 -0
- package/dist/server/tools/BuildInfoTools.js +36 -0
- package/dist/server/tools/CollectionTools.d.ts.map +1 -1
- package/dist/server/tools/CollectionTools.js +55 -46
- package/dist/server/tools/ConfigTools.d.ts.map +1 -1
- package/dist/server/tools/ConfigTools.js +29 -1
- package/dist/server/tools/PersonaTools.d.ts +4 -2
- package/dist/server/tools/PersonaTools.d.ts.map +1 -1
- package/dist/server/tools/PersonaTools.js +5 -152
- package/dist/server/tools/PortfolioTools.d.ts +12 -0
- package/dist/server/tools/PortfolioTools.d.ts.map +1 -0
- package/dist/server/tools/PortfolioTools.js +221 -0
- package/dist/server/tools/index.d.ts +3 -1
- package/dist/server/tools/index.d.ts.map +1 -1
- package/dist/server/tools/index.js +4 -2
- package/dist/server/types.d.ts +40 -5
- package/dist/server/types.d.ts.map +1 -1
- package/dist/server/types.js +1 -1
- package/dist/services/BuildInfoService.d.ts +84 -0
- package/dist/services/BuildInfoService.d.ts.map +1 -0
- package/dist/services/BuildInfoService.js +271 -0
- package/dist/tools/portfolio/PortfolioElementAdapter.d.ts +54 -0
- package/dist/tools/portfolio/PortfolioElementAdapter.d.ts.map +1 -0
- package/dist/tools/portfolio/PortfolioElementAdapter.js +229 -0
- package/dist/tools/portfolio/submitToPortfolioTool.d.ts +164 -0
- package/dist/tools/portfolio/submitToPortfolioTool.d.ts.map +1 -0
- package/dist/tools/portfolio/submitToPortfolioTool.js +1523 -0
- package/dist/tools/portfolio/types.d.ts +41 -0
- package/dist/tools/portfolio/types.d.ts.map +1 -0
- package/dist/tools/portfolio/types.js +15 -0
- package/dist/types/collection.d.ts +51 -0
- package/dist/types/collection.d.ts.map +1 -1
- package/dist/types/collection.js +1 -1
- package/dist/utils/EarlyTerminationSearch.d.ts +41 -0
- package/dist/utils/EarlyTerminationSearch.d.ts.map +1 -0
- package/dist/utils/EarlyTerminationSearch.js +164 -0
- package/dist/utils/ErrorHandler.d.ts +86 -0
- package/dist/utils/ErrorHandler.d.ts.map +1 -0
- package/dist/utils/ErrorHandler.js +201 -0
- package/dist/utils/FileDiscoveryUtil.d.ts +53 -0
- package/dist/utils/FileDiscoveryUtil.d.ts.map +1 -0
- package/dist/utils/FileDiscoveryUtil.js +169 -0
- package/dist/utils/GitHubRateLimiter.d.ts +88 -0
- package/dist/utils/GitHubRateLimiter.d.ts.map +1 -0
- package/dist/utils/GitHubRateLimiter.js +315 -0
- package/dist/utils/PerformanceMonitor.d.ts +134 -0
- package/dist/utils/PerformanceMonitor.d.ts.map +1 -0
- package/dist/utils/PerformanceMonitor.js +347 -0
- package/dist/utils/RateLimiter.d.ts.map +1 -0
- package/dist/utils/RateLimiter.js +172 -0
- package/dist/utils/SecureDownloader.d.ts +241 -0
- package/dist/utils/SecureDownloader.d.ts.map +1 -0
- package/dist/utils/SecureDownloader.js +759 -0
- package/dist/utils/ToolCache.d.ts +82 -0
- package/dist/utils/ToolCache.d.ts.map +1 -0
- package/dist/utils/ToolCache.js +196 -0
- package/dist/utils/errorCodes.d.ts +136 -0
- package/dist/utils/errorCodes.d.ts.map +1 -0
- package/dist/utils/errorCodes.js +87 -0
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +4 -1
- package/dist/utils/installation.d.ts +1 -1
- package/dist/utils/installation.d.ts.map +1 -1
- package/dist/utils/installation.js +9 -8
- package/dist/utils/searchUtils.d.ts +31 -0
- package/dist/utils/searchUtils.d.ts.map +1 -1
- package/dist/utils/searchUtils.js +62 -1
- package/package.json +17 -7
- package/dist/config/updateConfig.d.ts +0 -84
- package/dist/config/updateConfig.d.ts.map +0 -1
- package/dist/config/updateConfig.js +0 -148
- package/dist/server/tools/UpdateTools.d.ts +0 -10
- package/dist/server/tools/UpdateTools.d.ts.map +0 -1
- package/dist/server/tools/UpdateTools.js +0 -85
- package/dist/update/BackupManager.d.ts +0 -63
- package/dist/update/BackupManager.d.ts.map +0 -1
- package/dist/update/BackupManager.js +0 -370
- package/dist/update/DependencyChecker.d.ts +0 -41
- package/dist/update/DependencyChecker.d.ts.map +0 -1
- package/dist/update/DependencyChecker.js +0 -132
- package/dist/update/RateLimiter.d.ts.map +0 -1
- package/dist/update/RateLimiter.js +0 -172
- package/dist/update/SignatureVerifier.d.ts +0 -71
- package/dist/update/SignatureVerifier.d.ts.map +0 -1
- package/dist/update/SignatureVerifier.js +0 -214
- package/dist/update/UpdateChecker.d.ts +0 -132
- package/dist/update/UpdateChecker.d.ts.map +0 -1
- package/dist/update/UpdateChecker.js +0 -506
- package/dist/update/UpdateManager.d.ts +0 -60
- package/dist/update/UpdateManager.d.ts.map +0 -1
- package/dist/update/UpdateManager.js +0 -730
- package/dist/update/VersionManager.d.ts +0 -31
- package/dist/update/VersionManager.d.ts.map +0 -1
- package/dist/update/VersionManager.js +0 -181
- package/dist/update/index.d.ts +0 -9
- package/dist/update/index.d.ts.map +0 -1
- package/dist/update/index.js +0 -9
- /package/dist/{update → utils}/RateLimiter.d.ts +0 -0
|
@@ -0,0 +1,1523 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool for submitting content to GitHub portfolio repositories
|
|
3
|
+
* Replaces the broken issue-based submission with direct repository saves
|
|
4
|
+
*
|
|
5
|
+
* FIXES IMPLEMENTED (PR #503):
|
|
6
|
+
* 1. TYPE SAFETY FIX #1 (Issue #497): Changed apiCache from 'any' to proper APICache type
|
|
7
|
+
* 2. TYPE SAFETY FIX #2 (Issue #497): Replaced complex type casting with PortfolioElementAdapter
|
|
8
|
+
* 3. PERFORMANCE (PR #496 recommendation): Using FileDiscoveryUtil for optimized file search
|
|
9
|
+
*/
|
|
10
|
+
import { GitHubAuthManager } from '../../auth/GitHubAuthManager.js';
|
|
11
|
+
import { PortfolioRepoManager } from '../../portfolio/PortfolioRepoManager.js';
|
|
12
|
+
import { TokenManager } from '../../security/tokenManager.js';
|
|
13
|
+
import { ContentValidator } from '../../security/contentValidator.js';
|
|
14
|
+
import { PortfolioManager } from '../../portfolio/PortfolioManager.js';
|
|
15
|
+
import { PortfolioIndexManager } from '../../portfolio/PortfolioIndexManager.js';
|
|
16
|
+
import { ElementType } from '../../portfolio/types.js';
|
|
17
|
+
import { logger } from '../../utils/logger.js';
|
|
18
|
+
import { UnicodeValidator } from '../../security/validators/unicodeValidator.js';
|
|
19
|
+
import { SecurityMonitor } from '../../security/securityMonitor.js';
|
|
20
|
+
import { PortfolioElementAdapter } from './PortfolioElementAdapter.js';
|
|
21
|
+
import { FileDiscoveryUtil } from '../../utils/FileDiscoveryUtil.js';
|
|
22
|
+
import { ErrorHandler } from '../../utils/ErrorHandler.js';
|
|
23
|
+
import { FILE_SIZE_LIMITS, RETRY_CONFIG, SEARCH_CONFIG, getValidatedTimeout, calculateRetryDelay } from '../../config/portfolio-constants.js';
|
|
24
|
+
import { githubRateLimiter } from '../../utils/GitHubRateLimiter.js';
|
|
25
|
+
import { EarlyTerminationSearch } from '../../utils/EarlyTerminationSearch.js';
|
|
26
|
+
import * as path from 'path';
|
|
27
|
+
import * as fs from 'fs/promises';
|
|
28
|
+
export class SubmitToPortfolioTool {
|
|
29
|
+
authManager;
|
|
30
|
+
portfolioManager;
|
|
31
|
+
contentValidator;
|
|
32
|
+
constructor(apiCache) {
|
|
33
|
+
// TYPE SAFETY FIX #1: Proper typing for apiCache parameter
|
|
34
|
+
// Previously: constructor(apiCache: any)
|
|
35
|
+
// Now: constructor(apiCache: APICache) with proper import
|
|
36
|
+
this.authManager = new GitHubAuthManager(apiCache);
|
|
37
|
+
this.portfolioManager = new PortfolioRepoManager();
|
|
38
|
+
this.contentValidator = new ContentValidator();
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Validates and normalizes input parameters to prevent Unicode attacks and ensure data safety
|
|
42
|
+
* @param params The input parameters from the user
|
|
43
|
+
* @returns Validation result with normalized name or error response
|
|
44
|
+
*/
|
|
45
|
+
async validateAndNormalizeParams(params) {
|
|
46
|
+
// Normalize user input to prevent Unicode attacks (DMCP-SEC-004)
|
|
47
|
+
const normalizedName = UnicodeValidator.normalize(params.name);
|
|
48
|
+
if (!normalizedName.isValid) {
|
|
49
|
+
SecurityMonitor.logSecurityEvent({
|
|
50
|
+
type: 'UNICODE_VALIDATION_ERROR',
|
|
51
|
+
severity: 'MEDIUM',
|
|
52
|
+
source: 'SubmitToPortfolioTool.execute',
|
|
53
|
+
details: `Invalid Unicode in element name: ${normalizedName.detectedIssues?.[0] || 'unknown error'}`
|
|
54
|
+
});
|
|
55
|
+
return {
|
|
56
|
+
success: false,
|
|
57
|
+
error: {
|
|
58
|
+
success: false,
|
|
59
|
+
message: `Invalid characters in element name: ${normalizedName.detectedIssues?.[0] || 'unknown error'}`,
|
|
60
|
+
error: 'INVALID_INPUT'
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
success: true,
|
|
66
|
+
safeName: normalizedName.normalizedContent
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Checks if the user is authenticated with GitHub
|
|
71
|
+
* @returns Authentication check result with status or error response
|
|
72
|
+
*/
|
|
73
|
+
async checkAuthentication() {
|
|
74
|
+
const authStatus = await this.authManager.getAuthStatus();
|
|
75
|
+
if (!authStatus.isAuthenticated) {
|
|
76
|
+
// Log authentication required (using existing event type)
|
|
77
|
+
logger.warn('User attempted portfolio submission without authentication');
|
|
78
|
+
return {
|
|
79
|
+
success: false,
|
|
80
|
+
error: {
|
|
81
|
+
success: false,
|
|
82
|
+
message: 'Not authenticated. Please authenticate first using the GitHub OAuth flow.\n\n' +
|
|
83
|
+
'Visit: https://docs.anthropic.com/en/docs/claude-code/oauth-setup\n' +
|
|
84
|
+
'Or run: gh auth login --web',
|
|
85
|
+
error: 'NOT_AUTHENTICATED'
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
success: true,
|
|
91
|
+
authStatus
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Discovers content locally with smart type detection
|
|
96
|
+
* @param safeName The normalized name to search for
|
|
97
|
+
* @param explicitType Optional explicit element type provided by user
|
|
98
|
+
* @param originalName Original user-provided name for error messages
|
|
99
|
+
* @returns Content discovery result with element type and path or error response
|
|
100
|
+
*/
|
|
101
|
+
async discoverContentWithTypeDetection(safeName, explicitType, originalName) {
|
|
102
|
+
let elementType = explicitType;
|
|
103
|
+
let localPath = null;
|
|
104
|
+
if (elementType) {
|
|
105
|
+
// Type explicitly provided - search in that specific directory only
|
|
106
|
+
localPath = await this.findLocalContent(safeName, elementType);
|
|
107
|
+
if (!localPath) {
|
|
108
|
+
// UX IMPROVEMENT: Provide helpful suggestions for finding content
|
|
109
|
+
const portfolioManager = PortfolioManager.getInstance();
|
|
110
|
+
const elementDir = portfolioManager.getElementDir(elementType);
|
|
111
|
+
return {
|
|
112
|
+
success: false,
|
|
113
|
+
error: {
|
|
114
|
+
success: false,
|
|
115
|
+
message: `Could not find ${elementType} named "${originalName || safeName}" in local portfolio.\n\n` +
|
|
116
|
+
`**Searched in**: ${elementDir}\n\n` +
|
|
117
|
+
`**Troubleshooting Tips**:\n` +
|
|
118
|
+
`• Check if the file exists using your file explorer\n` +
|
|
119
|
+
`• Try using the exact filename (without extension)\n` +
|
|
120
|
+
`• Use \`list_portfolio\` to see all available ${elementType}\n` +
|
|
121
|
+
`• If unsure of the type, omit --type and let the system detect it\n\n` +
|
|
122
|
+
`**Common name formats that work**:\n` +
|
|
123
|
+
`• "my-element" (kebab-case)\n` +
|
|
124
|
+
`• "My Element" (with spaces)\n` +
|
|
125
|
+
`• "MyElement" (PascalCase)\n` +
|
|
126
|
+
`• Partial matches are supported`,
|
|
127
|
+
error: 'CONTENT_NOT_FOUND'
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
// CRITICAL FIX: No type provided - implement smart detection across ALL element types
|
|
134
|
+
// This prevents the previous hardcoded default to PERSONA and enables proper type detection
|
|
135
|
+
const detectionResult = await this.detectElementType(safeName);
|
|
136
|
+
if (!detectionResult.found) {
|
|
137
|
+
// UX IMPROVEMENT: Enhanced guidance with specific suggestions
|
|
138
|
+
const availableTypes = Object.values(ElementType).join(', ');
|
|
139
|
+
// Get suggestions for similar names
|
|
140
|
+
const suggestions = await this.generateNameSuggestions(safeName);
|
|
141
|
+
let message = `Content "${originalName || safeName}" not found in portfolio.\n\n`;
|
|
142
|
+
message += `🔍 **Searched in all element types**: ${availableTypes}\n\n`;
|
|
143
|
+
if (suggestions.length > 0) {
|
|
144
|
+
message += `💡 **Did you mean one of these?**\n`;
|
|
145
|
+
for (const suggestion of suggestions.slice(0, SEARCH_CONFIG.MAX_SUGGESTIONS)) {
|
|
146
|
+
message += ` • "${suggestion.name}" (${suggestion.type})\n`;
|
|
147
|
+
}
|
|
148
|
+
message += `\n`;
|
|
149
|
+
}
|
|
150
|
+
message += `🛠️ **Troubleshooting Steps**:\n`;
|
|
151
|
+
message += `1. 📝 Use \`list_portfolio\` to see all available content\n`;
|
|
152
|
+
message += `2. 🔍 Check exact spelling and try variations:\n`;
|
|
153
|
+
message += ` • "${(originalName || safeName).toLowerCase()}" (lowercase)\n`;
|
|
154
|
+
message += ` • "${(originalName || safeName).replace(/[^a-z0-9]/gi, '-').toLowerCase()}" (normalized)\n`;
|
|
155
|
+
if ((originalName || safeName).includes('.')) {
|
|
156
|
+
message += ` • "${(originalName || safeName).replace(/\./g, '')}" (no dots)\n`;
|
|
157
|
+
}
|
|
158
|
+
message += `3. 🎯 Specify element type: \`submit_content "${originalName || safeName}" --type=personas\`\n`;
|
|
159
|
+
message += `4. 📁 Check if file exists in portfolio directories\n\n`;
|
|
160
|
+
message += `📝 **Tip**: The system searches filenames AND metadata names with fuzzy matching.`;
|
|
161
|
+
return {
|
|
162
|
+
success: false,
|
|
163
|
+
error: {
|
|
164
|
+
success: false,
|
|
165
|
+
message,
|
|
166
|
+
error: 'CONTENT_NOT_FOUND'
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
if (detectionResult.matches.length > 1) {
|
|
171
|
+
// Multiple matches found - ask user to specify type
|
|
172
|
+
const matchDetails = detectionResult.matches.map(m => `- ${m.type}: ${m.path}`).join('\n');
|
|
173
|
+
return {
|
|
174
|
+
success: false,
|
|
175
|
+
error: {
|
|
176
|
+
success: false,
|
|
177
|
+
message: `Content "${originalName || safeName}" found in multiple element types:\n\n${matchDetails}\n\n` +
|
|
178
|
+
`Please specify the element type using the --type parameter to avoid ambiguity.`,
|
|
179
|
+
error: 'MULTIPLE_MATCHES_FOUND'
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
// Single match found - use it
|
|
184
|
+
const match = detectionResult.matches[0];
|
|
185
|
+
elementType = match.type;
|
|
186
|
+
localPath = match.path;
|
|
187
|
+
logger.info(`Smart detection: Found "${safeName}" as ${elementType}`, {
|
|
188
|
+
name: safeName,
|
|
189
|
+
detectedType: elementType,
|
|
190
|
+
path: localPath
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
return {
|
|
194
|
+
success: true,
|
|
195
|
+
elementType,
|
|
196
|
+
localPath
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Validates file size and content security before processing
|
|
201
|
+
* @param localPath Path to the local file to validate
|
|
202
|
+
* @returns Validation result with content or error response
|
|
203
|
+
*/
|
|
204
|
+
async validateFileAndContent(localPath) {
|
|
205
|
+
// SECURITY ENHANCEMENT (Task #7): Validate file path before processing
|
|
206
|
+
const pathValidation = await this.validatePortfolioPath(localPath);
|
|
207
|
+
if (!pathValidation.isValid) {
|
|
208
|
+
return {
|
|
209
|
+
success: false,
|
|
210
|
+
error: pathValidation.error
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
// Use the validated safe path for all subsequent operations
|
|
214
|
+
const safePath = pathValidation.safePath;
|
|
215
|
+
// Validate file size before reading
|
|
216
|
+
const stats = await fs.stat(safePath);
|
|
217
|
+
if (stats.size > FILE_SIZE_LIMITS.MAX_FILE_SIZE) {
|
|
218
|
+
SecurityMonitor.logSecurityEvent({
|
|
219
|
+
type: 'RATE_LIMIT_EXCEEDED',
|
|
220
|
+
severity: 'MEDIUM',
|
|
221
|
+
source: 'SubmitToPortfolioTool.execute',
|
|
222
|
+
details: `File size ${stats.size} exceeds limit of ${FILE_SIZE_LIMITS.MAX_FILE_SIZE}`
|
|
223
|
+
});
|
|
224
|
+
return {
|
|
225
|
+
success: false,
|
|
226
|
+
error: {
|
|
227
|
+
success: false,
|
|
228
|
+
message: `File size exceeds ${FILE_SIZE_LIMITS.MAX_FILE_SIZE_MB}MB limit`,
|
|
229
|
+
error: 'FILE_TOO_LARGE'
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
// Validate content security
|
|
234
|
+
const content = await fs.readFile(safePath, 'utf-8');
|
|
235
|
+
const validationResult = ContentValidator.validateAndSanitize(content);
|
|
236
|
+
if (!validationResult.isValid && validationResult.severity === 'critical') {
|
|
237
|
+
SecurityMonitor.logSecurityEvent({
|
|
238
|
+
type: 'CONTENT_INJECTION_ATTEMPT',
|
|
239
|
+
severity: 'HIGH',
|
|
240
|
+
source: 'SubmitToPortfolioTool.execute',
|
|
241
|
+
details: `Critical security issues detected: ${validationResult.detectedPatterns?.join(', ')}`
|
|
242
|
+
});
|
|
243
|
+
return {
|
|
244
|
+
success: false,
|
|
245
|
+
error: {
|
|
246
|
+
success: false,
|
|
247
|
+
message: `Content validation failed: ${validationResult.detectedPatterns?.join(', ')}`,
|
|
248
|
+
error: 'VALIDATION_FAILED'
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
return {
|
|
253
|
+
success: true,
|
|
254
|
+
content
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Prepares metadata for the portfolio element
|
|
259
|
+
* @param safeName The normalized name of the element
|
|
260
|
+
* @param elementType The type of the element
|
|
261
|
+
* @param authStatus Authentication status containing username
|
|
262
|
+
* @returns Metadata object for the element
|
|
263
|
+
*/
|
|
264
|
+
prepareElementMetadata(safeName, elementType, authStatus) {
|
|
265
|
+
return {
|
|
266
|
+
name: safeName,
|
|
267
|
+
description: `${elementType} submitted from local portfolio`,
|
|
268
|
+
author: authStatus.username || 'unknown',
|
|
269
|
+
created: new Date().toISOString(),
|
|
270
|
+
updated: new Date().toISOString(),
|
|
271
|
+
version: '1.0.0'
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Validates GitHub token and checks for expiration before usage
|
|
276
|
+
* SECURITY ENHANCEMENT (Task #5): Token expiration validation to prevent stale token usage
|
|
277
|
+
* @param token The GitHub token to validate
|
|
278
|
+
* @returns Validation result with status and expiration info
|
|
279
|
+
*/
|
|
280
|
+
async validateTokenBeforeUsage(token) {
|
|
281
|
+
try {
|
|
282
|
+
// Check token format first (basic validation)
|
|
283
|
+
if (!TokenManager.validateTokenFormat(token)) {
|
|
284
|
+
SecurityMonitor.logSecurityEvent({
|
|
285
|
+
type: 'TOKEN_VALIDATION_FAILURE',
|
|
286
|
+
severity: 'MEDIUM',
|
|
287
|
+
source: 'SubmitToPortfolioTool.validateTokenBeforeUsage',
|
|
288
|
+
details: 'Token has invalid format'
|
|
289
|
+
});
|
|
290
|
+
return {
|
|
291
|
+
isValid: false,
|
|
292
|
+
error: {
|
|
293
|
+
success: false,
|
|
294
|
+
message: 'Invalid token format. Please re-authenticate.',
|
|
295
|
+
error: 'INVALID_TOKEN_FORMAT'
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
// Validate token with GitHub API to check expiration and permissions
|
|
300
|
+
const validationResult = await TokenManager.validateTokenScopes(token, {
|
|
301
|
+
required: ['repo'],
|
|
302
|
+
optional: ['user:email']
|
|
303
|
+
});
|
|
304
|
+
if (!validationResult.isValid) {
|
|
305
|
+
SecurityMonitor.logSecurityEvent({
|
|
306
|
+
type: 'TOKEN_VALIDATION_FAILURE',
|
|
307
|
+
severity: 'MEDIUM',
|
|
308
|
+
source: 'SubmitToPortfolioTool.validateTokenBeforeUsage',
|
|
309
|
+
details: `Token validation failed: ${validationResult.error}`
|
|
310
|
+
});
|
|
311
|
+
return {
|
|
312
|
+
isValid: false,
|
|
313
|
+
error: {
|
|
314
|
+
success: false,
|
|
315
|
+
message: 'GitHub token is invalid or expired. Please re-authenticate.',
|
|
316
|
+
error: 'TOKEN_VALIDATION_FAILED'
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
// Check if token is near expiration (rate limit reset time can indicate token freshness)
|
|
321
|
+
let isNearExpiry = false;
|
|
322
|
+
if (validationResult.rateLimit?.resetTime) {
|
|
323
|
+
const now = new Date();
|
|
324
|
+
const timeUntilReset = validationResult.rateLimit.resetTime.getTime() - now.getTime();
|
|
325
|
+
const oneHour = 60 * 60 * 1000;
|
|
326
|
+
// Consider token "near expiry" if rate limit reset is more than 23 hours away
|
|
327
|
+
// (GitHub rate limits reset every hour, so this suggests token age)
|
|
328
|
+
if (timeUntilReset > 23 * oneHour) {
|
|
329
|
+
isNearExpiry = true;
|
|
330
|
+
logger.warn('GitHub token may be near expiration', {
|
|
331
|
+
tokenPrefix: TokenManager.getTokenPrefix(token),
|
|
332
|
+
rateLimitResetTime: validationResult.rateLimit.resetTime,
|
|
333
|
+
recommendation: 'Consider re-authenticating for long operations'
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
// Log successful validation
|
|
338
|
+
SecurityMonitor.logSecurityEvent({
|
|
339
|
+
type: 'TOKEN_VALIDATION_SUCCESS',
|
|
340
|
+
severity: 'LOW',
|
|
341
|
+
source: 'SubmitToPortfolioTool.validateTokenBeforeUsage',
|
|
342
|
+
details: 'GitHub token validated successfully before usage',
|
|
343
|
+
metadata: {
|
|
344
|
+
tokenType: TokenManager.getTokenType(token),
|
|
345
|
+
scopes: validationResult.scopes,
|
|
346
|
+
rateLimitRemaining: validationResult.rateLimit?.remaining,
|
|
347
|
+
isNearExpiry
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
return {
|
|
351
|
+
isValid: true,
|
|
352
|
+
isNearExpiry
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
catch (error) {
|
|
356
|
+
// Handle rate limit exceeded specifically
|
|
357
|
+
if (error?.code === 'RATE_LIMIT_EXCEEDED') {
|
|
358
|
+
logger.warn('Token validation rate limited, allowing operation to proceed with cached status');
|
|
359
|
+
return { isValid: true }; // Allow to proceed if rate limited, as basic format check passed
|
|
360
|
+
}
|
|
361
|
+
SecurityMonitor.logSecurityEvent({
|
|
362
|
+
type: 'TOKEN_VALIDATION_FAILURE',
|
|
363
|
+
severity: 'HIGH',
|
|
364
|
+
source: 'SubmitToPortfolioTool.validateTokenBeforeUsage',
|
|
365
|
+
details: `Token validation error: ${error.message || 'unknown error'}`
|
|
366
|
+
});
|
|
367
|
+
return {
|
|
368
|
+
isValid: false,
|
|
369
|
+
error: {
|
|
370
|
+
success: false,
|
|
371
|
+
message: 'Unable to validate GitHub token. Please check your connection and try again.',
|
|
372
|
+
error: 'TOKEN_VALIDATION_ERROR'
|
|
373
|
+
}
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Enhanced path validation for portfolio operations with comprehensive security checks
|
|
379
|
+
* SECURITY ENHANCEMENT (Task #7): Additional validation for special characters and malicious patterns
|
|
380
|
+
* @param filePath The file path to validate
|
|
381
|
+
* @returns Validation result with secure path or error response
|
|
382
|
+
*/
|
|
383
|
+
async validatePortfolioPath(filePath) {
|
|
384
|
+
try {
|
|
385
|
+
// Basic null/undefined check
|
|
386
|
+
if (!filePath || typeof filePath !== 'string') {
|
|
387
|
+
SecurityMonitor.logSecurityEvent({
|
|
388
|
+
type: 'PATH_TRAVERSAL_ATTEMPT',
|
|
389
|
+
severity: 'MEDIUM',
|
|
390
|
+
source: 'SubmitToPortfolioTool.validatePortfolioPath',
|
|
391
|
+
details: 'Invalid path provided - null, undefined, or non-string'
|
|
392
|
+
});
|
|
393
|
+
return {
|
|
394
|
+
isValid: false,
|
|
395
|
+
error: {
|
|
396
|
+
success: false,
|
|
397
|
+
message: 'Invalid file path provided',
|
|
398
|
+
error: 'INVALID_PATH'
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
// Check for suspicious patterns that could indicate path traversal or injection
|
|
403
|
+
const suspiciousPatterns = [
|
|
404
|
+
/\.\./, // Path traversal
|
|
405
|
+
/\/\.\./, // Unix path traversal
|
|
406
|
+
/\\\.\./, // Windows path traversal
|
|
407
|
+
/\x00/, // Null bytes
|
|
408
|
+
/[\x01-\x1f\x7f-\x9f]/, // Control characters
|
|
409
|
+
/[<>:"|?*]/, // Invalid filename characters on Windows
|
|
410
|
+
/^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])$/i, // Reserved Windows names
|
|
411
|
+
/^\./, // Hidden files (starting with dot)
|
|
412
|
+
/\s+$/, // Trailing whitespace
|
|
413
|
+
/^[\s]*$/, // Only whitespace
|
|
414
|
+
/%[0-9a-fA-F]{2}/, // URL encoding (potential bypass attempt)
|
|
415
|
+
/\\x[0-9a-fA-F]{2}/, // Hex encoding
|
|
416
|
+
/\$\{.*\}/, // Template literal injection
|
|
417
|
+
/`.*`/, // Backtick injection
|
|
418
|
+
/[\\\/]{2,}/ // Multiple consecutive slashes
|
|
419
|
+
];
|
|
420
|
+
for (const pattern of suspiciousPatterns) {
|
|
421
|
+
if (pattern.test(filePath)) {
|
|
422
|
+
SecurityMonitor.logSecurityEvent({
|
|
423
|
+
type: 'PATH_TRAVERSAL_ATTEMPT',
|
|
424
|
+
severity: 'HIGH',
|
|
425
|
+
source: 'SubmitToPortfolioTool.validatePortfolioPath',
|
|
426
|
+
details: `Suspicious pattern detected in file path: ${pattern.source}`,
|
|
427
|
+
metadata: {
|
|
428
|
+
pathLength: filePath.length,
|
|
429
|
+
pattern: pattern.source
|
|
430
|
+
}
|
|
431
|
+
});
|
|
432
|
+
return {
|
|
433
|
+
isValid: false,
|
|
434
|
+
error: {
|
|
435
|
+
success: false,
|
|
436
|
+
message: 'File path contains invalid or suspicious characters',
|
|
437
|
+
error: 'SUSPICIOUS_PATH_PATTERN'
|
|
438
|
+
}
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
// Check path length (prevent buffer overflow attempts)
|
|
443
|
+
const MAX_PATH_LENGTH = process.platform === 'win32' ? 260 : 4096;
|
|
444
|
+
if (filePath.length > MAX_PATH_LENGTH) {
|
|
445
|
+
SecurityMonitor.logSecurityEvent({
|
|
446
|
+
type: 'PATH_TRAVERSAL_ATTEMPT',
|
|
447
|
+
severity: 'MEDIUM',
|
|
448
|
+
source: 'SubmitToPortfolioTool.validatePortfolioPath',
|
|
449
|
+
details: `File path exceeds maximum length: ${filePath.length} > ${MAX_PATH_LENGTH}`
|
|
450
|
+
});
|
|
451
|
+
return {
|
|
452
|
+
isValid: false,
|
|
453
|
+
error: {
|
|
454
|
+
success: false,
|
|
455
|
+
message: 'File path is too long',
|
|
456
|
+
error: 'PATH_TOO_LONG'
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
// Normalize path to resolve any relative components safely
|
|
461
|
+
let normalizedPath;
|
|
462
|
+
try {
|
|
463
|
+
// Remove null bytes and normalize
|
|
464
|
+
const cleanPath = filePath.replace(/\x00/g, '');
|
|
465
|
+
normalizedPath = path.normalize(cleanPath);
|
|
466
|
+
// Ensure the normalized path doesn't escape the intended directory
|
|
467
|
+
if (normalizedPath.includes('..') || normalizedPath.startsWith('/') ||
|
|
468
|
+
(process.platform === 'win32' && /^[a-zA-Z]:/.test(normalizedPath))) {
|
|
469
|
+
throw new Error('Path normalization resulted in directory traversal');
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
catch (error) {
|
|
473
|
+
SecurityMonitor.logSecurityEvent({
|
|
474
|
+
type: 'PATH_TRAVERSAL_ATTEMPT',
|
|
475
|
+
severity: 'HIGH',
|
|
476
|
+
source: 'SubmitToPortfolioTool.validatePortfolioPath',
|
|
477
|
+
details: `Path normalization failed: ${error instanceof Error ? error.message : 'unknown error'}`
|
|
478
|
+
});
|
|
479
|
+
return {
|
|
480
|
+
isValid: false,
|
|
481
|
+
error: {
|
|
482
|
+
success: false,
|
|
483
|
+
message: 'File path could not be safely processed',
|
|
484
|
+
error: 'PATH_NORMALIZATION_FAILED'
|
|
485
|
+
}
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
// Validate file extension (only allow safe extensions for portfolio content)
|
|
489
|
+
const allowedExtensions = ['.md', '.markdown', '.txt', '.yml', '.yaml', '.json'];
|
|
490
|
+
const fileExtension = path.extname(normalizedPath).toLowerCase();
|
|
491
|
+
if (fileExtension && !allowedExtensions.includes(fileExtension)) {
|
|
492
|
+
SecurityMonitor.logSecurityEvent({
|
|
493
|
+
type: 'CONTENT_INJECTION_ATTEMPT',
|
|
494
|
+
severity: 'MEDIUM',
|
|
495
|
+
source: 'SubmitToPortfolioTool.validatePortfolioPath',
|
|
496
|
+
details: `Disallowed file extension: ${fileExtension}`,
|
|
497
|
+
metadata: {
|
|
498
|
+
allowedExtensions: allowedExtensions.join(', ')
|
|
499
|
+
}
|
|
500
|
+
});
|
|
501
|
+
return {
|
|
502
|
+
isValid: false,
|
|
503
|
+
error: {
|
|
504
|
+
success: false,
|
|
505
|
+
message: `File extension '${fileExtension}' is not allowed. Allowed extensions: ${allowedExtensions.join(', ')}`,
|
|
506
|
+
error: 'INVALID_FILE_EXTENSION'
|
|
507
|
+
}
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
// Validate filename characters (only allow safe characters)
|
|
511
|
+
const basename = path.basename(normalizedPath);
|
|
512
|
+
const safeFilenamePattern = /^[a-zA-Z0-9\-_.\s()[\]{}]+$/;
|
|
513
|
+
if (basename && !safeFilenamePattern.test(basename)) {
|
|
514
|
+
SecurityMonitor.logSecurityEvent({
|
|
515
|
+
type: 'CONTENT_INJECTION_ATTEMPT',
|
|
516
|
+
severity: 'MEDIUM',
|
|
517
|
+
source: 'SubmitToPortfolioTool.validatePortfolioPath',
|
|
518
|
+
details: 'Filename contains potentially dangerous characters',
|
|
519
|
+
metadata: {
|
|
520
|
+
filename: basename,
|
|
521
|
+
allowedPattern: safeFilenamePattern.source
|
|
522
|
+
}
|
|
523
|
+
});
|
|
524
|
+
return {
|
|
525
|
+
isValid: false,
|
|
526
|
+
error: {
|
|
527
|
+
success: false,
|
|
528
|
+
message: 'Filename contains invalid characters. Only letters, numbers, spaces, hyphens, underscores, dots, and common brackets are allowed.',
|
|
529
|
+
error: 'INVALID_FILENAME_CHARACTERS'
|
|
530
|
+
}
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
// Log successful validation
|
|
534
|
+
SecurityMonitor.logSecurityEvent({
|
|
535
|
+
type: 'CONTENT_INJECTION_ATTEMPT',
|
|
536
|
+
severity: 'LOW',
|
|
537
|
+
source: 'SubmitToPortfolioTool.validatePortfolioPath',
|
|
538
|
+
details: 'File path validation successful',
|
|
539
|
+
metadata: {
|
|
540
|
+
originalPathLength: filePath.length,
|
|
541
|
+
normalizedPathLength: normalizedPath.length,
|
|
542
|
+
fileExtension: fileExtension || 'none'
|
|
543
|
+
}
|
|
544
|
+
});
|
|
545
|
+
return {
|
|
546
|
+
isValid: true,
|
|
547
|
+
safePath: normalizedPath
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
catch (error) {
|
|
551
|
+
SecurityMonitor.logSecurityEvent({
|
|
552
|
+
type: 'PATH_TRAVERSAL_ATTEMPT',
|
|
553
|
+
severity: 'HIGH',
|
|
554
|
+
source: 'SubmitToPortfolioTool.validatePortfolioPath',
|
|
555
|
+
details: `Path validation error: ${error instanceof Error ? error.message : 'unknown error'}`
|
|
556
|
+
});
|
|
557
|
+
return {
|
|
558
|
+
isValid: false,
|
|
559
|
+
error: {
|
|
560
|
+
success: false,
|
|
561
|
+
message: 'Unable to validate file path. Please check the file path and try again.',
|
|
562
|
+
error: 'PATH_VALIDATION_ERROR'
|
|
563
|
+
}
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
/**
|
|
568
|
+
* Smart token management for long operations with refresh-like capabilities
|
|
569
|
+
* SECURITY ENHANCEMENT (Task #14): Token refresh logic for long operations
|
|
570
|
+
*
|
|
571
|
+
* Note: GitHub OAuth device flow tokens don't have traditional refresh tokens,
|
|
572
|
+
* but we can implement smart validation and guidance for long operations
|
|
573
|
+
*
|
|
574
|
+
* @param operationType Type of operation being performed
|
|
575
|
+
* @returns Token management result with recommendations
|
|
576
|
+
*/
|
|
577
|
+
async manageTokenForLongOperation(operationType) {
|
|
578
|
+
try {
|
|
579
|
+
// Get current token
|
|
580
|
+
const token = await TokenManager.getGitHubTokenAsync();
|
|
581
|
+
if (!token) {
|
|
582
|
+
return {
|
|
583
|
+
canProceed: false,
|
|
584
|
+
error: {
|
|
585
|
+
success: false,
|
|
586
|
+
message: 'No GitHub token available. Please authenticate first.',
|
|
587
|
+
error: 'NO_TOKEN'
|
|
588
|
+
}
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
// Validate token for the specific operation
|
|
592
|
+
const validation = await this.validateTokenBeforeUsage(token);
|
|
593
|
+
if (!validation.isValid) {
|
|
594
|
+
return {
|
|
595
|
+
canProceed: false,
|
|
596
|
+
error: validation.error
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
// Check if this is a long operation that might benefit from fresh authentication
|
|
600
|
+
const longOperations = ['portfolio_creation', 'collection_submission'];
|
|
601
|
+
const isLongOperation = longOperations.includes(operationType);
|
|
602
|
+
// Get token type to determine refresh capabilities
|
|
603
|
+
const tokenType = TokenManager.getTokenType(token);
|
|
604
|
+
let refreshRecommended = false;
|
|
605
|
+
// For long operations, check token age and recommend refresh if needed
|
|
606
|
+
if (isLongOperation && validation.isNearExpiry) {
|
|
607
|
+
refreshRecommended = true;
|
|
608
|
+
SecurityMonitor.logSecurityEvent({
|
|
609
|
+
type: 'TOKEN_VALIDATION_SUCCESS',
|
|
610
|
+
severity: 'LOW',
|
|
611
|
+
source: 'SubmitToPortfolioTool.manageTokenForLongOperation',
|
|
612
|
+
details: 'Long operation detected with aging token - refresh recommended',
|
|
613
|
+
metadata: {
|
|
614
|
+
operationType,
|
|
615
|
+
tokenType,
|
|
616
|
+
refreshRecommended: true
|
|
617
|
+
}
|
|
618
|
+
});
|
|
619
|
+
logger.warn('Long operation with potentially aging token detected', {
|
|
620
|
+
operationType,
|
|
621
|
+
tokenType,
|
|
622
|
+
recommendation: 'Consider re-authenticating if operation fails'
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
// For OAuth tokens in long operations, we can provide guidance
|
|
626
|
+
if (tokenType === 'OAuth Access Token' && isLongOperation) {
|
|
627
|
+
logger.info('OAuth token detected for long operation', {
|
|
628
|
+
operationType,
|
|
629
|
+
tokenType,
|
|
630
|
+
guidance: 'OAuth tokens are time-limited. If operation fails, re-authenticate using setup_github_auth'
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
// Log successful token management
|
|
634
|
+
SecurityMonitor.logSecurityEvent({
|
|
635
|
+
type: 'TOKEN_VALIDATION_SUCCESS',
|
|
636
|
+
severity: 'LOW',
|
|
637
|
+
source: 'SubmitToPortfolioTool.manageTokenForLongOperation',
|
|
638
|
+
details: 'Token management successful for long operation',
|
|
639
|
+
metadata: {
|
|
640
|
+
operationType,
|
|
641
|
+
tokenType,
|
|
642
|
+
isLongOperation,
|
|
643
|
+
refreshRecommended
|
|
644
|
+
}
|
|
645
|
+
});
|
|
646
|
+
return {
|
|
647
|
+
canProceed: true,
|
|
648
|
+
token,
|
|
649
|
+
refreshRecommended
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
catch (error) {
|
|
653
|
+
SecurityMonitor.logSecurityEvent({
|
|
654
|
+
type: 'TOKEN_VALIDATION_FAILURE',
|
|
655
|
+
severity: 'MEDIUM',
|
|
656
|
+
source: 'SubmitToPortfolioTool.manageTokenForLongOperation',
|
|
657
|
+
details: `Token management error: ${error.message || 'unknown error'}`
|
|
658
|
+
});
|
|
659
|
+
return {
|
|
660
|
+
canProceed: false,
|
|
661
|
+
error: {
|
|
662
|
+
success: false,
|
|
663
|
+
message: 'Unable to manage token for operation. Please check your authentication and try again.',
|
|
664
|
+
error: 'TOKEN_MANAGEMENT_ERROR'
|
|
665
|
+
}
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
/**
|
|
670
|
+
* Provides user guidance for token refresh when operations fail due to token issues
|
|
671
|
+
* SECURITY ENHANCEMENT (Task #14): User guidance for authentication refresh
|
|
672
|
+
*/
|
|
673
|
+
formatTokenRefreshGuidance(operationType, tokenType) {
|
|
674
|
+
let guidance = '\n\n🔄 **Token Refresh Guidance**:\n';
|
|
675
|
+
if (tokenType === 'OAuth Access Token') {
|
|
676
|
+
guidance += '• Your OAuth token may have expired\n';
|
|
677
|
+
guidance += '• Run `setup_github_auth` to authenticate again\n';
|
|
678
|
+
guidance += '• This will generate a fresh token for continued access\n';
|
|
679
|
+
}
|
|
680
|
+
else if (tokenType === 'Personal Access Token') {
|
|
681
|
+
guidance += '• Your Personal Access Token may have expired\n';
|
|
682
|
+
guidance += '• Check your GitHub settings: https://github.com/settings/tokens\n';
|
|
683
|
+
guidance += '• Generate a new token if needed and update GITHUB_TOKEN environment variable\n';
|
|
684
|
+
}
|
|
685
|
+
else {
|
|
686
|
+
guidance += '• Your GitHub token may have expired or been revoked\n';
|
|
687
|
+
guidance += '• Re-authenticate using `setup_github_auth`\n';
|
|
688
|
+
guidance += '• Ensure your token has the required permissions\n';
|
|
689
|
+
}
|
|
690
|
+
guidance += `\n**Operation**: ${operationType}\n`;
|
|
691
|
+
guidance += '**Required scopes**: repo, user:email\n\n';
|
|
692
|
+
guidance += '💡 **Tip**: Fresh tokens work better for complex operations like portfolio creation.';
|
|
693
|
+
return guidance;
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* Sets up GitHub repository access and ensures portfolio repository exists
|
|
697
|
+
* @param authStatus Authentication status containing username
|
|
698
|
+
* @returns Setup result or error response
|
|
699
|
+
*/
|
|
700
|
+
async setupGitHubRepository(authStatus) {
|
|
701
|
+
// SECURITY ENHANCEMENT (Task #14): Smart token management for long operations
|
|
702
|
+
const tokenManagement = await this.manageTokenForLongOperation('portfolio_creation');
|
|
703
|
+
if (!tokenManagement.canProceed) {
|
|
704
|
+
return {
|
|
705
|
+
success: false,
|
|
706
|
+
error: tokenManagement.error
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
const token = tokenManagement.token;
|
|
710
|
+
// Provide user guidance if refresh is recommended for this long operation
|
|
711
|
+
if (tokenManagement.refreshRecommended) {
|
|
712
|
+
const tokenType = TokenManager.getTokenType(token);
|
|
713
|
+
const guidance = this.formatTokenRefreshGuidance('portfolio creation', tokenType);
|
|
714
|
+
logger.warn(`Token refresh recommended for portfolio creation:${guidance}`);
|
|
715
|
+
}
|
|
716
|
+
this.portfolioManager.setToken(token);
|
|
717
|
+
// Check if portfolio exists and create if needed
|
|
718
|
+
const username = authStatus.username || 'unknown';
|
|
719
|
+
const portfolioExists = await this.portfolioManager.checkPortfolioExists(username);
|
|
720
|
+
if (!portfolioExists) {
|
|
721
|
+
logger.info('Creating portfolio repository...');
|
|
722
|
+
// Request consent for portfolio creation
|
|
723
|
+
const repoUrl = await this.portfolioManager.createPortfolio(username, true);
|
|
724
|
+
if (!repoUrl) {
|
|
725
|
+
return {
|
|
726
|
+
success: false,
|
|
727
|
+
error: {
|
|
728
|
+
success: false,
|
|
729
|
+
message: 'Failed to create portfolio repository',
|
|
730
|
+
error: 'CREATE_FAILED'
|
|
731
|
+
}
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
return { success: true };
|
|
736
|
+
}
|
|
737
|
+
/**
|
|
738
|
+
* Submits element to portfolio and handles the complete response workflow
|
|
739
|
+
* @param safeName The normalized name of the element
|
|
740
|
+
* @param elementType The type of the element
|
|
741
|
+
* @param metadata The metadata for the element
|
|
742
|
+
* @param content The content of the element
|
|
743
|
+
* @param authStatus Authentication status containing username and token
|
|
744
|
+
* @returns Complete submission result with success message or error
|
|
745
|
+
*/
|
|
746
|
+
async submitElementAndHandleResponse(safeName, elementType, metadata, content, authStatus) {
|
|
747
|
+
// Create element structure to save
|
|
748
|
+
const element = {
|
|
749
|
+
type: elementType,
|
|
750
|
+
metadata,
|
|
751
|
+
content
|
|
752
|
+
};
|
|
753
|
+
// TYPE SAFETY FIX #2: Use adapter pattern instead of complex type casting
|
|
754
|
+
// Previously: element as unknown as Parameters<typeof this.portfolioManager.saveElement>[0]
|
|
755
|
+
// Now: Clean adapter pattern that implements IElement interface properly
|
|
756
|
+
const adapter = new PortfolioElementAdapter(element);
|
|
757
|
+
// UX IMPROVEMENT: Add retry logic for transient failures
|
|
758
|
+
const fileUrl = await this.saveElementWithRetry(adapter, safeName, elementType);
|
|
759
|
+
if (!fileUrl) {
|
|
760
|
+
return {
|
|
761
|
+
success: false,
|
|
762
|
+
message: 'Failed to save element to GitHub portfolio after multiple attempts.\n\n' +
|
|
763
|
+
'💡 **Troubleshooting Tips**:\n' +
|
|
764
|
+
'• Check your GitHub authentication: `gh auth status`\n' +
|
|
765
|
+
'• Verify repository permissions\n' +
|
|
766
|
+
'• Try again in a few minutes (GitHub API rate limits)\n' +
|
|
767
|
+
'• Check GitHub status: https://status.github.com',
|
|
768
|
+
error: 'SAVE_FAILED'
|
|
769
|
+
};
|
|
770
|
+
}
|
|
771
|
+
// Log successful submission (DMCP-SEC-006)
|
|
772
|
+
logger.info(`Successfully submitted ${safeName} to GitHub portfolio`, {
|
|
773
|
+
elementType,
|
|
774
|
+
username: authStatus.username,
|
|
775
|
+
fileUrl
|
|
776
|
+
});
|
|
777
|
+
// SECURITY ENHANCEMENT (Task #14): Smart token management for collection submission
|
|
778
|
+
const collectionTokenManagement = await this.manageTokenForLongOperation('collection_submission');
|
|
779
|
+
if (!collectionTokenManagement.canProceed) {
|
|
780
|
+
// Token management failed for collection submission, but main submission succeeded
|
|
781
|
+
const errorMessage = collectionTokenManagement.error?.message || 'Token management failed';
|
|
782
|
+
return {
|
|
783
|
+
success: true,
|
|
784
|
+
message: `✅ Successfully uploaded ${safeName} to your GitHub portfolio!\n📁 Portfolio URL: ${fileUrl}\n\n⚠️ Collection submission skipped: ${errorMessage}`,
|
|
785
|
+
url: fileUrl
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
const token = collectionTokenManagement.token;
|
|
789
|
+
// Provide refresh guidance if recommended for collection submission
|
|
790
|
+
if (collectionTokenManagement.refreshRecommended) {
|
|
791
|
+
const tokenType = TokenManager.getTokenType(token);
|
|
792
|
+
logger.info('Collection submission proceeding with aging token', {
|
|
793
|
+
tokenType,
|
|
794
|
+
recommendation: 'If collection submission fails, try re-authenticating with setup_github_auth'
|
|
795
|
+
});
|
|
796
|
+
}
|
|
797
|
+
// ENHANCEMENT (Issue #549): Ask user if they want to submit to collection
|
|
798
|
+
// This completes the community contribution workflow
|
|
799
|
+
const collectionSubmissionResult = await this.promptForCollectionSubmission({
|
|
800
|
+
elementName: safeName,
|
|
801
|
+
elementType,
|
|
802
|
+
portfolioUrl: fileUrl,
|
|
803
|
+
username: authStatus.username || 'unknown',
|
|
804
|
+
metadata,
|
|
805
|
+
token
|
|
806
|
+
});
|
|
807
|
+
// Build the response message based on what happened
|
|
808
|
+
let message = `✅ Successfully uploaded ${safeName} to your GitHub portfolio!\n`;
|
|
809
|
+
message += `📁 Portfolio URL: ${fileUrl}\n\n`;
|
|
810
|
+
if (collectionSubmissionResult.submitted) {
|
|
811
|
+
message += `🎉 Also submitted to DollhouseMCP collection for community review!\n`;
|
|
812
|
+
message += `📋 Issue: ${collectionSubmissionResult.issueUrl}`;
|
|
813
|
+
}
|
|
814
|
+
else if (collectionSubmissionResult.declined) {
|
|
815
|
+
message += `💡 You can submit to the collection later using the same command.`;
|
|
816
|
+
}
|
|
817
|
+
else if (collectionSubmissionResult.error) {
|
|
818
|
+
message += `⚠️ Collection submission failed: ${collectionSubmissionResult.error}\n`;
|
|
819
|
+
message += `💡 You can manually submit at: https://github.com/DollhouseMCP/collection/issues/new`;
|
|
820
|
+
}
|
|
821
|
+
return {
|
|
822
|
+
success: true,
|
|
823
|
+
message,
|
|
824
|
+
url: fileUrl
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
async execute(params) {
|
|
828
|
+
try {
|
|
829
|
+
// Validate and normalize input parameters
|
|
830
|
+
const validationResult = await this.validateAndNormalizeParams(params);
|
|
831
|
+
if (!validationResult.success) {
|
|
832
|
+
return validationResult.error;
|
|
833
|
+
}
|
|
834
|
+
const safeName = validationResult.safeName;
|
|
835
|
+
// Check authentication status
|
|
836
|
+
const authResult = await this.checkAuthentication();
|
|
837
|
+
if (!authResult.success) {
|
|
838
|
+
return authResult.error;
|
|
839
|
+
}
|
|
840
|
+
const authStatus = authResult.authStatus;
|
|
841
|
+
// Find content locally with smart type detection
|
|
842
|
+
const contentResult = await this.discoverContentWithTypeDetection(safeName, params.type, params.name);
|
|
843
|
+
if (!contentResult.success) {
|
|
844
|
+
return contentResult.error;
|
|
845
|
+
}
|
|
846
|
+
const elementType = contentResult.elementType;
|
|
847
|
+
const localPath = contentResult.localPath;
|
|
848
|
+
// Validate file and content security
|
|
849
|
+
const securityResult = await this.validateFileAndContent(localPath);
|
|
850
|
+
if (!securityResult.success) {
|
|
851
|
+
return securityResult.error;
|
|
852
|
+
}
|
|
853
|
+
const content = securityResult.content;
|
|
854
|
+
// Get user consent (placeholder for now - could add interactive prompt later)
|
|
855
|
+
logger.info(`Preparing to submit ${safeName} to GitHub portfolio`);
|
|
856
|
+
// Prepare metadata for element
|
|
857
|
+
const metadata = this.prepareElementMetadata(safeName, elementType, authStatus);
|
|
858
|
+
// Set up GitHub repository access
|
|
859
|
+
const repoResult = await this.setupGitHubRepository(authStatus);
|
|
860
|
+
if (!repoResult.success) {
|
|
861
|
+
return repoResult.error;
|
|
862
|
+
}
|
|
863
|
+
// Submit element to portfolio and handle collection submission
|
|
864
|
+
return await this.submitElementAndHandleResponse(safeName, elementType, metadata, content, authStatus);
|
|
865
|
+
}
|
|
866
|
+
catch (error) {
|
|
867
|
+
// SECURITY ENHANCEMENT (Task #14): Enhanced error handling with token refresh guidance
|
|
868
|
+
ErrorHandler.logError('submitToPortfolio', error, {
|
|
869
|
+
elementName: params.name,
|
|
870
|
+
elementType: params.type
|
|
871
|
+
});
|
|
872
|
+
// Check if error is token-related and provide refresh guidance
|
|
873
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
874
|
+
const isTokenError = errorMessage.toLowerCase().includes('token') ||
|
|
875
|
+
errorMessage.toLowerCase().includes('auth') ||
|
|
876
|
+
errorMessage.toLowerCase().includes('401') ||
|
|
877
|
+
errorMessage.toLowerCase().includes('403');
|
|
878
|
+
let formattedError = ErrorHandler.formatForResponse(error);
|
|
879
|
+
if (isTokenError) {
|
|
880
|
+
try {
|
|
881
|
+
// Get current token to determine type for guidance
|
|
882
|
+
const currentToken = await TokenManager.getGitHubTokenAsync();
|
|
883
|
+
if (currentToken) {
|
|
884
|
+
const tokenType = TokenManager.getTokenType(currentToken);
|
|
885
|
+
const refreshGuidance = this.formatTokenRefreshGuidance('portfolio submission', tokenType);
|
|
886
|
+
// Append refresh guidance to error message
|
|
887
|
+
if (formattedError.message) {
|
|
888
|
+
formattedError.message += refreshGuidance;
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
catch (tokenError) {
|
|
893
|
+
// If we can't get token info, provide generic guidance
|
|
894
|
+
formattedError.message += '\n\n🔄 **Authentication Issue**: Try running `setup_github_auth` to refresh your authentication.';
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
return formattedError;
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
/**
|
|
901
|
+
* Prompts user to submit content to the DollhouseMCP collection
|
|
902
|
+
* ENHANCEMENT (Issue #549): Complete the community contribution workflow
|
|
903
|
+
*/
|
|
904
|
+
async promptForCollectionSubmission(params) {
|
|
905
|
+
try {
|
|
906
|
+
// Create a simple prompt message for the user
|
|
907
|
+
// Note: In MCP context, we can't do interactive prompts, so we'll need to
|
|
908
|
+
// either make this automatic or require a parameter
|
|
909
|
+
// For now, let's check if the user has set an environment variable
|
|
910
|
+
// to auto-submit to collection (opt-in behavior)
|
|
911
|
+
const autoSubmit = process.env.DOLLHOUSE_AUTO_SUBMIT_TO_COLLECTION === 'true';
|
|
912
|
+
if (!autoSubmit) {
|
|
913
|
+
// User hasn't opted in to auto-submission
|
|
914
|
+
logger.info('Collection submission skipped (set DOLLHOUSE_AUTO_SUBMIT_TO_COLLECTION=true to enable)');
|
|
915
|
+
return { submitted: false, declined: true };
|
|
916
|
+
}
|
|
917
|
+
logger.info('Auto-submitting to DollhouseMCP collection...');
|
|
918
|
+
// Create the issue in the collection repository
|
|
919
|
+
const issueUrl = await this.createCollectionIssue({
|
|
920
|
+
...params,
|
|
921
|
+
token: params.token
|
|
922
|
+
});
|
|
923
|
+
if (issueUrl) {
|
|
924
|
+
logger.info('Successfully created collection submission issue', { issueUrl });
|
|
925
|
+
return { submitted: true, declined: false, issueUrl };
|
|
926
|
+
}
|
|
927
|
+
else {
|
|
928
|
+
return { submitted: false, declined: false, error: 'Failed to create issue' };
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
catch (error) {
|
|
932
|
+
logger.error('Error in collection submission prompt', { error });
|
|
933
|
+
return {
|
|
934
|
+
submitted: false,
|
|
935
|
+
declined: false,
|
|
936
|
+
error: error instanceof Error ? error.message : 'Unknown error'
|
|
937
|
+
};
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
/**
|
|
941
|
+
* Creates an issue in the DollhouseMCP/collection repository
|
|
942
|
+
* ENHANCEMENT (Issue #549): GitHub API integration for collection submission
|
|
943
|
+
*/
|
|
944
|
+
async createCollectionIssue(params) {
|
|
945
|
+
try {
|
|
946
|
+
// Format the issue title
|
|
947
|
+
const title = `[${params.elementType}] Add ${params.elementName} by @${params.username}`;
|
|
948
|
+
// Format the issue body with all relevant information
|
|
949
|
+
const body = `## New ${params.elementType} Submission
|
|
950
|
+
|
|
951
|
+
` +
|
|
952
|
+
`**Name**: ${params.elementName}\n` +
|
|
953
|
+
`**Author**: @${params.username}\n` +
|
|
954
|
+
`**Type**: ${params.elementType}\n` +
|
|
955
|
+
`**Description**: ${params.metadata.description || 'No description provided'}\n\n` +
|
|
956
|
+
`### Portfolio Link\n` +
|
|
957
|
+
`${params.portfolioUrl}\n\n` +
|
|
958
|
+
`### Metadata\n` +
|
|
959
|
+
`\`\`\`json\n${JSON.stringify(params.metadata, null, 2)}\n\`\`\`\n\n` +
|
|
960
|
+
`### Review Checklist\n` +
|
|
961
|
+
`- [ ] Content is appropriate and follows community guidelines\n` +
|
|
962
|
+
`- [ ] No security vulnerabilities or malicious patterns\n` +
|
|
963
|
+
`- [ ] Metadata is complete and accurate\n` +
|
|
964
|
+
`- [ ] Element works as described\n` +
|
|
965
|
+
`- [ ] No duplicate of existing collection content\n\n` +
|
|
966
|
+
`---\n` +
|
|
967
|
+
`*This submission was created automatically via the DollhouseMCP submit_content tool.*`;
|
|
968
|
+
// Determine labels based on element type
|
|
969
|
+
const labels = [
|
|
970
|
+
'contribution', // All submissions get this
|
|
971
|
+
'pending-review', // Needs review
|
|
972
|
+
params.elementType.toLowerCase() // Element type label
|
|
973
|
+
];
|
|
974
|
+
// PERFORMANCE OPTIMIZATION (Task #6): Use GitHub rate limiter for API calls
|
|
975
|
+
// This prevents hitting GitHub rate limits and provides better error handling
|
|
976
|
+
const issueUrl = await githubRateLimiter.queueRequest('create-collection-issue', async () => {
|
|
977
|
+
const url = 'https://api.github.com/repos/DollhouseMCP/collection/issues';
|
|
978
|
+
// Create AbortController for timeout
|
|
979
|
+
const controller = new AbortController();
|
|
980
|
+
const timeoutId = setTimeout(() => controller.abort(), getValidatedTimeout());
|
|
981
|
+
try {
|
|
982
|
+
const response = await fetch(url, {
|
|
983
|
+
method: 'POST',
|
|
984
|
+
headers: {
|
|
985
|
+
'Accept': 'application/vnd.github.v3+json',
|
|
986
|
+
'Authorization': `Bearer ${params.token}`,
|
|
987
|
+
'Content-Type': 'application/json',
|
|
988
|
+
'User-Agent': 'DollhouseMCP/1.0'
|
|
989
|
+
},
|
|
990
|
+
body: JSON.stringify({
|
|
991
|
+
title,
|
|
992
|
+
body,
|
|
993
|
+
labels
|
|
994
|
+
}),
|
|
995
|
+
signal: controller.signal
|
|
996
|
+
});
|
|
997
|
+
clearTimeout(timeoutId);
|
|
998
|
+
// PERFORMANCE OPTIMIZATION (Task #15): Enhanced rate limit logging
|
|
999
|
+
// Log rate limit headers for diagnostics
|
|
1000
|
+
const rateLimitRemaining = response.headers.get('X-RateLimit-Remaining');
|
|
1001
|
+
const rateLimitReset = response.headers.get('X-RateLimit-Reset');
|
|
1002
|
+
const rateLimitLimit = response.headers.get('X-RateLimit-Limit');
|
|
1003
|
+
logger.debug('GitHub API rate limit status', {
|
|
1004
|
+
operation: 'create-collection-issue',
|
|
1005
|
+
remaining: rateLimitRemaining,
|
|
1006
|
+
limit: rateLimitLimit,
|
|
1007
|
+
resetTime: rateLimitReset ? new Date(parseInt(rateLimitReset) * 1000) : undefined,
|
|
1008
|
+
responseStatus: response.status
|
|
1009
|
+
});
|
|
1010
|
+
// Log warning if approaching rate limit
|
|
1011
|
+
if (rateLimitRemaining && parseInt(rateLimitRemaining) < 100) {
|
|
1012
|
+
logger.warn('Approaching GitHub API rate limit', {
|
|
1013
|
+
operation: 'create-collection-issue',
|
|
1014
|
+
remaining: rateLimitRemaining,
|
|
1015
|
+
resetTime: rateLimitReset ? new Date(parseInt(rateLimitReset) * 1000) : undefined,
|
|
1016
|
+
recommendation: 'Consider reducing API usage frequency or authenticating for higher limits'
|
|
1017
|
+
});
|
|
1018
|
+
}
|
|
1019
|
+
if (!response.ok) {
|
|
1020
|
+
const errorText = await response.text();
|
|
1021
|
+
logger.error('GitHub API error creating issue', {
|
|
1022
|
+
status: response.status,
|
|
1023
|
+
statusText: response.statusText,
|
|
1024
|
+
error: errorText,
|
|
1025
|
+
rateLimitRemaining,
|
|
1026
|
+
rateLimitReset
|
|
1027
|
+
});
|
|
1028
|
+
if (response.status === 404) {
|
|
1029
|
+
logger.error('Collection repository not found or no access');
|
|
1030
|
+
}
|
|
1031
|
+
else if (response.status === 403) {
|
|
1032
|
+
logger.error('Permission denied to create issue in collection repo');
|
|
1033
|
+
}
|
|
1034
|
+
else if (response.status === 401) {
|
|
1035
|
+
logger.error('Authentication failed for collection submission');
|
|
1036
|
+
}
|
|
1037
|
+
throw new Error(`GitHub API error: ${response.status} ${response.statusText}`);
|
|
1038
|
+
}
|
|
1039
|
+
const data = await response.json();
|
|
1040
|
+
return data.html_url;
|
|
1041
|
+
}
|
|
1042
|
+
catch (fetchError) {
|
|
1043
|
+
// Re-throw to outer catch block
|
|
1044
|
+
throw fetchError;
|
|
1045
|
+
}
|
|
1046
|
+
finally {
|
|
1047
|
+
clearTimeout(timeoutId);
|
|
1048
|
+
}
|
|
1049
|
+
}, 'high' // High priority for collection submission
|
|
1050
|
+
);
|
|
1051
|
+
return issueUrl;
|
|
1052
|
+
}
|
|
1053
|
+
catch (error) {
|
|
1054
|
+
// Handle timeout specifically
|
|
1055
|
+
if (error.name === 'AbortError') {
|
|
1056
|
+
logger.error(`GitHub API request timeout after ${getValidatedTimeout()}ms`);
|
|
1057
|
+
}
|
|
1058
|
+
else {
|
|
1059
|
+
logger.error('Failed to create collection issue', {
|
|
1060
|
+
error: error.message || error
|
|
1061
|
+
});
|
|
1062
|
+
}
|
|
1063
|
+
return null;
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
async findLocalContent(name, type) {
|
|
1067
|
+
try {
|
|
1068
|
+
// METADATA INDEX FIX: Use portfolio index for fast metadata-based lookups
|
|
1069
|
+
// This solves the critical issue where "Safe Roundtrip Tester" couldn't be found
|
|
1070
|
+
// because findLocalContent only searched filenames, not metadata names
|
|
1071
|
+
const indexManager = PortfolioIndexManager.getInstance();
|
|
1072
|
+
// UX IMPROVEMENT: Enhanced search with fuzzy matching
|
|
1073
|
+
const indexEntry = await indexManager.findByName(name, {
|
|
1074
|
+
elementType: type,
|
|
1075
|
+
fuzzyMatch: true
|
|
1076
|
+
});
|
|
1077
|
+
if (indexEntry) {
|
|
1078
|
+
logger.debug('Found content via metadata index', {
|
|
1079
|
+
searchName: name,
|
|
1080
|
+
metadataName: indexEntry.metadata.name,
|
|
1081
|
+
filename: indexEntry.filename,
|
|
1082
|
+
filePath: indexEntry.filePath,
|
|
1083
|
+
type
|
|
1084
|
+
});
|
|
1085
|
+
return indexEntry.filePath;
|
|
1086
|
+
}
|
|
1087
|
+
// FALLBACK: Use original file discovery if index lookup fails
|
|
1088
|
+
// This maintains backward compatibility and handles edge cases
|
|
1089
|
+
logger.debug('Index lookup failed, falling back to file discovery', { name, type });
|
|
1090
|
+
const portfolioManager = PortfolioManager.getInstance();
|
|
1091
|
+
const portfolioDir = portfolioManager.getElementDir(type);
|
|
1092
|
+
// UX IMPROVEMENT: Try multiple search strategies for better user experience
|
|
1093
|
+
let file = await FileDiscoveryUtil.findFile(portfolioDir, name, {
|
|
1094
|
+
extensions: ['.md', '.json', '.yaml', '.yml'],
|
|
1095
|
+
partialMatch: true,
|
|
1096
|
+
cacheResults: true
|
|
1097
|
+
});
|
|
1098
|
+
// If not found, try normalizing the name (e.g., "J.A.R.V.I.S." -> "j-a-r-v-i-s")
|
|
1099
|
+
if (!file) {
|
|
1100
|
+
const normalizedName = name.toLowerCase()
|
|
1101
|
+
.replace(/[^a-z0-9]/gi, '-') // Replace non-alphanumeric with dashes
|
|
1102
|
+
.replace(/-+/g, '-') // Replace multiple dashes with single dash
|
|
1103
|
+
.replace(/^-|-$/g, ''); // Remove leading/trailing dashes
|
|
1104
|
+
if (normalizedName !== name.toLowerCase()) {
|
|
1105
|
+
logger.debug('Trying normalized name search', {
|
|
1106
|
+
original: name,
|
|
1107
|
+
normalized: normalizedName,
|
|
1108
|
+
type
|
|
1109
|
+
});
|
|
1110
|
+
file = await FileDiscoveryUtil.findFile(portfolioDir, normalizedName, {
|
|
1111
|
+
extensions: ['.md', '.json', '.yaml', '.yml'],
|
|
1112
|
+
partialMatch: true,
|
|
1113
|
+
cacheResults: true
|
|
1114
|
+
});
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
// If still not found, try searching by display name patterns
|
|
1118
|
+
if (!file) {
|
|
1119
|
+
// Try common variations like removing dots, spaces, etc.
|
|
1120
|
+
const variations = [
|
|
1121
|
+
name.replace(/\./g, ''), // Remove dots: "J.A.R.V.I.S." -> "JARVIS"
|
|
1122
|
+
name.replace(/\s+/g, '-'), // Replace spaces with dashes
|
|
1123
|
+
name.replace(/[\s\.]/g, ''), // Remove spaces and dots
|
|
1124
|
+
name.replace(/[\s\.]/g, '-'), // Replace spaces and dots with dashes
|
|
1125
|
+
].filter(v => v !== name && v.length > 0);
|
|
1126
|
+
for (const variation of variations) {
|
|
1127
|
+
file = await FileDiscoveryUtil.findFile(portfolioDir, variation, {
|
|
1128
|
+
extensions: ['.md', '.json', '.yaml', '.yml'],
|
|
1129
|
+
partialMatch: true,
|
|
1130
|
+
cacheResults: true
|
|
1131
|
+
});
|
|
1132
|
+
if (file) {
|
|
1133
|
+
logger.debug('Found content using name variation', {
|
|
1134
|
+
original: name,
|
|
1135
|
+
variation,
|
|
1136
|
+
file,
|
|
1137
|
+
type
|
|
1138
|
+
});
|
|
1139
|
+
break;
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
if (file) {
|
|
1144
|
+
logger.debug('Found local content file via fallback', { name, type, file });
|
|
1145
|
+
return file;
|
|
1146
|
+
}
|
|
1147
|
+
logger.debug('No content found', { name, type });
|
|
1148
|
+
return null;
|
|
1149
|
+
}
|
|
1150
|
+
catch (error) {
|
|
1151
|
+
logger.error('Error finding local content', {
|
|
1152
|
+
name,
|
|
1153
|
+
type,
|
|
1154
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1155
|
+
});
|
|
1156
|
+
return null;
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
/**
|
|
1160
|
+
* Smart element type detection - searches across ALL element types for content
|
|
1161
|
+
* PERFORMANCE OPTIMIZATION (Task #9): Uses early termination for exact matches
|
|
1162
|
+
* This replaces the previous hardcoded default to PERSONA and enables proper type detection
|
|
1163
|
+
*
|
|
1164
|
+
* @param name The content name to search for
|
|
1165
|
+
* @returns Detection result with found matches across all element types
|
|
1166
|
+
*/
|
|
1167
|
+
async detectElementType(name) {
|
|
1168
|
+
try {
|
|
1169
|
+
// PERFORMANCE OPTIMIZATION (Task #9): Use early termination search utility
|
|
1170
|
+
// Create search functions for each element type
|
|
1171
|
+
const elementTypes = Object.values(ElementType);
|
|
1172
|
+
const searchFunctions = elementTypes.map((type) => async () => {
|
|
1173
|
+
try {
|
|
1174
|
+
const filePath = await this.findLocalContent(name, type);
|
|
1175
|
+
if (filePath) {
|
|
1176
|
+
return { type: type, path: filePath };
|
|
1177
|
+
}
|
|
1178
|
+
return null;
|
|
1179
|
+
}
|
|
1180
|
+
catch (error) {
|
|
1181
|
+
// Log unexpected errors but don't fail the search
|
|
1182
|
+
if (error?.code !== 'ENOENT' && error?.code !== 'ENOTDIR') {
|
|
1183
|
+
logger.debug(`Error searching ${type} directory for content detection`, {
|
|
1184
|
+
name,
|
|
1185
|
+
type,
|
|
1186
|
+
error: error?.message || String(error),
|
|
1187
|
+
code: error?.code
|
|
1188
|
+
});
|
|
1189
|
+
}
|
|
1190
|
+
// Return null instead of throwing to let other searches continue
|
|
1191
|
+
return null;
|
|
1192
|
+
}
|
|
1193
|
+
});
|
|
1194
|
+
// PERFORMANCE OPTIMIZATION (Task #9): Define exact match criteria
|
|
1195
|
+
const isExactMatch = (match) => {
|
|
1196
|
+
const filename = path.basename(match.path, path.extname(match.path));
|
|
1197
|
+
return filename.toLowerCase() === name.toLowerCase();
|
|
1198
|
+
};
|
|
1199
|
+
// Execute searches with early termination optimization
|
|
1200
|
+
const searchResults = await EarlyTerminationSearch.executeWithEarlyTermination(searchFunctions, isExactMatch, {
|
|
1201
|
+
operationName: 'element-type-detection',
|
|
1202
|
+
timeoutAfterExactMatch: 1000, // Wait 1 second for other searches after exact match
|
|
1203
|
+
maxParallelSearches: 8 // Limit concurrent searches to avoid overwhelming the system
|
|
1204
|
+
});
|
|
1205
|
+
// PERFORMANCE OPTIMIZATION (Task #8): Enhanced batch operation reporting
|
|
1206
|
+
const batchResults = {
|
|
1207
|
+
name,
|
|
1208
|
+
totalSearches: searchResults.totalSearches,
|
|
1209
|
+
completedSearches: searchResults.completedSearches,
|
|
1210
|
+
matches: searchResults.matches.length,
|
|
1211
|
+
failures: searchResults.failures.length,
|
|
1212
|
+
exactMatchFound: !!searchResults.exactMatch,
|
|
1213
|
+
exactMatchType: searchResults.exactMatch?.type,
|
|
1214
|
+
earlyTerminationTriggered: searchResults.earlyTerminationTriggered,
|
|
1215
|
+
performanceGain: searchResults.performanceGain,
|
|
1216
|
+
matchedTypes: searchResults.matches.map(m => m.type),
|
|
1217
|
+
failedTypes: searchResults.failures.map(f => elementTypes[f.index]).filter(Boolean)
|
|
1218
|
+
};
|
|
1219
|
+
logger.debug('Element type detection completed with early termination optimization', batchResults);
|
|
1220
|
+
// PERFORMANCE OPTIMIZATION (Task #8): Clear reporting of partial failures
|
|
1221
|
+
if (searchResults.failures.length > 0) {
|
|
1222
|
+
logger.warn('Some element type searches failed during batch operation', {
|
|
1223
|
+
name,
|
|
1224
|
+
failures: searchResults.failures.map(f => ({
|
|
1225
|
+
type: elementTypes[f.index] || 'unknown',
|
|
1226
|
+
error: f.error.substring(0, 100) // Truncate long error messages
|
|
1227
|
+
})),
|
|
1228
|
+
successRate: `${searchResults.completedSearches}/${searchResults.totalSearches}`,
|
|
1229
|
+
impactOnResults: searchResults.matches.length > 0
|
|
1230
|
+
? 'No impact - matches found in successful searches'
|
|
1231
|
+
: 'Potential impact - no matches found'
|
|
1232
|
+
});
|
|
1233
|
+
// If we have failures and no matches, provide actionable guidance
|
|
1234
|
+
if (searchResults.matches.length === 0 && searchResults.failures.length > 0) {
|
|
1235
|
+
logger.warn('Batch operation had failures and no matches found', {
|
|
1236
|
+
name,
|
|
1237
|
+
recommendation: 'Consider checking file permissions or portfolio structure',
|
|
1238
|
+
failureCount: searchResults.failures.length,
|
|
1239
|
+
totalSearches: searchResults.totalSearches
|
|
1240
|
+
});
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
// Log performance gains from early termination
|
|
1244
|
+
if (searchResults.earlyTerminationTriggered) {
|
|
1245
|
+
logger.info('Early termination optimization applied successfully', {
|
|
1246
|
+
name,
|
|
1247
|
+
exactMatchType: searchResults.exactMatch?.type,
|
|
1248
|
+
performanceGain: searchResults.performanceGain,
|
|
1249
|
+
searchesCompleted: searchResults.completedSearches,
|
|
1250
|
+
searchesTotal: searchResults.totalSearches
|
|
1251
|
+
});
|
|
1252
|
+
}
|
|
1253
|
+
return {
|
|
1254
|
+
found: searchResults.matches.length > 0,
|
|
1255
|
+
matches: searchResults.matches
|
|
1256
|
+
};
|
|
1257
|
+
}
|
|
1258
|
+
catch (error) {
|
|
1259
|
+
logger.error('Error in element type detection', {
|
|
1260
|
+
name,
|
|
1261
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1262
|
+
});
|
|
1263
|
+
// Return empty result on detection failure
|
|
1264
|
+
return {
|
|
1265
|
+
found: false,
|
|
1266
|
+
matches: []
|
|
1267
|
+
};
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
/**
|
|
1271
|
+
* UX IMPROVEMENT: Generate name suggestions for similar content
|
|
1272
|
+
* PERFORMANCE OPTIMIZATION (Task #8): Enhanced batch operation handling with clear partial failure reporting
|
|
1273
|
+
* Helps users find content when exact matches fail
|
|
1274
|
+
*/
|
|
1275
|
+
async generateNameSuggestions(searchName) {
|
|
1276
|
+
try {
|
|
1277
|
+
const suggestions = [];
|
|
1278
|
+
const searchLower = searchName.toLowerCase();
|
|
1279
|
+
const elementTypes = Object.values(ElementType);
|
|
1280
|
+
// Track batch operation results for better diagnostics
|
|
1281
|
+
const batchResults = {
|
|
1282
|
+
searchName,
|
|
1283
|
+
totalTypes: elementTypes.length,
|
|
1284
|
+
successfulScans: 0,
|
|
1285
|
+
failedScans: 0,
|
|
1286
|
+
failureDetails: [],
|
|
1287
|
+
totalSuggestions: 0,
|
|
1288
|
+
suggestionsByType: {}
|
|
1289
|
+
};
|
|
1290
|
+
// Process all element types for suggestions
|
|
1291
|
+
for (const elementType of elementTypes) {
|
|
1292
|
+
try {
|
|
1293
|
+
const portfolioManager = PortfolioManager.getInstance();
|
|
1294
|
+
const elementDir = portfolioManager.getElementDir(elementType);
|
|
1295
|
+
// Get files in this directory
|
|
1296
|
+
const files = await FileDiscoveryUtil.findFile(elementDir, '*', {
|
|
1297
|
+
extensions: ['.md', '.json', '.yaml', '.yml'],
|
|
1298
|
+
partialMatch: false,
|
|
1299
|
+
cacheResults: true
|
|
1300
|
+
});
|
|
1301
|
+
let typeSuggestions = 0;
|
|
1302
|
+
if (Array.isArray(files)) {
|
|
1303
|
+
for (const filePath of files) {
|
|
1304
|
+
const basename = path.basename(filePath, path.extname(filePath));
|
|
1305
|
+
// Calculate similarity using simple metrics
|
|
1306
|
+
if (this.calculateSimilarity(searchLower, basename.toLowerCase()) > SEARCH_CONFIG.MIN_SIMILARITY_SCORE) {
|
|
1307
|
+
suggestions.push({
|
|
1308
|
+
name: basename,
|
|
1309
|
+
type: elementType
|
|
1310
|
+
});
|
|
1311
|
+
typeSuggestions++;
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
else if (files) {
|
|
1316
|
+
const basename = path.basename(files, path.extname(files));
|
|
1317
|
+
if (this.calculateSimilarity(searchLower, basename.toLowerCase()) > SEARCH_CONFIG.MIN_SIMILARITY_SCORE) {
|
|
1318
|
+
suggestions.push({
|
|
1319
|
+
name: basename,
|
|
1320
|
+
type: elementType
|
|
1321
|
+
});
|
|
1322
|
+
typeSuggestions++;
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
batchResults.successfulScans++;
|
|
1326
|
+
batchResults.suggestionsByType[elementType] = typeSuggestions;
|
|
1327
|
+
}
|
|
1328
|
+
catch (error) {
|
|
1329
|
+
// PERFORMANCE OPTIMIZATION (Task #8): Track and report partial failures
|
|
1330
|
+
batchResults.failedScans++;
|
|
1331
|
+
batchResults.failureDetails.push({
|
|
1332
|
+
type: elementType,
|
|
1333
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1334
|
+
});
|
|
1335
|
+
// Log individual failures for diagnostics
|
|
1336
|
+
logger.debug('Failed to scan element type for suggestions', {
|
|
1337
|
+
elementType,
|
|
1338
|
+
searchName,
|
|
1339
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1340
|
+
});
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
batchResults.totalSuggestions = suggestions.length;
|
|
1344
|
+
// PERFORMANCE OPTIMIZATION (Task #8): Comprehensive batch operation reporting
|
|
1345
|
+
logger.debug('Name suggestion batch operation completed', {
|
|
1346
|
+
...batchResults,
|
|
1347
|
+
successRate: `${batchResults.successfulScans}/${batchResults.totalTypes}`,
|
|
1348
|
+
// Don't log full failure details at debug level to avoid spam
|
|
1349
|
+
hasFailures: batchResults.failedScans > 0
|
|
1350
|
+
});
|
|
1351
|
+
// Report failures clearly if they occurred
|
|
1352
|
+
if (batchResults.failedScans > 0) {
|
|
1353
|
+
logger.warn('Some element type scans failed during name suggestion generation', {
|
|
1354
|
+
searchName,
|
|
1355
|
+
failedTypes: batchResults.failureDetails.map(f => f.type),
|
|
1356
|
+
successfulTypes: batchResults.successfulScans,
|
|
1357
|
+
impactOnResults: batchResults.totalSuggestions > 0
|
|
1358
|
+
? 'Partial impact - suggestions found from successful scans'
|
|
1359
|
+
: 'Potential impact - no suggestions generated',
|
|
1360
|
+
recommendation: batchResults.totalSuggestions === 0 && batchResults.failedScans > 0
|
|
1361
|
+
? 'Check portfolio directory structure and file permissions'
|
|
1362
|
+
: 'Suggestion generation partially successful despite some failures'
|
|
1363
|
+
});
|
|
1364
|
+
}
|
|
1365
|
+
// Sort by similarity (higher is better) and return top suggestions
|
|
1366
|
+
const sortedSuggestions = suggestions.sort((a, b) => {
|
|
1367
|
+
const simA = this.calculateSimilarity(searchLower, a.name.toLowerCase());
|
|
1368
|
+
const simB = this.calculateSimilarity(searchLower, b.name.toLowerCase());
|
|
1369
|
+
return simB - simA;
|
|
1370
|
+
});
|
|
1371
|
+
logger.debug('Name suggestions generated successfully', {
|
|
1372
|
+
searchName,
|
|
1373
|
+
totalSuggestions: sortedSuggestions.length,
|
|
1374
|
+
topSuggestions: sortedSuggestions.slice(0, 3).map(s => s.name)
|
|
1375
|
+
});
|
|
1376
|
+
return sortedSuggestions;
|
|
1377
|
+
}
|
|
1378
|
+
catch (error) {
|
|
1379
|
+
logger.warn('Failed to generate name suggestions - batch operation failed completely', {
|
|
1380
|
+
searchName,
|
|
1381
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1382
|
+
recommendation: 'Check portfolio structure and permissions'
|
|
1383
|
+
});
|
|
1384
|
+
return [];
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
/**
|
|
1388
|
+
* Simple similarity calculation using Levenshtein-like approach
|
|
1389
|
+
* Returns value between 0 and 1, where 1 is identical
|
|
1390
|
+
*/
|
|
1391
|
+
calculateSimilarity(str1, str2) {
|
|
1392
|
+
// Handle exact matches
|
|
1393
|
+
if (str1 === str2)
|
|
1394
|
+
return 1;
|
|
1395
|
+
// Handle substring matches
|
|
1396
|
+
if (str1.includes(str2) || str2.includes(str1))
|
|
1397
|
+
return 0.8;
|
|
1398
|
+
// Handle partial matches
|
|
1399
|
+
const longer = str1.length > str2.length ? str1 : str2;
|
|
1400
|
+
const shorter = str1.length > str2.length ? str2 : str1;
|
|
1401
|
+
if (longer.length === 0)
|
|
1402
|
+
return 0;
|
|
1403
|
+
// Count common characters
|
|
1404
|
+
let common = 0;
|
|
1405
|
+
for (let i = 0; i < shorter.length; i++) {
|
|
1406
|
+
if (longer.includes(shorter[i])) {
|
|
1407
|
+
common++;
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
return common / longer.length;
|
|
1411
|
+
}
|
|
1412
|
+
/**
|
|
1413
|
+
* UX IMPROVEMENT: Save element with automatic retry logic for transient failures
|
|
1414
|
+
* Handles common GitHub API issues like rate limits and temporary network problems
|
|
1415
|
+
*/
|
|
1416
|
+
async saveElementWithRetry(adapter, elementName, elementType, maxRetries = RETRY_CONFIG.MAX_ATTEMPTS) {
|
|
1417
|
+
let lastError = null;
|
|
1418
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
1419
|
+
try {
|
|
1420
|
+
logger.debug(`Attempting to save element (attempt ${attempt}/${maxRetries})`, {
|
|
1421
|
+
elementName,
|
|
1422
|
+
elementType,
|
|
1423
|
+
attempt
|
|
1424
|
+
});
|
|
1425
|
+
const fileUrl = await this.portfolioManager.saveElement(adapter, true);
|
|
1426
|
+
if (fileUrl) {
|
|
1427
|
+
if (attempt > 1) {
|
|
1428
|
+
logger.info(`Element saved successfully after ${attempt} attempts`, {
|
|
1429
|
+
elementName,
|
|
1430
|
+
elementType,
|
|
1431
|
+
fileUrl
|
|
1432
|
+
});
|
|
1433
|
+
}
|
|
1434
|
+
return fileUrl;
|
|
1435
|
+
}
|
|
1436
|
+
// If saveElement returns null, treat as a failure but don't retry immediately
|
|
1437
|
+
lastError = new Error(`saveElement returned null on attempt ${attempt}`);
|
|
1438
|
+
}
|
|
1439
|
+
catch (error) {
|
|
1440
|
+
lastError = error;
|
|
1441
|
+
const isRetryable = this.isRetryableError(error);
|
|
1442
|
+
logger.warn(`Save attempt ${attempt} failed`, {
|
|
1443
|
+
elementName,
|
|
1444
|
+
elementType,
|
|
1445
|
+
attempt,
|
|
1446
|
+
error: error.message,
|
|
1447
|
+
isRetryable,
|
|
1448
|
+
willRetry: isRetryable && attempt < maxRetries
|
|
1449
|
+
});
|
|
1450
|
+
// If this is not a retryable error, fail immediately
|
|
1451
|
+
if (!isRetryable) {
|
|
1452
|
+
logger.error('Non-retryable error encountered, aborting retries', {
|
|
1453
|
+
elementName,
|
|
1454
|
+
error: error.message
|
|
1455
|
+
});
|
|
1456
|
+
break;
|
|
1457
|
+
}
|
|
1458
|
+
// If we have more attempts, wait before retrying
|
|
1459
|
+
if (attempt < maxRetries) {
|
|
1460
|
+
const delay = calculateRetryDelay(attempt);
|
|
1461
|
+
logger.debug(`Waiting ${delay}ms before retry`, { attempt, delay });
|
|
1462
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
// All attempts failed
|
|
1467
|
+
logger.error(`All ${maxRetries} save attempts failed`, {
|
|
1468
|
+
elementName,
|
|
1469
|
+
elementType,
|
|
1470
|
+
lastError: lastError?.message
|
|
1471
|
+
});
|
|
1472
|
+
return null;
|
|
1473
|
+
}
|
|
1474
|
+
/**
|
|
1475
|
+
* Determine if an error is worth retrying
|
|
1476
|
+
* Retryable: network issues, rate limits, temporary GitHub API problems
|
|
1477
|
+
* Non-retryable: authentication issues, validation errors, permanent failures
|
|
1478
|
+
*/
|
|
1479
|
+
isRetryableError(error) {
|
|
1480
|
+
const errorMessage = error?.message?.toLowerCase() || '';
|
|
1481
|
+
const errorCode = error?.code;
|
|
1482
|
+
const statusCode = error?.status || error?.statusCode;
|
|
1483
|
+
// Network and timeout errors
|
|
1484
|
+
if (errorCode === 'ENOTFOUND' || errorCode === 'ECONNRESET' || errorCode === 'ETIMEDOUT') {
|
|
1485
|
+
return true;
|
|
1486
|
+
}
|
|
1487
|
+
// GitHub API rate limits
|
|
1488
|
+
if (statusCode === 429 || errorMessage.includes('rate limit')) {
|
|
1489
|
+
return true;
|
|
1490
|
+
}
|
|
1491
|
+
// Temporary GitHub API issues
|
|
1492
|
+
if (statusCode >= 500 && statusCode < 600) {
|
|
1493
|
+
return true;
|
|
1494
|
+
}
|
|
1495
|
+
// Temporary GitHub API problems
|
|
1496
|
+
if (errorMessage.includes('temporarily unavailable') ||
|
|
1497
|
+
errorMessage.includes('service unavailable') ||
|
|
1498
|
+
errorMessage.includes('internal server error')) {
|
|
1499
|
+
return true;
|
|
1500
|
+
}
|
|
1501
|
+
// Connection issues
|
|
1502
|
+
if (errorMessage.includes('connection') &&
|
|
1503
|
+
(errorMessage.includes('timeout') || errorMessage.includes('reset'))) {
|
|
1504
|
+
return true;
|
|
1505
|
+
}
|
|
1506
|
+
// Don't retry authentication or permission issues
|
|
1507
|
+
if (statusCode === 401 || statusCode === 403 ||
|
|
1508
|
+
errorMessage.includes('unauthorized') ||
|
|
1509
|
+
errorMessage.includes('forbidden') ||
|
|
1510
|
+
errorMessage.includes('authentication')) {
|
|
1511
|
+
return false;
|
|
1512
|
+
}
|
|
1513
|
+
// Don't retry validation errors
|
|
1514
|
+
if (statusCode === 400 || statusCode === 422 ||
|
|
1515
|
+
errorMessage.includes('invalid') ||
|
|
1516
|
+
errorMessage.includes('validation')) {
|
|
1517
|
+
return false;
|
|
1518
|
+
}
|
|
1519
|
+
// Default to not retrying for unknown errors to avoid infinite loops
|
|
1520
|
+
return false;
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3VibWl0VG9Qb3J0Zm9saW9Ub29sLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL3Rvb2xzL3BvcnRmb2xpby9zdWJtaXRUb1BvcnRmb2xpb1Rvb2wudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7O0dBUUc7QUFFSCxPQUFPLEVBQUUsaUJBQWlCLEVBQUUsTUFBTSxpQ0FBaUMsQ0FBQztBQUNwRSxPQUFPLEVBQUUsb0JBQW9CLEVBQUUsTUFBTSx5Q0FBeUMsQ0FBQztBQUMvRSxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sZ0NBQWdDLENBQUM7QUFDOUQsT0FBTyxFQUFFLGdCQUFnQixFQUFFLE1BQU0sb0NBQW9DLENBQUM7QUFDdEUsT0FBTyxFQUFFLGdCQUFnQixFQUFFLE1BQU0scUNBQXFDLENBQUM7QUFDdkUsT0FBTyxFQUFFLHFCQUFxQixFQUFFLE1BQU0sMENBQTBDLENBQUM7QUFDakYsT0FBTyxFQUFFLFdBQVcsRUFBRSxNQUFNLDBCQUEwQixDQUFDO0FBQ3ZELE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSx1QkFBdUIsQ0FBQztBQUMvQyxPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSwrQ0FBK0MsQ0FBQztBQUNqRixPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sbUNBQW1DLENBQUM7QUFHcEUsT0FBTyxFQUFFLHVCQUF1QixFQUFFLE1BQU0sOEJBQThCLENBQUM7QUFDdkUsT0FBTyxFQUFFLGlCQUFpQixFQUFFLE1BQU0sa0NBQWtDLENBQUM7QUFDckUsT0FBTyxFQUFFLFlBQVksRUFBaUIsTUFBTSw2QkFBNkIsQ0FBQztBQUMxRSxPQUFPLEVBRUwsZ0JBQWdCLEVBQ2hCLFlBQVksRUFDWixhQUFhLEVBRWIsbUJBQW1CLEVBQ25CLG1CQUFtQixFQUNwQixNQUFNLHFDQUFxQyxDQUFDO0FBQzdDLE9BQU8sRUFBRSxpQkFBaUIsRUFBRSxNQUFNLGtDQUFrQyxDQUFDO0FBQ3JFLE9BQU8sRUFBRSxz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UUFBUSxDQUFDLFlBQVksQ0FBQztZQUNuQyxDQUFDLFlBQVksQ0FBQyxRQUFRLENBQUMsU0FBUyxDQUFDLElBQUksWUFBWSxDQUFDLFFBQVEsQ0FBQyxPQUFPLENBQUMsQ0FBQyxFQUFFLENBQUM7WUFDekUsT0FBTyxJQUFJLENBQUM7UUFDZCxDQUFDO1FBRUQsa0RBQWtEO1FBQ2xELElBQUksVUFBVSxLQUFLLEdBQUcsSUFBSSxVQUFVLEtBQUssR0FBRztZQUN4QyxZQUFZLENBQUMsUUFBUSxDQUFDLGNBQWMsQ0FBQztZQUNyQyxZQUFZLENBQUMsUUFBUSxDQUFDLFdBQVcsQ0FBQztZQUNsQyxZQUFZLENBQUMsUUFBUSxDQUFDLGdCQUFnQixDQUFDLEVBQUUsQ0FBQztZQUM1QyxPQUFPLEtBQUssQ0FBQztRQUNmLENBQUM7UUFFRCxnQ0FBZ0M7UUFDaEMsSUFBSSxVQUFVLEtBQUssR0FBRyxJQUFJLFVBQVUsS0FBSyxHQUFHO1lBQ3hDLFlBQVksQ0FBQyxRQUFRLENBQUMsU0FBUyxDQUFDO1lBQ2hDLFlBQVksQ0FBQyxRQUFRLENBQUMsWUFBWSxDQUFDLEVBQUUsQ0FBQztZQUN4QyxPQUFPLEtBQUssQ0FBQztRQUNmLENBQUM7UUFFRCxxRUFBcUU7UUFDckUsT0FBTyxLQUFLLENBQUM7SUFDZixDQUFDO0NBQ0YiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIFRvb2wgZm9yIHN1Ym1pdHRpbmcgY29udGVudCB0byBHaXRIdWIgcG9ydGZvbGlvIHJlcG9zaXRvcmllc1xuICogUmVwbGFjZXMgdGhlIGJyb2tlbiBpc3N1ZS1iYXNlZCBzdWJtaXNzaW9uIHdpdGggZGlyZWN0IHJlcG9zaXRvcnkgc2F2ZXNcbiAqIFxuICogRklYRVMgSU1QTEVNRU5URUQgKFBSICM1MDMpOlxuICogMS4gVFlQRSBTQUZFVFkgRklYICMxIChJc3N1ZSAjNDk3KTogQ2hhbmdlZCBhcGlDYWNoZSBmcm9tICdhbnknIHRvIHByb3BlciBBUElDYWNoZSB0eXBlXG4gKiAyLiBUWVBFIFNBRkVUWSBGSVggIzIgKElzc3VlICM0OTcpOiBSZXBsYWNlZCBjb21wbGV4IHR5cGUgY2FzdGluZyB3aXRoIFBvcnRmb2xpb0VsZW1lbnRBZGFwdGVyXG4gKiAzLiBQRVJGT1JNQU5DRSAoUFIgIzQ5NiByZWNvbW1lbmRhdGlvbik6IFVzaW5nIEZpbGVEaXNjb3ZlcnlVdGlsIGZvciBvcHRpbWl6ZWQgZmlsZSBzZWFyY2hcbiAqL1xuXG5pbXBvcnQgeyBHaXRIdWJBdXRoTWFuYWdlciB9IGZyb20gJy4uLy4uL2F1dGgvR2l0SHViQXV0aE1hbmFnZXIuanMnO1xuaW1wb3J0IHsgUG9ydGZvbGlvUmVwb01hbmFnZXIgfSBmcm9tICcuLi8uLi9wb3J0Zm9saW8vUG9ydGZvbGlvUmVwb01hbmFnZXIuanMnO1xuaW1wb3J0IHsgVG9rZW5NYW5hZ2VyIH0gZnJvbSAnLi4vLi4vc2VjdXJpdHkvdG9rZW5NYW5hZ2VyLmpzJztcbmltcG9ydCB7IENvbnRlbnRWYWxpZGF0b3IgfSBmcm9tICcuLi8uLi9zZWN1cml0eS9jb250ZW50VmFsaWRhdG9yLmpzJztcbmltcG9ydCB7IFBvcnRmb2xpb01hbmFnZXIgfSBmcm9tICcuLi8uLi9wb3J0Zm9saW8vUG9ydGZvbGlvTWFuYWdlci5qcyc7XG5pbXBvcnQgeyBQb3J0Zm9saW9JbmRleE1hbmFnZXIgfSBmcm9tICcuLi8uLi9wb3J0Zm9saW8vUG9ydGZvbGlvSW5kZXhNYW5hZ2VyLmpzJztcbmltcG9ydCB7IEVsZW1lbnRUeXBlIH0gZnJvbSAnLi4vLi4vcG9ydGZvbGlvL3R5cGVzLmpzJztcbmltcG9ydCB7IGxvZ2dlciB9IGZyb20gJy4uLy4uL3V0aWxzL2xvZ2dlci5qcyc7XG5pbXBvcnQgeyBVbmljb2RlVmFsaWRhdG9yIH0gZnJvbSAnLi4vLi4vc2VjdXJpdHkvdmFsaWRhdG9ycy91bmljb2RlVmFsaWRhdG9yLmpzJztcbmltcG9ydCB7IFNlY3VyaXR5TW9uaXRvciB9IGZyb20gJy4uLy4uL3NlY3VyaXR5L3NlY3VyaXR5TW9uaXRvci5qcyc7XG5pbXBvcnQgeyBQYXRoVmFsaWRhdG9yIH0gZnJvbSAnLi4vLi4vc2VjdXJpdHkvcGF0aFZhbGlkYXRvci5qcyc7XG5pbXBvcnQgeyBBUElDYWNoZSB9IGZyb20gJy4uLy4uL2NhY2hlL0FQSUNhY2hlLmpzJztcbmltcG9ydCB7IFBvcnRmb2xpb0VsZW1lbnRBZGFwdGVyIH0gZnJvbSAnLi9Qb3J0Zm9saW9FbGVtZW50QWRhcHRlci5qcyc7XG5pbXBvcnQgeyBGaWxlRGlzY292ZXJ5VXRpbCB9IGZyb20gJy4uLy4uL3V0aWxzL0ZpbGVEaXNjb3ZlcnlVdGlsLmpzJztcbmltcG9ydCB7IEVycm9ySGFuZGxlciwgRXJyb3JDYXRlZ29yeSB9IGZyb20gJy4uLy4uL3V0aWxzL0Vycm9ySGFuZGxlci5qcyc7XG5pbXBvcnQgeyBcbiAgR0lUSFVCX0FQSV9USU1FT1VULCBcbiAgRklMRV9TSVpFX0xJTUlUUywgXG4gIFJFVFJZX0NPTkZJRywgXG4gIFNFQVJDSF9DT05GSUcsXG4gIFBvcnRmb2xpb0VsZW1lbnRNZXRhZGF0YSxcbiAgZ2V0VmFsaWRhdGVkVGltZW91dCxcbiAgY2FsY3VsYXRlUmV0cnlEZWxheVxufSBmcm9tICcuLi8uLi9jb25maWcvcG9ydGZvbGlvLWNvbnN0YW50cy5qcyc7XG5pbXBvcnQgeyBnaXRodWJSYXRlTGltaXRlciB9IGZyb20gJy4uLy4uL3V0aWxzL0dpdEh1YlJhdGVMaW1pdGVyLmpzJztcbmltcG9ydCB7IEVhcmx5VGVybWluYXRpb25TZWFyY2ggfSBmcm9tICcuLi8uLi91dGlscy9FYXJseVRlcm1pbmF0aW9uU2VhcmNoLmpzJztcbmltcG9ydCAqIGFzIHBhdGggZnJvbSAncGF0aCc7XG5pbXBvcnQgKiBhcyBmcyBmcm9tICdmcy9wcm9taXNlcyc7XG5cbmV4cG9ydCBpbnRlcmZhY2UgU3VibWl0VG9Qb3J0Zm9saW9QYXJhbXMge1xuICBuYW1lOiBzdHJpbmc7XG4gIHR5cGU/OiBFbGVtZW50VHlwZTtcbn1cblxuZXhwb3J0IGludGVyZmFjZSBQb3J0Zm9saW9FbGVtZW50IHtcbiAgdHlwZTogRWxlbWVudFR5cGU7XG4gIG1ldGFkYXRhOiBQb3J0Zm9saW9FbGVtZW50TWV0YWRhdGE7XG4gIGNvbnRlbnQ6IHN0cmluZztcbn1cblxuZXhwb3J0IGludGVyZmFjZSBTdWJtaXRUb1BvcnRmb2xpb1Jlc3VsdCB7XG4gIHN1Y2Nlc3M6IGJvb2xlYW47XG4gIG1lc3NhZ2U6IHN0cmluZztcbiAgdXJsPzogc3RyaW5nO1xuICBlcnJvcj86IHN0cmluZztcbn1cblxuZXhwb3J0IGludGVyZmFjZSBFbGVtZW50RGV0ZWN0aW9uTWF0Y2gge1xuICB0eXBlOiBFbGVtZW50VHlwZTtcbiAgcGF0aDogc3RyaW5nO1xufVxuXG5leHBvcnQgaW50ZXJmYWNlIEVsZW1lbnREZXRlY3Rpb25SZXN1bHQge1xuICBmb3VuZDogYm9vbGVhbjtcbiAgbWF0Y2hlczogRWxlbWVudERldGVjdGlvbk1hdGNoW107XG59XG5cbmV4cG9ydCBjbGFzcyBTdWJtaXRUb1BvcnRmb2xpb1Rvb2wge1xuICBwcml2YXRlIGF1dGhNYW5hZ2VyOiBHaXRIdWJBdXRoTWFuYWdlcjtcbiAgcHJpdmF0ZSBwb3J0Zm9saW9NYW5hZ2VyOiBQb3J0Zm9saW9SZXBvTWFuYWdlcjtcbiAgcHJpdmF0ZSBjb250ZW50VmFsaWRhdG9yOiBDb250ZW50VmFsaWRhdG9yO1xuXG4gIGNvbnN0cnVjdG9yKGFwaUNhY2hlOiBBUElDYWNoZSkge1xuICAgIC8vIFRZUEUgU0FGRVRZIEZJWCAjMTogUHJvcGVyIHR5cGluZyBmb3IgYXBpQ2FjaGUgcGFyYW1ldGVyXG4gICAgLy8gUHJldmlvdXNseTogY29uc3RydWN0b3IoYXBpQ2FjaGU6IGFueSlcbiAgICAvLyBOb3c6IGNvbnN0cnVjdG9yKGFwaUNhY2hlOiBBUElDYWNoZSkgd2l0aCBwcm9wZXIgaW1wb3J0XG4gICAgdGhpcy5hdXRoTWFuYWdlciA9IG5ldyBHaXRIdWJBdXRoTWFuYWdlcihhcGlDYWNoZSk7XG4gICAgdGhpcy5wb3J0Zm9saW9NYW5hZ2VyID0gbmV3IFBvcnRmb2xpb1JlcG9NYW5hZ2VyKCk7XG4gICAgdGhpcy5jb250ZW50VmFsaWRhdG9yID0gbmV3IENvbnRlbnRWYWxpZGF0b3IoKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBWYWxpZGF0ZXMgYW5kIG5vcm1hbGl6ZXMgaW5wdXQgcGFyYW1ldGVycyB0byBwcmV2ZW50IFVuaWNvZGUgYXR0YWNrcyBhbmQgZW5zdXJlIGRhdGEgc2FmZXR5XG4gICAqIEBwYXJhbSBwYXJhbXMgVGhlIGlucHV0IHBhcmFtZXRlcnMgZnJvbSB0aGUgdXNlclxuICAgKiBAcmV0dXJucyBWYWxpZGF0aW9uIHJlc3VsdCB3aXRoIG5vcm1hbGl6ZWQgbmFtZSBvciBlcnJvciByZXNwb25zZVxuICAgKi9cbiAgcHJpdmF0ZSBhc3luYyB2YWxpZGF0ZUFuZE5vcm1hbGl6ZVBhcmFtcyhwYXJhbXM6IFN1Ym1pdFRvUG9ydGZvbGlvUGFyYW1zKTogUHJvbWlzZTx7XG4gICAgc3VjY2VzczogYm9vbGVhbjtcbiAgICBzYWZlTmFtZT86IHN0cmluZztcbiAgICBlcnJvcj86IFN1Ym1pdFRvUG9ydGZvbGlvUmVzdWx0O1xuICB9PiB7XG4gICAgLy8gTm9ybWFsaXplIHVzZXIgaW5wdXQgdG8gcHJldmVudCBVbmljb2RlIGF0dGFja3MgKERNQ1AtU0VDLTAwNClcbiAgICBjb25zdCBub3JtYWxpemVkTmFtZSA9IFVuaWNvZGVWYWxpZGF0b3Iubm9ybWFsaXplKHBhcmFtcy5uYW1lKTtcbiAgICBpZiAoIW5vcm1hbGl6ZWROYW1lLmlzVmFsaWQpIHtcbiAgICAgIFNlY3VyaXR5TW9uaXRvci5sb2dTZWN1cml0eUV2ZW50KHtcbiAgICAgICAgdHlwZTogJ1VOSUNPREVfVkFMSURBVElPTl9FUlJPUicsXG4gICAgICAgIHNldmVyaXR5OiAnTUVESVVNJyxcbiAgICAgICAgc291cmNlOiAnU3VibWl0VG9Qb3J0Zm9saW9Ub29sLmV4ZWN1dGUnLFxuICAgICAgICBkZXRhaWxzOiBgSW52YWxpZCBVbmljb2RlIGluIGVsZW1lbnQgbmFtZTogJHtub3JtYWxpemVkTmFtZS5kZXRlY3RlZElzc3Vlcz8uWzBdIHx8ICd1bmtub3duIGVycm9yJ31gXG4gICAgICB9KTtcbiAgICAgIHJldHVybiB7XG4gICAgICAgIHN1Y2Nlc3M6IGZhbHNlLFxuICAgICAgICBlcnJvcjoge1xuICAgICAgICAgIHN1Y2Nlc3M6IGZhbHNlLFxuICAgICAgICAgIG1lc3NhZ2U6IGBJbnZhbGlkIGNoYXJhY3RlcnMgaW4gZWxlbWVudCBuYW1lOiAke25vcm1hbGl6ZWROYW1lLmRldGVjdGVkSXNzdWVzPy5bMF0gfHwgJ3Vua25vd24gZXJyb3InfWAsXG4gICAgICAgICAgZXJyb3I6ICdJTlZBTElEX0lOUFVUJ1xuICAgICAgICB9XG4gICAgICB9O1xuICAgIH1cbiAgICBcbiAgICByZXR1cm4ge1xuICAgICAgc3VjY2VzczogdHJ1ZSxcbiAgICAgIHNhZmVOYW1lOiBub3JtYWxpemVkTmFtZS5ub3JtYWxpemVkQ29udGVudFxuICAgIH07XG4gIH1cblxuICAvKipcbiAgICogQ2hlY2tzIGlmIHRoZSB1c2VyIGlzIGF1dGhlbnRpY2F0ZWQgd2l0aCBHaXRIdWJcbiAgICogQHJldHVybnMgQXV0aGVudGljYXRpb24gY2hlY2sgcmVzdWx0IHdpdGggc3RhdHVzIG9yIGVycm9yIHJlc3BvbnNlXG4gICAqL1xuICBwcml2YXRlIGFzeW5jIGNoZWNrQXV0aGVudGljYXRpb24oKTogUHJvbWlzZTx7XG4gICAgc3VjY2VzczogYm9vbGVhbjtcbiAgICBhdXRoU3RhdHVzPzogYW55O1xuICAgIGVycm9yPzogU3VibWl0VG9Qb3J0Zm9saW9SZXN1bHQ7XG4gIH0+IHtcbiAgICBjb25zdCBhdXRoU3RhdHVzID0gYXdhaXQgdGhpcy5hdXRoTWFuYWdlci5nZXRBdXRoU3RhdHVzKCk7XG4gICAgaWYgKCFhdXRoU3RhdHVzLmlzQXV0aGVudGljYXRlZCkge1xuICAgICAgLy8gTG9nIGF1dGhlbnRpY2F0aW9uIHJlcXVpcmVkICh1c2luZyBleGlzdGluZyBldmVudCB0eXBlKVxuICAgICAgbG9nZ2VyLndhcm4oJ1VzZXIgYXR0ZW1wdGVkIHBvcnRmb2xpbyBzdWJtaXNzaW9uIHdpdGhvdXQgYXV0aGVudGljYXRpb24nKTtcbiAgICAgIHJldHVybiB7XG4gICAgICAgIHN1Y2Nlc3M6IGZhbHNlLFxuICAgICAgICBlcnJvcjoge1xuICAgICAgICAgIHN1Y2Nlc3M6IGZhbHNlLFxuICAgICAgICAgIG1lc3NhZ2U6ICdOb3QgYXV0aGVudGljYXRlZC4gUGxlYXNlIGF1dGhlbnRpY2F0ZSBmaXJzdCB1c2luZyB0aGUgR2l0SHViIE9BdXRoIGZsb3cuXFxuXFxuJyArXG4gICAgICAgICAgICAgICAgICAgJ1Zpc2l0OiBodHRwczovL2RvY3MuYW50aHJvcGljLmNvbS9lbi9kb2NzL2NsYXVkZS1jb2RlL29hdXRoLXNldHVwXFxuJyArXG4gICAgICAgICAgICAgICAgICAgJ09yIHJ1bjogZ2ggYXV0aCBsb2dpbiAtLXdlYicsXG4gICAgICAgICAgZXJyb3I6ICdOT1RfQVVUSEVOVElDQVRFRCdcbiAgICAgICAgfVxuICAgICAgfTtcbiAgICB9XG5cbiAgICByZXR1cm4ge1xuICAgICAgc3VjY2VzczogdHJ1ZSxcbiAgICAgIGF1dGhTdGF0dXNcbiAgICB9O1xuICB9XG5cbiAgLyoqXG4gICAqIERpc2NvdmVycyBjb250ZW50IGxvY2FsbHkgd2l0aCBzbWFydCB0eXBlIGRldGVjdGlvblxuICAgKiBAcGFyYW0gc2FmZU5hbWUgVGhlIG5vcm1hbGl6ZWQgbmFtZSB0byBzZWFyY2ggZm9yXG4gICAqIEBwYXJhbSBleHBsaWNpdFR5cGUgT3B0aW9uYWwgZXhwbGljaXQgZWxlbWVudCB0eXBlIHByb3ZpZGVkIGJ5IHVzZXJcbiAgICogQHBhcmFtIG9yaWdpbmFsTmFtZSBPcmlnaW5hbCB1c2VyLXByb3ZpZGVkIG5hbWUgZm9yIGVycm9yIG1lc3NhZ2VzXG4gICAqIEByZXR1cm5zIENvbnRlbnQgZGlzY292ZXJ5IHJlc3VsdCB3aXRoIGVsZW1lbnQgdHlwZSBhbmQgcGF0aCBvciBlcnJvciByZXNwb25zZVxuICAgKi9cbiAgcHJpdmF0ZSBhc3luYyBkaXNjb3ZlckNvbnRlbnRXaXRoVHlwZURldGVjdGlvbihcbiAgICBzYWZlTmFtZTogc3RyaW5nLCBcbiAgICBleHBsaWNpdFR5cGU/OiBFbGVtZW50VHlwZSwgXG4gICAgb3JpZ2luYWxOYW1lPzogc3RyaW5nXG4gICk6IFByb21pc2U8e1xuICAgIHN1Y2Nlc3M6IGJvb2xlYW47XG4gICAgZWxlbWVudFR5cGU/OiBFbGVtZW50VHlwZTtcbiAgICBsb2NhbFBhdGg/OiBzdHJpbmc7XG4gICAgZXJyb3I/OiBTdWJtaXRUb1BvcnRmb2xpb1Jlc3VsdDtcbiAgfT4ge1xuICAgIGxldCBlbGVtZW50VHlwZSA9IGV4cGxpY2l0VHlwZTtcbiAgICBsZXQgbG9jYWxQYXRoOiBzdHJpbmcgfCBudWxsID0gbnVsbDtcbiAgICBcbiAgICBpZiAoZWxlbWVudFR5cGUpIHtcbiAgICAgIC8vIFR5cGUgZXhwbGljaXRseSBwcm92aWRlZCAtIHNlYXJjaCBpbiB0aGF0IHNwZWNpZmljIGRpcmVjdG9yeSBvbmx5XG4gICAgICBsb2NhbFBhdGggPSBhd2FpdCB0aGlzLmZpbmRMb2NhbENvbnRlbnQoc2FmZU5hbWUsIGVsZW1lbnRUeXBlKTtcbiAgICAgIGlmICghbG9jYWxQYXRoKSB7XG4gICAgICAgIC8vIFVYIElNUFJPVkVNRU5UOiBQcm92aWRlIGhlbHBmdWwgc3VnZ2VzdGlvbnMgZm9yIGZpbmRpbmcgY29udGVudFxuICAgICAgICBjb25zdCBwb3J0Zm9saW9NYW5hZ2VyID0gUG9ydGZvbGlvTWFuYWdlci5nZXRJbnN0YW5jZSgpO1xuICAgICAgICBjb25zdCBlbGVtZW50RGlyID0gcG9ydGZvbGlvTWFuYWdlci5nZXRFbGVtZW50RGlyKGVsZW1lbnRUeXBlKTtcbiAgICAgICAgXG4gICAgICAgIHJldHVybiB7XG4gICAgICAgICAgc3VjY2VzczogZmFsc2UsXG4gICAgICAgICAgZXJyb3I6IHtcbiAgICAgICAgICAgIHN1Y2Nlc3M6IGZhbHNlLFxuICAgICAgICAgICAgbWVzc2FnZTogYENvdWxkIG5vdCBmaW5kICR7ZWxlbWVudFR5cGV9IG5hbWVkIFwiJHtvcmlnaW5hbE5hbWUgfHwgc2FmZU5hbWV9XCIgaW4gbG9jYWwgcG9ydGZvbGlvLlxcblxcbmAgK1xuICAgICAgICAgICAgICAgICAgICBgKipTZWFyY2hlZCBpbioqOiAke2VsZW1lbnREaXJ9XFxuXFxuYCArXG4gICAgICAgICAgICAgICAgICAgIGAqKlRyb3VibGVzaG9vdGluZyBUaXBzKio6XFxuYCArXG4gICAgICAgICAgICAgICAgICAgIGDigKIgQ2hlY2sgaWYgdGhlIGZpbGUgZXhpc3RzIHVzaW5nIHlvdXIgZmlsZSBleHBsb3JlclxcbmAgK1xuICAgICAgICAgICAgICAgICAgICBg4oCiIFRyeSB1c2luZyB0aGUgZXhhY3QgZmlsZW5hbWUgKHdpdGhvdXQgZXh0ZW5zaW9uKVxcbmAgK1xuICAgICAgICAgICAgICAgICAgICBg4oCiIFVzZSBcXGBsaXN0X3BvcnRmb2xpb1xcYCB0byBzZWUgYWxsIGF2YWlsYWJsZSAke2VsZW1lbnRUeXBlfVxcbmAgK1xuICAgICAgICAgICAgICAgICAgICBg4oCiIElmIHVuc3VyZSBvZiB0aGUgdHlwZSwgb21pdCAtLXR5cGUgYW5kIGxldCB0aGUgc3lzdGVtIGRldGVjdCBpdFxcblxcbmAgK1xuICAgICAgICAgICAgICAgICAgICBgKipDb21tb24gbmFtZSBmb3JtYXRzIHRoYXQgd29yayoqOlxcbmAgK1xuICAgICAgICAgICAgICAgICAgICBg4oCiIFwibXktZWxlbWVudFwiIChrZWJhYi1jYXNlKVxcbmAgK1xuICAgICAgICAgICAgICAgICAgICBg4oCiIFwiTXkgRWxlbWVudFwiICh3aXRoIHNwYWNlcylcXG5gICtcbiAgICAgICAgICAgICAgICAgICAgYOKAoiBcIk15RWxlbWVudFwiIChQYXNjYWxDYXNlKVxcbmAgK1xuICAgICAgICAgICAgICAgICAgICBg4oCiIFBhcnRpYWwgbWF0Y2hlcyBhcmUgc3VwcG9ydGVkYCxcbiAgICAgICAgICAgIGVycm9yOiAnQ09OVEVOVF9OT1RfRk9VTkQnXG4gICAgICAgICAgfVxuICAgICAgICB9O1xuICAgICAgfVxuICAgIH0gZWxzZSB7XG4gICAgICAvLyBDUklUSUNBTCBGSVg6IE5vIHR5cGUgcHJvdmlkZWQgLSBpbXBsZW1lbnQgc21hcnQgZGV0ZWN0aW9uIGFjcm9zcyBBTEwgZWxlbWVudCB0eXBlc1xuICAgICAgLy8gVGhpcyBwcmV2ZW50cyB0aGUgcHJldmlvdXMgaGFyZGNvZGVkIGRlZmF1bHQgdG8gUEVSU09OQSBhbmQgZW5hYmxlcyBwcm9wZXIgdHlwZSBkZXRlY3Rpb25cbiAgICAgIGNvbnN0IGRldGVjdGlvblJlc3VsdCA9IGF3YWl0IHRoaXMuZGV0ZWN0RWxlbWVudFR5cGUoc2FmZU5hbWUpO1xuICAgICAgXG4gICAgICBpZiAoIWRldGVjdGlvblJlc3VsdC5mb3VuZCkge1xuICAgICAgICAvLyBVWCBJTVBST1ZFTUVOVDogRW5oYW5jZWQgZ3VpZGFuY2Ugd2l0aCBzcGVjaWZpYyBzdWdnZXN0aW9uc1xuICAgICAgICBjb25zdCBhdmFpbGFibGVUeXBlcyA9IE9iamVjdC52YWx1ZXMoRWxlbWVudFR5cGUpLmpvaW4oJywgJyk7XG4gICAgICAgIFxuICAgICAgICAvLyBHZXQgc3VnZ2VzdGlvbnMgZm9yIHNpbWlsYXIgbmFtZXNcbiAgICAgICAgY29uc3Qgc3VnZ2VzdGlvbnMgPSBhd2FpdCB0aGlzLmdlbmVyYXRlTmFtZVN1Z2dlc3Rpb25zKHNhZmVOYW1lKTtcbiAgICAgICAgXG4gICAgICAgIGxldCBtZXNzYWdlID0gYENvbnRlbnQgXCIke29yaWdpbmFsTmFtZSB8fCBzYWZlTmFtZX1cIiBub3QgZm91bmQgaW4gcG9ydGZvbGlvLlxcblxcbmA7XG4gICAgICAgIG1lc3NhZ2UgKz0gYPCflI0gKipTZWFyY2hlZCBpbiBhbGwgZWxlbWVudCB0eXBlcyoqOiAke2F2YWlsYWJsZVR5cGVzfVxcblxcbmA7XG4gICAgICAgIFxuICAgICAgICBpZiAoc3VnZ2VzdGlvbnMubGVuZ3RoID4gMCkge1xuICAgICAgICAgIG1lc3NhZ2UgKz0gYPCfkqEgKipEaWQgeW91IG1lYW4gb25lIG9mIHRoZXNlPyoqXFxuYDtcbiAgICAgICAgICBmb3IgKGNvbnN0IHN1Z2dlc3Rpb24gb2Ygc3VnZ2VzdGlvbnMuc2xpY2UoMCwgU0VBUkNIX0NPTkZJRy5NQVhfU1VHR0VTVElPTlMpKSB7XG4gICAgICAgICAgICBtZXNzYWdlICs9IGAgIOKAoiBcIiR7c3VnZ2VzdGlvbi5uYW1lfVwiICgke3N1Z2dlc3Rpb24udHlwZX0pXFxuYDtcbiAgICAgICAgICB9XG4gICAgICAgICAgbWVzc2FnZSArPSBgXFxuYDtcbiAgICAgICAgfVxuICAgICAgICBcbiAgICAgICAgbWVzc2FnZSArPSBg8J+boO+4jyAqKlRyb3VibGVzaG9vdGluZyBTdGVwcyoqOlxcbmA7XG4gICAgICAgIG1lc3NhZ2UgKz0gYDEuIPCfk50gVXNlIFxcYGxpc3RfcG9ydGZvbGlvXFxgIHRvIHNlZSBhbGwgYXZhaWxhYmxlIGNvbnRlbnRcXG5gO1xuICAgICAgICBtZXNzYWdlICs9IGAyLiDwn5SNIENoZWNrIGV4YWN0IHNwZWxsaW5nIGFuZCB0cnkgdmFyaWF0aW9uczpcXG5gO1xuICAgICAgICBtZXNzYWdlICs9IGAgICDigKIgXCIkeyhvcmlnaW5hbE5hbWUgfHwgc2FmZU5hbWUpLnRvTG93ZXJDYXNlKCl9XCIgKGxvd2VyY2FzZSlcXG5gO1xuICAgICAgICBtZXNzYWdlICs9IGAgICDigKIgXCIkeyhvcmlnaW5hbE5hbWUgfHwgc2FmZU5hbWUpLnJlcGxhY2UoL1teYS16MC05XS9naSwgJy0nKS50b0xvd2VyQ2FzZSgpfVwiIChub3JtYWxpemVkKVxcbmA7XG4gICAgICAgIGlmICgob3JpZ2luYWxOYW1lIHx8IHNhZmVOYW1lKS5pbmNsdWRlcygnLicpKSB7XG4gICAgICAgICAgbWVzc2FnZSArPSBgICAg4oCiIFwiJHsob3JpZ2luYWxOYW1lIHx8IHNhZmVOYW1lKS5yZXBsYWNlKC9cXC4vZywgJycpfVwiIChubyBkb3RzKVxcbmA7XG4gICAgICAgIH1cbiAgICAgICAgbWVzc2FnZSArPSBgMy4g8J+OryBTcGVjaWZ5IGVsZW1lbnQgdHlwZTogXFxgc3VibWl0X2NvbnRlbnQgXCIke29yaWdpbmFsTmFtZSB8fCBzYWZlTmFtZX1cIiAtLXR5cGU9cGVyc29uYXNcXGBcXG5gO1xuICAgICAgICBtZXNzYWdlICs9IGA0LiDwn5OBIENoZWNrIGlmIGZpbGUgZXhpc3RzIGluIHBvcnRmb2xpbyBkaXJlY3Rvcmllc1xcblxcbmA7XG4gICAgICAgIG1lc3NhZ2UgKz0gYPCfk50gKipUaXAqKjogVGhlIHN5c3RlbSBzZWFyY2hlcyBmaWxlbmFtZXMgQU5EIG1ldGFkYXRhIG5hbWVzIHdpdGggZnV6enkgbWF0Y2hpbmcuYDtcbiAgICAgICAgXG4gICAgICAgIHJldHVybiB7XG4gICAgICAgICAgc3VjY2VzczogZmFsc2UsXG4gICAgICAgICAgZXJyb3I6IHtcbiAgICAgICAgICAgIHN1Y2Nlc3M6IGZhbHNlLFxuICAgICAgICAgICAgbWVzc2FnZSxcbiAgICAgICAgICAgIGVycm9yOiAnQ09OVEVOVF9OT1RfRk9VTkQnXG4gICAgICAgICAgfVxuICAgICAgICB9O1xuICAgICAgfVxuICAgICAgXG4gICAgICBpZiAoZGV0ZWN0aW9uUmVzdWx0Lm1hdGNoZXMubGVuZ3RoID4gMSkge1xuICAgICAgICAvLyBNdWx0aXBsZSBtYXRjaGVzIGZvdW5kIC0gYXNrIHVzZXIgdG8gc3BlY2lmeSB0eXBlXG4gICAgICAgIGNvbnN0IG1hdGNoRGV0YWlscyA9IGRldGVjdGlvblJlc3VsdC5tYXRjaGVzLm1hcChtID0+IGAtICR7bS50eXBlfTogJHttLnBhdGh9YCkuam9pbignXFxuJyk7XG4gICAgICAgIHJldHVybiB7XG4gICAgICAgICAgc3VjY2VzczogZmFsc2UsXG4gICAgICAgICAgZXJyb3I6IHtcbiAgICAgICAgICAgIHN1Y2Nlc3M6IGZhbHNlLFxuICAgICAgICAgICAgbWVzc2FnZTogYENvbnRlbnQgXCIke29yaWdpbmFsTmFtZSB8fCBzYWZlTmFtZX1cIiBmb3VuZCBpbiBtdWx0aXBsZSBlbGVtZW50IHR5cGVzOlxcblxcbiR7bWF0Y2hEZXRhaWxzfVxcblxcbmAgK1xuICAgICAgICAgICAgICAgICAgICBgUGxlYXNlIHNwZWNpZnkgdGhlIGVsZW1lbnQgdHlwZSB1c2luZyB0aGUgLS10eXBlIHBhcmFtZXRlciB0byBhdm9pZCBhbWJpZ3VpdHkuYCxcbiAgICAgICAgICAgIGVycm9yOiAnTVVMVElQTEVfTUFUQ0hFU19GT1VORCdcbiAgICAgICAgICB9XG4gICAgICAgIH07XG4gICAgICB9XG4gICAgICBcbiAgICAgIC8vIFNpbmdsZSBtYXRjaCBmb3VuZCAtIHVzZSBpdFxuICAgICAgY29uc3QgbWF0Y2ggPSBkZXRlY3Rpb25SZXN1bHQubWF0Y2hlc1swXTtcbiAgICAgIGVsZW1lbnRUeXBlID0gbWF0Y2gudHlwZTtcbiAgICAgIGxvY2FsUGF0aCA9IG1hdGNoLnBhdGg7XG4gICAgICBcbiAgICAgIGxvZ2dlci5pbmZvKGBTbWFydCBkZXRlY3Rpb246IEZvdW5kIFwiJHtzYWZlTmFtZX1cIiBhcyAke2VsZW1lbnRUeXBlfWAsIHsgXG4gICAgICAgIG5hbWU6IHNhZmVOYW1lLFxuICAgICAgICBkZXRlY3RlZFR5cGU6IGVsZW1lbnRUeXBlLFxuICAgICAgICBwYXRoOiBsb2NhbFBhdGhcbiAgICAgIH0pO1xuICAgIH1cblxuICAgIHJldHVybiB7XG4gICAgICBzdWNjZXNzOiB0cnVlLFxuICAgICAgZWxlbWVudFR5cGUsXG4gICAgICBsb2NhbFBhdGhcbiAgICB9O1xuICB9XG5cbiAgLyoqXG4gICAqIFZhbGlkYXRlcyBmaWxlIHNpemUgYW5kIGNvbnRlbnQgc2VjdXJpdHkgYmVmb3JlIHByb2Nlc3NpbmdcbiAgICogQHBhcmFtIGxvY2FsUGF0aCBQYXRoIHRvIHRoZSBsb2NhbCBmaWxlIHRvIHZhbGlkYXRlXG4gICAqIEByZXR1cm5zIFZhbGlkYXRpb24gcmVzdWx0IHdpdGggY29udGVudCBvciBlcnJvciByZXNwb25zZVxuICAgKi9cbiAgcHJpdmF0ZSBhc3luYyB2YWxpZGF0ZUZpbGVBbmRDb250ZW50KGxvY2FsUGF0aDogc3RyaW5nKTogUHJvbWlzZTx7XG4gICAgc3VjY2VzczogYm9vbGVhbjtcbiAgICBjb250ZW50Pzogc3RyaW5nO1xuICAgIGVycm9yPzogU3VibWl0VG9Qb3J0Zm9saW9SZXN1bHQ7XG4gIH0+IHtcbiAgICAvLyBTRUNVUklUWSBFTkhBTkNFTUVOVCAoVGFzayAjNyk6IFZhbGlkYXRlIGZpbGUgcGF0aCBiZWZvcmUgcHJvY2Vzc2luZ1xuICAgIGNvbnN0IHBhdGhWYWxpZGF0aW9uID0gYXdhaXQgdGhpcy52YWxpZGF0ZVBvcnRmb2xpb1BhdGgobG9jYWxQYXRoKTtcbiAgICBpZiAoIXBhdGhWYWxpZGF0aW9uLmlzVmFsaWQpIHtcbiAgICAgIHJldHVybiB7XG4gICAgICAgIHN1Y2Nlc3M6IGZhbHNlLFxuICAgICAgICBlcnJvcjogcGF0aFZhbGlkYXRpb24uZXJyb3JcbiAgICAgIH07XG4gICAgfVxuXG4gICAgLy8gVXNlIHRoZSB2YWxpZGF0ZWQgc2FmZSBwYXRoIGZvciBhbGwgc3Vic2VxdWVudCBvcGVyYXRpb25zXG4gICAgY29uc3Qgc2FmZVBhdGggPSBwYXRoVmFsaWRhdGlvbi5zYWZlUGF0aCE7XG5cbiAgICAvLyBWYWxpZGF0ZSBmaWxlIHNpemUgYmVmb3JlIHJlYWRpbmdcbiAgICBjb25zdCBzdGF0cyA9IGF3YWl0IGZzLnN0YXQoc2FmZVBhdGgpO1xuICAgIGlmIChzdGF0cy5zaXplID4gRklMRV9TSVpFX0xJTUlUUy5NQVhfRklMRV9TSVpFKSB7XG4gICAgICBTZWN1cml0eU1vbml0b3IubG9nU2VjdXJpdHlFdmVudCh7XG4gICAgICAgIHR5cGU6ICdSQVRFX0xJTUlUX0VYQ0VFREVEJyxcbiAgICAgICAgc2V2ZXJpdHk6ICdNRURJVU0nLFxuICAgICAgICBzb3VyY2U6ICdTdWJtaXRUb1BvcnRmb2xpb1Rvb2wuZXhlY3V0ZScsXG4gICAgICAgIGRldGFpbHM6IGBGaWxlIHNpemUgJHtzdGF0cy5zaXplfSBleGNlZWRzIGxpbWl0IG9mICR7RklMRV9TSVpFX0xJTUlUUy5NQVhfRklMRV9TSVpFfWBcbiAgICAgIH0pO1xuICAgICAgcmV0dXJuIHtcbiAgICAgICAgc3VjY2VzczogZmFsc2UsXG4gICAgICAgIGVycm9yOiB7XG4gICAgICAgICAgc3VjY2VzczogZmFsc2UsXG4gICAgICAgICAgbWVzc2FnZTogYEZpbGUgc2l6ZSBleGNlZWRzICR7RklMRV9TSVpFX0xJTUlUUy5NQVhfRklMRV9TSVpFX01CfU1CIGxpbWl0YCxcbiAgICAgICAgICBlcnJvcjogJ0ZJTEVfVE9PX0xBUkdFJ1xuICAgICAgICB9XG4gICAgICB9O1xuICAgIH1cblxuICAgIC8vIFZhbGlkYXRlIGNvbnRlbnQgc2VjdXJpdHlcbiAgICBjb25zdCBjb250ZW50ID0gYXdhaXQgZnMucmVhZEZpbGUoc2FmZVBhdGgsICd1dGYtOCcpO1xuICAgIGNvbnN0IHZhbGlkYXRpb25SZXN1bHQgPSBDb250ZW50VmFsaWRhdG9yLnZhbGlkYXRlQW5kU2FuaXRpemUoY29udGVudCk7XG5cbiAgICBpZiAoIXZhbGlkYXRpb25SZXN1bHQuaXNWYWxpZCAmJiB2YWxpZGF0aW9uUmVzdWx0LnNldmVyaXR5ID09PSAnY3JpdGljYWwnKSB7XG4gICAgICBTZWN1cml0eU1vbml0b3IubG9nU2VjdXJpdHlFdmVudCh7XG4gICAgICAgIHR5cGU6ICdDT05URU5UX0lOSkVDVElPTl9BVFRFTVBUJyxcbiAgICAgICAgc2V2ZXJpdHk6ICdISUdIJyxcbiAgICAgICAgc291cmNlOiAnU3VibWl0VG9Qb3J0Zm9saW9Ub29sLmV4ZWN1dGUnLFxuICAgICAgICBkZXRhaWxzOiBgQ3JpdGljYWwgc2VjdXJpdHkgaXNzdWVzIGRldGVjdGVkOiAke3ZhbGlkYXRpb25SZXN1bHQuZGV0ZWN0ZWRQYXR0ZXJucz8uam9pbignLCAnKX1gXG4gICAgICB9KTtcbiAgICAgIHJldHVybiB7XG4gICAgICAgIHN1Y2Nlc3M6IGZhbHNlLFxuICAgICAgICBlcnJvcjoge1xuICAgICAgICAgIHN1Y2Nlc3M6IGZhbHNlLFxuICAgICAgICAgIG1lc3NhZ2U6IGBDb250ZW50IHZhbGlkYXRpb24gZmFpbGVkOiAke3ZhbGlkYXRpb25SZXN1bHQuZGV0ZWN0ZWRQYXR0ZXJucz8uam9pbignLCAnKX1gLFxuICAgICAgICAgIGVycm9yOiAnVkFMSURBVElPTl9GQUlMRUQnXG4gICAgICAgIH1cbiAgICAgIH07XG4gICAgfVxuXG4gICAgcmV0dXJuIHtcbiAgICAgIHN1Y2Nlc3M6IHRydWUsXG4gICAgICBjb250ZW50XG4gICAgfTtcbiAgfVxuXG4gIC8qKlxuICAgKiBQcmVwYXJlcyBtZXRhZGF0YSBmb3IgdGhlIHBvcnRmb2xpbyBlbGVtZW50XG4gICAqIEBwYXJhbSBzYWZlTmFtZSBUaGUgbm9ybWFsaXplZCBuYW1lIG9mIHRoZSBlbGVtZW50XG4gICAqIEBwYXJhbSBlbGVtZW50VHlwZSBUaGUgdHlwZSBvZiB0aGUgZWxlbWVudFxuICAgKiBAcGFyYW0gYXV0aFN0YXR1cyBBdXRoZW50aWNhdGlvbiBzdGF0dXMgY29udGFpbmluZyB1c2VybmFtZVxuICAgKiBAcmV0dXJucyBNZXRhZGF0YSBvYmplY3QgZm9yIHRoZSBlbGVtZW50XG4gICAqL1xuICBwcml2YXRlIHByZXBhcmVFbGVtZW50TWV0YWRhdGEoXG4gICAgc2FmZU5hbWU6IHN0cmluZywgXG4gICAgZWxlbWVudFR5cGU6IEVsZW1lbnRUeXBlLCBcbiAgICBhdXRoU3RhdHVzOiBhbnlcbiAgKTogUG9ydGZvbGlvRWxlbWVudE1ldGFkYXRhIHtcbiAgICByZXR1cm4ge1xuICAgICAgbmFtZTogc2FmZU5hbWUsXG4gICAgICBkZXNjcmlwdGlvbjogYCR7ZWxlbWVudFR5cGV9IHN1Ym1pdHRlZCBmcm9tIGxvY2FsIHBvcnRmb2xpb2AsXG4gICAgICBhdXRob3I6IGF1dGhTdGF0dXMudXNlcm5hbWUgfHwgJ3Vua25vd24nLFxuICAgICAgY3JlYXRlZDogbmV3IERhdGUoKS50b0lTT1N0cmluZygpLFxuICAgICAgdXBkYXRlZDogbmV3IERhdGUoKS50b0lTT1N0cmluZygpLFxuICAgICAgdmVyc2lvbjogJzEuMC4wJ1xuICAgIH07XG4gIH1cblxuICAvKipcbiAgICogVmFsaWRhdGVzIEdpdEh1YiB0b2tlbiBhbmQgY2hlY2tzIGZvciBleHBpcmF0aW9uIGJlZm9yZSB1c2FnZVxuICAgKiBTRUNVUklUWSBFTkhBTkNFTUVOVCAoVGFzayAjNSk6IFRva2VuIGV4cGlyYXRpb24gdmFsaWRhdGlvbiB0byBwcmV2ZW50IHN0YWxlIHRva2VuIHVzYWdlXG4gICAqIEBwYXJhbSB0b2tlbiBUaGUgR2l0SHViIHRva2VuIHRvIHZhbGlkYXRlXG4gICAqIEByZXR1cm5zIFZhbGlkYXRpb24gcmVzdWx0IHdpdGggc3RhdHVzIGFuZCBleHBpcmF0aW9uIGluZm9cbiAgICovXG4gIHByaXZhdGUgYXN5bmMgdmFsaWRhdGVUb2tlbkJlZm9yZVVzYWdlKHRva2VuOiBzdHJpbmcpOiBQcm9taXNlPHtcbiAgICBpc1ZhbGlkOiBib29sZWFuO1xuICAgIGlzTmVhckV4cGlyeT86IGJvb2xlYW47XG4gICAgZXJyb3I/OiBTdWJtaXRUb1BvcnRmb2xpb1Jlc3VsdDtcbiAgfT4ge1xuICAgIHRyeSB7XG4gICAgICAvLyBDaGVjayB0b2tlbiBmb3JtYXQgZmlyc3QgKGJhc2ljIHZhbGlkYXRpb24pXG4gICAgICBpZiAoIVRva2VuTWFuYWdlci52YWxpZGF0ZVRva2VuRm9ybWF0KHRva2VuKSkge1xuICAgICAgICBTZWN1cml0eU1vbml0b3IubG9nU2VjdXJpdHlFdmVudCh7XG4gICAgICAgICAgdHlwZTogJ1RPS0VOX1ZBTElEQVRJT05fRkFJTFVSRScsXG4gICAgICAgICAgc2V2ZXJpdHk6ICdNRURJVU0nLFxuICAgICAgICAgIHNvdXJjZTogJ1N1Ym1pdFRvUG9ydGZvbGlvVG9vbC52YWxpZGF0ZVRva2VuQmVmb3JlVXNhZ2UnLFxuICAgICAgICAgIGRldGFpbHM6ICdUb2tlbiBoYXMgaW52YWxpZCBmb3JtYXQnXG4gICAgICAgIH0pO1xuICAgICAgICBcbiAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICBpc1ZhbGlkOiBmYWxzZSxcbiAgICAgICAgICBlcnJvcjoge1xuICAgICAgICAgICAgc3VjY2VzczogZmFsc2UsXG4gICAgICAgICAgICBtZXNzYWdlOiAnSW52YWxpZCB0b2tlbiBmb3JtYXQuIFBsZWFzZSByZS1hdXRoZW50aWNhdGUuJyxcbiAgICAgICAgICAgIGVycm9yOiAnSU5WQUxJRF9UT0tFTl9GT1JNQVQnXG4gICAgICAgICAgfVxuICAgICAgICB9O1xuICAgICAgfVxuXG4gICAgICAvLyBWYWxpZGF0ZSB0b2tlbiB3aXRoIEdpdEh1YiBBUEkgdG8gY2hlY2sgZXhwaXJhdGlvbiBhbmQgcGVybWlzc2lvbnNcbiAgICAgIGNvbnN0IHZhbGlkYXRpb25SZXN1bHQgPSBhd2FpdCBUb2tlbk1hbmFnZXIudmFsaWRhdGVUb2tlblNjb3Blcyh0b2tlbiwge1xuICAgICAgICByZXF1aXJlZDogWydyZXBvJ10sXG4gICAgICAgIG9wdGlvbmFsOiBbJ3VzZXI6ZW1haWwnXVxuICAgICAgfSk7XG5cbiAgICAgIGlmICghdmFsaWRhdGlvblJlc3VsdC5pc1ZhbGlkKSB7XG4gICAgICAgIFNlY3VyaXR5TW9uaXRvci5sb2dTZWN1cml0eUV2ZW50KHtcbiAgICAgICAgICB0eXBlOiAnVE9LRU5fVkFMSURBVElPTl9GQUlMVVJFJyxcbiAgICAgICAgICBzZXZlcml0eTogJ01FRElVTScsXG4gICAgICAgICAgc291cmNlOiAnU3VibWl0VG9Qb3J0Zm9saW9Ub29sLnZhbGlkYXRlVG9rZW5CZWZvcmVVc2FnZScsXG4gICAgICAgICAgZGV0YWlsczogYFRva2VuIHZhbGlkYXRpb24gZmFpbGVkOiAke3ZhbGlkYXRpb25SZXN1bHQuZXJyb3J9YFxuICAgICAgICB9KTtcblxuICAgICAgICByZXR1cm4ge1xuICAgICAgICAgIGlzVmFsaWQ6IGZhbHNlLFxuICAgICAgICAgIGVycm9yOiB7XG4gICAgICAgICAgICBzdWNjZXNzOiBmYWxzZSxcbiAgICAgICAgICAgIG1lc3NhZ2U6ICdHaXRIdWIgdG9rZW4gaXMgaW52YWxpZCBvciBleHBpcmVkLiBQbGVhc2UgcmUtYXV0aGVudGljYXRlLicsXG4gICAgICAgICAgICBlcnJvcjogJ1RPS0VOX1ZBTElEQVRJT05fRkFJTEVEJ1xuICAgICAgICAgIH1cbiAgICAgICAgfTtcbiAgICAgIH1cblxuICAgICAgLy8gQ2hlY2sgaWYgdG9rZW4gaXMgbmVhciBleHBpcmF0aW9uIChyYXRlIGxpbWl0IHJlc2V0IHRpbWUgY2FuIGluZGljYXRlIHRva2VuIGZyZXNobmVzcylcbiAgICAgIGxldCBpc05lYXJFeHBpcnkgPSBmYWxzZTtcbiAgICAgIGlmICh2YWxpZGF0aW9uUmVzdWx0LnJhdGVMaW1pdD8ucmVzZXRUaW1lKSB7XG4gICAgICAgIGNvbnN0IG5vdyA9IG5ldyBEYXRlKCk7XG4gICAgICAgIGNvbnN0IHRpbWVVbnRpbFJlc2V0ID0gdmFsaWRhdGlvblJlc3VsdC5yYXRlTGltaXQucmVzZXRUaW1lLmdldFRpbWUoKSAtIG5vdy5nZXRUaW1lKCk7XG4gICAgICAgIGNvbnN0IG9uZUhvdXIgPSA2MCAqIDYwICogMTAwMDtcbiAgICAgICAgXG4gICAgICAgIC8vIENvbnNpZGVyIHRva2VuIFwibmVhciBleHBpcnlcIiBpZiByYXRlIGxpbWl0IHJlc2V0IGlzIG1vcmUgdGhhbiAyMyBob3VycyBhd2F5XG4gICAgICAgIC8vIChHaXRIdWIgcmF0ZSBsaW1pdHMgcmVzZXQgZXZlcnkgaG91ciwgc28gdGhpcyBzdWdnZXN0cyB0b2tlbiBhZ2UpXG4gICAgICAgIGlmICh0aW1lVW50aWxSZXNldCA+IDIzICogb25lSG91cikge1xuICAgICAgICAgIGlzTmVhckV4cGlyeSA9IHRydWU7XG4gICAgICAgICAgbG9nZ2VyLndhcm4oJ0dpdEh1YiB0b2tlbiBtYXkgYmUgbmVhciBleHBpcmF0aW9uJywge1xuICAgICAgICAgICAgdG9rZW5QcmVmaXg6IFRva2VuTWFuYWdlci5nZXRUb2tlblByZWZpeCh0b2tlbiksXG4gICAgICAgICAgICByYXRlTGltaXRSZXNldFRpbWU6IHZhbGlkYXRpb25SZXN1bHQucmF0ZUxpbWl0LnJlc2V0VGltZSxcbiAgICAgICAgICAgIHJlY29tbWVuZGF0aW9uOiAnQ29uc2lkZXIgcmUtYXV0aGVudGljYXRpbmcgZm9yIGxvbmcgb3BlcmF0aW9ucydcbiAgICAgICAgICB9KTtcbiAgICAgICAgfVxuICAgICAgfVxuXG4gICAgICAvLyBMb2cgc3VjY2Vzc2Z1bCB2YWxpZGF0aW9uXG4gICAgICBTZWN1cml0eU1vbml0b3IubG9nU2VjdXJpdHlFdmVudCh7XG4gICAgICAgIHR5cGU6ICdUT0tFTl9WQUxJREFUSU9OX1NVQ0NFU1MnLFxuICAgICAgICBzZXZlcml0eTogJ0xPVycsXG4gICAgICAgIHNvdXJjZTogJ1N1Ym1pdFRvUG9ydGZvbGlvVG9vbC52YWxpZGF0ZVRva2VuQmVmb3JlVXNhZ2UnLFxuICAgICAgICBkZXRhaWxzOiAnR2l0SHViIHRva2VuIHZhbGlkYXRlZCBzdWNjZXNzZnVsbHkgYmVmb3JlIHVzYWdlJyxcbiAgICAgICAgbWV0YWRhdGE6IHtcbiAgICAgICAgICB0b2tlblR5cGU6IFRva2VuTWFuYWdlci5nZXRUb2tlblR5cGUodG9rZW4pLFxuICAgICAgICAgIHNjb3BlczogdmFsaWRhdGlvblJlc3VsdC5zY29wZXMsXG4gICAgICAgICAgcmF0ZUxpbWl0UmVtYWluaW5nOiB2YWxpZGF0aW9uUmVzdWx0LnJhdGVMaW1pdD8ucmVtYWluaW5nLFxuICAgICAgICAgIGlzTmVhckV4cGlyeVxuICAgICAgICB9XG4gICAgICB9KTtcblxuICAgICAgcmV0dXJuIHtcbiAgICAgICAgaXNWYWxpZDogdHJ1ZSxcbiAgICAgICAgaXNOZWFyRXhwaXJ5XG4gICAgICB9O1xuXG4gICAgfSBjYXRjaCAoZXJyb3I6IGFueSkge1xuICAgICAgLy8gSGFuZGxlIHJhdGUgbGltaXQgZXhjZWVkZWQgc3BlY2lmaWNhbGx5XG4gICAgICBpZiAoZXJyb3I/LmNvZGUgPT09ICdSQVRFX0xJTUlUX0VYQ0VFREVEJykge1xuICAgICAgICBsb2dnZXIud2FybignVG9rZW4gdmFsaWRhdGlvbiByYXRlIGxpbWl0ZWQsIGFsbG93aW5nIG9wZXJhdGlvbiB0byBwcm9jZWVkIHdpdGggY2FjaGVkIHN0YXR1cycpO1xuICAgICAgICByZXR1cm4geyBpc1ZhbGlkOiB0cnVlIH07IC8vIEFsbG93IHRvIHByb2NlZWQgaWYgcmF0ZSBsaW1pdGVkLCBhcyBiYXNpYyBmb3JtYXQgY2hlY2sgcGFzc2VkXG4gICAgICB9XG5cbiAgICAgIFNlY3VyaXR5TW9uaXRvci5sb2dTZWN1cml0eUV2ZW50KHtcbiAgICAgICAgdHlwZTogJ1RPS0VOX1ZBTElEQVRJT05fRkFJTFVSRScsXG4gICAgICAgIHNldmVyaXR5OiAnSElHSCcsXG4gICAgICAgIHNvdXJjZTogJ1N1Ym1pdFRvUG9ydGZvbGlvVG9vbC52YWxpZGF0ZVRva2VuQmVmb3JlVXNhZ2UnLFxuICAgICAgICBkZXRhaWxzOiBgVG9rZW4gdmFsaWRhdGlvbiBlcnJvcjogJHtlcnJvci5tZXNzYWdlIHx8ICd1bmtub3duIGVycm9yJ31gXG4gICAgICB9KTtcblxuICAgICAgcmV0dXJuIHtcbiAgICAgICAgaXNWYWxpZDogZmFsc2UsXG4gICAgICAgIGVycm9yOiB7XG4gICAgICAgICAgc3VjY2VzczogZmFsc2UsXG4gICAgICAgICAgbWVzc2FnZTogJ1VuYWJsZSB0byB2YWxpZGF0ZSBHaXRIdWIgdG9rZW4uIFBsZWFzZSBjaGVjayB5b3VyIGNvbm5lY3Rpb24gYW5kIHRyeSBhZ2Fpbi4nLFxuICAgICAgICAgIGVycm9yOiAnVE9LRU5fVkFMSURBVElPTl9FUlJPUidcbiAgICAgICAgfVxuICAgICAgfTtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogRW5oYW5jZWQgcGF0aCB2YWxpZGF0aW9uIGZvciBwb3J0Zm9saW8gb3BlcmF0aW9ucyB3aXRoIGNvbXByZWhlbnNpdmUgc2VjdXJpdHkgY2hlY2tzXG4gICAqIFNFQ1VSSVRZIEVOSEFOQ0VNRU5UIChUYXNrICM3KTogQWRkaXRpb25hbCB2YWxpZGF0aW9uIGZvciBzcGVjaWFsIGNoYXJhY3RlcnMgYW5kIG1hbGljaW91cyBwYXR0ZXJuc1xuICAgKiBAcGFyYW0gZmlsZVBhdGggVGhlIGZpbGUgcGF0aCB0byB2YWxpZGF0ZVxuICAgKiBAcmV0dXJucyBWYWxpZGF0aW9uIHJlc3VsdCB3aXRoIHNlY3VyZSBwYXRoIG9yIGVycm9yIHJlc3BvbnNlXG4gICAqL1xuICBwcml2YXRlIGFzeW5jIHZhbGlkYXRlUG9ydGZvbGlvUGF0aChmaWxlUGF0aDogc3RyaW5nKTogUHJvbWlzZTx7XG4gICAgaXNWYWxpZDogYm9vbGVhbjtcbiAgICBzYWZlUGF0aD86IHN0cmluZztcbiAgICBlcnJvcj86IFN1Ym1pdFRvUG9ydGZvbGlvUmVzdWx0O1xuICB9PiB7XG4gICAgdHJ5IHtcbiAgICAgIC8vIEJhc2ljIG51bGwvdW5kZWZpbmVkIGNoZWNrXG4gICAgICBpZiAoIWZpbGVQYXRoIHx8IHR5cGVvZiBmaWxlUGF0aCAhPT0gJ3N0cmluZycpIHtcbiAgICAgICAgU2VjdXJpdHlNb25pdG9yLmxvZ1NlY3VyaXR5RXZlbnQoe1xuICAgICAgICAgIHR5cGU6ICdQQVRIX1RSQVZFUlNBTF9BVFRFTVBUJyxcbiAgICAgICAgICBzZXZlcml0eTogJ01FRElVTScsXG4gICAgICAgICAgc291cmNlOiAnU3VibWl0VG9Qb3J0Zm9saW9Ub29sLnZhbGlkYXRlUG9ydGZvbGlvUGF0aCcsXG4gICAgICAgICAgZGV0YWlsczogJ0ludmFsaWQgcGF0aCBwcm92aWRlZCAtIG51bGwsIHVuZGVmaW5lZCwgb3Igbm9uLXN0cmluZydcbiAgICAgICAgfSk7XG4gICAgICAgIFxuICAgICAgICByZXR1cm4ge1xuICAgICAgICAgIGlzVmFsaWQ6IGZhbHNlLFxuICAgICAgICAgIGVycm9yOiB7XG4gICAgICAgICAgICBzdWNjZXNzOiBmYWxzZSxcbiAgICAgICAgICAgIG1lc3NhZ2U6ICdJbnZhbGlkIGZpbGUgcGF0aCBwcm92aWRlZCcsXG4gICAgICAgICAgICBlcnJvcjogJ0lOVkFMSURfUEFUSCdcbiAgICAgICAgICB9XG4gICAgICAgIH07XG4gICAgICB9XG5cbiAgICAgIC8vIENoZWNrIGZvciBzdXNwaWNpb3VzIHBhdHRlcm5zIHRoYXQgY291bGQgaW5kaWNhdGUgcGF0aCB0cmF2ZXJzYWwgb3IgaW5qZWN0aW9uXG4gICAgICBjb25zdCBzdXNwaWNpb3VzUGF0dGVybnMgPSBbXG4gICAgICAgIC9cXC5cXC4vLCAgICAgICAgICAgICAgICAgICAgLy8gUGF0aCB0cmF2ZXJzYWxcbiAgICAgICAgL1xcL1xcLlxcLi8sICAgICAgICAgICAgICAgICAgLy8gVW5peCBwYXRoIHRyYXZlcnNhbFxuICAgICAgICAvXFxcXFxcLlxcLi8sICAgICAgICAgICAgICAgICAgLy8gV2luZG93cyBwYXRoIHRyYXZlcnNhbFxuICAgICAgICAvXFx4MDAvLCAgICAgICAgICAgICAgICAgICAgLy8gTnVsbCBieXRlc1xuICAgICAgICAvW1xceDAxLVxceDFmXFx4N2YtXFx4OWZdLywgICAgLy8gQ29udHJvbCBjaGFyYWN0ZXJzXG4gICAgICAgIC9bPD46XCJ8PypdLywgICAgICAgICAgICAgICAvLyBJbnZhbGlkIGZpbGVuYW1lIGNoYXJhY3RlcnMgb24gV2luZG93c1xuICAgICAgICAvXihDT058UFJOfEFVWHxOVUx8Q09NWzEtOV18TFBUWzEtOV0pJC9pLCAvLyBSZXNlcnZlZCBXaW5kb3dzIG5hbWVzXG4gICAgICAgIC9eXFwuLywgICAgICAgICAgICAgICAgICAgICAvLyBIaWRkZW4gZmlsZXMgKHN0YXJ0aW5nIHdpdGggZG90KVxuICAgICAgICAvXFxzKyQvLCAgICAgICAgICAgICAgICAgICAgLy8gVHJhaWxpbmcgd2hpdGVzcGFjZVxuICAgICAgICAvXltcXHNdKiQvLCAgICAgICAgICAgICAgICAgLy8gT25seSB3aGl0ZXNwYWNlXG4gICAgICAgIC8lWzAtOWEtZkEtRl17Mn0vLCAgICAgICAgIC8vIFVSTCBlbmNvZGluZyAocG90ZW50aWFsIGJ5cGFzcyBhdHRlbXB0KVxuICAgICAgICAvXFxcXHhbMC05YS1mQS1GXXsyfS8sICAgICAgIC8vIEhleCBlbmNvZGluZ1xuICAgICAgICAvXFwkXFx7LipcXH0vLCAgICAgICAgICAgICAgICAvLyBUZW1wbGF0ZSBsaXRlcmFsIGluamVjdGlvblxuICAgICAgICAvYC4qYC8sICAgICAgICAgICAgICAgICAgICAvLyBCYWNrdGljayBpbmplY3Rpb25cbiAgICAgICAgL1tcXFxcXFwvXXsyLH0vICAgICAgICAgICAgICAgLy8gTXVsdGlwbGUgY29uc2VjdXRpdmUgc2xhc2hlc1xuICAgICAgXTtcblxuICAgICAgZm9yIChjb25zdCBwYXR0ZXJuIG9mIHN1c3BpY2lvdXNQYXR0ZXJucykge1xuICAgICAgICBpZiAocGF0dGVybi50ZXN0KGZpbGVQYXRoKSkge1xuICAgICAgICAgIFNlY3VyaXR5TW9uaXRvci5sb2dTZWN1cml0eUV2ZW50KHtcbiAgICAgICAgICAgIHR5cGU6ICdQQVRIX1RSQVZFUlNBTF9BVFRFTVBUJyxcbiAgICAgICAgICAgIHNldmVyaXR5OiAnSElHSCcsXG4gICAgICAgICAgICBzb3VyY2U6ICdTdWJtaXRUb1BvcnRmb2xpb1Rvb2wudmFsaWRhdGVQb3J0Zm9saW9QYXRoJyxcbiAgICAgICAgICAgIGRldGFpbHM6IGBTdXNwaWNpb3VzIHBhdHRlcm4gZGV0ZWN0ZWQgaW4gZmlsZSBwYXRoOiAke3BhdHRlcm4uc291cmNlfWAsXG4gICAgICAgICAgICBtZXRhZGF0YToge1xuICAgICAgICAgICAgICBwYXRoTGVuZ3RoOiBmaWxlUGF0aC5sZW5ndGgsXG4gICAgICAgICAgICAgIHBhdHRlcm46IHBhdHRlcm4uc291cmNlXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSk7XG4gICAgICAgICAgXG4gICAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICAgIGlzVmFsaWQ6IGZhbHNlLFxuICAgICAgICAgICAgZXJyb3I6IHtcbiAgICAgICAgICAgICAgc3VjY2VzczogZmFsc2UsXG4gICAgICAgICAgICAgIG1lc3NhZ2U6ICdGaWxlIHBhdGggY29udGFpbnMgaW52YWxpZCBvciBzdXNwaWNpb3VzIGNoYXJhY3RlcnMnLFxuICAgICAgICAgICAgICBlcnJvcjogJ1NVU1BJQ0lPVVNfUEFUSF9QQVRURVJOJ1xuICAgICAgICAgICAgfVxuICAgICAgICAgIH07XG4gICAgICAgIH1cbiAgICAgIH1cblxuICAgICAgLy8gQ2hlY2sgcGF0aCBsZW5ndGggKHByZXZlbnQgYnVmZmVyIG92ZXJmbG93IGF0dGVtcHRzKVxuICAgICAgY29uc3QgTUFYX1BBVEhfTEVOR1RIID0gcHJvY2Vzcy5wbGF0Zm9ybSA9PT0gJ3dpbjMyJyA/IDI2MCA6IDQwOTY7XG4gICAgICBpZiAoZmlsZVBhdGgubGVuZ3RoID4gTUFYX1BBVEhfTEVOR1RIKSB7XG4gICAgICAgIFNlY3VyaXR5TW9uaXRvci5sb2dTZWN1cml0eUV2ZW50KHtcbiAgICAgICAgICB0eXBlOiAnUEFUSF9UUkFWRVJTQUxfQVRURU1QVCcsXG4gICAgICAgICAgc2V2ZXJpdHk6ICdNRURJVU0nLFxuICAgICAgICAgIHNvdXJjZTogJ1N1Ym1pdFRvUG9ydGZvbGlvVG9vbC52YWxpZGF0ZVBvcnRmb2xpb1BhdGgnLFxuICAgICAgICAgIGRldGFpbHM6IGBGaWxlIHBhdGggZXhjZWVkcyBtYXhpbXVtIGxlbmd0aDogJHtmaWxlUGF0aC5sZW5ndGh9ID4gJHtNQVhfUEFUSF9MRU5HVEh9YFxuICAgICAgICB9KTtcbiAgICAgICAgXG4gICAgICAgIHJldHVybiB7XG4gICAgICAgICAgaXNWYWxpZDogZmFsc2UsXG4gICAgICAgICAgZXJyb3I6IHtcbiAgICAgICAgICAgIHN1Y2Nlc3M6IGZhbHNlLFxuICAgICAgICAgICAgbWVzc2FnZTogJ0ZpbGUgcGF0aCBpcyB0b28gbG9uZycsXG4gICAgICAgICAgICBlcnJvcjogJ1BBVEhfVE9PX0xPTkcnXG4gICAgICAgICAgfVxuICAgICAgICB9O1xuICAgICAgfVxuXG4gICAgICAvLyBOb3JtYWxpemUgcGF0aCB0byByZXNvbHZlIGFueSByZWxhdGl2ZSBjb21wb25lbnRzIHNhZmVseVxuICAgICAgbGV0IG5vcm1hbGl6ZWRQYXRoOiBzdHJpbmc7XG4gICAgICB0cnkge1xuICAgICAgICAvLyBSZW1vdmUgbnVsbCBieXRlcyBhbmQgbm9ybWFsaXplXG4gICAgICAgIGNvbnN0IGNsZWFuUGF0aCA9IGZpbGVQYXRoLnJlcGxhY2UoL1xceDAwL2csICcnKTtcbiAgICAgICAgbm9ybWFsaXplZFBhdGggPSBwYXRoLm5vcm1hbGl6ZShjbGVhblBhdGgpO1xuICAgICAgICBcbiAgICAgICAgLy8gRW5zdXJlIHRoZSBub3JtYWxpemVkIHBhdGggZG9lc24ndCBlc2NhcGUgdGhlIGludGVuZGVkIGRpcmVjdG9yeVxuICAgICAgICBpZiAobm9ybWFsaXplZFBhdGguaW5jbHVkZXMoJy4uJykgfHwgbm9ybWFsaXplZFBhdGguc3RhcnRzV2l0aCgnLycpIHx8IFxuICAgICAgICAgICAgKHByb2Nlc3MucGxhdGZvcm0gPT09ICd3aW4zMicgJiYgL15bYS16QS1aXTovLnRlc3Qobm9ybWFsaXplZFBhdGgpKSkge1xuICAgICAgICAgIHRocm93IG5ldyBFcnJvcignUGF0aCBub3JtYWxpemF0aW9uIHJlc3VsdGVkIGluIGRpcmVjdG9yeSB0cmF2ZXJzYWwnKTtcbiAgICAgICAgfVxuICAgICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgICAgU2VjdXJpdHlNb25pdG9yLmxvZ1NlY3VyaXR5RXZlbnQoe1xuICAgICAgICAgIHR5cGU6ICdQQVRIX1RSQVZFUlNBTF9BVFRFTVBUJyxcbiAgICAgICAgICBzZXZlcml0eTogJ0hJR0gnLFxuICAgICAgICAgIHNvdXJjZTogJ1N1Ym1pdFRvUG9ydGZvbGlvVG9vbC52YWxpZGF0ZVBvcnRmb2xpb1BhdGgnLFxuICAgICAgICAgIGRldGFpbHM6IGBQYXRoIG5vcm1hbGl6YXRpb24gZmFpbGVkOiAke2Vycm9yIGluc3RhbmNlb2YgRXJyb3IgPyBlcnJvci5tZXNzYWdlIDogJ3Vua25vd24gZXJyb3InfWBcbiAgICAgICAgfSk7XG4gICAgICAgIFxuICAgICAgICByZXR1cm4ge1xuICAgICAgICAgIGlzVmFsaWQ6IGZhbHNlLFxuICAgICAgICAgIGVycm9yOiB7XG4gICAgICAgICAgICBzdWNjZXNzOiBmYWxzZSxcbiAgICAgICAgICAgIG1lc3NhZ2U6ICdGaWxlIHBhdGggY291bGQgbm90IGJlIHNhZmVseSBwcm9jZXNzZWQnLFxuICAgICAgICAgICAgZXJyb3I6ICdQQVRIX05PUk1BTElaQVRJT05fRkFJTEVEJ1xuICAgICAgICAgIH1cbiAgICAgICAgfTtcbiAgICAgIH1cblxuICAgICAgLy8gVmFsaWRhdGUgZmlsZSBleHRlbnNpb24gKG9ubHkgYWxsb3cgc2FmZSBleHRlbnNpb25zIGZvciBwb3J0Zm9saW8gY29udGVudClcbiAgICAgIGNvbnN0IGFsbG93ZWRFeHRlbnNpb25zID0gWycubWQnLCAnLm1hcmtkb3duJywgJy50eHQnLCAnLnltbCcsICcueWFtbCcsICcuanNvbiddO1xuICAgICAgY29uc3QgZmlsZUV4dGVuc2lvbiA9IHBhdGguZXh0bmFtZShub3JtYWxpemVkUGF0aCkudG9Mb3dlckNhc2UoKTtcbiAgICAgIFxuICAgICAgaWYgKGZpbGVFeHRlbnNpb24gJiYgIWFsbG93ZWRFeHRlbnNpb25zLmluY2x1ZGVzKGZpbGVFeHRlbnNpb24pKSB7XG4gICAgICAgIFNlY3VyaXR5TW9uaXRvci5sb2dTZWN1cml0eUV2ZW50KHtcbiAgICAgICAgICB0eXBlOiAnQ09OVEVOVF9JTkpFQ1RJT05fQVRURU1QVCcsXG4gICAgICAgICAgc2V2ZXJpdHk6ICdNRURJVU0nLFxuICAgICAgICAgIHNvdXJjZTogJ1N1Ym1pdFRvUG9ydGZvbGlvVG9vbC52YWxpZGF0ZVBvcnRmb2xpb1BhdGgnLFxuICAgICAgICAgIGRldGFpbHM6IGBEaXNhbGxvd2VkIGZpbGUgZXh0ZW5zaW9uOiAke2ZpbGVFeHRlbnNpb259YCxcbiAgICAgICAgICBtZXRhZGF0YToge1xuICAgICAgICAgICAgYWxsb3dlZEV4dGVuc2lvbnM6IGFsbG93ZWRFeHRlbnNpb25zLmpvaW4oJywgJylcbiAgICAgICAgICB9XG4gICAgICAgIH0pO1xuICAgICAgICBcbiAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICBpc1ZhbGlkOiBmYWxzZSxcbiAgICAgICAgICBlcnJvcjoge1xuICAgICAgICAgICAgc3VjY2VzczogZmFsc2UsXG4gICAgICAgICAgICBtZXNzYWdlOiBgRmlsZSBleHRlbnNpb24gJyR7ZmlsZUV4dGVuc2lvbn0nIGlzIG5vdCBhbGxvd2VkLiBBbGxvd2VkIGV4dGVuc2lvbnM6ICR7YWxsb3dlZEV4dGVuc2lvbnMuam9pbignLCAnKX1gLFxuICAgICAgICAgICAgZXJyb3I6ICdJTlZBTElEX0ZJTEVfRVhURU5TSU9OJ1xuICAgICAgICAgIH1cbiAgICAgICAgfTtcbiAgICAgIH1cblxuICAgICAgLy8gVmFsaWRhdGUgZmlsZW5hbWUgY2hhcmFjdGVycyAob25seSBhbGxvdyBzYWZlIGNoYXJhY3RlcnMpXG4gICAgICBjb25zdCBiYXNlbmFtZSA9IHBhdGguYmFzZW5hbWUobm9ybWFsaXplZFBhdGgpO1xuICAgICAgY29uc3Qgc2FmZUZpbGVuYW1lUGF0dGVybiA9IC9eW2EtekEtWjAtOVxcLV8uXFxzKClbXFxde31dKyQvO1xuICAgICAgXG4gICAgICBpZiAoYmFzZW5hbWUgJiYgIXNhZmVGaWxlbmFtZVBhdHRlcm4udGVzdChiYXNlbmFtZSkpIHtcbiAgICAgICAgU2VjdXJpdHlNb25pdG9yLmxvZ1NlY3VyaXR5RXZlbnQoe1xuICAgICAgICAgIHR5cGU6ICdDT05URU5UX0lOSkVDVElPTl9BVFRFTVBUJyxcbiAgICAgICAgICBzZXZlcml0eTogJ01FRElVTScsXG4gICAgICAgICAgc291cmNlOiAnU3VibWl0VG9Qb3J0Zm9saW9Ub29sLnZhbGlkYXRlUG9ydGZvbGlvUGF0aCcsXG4gICAgICAgICAgZGV0YWlsczogJ0ZpbGVuYW1lIGNvbnRhaW5zIHBvdGVudGlhbGx5IGRhbmdlcm91cyBjaGFyYWN0ZXJzJyxcbiAgICAgICAgICBtZXRhZGF0YToge1xuICAgICAgICAgICAgZmlsZW5hbWU6IGJhc2VuYW1lLFxuICAgICAgICAgICAgYWxsb3dlZFBhdHRlcm46IHNhZmVGaWxlbmFtZVBhdHRlcm4uc291cmNlXG4gICAgICAgICAgfVxuICAgICAgICB9KTtcbiAgICAgICAgXG4gICAgICAgIHJldHVybiB7XG4gICAgICAgICAgaXNWYWxpZDogZmFsc2UsXG4gICAgICAgICAgZXJyb3I6IHtcbiAgICAgICAgICAgIHN1Y2Nlc3M6IGZhbHNlLFxuICAgICAgICAgICAgbWVzc2FnZTogJ0ZpbGVuYW1lIGNvbnRhaW5zIGludmFsaWQgY2hhcmFjdGVycy4gT25seSBsZXR0ZXJzLCBudW1iZXJzLCBzcGFjZXMsIGh5cGhlbnMsIHVuZGVyc2NvcmVzLCBkb3RzLCBhbmQgY29tbW9uIGJyYWNrZXRzIGFyZSBhbGxvd2VkLicsXG4gICAgICAgICAgICBlcnJvcjogJ0lOVkFMSURfRklMRU5BTUVfQ0hBUkFDVEVSUydcbiAgICAgICAgICB9XG4gICAgICAgIH07XG4gICAgICB9XG5cbiAgICAgIC8vIExvZyBzdWNjZXNzZnVsIHZhbGlkYXRpb25cbiAgICAgIFNlY3VyaXR5TW9uaXRvci5sb2dTZWN1cml0eUV2ZW50KHtcbiAgICAgICAgdHlwZTogJ0NPTlRFTlRfSU5KRUNUSU9OX0FUVEVNUFQnLFxuICAgICAgICBzZXZlcml0eTogJ0xPVycsXG4gICAgICAgIHNvdXJjZTogJ1N1Ym1pdFRvUG9ydGZvbGlvVG9vbC52YWxpZGF0ZVBvcnRmb2xpb1BhdGgnLFxuICAgICAgICBkZXRhaWxzOiAnRmlsZSBwYXRoIHZhbGlkYXRpb24gc3VjY2Vzc2Z1bCcsXG4gICAgICAgIG1ldGFkYXRhOiB7XG4gICAgICAgICAgb3JpZ2luYWxQYXRoTGVuZ3RoOiBmaWxlUGF0aC5sZW5ndGgsXG4gICAgICAgICAgbm9ybWFsaXplZFBhdGhMZW5ndGg6IG5vcm1hbGl6ZWRQYXRoLmxlbmd0aCxcbiAgICAgICAgICBmaWxlRXh0ZW5zaW9uOiBmaWxlRXh0ZW5zaW9uIHx8ICdub25lJ1xuICAgICAgICB9XG4gICAgICB9KTtcblxuICAgICAgcmV0dXJuIHtcbiAgICAgICAgaXNWYWxpZDogdHJ1ZSxcbiAgICAgICAgc2FmZVBhdGg6IG5vcm1hbGl6ZWRQYXRoXG4gICAgICB9O1xuXG4gICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgIFNlY3VyaXR5TW9uaXRvci5sb2dTZWN1cml0eUV2ZW50KHtcbiAgICAgICAgdHlwZTogJ1BBVEhfVFJBVkVSU0FMX0FUVEVNUFQnLFxuICAgICAgICBzZXZlcml0eTogJ0hJR0gnLFxuICAgICAgICBzb3VyY2U6ICdTdWJtaXRUb1BvcnRmb2xpb1Rvb2wudmFsaWRhdGVQb3J0Zm9saW9QYXRoJyxcbiAgICAgICAgZGV0YWlsczogYFBhdGggdmFsaWRhdGlvbiBlcnJvcjogJHtlcnJvciBpbnN0YW5jZW9mIEVycm9yID8gZXJyb3IubWVzc2FnZSA6ICd1bmtub3duIGVycm9yJ31gXG4gICAgICB9KTtcbiAgICAgIFxuICAgICAgcmV0dXJuIHtcbiAgICAgICAgaXNWYWxpZDogZmFsc2UsXG4gICAgICAgIGVycm9yOiB7XG4gICAgICAgICAgc3VjY2VzczogZmFsc2UsXG4gICAgICAgICAgbWVzc2FnZTogJ1VuYWJsZSB0byB2YWxpZGF0ZSBmaWxlIHBhdGguIFBsZWFzZSBjaGVjayB0aGUgZmlsZSBwYXRoIGFuZCB0cnkgYWdhaW4uJyxcbiAgICAgICAgICBlcnJvcjogJ1BBVEhfVkFMSURBVElPTl9FUlJPUidcbiAgICAgICAgfVxuICAgICAgfTtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogU21hcnQgdG9rZW4gbWFuYWdlbWVudCBmb3IgbG9uZyBvcGVyYXRpb25zIHdpdGggcmVmcmVzaC1saWtlIGNhcGFiaWxpdGllc1xuICAgKiBTRUNVUklUWSBFTkhBTkNFTUVOVCAoVGFzayAjMTQpOiBUb2tlbiByZWZyZXNoIGxvZ2ljIGZvciBsb25nIG9wZXJhdGlvbnNcbiAgICogXG4gICAqIE5vdGU6IEdpdEh1YiBPQXV0aCBkZXZpY2UgZmxvdyB0b2tlbnMgZG9uJ3QgaGF2ZSB0cmFkaXRpb25hbCByZWZyZXNoIHRva2VucyxcbiAgICogYnV0IHdlIGNhbiBpbXBsZW1lbnQgc21hcnQgdmFsaWRhdGlvbiBhbmQgZ3VpZGFuY2UgZm9yIGxvbmcgb3BlcmF0aW9uc1xuICAgKiBcbiAgICogQHBhcmFtIG9wZXJhdGlvblR5cGUgVHlwZSBvZiBvcGVyYXRpb24gYmVpbmcgcGVyZm9ybWVkXG4gICAqIEByZXR1cm5zIFRva2VuIG1hbmFnZW1lbnQgcmVzdWx0IHdpdGggcmVjb21tZW5kYXRpb25zXG4gICAqL1xuICBwcml2YXRlIGFzeW5jIG1hbmFnZVRva2VuRm9yTG9uZ09wZXJhdGlvbihvcGVyYXRpb25UeXBlOiAncG9ydGZvbGlvX2NyZWF0aW9uJyB8ICdjb2xsZWN0aW9uX3N1Ym1pc3Npb24nIHwgJ2ZpbGVfdXBsb2FkJyk6IFByb21pc2U8e1xuICAgIGNhblByb2NlZWQ6IGJvb2xlYW47XG4gICAgdG9rZW4/OiBzdHJpbmc7XG4gICAgcmVmcmVzaFJlY29tbWVuZGVkPzogYm9vbGVhbjtcbiAgICBlcnJvcj86IFN1Ym1pdFRvUG9ydGZvbGlvUmVzdWx0O1xuICB9PiB7XG4gICAgdHJ5IHtcbiAgICAgIC8vIEdldCBjdXJyZW50IHRva2VuXG4gICAgICBjb25zdCB0b2tlbiA9IGF3YWl0IFRva2VuTWFuYWdlci5nZXRHaXRIdWJUb2tlbkFzeW5jKCk7XG4gICAgICBpZiAoIXRva2VuKSB7XG4gICAgICAgIHJldHVybiB7XG4gICAgICAgICAgY2FuUHJvY2VlZDogZmFsc2UsXG4gICAgICAgICAgZXJyb3I6IHtcbiAgICAgICAgICAgIHN1Y2Nlc3M6IGZhbHNlLFxuICAgICAgICAgICAgbWVzc2FnZTogJ05vIEdpdEh1YiB0b2tlbiBhdmFpbGFibGUuIFBsZWFzZSBhdXRoZW50aWNhdGUgZmlyc3QuJyxcbiAgICAgICAgICAgIGVycm9yOiAnTk9fVE9LRU4nXG4gICAgICAgICAgfVxuICAgICAgICB9O1xuICAgICAgfVxuXG4gICAgICAvLyBWYWxpZGF0ZSB0b2tlbiBmb3IgdGhlIHNwZWNpZmljIG9wZXJhdGlvblxuICAgICAgY29uc3QgdmFsaWRhdGlvbiA9IGF3YWl0IHRoaXMudmFsaWRhdGVUb2tlbkJlZm9yZVVzYWdlKHRva2VuKTtcbiAgICAgIGlmICghdmFsaWRhdGlvbi5pc1ZhbGlkKSB7XG4gICAgICAgIHJldHVybiB7XG4gICAgICAgICAgY2FuUHJvY2VlZDogZmFsc2UsXG4gICAgICAgICAgZXJyb3I6IHZhbGlkYXRpb24uZXJyb3JcbiAgICAgICAgfTtcbiAgICAgIH1cblxuICAgICAgLy8gQ2hlY2sgaWYgdGhpcyBpcyBhIGxvbmcgb3BlcmF0aW9uIHRoYXQgbWlnaHQgYmVuZWZpdCBmcm9tIGZyZXNoIGF1dGhlbnRpY2F0aW9uXG4gICAgICBjb25zdCBsb25nT3BlcmF0aW9ucyA9IFsncG9ydGZvbGlvX2NyZWF0aW9uJywgJ2NvbGxlY3Rpb25fc3VibWlzc2lvbiddO1xuICAgICAgY29uc3QgaXNMb25nT3BlcmF0aW9uID0gbG9uZ09wZXJhdGlvbnMuaW5jbHVkZXMob3BlcmF0aW9uVHlwZSk7XG5cbiAgICAgIC8vIEdldCB0b2tlbiB0eXBlIHRvIGRldGVybWluZSByZWZyZXNoIGNhcGFiaWxpdGllc1xuICAgICAgY29uc3QgdG9rZW5UeXBlID0gVG9rZW5NYW5hZ2VyLmdldFRva2VuVHlwZSh0b2tlbik7XG4gICAgICBsZXQgcmVmcmVzaFJlY29tbWVuZGVkID0gZmFsc2U7XG5cbiAgICAgIC8vIEZvciBsb25nIG9wZXJhdGlvbnMsIGNoZWNrIHRva2VuIGFnZSBhbmQgcmVjb21tZW5kIHJlZnJlc2ggaWYgbmVlZGVkXG4gICAgICBpZiAoaXNMb25nT3BlcmF0aW9uICYmIHZhbGlkYXRpb24uaXNOZWFyRXhwaXJ5KSB7XG4gICAgICAgIHJlZnJlc2hSZWNvbW1lbmRlZCA9IHRydWU7XG4gICAgICAgIFxuICAgICAgICBTZWN1cml0eU1vbml0b3IubG9nU2VjdXJpdHlFdmVudCh7XG4gICAgICAgICAgdHlwZTogJ1RPS0VOX1ZBTElEQVRJT05fU1VDQ0VTUycsXG4gICAgICAgICAgc2V2ZXJpdHk6ICdMT1cnLFxuICAgICAgICAgIHNvdXJjZTogJ1N1Ym1pdFRvUG9ydGZvbGlvVG9vbC5tYW5hZ2VUb2tlbkZvckxvbmdPcGVyYXRpb24nLFxuICAgICAgICAgIGRldGFpbHM6ICdMb25nIG9wZXJhdGlvbiBkZXRlY3RlZCB3aXRoIGFnaW5nIHRva2VuIC0gcmVmcmVzaCByZWNvbW1lbmRlZCcsXG4gICAgICAgICAgbWV0YWRhdGE6IHtcbiAgICAgICAgICAgIG9wZXJhdGlvblR5cGUsXG4gICAgICAgICAgICB0b2tlblR5cGUsXG4gICAgICAgICAgICByZWZyZXNoUmVjb21tZW5kZWQ6IHRydWVcbiAgICAgICAgICB9XG4gICAgICAgIH0pO1xuXG4gICAgICAgIGxvZ2dlci53YXJuKCdMb25nIG9wZXJhdGlvbiB3aXRoIHBvdGVudGlhbGx5IGFnaW5nIHRva2VuIGRldGVjdGVkJywge1xuICAgICAgICAgIG9wZXJhdGlvblR5cGUsXG4gICAgICAgICAgdG9rZW5UeXBlLFxuICAgICAgICAgIHJlY29tbWVuZGF0aW9uOiAnQ29uc2lkZXIgcmUtYXV0aGVudGljYXRpbmcgaWYgb3BlcmF0aW9uIGZhaWxzJ1xuICAgICAgICB9KTtcbiAgICAgIH1cblxuICAgICAgLy8gRm9yIE9BdXRoIHRva2VucyBpbiBsb25nIG9wZXJhdGlvbnMsIHdlIGNhbiBwcm92aWRlIGd1aWRhbmNlXG4gICAgICBpZiAodG9rZW5UeXBlID09PSAnT0F1dGggQWNjZXNzIFRva2VuJyAmJiBpc0xvbmdPcGVyYXRpb24pIHtcbiAgICAgICAgbG9nZ2VyLmluZm8oJ09BdXRoIHRva2VuIGRldGVjdGVkIGZvciBsb25nIG9wZXJhdGlvbicsIHtcbiAgICAgICAgICBvcGVyYXRpb25UeXBlLFxuICAgICAgICAgIHRva2VuVHlwZSxcbiAgICAgICAgICBndWlkYW5jZTogJ09BdXRoIHRva2VucyBhcmUgdGltZS1saW1pdGVkLiBJZiBvcGVyYXRpb24gZmFpbHMsIHJlLWF1dGhlbnRpY2F0ZSB1c2luZyBzZXR1cF9naXRodWJfYXV0aCdcbiAgICAgICAgfSk7XG4gICAgICB9XG5cbiAgICAgIC8vIExvZyBzdWNjZXNzZnVsIHRva2VuIG1hbmFnZW1lbnRcbiAgICAgIFNlY3VyaXR5TW9uaXRvci5sb2dTZWN1cml0eUV2ZW50KHtcbiAgICAgICAgdHlwZTogJ1RPS0VOX1ZBTElEQVRJT05fU1VDQ0VTUycsXG4gICAgICAgIHNldmVyaXR5OiAnTE9XJyxcbiAgICAgICAgc291cmNlOiAnU3VibWl0VG9Qb3J0Zm9saW9Ub29sLm1hbmFnZVRva2VuRm9yTG9uZ09wZXJhdGlvbicsXG4gICAgICAgIGRldGFpbHM6ICdUb2tlbiBtYW5hZ2VtZW50IHN1Y2Nlc3NmdWwgZm9yIGxvbmcgb3BlcmF0aW9uJyxcbiAgICAgICAgbWV0YWRhdGE6IHtcbiAgICAgICAgICBvcGVyYXRpb25UeXBlLFxuICAgICAgICAgIHRva2VuVHlwZSxcbiAgICAgICAgICBpc0xvbmdPcGVyYXRpb24sXG4gICAgICAgICAgcmVmcmVzaFJlY29tbWVuZGVkXG4gICAgICAgIH1cbiAgICAgIH0pO1xuXG4gICAgICByZXR1cm4ge1xuICAgICAgICBjYW5Qcm9jZWVkOiB0cnVlLFxuICAgICAgICB0b2tlbixcbiAgICAgICAgcmVmcmVzaFJlY29tbWVuZGVkXG4gICAgICB9O1xuXG4gICAgfSBjYXRjaCAoZXJyb3I6IGFueSkge1xuICAgICAgU2VjdXJpdHlNb25pdG9yLmxvZ1NlY3VyaXR5RXZlbnQoe1xuICAgICAgICB0eXBlOiAnVE9LRU5fVkFMSURBVElPTl9GQUlMVVJFJyxcbiAgICAgICAgc2V2ZXJpdHk6ICdNRURJVU0nLFxuICAgICAgICBzb3VyY2U6ICdTdWJtaXRUb1BvcnRmb2xpb1Rvb2wubWFuYWdlVG9rZW5Gb3JMb25nT3BlcmF0aW9uJyxcbiAgICAgICAgZGV0YWlsczogYFRva2VuIG1hbmFnZW1lbnQgZXJyb3I6ICR7ZXJyb3IubWVzc2FnZSB8fCAndW5rbm93biBlcnJvcid9YFxuICAgICAgfSk7XG5cbiAgICAgIHJldHVybiB7XG4gICAgICAgIGNhblByb2NlZWQ6IGZhbHNlLFxuICAgICAgICBlcnJvcjoge1xuICAgICAgICAgIHN1Y2Nlc3M6IGZhbHNlLFxuICAgICAgICAgIG1lc3NhZ2U6ICdVbmFibGUgdG8gbWFuYWdlIHRva2VuIGZvciBvcGVyYXRpb24uIFBsZWFzZSBjaGVjayB5b3VyIGF1dGhlbnRpY2F0aW9uIGFuZCB0cnkgYWdhaW4uJyxcbiAgICAgICAgICBlcnJvcjogJ1RPS0VOX01BTkFHRU1FTlRfRVJST1InXG4gICAgICAgIH1cbiAgICAgIH07XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIFByb3ZpZGVzIHVzZXIgZ3VpZGFuY2UgZm9yIHRva2VuIHJlZnJlc2ggd2hlbiBvcGVyYXRpb25zIGZhaWwgZHVlIHRvIHRva2VuIGlzc3Vlc1xuICAgKiBTRUNVUklUWSBFTkhBTkNFTUVOVCAoVGFzayAjMTQpOiBVc2VyIGd1aWRhbmNlIGZvciBhdXRoZW50aWNhdGlvbiByZWZyZXNoXG4gICAqL1xuICBwcml2YXRlIGZvcm1hdFRva2VuUmVmcmVzaEd1aWRhbmNlKG9wZXJhdGlvblR5cGU6IHN0cmluZywgdG9rZW5UeXBlOiBzdHJpbmcpOiBzdHJpbmcge1xuICAgIGxldCBndWlkYW5jZSA9ICdcXG5cXG7wn5SEICoqVG9rZW4gUmVmcmVzaCBHdWlkYW5jZSoqOlxcbic7XG4gICAgXG4gICAgaWYgKHRva2VuVHlwZSA9PT0gJ09BdXRoIEFjY2VzcyBUb2tlbicpIHtcbiAgICAgIGd1aWRhbmNlICs9ICfigKIgWW91ciBPQXV0aCB0b2tlbiBtYXkgaGF2ZSBleHBpcmVkXFxuJztcbiAgICAgIGd1aWRhbmNlICs9ICfigKIgUnVuIGBzZXR1cF9naXRodWJfYXV0aGAgdG8gYXV0aGVudGljYXRlIGFnYWluXFxuJztcbiAgICAgIGd1aWRhbmNlICs9ICfigKIgVGhpcyB3aWxsIGdlbmVyYXRlIGEgZnJlc2ggdG9rZW4gZm9yIGNvbnRpbnVlZCBhY2Nlc3NcXG4nO1xuICAgIH0gZWxzZSBpZiAodG9rZW5UeXBlID09PSAnUGVyc29uYWwgQWNjZXNzIFRva2VuJykge1xuICAgICAgZ3VpZGFuY2UgKz0gJ+KAoiBZb3VyIFBlcnNvbmFsIEFjY2VzcyBUb2tlbiBtYXkgaGF2ZSBleHBpcmVkXFxuJztcbiAgICAgIGd1aWRhbmNlICs9ICfigKIgQ2hlY2sgeW91ciBHaXRIdWIgc2V0dGluZ3M6IGh0dHBzOi8vZ2l0aHViLmNvbS9zZXR0aW5ncy90b2tlbnNcXG4nO1xuICAgICAgZ3VpZGFuY2UgKz0gJ+KAoiBHZW5lcmF0ZSBhIG5ldyB0b2tlbiBpZiBuZWVkZWQgYW5kIHVwZGF0ZSBHSVRIVUJfVE9LRU4gZW52aXJvbm1lbnQgdmFyaWFibGVcXG4nO1xuICAgIH0gZWxzZSB7XG4gICAgICBndWlkYW5jZSArPSAn4oCiIFlvdXIgR2l0SHViIHRva2VuIG1heSBoYXZlIGV4cGlyZWQgb3IgYmVlbiByZXZva2VkXFxuJztcbiAgICAgIGd1aWRhbmNlICs9ICfigKIgUmUtYXV0aGVudGljYXRlIHVzaW5nIGBzZXR1cF9naXRodWJfYXV0aGBcXG4nO1xuICAgICAgZ3VpZGFuY2UgKz0gJ+KAoiBFbnN1cmUgeW91ciB0b2tlbiBoYXMgdGhlIHJlcXVpcmVkIHBlcm1pc3Npb25zXFxuJztcbiAgICB9XG5cbiAgICBndWlkYW5jZSArPSBgXFxuKipPcGVyYXRpb24qKjogJHtvcGVyYXRpb25UeXBlfVxcbmA7XG4gICAgZ3VpZGFuY2UgKz0gJyoqUmVxdWlyZWQgc2NvcGVzKio6IHJlcG8sIHVzZXI6ZW1haWxcXG5cXG4nO1xuICAgIGd1aWRhbmNlICs9ICfwn5KhICoqVGlwKio6IEZyZXNoIHRva2VucyB3b3JrIGJldHRlciBmb3IgY29tcGxleCBvcGVyYXRpb25zIGxpa2UgcG9ydGZvbGlvIGNyZWF0aW9uLic7XG5cbiAgICByZXR1cm4gZ3VpZGFuY2U7XG4gIH1cblxuICAvKipcbiAgICogU2V0cyB1cCBHaXRIdWIgcmVwb3NpdG9yeSBhY2Nlc3MgYW5kIGVuc3VyZXMgcG9ydGZvbGlvIHJlcG9zaXRvcnkgZXhpc3RzXG4gICAqIEBwYXJhbSBhdXRoU3RhdHVzIEF1dGhlbnRpY2F0aW9uIHN0YXR1cyBjb250YWluaW5nIHVzZXJuYW1lXG4gICAqIEByZXR1cm5zIFNldHVwIHJlc3VsdCBvciBlcnJvciByZXNwb25zZVxuICAgKi9cbiAgcHJpdmF0ZSBhc3luYyBzZXR1cEdpdEh1YlJlcG9zaXRvcnkoYXV0aFN0YXR1czogYW55KTogUHJvbWlzZTx7XG4gICAgc3VjY2VzczogYm9vbGVhbjtcbiAgICBlcnJvcj86IFN1Ym1pdFRvUG9ydGZvbGlvUmVzdWx0O1xuICB9PiB7XG4gICAgLy8gU0VDVVJJVFkgRU5IQU5DRU1FTlQgKFRhc2sgIzE0KTogU21hcnQgdG9rZW4gbWFuYWdlbWVudCBmb3IgbG9uZyBvcGVyYXRpb25zXG4gICAgY29uc3QgdG9rZW5NYW5hZ2VtZW50ID0gYXdhaXQgdGhpcy5tYW5hZ2VUb2tlbkZvckxvbmdPcGVyYXRpb24oJ3BvcnRmb2xpb19jcmVhdGlvbicpO1xuICAgIGlmICghdG9rZW5NYW5hZ2VtZW50LmNhblByb2NlZWQpIHtcbiAgICAgIHJldHVybiB7XG4gICAgICAgIHN1Y2Nlc3M6IGZhbHNlLFxuICAgICAgICBlcnJvcjogdG9rZW5NYW5hZ2VtZW50LmVycm9yXG4gICAgICB9O1xuICAgIH1cblxuICAgIGNvbnN0IHRva2VuID0gdG9rZW5NYW5hZ2VtZW50LnRva2VuITtcblxuICAgIC8vIFByb3ZpZGUgdXNlciBndWlkYW5jZSBpZiByZWZyZXNoIGlzIHJlY29tbWVuZGVkIGZvciB0aGlzIGxvbmcgb3BlcmF0aW9uXG4gICAgaWYgKHRva2VuTWFuYWdlbWVudC5yZWZyZXNoUmVjb21tZW5kZWQpIHtcbiAgICAgIGNvbnN0IHRva2VuVHlwZSA9IFRva2VuTWFuYWdlci5nZXRUb2tlblR5cGUodG9rZW4pO1xuICAgICAgY29uc3QgZ3VpZGFuY2UgPSB0aGlzLmZvcm1hdFRva2VuUmVmcmVzaEd1aWRhbmNlKCdwb3J0Zm9saW8gY3JlYXRpb24nLCB0b2tlblR5cGUpO1xuICAgICAgbG9nZ2VyLndhcm4oYFRva2VuIHJlZnJlc2ggcmVjb21tZW5kZWQgZm9yIHBvcnRmb2xpbyBjcmVhdGlvbjoke2d1aWRhbmNlfWApO1xuICAgIH1cblxuICAgIHRoaXMucG9ydGZvbGlvTWFuYWdlci5zZXRUb2tlbih0b2tlbik7XG5cbiAgICAvLyBDaGVjayBpZiBwb3J0Zm9saW8gZXhpc3RzIGFuZCBjcmVhdGUgaWYgbmVlZGVkXG4gICAgY29uc3QgdXNlcm5hbWUgPSBhdXRoU3RhdHVzLnVzZXJuYW1lIHx8ICd1bmtub3duJztcbiAgICBjb25zdCBwb3J0Zm9saW9FeGlzdHMgPSBhd2FpdCB0aGlzLnBvcnRmb2xpb01hbmFnZXIuY2hlY2tQb3J0Zm9saW9FeGlzdHModXNlcm5hbWUpO1xuICAgIFxuICAgIGlmICghcG9ydGZvbGlvRXhpc3RzKSB7XG4gICAgICBsb2dnZXIuaW5mbygnQ3JlYXRpbmcgcG9ydGZvbGlvIHJlcG9zaXRvcnkuLi4nKTtcbiAgICAgIC8vIFJlcXVlc3QgY29uc2VudCBmb3IgcG9ydGZvbGlvIGNyZWF0aW9uXG4gICAgICBjb25zdCByZXBvVXJsID0gYXdhaXQgdGhpcy5wb3J0Zm9saW9NYW5hZ2VyLmNyZWF0ZVBvcnRmb2xpbyh1c2VybmFtZSwgdHJ1ZSk7XG4gICAgICBpZiAoIXJlcG9VcmwpIHtcbiAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICBzdWNjZXNzOiBmYWxzZSxcbiAgICAgICAgICBlcnJvcjoge1xuICAgICAgICAgICAgc3VjY2VzczogZmFsc2UsXG4gICAgICAgICAgICBtZXNzYWdlOiAnRmFpbGVkIHRvIGNyZWF0ZSBwb3J0Zm9saW8gcmVwb3NpdG9yeScsXG4gICAgICAgICAgICBlcnJvcjogJ0NSRUFURV9GQUlMRUQnXG4gICAgICAgICAgfVxuICAgICAgICB9O1xuICAgICAgfVxuICAgIH1cblxuICAgIHJldHVybiB7IHN1Y2Nlc3M6IHRydWUgfTtcbiAgfVxuXG4gIC8qKlxuICAgKiBTdWJtaXRzIGVsZW1lbnQgdG8gcG9ydGZvbGlvIGFuZCBoYW5kbGVzIHRoZSBjb21wbGV0ZSByZXNwb25zZSB3b3JrZmxvd1xuICAgKiBAcGFyYW0gc2FmZU5hbWUgVGhlIG5vcm1hbGl6ZWQgbmFtZSBvZiB0aGUgZWxlbWVudFxuICAgKiBAcGFyYW0gZWxlbWVudFR5cGUgVGhlIHR5cGUgb2YgdGhlIGVsZW1lbnRcbiAgICogQHBhcmFtIG1ldGFkYXRhIFRoZSBtZXRhZGF0YSBmb3IgdGhlIGVsZW1lbnRcbiAgICogQHBhcmFtIGNvbnRlbnQgVGhlIGNvbnRlbnQgb2YgdGhlIGVsZW1lbnRcbiAgICogQHBhcmFtIGF1dGhTdGF0dXMgQXV0aGVudGljYXRpb24gc3RhdHVzIGNvbnRhaW5pbmcgdXNlcm5hbWUgYW5kIHRva2VuXG4gICAqIEByZXR1cm5zIENvbXBsZXRlIHN1Ym1pc3Npb24gcmVzdWx0IHdpdGggc3VjY2VzcyBtZXNzYWdlIG9yIGVycm9yXG4gICAqL1xuICBwcml2YXRlIGFzeW5jIHN1Ym1pdEVsZW1lbnRBbmRIYW5kbGVSZXNwb25zZShcbiAgICBzYWZlTmFtZTogc3RyaW5nLFxuICAgIGVsZW1lbnRUeXBlOiBFbGVtZW50VHlwZSxcbiAgICBtZXRhZGF0YTogUG9ydGZvbGlvRWxlbWVudE1ldGFkYXRhLFxuICAgIGNvbnRlbnQ6IHN0cmluZyxcbiAgICBhdXRoU3RhdHVzOiBhbnlcbiAgKTogUHJvbWlzZTxTdWJtaXRUb1BvcnRmb2xpb1Jlc3VsdD4ge1xuICAgIC8vIENyZWF0ZSBlbGVtZW50IHN0cnVjdHVyZSB0byBzYXZlXG4gICAgY29uc3QgZWxlbWVudDogUG9ydGZvbGlvRWxlbWVudCA9IHtcbiAgICAgIHR5cGU6IGVsZW1lbnRUeXBlLFxuICAgICAgbWV0YWRhdGEsXG4gICAgICBjb250ZW50XG4gICAgfTtcbiAgICBcbiAgICAvLyBUWVBFIFNBRkVUWSBGSVggIzI6IFVzZSBhZGFwdGVyIHBhdHRlcm4gaW5zdGVhZCBvZiBjb21wbGV4IHR5cGUgY2FzdGluZ1xuICAgIC8vIFByZXZpb3VzbHk6IGVsZW1lbnQgYXMgdW5rbm93biBhcyBQYXJhbWV0ZXJzPHR5cGVvZiB0aGlzLnBvcnRmb2xpb01hbmFnZXIuc2F2ZUVsZW1lbnQ+WzBdXG4gICAgLy8gTm93OiBDbGVhbiBhZGFwdGVyIHBhdHRlcm4gdGhhdCBpbXBsZW1lbnRzIElFbGVtZW50IGludGVyZmFjZSBwcm9wZXJseVxuICAgIGNvbnN0IGFkYXB0ZXIgPSBuZXcgUG9ydGZvbGlvRWxlbWVudEFkYXB0ZXIoZWxlbWVudCk7XG4gICAgXG4gICAgLy8gVVggSU1QUk9WRU1FTlQ6IEFkZCByZXRyeSBsb2dpYyBmb3IgdHJhbnNpZW50IGZhaWx1cmVzXG4gICAgY29uc3QgZmlsZVVybCA9IGF3YWl0IHRoaXMuc2F2ZUVsZW1lbnRXaXRoUmV0cnkoYWRhcHRlciwgc2FmZU5hbWUsIGVsZW1lbnRUeXBlKTtcbiAgICBcbiAgICBpZiAoIWZpbGVVcmwpIHtcbiAgICAgIHJldHVybiB7XG4gICAgICAgIHN1Y2Nlc3M6IGZhbHNlLFxuICAgICAgICBtZXNzYWdlOiAnRmFpbGVkIHRvIHNhdmUgZWxlbWVudCB0byBHaXRIdWIgcG9ydGZvbGlvIGFmdGVyIG11bHRpcGxlIGF0dGVtcHRzLlxcblxcbicgK1xuICAgICAgICAgICAgICAgICfwn5KhICoqVHJvdWJsZXNob290aW5nIFRpcHMqKjpcXG4nICtcbiAgICAgICAgICAgICAgICAn4oCiIENoZWNrIHlvdXIgR2l0SHViIGF1dGhlbnRpY2F0aW9uOiBgZ2ggYXV0aCBzdGF0dXNgXFxuJyArXG4gICAgICAgICAgICAgICAgJ+KAoiBWZXJpZnkgcmVwb3NpdG9yeSBwZXJtaXNzaW9uc1xcbicgK1xuICAgICAgICAgICAgICAgICfigKIgVHJ5IGFnYWluIGluIGEgZmV3IG1pbnV0ZXMgKEdpdEh1YiBBUEkgcmF0ZSBsaW1pdHMpXFxuJyArXG4gICAgICAgICAgICAgICAgJ+KAoiBDaGVjayBHaXRIdWIgc3RhdHVzOiBodHRwczovL3N0YXR1cy5naXRodWIuY29tJyxcbiAgICAgICAgZXJyb3I6ICdTQVZFX0ZBSUxFRCdcbiAgICAgIH07XG4gICAgfVxuXG4gICAgLy8gTG9nIHN1Y2Nlc3NmdWwgc3VibWlzc2lvbiAoRE1DUC1TRUMtMDA2KVxuICAgIGxvZ2dlci5pbmZvKGBTdWNjZXNzZnVsbHkgc3VibWl0dGVkICR7c2FmZU5hbWV9IHRvIEdpdEh1YiBwb3J0Zm9saW9gLCB7XG4gICAgICBlbGVtZW50VHlwZSxcbiAgICAgIHVzZXJuYW1lOiBhdXRoU3RhdHVzLnVzZXJuYW1lLFxuICAgICAgZmlsZVVybFxuICAgIH0pO1xuXG4gICAgLy8gU0VDVVJJVFkgRU5IQU5DRU1FTlQgKFRhc2sgIzE0KTogU21hcnQgdG9rZW4gbWFuYWdlbWVudCBmb3IgY29sbGVjdGlvbiBzdWJtaXNzaW9uXG4gICAgY29uc3QgY29sbGVjdGlvblRva2VuTWFuYWdlbWVudCA9IGF3YWl0IHRoaXMubWFuYWdlVG9rZW5Gb3JMb25nT3BlcmF0aW9uKCdjb2xsZWN0aW9uX3N1Ym1pc3Npb24nKTtcbiAgICBpZiAoIWNvbGxlY3Rpb25Ub2tlbk1hbmFnZW1lbnQuY2FuUHJvY2VlZCkge1xuICAgICAgLy8gVG9rZW4gbWFuYWdlbWVudCBmYWlsZWQgZm9yIGNvbGxlY3Rpb24gc3VibWlzc2lvbiwgYnV0IG1haW4gc3VibWlzc2lvbiBzdWNjZWVkZWRcbiAgICAgIGNvbnN0IGVycm9yTWVzc2FnZSA9IGNvbGxlY3Rpb25Ub2tlbk1hbmFnZW1lbnQuZXJyb3I/Lm1lc3NhZ2UgfHwgJ1Rva2VuIG1hbmFnZW1lbnQgZmFpbGVkJztcbiAgICAgIHJldHVybiB7XG4gICAgICAgIHN1Y2Nlc3M6IHRydWUsXG4gICAgICAgIG1lc3NhZ2U6IGDinIUgU3VjY2Vzc2Z1bGx5IHVwbG9hZGVkICR7c2FmZU5hbWV9IHRvIHlvdXIgR2l0SHViIHBvcnRmb2xpbyFcXG7wn5OBIFBvcnRmb2xpbyBVUkw6ICR7ZmlsZVVybH1cXG5cXG7imqDvuI8gQ29sbGVjdGlvbiBzdWJtaXNzaW9uIHNraXBwZWQ6ICR7ZXJyb3JNZXNzYWdlfWAsXG4gICAgICAgIHVybDogZmlsZVVybFxuICAgICAgfTtcbiAgICB9XG5cbiAgICBjb25zdCB0b2tlbiA9IGNvbGxlY3Rpb25Ub2tlbk1hbmFnZW1lbnQudG9rZW4hO1xuXG4gICAgLy8gUHJvdmlkZSByZWZyZXNoIGd1aWRhbmNlIGlmIHJlY29tbWVuZGVkIGZvciBjb2xsZWN0aW9uIHN1Ym1pc3Npb25cbiAgICBpZiAoY29sbGVjdGlvblRva2VuTWFuYWdlbWVudC5yZWZyZXNoUmVjb21tZW5kZWQpIHtcbiAgICAgIGNvbnN0IHRva2VuVHlwZSA9IFRva2VuTWFuYWdlci5nZXRUb2tlblR5cGUodG9rZW4pO1xuICAgICAgbG9nZ2VyLmluZm8oJ0NvbGxlY3Rpb24gc3VibWlzc2lvbiBwcm9jZWVkaW5nIHdpdGggYWdpbmcgdG9rZW4nLCB7XG4gICAgICAgIHRva2VuVHlwZSxcbiAgICAgICAgcmVjb21tZW5kYXRpb246ICdJZiBjb2xsZWN0aW9uIHN1Ym1pc3Npb24gZmFpbHMsIHRyeSByZS1hdXRoZW50aWNhdGluZyB3aXRoIHNldHVwX2dpdGh1Yl9hdXRoJ1xuICAgICAgfSk7XG4gICAgfVxuXG4gICAgLy8gRU5IQU5DRU1FTlQgKElzc3VlICM1NDkpOiBBc2sgdXNlciBpZiB0aGV5IHdhbnQgdG8gc3VibWl0IHRvIGNvbGxlY3Rpb25cbiAgICAvLyBUaGlzIGNvbXBsZXRlcyB0aGUgY29tbXVuaXR5IGNvbnRyaWJ1dGlvbiB3b3JrZmxvd1xuICAgIGNvbnN0IGNvbGxlY3Rpb25TdWJtaXNzaW9uUmVzdWx0ID0gYXdhaXQgdGhpcy5wcm9tcHRGb3JDb2xsZWN0aW9uU3VibWlzc2lvbih7XG4gICAgICBlbGVtZW50TmFtZTogc2FmZU5hbWUsXG4gICAgICBlbGVtZW50VHlwZSxcbiAgICAgIHBvcnRmb2xpb1VybDogZmlsZVVybCxcbiAgICAgIHVzZXJuYW1lOiBhdXRoU3RhdHVzLnVzZXJuYW1lIHx8ICd1bmtub3duJyxcbiAgICAgIG1ldGFkYXRhLFxuICAgICAgdG9rZW5cbiAgICB9KTtcblxuICAgIC8vIEJ1aWxkIHRoZSByZXNwb25zZSBtZXNzYWdlIGJhc2VkIG9uIHdoYXQgaGFwcGVuZWRcbiAgICBsZXQgbWVzc2FnZSA9IGDinIUgU3VjY2Vzc2Z1bGx5IHVwbG9hZGVkICR7c2FmZU5hbWV9IHRvIHlvdXIgR2l0SHViIHBvcnRmb2xpbyFcXG5gO1xuICAgIG1lc3NhZ2UgKz0gYPCfk4EgUG9ydGZvbGlvIFVSTDogJHtmaWxlVXJsfVxcblxcbmA7XG4gICAgXG4gICAgaWYgKGNvbGxlY3Rpb25TdWJtaXNzaW9uUmVzdWx0LnN1Ym1pdHRlZCkge1xuICAgICAgbWVzc2FnZSArPSBg8J+OiSBBbHNvIHN1Ym1pdHRlZCB0byBEb2xsaG91c2VNQ1AgY29sbGVjdGlvbiBmb3IgY29tbXVuaXR5IHJldmlldyFcXG5gO1xuICAgICAgbWVzc2FnZSArPSBg8J+TiyBJc3N1ZTogJHtjb2xsZWN0aW9uU3VibWlzc2lvblJlc3VsdC5pc3N1ZVVybH1gO1xuICAgIH0gZWxzZSBpZiAoY29sbGVjdGlvblN1Ym1pc3Npb25SZXN1bHQuZGVjbGluZWQpIHtcbiAgICAgIG1lc3NhZ2UgKz0gYPCfkqEgWW91IGNhbiBzdWJtaXQgdG8gdGhlIGNvbGxlY3Rpb24gbGF0ZXIgdXNpbmcgdGhlIHNhbWUgY29tbWFuZC5gO1xuICAgIH0gZWxzZSBpZiAoY29sbGVjdGlvblN1Ym1pc3Npb25SZXN1bHQuZXJyb3IpIHtcbiAgICAgIG1lc3NhZ2UgKz0gYOKaoO+4jyBDb2xsZWN0aW9uIHN1Ym1pc3Npb24gZmFpbGVkOiAke2NvbGxlY3Rpb25TdWJtaXNzaW9uUmVzdWx0LmVycm9yfVxcbmA7XG4gICAgICBtZXNzYWdlICs9IGDwn5KhIFlvdSBjYW4gbWFudWFsbHkgc3VibWl0IGF0OiBodHRwczovL2dpdGh1Yi5jb20vRG9sbGhvdXNlTUNQL2NvbGxlY3Rpb24vaXNzdWVzL25ld2A7XG4gICAgfVxuXG4gICAgcmV0dXJuIHtcbiAgICAgIHN1Y2Nlc3M6IHRydWUsXG4gICAgICBtZXNzYWdlLFxuICAgICAgdXJsOiBmaWxlVXJsXG4gICAgfTtcbiAgfVxuXG4gIGFzeW5jIGV4ZWN1dGUocGFyYW1zOiBTdWJtaXRUb1BvcnRmb2xpb1BhcmFtcyk6IFByb21pc2U8U3VibWl0VG9Qb3J0Zm9saW9SZXN1bHQ+IHtcbiAgICB0cnkge1xuICAgICAgLy8gVmFsaWRhdGUgYW5kIG5vcm1hbGl6ZSBpbnB1dCBwYXJhbWV0ZXJzXG4gICAgICBjb25zdCB2YWxpZGF0aW9uUmVzdWx0ID0gYXdhaXQgdGhpcy52YWxpZGF0ZUFuZE5vcm1hbGl6ZVBhcmFtcyhwYXJhbXMpO1xuICAgICAgaWYgKCF2YWxpZGF0aW9uUmVzdWx0LnN1Y2Nlc3MpIHtcbiAgICAgICAgcmV0dXJuIHZhbGlkYXRpb25SZXN1bHQuZXJyb3IhO1xuICAgICAgfVxuICAgICAgY29uc3Qgc2FmZU5hbWUgPSB2YWxpZGF0aW9uUmVzdWx0LnNhZmVOYW1lITtcblxuICAgICAgLy8gQ2hlY2sgYXV0aGVudGljYXRpb24gc3RhdHVzXG4gICAgICBjb25zdCBhdXRoUmVzdWx0ID0gYXdhaXQgdGhpcy5jaGVja0F1dGhlbnRpY2F0aW9uKCk7XG4gICAgICBpZiAoIWF1dGhSZXN1bHQuc3VjY2Vzcykge1xuICAgICAgICByZXR1cm4gYXV0aFJlc3VsdC5lcnJvciE7XG4gICAgICB9XG4gICAgICBjb25zdCBhdXRoU3RhdHVzID0gYXV0aFJlc3VsdC5hdXRoU3RhdHVzITtcblxuICAgICAgLy8gRmluZCBjb250ZW50IGxvY2FsbHkgd2l0aCBzbWFydCB0eXBlIGRldGVjdGlvblxuICAgICAgY29uc3QgY29udGVudFJlc3VsdCA9IGF3YWl0IHRoaXMuZGlzY292ZXJDb250ZW50V2l0aFR5cGVEZXRlY3Rpb24oc2FmZU5hbWUhLCBwYXJhbXMudHlwZSwgcGFyYW1zLm5hbWUpO1xuICAgICAgaWYgKCFjb250ZW50UmVzdWx0LnN1Y2Nlc3MpIHtcbiAgICAgICAgcmV0dXJuIGNvbnRlbnRSZXN1bHQuZXJyb3IhO1xuICAgICAgfVxuICAgICAgY29uc3QgZWxlbWVudFR5cGUgPSBjb250ZW50UmVzdWx0LmVsZW1lbnRUeXBlITtcbiAgICAgIGNvbnN0IGxvY2FsUGF0aCA9IGNvbnRlbnRSZXN1bHQubG9jYWxQYXRoITtcblxuICAgICAgLy8gVmFsaWRhdGUgZmlsZSBhbmQgY29udGVudCBzZWN1cml0eVxuICAgICAgY29uc3Qgc2VjdXJpdHlSZXN1bHQgPSBhd2FpdCB0aGlzLnZhbGlkYXRlRmlsZUFuZENvbnRlbnQobG9jYWxQYXRoKTtcbiAgICAgIGlmICghc2VjdXJpdHlSZXN1bHQuc3VjY2Vzcykge1xuICAgICAgICByZXR1cm4gc2VjdXJpdHlSZXN1bHQuZXJyb3IhO1xuICAgICAgfVxuICAgICAgY29uc3QgY29udGVudCA9IHNlY3VyaXR5UmVzdWx0LmNvbnRlbnQhO1xuXG4gICAgICAvLyBHZXQgdXNlciBjb25zZW50IChwbGFjZWhvbGRlciBmb3Igbm93IC0gY291bGQgYWRkIGludGVyYWN0aXZlIHByb21wdCBsYXRlcilcbiAgICAgIGxvZ2dlci5pbmZvKGBQcmVwYXJpbmcgdG8gc3VibWl0ICR7c2FmZU5hbWV9IHRvIEdpdEh1YiBwb3J0Zm9saW9gKTtcblxuICAgICAgLy8gUHJlcGFyZSBtZXRhZGF0YSBmb3IgZWxlbWVudFxuICAgICAgY29uc3QgbWV0YWRhdGEgPSB0aGlzLnByZXBhcmVFbGVtZW50TWV0YWRhdGEoc2FmZU5hbWUhLCBlbGVtZW50VHlwZSwgYXV0aFN0YXR1cyk7XG5cbiAgICAgIC8vIFNldCB1cCBHaXRIdWIgcmVwb3NpdG9yeSBhY2Nlc3NcbiAgICAgIGNvbnN0IHJlcG9SZXN1bHQgPSBhd2FpdCB0aGlzLnNldHVwR2l0SHViUmVwb3NpdG9yeShhdXRoU3RhdHVzKTtcbiAgICAgIGlmICghcmVwb1Jlc3VsdC5zdWNjZXNzKSB7XG4gICAgICAgIHJldHVybiByZXBvUmVzdWx0LmVycm9yITtcbiAgICAgIH1cblxuICAgICAgLy8gU3VibWl0IGVsZW1lbnQgdG8gcG9ydGZvbGlvIGFuZCBoYW5kbGUgY29sbGVjdGlvbiBzdWJtaXNzaW9uXG4gICAgICByZXR1cm4gYXdhaXQgdGhpcy5zdWJtaXRFbGVtZW50QW5kSGFuZGxlUmVzcG9uc2UoXG4gICAgICAgIHNhZmVOYW1lISwgXG4gICAgICAgIGVsZW1lbnRUeXBlLCBcbiAgICAgICAgbWV0YWRhdGEsIFxuICAgICAgICBjb250ZW50LCBcbiAgICAgICAgYXV0aFN0YXR1c1xuICAgICAgKTtcblxuICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICAvLyBTRUNVUklUWSBFTkhBTkNFTUVOVCAoVGFzayAjMTQpOiBFbmhhbmNlZCBlcnJvciBoYW5kbGluZyB3aXRoIHRva2VuIHJlZnJlc2ggZ3VpZGFuY2VcbiAgICAgIEVycm9ySGFuZGxlci5sb2dFcnJvcignc3VibWl0VG9Qb3J0Zm9saW8nLCBlcnJvciwge1xuICAgICAgICBlbGVtZW50TmFtZTogcGFyYW1zLm5hbWUsXG4gICAgICAgIGVsZW1lbnRUeXBlOiBwYXJhbXMudHlwZVxuICAgICAgfSk7XG5cbiAgICAgIC8vIENoZWNrIGlmIGVycm9yIGlzIHRva2VuLXJlbGF0ZWQgYW5kIHByb3ZpZGUgcmVmcmVzaCBndWlkYW5jZVxuICAgICAgY29uc3QgZXJyb3JNZXNzYWdlID0gZXJyb3IgaW5zdGFuY2VvZiBFcnJvciA/IGVycm9yLm1lc3NhZ2UgOiBTdHJpbmcoZXJyb3IpO1xuICAgICAgY29uc3QgaXNUb2tlbkVycm9yID0gZXJyb3JNZXNzYWdlLnRvTG93ZXJDYXNlKCkuaW5jbHVkZXMoJ3Rva2VuJykgfHwgXG4gICAgICAgICAgICAgICAgICAgICAgICAgIGVycm9yTWVzc2FnZS50b0xvd2VyQ2FzZSgpLmluY2x1ZGVzKCdhdXRoJykgfHxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgZXJyb3JNZXNzYWdlLnRvTG93ZXJDYXNlKCkuaW5jbHVkZXMoJzQwMScpIHx8XG4gICAgICAgICAgICAgICAgICAgICAgICAgIGVycm9yTWVzc2FnZS50b0xvd2VyQ2FzZSgpLmluY2x1ZGVzKCc0MDMnKTtcblxuICAgICAgbGV0IGZvcm1hdHRlZEVycm9yID0gRXJyb3JIYW5kbGVyLmZvcm1hdEZvclJlc3BvbnNlKGVycm9yKTtcblxuICAgICAgaWYgKGlzVG9rZW5FcnJvcikge1xuICAgICAgICB0cnkge1xuICAgICAgICAgIC8vIEdldCBjdXJyZW50IHRva2VuIHRvIGRldGVybWluZSB0eXBlIGZvciBndWlkYW5jZVxuICAgICAgICAgIGNvbnN0IGN1cnJlbnRUb2tlbiA9IGF3YWl0IFRva2VuTWFuYWdlci5nZXRHaXRIdWJUb2tlbkFzeW5jKCk7XG4gICAgICAgICAgaWYgKGN1cnJlbnRUb2tlbikge1xuICAgICAgICAgICAgY29uc3QgdG9rZW5UeXBlID0gVG9rZW5NYW5hZ2VyLmdldFRva2VuVHlwZShjdXJyZW50VG9rZW4pO1xuICAgICAgICAgICAgY29uc3QgcmVmcmVzaEd1aWRhbmNlID0gdGhpcy5mb3JtYXRUb2tlblJlZnJlc2hHdWlkYW5jZSgncG9ydGZvbGlvIHN1Ym1pc3Npb24nLCB0b2tlblR5cGUpO1xuICAgICAgICAgICAgXG4gICAgICAgICAgICAvLyBBcHBlbmQgcmVmcmVzaCBndWlkYW5jZSB0byBlcnJvciBtZXNzYWdlXG4gICAgICAgICAgICBpZiAoZm9ybWF0dGVkRXJyb3IubWVzc2FnZSkge1xuICAgICAgICAgICAgICBmb3JtYXR0ZWRFcnJvci5tZXNzYWdlICs9IHJlZnJlc2hHdWlkYW5jZTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgIH0gY2F0Y2ggKHRva2VuRXJyb3IpIHtcbiAgICAgICAgICAvLyBJZiB3ZSBjYW4ndCBnZXQgdG9rZW4gaW5mbywgcHJvdmlkZSBnZW5lcmljIGd1aWRhbmNlXG4gICAgICAgICAgZm9ybWF0dGVkRXJyb3IubWVzc2FnZSArPSAnXFxuXFxu8J+UhCAqKkF1dGhlbnRpY2F0aW9uIElzc3VlKio6IFRyeSBydW5uaW5nIGBzZXR1cF9naXRodWJfYXV0aGAgdG8gcmVmcmVzaCB5b3VyIGF1dGhlbnRpY2F0aW9uLic7XG4gICAgICAgIH1cbiAgICAgIH1cblxuICAgICAgcmV0dXJuIGZvcm1hdHRlZEVycm9yO1xuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBQcm9tcHRzIHVzZXIgdG8gc3VibWl0IGNvbnRlbnQgdG8gdGhlIERvbGxob3VzZU1DUCBjb2xsZWN0aW9uXG4gICAqIEVOSEFOQ0VNRU5UIChJc3N1ZSAjNTQ5KTogQ29tcGxldGUgdGhlIGNvbW11bml0eSBjb250cmlidXRpb24gd29ya2Zsb3dcbiAgICovXG4gIHByaXZhdGUgYXN5bmMgcHJvbXB0Rm9yQ29sbGVjdGlvblN1Ym1pc3Npb24ocGFyYW1zOiB7XG4gICAgZWxlbWVudE5hbWU6IHN0cmluZztcbiAgICBlbGVtZW50VHlwZTogRWxlbWVudFR5cGU7XG4gICAgcG9ydGZvbGlvVXJsOiBzdHJpbmc7XG4gICAgdXNlcm5hbWU6IHN0cmluZztcbiAgICBtZXRhZGF0YTogUG9ydGZvbGlvRWxlbWVudE1ldGFkYXRhO1xuICAgIHRva2VuOiBzdHJpbmc7XG4gIH0pOiBQcm9taXNlPHsgc3VibWl0dGVkOiBib29sZWFuOyBkZWNsaW5lZDogYm9vbGVhbjsgZXJyb3I/OiBzdHJpbmc7IGlzc3VlVXJsPzogc3RyaW5nIH0+IHtcbiAgICB0cnkge1xuICAgICAgLy8gQ3JlYXRlIGEgc2ltcGxlIHByb21wdCBtZXNzYWdlIGZvciB0aGUgdXNlclxuICAgICAgLy8gTm90ZTogSW4gTUNQIGNvbnRleHQsIHdlIGNhbid0IGRvIGludGVyYWN0aXZlIHByb21wdHMsIHNvIHdlJ2xsIG5lZWQgdG9cbiAgICAgIC8vIGVpdGhlciBtYWtlIHRoaXMgYXV0b21hdGljIG9yIHJlcXVpcmUgYSBwYXJhbWV0ZXJcbiAgICAgIFxuICAgICAgLy8gRm9yIG5vdywgbGV0J3MgY2hlY2sgaWYgdGhlIHVzZXIgaGFzIHNldCBhbiBlbnZpcm9ubWVudCB2YXJpYWJsZVxuICAgICAgLy8gdG8gYXV0by1zdWJtaXQgdG8gY29sbGVjdGlvbiAob3B0LWluIGJlaGF2aW9yKVxuICAgICAgY29uc3QgYXV0b1N1Ym1pdCA9IHByb2Nlc3MuZW52LkRPTExIT1VTRV9BVVRPX1NVQk1JVF9UT19DT0xMRUNUSU9OID09PSAndHJ1ZSc7XG4gICAgICBcbiAgICAgIGlmICghYXV0b1N1Ym1pdCkge1xuICAgICAgICAvLyBVc2VyIGhhc24ndCBvcHRlZCBpbiB0byBhdXRvLXN1Ym1pc3Npb25cbiAgICAgICAgbG9nZ2VyLmluZm8oJ0NvbGxlY3Rpb24gc3VibWlzc2lvbiBza2lwcGVkIChzZXQgRE9MTEhPVVNFX0FVVE9fU1VCTUlUX1RPX0NPTExFQ1RJT049dHJ1ZSB0byBlbmFibGUpJyk7XG4gICAgICAgIHJldHVybiB7IHN1Ym1pdHRlZDogZmFsc2UsIGRlY2xpbmVkOiB0cnVlIH07XG4gICAgICB9XG5cbiAgICAgIGxvZ2dlci5pbmZvKCdBdXRvLXN1Ym1pdHRpbmcgdG8gRG9sbGhvdXNlTUNQIGNvbGxlY3Rpb24uLi4nKTtcblxuICAgICAgLy8gQ3JlYXRlIHRoZSBpc3N1ZSBpbiB0aGUgY29sbGVjdGlvbiByZXBvc2l0b3J5XG4gICAgICBjb25zdCBpc3N1ZVVybCA9IGF3YWl0IHRoaXMuY3JlYXRlQ29sbGVjdGlvbklzc3VlKHtcbiAgICAgICAgLi4ucGFyYW1zLFxuICAgICAgICB0b2tlbjogcGFyYW1zLnRva2VuXG4gICAgICB9KTtcblxuICAgICAgaWYgKGlzc3VlVXJsKSB7XG4gICAgICAgIGxvZ2dlci5pbmZvKCdTdWNjZXNzZnVsbHkgY3JlYXRlZCBjb2xsZWN0aW9uIHN1Ym1pc3Npb24gaXNzdWUnLCB7IGlzc3VlVXJsIH0pO1xuICAgICAgICByZXR1cm4geyBzdWJtaXR0ZWQ6IHRydWUsIGRlY2xpbmVkOiBmYWxzZSwgaXNzdWVVcmwgfTtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIHJldHVybiB7IHN1Ym1pdHRlZDogZmFsc2UsIGRlY2xpbmVkOiBmYWxzZSwgZXJyb3I6ICdGYWlsZWQgdG8gY3JlYXRlIGlzc3VlJyB9O1xuICAgICAgfVxuXG4gICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgIGxvZ2dlci5lcnJvcignRXJyb3IgaW4gY29sbGVjdGlvbiBzdWJtaXNzaW9uIHByb21wdCcsIHsgZXJyb3IgfSk7XG4gICAgICByZXR1cm4ge1xuICAgICAgICBzdWJtaXR0ZWQ6IGZhbHNlLFxuICAgICAgICBkZWNsaW5lZDogZmFsc2UsXG4gICAgICAgIGVycm9yOiBlcnJvciBpbnN0YW5jZW9mIEVycm9yID8gZXJyb3IubWVzc2FnZSA6ICdVbmtub3duIGVycm9yJ1xuICAgICAgfTtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogQ3JlYXRlcyBhbiBpc3N1ZSBpbiB0aGUgRG9sbGhvdXNlTUNQL2NvbGxlY3Rpb24gcmVwb3NpdG9yeVxuICAgKiBFTkhBTkNFTUVOVCAoSXNzdWUgIzU0OSk6IEdpdEh1YiBBUEkgaW50ZWdyYXRpb24gZm9yIGNvbGxlY3Rpb24gc3VibWlzc2lvblxuICAgKi9cbiAgcHJpdmF0ZSBhc3luYyBjcmVhdGVDb2xsZWN0aW9uSXNzdWUocGFyYW1zOiB7XG4gICAgZWxlbWVudE5hbWU6IHN0cmluZztcbiAgICBlbGVtZW50VHlwZTogRWxlbWVudFR5cGU7XG4gICAgcG9ydGZvbGlvVXJsOiBzdHJpbmc7XG4gICAgdXNlcm5hbWU6IHN0cmluZztcbiAgICBtZXRhZGF0YTogUG9ydGZvbGlvRWxlbWVudE1ldGFkYXRhO1xuICAgIHRva2VuOiBzdHJpbmc7XG4gIH0pOiBQcm9taXNlPHN0cmluZyB8IG51bGw+IHtcbiAgICB0cnkge1xuXG4gICAgICAvLyBGb3JtYXQgdGhlIGlzc3VlIHRpdGxlXG4gICAgICBjb25zdCB0aXRsZSA9IGBbJHtwYXJhbXMuZWxlbWVudFR5cGV9XSBBZGQgJHtwYXJhbXMuZWxlbWVudE5hbWV9IGJ5IEAke3BhcmFtcy51c2VybmFtZX1gO1xuXG4gICAgICAvLyBGb3JtYXQgdGhlIGlzc3VlIGJvZHkgd2l0aCBhbGwgcmVsZXZhbnQgaW5mb3JtYXRpb25cbiAgICAgIGNvbnN0IGJvZHkgPSBgIyMgTmV3ICR7cGFyYW1zLmVsZW1lbnRUeXBlfSBTdWJtaXNzaW9uXG5cbmAgK1xuICAgICAgICBgKipOYW1lKio6ICR7cGFyYW1zLmVsZW1lbnROYW1lfVxcbmAgK1xuICAgICAgICBgKipBdXRob3IqKjogQCR7cGFyYW1zLnVzZXJuYW1lfVxcbmAgK1xuICAgICAgICBgKipUeXBlKio6ICR7cGFyYW1zLmVsZW1lbnRUeXBlfVxcbmAgK1xuICAgICAgICBgKipEZXNjcmlwdGlvbioqOiAke3BhcmFtcy5tZXRhZGF0YS5kZXNjcmlwdGlvbiB8fCAnTm8gZGVzY3JpcHRpb24gcHJvdmlkZWQnfVxcblxcbmAgK1xuICAgICAgICBgIyMjIFBvcnRmb2xpbyBMaW5rXFxuYCArXG4gICAgICAgIGAke3BhcmFtcy5wb3J0Zm9saW9Vcmx9XFxuXFxuYCArXG4gICAgICAgIGAjIyMgTWV0YWRhdGFcXG5gICtcbiAgICAgICAgYFxcYFxcYFxcYGpzb25cXG4ke0pTT04uc3RyaW5naWZ5KHBhcmFtcy5tZXRhZGF0YSwgbnVsbCwgMil9XFxuXFxgXFxgXFxgXFxuXFxuYCArXG4gICAgICAgIGAjIyMgUmV2aWV3IENoZWNrbGlzdFxcbmAgK1xuICAgICAgICBgLSBbIF0gQ29udGVudCBpcyBhcHByb3ByaWF0ZSBhbmQgZm9sbG93cyBjb21tdW5pdHkgZ3VpZGVsaW5lc1xcbmAgK1xuICAgICAgICBgLSBbIF0gTm8gc2VjdXJpdHkgdnVsbmVyYWJpbGl0aWVzIG9yIG1hbGljaW91cyBwYXR0ZXJuc1xcbmAgK1xuICAgICAgICBgLSBbIF0gTWV0YWRhdGEgaXMgY29tcGxldGUgYW5kIGFjY3VyYXRlXFxuYCArXG4gICAgICAgIGAtIFsgXSBFbGVtZW50IHdvcmtzIGFzIGRlc2NyaWJlZFxcbmAgK1xuICAgICAgICBgLSBbIF0gTm8gZHVwbGljYXRlIG9mIGV4aXN0aW5nIGNvbGxlY3Rpb24gY29udGVudFxcblxcbmAgK1xuICAgICAgICBgLS0tXFxuYCArXG4gICAgICAgIGAqVGhpcyBzdWJtaXNzaW9uIHdhcyBjcmVhdGVkIGF1dG9tYXRpY2FsbHkgdmlhIHRoZSBEb2xsaG91c2VNQ1Agc3VibWl0X2NvbnRlbnQgdG9vbC4qYDtcblxuICAgICAgLy8gRGV0ZXJtaW5lIGxhYmVscyBiYXNlZCBvbiBlbGVtZW50IHR5cGVcbiAgICAgIGNvbnN0IGxhYmVscyA9IFtcbiAgICAgICAgJ2NvbnRyaWJ1dGlvbicsICAvLyBBbGwgc3VibWlzc2lvbnMgZ2V0IHRoaXNcbiAgICAgICAgJ3BlbmRpbmctcmV2aWV3JywgLy8gTmVlZHMgcmV2aWV3XG4gICAgICAgIHBhcmFtcy5lbGVtZW50VHlwZS50b0xvd2VyQ2FzZSgpIC8vIEVsZW1lbnQgdHlwZSBsYWJlbFxuICAgICAgXTtcblxuICAgICAgLy8gUEVSRk9STUFOQ0UgT1BUSU1JWkFUSU9OIChUYXNrICM2KTogVXNlIEdpdEh1YiByYXRlIGxpbWl0ZXIgZm9yIEFQSSBjYWxsc1xuICAgICAgLy8gVGhpcyBwcmV2ZW50cyBoaXR0aW5nIEdpdEh1YiByYXRlIGxpbWl0cyBhbmQgcHJvdmlkZXMgYmV0dGVyIGVycm9yIGhhbmRsaW5nXG4gICAgICBjb25zdCBpc3N1ZVVybCA9IGF3YWl0IGdpdGh1YlJhdGVMaW1pdGVyLnF1ZXVlUmVxdWVzdChcbiAgICAgICAgJ2NyZWF0ZS1jb2xsZWN0aW9uLWlzc3VlJyxcbiAgICAgICAgYXN5bmMgKCkgPT4ge1xuICAgICAgICAgIGNvbnN0IHVybCA9ICdodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL0RvbGxob3VzZU1DUC9jb2xsZWN0aW9uL2lzc3Vlcyc7XG4gICAgICAgICAgXG4gICAgICAgICAgLy8gQ3JlYXRlIEFib3J0Q29udHJvbGxlciBmb3IgdGltZW91dFxuICAgICAgICAgIGNvbnN0IGNvbnRyb2xsZXIgPSBuZXcgQWJvcnRDb250cm9sbGVyKCk7XG4gICAgICAgICAgY29uc3QgdGltZW91dElkID0gc2V0VGltZW91dCgoKSA9PiBjb250cm9sbGVyLmFib3J0KCksIGdldFZhbGlkYXRlZFRpbWVvdXQoKSk7XG4gICAgICAgICAgXG4gICAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgIGNvbnN0IHJlc3BvbnNlID0gYXdhaXQgZmV0Y2godXJsLCB7XG4gICAgICAgICAgICAgIG1ldGhvZDogJ1BPU1QnLFxuICAgICAgICAgICAgICBoZWFkZXJzOiB7XG4gICAgICAgICAgICAgICAgJ0FjY2VwdCc6ICdhcHBsaWNhdGlvbi92bmQuZ2l0aHViLnYzK2pzb24nLFxuICAgICAgICAgICAgICAgICdBdXRob3JpemF0aW9uJzogYEJlYXJlciAke3BhcmFtcy50b2tlbn1gLFxuICAgICAgICAgICAgICAgICdDb250ZW50LVR5cGUnOiAnYXBwbGljYXRpb24vanNvbicsXG4gICAgICAgICAgICAgICAgJ1VzZXItQWdlbnQnOiAnRG9sbGhvdXNlTUNQLzEuMCdcbiAgICAgICAgICAgICAgfSxcbiAgICAgICAgICAgICAgYm9keTogSlNPTi5zdHJpbmdpZnkoe1xuICAgICAgICAgICAgICAgIHRpdGxlLFxuICAgICAgICAgICAgICAgIGJvZHksXG4gICAgICAgICAgICAgICAgbGFiZWxzXG4gICAgICAgICAgICAgIH0pLFxuICAgICAgICAgICAgICBzaWduYWw6IGNvbnRyb2xsZXIuc2lnbmFsXG4gICAgICAgICAgICB9KTtcbiAgICAgICAgICAgIFxuICAgICAgICAgICAgY2xlYXJUaW1lb3V0KHRpbWVvdXRJZCk7XG5cbiAgICAgICAgICAgIC8vIFBFUkZPUk1BTkNFIE9QVElNSVpBVElPTiAoVGFzayAjMTUpOiBFbmhhbmNlZCByYXRlIGxpbWl0IGxvZ2dpbmdcbiAgICAgICAgICAgIC8vIExvZyByYXRlIGxpbWl0IGhlYWRlcnMgZm9yIGRpYWdub3N0aWNzXG4gICAgICAgICAgICBjb25zdCByYXRlTGltaXRSZW1haW5pbmcgPSByZXNwb25zZS5oZWFkZXJzLmdldCgnWC1SYXRlTGltaXQtUmVtYWluaW5nJyk7XG4gICAgICAgICAgICBjb25zdCByYXRlTGltaXRSZXNldCA9IHJlc3BvbnNlLmhlYWRlcnMuZ2V0KCdYLVJhdGVMaW1pdC1SZXNldCcpO1xuICAgICAgICAgICAgY29uc3QgcmF0ZUxpbWl0TGltaXQgPSByZXNwb25zZS5oZWFkZXJzLmdldCgnWC1SYXRlTGltaXQtTGltaXQnKTtcbiAgICAgICAgICAgIFxuICAgICAgICAgICAgbG9nZ2VyLmRlYnVnKCdHaXRIdWIgQVBJIHJhdGUgbGltaXQgc3RhdHVzJywge1xuICAgICAgICAgICAgICBvcGVyYXRpb246ICdjcmVhdGUtY29sbGVjdGlvbi1pc3N1ZScsXG4gICAgICAgICAgICAgIHJlbWFpbmluZzogcmF0ZUxpbWl0UmVtYWluaW5nLFxuICAgICAgICAgICAgICBsaW1pdDogcmF0ZUxpbWl0TGltaXQsXG4gICAgICAgICAgICAgIHJlc2V0VGltZTogcmF0ZUxpbWl0UmVzZXQgPyBuZXcgRGF0ZShwYXJzZUludChyYXRlTGltaXRSZXNldCkgKiAxMDAwKSA6IHVuZGVmaW5lZCxcbiAgICAgICAgICAgICAgcmVzcG9uc2VTdGF0dXM6IHJlc3BvbnNlLnN0YXR1c1xuICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICBcbiAgICAgICAgICAgIC8vIExvZyB3YXJuaW5nIGlmIGFwcHJvYWNoaW5nIHJhdGUgbGltaXRcbiAgICAgICAgICAgIGlmIChyYXRlTGltaXRSZW1haW5pbmcgJiYgcGFyc2VJbnQocmF0ZUxpbWl0UmVtYWluaW5nKSA8IDEwMCkge1xuICAgICAgICAgICAgICBsb2dnZXIud2FybignQXBwcm9hY2hpbmcgR2l0SHViIEFQSSByYXRlIGxpbWl0Jywge1xuICAgICAgICAgICAgICAgIG9wZXJhdGlvbjogJ2NyZWF0ZS1jb2xsZWN0aW9uLWlzc3VlJyxcbiAgICAgICAgICAgICAgICByZW1haW5pbmc6IHJhdGVMaW1pdFJlbWFpbmluZyxcbiAgICAgICAgICAgICAgICByZXNldFRpbWU6IHJhdGVMaW1pdFJlc2V0ID8gbmV3IERhdGUocGFyc2VJbnQocmF0ZUxpbWl0UmVzZXQpICogMTAwMCkgOiB1bmRlZmluZWQsXG4gICAgICAgICAgICAgICAgcmVjb21tZW5kYXRpb246ICdDb25zaWRlciByZWR1Y2luZyBBUEkgdXNhZ2UgZnJlcXVlbmN5IG9yIGF1dGhlbnRpY2F0aW5nIGZvciBoaWdoZXIgbGltaXRzJ1xuICAgICAgICAgICAgICB9KTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgaWYgKCFyZXNwb25zZS5vaykge1xuICAgICAgICAgICAgICBjb25zdCBlcnJvclRleHQgPSBhd2FpdCByZXNwb25zZS50ZXh0KCk7XG4gICAgICAgICAgICAgIGxvZ2dlci5lcnJvcignR2l0SHViIEFQSSBlcnJvciBjcmVhdGluZyBpc3N1ZScsIHsgXG4gICAgICAgICAgICAgICAgc3RhdHVzOiByZXNwb25zZS5zdGF0dXMsIFxuICAgICAgICAgICAgICAgIHN0YXR1c1RleHQ6IHJlc3BvbnNlLnN0YXR1c1RleHQsXG4gICAgICAgICAgICAgICAgZXJyb3I6IGVycm9yVGV4dCxcbiAgICAgICAgICAgICAgICByYXRlTGltaXRSZW1haW5pbmcsXG4gICAgICAgICAgICAgICAgcmF0ZUxpbWl0UmVzZXRcbiAgICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICAgIFxuICAgICAgICAgICAgICBpZiAocmVzcG9uc2Uuc3RhdHVzID09PSA0MDQpIHtcbiAgICAgICAgICAgICAgICBsb2dnZXIuZXJyb3IoJ0NvbGxlY3Rpb24gcmVwb3NpdG9yeSBub3QgZm91bmQgb3Igbm8gYWNjZXNzJyk7XG4gICAgICAgICAgICAgIH0gZWxzZSBpZiAocmVzcG9uc2Uuc3RhdHVzID09PSA0MDMpIHtcbiAgICAgICAgICAgICAgICBsb2dnZXIuZXJyb3IoJ1Blcm1pc3Npb24gZGVuaWVkIHRvIGNyZWF0ZSBpc3N1ZSBpbiBjb2xsZWN0aW9uIHJlcG8nKTtcbiAgICAgICAgICAgICAgfSBlbHNlIGlmIChyZXNwb25zZS5zdGF0dXMgPT09IDQwMSkge1xuICAgICAgICAgICAgICAgIGxvZ2dlci5lcnJvcignQXV0aGVudGljYXRpb24gZmFpbGVkIGZvciBjb2xsZWN0aW9uIHN1Ym1pc3Npb24nKTtcbiAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYEdpdEh1YiBBUEkgZXJyb3I6ICR7cmVzcG9uc2Uuc3RhdHVzfSAke3Jlc3BvbnNlLnN0YXR1c1RleHR9YCk7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGNvbnN0IGRhdGEgPSBhd2FpdCByZXNwb25zZS5qc29uKCk7XG4gICAgICAgICAgICByZXR1cm4gZGF0YS5odG1sX3VybDtcbiAgICAgICAgICAgIFxuICAgICAgICAgIH0gY2F0Y2ggKGZldGNoRXJyb3I6IGFueSkge1xuICAgICAgICAgICAgLy8gUmUtdGhyb3cgdG8gb3V0ZXIgY2F0Y2ggYmxvY2tcbiAgICAgICAgICAgIHRocm93IGZldGNoRXJyb3I7XG4gICAgICAgICAgfSBmaW5hbGx5IHtcbiAgICAgICAgICAgIGNsZWFyVGltZW91dCh0aW1lb3V0SWQpO1xuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAgJ2hpZ2gnIC8vIEhpZ2ggcHJpb3JpdHkgZm9yIGNvbGxlY3Rpb24gc3VibWlzc2lvblxuICAgICAgKTtcbiAgICAgIFxuICAgICAgcmV0dXJuIGlzc3VlVXJsO1xuXG4gICAgfSBjYXRjaCAoZXJyb3I6IGFueSkge1xuICAgICAgLy8gSGFuZGxlIHRpbWVvdXQgc3BlY2lmaWNhbGx5XG4gICAgICBpZiAoZXJyb3IubmFtZSA9PT0gJ0Fib3J0RXJyb3InKSB7XG4gICAgICAgIGxvZ2dlci5lcnJvcihgR2l0SHViIEFQSSByZXF1ZXN0IHRpbWVvdXQgYWZ0ZXIgJHtnZXRWYWxpZGF0ZWRUaW1lb3V0KCl9bXNgKTtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIGxvZ2dlci5lcnJvcignRmFpbGVkIHRvIGNyZWF0ZSBjb2xsZWN0aW9uIGlzc3VlJywgeyBcbiAgICAgICAgICBlcnJvcjogZXJyb3IubWVzc2FnZSB8fCBlcnJvclxuICAgICAgICB9KTtcbiAgICAgIH1cbiAgICAgIHJldHVybiBudWxsO1xuICAgIH1cbiAgfVxuXG4gIHByaXZhdGUgYXN5bmMgZmluZExvY2FsQ29udGVudChuYW1lOiBzdHJpbmcsIHR5cGU6IEVsZW1lbnRUeXBlKTogUHJvbWlzZTxzdHJpbmcgfCBudWxsPiB7XG4gICAgdHJ5IHtcbiAgICAgIC8vIE1FVEFEQVRBIElOREVYIEZJWDogVXNlIHBvcnRmb2xpbyBpbmRleCBmb3IgZmFzdCBtZXRhZGF0YS1iYXNlZCBsb29rdXBzXG4gICAgICAvLyBUaGlzIHNvbHZlcyB0aGUgY3JpdGljYWwgaXNzdWUgd2hlcmUgXCJTYWZlIFJvdW5kdHJpcCBUZXN0ZXJcIiBjb3VsZG4ndCBiZSBmb3VuZFxuICAgICAgLy8gYmVjYXVzZSBmaW5kTG9jYWxDb250ZW50IG9ubHkgc2VhcmNoZWQgZmlsZW5hbWVzLCBub3QgbWV0YWRhdGEgbmFtZXNcbiAgICAgIGNvbnN0IGluZGV4TWFuYWdlciA9IFBvcnRmb2xpb0luZGV4TWFuYWdlci5nZXRJbnN0YW5jZSgpO1xuICAgICAgXG4gICAgICAvLyBVWCBJTVBST1ZFTUVOVDogRW5oYW5jZWQgc2VhcmNoIHdpdGggZnV6enkgbWF0Y2hpbmdcbiAgICAgIGNvbnN0IGluZGV4RW50cnkgPSBhd2FpdCBpbmRleE1hbmFnZXIuZmluZEJ5TmFtZShuYW1lLCB7IFxuICAgICAgICBlbGVtZW50VHlwZTogdHlwZSxcbiAgICAgICAgZnV6enlNYXRjaDogdHJ1ZVxuICAgICAgfSk7XG4gICAgICBcbiAgICAgIGlmIChpbmRleEVudHJ5KSB7XG4gICAgICAgIGxvZ2dlci5kZWJ1ZygnRm91bmQgY29udGVudCB2aWEgbWV0YWRhdGEgaW5kZXgnLCB7IFxuICAgICAgICAgIHNlYXJjaE5hbWU6IG5hbWUsIFxuICAgICAgICAgIG1ldGFkYXRhTmFtZTogaW5kZXhFbnRyeS5tZXRhZGF0YS5uYW1lLFxuICAgICAgICAgIGZpbGVuYW1lOiBpbmRleEVudHJ5LmZpbGVuYW1lLFxuICAgICAgICAgIGZpbGVQYXRoOiBpbmRleEVudHJ5LmZpbGVQYXRoLFxuICAgICAgICAgIHR5cGUgXG4gICAgICAgIH0pO1xuICAgICAgICByZXR1cm4gaW5kZXhFbnRyeS5maWxlUGF0aDtcbiAgICAgIH1cbiAgICAgIFxuICAgICAgLy8gRkFMTEJBQ0s6IFVzZSBvcmlnaW5hbCBmaWxlIGRpc2NvdmVyeSBpZiBpbmRleCBsb29rdXAgZmFpbHNcbiAgICAgIC8vIFRoaXMgbWFpbnRhaW5zIGJhY2t3YXJkIGNvbXBhdGliaWxpdHkgYW5kIGhhbmRsZXMgZWRnZSBjYXNlc1xuICAgICAgbG9nZ2VyLmRlYnVnKCdJbmRleCBsb29rdXAgZmFpbGVkLCBmYWxsaW5nIGJhY2sgdG8gZmlsZSBkaXNjb3ZlcnknLCB7IG5hbWUsIHR5cGUgfSk7XG4gICAgICBcbiAgICAgIGNvbnN0IHBvcnRmb2xpb01hbmFnZXIgPSBQb3J0Zm9saW9NYW5hZ2VyLmdldEluc3RhbmNlKCk7XG4gICAgICBjb25zdCBwb3J0Zm9saW9EaXIgPSBwb3J0Zm9saW9NYW5hZ2VyLmdldEVsZW1lbnREaXIodHlwZSk7XG4gICAgICBcbiAgICAgIC8vIFVYIElNUFJPVkVNRU5UOiBUcnkgbXVsdGlwbGUgc2VhcmNoIHN0cmF0ZWdpZXMgZm9yIGJldHRlciB1c2VyIGV4cGVyaWVuY2VcbiAgICAgIGxldCBmaWxlID0gYXdhaXQgRmlsZURpc2NvdmVyeVV0aWwuZmluZEZpbGUocG9ydGZvbGlvRGlyLCBuYW1lLCB7XG4gICAgICAgIGV4dGVuc2lvbnM6IFsnLm1kJywgJy5qc29uJywgJy55YW1sJywgJy55bWwnXSxcbiAgICAgICAgcGFydGlhbE1hdGNoOiB0cnVlLFxuICAgICAgICBjYWNoZVJlc3VsdHM6IHRydWVcbiAgICAgIH0pO1xuICAgICAgXG4gICAgICAvLyBJZiBub3QgZm91bmQsIHRyeSBub3JtYWxpemluZyB0aGUgbmFtZSAoZS5nLiwgXCJKLkEuUi5WLkkuUy5cIiAtPiBcImotYS1yLXYtaS1zXCIpXG4gICAgICBpZiAoIWZpbGUpIHtcbiAgICAgICAgY29uc3Qgbm9ybWFsaXplZE5hbWUgPSBuYW1lLnRvTG93ZXJDYXNlKClcbiAgICAgICAgICAucmVwbGFjZSgvW15hLXowLTldL2dpLCAnLScpICAvLyBSZXBsYWNlIG5vbi1hbHBoYW51bWVyaWMgd2l0aCBkYXNoZXNcbiAgICAgICAgICAucmVwbGFjZSgvLSsvZywgJy0nKSAgICAgICAgIC8vIFJlcGxhY2UgbXVsdGlwbGUgZGFzaGVzIHdpdGggc2luZ2xlIGRhc2hcbiAgICAgICAgICAucmVwbGFjZSgvXi18LSQvZywgJycpOyAgICAgIC8vIFJlbW92ZSBsZWFkaW5nL3RyYWlsaW5nIGRhc2hlc1xuICAgICAgICAgIFxuICAgICAgICBpZiAobm9ybWFsaXplZE5hbWUgIT09IG5hbWUudG9Mb3dlckNhc2UoKSkge1xuICAgICAgICAgIGxvZ2dlci5kZWJ1ZygnVHJ5aW5nIG5vcm1hbGl6ZWQgbmFtZSBzZWFyY2gnLCB7IFxuICAgICAgICAgICAgb3JpZ2luYWw6IG5hbWUsIFxuICAgICAgICAgICAgbm9ybWFsaXplZDogbm9ybWFsaXplZE5hbWUsXG4gICAgICAgICAgICB0eXBlIFxuICAgICAgICAgIH0pO1xuICAgICAgICAgIFxuICAgICAgICAgIGZpbGUgPSBhd2FpdCBGaWxlRGlzY292ZXJ5VXRpbC5maW5kRmlsZShwb3J0Zm9saW9EaXIsIG5vcm1hbGl6ZWROYW1lLCB7XG4gICAgICAgICAgICBleHRlbnNpb25zOiBbJy5tZCcsICcuanNvbicsICcueWFtbCcsICcueW1sJ10sXG4gICAgICAgICAgICBwYXJ0aWFsTWF0Y2g6IHRydWUsXG4gICAgICAgICAgICBjYWNoZVJlc3VsdHM6IHRydWVcbiAgICAgICAgICB9KTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgICAgXG4gICAgICAvLyBJZiBzdGlsbCBub3QgZm91bmQsIHRyeSBzZWFyY2hpbmcgYnkgZGlzcGxheSBuYW1lIHBhdHRlcm5zXG4gICAgICBpZiAoIWZpbGUpIHtcbiAgICAgICAgLy8gVHJ5IGNvbW1vbiB2YXJpYXRpb25zIGxpa2UgcmVtb3ZpbmcgZG90cywgc3BhY2VzLCBldGMuXG4gICAgICAgIGNvbnN0IHZhcmlhdGlvbnMgPSBbXG4gICAgICAgICAgbmFtZS5yZXBsYWNlKC9cXC4vZywgJycpLCAgICAgICAgLy8gUmVtb3ZlIGRvdHM6IFwiSi5BLlIuVi5JLlMuXCIgLT4gXCJKQVJWSVNcIlxuICAgICAgICAgIG5hbWUucmVwbGFjZSgvXFxzKy9nLCAnLScpLCAgICAgIC8vIFJlcGxhY2Ugc3BhY2VzIHdpdGggZGFzaGVzXG4gICAgICAgICAgbmFtZS5yZXBsYWNlKC9bXFxzXFwuXS9nLCAnJyksICAgIC8vIFJlbW92ZSBzcGFjZXMgYW5kIGRvdHNcbiAgICAgICAgICBuYW1lLnJlcGxhY2UoL1tcXHNcXC5dL2csICctJyksICAgLy8gUmVwbGFjZSBzcGFjZXMgYW5kIGRvdHMgd2l0aCBkYXNoZXNcbiAgICAgICAgXS5maWx0ZXIodiA9PiB2ICE9PSBuYW1lICYmIHYubGVuZ3RoID4gMCk7XG4gICAgICAgIFxuICAgICAgICBmb3IgKGNvbnN0IHZhcmlhdGlvbiBvZiB2YXJpYXRpb25zKSB7XG4gICAgICAgICAgZmlsZSA9IGF3YWl0IEZpbGVEaXNjb3ZlcnlVdGlsLmZpbmRGaWxlKHBvcnRmb2xpb0RpciwgdmFyaWF0aW9uLCB7XG4gICAgICAgICAgICBleHRlbnNpb25zOiBbJy5tZCcsICcuanNvbicsICcueWFtbCcsICcueW1sJ10sXG4gICAgICAgICAgICBwYXJ0aWFsTWF0Y2g6IHRydWUsXG4gICAgICAgICAgICBjYWNoZVJlc3VsdHM6IHRydWVcbiAgICAgICAgICB9KTtcbiAgICAgICAgICBcbiAgICAgICAgICBpZiAoZmlsZSkge1xuICAgICAgICAgICAgbG9nZ2VyLmRlYnVnKCdGb3VuZCBjb250ZW50IHVzaW5nIG5hbWUgdmFyaWF0aW9uJywge1xuICAgICAgICAgICAgICBvcmlnaW5hbDogbmFtZSxcbiAgICAgICAgICAgICAgdmFyaWF0aW9uLFxuICAgICAgICAgICAgICBmaWxlLFxuICAgICAgICAgICAgICB0eXBlXG4gICAgICAgICAgICB9KTtcbiAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgfVxuICAgICAgXG4gICAgICBpZiAoZmlsZSkge1xuICAgICAgICBsb2dnZXIuZGVidWcoJ0ZvdW5kIGxvY2FsIGNvbnRlbnQgZmlsZSB2aWEgZmFsbGJhY2snLCB7IG5hbWUsIHR5cGUsIGZpbGUgfSk7XG4gICAgICAgIHJldHVybiBmaWxlO1xuICAgICAgfVxuICAgICAgXG4gICAgICBsb2dnZXIuZGVidWcoJ05vIGNvbnRlbnQgZm91bmQnLCB7IG5hbWUsIHR5cGUgfSk7XG4gICAgICByZXR1cm4gbnVsbDtcbiAgICAgIFxuICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICBsb2dnZXIuZXJyb3IoJ0Vycm9yIGZpbmRpbmcgbG9jYWwgY29udGVudCcsIHtcbiAgICAgICAgbmFtZSxcbiAgICAgICAgdHlwZSxcbiAgICAgICAgZXJyb3I6IGVycm9yIGluc3RhbmNlb2YgRXJyb3IgPyBlcnJvci5tZXNzYWdlIDogU3RyaW5nKGVycm9yKVxuICAgICAgfSk7XG4gICAgICByZXR1cm4gbnVsbDtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogU21hcnQgZWxlbWVudCB0eXBlIGRldGVjdGlvbiAtIHNlYXJjaGVzIGFjcm9zcyBBTEwgZWxlbWVudCB0eXBlcyBmb3IgY29udGVudFxuICAgKiBQRVJGT1JNQU5DRSBPUFRJTUlaQVRJT04gKFRhc2sgIzkpOiBVc2VzIGVhcmx5IHRlcm1pbmF0aW9uIGZvciBleGFjdCBtYXRjaGVzXG4gICAqIFRoaXMgcmVwbGFjZXMgdGhlIHByZXZpb3VzIGhhcmRjb2RlZCBkZWZhdWx0IHRvIFBFUlNPTkEgYW5kIGVuYWJsZXMgcHJvcGVyIHR5cGUgZGV0ZWN0aW9uXG4gICAqIFxuICAgKiBAcGFyYW0gbmFtZSBUaGUgY29udGVudCBuYW1lIHRvIHNlYXJjaCBmb3JcbiAgICogQHJldHVybnMgRGV0ZWN0aW9uIHJlc3VsdCB3aXRoIGZvdW5kIG1hdGNoZXMgYWNyb3NzIGFsbCBlbGVtZW50IHR5cGVzXG4gICAqL1xuICBwcml2YXRlIGFzeW5jIGRldGVjdEVsZW1lbnRUeXBlKG5hbWU6IHN0cmluZyk6IFByb21pc2U8RWxlbWVudERldGVjdGlvblJlc3VsdD4ge1xuICAgIHRyeSB7XG4gICAgICAvLyBQRVJGT1JNQU5DRSBPUFRJTUlaQVRJT04gKFRhc2sgIzkpOiBVc2UgZWFybHkgdGVybWluYXRpb24gc2VhcmNoIHV0aWxpdHlcbiAgICAgIC8vIENyZWF0ZSBzZWFyY2ggZnVuY3Rpb25zIGZvciBlYWNoIGVsZW1lbnQgdHlwZVxuICAgICAgY29uc3QgZWxlbWVudFR5cGVzID0gT2JqZWN0LnZhbHVlcyhFbGVtZW50VHlwZSk7XG4gICAgICBjb25zdCBzZWFyY2hGdW5jdGlvbnMgPSBlbGVtZW50VHlwZXMubWFwKCh0eXBlKSA9PiBhc3luYyAoKSA9PiB7XG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgY29uc3QgZmlsZVBhdGggPSBhd2FpdCB0aGlzLmZpbmRMb2NhbENvbnRlbnQobmFtZSwgdHlwZSk7XG4gICAgICAgICAgaWYgKGZpbGVQYXRoKSB7XG4gICAgICAgICAgICByZXR1cm4geyB0eXBlOiB0eXBlIGFzIEVsZW1lbnRUeXBlLCBwYXRoOiBmaWxlUGF0aCB9O1xuICAgICAgICAgIH1cbiAgICAgICAgICByZXR1cm4gbnVsbDtcbiAgICAgICAgfSBjYXRjaCAoZXJyb3I6IGFueSkge1xuICAgICAgICAgIC8vIExvZyB1bmV4cGVjdGVkIGVycm9ycyBidXQgZG9uJ3QgZmFpbCB0aGUgc2VhcmNoXG4gICAgICAgICAgaWYgKGVycm9yPy5jb2RlICE9PSAnRU5PRU5UJyAmJiBlcnJvcj8uY29kZSAhPT0gJ0VOT1RESVInKSB7XG4gICAgICAgICAgICBsb2dnZXIuZGVidWcoYEVycm9yIHNlYXJjaGluZyAke3R5cGV9IGRpcmVjdG9yeSBmb3IgY29udGVudCBkZXRlY3Rpb25gLCB7IFxuICAgICAgICAgICAgICBuYW1lLFxuICAgICAgICAgICAgICB0eXBlLFxuICAgICAgICAgICAgICBlcnJvcjogZXJyb3I/Lm1lc3NhZ2UgfHwgU3RyaW5nKGVycm9yKSxcbiAgICAgICAgICAgICAgY29kZTogZXJyb3I/LmNvZGUgXG4gICAgICAgICAgICB9KTtcbiAgICAgICAgICB9XG4gICAgICAgICAgLy8gUmV0dXJuIG51bGwgaW5zdGVhZCBvZiB0aHJvd2luZyB0byBsZXQgb3RoZXIgc2VhcmNoZXMgY29udGludWVcbiAgICAgICAgICByZXR1cm4gbnVsbDtcbiAgICAgICAgfVxuICAgICAgfSk7XG5cbiAgICAgIC8vIFBFUkZPUk1BTkNFIE9QVElNSVpBVElPTiAoVGFzayAjOSk6IERlZmluZSBleGFjdCBtYXRjaCBjcml0ZXJpYVxuICAgICAgY29uc3QgaXNFeGFjdE1hdGNoID0gKG1hdGNoOiBFbGVtZW50RGV0ZWN0aW9uTWF0Y2gpOiBib29sZWFuID0+IHtcbiAgICAgICAgY29uc3QgZmlsZW5hbWUgPSBwYXRoLmJhc2VuYW1lKG1hdGNoLnBhdGgsIHBhdGguZXh0bmFtZShtYXRjaC5wYXRoKSk7XG4gICAgICAgIHJldHVybiBmaWxlbmFtZS50b0xvd2VyQ2FzZSgpID09PSBuYW1lLnRvTG93ZXJDYXNlKCk7XG4gICAgICB9O1xuXG4gICAgICAvLyBFeGVjdXRlIHNlYXJjaGVzIHdpdGggZWFybHkgdGVybWluYXRpb24gb3B0aW1pemF0aW9uXG4gICAgICBjb25zdCBzZWFyY2hSZXN1bHRzID0gYXdhaXQgRWFybHlUZXJtaW5hdGlvblNlYXJjaC5leGVjdXRlV2l0aEVhcmx5VGVybWluYXRpb24oXG4gICAgICAgIHNlYXJjaEZ1bmN0aW9ucyxcbiAgICAgICAgaXNFeGFjdE1hdGNoLFxuICAgICAgICB7XG4gICAgICAgICAgb3BlcmF0aW9uTmFtZTogJ2VsZW1lbnQtdHlwZS1kZXRlY3Rpb24nLFxuICAgICAgICAgIHRpbWVvdXRBZnRlckV4YWN0TWF0Y2g6IDEwMDAsIC8vIFdhaXQgMSBzZWNvbmQgZm9yIG90aGVyIHNlYXJjaGVzIGFmdGVyIGV4YWN0IG1hdGNoXG4gICAgICAgICAgbWF4UGFyYWxsZWxTZWFyY2hlczogOCAvLyBMaW1pdCBjb25jdXJyZW50IHNlYXJjaGVzIHRvIGF2b2lkIG92ZXJ3aGVsbWluZyB0aGUgc3lzdGVtXG4gICAgICAgIH1cbiAgICAgICk7XG5cbiAgICAgIC8vIFBFUkZPUk1BTkNFIE9QVElNSVpBVElPTiAoVGFzayAjOCk6IEVuaGFuY2VkIGJhdGNoIG9wZXJhdGlvbiByZXBvcnRpbmdcbiAgICAgIGNvbnN0IGJhdGNoUmVzdWx0cyA9IHtcbiAgICAgICAgbmFtZSxcbiAgICAgICAgdG90YWxTZWFyY2hlczogc2VhcmNoUmVzdWx0cy50b3RhbFNlYXJjaGVzLFxuICAgICAgICBjb21wbGV0ZWRTZWFyY2hlczogc2VhcmNoUmVzdWx0cy5jb21wbGV0ZWRTZWFyY2hlcyxcbiAgICAgICAgbWF0Y2hlczogc2VhcmNoUmVzdWx0cy5tYXRjaGVzLmxlbmd0aCxcbiAgICAgICAgZmFpbHVyZXM6IHNlYXJjaFJlc3VsdHMuZmFpbHVyZXMubGVuZ3RoLFxuICAgICAgICBleGFjdE1hdGNoRm91bmQ6ICEhc2VhcmNoUmVzdWx0cy5leGFjdE1hdGNoLFxuICAgICAgICBleGFjdE1hdGNoVHlwZTogc2VhcmNoUmVzdWx0cy5leGFjdE1hdGNoPy50eXBlLFxuICAgICAgICBlYXJseVRlcm1pbmF0aW9uVHJpZ2dlcmVkOiBzZWFyY2hSZXN1bHRzLmVhcmx5VGVybWluYXRpb25UcmlnZ2VyZWQsXG4gICAgICAgIHBlcmZvcm1hbmNlR2Fpbjogc2VhcmNoUmVzdWx0cy5wZXJmb3JtYW5jZUdhaW4sXG4gICAgICAgIG1hdGNoZWRUeXBlczogc2VhcmNoUmVzdWx0cy5tYXRjaGVzLm1hcChtID0+IG0udHlwZSksXG4gICAgICAgIGZhaWxlZFR5cGVzOiBzZWFyY2hSZXN1bHRzLmZhaWx1cmVzLm1hcChmID0+IGVsZW1lbnRUeXBlc1tmLmluZGV4XSkuZmlsdGVyKEJvb2xlYW4pXG4gICAgICB9O1xuXG4gICAgICBsb2dnZXIuZGVidWcoJ0VsZW1lbnQgdHlwZSBkZXRlY3Rpb24gY29tcGxldGVkIHdpdGggZWFybHkgdGVybWluYXRpb24gb3B0aW1pemF0aW9uJywgYmF0Y2hSZXN1bHRzKTtcblxuICAgICAgLy8gUEVSRk9STUFOQ0UgT1BUSU1JWkFUSU9OIChUYXNrICM4KTogQ2xlYXIgcmVwb3J0aW5nIG9mIHBhcnRpYWwgZmFpbHVyZXNcbiAgICAgIGlmIChzZWFyY2hSZXN1bHRzLmZhaWx1cmVzLmxlbmd0aCA+IDApIHtcbiAgICAgICAgbG9nZ2VyLndhcm4oJ1NvbWUgZWxlbWVudCB0eXBlIHNlYXJjaGVzIGZhaWxlZCBkdXJpbmcgYmF0Y2ggb3BlcmF0aW9uJywge1xuICAgICAgICAgIG5hbWUsXG4gICAgICAgICAgZmFpbHVyZXM6IHNlYXJjaFJlc3VsdHMuZmFpbHVyZXMubWFwKGYgPT4gKHtcbiAgICAgICAgICAgIHR5cGU6IGVsZW1lbnRUeXBlc1tmLmluZGV4XSB8fCAndW5rbm93bicsXG4gICAgICAgICAgICBlcnJvcjogZi5lcnJvci5zdWJzdHJpbmcoMCwgMTAwKSAvLyBUcnVuY2F0ZSBsb25nIGVycm9yIG1lc3NhZ2VzXG4gICAgICAgICAgfSkpLFxuICAgICAgICAgIHN1Y2Nlc3NSYXRlOiBgJHtzZWFyY2hSZXN1bHRzLmNvbXBsZXRlZFNlYXJjaGVzfS8ke3NlYXJjaFJlc3VsdHMudG90YWxTZWFyY2hlc31gLFxuICAgICAgICAgIGltcGFjdE9uUmVzdWx0czogc2VhcmNoUmVzdWx0cy5tYXRjaGVzLmxlbmd0aCA+IDAgXG4gICAgICAgICAgICA/ICdObyBpbXBhY3QgLSBtYXRjaGVzIGZvdW5kIGluIHN1Y2Nlc3NmdWwgc2VhcmNoZXMnIFxuICAgICAgICAgICAgOiAnUG90ZW50aWFsIGltcGFjdCAtIG5vIG1hdGNoZXMgZm91bmQnXG4gICAgICAgIH0pO1xuXG4gICAgICAgIC8vIElmIHdlIGhhdmUgZmFpbHVyZXMgYW5kIG5vIG1hdGNoZXMsIHByb3ZpZGUgYWN0aW9uYWJsZSBndWlkYW5jZVxuICAgICAgICBpZiAoc2VhcmNoUmVzdWx0cy5tYXRjaGVzLmxlbmd0aCA9PT0gMCAmJiBzZWFyY2hSZXN1bHRzLmZhaWx1cmVzLmxlbmd0aCA+IDApIHtcbiAgICAgICAgICBsb2dnZXIud2FybignQmF0Y2ggb3BlcmF0aW9uIGhhZCBmYWlsdXJlcyBhbmQgbm8gbWF0Y2hlcyBmb3VuZCcsIHtcbiAgICAgICAgICAgIG5hbWUsXG4gICAgICAgICAgICByZWNvbW1lbmRhdGlvbjogJ0NvbnNpZGVyIGNoZWNraW5nIGZpbGUgcGVybWlzc2lvbnMgb3IgcG9ydGZvbGlvIHN0cnVjdHVyZScsXG4gICAgICAgICAgICBmYWlsdXJlQ291bnQ6IHNlYXJjaFJlc3VsdHMuZmFpbHVyZXMubGVuZ3RoLFxuICAgICAgICAgICAgdG90YWxTZWFyY2hlczogc2VhcmNoUmVzdWx0cy50b3RhbFNlYXJjaGVzXG4gICAgICAgICAgfSk7XG4gICAgICAgIH1cbiAgICAgIH1cblxuICAgICAgLy8gTG9nIHBlcmZvcm1hbmNlIGdhaW5zIGZyb20gZWFybHkgdGVybWluYXRpb25cbiAgICAgIGlmIChzZWFyY2hSZXN1bHRzLmVhcmx5VGVybWluYXRpb25UcmlnZ2VyZWQpIHtcbiAgICAgICAgbG9nZ2VyLmluZm8oJ0Vhcmx5IHRlcm1pbmF0aW9uIG9wdGltaXphdGlvbiBhcHBsaWVkIHN1Y2Nlc3NmdWxseScsIHtcbiAgICAgICAgICBuYW1lLFxuICAgICAgICAgIGV4YWN0TWF0Y2hUeXBlOiBzZWFyY2hSZXN1bHRzLmV4YWN0TWF0Y2g/LnR5cGUsXG4gICAgICAgICAgcGVyZm9ybWFuY2VHYWluOiBzZWFyY2hSZXN1bHRzLnBlcmZvcm1hbmNlR2FpbixcbiAgICAgICAgICBzZWFyY2hlc0NvbXBsZXRlZDogc2VhcmNoUmVzdWx0cy5jb21wbGV0ZWRTZWFyY2hlcyxcbiAgICAgICAgICBzZWFyY2hlc1RvdGFsOiBzZWFyY2hSZXN1bHRzLnRvdGFsU2VhcmNoZXNcbiAgICAgICAgfSk7XG4gICAgICB9XG5cbiAgICAgIHJldHVybiB7XG4gICAgICAgIGZvdW5kOiBzZWFyY2hSZXN1bHRzLm1hdGNoZXMubGVuZ3RoID4gMCxcbiAgICAgICAgbWF0Y2hlczogc2VhcmNoUmVzdWx0cy5tYXRjaGVzXG4gICAgICB9O1xuXG4gICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgIGxvZ2dlci5lcnJvcignRXJyb3IgaW4gZWxlbWVudCB0eXBlIGRldGVjdGlvbicsIHtcbiAgICAgICAgbmFtZSxcbiAgICAgICAgZXJyb3I6IGVycm9yIGluc3RhbmNlb2YgRXJyb3IgPyBlcnJvci5tZXNzYWdlIDogU3RyaW5nKGVycm9yKVxuICAgICAgfSk7XG4gICAgICBcbiAgICAgIC8vIFJldHVybiBlbXB0eSByZXN1bHQgb24gZGV0ZWN0aW9uIGZhaWx1cmVcbiAgICAgIHJldHVybiB7XG4gICAgICAgIGZvdW5kOiBmYWxzZSxcbiAgICAgICAgbWF0Y2hlczogW11cbiAgICAgIH07XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIFVYIElNUFJPVkVNRU5UOiBHZW5lcmF0ZSBuYW1lIHN1Z2dlc3Rpb25zIGZvciBzaW1pbGFyIGNvbnRlbnRcbiAgICogUEVSRk9STUFOQ0UgT1BUSU1JWkFUSU9OIChUYXNrICM4KTogRW5oYW5jZWQgYmF0Y2ggb3BlcmF0aW9uIGhhbmRsaW5nIHdpdGggY2xlYXIgcGFydGlhbCBmYWlsdXJlIHJlcG9ydGluZ1xuICAgKiBIZWxwcyB1c2VycyBmaW5kIGNvbnRlbnQgd2hlbiBleGFjdCBtYXRjaGVzIGZhaWxcbiAgICovXG4gIHByaXZhdGUgYXN5bmMgZ2VuZXJhdGVOYW1lU3VnZ2VzdGlvbnMoc2VhcmNoTmFtZTogc3RyaW5nKTogUHJvbWlzZTxBcnJheTx7bmFtZTogc3RyaW5nLCB0eXBlOiBzdHJpbmd9Pj4ge1xuICAgIHRyeSB7XG4gICAgICBjb25zdCBzdWdnZXN0aW9uczogQXJyYXk8e25hbWU6IHN0cmluZywgdHlwZTogc3RyaW5nfT4gPSBbXTtcbiAgICAgIGNvbnN0IHNlYXJjaExvd2VyID0gc2VhcmNoTmFtZS50b0xvd2VyQ2FzZSgpO1xuICAgICAgY29uc3QgZWxlbWVudFR5cGVzID0gT2JqZWN0LnZhbHVlcyhFbGVtZW50VHlwZSk7XG4gICAgICBcbiAgICAgIC8vIFRyYWNrIGJhdGNoIG9wZXJhdGlvbiByZXN1bHRzIGZvciBiZXR0ZXIgZGlhZ25vc3RpY3NcbiAgICAgIGNvbnN0IGJhdGNoUmVzdWx0cyA9IHtcbiAgICAgICAgc2VhcmNoTmFtZSxcbiAgICAgICAgdG90YWxUeXBlczogZWxlbWVudFR5cGVzLmxlbmd0aCxcbiAgICAgICAgc3VjY2Vzc2Z1bFNjYW5zOiAwLFxuICAgICAgICBmYWlsZWRTY2FuczogMCxcbiAgICAgICAgZmFpbHVyZURldGFpbHM6IFtdIGFzIEFycmF5PHsgdHlwZTogRWxlbWVudFR5cGU7IGVycm9yOiBzdHJpbmcgfT4sXG4gICAgICAgIHRvdGFsU3VnZ2VzdGlvbnM6IDAsXG4gICAgICAgIHN1Z2dlc3Rpb25zQnlUeXBlOiB7fSBhcyBSZWNvcmQ8c3RyaW5nLCBudW1iZXI+XG4gICAgICB9O1xuICAgICAgXG4gICAgICAvLyBQcm9jZXNzIGFsbCBlbGVtZW50IHR5cGVzIGZvciBzdWdnZXN0aW9uc1xuICAgICAgZm9yIChjb25zdCBlbGVtZW50VHlwZSBvZiBlbGVtZW50VHlwZXMpIHtcbiAgICAgICAgdHJ5IHtcbiAgICAgICAgICBjb25zdCBwb3J0Zm9saW9NYW5hZ2VyID0gUG9ydGZvbGlvTWFuYWdlci5nZXRJbnN0YW5jZSgpO1xuICAgICAgICAgIGNvbnN0IGVsZW1lbnREaXIgPSBwb3J0Zm9saW9NYW5hZ2VyLmdldEVsZW1lbnREaXIoZWxlbWVudFR5cGUpO1xuICAgICAgICAgIFxuICAgICAgICAgIC8vIEdldCBmaWxlcyBpbiB0aGlzIGRpcmVjdG9yeVxuICAgICAgICAgIGNvbnN0IGZpbGVzID0gYXdhaXQgRmlsZURpc2NvdmVyeVV0aWwuZmluZEZpbGUoZWxlbWVudERpciwgJyonLCB7XG4gICAgICAgICAgICBleHRlbnNpb25zOiBbJy5tZCcsICcuanNvbicsICcueWFtbCcsICcueW1sJ10sXG4gICAgICAgICAgICBwYXJ0aWFsTWF0Y2g6IGZhbHNlLFxuICAgICAgICAgICAgY2FjaGVSZXN1bHRzOiB0cnVlXG4gICAgICAgICAgfSk7XG4gICAgICAgICAgXG4gICAgICAgICAgbGV0IHR5cGVTdWdnZXN0aW9ucyA9IDA7XG4gICAgICAgICAgXG4gICAgICAgICAgaWYgKEFycmF5LmlzQXJyYXkoZmlsZXMpKSB7XG4gICAgICAgICAgICBmb3IgKGNvbnN0IGZpbGVQYXRoIG9mIGZpbGVzKSB7XG4gICAgICAgICAgICAgIGNvbnN0IGJhc2VuYW1lID0gcGF0aC5iYXNlbmFtZShmaWxlUGF0aCwgcGF0aC5leHRuYW1lKGZpbGVQYXRoKSk7XG4gICAgICAgICAgICAgIFxuICAgICAgICAgICAgICAvLyBDYWxjdWxhdGUgc2ltaWxhcml0eSB1c2luZyBzaW1wbGUgbWV0cmljc1xuICAgICAgICAgICAgICBpZiAodGhpcy5jYWxjdWxhdGVTaW1pbGFyaXR5KHNlYXJjaExvd2VyLCBiYXNlbmFtZS50b0xvd2VyQ2FzZSgpKSA+IFNFQVJDSF9DT05GSUcuTUlOX1NJTUlMQVJJVFlfU0NPUkUpIHtcbiAgICAgICAgICAgICAgICBzdWdnZXN0aW9ucy5wdXNoKHtcbiAgICAgICAgICAgICAgICAgIG5hbWU6IGJhc2VuYW1lLFxuICAgICAgICAgICAgICAgICAgdHlwZTogZWxlbWVudFR5cGVcbiAgICAgICAgICAgICAgICB9KTtcbiAgICAgICAgICAgICAgICB0eXBlU3VnZ2VzdGlvbnMrKztcbiAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0gZWxzZSBpZiAoZmlsZXMpIHtcbiAgICAgICAgICAgIGNvbnN0IGJhc2VuYW1lID0gcGF0aC5iYXNlbmFtZShmaWxlcywgcGF0aC5leHRuYW1lKGZpbGVzKSk7XG4gICAgICAgICAgICBpZiAodGhpcy5jYWxjdWxhdGVTaW1pbGFyaXR5KHNlYXJjaExvd2VyLCBiYXNlbmFtZS50b0xvd2VyQ2FzZSgpKSA+IFNFQVJDSF9DT05GSUcuTUlOX1NJTUlMQVJJVFlfU0NPUkUpIHtcbiAgICAgICAgICAgICAgc3VnZ2VzdGlvbnMucHVzaCh7XG4gICAgICAgICAgICAgICAgbmFtZTogYmFzZW5hbWUsXG4gICAgICAgICAgICAgICAgdHlwZTogZWxlbWVudFR5cGVcbiAgICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICAgIHR5cGVTdWdnZXN0aW9ucysrO1xuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgICBcbiAgICAgICAgICBiYXRjaFJlc3VsdHMuc3VjY2Vzc2Z1bFNjYW5zKys7XG4gICAgICAgICAgYmF0Y2hSZXN1bHRzLnN1Z2dlc3Rpb25zQnlUeXBlW2VsZW1lbnRUeXBlXSA9IHR5cGVTdWdnZXN0aW9ucztcbiAgICAgICAgICBcbiAgICAgICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgICAgICAvLyBQRVJGT1JNQU5DRSBPUFRJTUlaQVRJT04gKFRhc2sgIzgpOiBUcmFjayBhbmQgcmVwb3J0IHBhcnRpYWwgZmFpbHVyZXNcbiAgICAgICAgICBiYXRjaFJlc3VsdHMuZmFpbGVkU2NhbnMrKztcbiAgICAgICAgICBiYXRjaFJlc3VsdHMuZmFpbHVyZURldGFpbHMucHVzaCh7XG4gICAgICAgICAgICB0eXBlOiBlbGVtZW50VHlwZSxcbiAgICAgICAgICAgIGVycm9yOiBlcnJvciBpbnN0YW5jZW9mIEVycm9yID8gZXJyb3IubWVzc2FnZSA6IFN0cmluZyhlcnJvcilcbiAgICAgICAgICB9KTtcbiAgICAgICAgICBcbiAgICAgICAgICAvLyBMb2cgaW5kaXZpZHVhbCBmYWlsdXJlcyBmb3IgZGlhZ25vc3RpY3NcbiAgICAgICAgICBsb2dnZXIuZGVidWcoJ0ZhaWxlZCB0byBzY2FuIGVsZW1lbnQgdHlwZSBmb3Igc3VnZ2VzdGlvbnMnLCB7XG4gICAgICAgICAgICBlbGVtZW50VHlwZSxcbiAgICAgICAgICAgIHNlYXJjaE5hbWUsXG4gICAgICAgICAgICBlcnJvcjogZXJyb3IgaW5zdGFuY2VvZiBFcnJvciA/IGVycm9yLm1lc3NhZ2UgOiBTdHJpbmcoZXJyb3IpXG4gICAgICAgICAgfSk7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICAgIFxuICAgICAgYmF0Y2hSZXN1bHRzLnRvdGFsU3VnZ2VzdGlvbnMgPSBzdWdnZXN0aW9ucy5sZW5ndGg7XG4gICAgICBcbiAgICAgIC8vIFBFUkZPUk1BTkNFIE9QVElNSVpBVElPTiAoVGFzayAjOCk6IENvbXByZWhlbnNpdmUgYmF0Y2ggb3BlcmF0aW9uIHJlcG9ydGluZ1xuICAgICAgbG9nZ2VyLmRlYnVnKCdOYW1lIHN1Z2dlc3Rpb24gYmF0Y2ggb3BlcmF0aW9uIGNvbXBsZXRlZCcsIHtcbiAgICAgICAgLi4uYmF0Y2hSZXN1bHRzLFxuICAgICAgICBzdWNjZXNzUmF0ZTogYCR7YmF0Y2hSZXN1bHRzLnN1Y2Nlc3NmdWxTY2Fuc30vJHtiYXRjaFJlc3VsdHMudG90YWxUeXBlc31gLFxuICAgICAgICAvLyBEb24ndCBsb2cgZnVsbCBmYWlsdXJlIGRldGFpbHMgYXQgZGVidWcgbGV2ZWwgdG8gYXZvaWQgc3BhbVxuICAgICAgICBoYXNGYWlsdXJlczogYmF0Y2hSZXN1bHRzLmZhaWxlZFNjYW5zID4gMFxuICAgICAgfSk7XG4gICAgICBcbiAgICAgIC8vIFJlcG9ydCBmYWlsdXJlcyBjbGVhcmx5IGlmIHRoZXkgb2NjdXJyZWRcbiAgICAgIGlmIChiYXRjaFJlc3VsdHMuZmFpbGVkU2NhbnMgPiAwKSB7XG4gICAgICAgIGxvZ2dlci53YXJuKCdTb21lIGVsZW1lbnQgdHlwZSBzY2FucyBmYWlsZWQgZHVyaW5nIG5hbWUgc3VnZ2VzdGlvbiBnZW5lcmF0aW9uJywge1xuICAgICAgICAgIHNlYXJjaE5hbWUsXG4gICAgICAgICAgZmFpbGVkVHlwZXM6IGJhdGNoUmVzdWx0cy5mYWlsdXJlRGV0YWlscy5tYXAoZiA9PiBmLnR5cGUpLFxuICAgICAgICAgIHN1Y2Nlc3NmdWxUeXBlczogYmF0Y2hSZXN1bHRzLnN1Y2Nlc3NmdWxTY2FucyxcbiAgICAgICAgICBpbXBhY3RPblJlc3VsdHM6IGJhdGNoUmVzdWx0cy50b3RhbFN1Z2dlc3Rpb25zID4gMCBcbiAgICAgICAgICAgID8gJ1BhcnRpYWwgaW1wYWN0IC0gc3VnZ2VzdGlvbnMgZm91bmQgZnJvbSBzdWNjZXNzZnVsIHNjYW5zJyBcbiAgICAgICAgICAgIDogJ1BvdGVudGlhbCBpbXBhY3QgLSBubyBzdWdnZXN0aW9ucyBnZW5lcmF0ZWQnLFxuICAgICAgICAgIHJlY29tbWVuZGF0aW9uOiBiYXRjaFJlc3VsdHMudG90YWxTdWdnZXN0aW9ucyA9PT0gMCAmJiBiYXRjaFJlc3VsdHMuZmFpbGVkU2NhbnMgPiAwXG4gICAgICAgICAgICA/ICdDaGVjayBwb3J0Zm9saW8gZGlyZWN0b3J5IHN0cnVjdHVyZSBhbmQgZmlsZSBwZXJtaXNzaW9ucydcbiAgICAgICAgICAgIDogJ1N1Z2dlc3Rpb24gZ2VuZXJhdGlvbiBwYXJ0aWFsbHkgc3VjY2Vzc2Z1bCBkZXNwaXRlIHNvbWUgZmFpbHVyZXMnXG4gICAgICAgIH0pO1xuICAgICAgfVxuICAgICAgXG4gICAgICAvLyBTb3J0IGJ5IHNpbWlsYXJpdHkgKGhpZ2hlciBpcyBiZXR0ZXIpIGFuZCByZXR1cm4gdG9wIHN1Z2dlc3Rpb25zXG4gICAgICBjb25zdCBzb3J0ZWRTdWdnZXN0aW9ucyA9IHN1Z2dlc3Rpb25zLnNvcnQoKGEsIGIpID0+IHtcbiAgICAgICAgY29uc3Qgc2ltQSA9IHRoaXMuY2FsY3VsYXRlU2ltaWxhcml0eShzZWFyY2hMb3dlciwgYS5uYW1lLnRvTG93ZXJDYXNlKCkpO1xuICAgICAgICBjb25zdCBzaW1CID0gdGhpcy5jYWxjdWxhdGVTaW1pbGFyaXR5KHNlYXJjaExvd2VyLCBiLm5hbWUudG9Mb3dlckNhc2UoKSk7XG4gICAgICAgIHJldHVybiBzaW1CIC0gc2ltQTtcbiAgICAgIH0pO1xuICAgICAgXG4gICAgICBsb2dnZXIuZGVidWcoJ05hbWUgc3VnZ2VzdGlvbnMgZ2VuZXJhdGVkIHN1Y2Nlc3NmdWxseScsIHtcbiAgICAgICAgc2VhcmNoTmFtZSxcbiAgICAgICAgdG90YWxTdWdnZXN0aW9uczogc29ydGVkU3VnZ2VzdGlvbnMubGVuZ3RoLFxuICAgICAgICB0b3BTdWdnZXN0aW9uczogc29ydGVkU3VnZ2VzdGlvbnMuc2xpY2UoMCwgMykubWFwKHMgPT4gcy5uYW1lKVxuICAgICAgfSk7XG4gICAgICBcbiAgICAgIHJldHVybiBzb3J0ZWRTdWdnZXN0aW9ucztcbiAgICAgIFxuICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICBsb2dnZXIud2FybignRmFpbGVkIHRvIGdlbmVyYXRlIG5hbWUgc3VnZ2VzdGlvbnMgLSBiYXRjaCBvcGVyYXRpb24gZmFpbGVkIGNvbXBsZXRlbHknLCB7IFxuICAgICAgICBzZWFyY2hOYW1lLCBcbiAgICAgICAgZXJyb3I6IGVycm9yIGluc3RhbmNlb2YgRXJyb3IgPyBlcnJvci5tZXNzYWdlIDogU3RyaW5nKGVycm9yKSxcbiAgICAgICAgcmVjb21tZW5kYXRpb246ICdDaGVjayBwb3J0Zm9saW8gc3RydWN0dXJlIGFuZCBwZXJtaXNzaW9ucydcbiAgICAgIH0pO1xuICAgICAgcmV0dXJuIFtdO1xuICAgIH1cbiAgfVxuICBcbiAgLyoqXG4gICAqIFNpbXBsZSBzaW1pbGFyaXR5IGNhbGN1bGF0aW9uIHVzaW5nIExldmVuc2h0ZWluLWxpa2UgYXBwcm9hY2hcbiAgICogUmV0dXJucyB2YWx1ZSBiZXR3ZWVuIDAgYW5kIDEsIHdoZXJlIDEgaXMgaWRlbnRpY2FsXG4gICAqL1xuICBwcml2YXRlIGNhbGN1bGF0ZVNpbWlsYXJpdHkoc3RyMTogc3RyaW5nLCBzdHIyOiBzdHJpbmcpOiBudW1iZXIge1xuICAgIC8vIEhhbmRsZSBleGFjdCBtYXRjaGVzXG4gICAgaWYgKHN0cjEgPT09IHN0cjIpIHJldHVybiAxO1xuICAgIFxuICAgIC8vIEhhbmRsZSBzdWJzdHJpbmcgbWF0Y2hlc1xuICAgIGlmIChzdHIxLmluY2x1ZGVzKHN0cjIpIHx8IHN0cjIuaW5jbHVkZXMoc3RyMSkpIHJldHVybiAwLjg7XG4gICAgXG4gICAgLy8gSGFuZGxlIHBhcnRpYWwgbWF0Y2hlc1xuICAgIGNvbnN0IGxvbmdlciA9IHN0cjEubGVuZ3RoID4gc3RyMi5sZW5ndGggPyBzdHIxIDogc3RyMjtcbiAgICBjb25zdCBzaG9ydGVyID0gc3RyMS5sZW5ndGggPiBzdHIyLmxlbmd0aCA/IHN0cjIgOiBzdHIxO1xuICAgIFxuICAgIGlmIChsb25nZXIubGVuZ3RoID09PSAwKSByZXR1cm4gMDtcbiAgICBcbiAgICAvLyBDb3VudCBjb21tb24gY2hhcmFjdGVyc1xuICAgIGxldCBjb21tb24gPSAwO1xuICAgIGZvciAobGV0IGkgPSAwOyBpIDwgc2hvcnRlci5sZW5ndGg7IGkrKykge1xuICAgICAgaWYgKGxvbmdlci5pbmNsdWRlcyhzaG9ydGVyW2ldKSkge1xuICAgICAgICBjb21tb24rKztcbiAgICAgIH1cbiAgICB9XG4gICAgXG4gICAgcmV0dXJuIGNvbW1vbiAvIGxvbmdlci5sZW5ndGg7XG4gIH1cbiAgXG4gIC8qKlxuICAgKiBVWCBJTVBST1ZFTUVOVDogU2F2ZSBlbGVtZW50IHdpdGggYXV0b21hdGljIHJldHJ5IGxvZ2ljIGZvciB0cmFuc2llbnQgZmFpbHVyZXNcbiAgICogSGFuZGxlcyBjb21tb24gR2l0SHViIEFQSSBpc3N1ZXMgbGlrZSByYXRlIGxpbWl0cyBhbmQgdGVtcG9yYXJ5IG5ldHdvcmsgcHJvYmxlbXNcbiAgICovXG4gIHByaXZhdGUgYXN5bmMgc2F2ZUVsZW1lbnRXaXRoUmV0cnkoXG4gICAgYWRhcHRlcjogUG9ydGZvbGlvRWxlbWVudEFkYXB0ZXIsIFxuICAgIGVsZW1lbnROYW1lOiBzdHJpbmcsIFxuICAgIGVsZW1lbnRUeXBlOiBFbGVtZW50VHlwZSxcbiAgICBtYXhSZXRyaWVzOiBudW1iZXIgPSBSRVRSWV9DT05GSUcuTUFYX0FUVEVNUFRTXG4gICk6IFByb21pc2U8c3RyaW5nIHwgbnVsbD4ge1xuICAgIGxldCBsYXN0RXJyb3I6IGFueSA9IG51bGw7XG4gICAgXG4gICAgZm9yIChsZXQgYXR0ZW1wdCA9IDE7IGF0dGVtcHQgPD0gbWF4UmV0cmllczsgYXR0ZW1wdCsrKSB7XG4gICAgICB0cnkge1xuICAgICAgICBsb2dnZXIuZGVidWcoYEF0dGVtcHRpbmcgdG8gc2F2ZSBlbGVtZW50IChhdHRlbXB0ICR7YXR0ZW1wdH0vJHttYXhSZXRyaWVzfSlgLCB7XG4gICAgICAgICAgZWxlbWVudE5hbWUsXG4gICAgICAgICAgZWxlbWVudFR5cGUsXG4gICAgICAgICAgYXR0ZW1wdFxuICAgICAgICB9KTtcbiAgICAgICAgXG4gICAgICAgIGNvbnN0IGZpbGVVcmwgPSBhd2FpdCB0aGlzLnBvcnRmb2xpb01hbmFnZXIuc2F2ZUVsZW1lbnQoYWRhcHRlciwgdHJ1ZSk7XG4gICAgICAgIFxuICAgICAgICBpZiAoZmlsZVVybCkge1xuICAgICAgICAgIGlmIChhdHRlbXB0ID4gMSkge1xuICAgICAgICAgICAgbG9nZ2VyLmluZm8oYEVsZW1lbnQgc2F2ZWQgc3VjY2Vzc2Z1bGx5IGFmdGVyICR7YXR0ZW1wdH0gYXR0ZW1wdHNgLCB7XG4gICAgICAgICAgICAgIGVsZW1lbnROYW1lLFxuICAgICAgICAgICAgICBlbGVtZW50VHlwZSxcbiAgICAgICAgICAgICAgZmlsZVVybFxuICAgICAgICAgICAgfSk7XG4gICAgICAgICAgfVxuICAgICAgICAgIHJldHVybiBmaWxlVXJsO1xuICAgICAgICB9XG4gICAgICAgIFxuICAgICAgICAvLyBJZiBzYXZlRWxlbWVudCByZXR1cm5zIG51bGwsIHRyZWF0IGFzIGEgZmFpbHVyZSBidXQgZG9uJ3QgcmV0cnkgaW1tZWRpYXRlbHlcbiAgICAgICAgbGFzdEVycm9yID0gbmV3IEVycm9yKGBzYXZlRWxlbWVudCByZXR1cm5lZCBudWxsIG9uIGF0dGVtcHQgJHthdHRlbXB0fWApO1xuICAgICAgICBcbiAgICAgIH0gY2F0Y2ggKGVycm9yOiBhbnkpIHtcbiAgICAgICAgbGFzdEVycm9yID0gZXJyb3I7XG4gICAgICAgIGNvbnN0IGlzUmV0cnlhYmxlID0gdGhpcy5pc1JldHJ5YWJsZUVycm9yKGVycm9yKTtcbiAgICAgICAgXG4gICAgICAgIGxvZ2dlci53YXJuKGBTYXZlIGF0dGVtcHQgJHthdHRlbXB0fSBmYWlsZWRgLCB7XG4gICAgICAgICAgZWxlbWVudE5hbWUsXG4gICAgICAgICAgZWxlbWVudFR5cGUsXG4gICAgICAgICAgYXR0ZW1wdCxcbiAgICAgICAgICBlcnJvcjogZXJyb3IubWVzc2FnZSxcbiAgICAgICAgICBpc1JldHJ5YWJsZSxcbiAgICAgICAgICB3aWxsUmV0cnk6IGlzUmV0cnlhYmxlICYmIGF0dGVtcHQgPCBtYXhSZXRyaWVzXG4gICAgICAgIH0pO1xuICAgICAgICBcbiAgICAgICAgLy8gSWYgdGhpcyBpcyBub3QgYSByZXRyeWFibGUgZXJyb3IsIGZhaWwgaW1tZWRpYXRlbHlcbiAgICAgICAgaWYgKCFpc1JldHJ5YWJsZSkge1xuICAgICAgICAgIGxvZ2dlci5lcnJvcignTm9uLXJldHJ5YWJsZSBlcnJvciBlbmNvdW50ZXJlZCwgYWJvcnRpbmcgcmV0cmllcycsIHtcbiAgICAgICAgICAgIGVsZW1lbnROYW1lLFxuICAgICAgICAgICAgZXJyb3I6IGVycm9yLm1lc3NhZ2VcbiAgICAgICAgICB9KTtcbiAgICAgICAgICBicmVhaztcbiAgICAgICAgfVxuICAgICAgICBcbiAgICAgICAgLy8gSWYgd2UgaGF2ZSBtb3JlIGF0dGVtcHRzLCB3YWl0IGJlZm9yZSByZXRyeWluZ1xuICAgICAgICBpZiAoYXR0ZW1wdCA8IG1heFJldHJpZXMpIHtcbiAgICAgICAgICBjb25zdCBkZWxheSA9IGNhbGN1bGF0ZVJldHJ5RGVsYXkoYXR0ZW1wdCk7XG4gICAgICAgICAgbG9nZ2VyLmRlYnVnKGBXYWl0aW5nICR7ZGVsYXl9bXMgYmVmb3JlIHJldHJ5YCwgeyBhdHRlbXB0LCBkZWxheSB9KTtcbiAgICAgICAgICBhd2FpdCBuZXcgUHJvbWlzZShyZXNvbHZlID0+IHNldFRpbWVvdXQocmVzb2x2ZSwgZGVsYXkpKTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cbiAgICBcbiAgICAvLyBBbGwgYXR0ZW1wdHMgZmFpbGVkXG4gICAgbG9nZ2VyLmVycm9yKGBBbGwgJHttYXhSZXRyaWVzfSBzYXZlIGF0dGVtcHRzIGZhaWxlZGAsIHtcbiAgICAgIGVsZW1lbnROYW1lLFxuICAgICAgZWxlbWVudFR5cGUsXG4gICAgICBsYXN0RXJyb3I6IGxhc3RFcnJvcj8ubWVzc2FnZVxuICAgIH0pO1xuICAgIFxuICAgIHJldHVybiBudWxsO1xuICB9XG4gIFxuICAvKipcbiAgICogRGV0ZXJtaW5lIGlmIGFuIGVycm9yIGlzIHdvcnRoIHJldHJ5aW5nXG4gICAqIFJldHJ5YWJsZTogbmV0d29yayBpc3N1ZXMsIHJhdGUgbGltaXRzLCB0ZW1wb3JhcnkgR2l0SHViIEFQSSBwcm9ibGVtc1xuICAgKiBOb24tcmV0cnlhYmxlOiBhdXRoZW50aWNhdGlvbiBpc3N1ZXMsIHZhbGlkYXRpb24gZXJyb3JzLCBwZXJtYW5lbnQgZmFpbHVyZXNcbiAgICovXG4gIHByaXZhdGUgaXNSZXRyeWFibGVFcnJvcihlcnJvcjogYW55KTogYm9vbGVhbiB7XG4gICAgY29uc3QgZXJyb3JNZXNzYWdlID0gZXJyb3I/Lm1lc3NhZ2U/LnRvTG93ZXJDYXNlKCkgfHwgJyc7XG4gICAgY29uc3QgZXJyb3JDb2RlID0gZXJyb3I/LmNvZGU7XG4gICAgY29uc3Qgc3RhdHVzQ29kZSA9IGVycm9yPy5zdGF0dXMgfHwgZXJyb3I/LnN0YXR1c0NvZGU7XG4gICAgXG4gICAgLy8gTmV0d29yayBhbmQgdGltZW91dCBlcnJvcnNcbiAgICBpZiAoZXJyb3JDb2RlID09PSAnRU5PVEZPVU5EJyB8fCBlcnJvckNvZGUgPT09ICdFQ09OTlJFU0VUJyB8fCBlcnJvckNvZGUgPT09ICdFVElNRURPVVQnKSB7XG4gICAgICByZXR1cm4gdHJ1ZTtcbiAgICB9XG4gICAgXG4gICAgLy8gR2l0SHViIEFQSSByYXRlIGxpbWl0c1xuICAgIGlmIChzdGF0dXNDb2RlID09PSA0MjkgfHwgZXJyb3JNZXNzYWdlLmluY2x1ZGVzKCdyYXRlIGxpbWl0JykpIHtcbiAgICAgIHJldHVybiB0cnVlO1xuICAgIH1cbiAgICBcbiAgICAvLyBUZW1wb3JhcnkgR2l0SHViIEFQSSBpc3N1ZXNcbiAgICBpZiAoc3RhdHVzQ29kZSA+PSA1MDAgJiYgc3RhdHVzQ29kZSA8IDYwMCkge1xuICAgICAgcmV0dXJuIHRydWU7XG4gICAgfVxuICAgIFxuICAgIC8vIFRlbXBvcmFyeSBHaXRIdWIgQVBJIHByb2JsZW1zXG4gICAgaWYgKGVycm9yTWVzc2FnZS5pbmNsdWRlcygndGVtcG9yYXJpbHkgdW5hdmFpbGFibGUnKSB8fCBcbiAgICAgICAgZXJyb3JNZXNzYWdlLmluY2x1ZGVzKCdzZXJ2aWNlIHVuYXZhaWxhYmxlJykgfHxcbiAgICAgICAgZXJyb3JNZXNzYWdlLmluY2x1ZGVzKCdpbnRlcm5hbCBzZXJ2ZXIgZXJyb3InKSkge1xuICAgICAgcmV0dXJuIHRydWU7XG4gICAgfVxuICAgIFxuICAgIC8vIENvbm5lY3Rpb24gaXNzdWVzXG4gICAgaWYgKGVycm9yTWVzc2FnZS5pbmNsdWRlcygnY29ubmVjdGlvbicpICYmIFxuICAgICAgICAoZXJyb3JNZXNzYWdlLmluY2x1ZGVzKCd0aW1lb3V0JykgfHwgZXJyb3JNZXNzYWdlLmluY2x1ZGVzKCdyZXNldCcpKSkge1xuICAgICAgcmV0dXJuIHRydWU7XG4gICAgfVxuICAgIFxuICAgIC8vIERvbid0IHJldHJ5IGF1dGhlbnRpY2F0aW9uIG9yIHBlcm1pc3Npb24gaXNzdWVzXG4gICAgaWYgKHN0YXR1c0NvZGUgPT09IDQwMSB8fCBzdGF0dXNDb2RlID09PSA0MDMgfHwgXG4gICAgICAgIGVycm9yTWVzc2FnZS5pbmNsdWRlcygndW5hdXRob3JpemVkJykgfHwgXG4gICAgICAgIGVycm9yTWVzc2FnZS5pbmNsdWRlcygnZm9yYmlkZGVuJykgfHxcbiAgICAgICAgZXJyb3JNZXNzYWdlLmluY2x1ZGVzKCdhdXRoZW50aWNhdGlvbicpKSB7XG4gICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuICAgIFxuICAgIC8vIERvbid0IHJldHJ5IHZhbGlkYXRpb24gZXJyb3JzXG4gICAgaWYgKHN0YXR1c0NvZGUgPT09IDQwMCB8fCBzdGF0dXNDb2RlID09PSA0MjIgfHxcbiAgICAgICAgZXJyb3JNZXNzYWdlLmluY2x1ZGVzKCdpbnZhbGlkJykgfHxcbiAgICAgICAgZXJyb3JNZXNzYWdlLmluY2x1ZGVzKCd2YWxpZGF0aW9uJykpIHtcbiAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9XG4gICAgXG4gICAgLy8gRGVmYXVsdCB0byBub3QgcmV0cnlpbmcgZm9yIHVua25vd24gZXJyb3JzIHRvIGF2b2lkIGluZmluaXRlIGxvb3BzXG4gICAgcmV0dXJuIGZhbHNlO1xuICB9XG59Il19
|