UNPKG

vite-plugin-react-server

Version:
537 lines (535 loc) 77.9 kB
/** * vite-plugin-react-server * Copyright (c) Nico Brinkkemper * MIT License */ import { createLogger } from 'vite'; import { resolveOptions } from '../config/resolveOptions.js'; import { createBuildLoader } from './createBuildLoader.server.js'; import { renderPagesBatched } from './renderPagesBatched.js'; import { renderPages } from './renderPages.js'; import { getBundleManifest } from '../helpers/getBundleManifest.js'; import { createWorker } from '../worker/createWorker.js'; import { serializedOptions, serializeResolvedConfig } from '../helpers/serializeUserOptions.js'; import { performance } from 'node:perf_hooks'; import { baseURL } from '../utils/envUrls.node.js'; import { handleError } from '../error/handleError.js'; import { shouldCausePanic } from '../error/panicThresholdHandler.js'; import { renderPage } from './renderPage.server.js'; import { temporaryReferences } from './temporaryReferences.server.js'; import { configurePreviewServer } from './configurePreviewServer.js'; import { envPrefixFromConfig } from '../config/envPrefixFromConfig.js'; import { processCssFilesForPages } from './processCssFilesForPages.js'; import { createWorkerStartupMetrics } from '../metrics/createWorkerStartupMetrics.js'; import { tryManifest } from '../helpers/tryManifest.js'; import { join } from 'node:path'; import { resolveAutoDiscover } from '../config/autoDiscover/resolveAutoDiscover.js'; import { assertReactServer } from '../config/getCondition.js'; import { toError } from '../error/toError.js'; assertReactServer(); let globalWorker; const reactStaticPlugin = function _reactStaticPlugin(options) { let worker; let logger; let resolvedConfig; let autoDiscoveredFiles = null; let serverManifest = void 0; let configEnv; const timing = { start: performance.now(), configResolved: 0, buildStart: 0, renderStart: 0 }; const resolvedOptions = resolveOptions(options); if (resolvedOptions.type === "error") { throw resolvedOptions.error; } const userOptions = resolvedOptions.userOptions; return { name: "vite:plugin-react-server/server-static", enforce: "post", api: { meta: { timing } }, async config(_config, viteConfigEnv) { configEnv = viteConfigEnv; }, applyToEnvironment(partialEnvironment) { if (["server"].includes( partialEnvironment.name )) { return true; } return false; }, async configResolved(config) { resolvedConfig = config; if (!logger) { logger = config.customLogger ?? createLogger(); } const autoDiscoverResult = await resolveAutoDiscover({ config, configEnv, userOptions, logger }); if (autoDiscoverResult.type === "error") { throw autoDiscoverResult.error; } autoDiscoveredFiles = autoDiscoverResult.autoDiscoveredFiles; }, async buildStart() { if (!logger) { logger = this.environment.logger; } timing.buildStart = performance.now(); if (userOptions.onEvent && autoDiscoveredFiles) { try { userOptions.onEvent({ type: "build.start", data: { pages: Array.from(autoDiscoveredFiles.urlMap.keys()), files: autoDiscoveredFiles } }); } catch (error) { const panicError = handleError({ error, logger, panicThreshold: userOptions.panicThreshold}); if (panicError != null) { worker?.terminate(); throw panicError; } else { this.error(new Error("Failed to emit build.start event")); } } } }, // the preview server helps to view the generated static folder, but only when the static plugin is enabled // if no build.pages, then the preview server will instead use default vite preview server // it works the same under both conditions async configurePreviewServer(server) { logger = server.config.customLogger || server.config.logger; configurePreviewServer({ server, userOptions }); }, async renderStart() { timing.renderStart = performance.now(); }, generateBundle(_options, bundle) { if (this.environment.name === "server") { const keysToDelete = []; for (const [key, chunk] of Object.entries(bundle)) { if (chunk.type === "chunk") { const isVirtual = chunk.fileName?.includes("_virtual") || key.includes("_virtual") || chunk.facadeModuleId?.includes("_virtual") || chunk.moduleIds?.some((id) => id.includes("_virtual")); const isDynamicImportHelper = chunk.fileName?.includes("dynamic-import-helper") || key.includes("dynamic-import-helper"); if (isVirtual && !isDynamicImportHelper) { keysToDelete.push(key); if (userOptions.verbose) { logger?.info(`[plugin.server] Filtered out virtual file: ${chunk.fileName || key} (moduleId: ${chunk.facadeModuleId || chunk.moduleIds?.[0]})`); } } } } for (const key of keysToDelete) { delete bundle[key]; } } }, async writeBundle(_options, bundle) { if (this.environment.name !== "server") { if (userOptions.verbose) { logger?.info(`[plugin.server] Skipping static generation for environment: ${this.environment.name}`); } return; } let panicError = null; let bundleManifest = void 0; if (!logger) { logger = this.environment.logger; } try { bundleManifest = getBundleManifest({ bundle, normalizer: userOptions.normalizer }); const manifestPath = typeof resolvedConfig.build.manifest === "string" ? resolvedConfig.build.manifest : ".vite/manifest.json"; if (!bundleManifest[manifestPath] || !("source" in bundleManifest[manifestPath])) { throw new Error("Server manifest not found"); } serverManifest = JSON.parse( bundleManifest[manifestPath].source ); if (!serverManifest) { throw new Error("Failed to parse server manifest"); } } catch (error) { const panicError2 = handleError({ error, logger, panicThreshold: userOptions.panicThreshold}); if (panicError2 != null) { throw panicError2; } else { throw new Error("Failed to get bundle manifest"); } } try { const staticManifestResult = await tryManifest({ root: userOptions.projectRoot, outDir: join(userOptions.build.outDir, userOptions.build.static), manifestPath: resolvedConfig.build.manifest, ssrManifest: false }); if (staticManifestResult.type === "error") { throw staticManifestResult.error; } const staticManifest = staticManifestResult.manifest; const buildLoader = createBuildLoader( { userOptions, serverManifest: serverManifest ?? {}, staticManifest }, bundle, temporaryReferences, logger ); const { cssFilesByPage, globalCss } = processCssFilesForPages({ userOptions, autoDiscoveredFiles, serverManifest, staticManifest, bundle, logger }); if (userOptions.verbose) { logger.info( `[plugin.server] cssFilesByPage size: ${cssFilesByPage.size}` ); for (const [route, cssMap] of cssFilesByPage.entries()) { logger.info( `[plugin.server] Route ${route}: ${cssMap.size} CSS files` ); } } const indexHtml = staticManifest?.["index.html"]?.file; const serverPipeableStreamOptions = { ...userOptions.serverPipeableStreamOptions, bootstrapModules: [ ...indexHtml ? [baseURL(indexHtml)] : [], ...userOptions.serverPipeableStreamOptions?.bootstrapModules ?? [] ] }; userOptions.serverPipeableStreamOptions = serverPipeableStreamOptions; const clientPipeableStreamOptions = { ...userOptions.clientPipeableStreamOptions, bootstrapModules: [ ...indexHtml ? [baseURL(indexHtml)] : [], ...userOptions.clientPipeableStreamOptions?.bootstrapModules ?? [] ] }; const routes = !autoDiscoveredFiles ? [] : Array.from(autoDiscoveredFiles.urlMap.keys()); if (routes.length === 0) { logger?.info( "[plugin.server] No pages to generate, skipping static generation" ); return; } const serializedUserOptions = serializedOptions( userOptions, autoDiscoveredFiles ); if (globalWorker) { logger?.warn("[plugin.server] Global worker already exists, reusing existing worker"); worker = globalWorker; } else { const workerStartTime = performance.now(); const viteEnvPrefix = envPrefixFromConfig(resolvedConfig); const routeCount = autoDiscoveredFiles?.urlMap.size ?? 0; const maxListeners = routeCount + 1; const workerResult = await createWorker({ projectRoot: userOptions.projectRoot, workerPath: userOptions.htmlWorkerPath, currentCondition: "react-server", reverseCondition: "react-client", // HTML worker needs react-client for react-dom/server maxListeners, envPrefix: viteEnvPrefix, logger, workerData: { resolvedConfig: serializeResolvedConfig(resolvedConfig), userOptions: serializedUserOptions, configEnv } }); if (workerResult.type === "error") { if (workerResult.error != null) { throw workerResult.error; } throw new Error("React static plugin failed to create worker"); } else if (workerResult.type === "skip") { logger.info("Worker not created, skipping static build"); return; } else { worker = workerResult.worker; const workerStartupTime = performance.now() - workerStartTime; if (userOptions.onMetrics) { const workerStartupMetric = createWorkerStartupMetrics({ route: "/", // Worker startup is global, not route-specific workerType: "html", // This is the HTML worker for server-side static generation startupTime: workerStartupTime, fromMainThread: true, fromRscWorker: false, fromHtmlWorker: false, description: `HTML worker startup for server-side static generation` }); if (this.environment.name === "server") { userOptions.onMetrics(workerStartupMetric); } } globalWorker = worker; } } const { onEvent, ...handlerOptions } = userOptions; if (typeof userOptions.onEvent === "function") { try { const r = userOptions.onEvent({ type: "build.ssg.start", data: { pages: Array.from(autoDiscoveredFiles?.urlMap.keys() ?? []), options: null, // No specific rollup output options for static generation bundle } }); if (r != null && typeof r === "object" && "then" in r) { await r; } } catch (error) { const eventPanicError = handleError({ error, logger, panicThreshold: userOptions.panicThreshold, context: "onEvent(build.ssg.start)" }); if (eventPanicError != null) { throw eventPanicError; } else { throw new Error("Failed to emit build.ssg.start event"); } } } const renderMode = userOptions.build?.renderMode ?? "parallel"; const renderPages$1 = renderMode === "sequential" ? renderPages : renderPagesBatched; if (userOptions.verbose) { logger.info(`[static] Using ${renderMode} rendering${renderMode === "parallel" ? ` (batch size: ${userOptions.build?.batchSize ?? 8})` : ""}`); } const renderPagesGenerator = renderPages$1( routes, { ...handlerOptions, loader: buildLoader, worker, htmlWorker: worker, // Pass the HTML worker for HTML generation logger, // Pass global CSS to downstream renderer globalCss, // Pass abort signal to cancel operations when errors occur signal: AbortSignal.timeout(handlerOptions.htmlTimeout), onEvent, serverPipeableStreamOptions, clientPipeableStreamOptions, manifest: serverManifest ?? {}, staticManifest, // Pass static manifest for path resolution autoDiscoveredFiles, cssFilesByPage, batchSize: userOptions.build?.batchSize }, renderPage ); let finalResult; try { for await (const result of renderPagesGenerator) { if (result.type === "error") { throw result.error; } if (result.type === "success" && result.failedRoutes && result.failedRoutes.size > 0) { const firstError = result.failedRoutes.values().next().value; if (firstError != null && shouldCausePanic(firstError, { panicThreshold: userOptions.panicThreshold })) { throw firstError; } for (const [route, error] of result.failedRoutes) { const err = error instanceof Error ? error : toError(error); this.warn( new Error("Failed to render route: " + route + "\n" + err.message + "\n" + err.stack, { cause: err }) ); } } finalResult = result; } } catch (renderError) { const renderPanicError = handleError({ error: renderError, logger, panicThreshold: userOptions.panicThreshold, context: "renderPages" }); if (renderPanicError != null) { throw renderPanicError; } throw renderError; } if (!finalResult) { throw new Error("No render result produced"); } const duration = Math.round( performance.now() - (timing.renderStart || timing.start) ); this.info( `Rendered ${finalResult.completedRoutes.size} pages in ${duration}ms` ); if (typeof userOptions.onEvent === "function") { try { const r = userOptions.onEvent({ type: "build.ssg.end", data: { pages: Array.from(autoDiscoveredFiles?.urlMap.keys() ?? []), options: null, // No specific rollup output options for static generation bundle } }); if (r != null && typeof r === "object" && "then" in r) { await r; } } catch (error) { if (error != null) { throw error; } else { throw new Error("Failed to emit build.ssg.end event"); } } } if (process.env["NODE_ENV"] !== "production") { this.warn( `THIS BUILD IS NOT INTENDED FOR PRODUCTION (${process.env["NODE_ENV"]})` ); } timing.render = performance.now() - (timing.renderStart ?? timing.start); } catch (error) { panicError = handleError({ error, logger, panicThreshold: userOptions.panicThreshold}); } finally { autoDiscoveredFiles = null; serverManifest = void 0; if (worker) { try { worker.removeAllListeners(); await worker.terminate(); } catch (terminateError) { } worker = void 0; globalWorker = void 0; } } if (panicError != null) { const errorToThrow = panicError instanceof Error ? panicError : new Error(String(panicError)); const finalError = new Error(errorToThrow.message); finalError.stack = errorToThrow.stack; finalError.cause = errorToThrow.cause; if (errorToThrow.name) finalError.name = errorToThrow.name; throw finalError; } }, async closeBundle() { if (this.environment.name === "server") { try { const { existsSync } = await import('node:fs'); const { join: join2, resolve } = await import('node:path'); const resolvedOutDir = this.environment.config.build?.outDir ? resolve(this.environment.config.root || userOptions.projectRoot, this.environment.config.build.outDir) : resolve(userOptions.projectRoot, userOptions.build.outDir); const staticOutDir = join2(resolvedOutDir, userOptions.build.static || "static"); const staticVirtualDir = join2(staticOutDir, "_virtual"); if (existsSync(staticVirtualDir)) { const { rmSync } = await import('node:fs'); rmSync(staticVirtualDir, { recursive: true, force: true }); if (userOptions.verbose) { logger?.info(`[plugin.server] Cleaned up _virtual directory: ${staticVirtualDir}`); } } } catch (error) { if (userOptions.verbose) { logger?.warn(`[plugin.server] Failed to clean up _virtual directory: ${error}`); } } } if (worker) { try { await Promise.race([ new Promise((resolve, reject) => { const timeout = setTimeout(() => { reject(new Error("Worker shutdown timeout")); }, userOptions.workerShutdownTimeout); const backupTimeout = setTimeout(() => { reject(new Error("Worker shutdown backup timeout")); }, Math.floor(userOptions.workerShutdownTimeout * 0.6)); const messageHandler = (message) => { if (message.type === "SHUTDOWN_COMPLETE") { if (userOptions.verbose) { logger.info("Worker shutdown complete"); } clearTimeout(timeout); clearTimeout(backupTimeout); worker?.removeListener("message", messageHandler); worker?.removeAllListeners(); resolve(); } else if (message.type === "CLEANUP_COMPLETE") { if (userOptions.verbose) { logger.info("Worker cleanup completed during shutdown"); } } else { if (userOptions.verbose) { logger.info( "Worker is still busy, received message " + message?.type ); } } }; worker?.on("message", messageHandler); worker?.postMessage({ type: "SHUTDOWN", id: "*" }); }) ]); } catch (error) { this.warn( "Worker shutdown protocol failed, forcing termination: " + (error instanceof Error ? error.message : String(error)) ); } finally { if (worker) { try { worker.removeAllListeners(); worker.terminate(); } catch (terminateError) { } worker = void 0; globalWorker = void 0; } } } } }; }; export { reactStaticPlugin }; //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGx1Z2luLnNlcnZlci5qcyIsInNvdXJjZXMiOlsiLi4vLi4vLi4vcGx1Z2luL3JlYWN0LXN0YXRpYy9wbHVnaW4uc2VydmVyLnRzIl0sInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogcGx1Z2luLnRzXG4gKlxuICogUFVSUE9TRTogTWFpbiBWaXRlIHBsdWdpbiBmb3IgUmVhY3QgU2VydmVyIENvbXBvbmVudHMgKFJTQykgc3RhdGljIHNpdGUgZ2VuZXJhdGlvblxuICpcbiAqIFRoaXMgbW9kdWxlOlxuICogMS4gT3JjaGVzdHJhdGVzIHRoZSBlbnRpcmUgc3RhdGljIHNpdGUgZ2VuZXJhdGlvbiBwcm9jZXNzXG4gKiAyLiBNYW5hZ2VzIHRoZSBsaWZlY3ljbGUgb2YgdGhlIFJTQyByZW5kZXJpbmcgcHJvY2Vzc1xuICogMy4gSGFuZGxlcyBmaWxlIHdyaXRpbmcgZm9yIGJvdGggaW5pdGlhbCBwYWdlIGxvYWRzIGFuZCBjbGllbnQtc2lkZSBuYXZpZ2F0aW9uXG4gKiAgICAtIFdyaXRlcyAuaHRtbCBmaWxlcyBmb3IgaW5pdGlhbCBwYWdlIGxvYWRzIChjb21wbGV0ZSBIVE1MIGRvY3VtZW50KVxuICogICAgLSBXcml0ZXMgLnJzYyBmaWxlcyBmb3IgY2xpZW50LXNpZGUgbmF2aWdhdGlvbiAoUlNDIGNvbnRlbnQgb25seSlcbiAqIDQuIFByb3ZpZGVzIGhvb2tzIGZvciBWaXRlIHRvIGludGVncmF0ZSB3aXRoIHRoZSBidWlsZCBwcm9jZXNzXG4gKiA1LiBNYW5hZ2VzIHdvcmtlciB0aHJlYWRzIGZvciBwYXJhbGxlbCByZW5kZXJpbmdcbiAqIDYuIEhhbmRsZXMgZXJyb3IgcmVwb3J0aW5nIGFuZCBtZXRyaWNzIGNvbGxlY3Rpb25cbiAqL1xuXG5pbXBvcnQgdHlwZSB7IFdvcmtlciB9IGZyb20gXCJub2RlOndvcmtlcl90aHJlYWRzXCI7XG5pbXBvcnQge1xuICB0eXBlIENvbmZpZ0VudixcbiAgdHlwZSBMb2dnZXIsXG4gIHR5cGUgTWFuaWZlc3QsXG4gIHR5cGUgTWFuaWZlc3RDaHVuayxcbiAgdHlwZSBSZXNvbHZlZENvbmZpZyxcbiAgY3JlYXRlTG9nZ2VyLFxufSBmcm9tIFwidml0ZVwiO1xuaW1wb3J0IHsgcmVzb2x2ZU9wdGlvbnMgfSBmcm9tIFwiLi4vY29uZmlnL3Jlc29sdmVPcHRpb25zLmpzXCI7XG5pbXBvcnQgeyBjcmVhdGVCdWlsZExvYWRlciB9IGZyb20gXCIuL2NyZWF0ZUJ1aWxkTG9hZGVyLnNlcnZlci5qc1wiO1xuaW1wb3J0IHR5cGUge1xuICBCdWlsZFRpbWluZyxcbiAgUmVuZGVyUGFnZXNSZXN1bHQsXG4gIEF1dG9EaXNjb3ZlcmVkRmlsZXMsXG4gIFZpdGVQbHVnaW5Gbixcbn0gZnJvbSBcIi4uL3R5cGVzLmpzXCI7XG5pbXBvcnQgeyByZW5kZXJQYWdlc0JhdGNoZWQgfSBmcm9tIFwiLi9yZW5kZXJQYWdlc0JhdGNoZWQuanNcIjtcbmltcG9ydCB7IHJlbmRlclBhZ2VzIGFzIHJlbmRlclBhZ2VzU2VxdWVudGlhbCB9IGZyb20gXCIuL3JlbmRlclBhZ2VzLmpzXCI7XG5pbXBvcnQgeyBnZXRCdW5kbGVNYW5pZmVzdCB9IGZyb20gXCIuLi9oZWxwZXJzL2dldEJ1bmRsZU1hbmlmZXN0LmpzXCI7XG5pbXBvcnQgeyBjcmVhdGVXb3JrZXIgfSBmcm9tIFwiLi4vd29ya2VyL2NyZWF0ZVdvcmtlci5qc1wiO1xuaW1wb3J0IHtcbiAgc2VyaWFsaXplZE9wdGlvbnMsXG4gIHNlcmlhbGl6ZVJlc29sdmVkQ29uZmlnLFxufSBmcm9tIFwiLi4vaGVscGVycy9zZXJpYWxpemVVc2VyT3B0aW9ucy5qc1wiO1xuaW1wb3J0IHsgcGVyZm9ybWFuY2UgfSBmcm9tIFwibm9kZTpwZXJmX2hvb2tzXCI7XG5pbXBvcnQgeyBiYXNlVVJMIH0gZnJvbSBcIi4uL3V0aWxzL2VudlVybHMubm9kZS5qc1wiO1xuaW1wb3J0IHsgaGFuZGxlRXJyb3IgfSBmcm9tIFwiLi4vZXJyb3IvaGFuZGxlRXJyb3IuanNcIjtcbmltcG9ydCB7IHNob3VsZENhdXNlUGFuaWMgfSBmcm9tIFwiLi4vZXJyb3IvcGFuaWNUaHJlc2hvbGRIYW5kbGVyLmpzXCI7XG5pbXBvcnQgeyByZW5kZXJQYWdlIH0gZnJvbSBcIi4vcmVuZGVyUGFnZS5zZXJ2ZXIuanNcIjtcbmltcG9ydCB7IHRlbXBvcmFyeVJlZmVyZW5jZXMgfSBmcm9tIFwiLi90ZW1wb3JhcnlSZWZlcmVuY2VzLnNlcnZlci5qc1wiO1xuaW1wb3J0IHsgY29uZmlndXJlUHJldmlld1NlcnZlciB9IGZyb20gXCIuL2NvbmZpZ3VyZVByZXZpZXdTZXJ2ZXIuanNcIjtcbmltcG9ydCB7IGVudlByZWZpeEZyb21Db25maWcgfSBmcm9tIFwiLi4vY29uZmlnL2VudlByZWZpeEZyb21Db25maWcuanNcIjtcblxuaW1wb3J0IHsgcHJvY2Vzc0Nzc0ZpbGVzRm9yUGFnZXMgfSBmcm9tIFwiLi9wcm9jZXNzQ3NzRmlsZXNGb3JQYWdlcy5qc1wiO1xuaW1wb3J0IHsgY3JlYXRlV29ya2VyU3RhcnR1cE1ldHJpY3MgfSBmcm9tIFwiLi4vbWV0cmljcy9jcmVhdGVXb3JrZXJTdGFydHVwTWV0cmljcy5qc1wiO1xuaW1wb3J0IHsgdHJ5TWFuaWZlc3QgfSBmcm9tIFwiLi4vaGVscGVycy90cnlNYW5pZmVzdC5qc1wiO1xuaW1wb3J0IHsgam9pbiB9IGZyb20gXCJub2RlOnBhdGhcIjtcbmltcG9ydCB7IHJlc29sdmVBdXRvRGlzY292ZXIgfSBmcm9tIFwiLi4vY29uZmlnL2F1dG9EaXNjb3Zlci9yZXNvbHZlQXV0b0Rpc2NvdmVyLmpzXCI7XG5pbXBvcnQgeyBhc3NlcnRSZWFjdFNlcnZlciB9IGZyb20gXCIuLi9jb25maWcvZ2V0Q29uZGl0aW9uLmpzXCI7XG5pbXBvcnQgeyB0b0Vycm9yIH0gZnJvbSBcIi4uL2Vycm9yL3RvRXJyb3IuanNcIjtcblxuYXNzZXJ0UmVhY3RTZXJ2ZXIoKTtcblxuLyoqXG4gKiBNYWluIGVudHJ5cG9pbnQgZm9yIHRoZSBzdGF0aWMgcGx1Z2luLlxuICpcbiAqIFRoaXMgcGx1Z2luIGlzIHJlc3BvbnNpYmxlIGZvcjpcbiAqIDEuIE9yY2hlc3RyYXRpbmcgdGhlIHN0YXRpYyBzaXRlIGdlbmVyYXRpb24gcHJvY2Vzc1xuICogMi4gSGFuZGxpbmcgdGhlIGxpZmVjeWNsZSBvZiB0aGUgUlNDIHJlbmRlcmluZyBwcm9jZXNzIChtYWluIHRocmVhZClcbiAqIDMuIFdyaXRpbmcgLmh0bWwgZmlsZXMgZm9yIGluaXRpYWwgcGFnZSBsb2FkcyAoY29tcGxldGUgSFRNTCBkb2N1bWVudClcbiAqIDQuIFdyaXRpbmcgLnJzYyBmaWxlcyBmb3IgY2xpZW50LXNpZGUgbmF2aWdhdGlvbiAoUlNDIGNvbnRlbnQgb25seSlcbiAqIDUuIE1hbmFnaW5nIHdvcmtlciB0aHJlYWRzIGZvciBwYXJhbGxlbCByZW5kZXJpbmcgKGh0bWwgd29ya2VyKVxuICogNi4gSGFuZGxpbmcgZXJyb3IgcmVwb3J0aW5nIGFuZCBtZXRyaWNzIGNvbGxlY3Rpb25cbiAqL1xuLy8gR2xvYmFsIHdvcmtlciBpbnN0YW5jZSB0byBwcmV2ZW50IGR1cGxpY2F0ZSBjcmVhdGlvbiBhY3Jvc3MgcGx1Z2luIGluc3RhbmNlc1xubGV0IGdsb2JhbFdvcmtlcjogV29ya2VyIHwgdW5kZWZpbmVkO1xuXG5leHBvcnQgY29uc3QgcmVhY3RTdGF0aWNQbHVnaW46IFZpdGVQbHVnaW5GbiA9IGZ1bmN0aW9uIF9yZWFjdFN0YXRpY1BsdWdpbihcbiAgb3B0aW9uc1xuKSB7XG4gIGxldCB3b3JrZXI6IFdvcmtlciB8IHVuZGVmaW5lZDtcbiAgbGV0IGxvZ2dlcjogTG9nZ2VyO1xuICBsZXQgcmVzb2x2ZWRDb25maWc6IFJlc29sdmVkQ29uZmlnO1xuICBsZXQgYXV0b0Rpc2NvdmVyZWRGaWxlczogQXV0b0Rpc2NvdmVyZWRGaWxlcyB8IG51bGwgPSBudWxsO1xuICBsZXQgc2VydmVyTWFuaWZlc3Q6IE1hbmlmZXN0IHwgdW5kZWZpbmVkID0gdW5kZWZpbmVkO1xuICBsZXQgY29uZmlnRW52OiBDb25maWdFbnYgfCB1bmRlZmluZWQ7XG4gIGNvbnN0IHRpbWluZzogQnVpbGRUaW1pbmcgPSB7XG4gICAgc3RhcnQ6IHBlcmZvcm1hbmNlLm5vdygpLFxuICAgIGNvbmZpZ1Jlc29sdmVkOiAwLFxuICAgIGJ1aWxkU3RhcnQ6IDAsXG4gICAgcmVuZGVyU3RhcnQ6IDAsXG4gIH07XG5cbiAgY29uc3QgcmVzb2x2ZWRPcHRpb25zID0gcmVzb2x2ZU9wdGlvbnMob3B0aW9ucyk7XG4gIGlmIChyZXNvbHZlZE9wdGlvbnMudHlwZSA9PT0gXCJlcnJvclwiKSB7XG4gICAgdGhyb3cgcmVzb2x2ZWRPcHRpb25zLmVycm9yO1xuICB9XG4gIGNvbnN0IHVzZXJPcHRpb25zID0gcmVzb2x2ZWRPcHRpb25zLnVzZXJPcHRpb25zO1xuXG4gIHJldHVybiB7XG4gICAgbmFtZTogXCJ2aXRlOnBsdWdpbi1yZWFjdC1zZXJ2ZXIvc2VydmVyLXN0YXRpY1wiLFxuICAgIGVuZm9yY2U6IFwicG9zdFwiLFxuICAgIGFwaToge1xuICAgICAgbWV0YTogeyB0aW1pbmcgfSxcbiAgICB9LFxuICAgIGFzeW5jIGNvbmZpZyhfY29uZmlnLCB2aXRlQ29uZmlnRW52KSB7XG4gICAgICBjb25maWdFbnYgPSB2aXRlQ29uZmlnRW52O1xuICAgIH0sXG4gICAgYXBwbHlUb0Vudmlyb25tZW50KHBhcnRpYWxFbnZpcm9ubWVudCkge1xuXG4gICAgICBpZiAoXG4gICAgICAgIFtcInNlcnZlclwiXS5pbmNsdWRlcyhcbiAgICAgICAgICBwYXJ0aWFsRW52aXJvbm1lbnQubmFtZSBhcyBcImNsaWVudFwiIHwgXCJzZXJ2ZXJcIiB8IFwic3NyXCJcbiAgICAgICAgKVxuICAgICAgKSB7XG4gICAgICAgIHJldHVybiB0cnVlO1xuICAgICAgfVxuICAgICAgcmV0dXJuIGZhbHNlO1xuICAgIH0sXG4gICAgYXN5bmMgY29uZmlnUmVzb2x2ZWQoY29uZmlnKSB7XG4gICAgICByZXNvbHZlZENvbmZpZyA9IGNvbmZpZztcbiAgICAgIGlmICghbG9nZ2VyKSB7XG4gICAgICAgIGxvZ2dlciA9IGNvbmZpZy5jdXN0b21Mb2dnZXIgPz8gY3JlYXRlTG9nZ2VyKCk7XG4gICAgICB9XG4gICAgICBjb25zdCBhdXRvRGlzY292ZXJSZXN1bHQgPSBhd2FpdCByZXNvbHZlQXV0b0Rpc2NvdmVyKHtcbiAgICAgICAgY29uZmlnOiBjb25maWcsXG4gICAgICAgIGNvbmZpZ0VudjogY29uZmlnRW52ISxcbiAgICAgICAgdXNlck9wdGlvbnMsXG4gICAgICAgIGxvZ2dlcixcbiAgICAgIH0pO1xuICAgICAgaWYgKGF1dG9EaXNjb3ZlclJlc3VsdC50eXBlID09PSBcImVycm9yXCIpIHtcbiAgICAgICAgdGhyb3cgYXV0b0Rpc2NvdmVyUmVzdWx0LmVycm9yO1xuICAgICAgfVxuICAgICAgYXV0b0Rpc2NvdmVyZWRGaWxlcyA9IGF1dG9EaXNjb3ZlclJlc3VsdC5hdXRvRGlzY292ZXJlZEZpbGVzO1xuICAgIH0sXG4gICAgYXN5bmMgYnVpbGRTdGFydCgpIHtcbiAgICAgIGlmICghbG9nZ2VyKSB7XG4gICAgICAgIGxvZ2dlciA9IHRoaXMuZW52aXJvbm1lbnQubG9nZ2VyO1xuICAgICAgfVxuICAgICAgdGltaW5nLmJ1aWxkU3RhcnQgPSBwZXJmb3JtYW5jZS5ub3coKTtcbiAgICAgIGlmICh1c2VyT3B0aW9ucy5vbkV2ZW50ICYmIGF1dG9EaXNjb3ZlcmVkRmlsZXMpIHtcbiAgICAgICAgdHJ5IHtcbiAgICAgICAgICB1c2VyT3B0aW9ucy5vbkV2ZW50KHtcbiAgICAgICAgICAgIHR5cGU6IFwiYnVpbGQuc3RhcnRcIixcbiAgICAgICAgICAgIGRhdGE6IHtcbiAgICAgICAgICAgICAgcGFnZXM6IEFycmF5LmZyb20oYXV0b0Rpc2NvdmVyZWRGaWxlcy51cmxNYXAua2V5cygpKSxcbiAgICAgICAgICAgICAgZmlsZXM6IGF1dG9EaXNjb3ZlcmVkRmlsZXMsXG4gICAgICAgICAgICB9LFxuICAgICAgICAgIH0pO1xuICAgICAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgICAgIGNvbnN0IHBhbmljRXJyb3IgPSBoYW5kbGVFcnJvcih7XG4gICAgICAgICAgICBlcnJvcixcbiAgICAgICAgICAgIGxvZ2dlcjogbG9nZ2VyLFxuICAgICAgICAgICAgcGFuaWNUaHJlc2hvbGQ6IHVzZXJPcHRpb25zLnBhbmljVGhyZXNob2xkLFxuICAgICAgICAgICAgY29udGV4dDogXCJidWlsZFN0YXJ0XCIsXG4gICAgICAgICAgfSk7XG4gICAgICAgICAgaWYgKHBhbmljRXJyb3IgIT0gbnVsbCkge1xuICAgICAgICAgICAgd29ya2VyPy50ZXJtaW5hdGUoKTtcbiAgICAgICAgICAgIHRocm93IHBhbmljRXJyb3I7XG4gICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIHRoaXMuZXJyb3IobmV3IEVycm9yKFwiRmFpbGVkIHRvIGVtaXQgYnVpbGQuc3RhcnQgZXZlbnRcIikpO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgfVxuICAgIH0sXG5cbiAgICAvLyB0aGUgcHJldmlldyBzZXJ2ZXIgaGVscHMgdG8gdmlldyB0aGUgZ2VuZXJhdGVkIHN0YXRpYyBmb2xkZXIsIGJ1dCBvbmx5IHdoZW4gdGhlIHN0YXRpYyBwbHVnaW4gaXMgZW5hYmxlZFxuICAgIC8vIGlmIG5vIGJ1aWxkLnBhZ2VzLCB0aGVuIHRoZSBwcmV2aWV3IHNlcnZlciB3aWxsIGluc3RlYWQgdXNlIGRlZmF1bHQgdml0ZSBwcmV2aWV3IHNlcnZlclxuICAgIC8vIGl0IHdvcmtzIHRoZSBzYW1lIHVuZGVyIGJvdGggY29uZGl0aW9uc1xuICAgIGFzeW5jIGNvbmZpZ3VyZVByZXZpZXdTZXJ2ZXIoc2VydmVyKSB7XG4gICAgICBsb2dnZXIgPSBzZXJ2ZXIuY29uZmlnLmN1c3RvbUxvZ2dlciB8fCBzZXJ2ZXIuY29uZmlnLmxvZ2dlcjtcbiAgICAgIGNvbmZpZ3VyZVByZXZpZXdTZXJ2ZXIoe1xuICAgICAgICBzZXJ2ZXIsXG4gICAgICAgIHVzZXJPcHRpb25zLFxuICAgICAgfSk7XG4gICAgfSxcblxuICAgIGFzeW5jIHJlbmRlclN0YXJ0KCkge1xuICAgICAgdGltaW5nLnJlbmRlclN0YXJ0ID0gcGVyZm9ybWFuY2Uubm93KCk7XG4gICAgfSxcbiAgICBnZW5lcmF0ZUJ1bmRsZShfb3B0aW9ucywgYnVuZGxlKSB7XG4gICAgICAvLyBGaWx0ZXIgb3V0IHVubmVjZXNzYXJ5IF92aXJ0dWFsIGZpbGVzIGZyb20gdGhlIGJ1bmRsZVxuICAgICAgLy8gS2VlcCBkeW5hbWljLWltcG9ydC1oZWxwZXIuanMgc2luY2UgaXQncyBuZWVkZWQgZm9yIGR5bmFtaWMgaW1wb3J0c1xuICAgICAgLy8gTm90ZTogU3RhdGljIGJ1aWxkcyBhcmUgaGFuZGxlZCBieSBwbHVnaW4uY2xpZW50LnRzLCB0aGlzIG9ubHkgaGFuZGxlcyBzZXJ2ZXIgYnVpbGRzXG4gICAgICBpZiAodGhpcy5lbnZpcm9ubWVudC5uYW1lID09PSBcInNlcnZlclwiKSB7XG4gICAgICAgIGNvbnN0IGtleXNUb0RlbGV0ZTogc3RyaW5nW10gPSBbXTtcbiAgICAgICAgZm9yIChjb25zdCBba2V5LCBjaHVua10gb2YgT2JqZWN0LmVudHJpZXMoYnVuZGxlKSkge1xuICAgICAgICAgIGlmIChjaHVuay50eXBlID09PSBcImNodW5rXCIpIHtcbiAgICAgICAgICAgIC8vIENoZWNrIGZpbGVOYW1lLCBrZXksIG1vZHVsZUlkcywgYW5kIGZhY2FkZU1vZHVsZUlkIGZvciBfdmlydHVhbFxuICAgICAgICAgICAgY29uc3QgaXNWaXJ0dWFsID0gXG4gICAgICAgICAgICAgIGNodW5rLmZpbGVOYW1lPy5pbmNsdWRlcyhcIl92aXJ0dWFsXCIpIHx8XG4gICAgICAgICAgICAgIGtleS5pbmNsdWRlcyhcIl92aXJ0dWFsXCIpIHx8XG4gICAgICAgICAgICAgIGNodW5rLmZhY2FkZU1vZHVsZUlkPy5pbmNsdWRlcyhcIl92aXJ0dWFsXCIpIHx8XG4gICAgICAgICAgICAgIGNodW5rLm1vZHVsZUlkcz8uc29tZShpZCA9PiBpZC5pbmNsdWRlcyhcIl92aXJ0dWFsXCIpKTtcbiAgICAgICAgICAgIFxuICAgICAgICAgICAgLy8gS2VlcCBkeW5hbWljLWltcG9ydC1oZWxwZXIuanMgLSBpdCdzIG5lZWRlZCBmb3IgZHluYW1pYyBpbXBvcnRzXG4gICAgICAgICAgICBjb25zdCBpc0R5bmFtaWNJbXBvcnRIZWxwZXIgPSBcbiAgICAgICAgICAgICAgY2h1bmsuZmlsZU5hbWU/LmluY2x1ZGVzKFwiZHluYW1pYy1pbXBvcnQtaGVscGVyXCIpIHx8XG4gICAgICAgICAgICAgIGtleS5pbmNsdWRlcyhcImR5bmFtaWMtaW1wb3J0LWhlbHBlclwiKTtcbiAgICAgICAgICAgIFxuICAgICAgICAgICAgaWYgKGlzVmlydHVhbCAmJiAhaXNEeW5hbWljSW1wb3J0SGVscGVyKSB7XG4gICAgICAgICAgICAgIGtleXNUb0RlbGV0ZS5wdXNoKGtleSk7XG4gICAgICAgICAgICAgIGlmICh1c2VyT3B0aW9ucy52ZXJib3NlKSB7XG4gICAgICAgICAgICAgICAgbG9nZ2VyPy5pbmZvKGBbcGx1Z2luLnNlcnZlcl0gRmlsdGVyZWQgb3V0IHZpcnR1YWwgZmlsZTogJHtjaHVuay5maWxlTmFtZSB8fCBrZXl9IChtb2R1bGVJZDogJHtjaHVuay5mYWNhZGVNb2R1bGVJZCB8fCBjaHVuay5tb2R1bGVJZHM/LlswXX0pYCk7XG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgLy8gRGVsZXRlIGFmdGVyIGl0ZXJhdGlvbiB0byBhdm9pZCBtb2RpZnlpbmcgd2hpbGUgaXRlcmF0aW5nXG4gICAgICAgIGZvciAoY29uc3Qga2V5IG9mIGtleXNUb0RlbGV0ZSkge1xuICAgICAgICAgIGRlbGV0ZSBidW5kbGVba2V5XTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH0sXG5cbiAgICBhc3luYyB3cml0ZUJ1bmRsZShfb3B0aW9ucywgYnVuZGxlKSB7XG4gICAgICAvLyBPbmx5IGV4ZWN1dGUgc3RhdGljIGdlbmVyYXRpb24gZm9yIHRoZSBzZXJ2ZXIgZW52aXJvbm1lbnRcbiAgICAgIGlmICh0aGlzLmVudmlyb25tZW50Lm5hbWUgIT09IFwic2VydmVyXCIpIHtcbiAgICAgICAgaWYgKHVzZXJPcHRpb25zLnZlcmJvc2UpIHtcbiAgICAgICAgICBsb2dnZXI/LmluZm8oYFtwbHVnaW4uc2VydmVyXSBTa2lwcGluZyBzdGF0aWMgZ2VuZXJhdGlvbiBmb3IgZW52aXJvbm1lbnQ6ICR7dGhpcy5lbnZpcm9ubWVudC5uYW1lfWApO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybjtcbiAgICAgIH1cbiAgICAgIFxuICAgICAgbGV0IHBhbmljRXJyb3I6IEVycm9yIHwgbnVsbCA9IG51bGw7XG4gICAgICBsZXQgYnVuZGxlTWFuaWZlc3Q6XG4gICAgICAgIHwge1xuICAgICAgICAgICAgW2tleTogc3RyaW5nXTogTWFuaWZlc3RDaHVuayAmIHtcbiAgICAgICAgICAgICAgc291cmNlOiBzdHJpbmc7XG4gICAgICAgICAgICB9O1xuICAgICAgICAgIH1cbiAgICAgICAgfCB1bmRlZmluZWQgPSB1bmRlZmluZWQ7XG4gICAgICBpZiAoIWxvZ2dlcikge1xuICAgICAgICBsb2dnZXIgPSB0aGlzLmVudmlyb25tZW50LmxvZ2dlcjtcbiAgICAgIH1cblxuICAgICAgLy8gaGFuZGxlIHRoZSBidW5kbGUgbWFuaWZlc3RcbiAgICAgIHRyeSB7XG4gICAgICAgIGJ1bmRsZU1hbmlmZXN0ID0gZ2V0QnVuZGxlTWFuaWZlc3Q8ZmFsc2U+KHtcbiAgICAgICAgICBidW5kbGUsXG4gICAgICAgICAgbm9ybWFsaXplcjogdXNlck9wdGlvbnMubm9ybWFsaXplcixcbiAgICAgICAgfSk7XG5cbiAgICAgICAgLy8gbWFrZSBzdXJlIHRoYXQgd2UgaGF2ZSBhIG1hbmlmZXN0XG4gICAgICAgIGNvbnN0IG1hbmlmZXN0UGF0aCA9XG4gICAgICAgICAgdHlwZW9mIHJlc29sdmVkQ29uZmlnLmJ1aWxkLm1hbmlmZXN0ID09PSBcInN0cmluZ1wiXG4gICAgICAgICAgICA/IHJlc29sdmVkQ29uZmlnLmJ1aWxkLm1hbmlmZXN0XG4gICAgICAgICAgICA6IFwiLnZpdGUvbWFuaWZlc3QuanNvblwiO1xuICAgICAgICBpZiAoXG4gICAgICAgICAgIWJ1bmRsZU1hbmlmZXN0W21hbmlmZXN0UGF0aF0gfHxcbiAgICAgICAgICAhKFwic291cmNlXCIgaW4gYnVuZGxlTWFuaWZlc3RbbWFuaWZlc3RQYXRoXSlcbiAgICAgICAgKSB7XG4gICAgICAgICAgdGhyb3cgbmV3IEVycm9yKFwiU2VydmVyIG1hbmlmZXN0IG5vdCBmb3VuZFwiKTtcbiAgICAgICAgfVxuXG4gICAgICAgIC8vIHBhcnNlIHRoZSBtYW5pZmVzdFxuICAgICAgICBzZXJ2ZXJNYW5pZmVzdCA9IEpTT04ucGFyc2UoXG4gICAgICAgICAgYnVuZGxlTWFuaWZlc3RbbWFuaWZlc3RQYXRoXS5zb3VyY2UgYXMgc3RyaW5nXG4gICAgICAgICk7XG5cbiAgICAgICAgLy8gbWFrZSBzdXJlIHRoYXQgd2UgaGF2ZSBhIG1hbmlmZXN0XG4gICAgICAgIGlmICghc2VydmVyTWFuaWZlc3QpIHtcbiAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXCJGYWlsZWQgdG8gcGFyc2Ugc2VydmVyIG1hbmlmZXN0XCIpO1xuICAgICAgICB9XG4gICAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgICBjb25zdCBwYW5pY0Vycm9yID0gaGFuZGxlRXJyb3Ioe1xuICAgICAgICAgIGVycm9yLFxuICAgICAgICAgIGxvZ2dlcjogbG9nZ2VyLFxuICAgICAgICAgIHBhbmljVGhyZXNob2xkOiB1c2VyT3B0aW9ucy5wYW5pY1RocmVzaG9sZCxcbiAgICAgICAgICBjb250ZXh0OiBcIndyaXRlQnVuZGxlKGJ1bmRsZU1hbmlmZXN0KVwiLFxuICAgICAgICB9KTtcbiAgICAgICAgaWYgKHBhbmljRXJyb3IgIT0gbnVsbCkge1xuICAgICAgICAgIHRocm93IHBhbmljRXJyb3I7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgdGhyb3cgbmV3IEVycm9yKFwiRmFpbGVkIHRvIGdldCBidW5kbGUgbWFuaWZlc3RcIik7XG4gICAgICAgIH1cbiAgICAgIH1cblxuICAgICAgdHJ5IHtcbiAgICAgICAgY29uc3Qgc3RhdGljTWFuaWZlc3RSZXN1bHQgPSBhd2FpdCB0cnlNYW5pZmVzdCh7XG4gICAgICAgICAgcm9vdDogdXNlck9wdGlvbnMucHJvamVjdFJvb3QsXG4gICAgICAgICAgb3V0RGlyOiBqb2luKHVzZXJPcHRpb25zLmJ1aWxkLm91dERpciwgdXNlck9wdGlvbnMuYnVpbGQuc3RhdGljKSxcbiAgICAgICAgICBtYW5pZmVzdFBhdGg6IHJlc29sdmVkQ29uZmlnLmJ1aWxkLm1hbmlmZXN0LFxuICAgICAgICAgIHNzck1hbmlmZXN0OiBmYWxzZSxcbiAgICAgICAgfSk7XG4gICAgICAgIGlmIChzdGF0aWNNYW5pZmVzdFJlc3VsdC50eXBlID09PSBcImVycm9yXCIpIHtcbiAgICAgICAgICB0aHJvdyBzdGF0aWNNYW5pZmVzdFJlc3VsdC5lcnJvcjtcbiAgICAgICAgfVxuICAgICAgICBjb25zdCBzdGF0aWNNYW5pZmVzdCA9IHN0YXRpY01hbmlmZXN0UmVzdWx0Lm1hbmlmZXN0O1xuICAgICAgICBcbiAgICAgICAgLy8gRG9uJ3QgY3JlYXRlIGhlbHBlciBmaWxlIC0gbGV0IHJlc29sdmVWaXJ0dWFsQW5kTm9kZU1vZHVsZXMgc2hpbSBoYW5kbGUgaXRcbiAgICAgICAgLy8gU2FtZSBhcHByb2FjaCBhcyBjbGllbnQgZW52aXJvbm1lbnQgLSBubyBzcGVjaWFsIGZpbGUgbmVlZGVkXG4gICAgICAgIFxuICAgICAgICBjb25zdCBidWlsZExvYWRlciA9IGNyZWF0ZUJ1aWxkTG9hZGVyKFxuICAgICAgICAgIHtcbiAgICAgICAgICAgIHVzZXJPcHRpb25zOiB1c2VyT3B0aW9ucyxcbiAgICAgICAgICAgIHNlcnZlck1hbmlmZXN0OiBzZXJ2ZXJNYW5pZmVzdCA/PyB7fSxcbiAgICAgICAgICAgIHN0YXRpY01hbmlmZXN0OiBzdGF0aWNNYW5pZmVzdCxcbiAgICAgICAgICB9LFxuICAgICAgICAgIGJ1bmRsZSxcbiAgICAgICAgICB0ZW1wb3JhcnlSZWZlcmVuY2VzLFxuICAgICAgICAgIGxvZ2dlclxuICAgICAgICApO1xuICAgICAgICAvLyBDcmVhdGUgQ1NTIHByb3BzIGZvciBlYWNoIENTUyBmaWxlXG4gICAgICAgIGNvbnN0IHsgY3NzRmlsZXNCeVBhZ2UsIGdsb2JhbENzcyB9ID0gcHJvY2Vzc0Nzc0ZpbGVzRm9yUGFnZXMoe1xuICAgICAgICAgIHVzZXJPcHRpb25zLFxuICAgICAgICAgIGF1dG9EaXNjb3ZlcmVkRmlsZXMsXG4gICAgICAgICAgc2VydmVyTWFuaWZlc3QsXG4gICAgICAgICAgc3RhdGljTWFuaWZlc3QsXG4gICAgICAgICAgYnVuZGxlLFxuICAgICAgICAgIGxvZ2dlcixcbiAgICAgICAgfSk7XG5cbiAgICAgICAgaWYgKHVzZXJPcHRpb25zLnZlcmJvc2UpIHtcbiAgICAgICAgICBsb2dnZXIuaW5mbyhcbiAgICAgICAgICAgIGBbcGx1Z2luLnNlcnZlcl0gY3NzRmlsZXNCeVBhZ2Ugc2l6ZTogJHtjc3NGaWxlc0J5UGFnZS5zaXplfWBcbiAgICAgICAgICApO1xuICAgICAgICAgIGZvciAoY29uc3QgW3JvdXRlLCBjc3NNYXBdIG9mIGNzc0ZpbGVzQnlQYWdlLmVudHJpZXMoKSkge1xuICAgICAgICAgICAgbG9nZ2VyLmluZm8oXG4gICAgICAgICAgICAgIGBbcGx1Z2luLnNlcnZlcl0gUm91dGUgJHtyb3V0ZX06ICR7Y3NzTWFwLnNpemV9IENTUyBmaWxlc2BcbiAgICAgICAgICAgICk7XG4gICAgICAgICAgICBcbiAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICBjb25zdCBpbmRleEh0bWwgPSBzdGF0aWNNYW5pZmVzdD8uW1wiaW5kZXguaHRtbFwiXT8uZmlsZTtcbiAgICAgICAgY29uc3Qgc2VydmVyUGlwZWFibGVTdHJlYW1PcHRpb25zID0ge1xuICAgICAgICAgIC4uLnVzZXJPcHRpb25zLnNlcnZlclBpcGVhYmxlU3RyZWFtT3B0aW9ucyxcbiAgICAgICAgICBib290c3RyYXBNb2R1bGVzOiBbXG4gICAgICAgICAgICAuLi4oaW5kZXhIdG1sID8gW2Jhc2VVUkwoaW5kZXhIdG1sKV0gOiBbXSksXG4gICAgICAgICAgICAuLi4odXNlck9wdGlvbnMuc2VydmVyUGlwZWFibGVTdHJlYW1PcHRpb25zPy5ib290c3RyYXBNb2R1bGVzID8/XG4gICAgICAgICAgICAgIFtdKSxcbiAgICAgICAgICBdLFxuICAgICAgICB9O1xuICAgICAgICB1c2VyT3B0aW9ucy5zZXJ2ZXJQaXBlYWJsZVN0cmVhbU9wdGlvbnMgPSBzZXJ2ZXJQaXBlYWJsZVN0cmVhbU9wdGlvbnM7XG4gICAgICAgIGNvbnN0IGNsaWVudFBpcGVhYmxlU3RyZWFtT3B0aW9ucyA9IHtcbiAgICAgICAgICAuLi51c2VyT3B0aW9ucy5jbGllbnRQaXBlYWJsZVN0cmVhbU9wdGlvbnMsXG4gICAgICAgICAgYm9vdHN0cmFwTW9kdWxlczogW1xuICAgICAgICAgICAgLi4uKGluZGV4SHRtbCA/IFtiYXNlVVJMKGluZGV4SHRtbCldIDogW10pLFxuICAgICAgICAgICAgLi4uKHVzZXJPcHRpb25zLmNsaWVudFBpcGVhYmxlU3RyZWFtT3B0aW9ucz8uYm9vdHN0cmFwTW9kdWxlcyA/P1xuICAgICAgICAgICAgICBbXSksXG4gICAgICAgICAgXSxcbiAgICAgICAgfTtcbiAgICAgICAgLy8gR2V0IHJvdXRlcyBmb3Igd29ya2VyIGNvbmZpZ3VyYXRpb25cbiAgICAgICAgY29uc3Qgcm91dGVzID0gIWF1dG9EaXNjb3ZlcmVkRmlsZXNcbiAgICAgICAgICA/IFtdXG4gICAgICAgICAgOiBBcnJheS5mcm9tKGF1dG9EaXNjb3ZlcmVkRmlsZXMhLnVybE1hcC5rZXlzKCkpO1xuXG4gICAgICAgIC8vIElmIG5vIHBhZ2VzIHRvIGdlbmVyYXRlLCBza2lwIHN0YXRpYyBnZW5lcmF0aW9uIGVudGlyZWx5IChpbmNsdWRpbmcgd29ya2VyIGNyZWF0aW9uKVxuICAgICAgICBpZiAocm91dGVzLmxlbmd0aCA9PT0gMCkge1xuICAgICAgICAgIGxvZ2dlcj8uaW5mbyhcbiAgICAgICAgICAgIFwiW3BsdWdpbi5zZXJ2ZXJdIE5vIHBhZ2VzIHRvIGdlbmVyYXRlLCBza2lwcGluZyBzdGF0aWMgZ2VuZXJhdGlvblwiXG4gICAgICAgICAgKTtcbiAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cblxuICAgICAgICBjb25zdCBzZXJpYWxpemVkVXNlck9wdGlvbnMgPSBzZXJpYWxpemVkT3B0aW9ucyhcbiAgICAgICAgICB1c2VyT3B0aW9ucyxcbiAgICAgICAgICBhdXRvRGlzY292ZXJlZEZpbGVzIVxuICAgICAgICApO1xuICAgICAgICAvLyBDcmVhdGUgSFRNTCB3b3JrZXIgZm9yIEhUTUwgZ2VuZXJhdGlvblxuICAgICAgICAvLyBJTVBPUlRBTlQ6IFdlIGNyZWF0ZSBhIG5ldyB3b3JrZXIgZm9yIGVhY2ggcGFnZSByZW5kZXIgdG8gZW5zdXJlIGNvbXBsZXRlbHkgY2xlYW4gc3RhdGVcbiAgICAgICAgLy8gVGhpcyBwcmV2ZW50cyByYWNlIGNvbmRpdGlvbnMgd2hlcmUgd29ya2VyIHN0YXRlIHBlcnNpc3RzIGJldHdlZW4gcmVuZGVyc1xuICAgICAgICAvLyBHdWFyZCBhZ2FpbnN0IGR1cGxpY2F0ZSB3b3JrZXIgY3JlYXRpb24gaWYgcGx1Z2luIGlzIGluc3RhbnRpYXRlZCBtdWx0aXBsZSB0aW1lc1xuICAgICAgICBpZiAoZ2xvYmFsV29ya2VyKSB7XG4gICAgICAgICAgbG9nZ2VyPy53YXJuKFwiW3BsdWdpbi5zZXJ2ZXJdIEdsb2JhbCB3b3JrZXIgYWxyZWFkeSBleGlzdHMsIHJldXNpbmcgZXhpc3Rpbmcgd29ya2VyXCIpO1xuICAgICAgICAgIHdvcmtlciA9IGdsb2JhbFdvcmtlcjtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICBjb25zdCB3b3JrZXJTdGFydFRpbWUgPSBwZXJmb3JtYW5jZS5ub3coKTtcbiAgICAgICAgICBjb25zdCB2aXRlRW52UHJlZml4ID0gZW52UHJlZml4RnJvbUNvbmZpZyhyZXNvbHZlZENvbmZpZyk7XG4gICAgICAgICAgY29uc3Qgcm91dGVDb3VudCA9IGF1dG9EaXNjb3ZlcmVkRmlsZXM/LnVybE1hcC5zaXplID8/IDA7XG4gICAgICAgICAgY29uc3QgbWF4TGlzdGVuZXJzID0gcm91dGVDb3VudCArIDE7XG4gICAgICAgICAgY29uc3Qgd29ya2VyUmVzdWx0ID0gYXdhaXQgY3JlYXRlV29ya2VyKHtcbiAgICAgICAgICAgIHByb2plY3RSb290OiB1c2VyT3B0aW9ucy5wcm9qZWN0Um9vdCxcbiAgICAgICAgICAgIHdvcmtlclBhdGg6IHVzZXJPcHRpb25zLmh0bWxXb3JrZXJQYXRoLFxuICAgICAgICAgICAgY3VycmVudENvbmRpdGlvbjogXCJyZWFjdC1zZXJ2ZXJcIixcbiAgICAgICAgICAgIHJldmVyc2VDb25kaXRpb246IFwicmVhY3QtY2xpZW50XCIsIC8vIEhUTUwgd29ya2VyIG5lZWRzIHJlYWN0LWNsaWVudCBmb3IgcmVhY3QtZG9tL3NlcnZlclxuICAgICAgICAgICAgbWF4TGlzdGVuZXJzOiBtYXhMaXN0ZW5lcnMsXG4gICAgICAgICAgICBlbnZQcmVmaXg6IHZpdGVFbnZQcmVmaXgsXG4gICAgICAgICAgICBsb2dnZXI6IGxvZ2dlcixcbiAgICAgICAgICAgIHdvcmtlckRhdGE6IHtcbiAgICAgICAgICAgICAgcmVzb2x2ZWRDb25maWc6IHNlcmlhbGl6ZVJlc29sdmVkQ29uZmlnKHJlc29sdmVkQ29uZmlnKSxcbiAgICAgICAgICAgICAgdXNlck9wdGlvbnM6IHNlcmlhbGl6ZWRVc2VyT3B0aW9ucyxcbiAgICAgICAgICAgICAgY29uZmlnRW52LFxuICAgICAgICAgICAgfSxcbiAgICAgICAgICB9KTtcbiAgICAgICAgICBpZiAod29ya2VyUmVzdWx0LnR5cGUgPT09IFwiZXJyb3JcIikge1xuICAgICAgICAgICAgaWYgKHdvcmtlclJlc3VsdC5lcnJvciAhPSBudWxsKSB7XG4gICAgICAgICAgICAgIHRocm93IHdvcmtlclJlc3VsdC5lcnJvcjtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcihcIlJlYWN0IHN0YXRpYyBwbHVnaW4gZmFpbGVkIHRvIGNyZWF0ZSB3b3JrZXJcIik7XG4gICAgICAgICAgfSBlbHNlIGlmICh3b3JrZXJSZXN1bHQudHlwZSA9PT0gXCJza2lwXCIpIHtcbiAgICAgICAgICAgIGxvZ2dlci5pbmZvKFwiV29ya2VyIG5vdCBjcmVhdGVkLCBza2lwcGluZyBzdGF0aWMgYnVpbGRcIik7XG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIHdvcmtlciA9IHdvcmtlclJlc3VsdC53b3JrZXI7XG4gICAgICAgICAgICAvLyBFbWl0IHdvcmtlciBzdGFydHVwIG1ldHJpYyBhZnRlciB3b3JrZXIgaXMgY3JlYXRlZFxuICAgICAgICAgICAgY29uc3Qgd29ya2VyU3RhcnR1cFRpbWUgPSBwZXJmb3JtYW5jZS5ub3coKSAtIHdvcmtlclN0YXJ0VGltZTtcbiAgICAgICAgICAgIGlmICh1c2VyT3B0aW9ucy5vbk1ldHJpY3MpIHtcbiAgICAgICAgICAgICAgY29uc3Qgd29ya2VyU3RhcnR1cE1ldHJpYyA9IGNyZWF0ZVdvcmtlclN0YXJ0dXBNZXRyaWNzKHtcbiAgICAgICAgICAgICAgICByb3V0ZTogXCIvXCIsIC8vIFdvcmtlciBzdGFydHVwIGlzIGdsb2JhbCwgbm90IHJvdXRlLXNwZWNpZmljXG4gICAgICAgICAgICAgICAgd29ya2VyVHlwZTogXCJodG1sXCIsIC8vIFRoaXMgaXMgdGhlIEhUTUwgd29ya2VyIGZvciBzZXJ2ZXItc2lkZSBzdGF0aWMgZ2VuZXJhdGlvblxuICAgICAgICAgICAgICAgIHN0YXJ0dXBUaW1lOiB3b3JrZXJTdGFydHVwVGltZSxcbiAgICAgICAgICAgICAgICBmcm9tTWFpblRocmVhZDogdHJ1ZSxcbiAgICAgICAgICAgICAgICBmcm9tUnNjV29ya2VyOiBmYWxzZSxcbiAgICAgICAgICAgICAgICBmcm9tSHRtbFdvcmtlcjogZmFsc2UsXG4gICAgICAgICAgICAgICAgZGVzY3JpcHRpb246IGBIVE1MIHdvcmtlciBzdGFydHVwIGZvciBzZXJ2ZXItc2lkZSBzdGF0aWMgZ2VuZXJhdGlvbmAsXG4gICAgICAgICAgICAgIH0pO1xuICAgICAgICAgICAgICAvLyBPbmx5IGVtaXQgbWV0cmljcyBmcm9tIHRoZSBzZXJ2ZXIgZW52aXJvbm1lbnQgdG8gcHJldmVudCBkdXBsaWNhdGVzXG4gICAgICAgICAgICAgIGlmICh0aGlzLmVudmlyb25tZW50Lm5hbWUgPT09IFwic2VydmVyXCIpIHtcbiAgICAgICAgICAgICAgICB1c2VyT3B0aW9ucy5vbk1ldHJpY3Mod29ya2VyU3RhcnR1cE1ldHJpYyk7XG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIC8vIFN0b3JlIHRoZSB3b3JrZXIgZ2xvYmFsbHkgdG8gcHJldmVudCBkdXBsaWNhdGUgY3JlYXRpb25cbiAgICAgICAgICAgIGdsb2JhbFdvcmtlciA9IHdvcmtlcjtcbiAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICAvLyBObyBSU0Mgd29ya2VyIG5lZWRlZCBmb3Igc3RhdGljIGdlbmVyYXRpb24gLSBtYWluIHRocmVhZCBydW5zIHdpdGggcmVhY3Qtc2VydmVyIGNvbmRpdGlvbnNcbiAgICAgICAgLy8gUmVuZGVyIHBhZ2VzIC0gY29tcG9uZW50IHJlc29sdXRpb24gbm93IGhhcHBlbnMgcGVyLXJvdXRlIGluIHJlbmRlclBhZ2VcbiAgICAgICAgY29uc3QgeyBvbkV2ZW50LCAuLi5oYW5kbGVyT3B0aW9ucyB9ID0gdXNlck9wdGlvbnM7XG5cbiAgICAgICAgLy8gRW1pdCB0aGUgc3RhdGljIHNpdGUgZ2VuZXJhdGlvbiBzdGFydCBldmVudFxuICAgICAgICBpZiAodHlwZW9mIHVzZXJPcHRpb25zLm9uRXZlbnQgPT09IFwiZnVuY3Rpb25cIikge1xuICAgICAgICAgIHRyeSB7XG4gICAgICAgICAgICBjb25zdCByID0gdXNlck9wdGlvbnMub25FdmVudCh7XG4gICAgICAgICAgICAgIHR5cGU6IFwiYnVpbGQuc3NnLnN0YXJ0XCIsXG4gICAgICAgICAgICAgIGRhdGE6IHtcbiAgICAgICAgICAgICAgICBwYWdlczogQXJyYXkuZnJvbShhdXRvRGlzY292ZXJlZEZpbGVzPy51cmxNYXAua2V5cygpID8/IFtdKSxcbiAgICAgICAgICAgICAgICBvcHRpb25zOiBudWxsIGFzIGFueSwgLy8gTm8gc3BlY2lmaWMgcm9sbHVwIG91dHB1dCBvcHRpb25zIGZvciBzdGF0aWMgZ2VuZXJhdGlvblxuICAgICAgICAgICAgICAgIGJ1bmRsZTogYnVuZGxlLFxuICAgICAgICAgICAgICB9LFxuICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICBpZiAociAhPSBudWxsICYmIHR5cGVvZiByID09PSBcIm9iamVjdFwiICYmIFwidGhlblwiIGluIHIpIHtcbiAgICAgICAgICAgICAgYXdhaXQgKHIgYXMgUHJvbWlzZTxhbnk+KTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgICAgICAgY29uc3QgZXZlbnRQYW5pY0Vycm9yID0gaGFuZGxlRXJyb3Ioe1xuICAgICAgICAgICAgICBlcnJvcixcbiAgICAgICAgICAgICAgbG9nZ2VyOiBsb2dnZXIsXG4gICAgICAgICAgICAgIHBhbmljVGhyZXNob2xkOiB1c2VyT3B0aW9ucy5wYW5pY1RocmVzaG9sZCxcbiAgICAgICAgICAgICAgY29udGV4dDogXCJvbkV2ZW50KGJ1aWxkLnNzZy5zdGFydClcIixcbiAgICAgICAgICAgIH0pO1xuICAgICAgICAgICAgaWYgKGV2ZW50UGFuaWNFcnJvciAhPSBudWxsKSB7XG4gICAgICAgICAgICAgIHRocm93IGV2ZW50UGFuaWNFcnJvcjsgLy8gUmUtdGhyb3cgdG8gYWJvcnQgdGhlIGJ1aWxkXG4gICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXCJGYWlsZWQgdG8gZW1pdCBidWlsZC5zc2cuc3RhcnQgZXZlbnRcIik7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgLy8gU2VsZWN0IHJlbmRlciBtb2RlIGJhc2VkIG9uIGJ1aWxkIGNvbmZpZ1xuICAgICAgICBjb25zdCByZW5kZXJNb2RlID0gdXNlck9wdGlvbnMuYnVpbGQ/LnJlbmRlck1vZGUgPz8gXCJwYXJhbGxlbFwiO1xuICAgICAgICBjb25zdCByZW5kZXJQYWdlcyA9IHJlbmRlck1vZGUgPT09IFwic2VxdWVudGlhbFwiID8gcmVuZGVyUGFnZXNTZXF1ZW50aWFsIDogcmVuZGVyUGFnZXNCYXRjaGVkO1xuXG4gICAgICAgIGlmICh1c2VyT3B0aW9ucy52ZXJib3NlKSB7XG4gICAgICAgICAgbG9nZ2VyLmluZm8oYFtzdGF0aWNdIFVzaW5nICR7cmVuZGVyTW9kZX0gcmVuZGVyaW5nJHtyZW5kZXJNb2RlID09PSBcInBhcmFsbGVsXCIgPyBgIChiYXRjaCBzaXplOiAke3VzZXJPcHRpb25zLmJ1aWxkPy5iYXRjaFNpemUgPz8gOH0pYCA6IFwiXCJ9YCk7XG4gICAgICAgIH1cblxuICAgICAgICAvLyB0aGlzIHdpbGwgcmVuZGVyIHRoZSByb3V0ZXNcbiAgICAgICAgY29uc3QgcmVuZGVyUGFnZXNHZW5lcmF0b3IgPSByZW5kZXJQYWdlcyhcbiAgICAgICAgICByb3V0ZXMsXG4gICAgICAgICAge1xuICAgICAgICAgICAgLi4uaGFuZGxlck9wdGlvbnMsXG4gICAgICAgICAgICBsb2FkZXI6IGJ1aWxkTG9hZGVyLFxuICAgICAgICAgICAgd29ya2VyOiB3b3JrZXIsXG4gICAgICAgICAgICBodG1sV29ya2VyOiB3b3JrZXIsIC8vIFBhc3MgdGhlIEhUTUwgd29ya2VyIGZvciBIVE1MIGdlbmVyYXRpb25cbiAgICAgICAgICAgIGxvZ2dlcjogbG9nZ2VyLFxuICAgICAgICAgICAgLy8gUGFzcyBnbG9iYWwgQ1NTIHRvIGRvd25zdHJlYW0gcmVuZGVyZXJcbiAgICAgICAgICAgIGdsb2JhbENzcyxcbiAgICAgICAgICAgIC8vIFBhc3MgYWJvcnQgc2lnbmFsIHRvIGNhbmNlbCBvcGVyYXRpb25zIHdoZW4gZXJyb3JzIG9jY3VyXG4gICAgICAgICAgICBzaWduYWw6IEFib3J0U2lnbmFsLnRpbWVvdXQoaGFuZGxlck9wdGlvbnMuaHRtbFRpbWVvdXQpLFxuICAgICAgICAgICAgb25FdmVudDogb25FdmVudCxcbiAgICAgICAgICAgIHNlcnZlclBpcGVhYmxlU3RyZWFtT3B0aW9uczogc2VydmVyUGlwZWFibGVTdHJlYW1PcHRpb25zLFxuICAgICAgICAgICAgY2xpZW50UGlwZWFibGVTdHJlYW1PcHRpb25zOiBjbGllbnRQaXBlYWJsZVN0cmVhbU9wdGlvbnMsXG4gICAgICAgICAgICBtYW5pZmVzdDogc2VydmVyTWFuaWZlc3QgPz8ge30sXG4gICAgICAgICAgICBzdGF0aWNNYW5pZmVzdDogc3RhdGljTWFuaWZlc3QsIC8vIFBhc3Mgc3RhdGljIG1hbmlmZXN0IGZvciBwYXRoIHJlc29sdXRpb25cbiAgICAgICAgICAgIGF1dG9EaXNjb3ZlcmVkRmlsZXM6IGF1dG9EaXNjb3ZlcmVkRmlsZXMhLFxuICAgICAgICAgICAgY3NzRmlsZXNCeVBhZ2U6IGNzc0ZpbGVzQnlQYWdlLFxuICAgICAgICAgICAgYmF0Y2hTaXplOiB1c2VyT3B0aW9ucy5idWlsZD8uYmF0Y2hTaXplLFxuICAgICAgICAgIH0sXG4gICAgICAgICAgcmVuZGVyUGFnZVxuICAgICAgICApO1xuXG4gICAgICAgIC8vIFByb2Nlc3MgcmVuZGVyIHJlc3VsdHNcbiAgICAgICAgbGV0IGZpbmFsUmVzdWx0OiBSZW5kZXJQYWdlc1Jlc3VsdCB8IHVuZGVmaW5lZDtcbiAgICAgICAgdHJ5IHtcbiAgICAgICAgICBmb3IgYXdhaXQgKGNvbnN0IHJlc3VsdCBvZiByZW5kZXJQYWdlc0dlbmVyYXRvcikge1xuICAgICAgICAgICAgLy8gSGFuZGxlIGVycm9yIHJlc3VsdHMgaW1tZWRpYXRlbHlcbiAgICAgICAgICAgIGlmIChyZXN1bHQudHlwZSA9PT0gXCJlcnJvclwiKSB7XG4gICAgICAgICAgICAgIHRocm93IHJlc3VsdC5lcnJvcjtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgLy8gSGFuZGxlIGZhaWxlZCByb3V0ZXMgYmFzZWQgb24gcGFuaWMgdGhyZXNob2xkXG4gICAgICAgICAgICBpZiAoXG4gICAgICAgICAgICAgIHJlc3VsdC50eXBlID09PSBcInN1Y2Nlc3NcIiAmJlxuICAgICAgICAgICAgICByZXN1bHQuZmFpbGVkUm91dGVzICYmXG4gICAgICAgICAgICAgIHJlc3VsdC5mYWlsZWRSb3V0ZXMuc2l6ZSA+IDBcbiAgICAgICAgICAgICkge1xuICAgICAgICAgICAgICAvLyBVc2UgY2VudHJhbGl6ZWQgcGFuaWMgdGhyZXNob2xkIGxvZ2ljXG4gICAgICAgICAgICAgIGNvbnN0IGZpcnN0RXJyb3IgPSByZXN1bHQuZmFpbGVkUm91dGVzLnZhbHVlcygpLm5leHQoKS52YWx1ZTtcbiAgICAgICAgICAgICAgaWYgKGZpcnN0RXJyb3IgIT0gbnVsbCAmJiBzaG91bGRDYXVzZVBhbmljKGZpcnN0RXJyb3IsIHsgcGFuaWNUaHJlc2hvbGQ6IHVzZXJPcHRpb25zLnBhbmljVGhyZXNob2xkIH0pKSB7XG4gICAgICAgICAgICAgICAgLy8gVGhpcyBzaG91bGQgY2F1c2UgYSBwYW5pYywgdGhyb3cgdGhlIGVycm9yXG4gICAgICAgICAgICAgICAgdGhyb3cgZmlyc3RFcnJvcjtcbiAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAvLyBGb3Igbm9uLXBhbmljIGVycm9ycywgbG9nIHdhcm5pbmdzIGJ1dCBjb250aW51ZVxuICAgICAgICAgICAgICBmb3IgKGNvbnN0IFtyb3V0ZSwgZXJyb3JdIG9mIHJlc3VsdC5mYWlsZWRSb3V0ZXMpIHtcbiAgICAgICAgICAgICAgICBjb25zdCBlcnIgPSBlcnJvciBpbnN0YW5jZW9mIEVycm9yID8gZXJyb3IgOiB0b0Vycm9yKGVycm9yKTtcbiAgICAgICAgICAgICAgICB0aGlzLndhcm4oXG4gICAgICAgICAgICAgICAgICBuZXcgRXJyb3IoXCJGYWlsZWQgdG8gcmVuZGVyIHJvdXRlOiBcIiArIHJvdXRlICsgXCJcXG5cIiArIGVyci5tZXNzYWdlICsgXCJcXG5cIiArIGVyci5zdGFjaywgeyBjYXVzZTogZXJyIH0pXG4gICAgICAgICAgICAgICAgKTtcbiAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBmaW5hbFJlc3VsdCA9IHJlc3VsdDtcbiAgICAgICAgICB9XG4gICAgICAgIH0gY2F0Y2ggKHJlbmRlckVycm9yKSB7XG4gICAgICAgICAgLy8gSGFuZGxlIHJlbmRlciBlcnJvcnMgd2l0aCBwYW5pYyB0aHJlc2hvbGQgbG9naWNcbiAgICAgICAgICBjb25zdCByZW5kZXJQYW5pY0Vycm9yID0gaGFuZGxlRXJyb3Ioe1xuICAgICAgICAgICAgZXJyb3I6IHJlbmRlckVycm9yLFxuICAgICAgICAgICAgbG9nZ2VyOiBsb2dnZXIsXG4gICAgICAgICAgICBwYW5pY1RocmVzaG9sZDogdXNlck9wdGlvbnMucGFuaWNUaHJlc2hvbGQsXG4gICAgICAgICAgICBjb250ZXh0OiBcInJlbmRlclBhZ2VzXCIsXG4gICAgICAgICAgfSk7XG4gICAgICAgICAgaWYgKHJlbmRlclBhbmljRXJyb3IgIT0gbnVsbCkge1xuICAgICAgICAgICAgdGhyb3cgcmVuZGVyUGFuaWNFcnJvcjtcbiAgICAgICAgICB9XG4gICAgICAgICAgdGhyb3cgcmVuZGVyRXJyb3I7XG4gICAgICAgIH1cblxuICAgICAgICBpZiAoIWZpbmFsUmVzdWx0KSB7XG4gICAgICAgICAgdGhyb3cgbmV3IEVycm9yKFwiTm8gcmVuZGVyIHJlc3VsdCBwcm9kdWNlZFwiKTtcbiAgICAgICAgfVxuICAgICAgICAvLyBDYWxjdWxhdGUgZHVyYXRpb24gZnJvbSB0aW1pbmdcbiAgICAgICAgY29uc3QgZHVyYXRpb24gPSBNYXRoLnJvdW5kKFxuICAgICAgICAgIHBlcmZvcm1hbmNlLm5vdygpIC0gKHRpbWluZy5yZW5kZXJTdGFydCB8fCB0aW1pbmcuc3RhcnQpXG4gICAgICAgICk7XG5cbiAgICAgICAgdGhpcy5pbmZvKFxuICAgICAgICAgIGBSZW5kZXJlZCAke2ZpbmFsUmVzdWx0LmNvbXBsZXRlZFJvdXRlcy5zaXplfSBwYWdlcyBpbiAke2R1cmF0aW9ufW1zYFxuICAgICAgICApO1xuXG4gICAgICAgIC8vIEVtaXQgdGhlIHN0YXRpYyBzaXRlIGdlbmVyYXRpb24gY29tcGxldGlvbiBldmVudCBvbmNlXG4gICAgICAgIGlmICh0eXBlb2YgdXNlck9wdGlvbnMub25FdmVudCA9PT0gXCJmdW5jdGlvblwiKSB7XG4gICAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgIGNvbnN0IHIgPSB1c2VyT3B0aW9ucy5vbkV2ZW50KHtcbiAgICAgICAgICAgICAgdHlwZTogXCJidWlsZC5zc2cuZW5kXCIsXG4gICAgICAgICAgICAgIGRhdGE6IHtcbiAgICAgICAgICAgICAgICBwYWdlczogQXJyYXkuZnJvbShhdXRvRGlzY292ZXJlZEZpbGVzPy51cmxNYXAua2V5cygpID8/IFtdKSxcbiAgICAgICAgICAgICAgICBvcHRpb25zOiBudWxsIGFzIGFueSwgLy8gTm8gc3BlY2lmaWMgcm9sbHVwIG91dHB1dCBvcHRpb25zIGZvciBzdGF0aWMgZ2VuZXJhdGlvblxuICAgICAgICAgICAgICAgIGJ1bmRsZTogYnVuZGxlLFxuICAgICAgICAgICAgICB9LFxuICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICBpZiAociAhPSBudWxsICYmIHR5cGVvZiByID09PSBcIm9iamVjdFwiICYmIFwidGhlblwiIGluIHIpIHtcbiAgICAgICAgICAgICAgYXdhaXQgKHIgYXMgUHJvbWlzZTxhbnk+KTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgICAgICAgaWYgKGVycm9yICE9IG51bGwpIHtcbiAgICAgICAgICAgICAgdGhyb3cgZXJyb3I7IC8vIFJlLXRocm93IHRvIGFib3J0IHRoZSBidWlsZFxuICAgICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKFwiRmFpbGVkIHRvIGVtaXQgYnVpbGQuc3NnLmVuZCBldmVudFwiKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICBpZiAocHJvY2Vzcy5lbnZbXCJOT0RFX0VOVlwiXSAhPT0gXCJwcm9kdWN0aW9uXCIpIHtcbiAgICAgICAgICB0aGlzLndhcm4oXG4gICAgICAgICAgICBgVEhJUyBCVUlMRCBJUyBOT1QgSU5URU5ERUQgRk9SIFBST0RVQ1RJT04gKCR7cHJvY2Vzcy5lbnZbXCJOT0RFX0VOVlwiXX0pYFxuICAgICAgICAgICk7XG4gICAgICAgIH1cblxuICAgICAgICAvLyBVcGRhdGUgdGltaW5nXG4gICAgICAgIHRpbWluZy5yZW5kZXIgPVxuICAgICAgICAgIHBlcmZvcm1hbmNlLm5vdygpIC0gKHRpbWluZy5yZW5kZXJTdGFydCA/PyB0aW1pbmcuc3RhcnQpO1xuICAgICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgICAgcGFuaWNFcnJvciA9IGhhbmRsZUVycm9yKHtcbiAgICAgICAgICBlcnJvcixcbiAgICAgICAgICBsb2dnZXI6IGxvZ2dlcixcbiAgICAgICAgICBwYW5pY1RocmVz