vite-plugin-react-server
Version:
Vite plugin for React Server Components (RSC)
537 lines (535 loc) • 77.9 kB
JavaScript
/**
* 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