vite-plugin-react-server
Version:
Vite plugin for React Server Components (RSC)
567 lines (565 loc) • 87.3 kB
JavaScript
/**
* vite-plugin-react-server
* Copyright (c) Nico Brinkkemper
* MIT License
*/
import { handleError } from '../error/handleError.js';
import { shouldCausePanic } from '../error/panicThresholdHandler.js';
import { fileWriter } from './fileWriter.js';
import { createRenderMetrics } from '../metrics/createRenderMetrics.js';
import { createStreamMetrics } from '../metrics/createStreamMetrics.js';
function resolvePathWithManifest(path, manifest) {
const entry = manifest[path];
if (entry && entry.file) {
return entry.file;
}
return path;
}
const renderPages = (routes, handlerOptions, renderPage) => {
const {
autoDiscoveredFiles,
cssFilesByPage,
manifest = {},
...options
} = handlerOptions;
const completedRoutes = /* @__PURE__ */ new Set();
const failedRoutes = /* @__PURE__ */ new Map();
const activeStreams = /* @__PURE__ */ new Map();
const results = /* @__PURE__ */ new Map();
if (!autoDiscoveredFiles.urlMap) {
return async function* _renderPages() {
yield {
type: "error",
error: new Error("No urlMap provided to renderPages"),
route: "",
failedRoutes: /* @__PURE__ */ new Map(),
completedRoutes: /* @__PURE__ */ new Set(),
results: /* @__PURE__ */ new Map()
};
return {
type: "error",
error: new Error("No urlMap provided to renderPages"),
route: "",
failedRoutes: /* @__PURE__ */ new Map(),
completedRoutes: /* @__PURE__ */ new Set(),
results: /* @__PURE__ */ new Map()
};
}();
}
return async function* _renderPages() {
for (const route of routes) {
if (options.signal?.aborted) {
yield {
type: "error",
error: options.signal.reason || new Error("Build aborted"),
route,
failedRoutes,
completedRoutes,
results
};
return {
type: "error",
error: options.signal.reason || new Error("Build aborted"),
route,
failedRoutes,
completedRoutes,
results
};
}
const { page, props, root, html } = autoDiscoveredFiles.urlMap.get(route) || {};
if (!page) continue;
try {
const resolvedPagePath = page ? resolvePathWithManifest(page, manifest) : void 0;
const resolvedPropsPath = props ? resolvePathWithManifest(props, manifest) : void 0;
const resolvedRootPath = root ? resolvePathWithManifest(root, manifest) : void 0;
const resolvedHtmlPath = html ? resolvePathWithManifest(html, manifest) : void 0;
if (options.verbose) {
options.logger?.info(
`[renderPages] Resolved paths for route ${route}:`
);
options.logger?.info(` page: ${page} -> ${resolvedPagePath}`);
options.logger?.info(` props: ${props} -> ${resolvedPropsPath}`);
options.logger?.info(` root: ${root} -> ${resolvedRootPath}`);
options.logger?.info(` html: ${html} -> ${resolvedHtmlPath}`);
}
if (options.verbose) {
options.logger?.info(
`[renderPages] Global CSS: ${options.globalCss?.size} files`
);
for (const [key, value] of options.globalCss?.entries() ?? []) {
options.logger?.info(
`[renderPages] Global CSS: ${key} -> ${value.as} (${value.children ? "inline" : "link"})`
);
}
options.logger?.info(
`[renderPages] CSS files: ${cssFilesByPage.get(route)?.size} files`
);
for (const [key, value] of cssFilesByPage.get(route)?.entries() ?? []) {
options.logger?.info(
`[renderPages] CSS file: ${key} -> ${value.as} (${value.children ? "inline" : "link"})`
);
}
}
const renderPageWrapperOnEvent = (event) => {
if (options.onEvent) {
options.onEvent(event);
}
if (event.type === "route.error") {
const detectedPanicError = handleError({
error: event.data.error,
logger: options.logger,
panicThreshold: event.data.panicThreshold,
context: `route.error (${event.data.route})`
});
if (detectedPanicError != null) {
options.logger?.error(
`[renderPages] Panic error for route ${event.data.route}: ${event.data.error.message}`
);
failedRoutes.set(event.data.route, event.data.error);
} else {
options.logger?.warn(
`[renderPages] Non-panic error for route ${event.data.route}: ${event.data.error.message}`
);
}
}
};
const routeHandlerOptions = {
...options,
manifest,
route,
pagePath: resolvedPagePath,
propsPath: resolvedPropsPath,
rootPath: resolvedRootPath,
htmlPath: resolvedHtmlPath,
cssFiles: cssFilesByPage.get(route) ?? /* @__PURE__ */ new Map(),
// Ensure global CSS is available to Html component
globalCss: options.globalCss ?? /* @__PURE__ */ new Map(),
// Generate unique ID for this route
id: `${route}-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`,
// Override onEvent to use our wrapper
onEvent: renderPageWrapperOnEvent
};
const pageRenderer = renderPage(routeHandlerOptions);
if (options.verbose) {
options.logger?.info(
`[renderPages] Starting to process route: ${route}`
);
}
for await (const result of pageRenderer) {
if (options.verbose) {
options.logger?.info(
`[renderPages] Received result for route ${route}: ${result.type}`
);
}
if (result.type === "skip") {
if (options.verbose) {
options.logger?.info(
`[renderPages] Skipping RSC for route ${route} due to error: ${result.reason}`
);
}
failedRoutes.set(route, result.reason);
results.set(route, {
type: "success",
html: result.html,
rsc: result.rsc,
metrics: result.metrics
});
try {
const wrapperOnEvent = (event) => {
if (options.onEvent) {
options.onEvent(event);
}
if (event.type === "file.write.done" && event.data.route === route && event.data.fileType === "html") {
const routeResult = results.get(route);
if (routeResult && routeResult.type === "success") {
const endTime = performance.now();
const htmlMetrics = createRenderMetrics({
route,
type: routeResult.metrics.html.type,
fromMainThread: routeResult.metrics.html.fromMainThread,
fromRscWorker: routeResult.metrics.html.fromRscWorker,
fromHtmlWorker: routeResult.metrics.html.fromHtmlWorker,
fileSize: event.data.content.length,
chunks: event.data.chunks || 0,
processingTime: endTime - routeResult.metrics.html.streamMetrics.startTime,
chunkRate: (event.data.chunks || 0) / ((endTime - routeResult.metrics.html.streamMetrics.startTime) / 1e3),
fileName: event.data.fileName,
outputPath: event.data.path,
baseDir: event.data.baseDir,
routePath: event.data.routePath,
streamMetrics: createStreamMetrics({
...routeResult.metrics.html.streamMetrics,
chunks: event.data.chunks || 0,
bytes: event.data.content.length,
duration: endTime - routeResult.metrics.html.streamMetrics.startTime,
endTime
})
});
if (options.onMetrics) {
options.onMetrics(htmlMetrics);
}
}
}
};
const rscWritePromise = fileWriter(
result.rsc,
"rsc",
{
...options,
route,
onEvent: wrapperOnEvent,
logger: options.logger
},
options.signal
);
const htmlWritePromise = fileWriter(
result.html,
"html",
{
...options,
route,
onEvent: wrapperOnEvent,
logger: options.logger
},
options.signal
);
await Promise.all([rscWritePromise, htmlWritePromise]);
completedRoutes.add(route);
if (options.verbose) {
options.logger?.info(
`[renderPages] Wrote HTML-only file for skipped route: ${route}`
);
}
} catch (writeError) {
if (options.verbose) {
options.logger?.error(
`[renderPages] Failed to write HTML for skipped route ${route}: ${writeError}`
);
}
completedRoutes.delete(route);
}
continue;
}
if (result.type === "error") {
if (options.verbose) {
options.logger?.error(
`[renderPages] Error for route ${route}: ${result.error}`
);
}
failedRoutes.set(route, result.error);
yield {
type: "error",
error: result.error,
route,
failedRoutes,
completedRoutes,
results
};
continue;
}
if (result.type === "success") {
if (options.verbose) {
options.logger?.info(
`[renderPages] Success for route ${route}, starting file writes`
);
}
completedRoutes.add(route);
results.set(route, {
type: "success",
html: result.html,
rsc: result.rsc,
metrics: result.metrics
});
activeStreams.set(route, {
html: result.html,
rsc: result.rsc
});
try {
const wrapperOnEvent = (event) => {
try {
if (options.onEvent) {
options.onEvent(event);
}
} catch (error) {
options.logger?.error(`[renderPages] onEvent handler threw error: ${error.message}`);
for (const [, streams] of activeStreams) {
try {
streams.html.abort("Build cancelled");
streams.rsc.abort("Build cancelled");
} catch (abortError) {
}
}
if (options.rscWorker) {
try {
options.rscWorker.postMessage({
type: "SHUTDOWN",
id: "*"
});
} catch (shutdownError) {
}
}
throw error;
}
if (event.type === "route.error") {
const detectedPanicError = handleError({
error: event.data.error,
logger: options.logger,
panicThreshold: event.data.panicThreshold,
context: `route.error (${event.data.route})`
});
if (detectedPanicError != null) {
options.logger?.error(
`[renderPages] Panic error for route ${event.data.route}: ${event.data.error.message}`
);
failedRoutes.set(event.data.route, event.data.error);
} else {
options.logger?.warn(
`[renderPages] Non-panic error for route ${event.data.route}: ${event.data.error.message}`
);
}
}
if (event.type === "file.write.done" && event.data.route === route) {
const routeResult = results.get(route);
if (routeResult && routeResult.type === "success") {
if (event.data.fileType === "html") {
const endTime = performance.now();
const htmlMetrics = createRenderMetrics({
route,
type: routeResult.metrics.html.type,
fromMainThread: routeResult.metrics.html.fromMainThread,
fromRscWorker: routeResult.metrics.html.fromRscWorker,
fromHtmlWorker: routeResult.metrics.html.fromHtmlWorker,
fileSize: event.data.content.length,
chunks: event.data.chunks || 0,
processingTime: endTime - routeResult.metrics.html.streamMetrics.startTime,
chunkRate: (event.data.chunks || 0) / ((endTime - routeResult.metrics.html.streamMetrics.startTime) / 1e3),
fileName: event.data.fileName,
outputPath: event.data.path,
baseDir: event.data.baseDir,
routePath: event.data.routePath,
streamMetrics: createStreamMetrics({
...routeResult.metrics.html.streamMetrics,
chunks: event.data.chunks || 0,
bytes: event.data.content.length,
duration: endTime - routeResult.metrics.html.streamMetrics.startTime,
endTime
})
});
if (options.onMetrics) {
options.onMetrics(htmlMetrics);
}
if (routeResult.metrics?.rscFull) {
const rscFullEndTime = performance.now();
const rscFullMetrics = createRenderMetrics({
route,
type: routeResult.metrics.rscFull.type,
fromMainThread: routeResult.metrics.rscFull.fromMainThread,
fromRscWorker: routeResult.metrics.rscFull.fromRscWorker,
fromHtmlWorker: routeResult.metrics.rscFull.fromHtmlWorker,
processingTime: rscFullEndTime - routeResult.metrics.rscFull.streamMetrics.startTime,
chunks: routeResult.metrics.rscFull.streamMetrics.chunks,
chunkRate: routeResult.metrics.rscFull.streamMetrics.chunks / ((rscFullEndTime - routeResult.metrics.rscFull.streamMetrics.startTime) / 1e3),
fileName: event.data.fileName,
outputPath: event.data.path,
baseDir: event.data.baseDir,
routePath: event.data.routePath,
streamMetrics: createStreamMetrics({
...routeResult.metrics.rscFull.streamMetrics,
duration: rscFullEndTime - routeResult.metrics.rscFull.streamMetrics.startTime,
endTime: rscFullEndTime
})
// this stream is consumed by the html stream
});
if (options.onMetrics) {
options.onMetrics(rscFullMetrics);
}
}
} else if (event.data.fileType === "rsc") {
const rscEndTime = performance.now();
const rscMetrics = createRenderMetrics({
route,
type: routeResult.metrics.rscHeadless.type,
fromMainThread: routeResult.metrics.rscHeadless.fromMainThread,
fromRscWorker: routeResult.metrics.rscHeadless.fromRscWorker,
fromHtmlWorker: routeResult.metrics.rscHeadless.fromHtmlWorker,
fileSize: event.data.content.length,
chunks: event.data.chunks || 0,
processingTime: rscEndTime - routeResult.metrics.rscHeadless.streamMetrics.startTime,
chunkRate: (event.data.chunks || 0) / ((rscEndTime - routeResult.metrics.rscHeadless.streamMetrics.startTime) / 1e3),
fileName: event.data.fileName,
outputPath: event.data.path,
baseDir: event.data.baseDir,
routePath: event.data.routePath,
streamMetrics: createStreamMetrics({
...routeResult.metrics.rscHeadless.streamMetrics,
chunks: event.data.chunks || 0,
bytes: event.data.content.length,
duration: rscEndTime - routeResult.metrics.rscHeadless.streamMetrics.startTime,
endTime: rscEndTime
})
});
if (options.onMetrics) {
options.onMetrics(rscMetrics);
}
}
}
}
};
const renderPageOnEvent = (event) => {
wrapperOnEvent(event);
};
const rscWritePromise = fileWriter(
result.rsc,
"rsc",
{
...options,
route,
onEvent: renderPageOnEvent,
logger: options.logger
},
options.signal
);
const htmlWritePromise = fileWriter(
result.html,
"html",
{
...options,
route,
onEvent: renderPageOnEvent,
logger: options.logger
},
options.signal
);
await Promise.all([rscWritePromise, htmlWritePromise]);
activeStreams.delete(route);
if (options.verbose) {
options.logger?.info(
`[renderPages] Successfully wrote files for route: ${route}`
);
}
} catch (error) {
if (options.verbose) {
options.logger?.error(
`[renderPages] Failed to write files for route: ${route}: ${error}`
);
}
failedRoutes.set(route, error);
completedRoutes.delete(route);
results.delete(route);
}
yield {
type: "success",
route,
completedRoutes,
failedRoutes,
results
};
}
}
} catch (err) {
const panicError = handleError({
error: err,
logger: options.logger,
panicThreshold: options.panicThreshold});
if (panicError != null) {
yield {
type: "error",
error: panicError,
route,
failedRoutes,
completedRoutes,
results
};
break;
}
try {
results.delete(route);
completedRoutes.delete(route);
failedRoutes.set(route, err);
} catch (cleanupError) {
options.logger?.warn(
`Failed to cleanup resources for route ${route}: ${cleanupError}`
);
}
yield {
type: "success",
// Change from error to success for non-panic route errors
route,
failedRoutes,
completedRoutes,
results
};
if (options.panicThreshold === "none") {
if (options.verbose) {
options.logger.info(
`[renderPages] Stopping render loop due to error with panicThreshold: "none"`
);
}
break;
}
}
}
if (options.verbose) {
options.logger.info(
`[renderPages] Final state - completedRoutes: ${completedRoutes.size}, failedRoutes: ${failedRoutes.size}`
);
}
yield {
type: "success",
route: "",
// No specific route for final result
completedRoutes,
failedRoutes,
results
};
if (options.verbose) {
options.logger.info(`[renderPages] Returning success result`);
}
if (options.verbose) {
options.logger.info(`[renderPages] About to return success result`);
}
const shouldPanic = failedRoutes.size > 0 && shouldCausePanic(
Array.from(failedRoutes.values())[0],
// Check first failed route
{ panicThreshold: options.panicThreshold }
);
if (options.verbose) {
options.logger?.info(
`[renderPages] Panic check - failedRoutes.size: ${failedRoutes.size}, panicThreshold: ${options.panicThreshold}, shouldPanic: ${shouldPanic}`
);
}
if (shouldPanic) {
const firstError = Array.from(failedRoutes.values())[0];
if (options.verbose) {
options.logger?.error(
`[renderPages] Yielding panic error: ${firstError instanceof Error ? firstError.message : String(firstError)}`
);
}
yield {
type: "error",
error: firstError instanceof Error ? firstError : new Error(String(firstError)),
route: "",
// No specific route for global panic
completedRoutes,
failedRoutes,
results
};
return {
type: "error",
error: firstError instanceof Error ? firstError : new Error(String(firstError)),
route: "",
completedRoutes,
failedRoutes,
results
};
}
return {
type: "success",
completedRoutes,
failedRoutes,
results
};
}();
};
export { renderPages };
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVuZGVyUGFnZXMuanMiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3BsdWdpbi9yZWFjdC1zdGF0aWMvcmVuZGVyUGFnZXMudHMiXSwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiByZW5kZXJQYWdlcy50c1xuICpcbiAqIFBVUlBPU0U6IFJlbmRlcnMgbXVsdGlwbGUgcGFnZXMgaW4gcGFyYWxsZWwgd2l0aCBwcm9wZXIgZXJyb3IgaGFuZGxpbmdcbiAqXG4gKiBUaGlzIG1vZHVsZTpcbiAqIDEuIFRha2VzIGEgbGlzdCBvZiByb3V0ZXMgYW5kIHJlbmRlcnMgZWFjaCBwYWdlXG4gKiAyLiBIYW5kbGVzIGVycm9ycyBncmFjZWZ1bGx5IHdpdGggY29uZmlndXJhYmxlIHBhbmljIHRocmVzaG9sZHNcbiAqIDMuIENvbGxlY3RzIG1ldHJpY3MgZm9yIGVhY2ggcmVuZGVyZWQgcGFnZVxuICogNC4gU3VwcG9ydHMgcmV0cnlpbmcgZmFpbGVkIHJvdXRlcyB3aXRoIGZhbGxiYWNrIGNvbXBvbmVudHNcbiAqIDUuIFJldHJpZXMgZmFpbGVkIHJvdXRlcyB3aXRoIG5vLW9wIFBhZ2UgY29tcG9uZW50IGZvciBtaW5pbWFsIEhUTUwgc2hlbGxzXG4gKi9cbmltcG9ydCB0eXBlIHsgUmVuZGVyUGFnZXNSZXN1bHQsIFJlbmRlclBhZ2VSZXN1bHQgfSBmcm9tIFwiLi4vdHlwZXMuanNcIjtcbmltcG9ydCB0eXBlIHsgUmVuZGVyUGFnZXNGbiB9IGZyb20gXCIuL3R5cGVzLmpzXCI7XG5pbXBvcnQgeyBoYW5kbGVFcnJvciB9IGZyb20gXCIuLi9lcnJvci9oYW5kbGVFcnJvci5qc1wiO1xuaW1wb3J0IHsgc2hvdWxkQ2F1c2VQYW5pYyB9IGZyb20gXCIuLi9lcnJvci9wYW5pY1RocmVzaG9sZEhhbmRsZXIuanNcIjtcbmltcG9ydCB7IGZpbGVXcml0ZXIgfSBmcm9tIFwiLi9maWxlV3JpdGVyLmpzXCI7XG5cbmltcG9ydCB0eXBlIHsgTWFuaWZlc3QgfSBmcm9tIFwidml0ZVwiO1xuaW1wb3J0IHsgY3JlYXRlUmVuZGVyTWV0cmljcyB9IGZyb20gXCIuLi9tZXRyaWNzL2NyZWF0ZVJlbmRlck1ldHJpY3MuanNcIjtcbmltcG9ydCB7IGNyZWF0ZVN0cmVhbU1ldHJpY3MgfSBmcm9tIFwiLi4vbWV0cmljcy9jcmVhdGVTdHJlYW1NZXRyaWNzLmpzXCI7XG5cbmZ1bmN0aW9uIHJlc29sdmVQYXRoV2l0aE1hbmlmZXN0KHBhdGg6IHN0cmluZywgbWFuaWZlc3Q6IE1hbmlmZXN0KTogc3RyaW5nIHtcbiAgY29uc3QgZW50cnkgPSBtYW5pZmVzdFtwYXRoXTtcbiAgaWYgKGVudHJ5ICYmIGVudHJ5LmZpbGUpIHtcbiAgICByZXR1cm4gZW50cnkuZmlsZTtcbiAgfVxuICByZXR1cm4gcGF0aDtcbn1cblxuLyoqXG4gKiBSZW5kZXJzIGFsbCBwYWdlcyBmb3Igc3RhdGljIGdlbmVyYXRpb25cbiAqXG4gKiBUaGlzIGZ1bmN0aW9uOlxuICogMS4gSXRlcmF0ZXMgdGhyb3VnaCBhbGwgcm91dGVzIGluIHRoZSB1cmxNYXBcbiAqIDIuIFJlbmRlcnMgZWFjaCBwYWdlIHVzaW5nIHRoZSBwcm92aWRlZCByZW5kZXJQYWdlIGZ1bmN0aW9uXG4gKiAzLiBXcml0ZXMgYm90aCBSU0MgYW5kIEhUTUwgZmlsZXMgZm9yIGVhY2ggcm91dGVcbiAqIDQuIEhhbmRsZXMgZXJyb3JzIGFjY29yZGluZyB0byBwYW5pYyB0aHJlc2hvbGRcbiAqIDUuIFJldHJpZXMgZmFpbGVkIHJvdXRlcyB3aXRoIG5vLW9wIFBhZ2UgY29tcG9uZW50IGZvciBtaW5pbWFsIEhUTUwgc2hlbGxzXG4gKi9cbmV4cG9ydCBjb25zdCByZW5kZXJQYWdlczogUmVuZGVyUGFnZXNGbiA9IChcbiAgcm91dGVzLFxuICBoYW5kbGVyT3B0aW9ucyxcbiAgcmVuZGVyUGFnZVxuKSA9PiB7XG4gIGNvbnN0IHtcbiAgICBhdXRvRGlzY292ZXJlZEZpbGVzLFxuICAgIGNzc0ZpbGVzQnlQYWdlLFxuICAgIG1hbmlmZXN0ID0ge30sXG4gICAgLi4ub3B0aW9uc1xuICB9ID0gaGFuZGxlck9wdGlvbnM7XG4gIGNvbnN0IGNvbXBsZXRlZFJvdXRlcyA9IG5ldyBTZXQ8c3RyaW5nPigpO1xuICBjb25zdCBmYWlsZWRSb3V0ZXMgPSBuZXcgTWFwPHN0cmluZywgdW5rbm93bj4oKTtcbiAgY29uc3QgYWN0aXZlU3RyZWFtcyA9IG5ldyBNYXA8c3RyaW5nLCB7IGh0bWw6IHsgYWJvcnQ6IChyZWFzb24/OiB1bmtub3duKSA9PiB2b2lkIH0sIHJzYzogeyBhYm9ydDogKHJlYXNvbj86IHVua25vd24pID0+IHZvaWQgfSB9PigpO1xuXG4gIGNvbnN0IHJlc3VsdHMgPSBuZXcgTWFwPHN0cmluZywgUmVuZGVyUGFnZVJlc3VsdD4oKTtcblxuICBpZiAoIWF1dG9EaXNjb3ZlcmVkRmlsZXMudXJsTWFwKSB7XG4gICAgLy8gUmV0dXJuIGFuIGVycm9yIHJlc3VsdCBpbnN0ZWFkIG9mIHRocm93aW5nXG4gICAgcmV0dXJuIChhc3luYyBmdW5jdGlvbiogX3JlbmRlclBhZ2VzKCk6IEFzeW5jR2VuZXJhdG9yPFJlbmRlclBhZ2VzUmVzdWx0LCBSZW5kZXJQYWdlc1Jlc3VsdCwgdW5rbm93bj4ge1xuICAgICAgeWllbGQge1xuICAgICAgICB0eXBlOiBcImVycm9yXCIsXG4gICAgICAgIGVycm9yOiBuZXcgRXJyb3IoXCJObyB1cmxNYXAgcHJvdmlkZWQgdG8gcmVuZGVyUGFnZXNcIiksXG4gICAgICAgIHJvdXRlOiBcIlwiLFxuICAgICAgICBmYWlsZWRSb3V0ZXM6IG5ldyBNYXAoKSxcbiAgICAgICAgY29tcGxldGVkUm91dGVzOiBuZXcgU2V0KCksXG4gICAgICAgIHJlc3VsdHM6IG5ldyBNYXAoKSxcbiAgICAgIH0gc2F0aXNmaWVzIFJlbmRlclBhZ2VzUmVzdWx0O1xuICAgICAgcmV0dXJuIHtcbiAgICAgICAgdHlwZTogXCJlcnJvclwiLFxuICAgICAgICBlcnJvcjogbmV3IEVycm9yKFwiTm8gdXJsTWFwIHByb3ZpZGVkIHRvIHJlbmRlclBhZ2VzXCIpLFxuICAgICAgICByb3V0ZTogXCJcIixcbiAgICAgICAgZmFpbGVkUm91dGVzOiBuZXcgTWFwKCksXG4gICAgICAgIGNvbXBsZXRlZFJvdXRlczogbmV3IFNldCgpLFxuICAgICAgICByZXN1bHRzOiBuZXcgTWFwKCksXG4gICAgICB9IHNhdGlzZmllcyBSZW5kZXJQYWdlc1Jlc3VsdDtcbiAgICB9KSgpO1xuICB9XG5cbiAgcmV0dXJuIChhc3luYyBmdW5jdGlvbiogX3JlbmRlclBhZ2VzKCk6IEFzeW5jR2VuZXJhdG9yPFJlbmRlclBhZ2VzUmVzdWx0LCBSZW5kZXJQYWdlc1Jlc3VsdCwgdW5rbm93bj4ge1xuICAgIC8vIEZpcnN0IHBhc3M6IHJlbmRlciBhbGwgcm91dGVzIG5vcm1hbGx5XG4gICAgZm9yIChjb25zdCByb3V0ZSBvZiByb3V0ZXMpIHtcbiAgICAgIC8vIENoZWNrIGZvciBhYm9ydCBzaWduYWxcbiAgICAgIGlmIChvcHRpb25zLnNpZ25hbD8uYWJvcnRlZCkge1xuICAgICAgICB5aWVsZCB7XG4gICAgICAgICAgdHlwZTogXCJlcnJvclwiLFxuICAgICAgICAgIGVycm9yOiBvcHRpb25zLnNpZ25hbC5yZWFzb24gfHwgbmV3IEVycm9yKFwiQnVpbGQgYWJvcnRlZFwiKSxcbiAgICAgICAgICByb3V0ZTogcm91dGUsXG4gICAgICAgICAgZmFpbGVkUm91dGVzLFxuICAgICAgICAgIGNvbXBsZXRlZFJvdXRlcyxcbiAgICAgICAgICByZXN1bHRzLFxuICAgICAgICB9IHNhdGlzZmllcyBSZW5kZXJQYWdlc1Jlc3VsdDtcbiAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICB0eXBlOiBcImVycm9yXCIsXG4gICAgICAgICAgZXJyb3I6IG9wdGlvbnMuc2lnbmFsLnJlYXNvbiB8fCBuZXcgRXJyb3IoXCJCdWlsZCBhYm9ydGVkXCIpLFxuICAgICAgICAgIHJvdXRlOiByb3V0ZSxcbiAgICAgICAgICBmYWlsZWRSb3V0ZXMsXG4gICAgICAgICAgY29tcGxldGVkUm91dGVzLFxuICAgICAgICAgIHJlc3VsdHMsXG4gICAgICAgIH0gc2F0aXNmaWVzIFJlbmRlclBhZ2VzUmVzdWx0O1xuICAgICAgfVxuICAgICAgY29uc3QgeyBwYWdlLCBwcm9wcywgcm9vdCwgaHRtbCB9ID1cbiAgICAgICAgYXV0b0Rpc2NvdmVyZWRGaWxlcy51cmxNYXAuZ2V0KHJvdXRlKSB8fCB7fTtcbiAgICAgIGlmICghcGFnZSkgY29udGludWU7XG5cbiAgICAgIHRyeSB7XG4gICAgICAgIGNvbnN0IHJlc29sdmVkUGFnZVBhdGggPSBwYWdlXG4gICAgICAgICAgPyByZXNvbHZlUGF0aFdpdGhNYW5pZmVzdChwYWdlLCBtYW5pZmVzdClcbiAgICAgICAgICA6IHVuZGVmaW5lZDtcbiAgICAgICAgY29uc3QgcmVzb2x2ZWRQcm9wc1BhdGggPSBwcm9wc1xuICAgICAgICAgID8gcmVzb2x2ZVBhdGhXaXRoTWFuaWZlc3QocHJvcHMsIG1hbmlmZXN0KVxuICAgICAgICAgIDogdW5kZWZpbmVkO1xuICAgICAgICBjb25zdCByZXNvbHZlZFJvb3RQYXRoID0gcm9vdFxuICAgICAgICAgID8gcmVzb2x2ZVBhdGhXaXRoTWFuaWZlc3Qocm9vdCwgbWFuaWZlc3QpXG4gICAgICAgICAgOiB1bmRlZmluZWQ7XG4gICAgICAgIGNvbnN0IHJlc29sdmVkSHRtbFBhdGggPSBodG1sXG4gICAgICAgICAgPyByZXNvbHZlUGF0aFdpdGhNYW5pZmVzdChodG1sLCBtYW5pZmVzdClcbiAgICAgICAgICA6IHVuZGVmaW5lZDtcblxuICAgICAgICBpZiAob3B0aW9ucy52ZXJib3NlKSB7XG4gICAgICAgICAgb3B0aW9ucy5sb2dnZXI/LmluZm8oXG4gICAgICAgICAgICBgW3JlbmRlclBhZ2VzXSBSZXNvbHZlZCBwYXRocyBmb3Igcm91dGUgJHtyb3V0ZX06YFxuICAgICAgICAgICk7XG4gICAgICAgICAgb3B0aW9ucy5sb2dnZXI/LmluZm8oYCAgcGFnZTogJHtwYWdlfSAtPiAke3Jlc29sdmVkUGFnZVBhdGh9YCk7XG4gICAgICAgICAgb3B0aW9ucy5sb2dnZXI/LmluZm8oYCAgcHJvcHM6ICR7cHJvcHN9IC0+ICR7cmVzb2x2ZWRQcm9wc1BhdGh9YCk7XG4gICAgICAgICAgb3B0aW9ucy5sb2dnZXI/LmluZm8oYCAgcm9vdDogJHtyb290fSAtPiAke3Jlc29sdmVkUm9vdFBhdGh9YCk7XG4gICAgICAgICAgb3B0aW9ucy5sb2dnZXI/LmluZm8oYCAgaHRtbDogJHtodG1sfSAtPiAke3Jlc29sdmVkSHRtbFBhdGh9YCk7XG4gICAgICAgIH1cbiAgICAgICAgaWYgKG9wdGlvbnMudmVyYm9zZSkge1xuICAgICAgICAgIG9wdGlvbnMubG9nZ2VyPy5pbmZvKFxuICAgICAgICAgICAgYFtyZW5kZXJQYWdlc10gR2xvYmFsIENTUzogJHtvcHRpb25zLmdsb2JhbENzcz8uc2l6ZX0gZmlsZXNgXG4gICAgICAgICAgKTtcbiAgICAgICAgICBmb3IgKGNvbnN0IFtrZXksIHZhbHVlXSBvZiBvcHRpb25zLmdsb2JhbENzcz8uZW50cmllcygpID8/IFtdKSB7XG4gICAgICAgICAgICBvcHRpb25zLmxvZ2dlcj8uaW5mbyhcbiAgICAgICAgICAgICAgYFtyZW5kZXJQYWdlc10gR2xvYmFsIENTUzogJHtrZXl9IC0+ICR7dmFsdWUuYXN9ICgke1xuICAgICAgICAgICAgICAgIHZhbHVlLmNoaWxkcmVuID8gXCJpbmxpbmVcIiA6IFwibGlua1wiXG4gICAgICAgICAgICAgIH0pYFxuICAgICAgICAgICAgKTtcbiAgICAgICAgICB9XG4gICAgICAgICAgb3B0aW9ucy5sb2dnZXI/LmluZm8oXG4gICAgICAgICAgICBgW3JlbmRlclBhZ2VzXSBDU1MgZmlsZXM6ICR7Y3NzRmlsZXNCeVBhZ2UuZ2V0KHJvdXRlKT8uc2l6ZX0gZmlsZXNgXG4gICAgICAgICAgKTtcbiAgICAgICAgICBmb3IgKGNvbnN0IFtrZXksIHZhbHVlXSBvZiBjc3NGaWxlc0J5UGFnZS5nZXQocm91dGUpPy5lbnRyaWVzKCkgPz9cbiAgICAgICAgICAgIFtdKSB7XG4gICAgICAgICAgICBvcHRpb25zLmxvZ2dlcj8uaW5mbyhcbiAgICAgICAgICAgICAgYFtyZW5kZXJQYWdlc10gQ1NTIGZpbGU6ICR7a2V5fSAtPiAke3ZhbHVlLmFzfSAoJHtcbiAgICAgICAgICAgICAgICB2YWx1ZS5jaGlsZHJlbiA/IFwiaW5saW5lXCIgOiBcImxpbmtcIlxuICAgICAgICAgICAgICB9KWBcbiAgICAgICAgICAgICk7XG4gICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgLy8gQ3JlYXRlIGEgd3JhcHBlciBvbkV2ZW50IHRoYXQgaGFuZGxlcyByb3V0ZS5lcnJvciBldmVudHMgaW4gcmVuZGVyUGFnZXNcbiAgICAgICAgY29uc3QgcmVuZGVyUGFnZVdyYXBwZXJPbkV2ZW50ID0gKGV2ZW50OiBhbnkpID0+IHtcbiAgICAgICAgICAvLyBDYWxsIHRoZSBvcmlnaW5hbCBvbkV2ZW50IGZpcnN0XG4gICAgICAgICAgaWYgKG9wdGlvbnMub25FdmVudCkge1xuICAgICAgICAgICAgb3B0aW9ucy5vbkV2ZW50KGV2ZW50KTtcbiAgICAgICAgICB9XG5cbiAgICAgICAgICAvLyBIYW5kbGUgcm91dGUuZXJyb3IgZXZlbnRzIGhlcmUgaW4gcmVuZGVyUGFnZXNcbiAgICAgICAgICBpZiAoZXZlbnQudHlwZSA9PT0gXCJyb3V0ZS5lcnJvclwiKSB7XG4gICAgICAgICAgICAvLyBNYWtlIHBhbmljIGRlY2lzaW9uIGluIHRoZSBtYWluIHRocmVhZCBiYXNlZCBvbiBwYW5pY1RocmVzaG9sZFxuICAgICAgICAgICAgY29uc3QgZGV0ZWN0ZWRQYW5pY0Vycm9yID0gaGFuZGxlRXJyb3Ioe1xuICAgICAgICAgICAgICBlcnJvcjogZXZlbnQuZGF0YS5lcnJvcixcbiAgICAgICAgICAgICAgbG9nZ2VyOiBvcHRpb25zLmxvZ2dlcixcbiAgICAgICAgICAgICAgcGFuaWNUaHJlc2hvbGQ6IGV2ZW50LmRhdGEucGFuaWNUaHJlc2hvbGQsXG4gICAgICAgICAgICAgIGNvbnRleHQ6IGByb3V0ZS5lcnJvciAoJHtldmVudC5kYXRhLnJvdXRlfSlgLFxuICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICBcbiAgICAgICAgICAgIGlmIChkZXRlY3RlZFBhbmljRXJyb3IgIT0gbnVsbCkge1xuICAgICAgICAgICAgICAvLyBUaGlzIGlzIGEgcGFuaWMgdGhyZXNob2xkIGVycm9yLCBhZGQgaXQgdG8gZmFpbGVkIHJvdXRlc1xuICAgICAgICAgICAgICBvcHRpb25zLmxvZ2dlcj8uZXJyb3IoXG4gICAgICAgICAgICAgICAgYFtyZW5kZXJQYWdlc10gUGFuaWMgZXJyb3IgZm9yIHJvdXRlICR7ZXZlbnQuZGF0YS5yb3V0ZX06ICR7ZXZlbnQuZGF0YS5lcnJvci5tZXNzYWdlfWBcbiAgICAgICAgICAgICAgKTtcbiAgICAgICAgICAgICAgZmFpbGVkUm91dGVzLnNldChldmVudC5kYXRhLnJvdXRlLCBldmVudC5kYXRhLmVycm9yKTtcbiAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgIC8vIEZvciBub24tcGFuaWMgZXJyb3JzLCBqdXN0IGxvZyBhbmQgY29udGludWVcbiAgICAgICAgICAgICAgb3B0aW9ucy5sb2dnZXI/Lndhcm4oXG4gICAgICAgICAgICAgICAgYFtyZW5kZXJQYWdlc10gTm9uLXBhbmljIGVycm9yIGZvciByb3V0ZSAke2V2ZW50LmRhdGEucm91dGV9OiAke2V2ZW50LmRhdGEuZXJyb3IubWVzc2FnZX1gXG4gICAgICAgICAgICAgICk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICB9O1xuXG4gICAgICAgIC8vIENyZWF0ZSB1bmlxdWUgaGFuZGxlciBvcHRpb25zIGZvciB0aGlzIHJvdXRlXG4gICAgICAgIGNvbnN0IHJvdXRlSGFuZGxlck9wdGlvbnMgPSB7XG4gICAgICAgICAgLi4ub3B0aW9ucyxcbiAgICAgICAgICBtYW5pZmVzdCxcbiAgICAgICAgICByb3V0ZSxcbiAgICAgICAgICBwYWdlUGF0aDogcmVzb2x2ZWRQYWdlUGF0aCBhcyBzdHJpbmcsXG4gICAgICAgICAgcHJvcHNQYXRoOiByZXNvbHZlZFByb3BzUGF0aCBhcyBzdHJpbmcsXG4gICAgICAgICAgcm9vdFBhdGg6IHJlc29sdmVkUm9vdFBhdGggYXMgc3RyaW5nLFxuICAgICAgICAgIGh0bWxQYXRoOiByZXNvbHZlZEh0bWxQYXRoIGFzIHN0cmluZyxcbiAgICAgICAgICBjc3NGaWxlczogY3NzRmlsZXNCeVBhZ2UuZ2V0KHJvdXRlKSA/PyBuZXcgTWFwKCksXG4gICAgICAgICAgLy8gRW5zdXJlIGdsb2JhbCBDU1MgaXMgYXZhaWxhYmxlIHRvIEh0bWwgY29tcG9uZW50XG4gICAgICAgICAgZ2xvYmFsQ3NzOiBvcHRpb25zLmdsb2JhbENzcyA/PyBuZXcgTWFwKCksXG4gICAgICAgICAgLy8gR2VuZXJhdGUgdW5pcXVlIElEIGZvciB0aGlzIHJvdXRlXG4gICAgICAgICAgaWQ6IGAke3JvdXRlfS0ke0RhdGUubm93KCl9LSR7TWF0aC5yYW5kb20oKVxuICAgICAgICAgICAgLnRvU3RyaW5nKDM2KVxuICAgICAgICAgICAgLnN1YnN0cmluZygyLCAxMSl9YCxcbiAgICAgICAgICAvLyBPdmVycmlkZSBvbkV2ZW50IHRvIHVzZSBvdXIgd3JhcHBlclxuICAgICAgICAgIG9uRXZlbnQ6IHJlbmRlclBhZ2VXcmFwcGVyT25FdmVudCxcbiAgICAgICAgfTtcblxuICAgICAgICBjb25zdCBwYWdlUmVuZGVyZXIgPSByZW5kZXJQYWdlKHJvdXRlSGFuZGxlck9wdGlvbnMpO1xuXG4gICAgICAgIGlmIChvcHRpb25zLnZlcmJvc2UpIHtcbiAgICAgICAgICBvcHRpb25zLmxvZ2dlcj8uaW5mbyhcbiAgICAgICAgICAgIGBbcmVuZGVyUGFnZXNdIFN0YXJ0aW5nIHRvIHByb2Nlc3Mgcm91dGU6ICR7cm91dGV9YFxuICAgICAgICAgICk7XG4gICAgICAgIH1cblxuICAgICAgICBmb3IgYXdhaXQgKGNvbnN0IHJlc3VsdCBvZiBwYWdlUmVuZGVyZXIpIHtcbiAgICAgICAgICBpZiAob3B0aW9ucy52ZXJib3NlKSB7XG4gICAgICAgICAgICBvcHRpb25zLmxvZ2dlcj8uaW5mbyhcbiAgICAgICAgICAgICAgYFtyZW5kZXJQYWdlc10gUmVjZWl2ZWQgcmVzdWx0IGZvciByb3V0ZSAke3JvdXRlfTogJHtyZXN1bHQudHlwZX1gXG4gICAgICAgICAgICApO1xuICAgICAgICAgIH1cblxuICAgICAgICAgIGlmIChyZXN1bHQudHlwZSA9PT0gXCJza2lwXCIpIHtcbiAgICAgICAgICAgIGlmIChvcHRpb25zLnZlcmJvc2UpIHtcbiAgICAgICAgICAgICAgb3B0aW9ucy5sb2dnZXI/LmluZm8oXG4gICAgICAgICAgICAgICAgYFtyZW5kZXJQYWdlc10gU2tpcHBpbmcgUlNDIGZvciByb3V0ZSAke3JvdXRlfSBkdWUgdG8gZXJyb3I6ICR7cmVzdWx0LnJlYXNvbn1gXG4gICAgICAgICAgICAgICk7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIC8vIEZvciBza2lwcGVkIHJvdXRlcywgd2Ugc3RpbGwgd2FudCB0byB3cml0ZSB0aGUgSFRNTCBmaWxlIChjbGllbnQtb25seSlcbiAgICAgICAgICAgIC8vIGJ1dCBza2lwIHRoZSBSU0MgZmlsZSBzaW5jZSB0aGVyZSB3YXMgYSBzZXJ2ZXIgZXJyb3JcbiAgICAgICAgICAgIGZhaWxlZFJvdXRlcy5zZXQocm91dGUsIHJlc3VsdC5yZWFzb24pO1xuXG4gICAgICAgICAgICAvLyBTdG9yZSB0aGUgcmVzdWx0IHdpdGggdGhlIHN0cmVhbXMgcHJvdmlkZWQgYnkgcmVuZGVyUGFnZVxuICAgICAgICAgICAgcmVzdWx0cy5zZXQocm91dGUsIHtcbiAgICAgICAgICAgICAgdHlwZTogXCJzdWNjZXNzXCIsXG4gICAgICAgICAgICAgIGh0bWw6IHJlc3VsdC5odG1sLFxuICAgICAgICAgICAgICByc2M6IHJlc3VsdC5yc2MsXG4gICAgICAgICAgICAgIG1ldHJpY3M6IHJlc3VsdC5tZXRyaWNzLFxuICAgICAgICAgICAgfSk7XG5cbiAgICAgICAgICAgIC8vIFdyaXRlIGJvdGggSFRNTCBhbmQgUlNDIGZpbGVzIGZvciBza2lwcGVkIHJvdXRlcyAoUlNDIHdpbGwgYmUgZW1wdHkpXG4gICAgICAgICAgICB0cnkge1xuICAgICAgICAgICAgICBjb25zdCB3cmFwcGVyT25FdmVudCA9IChldmVudDogYW55KSA9PiB7XG4gICAgICAgICAgICAgICAgaWYgKG9wdGlvbnMub25FdmVudCkge1xuICAgICAgICAgICAgICAgICAgb3B0aW9ucy5vbkV2ZW50KGV2ZW50KTtcbiAgICAgICAgICAgICAgICB9XG5cbiAgICAgICAgICAgICAgICAvLyBIYW5kbGUgbWV0cmljcyBmb3IgSFRNTC1vbmx5IHdyaXRlc1xuICAgICAgICAgICAgICAgIGlmIChcbiAgICAgICAgICAgICAgICAgIGV2ZW50LnR5cGUgPT09IFwiZmlsZS53cml0ZS5kb25lXCIgJiZcbiAgICAgICAgICAgICAgICAgIGV2ZW50LmRhdGEucm91dGUgPT09IHJvdXRlICYmXG4gICAgICAgICAgICAgICAgICBldmVudC5kYXRhLmZpbGVUeXBlID09PSBcImh0bWxcIlxuICAgICAgICAgICAgICAgICkge1xuICAgICAgICAgICAgICAgICAgY29uc3Qgcm91dGVSZXN1bHQgPSByZXN1bHRzLmdldChyb3V0ZSk7XG4gICAgICAgICAgICAgICAgICBpZiAocm91dGVSZXN1bHQgJiYgcm91dGVSZXN1bHQudHlwZSA9PT0gXCJzdWNjZXNzXCIpIHtcbiAgICAgICAgICAgICAgICAgICAgY29uc3QgZW5kVGltZSA9IHBlcmZvcm1hbmNlLm5vdygpO1xuICAgICAgICAgICAgICAgICAgICBjb25zdCBodG1sTWV0cmljcyA9IGNyZWF0ZVJlbmRlck1ldHJpY3Moe1xuICAgICAgICAgICAgICAgICAgICAgIHJvdXRlOiByb3V0ZSxcbiAgICAgICAgICAgICAgICAgICAgICB0eXBlOiByb3V0ZVJlc3VsdC5tZXRyaWNzLmh0bWwudHlwZSxcbiAgICAgICAgICAgICAgICAgICAgICBmcm9tTWFpblRocmVhZDogcm91dGVSZXN1bHQubWV0cmljcy5odG1sLmZyb21NYWluVGhyZWFkLFxuICAgICAgICAgICAgICAgICAgICAgIGZyb21Sc2NXb3JrZXI6IHJvdXRlUmVzdWx0Lm1ldHJpY3MuaHRtbC5mcm9tUnNjV29ya2VyLFxuICAgICAgICAgICAgICAgICAgICAgIGZyb21IdG1sV29ya2VyOiByb3V0ZVJlc3VsdC5tZXRyaWNzLmh0bWwuZnJvbUh0bWxXb3JrZXIsXG4gICAgICAgICAgICAgICAgICAgICAgZmlsZVNpemU6IGV2ZW50LmRhdGEuY29udGVudC5sZW5ndGgsXG4gICAgICAgICAgICAgICAgICAgICAgY2h1bmtzOiBldmVudC5kYXRhLmNodW5rcyB8fCAwLFxuICAgICAgICAgICAgICAgICAgICAgIHByb2Nlc3NpbmdUaW1lOlxuICAgICAgICAgICAgICAgICAgICAgICAgZW5kVGltZSAtXG4gICAgICAgICAgICAgICAgICAgICAgICByb3V0ZVJlc3VsdC5tZXRyaWNzLmh0bWwuc3RyZWFtTWV0cmljcy5zdGFydFRpbWUsXG4gICAgICAgICAgICAgICAgICAgICAgY2h1bmtSYXRlOlxuICAgICAgICAgICAgICAgICAgICAgICAgKGV2ZW50LmRhdGEuY2h1bmtzIHx8IDApIC9cbiAgICAgICAgICAgICAgICAgICAgICAgICgoZW5kVGltZSAtXG4gICAgICAgICAgICAgICAgICAgICAgICAgIHJvdXRlUmVzdWx0Lm1ldHJpY3MuaHRtbC5zdHJlYW1NZXRyaWNzLnN0YXJ0VGltZSkgL1xuICAgICAgICAgICAgICAgICAgICAgICAgICAxMDAwKSxcbiAgICAgICAgICAgICAgICAgICAgICBmaWxlTmFtZTogZXZlbnQuZGF0YS5maWxlTmFtZSxcbiAgICAgICAgICAgICAgICAgICAgICBvdXRwdXRQYXRoOiBldmVudC5kYXRhLnBhdGgsXG4gICAgICAgICAgICAgICAgICAgICAgYmFzZURpcjogZXZlbnQuZGF0YS5iYXNlRGlyLFxuICAgICAgICAgICAgICAgICAgICAgIHJvdXRlUGF0aDogZXZlbnQuZGF0YS5yb3V0ZVBhdGgsXG4gICAgICAgICAgICAgICAgICAgICAgc3RyZWFtTWV0cmljczogY3JlYXRlU3RyZWFtTWV0cmljcyh7XG4gICAgICAgICAgICAgICAgICAgICAgICAuLi5yb3V0ZVJlc3VsdC5tZXRyaWNzLmh0bWwuc3RyZWFtTWV0cmljcyxcbiAgICAgICAgICAgICAgICAgICAgICAgIGNodW5rczogZXZlbnQuZGF0YS5jaHVua3MgfHwgMCxcbiAgICAgICAgICAgICAgICAgICAgICAgIGJ5dGVzOiBldmVudC5kYXRhLmNvbnRlbnQubGVuZ3RoLFxuICAgICAgICAgICAgICAgICAgICAgICAgZHVyYXRpb246XG4gICAgICAgICAgICAgICAgICAgICAgICAgIGVuZFRpbWUgLVxuICAgICAgICAgICAgICAgICAgICAgICAgICByb3V0ZVJlc3VsdC5tZXRyaWNzLmh0bWwuc3RyZWFtTWV0cmljcy5zdGFydFRpbWUsXG4gICAgICAgICAgICAgICAgICAgICAgICBlbmRUaW1lOiBlbmRUaW1lLFxuICAgICAgICAgICAgICAgICAgICAgIH0pLFxuICAgICAgICAgICAgICAgICAgICB9KTtcblxuICAgICAgICAgICAgICAgICAgICBpZiAob3B0aW9ucy5vbk1ldHJpY3MpIHtcbiAgICAgICAgICAgICAgICAgICAgICBvcHRpb25zLm9uTWV0cmljcyhodG1sTWV0cmljcyk7XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgIH07XG5cbiAgICAgICAgICAgICAgY29uc3QgcnNjV3JpdGVQcm9taXNlID0gZmlsZVdyaXRlcihcbiAgICAgICAgICAgICAgICByZXN1bHQucnNjIGFzIGFueSxcbiAgICAgICAgICAgICAgICBcInJzY1wiLFxuICAgICAgICAgICAgICAgIHtcbiAgICAgICAgICAgICAgICAgIC4uLm9wdGlvbnMsXG4gICAgICAgICAgICAgICAgICByb3V0ZSxcbiAgICAgICAgICAgICAgICAgIG9uRXZlbnQ6IHdyYXBwZXJPbkV2ZW50LFxuICAgICAgICAgICAgICAgICAgbG9nZ2VyOiBvcHRpb25zLmxvZ2dlcixcbiAgICAgICAgICAgICAgICB9LFxuICAgICAgICAgICAgICAgIG9wdGlvbnMuc2lnbmFsXG4gICAgICAgICAgICAgICk7XG5cbiAgICAgICAgICAgICAgY29uc3QgaHRtbFdyaXRlUHJvbWlzZSA9IGZpbGVXcml0ZXIoXG4gICAgICAgICAgICAgICAgcmVzdWx0Lmh0bWwgYXMgYW55LFxuICAgICAgICAgICAgICAgIFwiaHRtbFwiLFxuICAgICAgICAgICAgICAgIHtcbiAgICAgICAgICAgICAgICAgIC4uLm9wdGlvbnMsXG4gICAgICAgICAgICAgICAgICByb3V0ZSxcbiAgICAgICAgICAgICAgICAgIG9uRXZlbnQ6IHdyYXBwZXJPbkV2ZW50LFxuICAgICAgICAgICAgICAgICAgbG9nZ2VyOiBvcHRpb25zLmxvZ2dlcixcbiAgICAgICAgICAgICAgICB9LFxuICAgICAgICAgICAgICAgIG9wdGlvbnMuc2lnbmFsXG4gICAgICAgICAgICAgICk7XG5cbiAgICAgICAgICAgICAgLy8gV2FpdCBmb3IgYm90aCBSU0MgYW5kIEhUTUwgZmlsZXMgdG8gYmUgd3JpdHRlblxuICAgICAgICAgICAgICBhd2FpdCBQcm9taXNlLmFsbChbcnNjV3JpdGVQcm9taXNlLCBodG1sV3JpdGVQcm9taXNlXSk7XG5cbiAgICAgICAgICAgICAgY29tcGxldGVkUm91dGVzLmFkZChyb3V0ZSk7XG5cbiAgICAgICAgICAgICAgaWYgKG9wdGlvbnMudmVyYm9zZSkge1xuICAgICAgICAgICAgICAgIG9wdGlvbnMubG9nZ2VyPy5pbmZvKFxuICAgICAgICAgICAgICAgICAgYFtyZW5kZXJQYWdlc10gV3JvdGUgSFRNTC1vbmx5IGZpbGUgZm9yIHNraXBwZWQgcm91dGU6ICR7cm91dGV9YFxuICAgICAgICAgICAgICAgICk7XG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH0gY2F0Y2ggKHdyaXRlRXJyb3IpIHtcbiAgICAgICAgICAgICAgaWYgKG9wdGlvbnMudmVyYm9zZSkge1xuICAgICAgICAgICAgICAgIG9wdGlvbnMubG9nZ2VyPy5lcnJvcihcbiAgICAgICAgICAgICAgICAgIGBbcmVuZGVyUGFnZXNdIEZhaWxlZCB0byB3cml0ZSBIVE1MIGZvciBza2lwcGVkIHJvdXRlICR7cm91dGV9OiAke3dyaXRlRXJyb3J9YFxuICAgICAgICAgICAgICAgICk7XG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgLy8gUmVtb3ZlIGZyb20gY29tcGxldGVkIHJvdXRlcyBpZiBIVE1MIHdyaXRlIGZhaWxlZFxuICAgICAgICAgICAgICBjb21wbGV0ZWRSb3V0ZXMuZGVsZXRlKHJvdXRlKTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgY29udGludWU7XG4gICAgICAgICAgfVxuXG4gICAgICAgICAgaWYgKHJlc3VsdC50eXBlID09PSBcImVycm9yXCIpIHtcbiAgICAgICAgICAgIGlmIChvcHRpb25zLnZlcmJvc2UpIHtcbiAgICAgICAgICAgICAgb3B0aW9ucy5sb2dnZXI/LmVycm9yKFxuICAgICAgICAgICAgICAgIGBbcmVuZGVyUGFnZXNdIEVycm9yIGZvciByb3V0ZSAke3JvdXRlfTogJHtyZXN1bHQuZXJyb3J9YFxuICAgICAgICAgICAgICApO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZmFpbGVkUm91dGVzLnNldChyb3V0ZSwgcmVzdWx0LmVycm9yKTtcbiAgICAgICAgICAgIHlpZWxkIHtcbiAgICAgICAgICAgICAgdHlwZTogXCJlcnJvclwiLFxuICAgICAgICAgICAgICBlcnJvcjogcmVzdWx0LmVycm9yLFxuICAgICAgICAgICAgICByb3V0ZTogcm91dGUsXG4gICAgICAgICAgICAgIGZhaWxlZFJvdXRlcyxcbiAgICAgICAgICAgICAgY29tcGxldGVkUm91dGVzLFxuICAgICAgICAgICAgICByZXN1bHRzLFxuICAgICAgICAgICAgfSBzYXRpc2ZpZXMgUmVuZGVyUGFnZXNSZXN1bHQ7XG4gICAgICAgICAgICBjb250aW51ZTtcbiAgICAgICAgICB9XG5cbiAgICAgICAgICBpZiAocmVzdWx0LnR5cGUgPT09IFwic3VjY2Vzc1wiKSB7XG4gICAgICAgICAgICBpZiAob3B0aW9ucy52ZXJib3NlKSB7XG4gICAgICAgICAgICAgIG9wdGlvbnMubG9nZ2VyPy5pbmZvKFxuICAgICAgICAgICAgICAgIGBbcmVuZGVyUGFnZXNdIFN1Y2Nlc3MgZm9yIHJvdXRlICR7cm91dGV9LCBzdGFydGluZyBmaWxlIHdyaXRlc2BcbiAgICAgICAgICAgICAgKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGNvbXBsZXRlZFJvdXRlcy5hZGQocm91dGUpO1xuICAgICAgICAgICAgLy8gU3RvcmUgdGhlIHJlc3VsdCB3aXRoIHRoZSBjb3JyZWN0IHR5cGUgc3RydWN0dXJlXG4gICAgICAgICAgICByZXN1bHRzLnNldChyb3V0ZSwge1xuICAgICAgICAgICAgICB0eXBlOiBcInN1Y2Nlc3NcIixcbiAgICAgICAgICAgICAgaHRtbDogcmVzdWx0Lmh0bWwsXG4gICAgICAgICAgICAgIHJzYzogcmVzdWx0LnJzYyxcbiAgICAgICAgICAgICAgbWV0cmljczogcmVzdWx0Lm1ldHJpY3MsXG4gICAgICAgICAgICB9KTtcblxuICAgICAgICAgICAgLy8gU3RvcmUgYWN0aXZlIHN0cmVhbXMgZm9yIHBvdGVudGlhbCBhYm9ydGlvbiBkdXJpbmcgY2FuY2VsbGF0aW9uXG4gICAgICAgICAgICBhY3RpdmVTdHJlYW1zLnNldChyb3V0ZSwge1xuICAgICAgICAgICAgICBodG1sOiByZXN1bHQuaHRtbCxcbiAgICAgICAgICAgICAgcnNjOiByZXN1bHQucnNjLFxuICAgICAgICAgICAgfSk7XG5cbiAgICAgICAgICAgIC8vIFdyaXRlIGZpbGVzIGRpcmVjdGx5IGluIHJlbmRlclBhZ2VzXG4gICAgICAgICAgICB0cnkge1xuICAgICAgICAgICAgICAvLyBDcmVhdGUgYSB3cmFwcGVyIG9uRXZlbnQgdGhhdCBjYWxscyBib3RoIHRoZSByZW5kZXJQYWdlJ3MgZXZlbnQgaGFuZGxlciBhbmQgdGhlIG9yaWdpbmFsIG9uRXZlbnRcbiAgICAgICAgICAgICAgY29uc3Qgd3JhcHBlck9uRXZlbnQgPSAoZXZlbnQ6IGFueSkgPT4ge1xuICAgICAgICAgICAgICAgIHRyeSB7XG4gICAgICAgICAgICAgICAgICAvLyBDYWxsIHRoZSBvcmlnaW5hbCBvbkV2ZW50IGZpcnN0XG4gICAgICAgICAgICAgICAgICBpZiAob3B0aW9ucy5vbkV2ZW50KSB7XG4gICAgICAgICAgICAgICAgICAgIG9wdGlvbnMub25FdmVudChldmVudCk7XG4gICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgICAgICAgICAgICAgIC8vIElmIG9uRXZlbnQgdGhyb3dzIGFuIGVycm9yIChlLmcuLCBidWlsZCBjYW5jZWxsYXRpb24pLCB3ZSBuZWVkIHRvIHN0b3AgcHJvY2Vzc2luZ1xuICAgICAgICAgICAgICAgICAgLy8gVGhpcyBwcmV2ZW50cyB0aGUgd29ya2VyIGZyb20gY29udGludWluZyB0byBwcm9jZXNzIGFmdGVyIHRoZSBidWlsZCBpcyBjYW5jZWxsZWRcbiAgICAgICAgICAgICAgICAgIG9wdGlvbnMubG9nZ2VyPy5lcnJvcihgW3JlbmRlclBhZ2VzXSBvbkV2ZW50IGhhbmRsZXIgdGhyZXcgZXJyb3I6ICR7KGVycm9yIGFzIEVycm9yKS5tZXNzYWdlfWApO1xuICAgICAgICAgICAgICAgICAgXG4gICAgICAgICAgICAgICAgICAvLyBGaXJzdCwgYWJvcnQgYWxsIGFjdGl2ZSBzdHJlYW1zIHRvIHN0b3AgZGF0YSBmbG93XG4gICAgICAgICAgICAgICAgICAvLyBUaGlzIGlzIGNyaXRpY2FsIHRvIHByZXZlbnQgdW5oYW5kbGVkIGVycm9ycyBhbmQgbWVtb3J5IGxlYWtzXG4gICAgICAgICAgICAgICAgICBmb3IgKGNvbnN0IFssIHN0cmVhbXNdIG9mIGFjdGl2ZVN0cmVhbXMpIHtcbiAgICAgICAgICAgICAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgICAgICAgICAgICBzdHJlYW1zLmh0bWwuYWJvcnQoXCJCdWlsZCBjYW5jZWxsZWRcIik7XG4gICAgICAgICAgICAgICAgICAgICAgc3RyZWFtcy5yc2MuYWJvcnQoXCJCdWlsZCBjYW5jZWxsZWRcIik7XG4gICAgICAgICAgICAgICAgICAgIH0gY2F0Y2ggKGFib3J0RXJyb3IpIHtcbiAgICAgICAgICAgICAgICAgICAgICAvLyBTdHJlYW1zIG1heSBhbHJlYWR5IGJlIGNsb3NlZCwgaWdub3JlXG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgIFxuICAgICAgICAgICAgICAgICAgLy8gVGhlbiBzZW5kIHNodXRkb3duIHNpZ25hbCB0byB3b3JrZXIgdG8gcHJldmVudCBpdCBmcm9tIGNvbnRpbnVpbmcgdG8gcHJvY2Vzc1xuICAgICAgICAgICAgICAgICAgaWYgKG9wdGlvbnMucnNjV29ya2VyKSB7XG4gICAgICAgICAgICAgICAgICAgIHRyeSB7XG4gICAgICAgICAgICAgICAgICAgICAgb3B0aW9ucy5yc2NXb3JrZXIucG9zdE1lc3NhZ2Uoe1xuICAgICAgICAgICAgICAgICAgICAgICAgdHlwZTogXCJTSFVURE9XTlwiLFxuICAgICAgICAgICAgICAgICAgICAgICAgaWQ6IFwiKlwiLFxuICAgICAgICAgICAgICAgICAgICAgIH0pO1xuICAgICAgICAgICAgICAgICAgICB9IGNhdGNoIChzaHV0ZG93bkVycm9yKSB7XG4gICAgICAgICAgICAgICAgICAgICAgLy8gV29ya2VyIG1heSBhbHJlYWR5IGJlIHRlcm1pbmF0ZWQsIGlnbm9yZVxuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICBcbiAgICAgICAgICAgICAgICAgIC8vIFJlLXRocm93IHRoZSBlcnJvciB0byBzdG9wIHRoZSBidWlsZCBwcm9jZXNzXG4gICAgICAgICAgICAgICAgICB0aHJvdyBlcnJvcjtcbiAgICAgICAgICAgICAgICB9XG5cbiAgICAgICAgICAgICAgICAvLyBIYW5kbGUgcm91dGUuZXJyb3IgZXZlbnRzXG4gICAgICAgICAgICAgICAgaWYgKGV2ZW50LnR5cGUgPT09IFwicm91dGUuZXJyb3JcIikge1xuICAgICAgICAgICAgICAgICAgLy8gTWFrZSBwYW5pYyBkZWNpc2lvbiBpbiB0aGUgbWFpbiB0aHJlYWQgYmFzZWQgb24gcGFuaWNUaHJlc2hvbGRcbiAgICAgICAgICAgICAgICAgIGNvbnN0IGRldGVjdGVkUGFuaWNFcnJvciA9IGhhbmRsZUVycm9yKHtcbiAgICAgICAgICAgICAgICAgICAgZXJyb3I6IGV2ZW50LmRhdGEuZXJyb3IsXG4gICAgICAgICAgICAgICAgICAgIGxvZ2dlcjogb3B0aW9ucy5sb2dnZXIsXG4gICAgICAgICAgICAgICAgICAgIHBhbmljVGhyZXNob2xkOiBldmVudC5kYXRhLnBhbmljVGhyZXNob2xkLFxuICAgICAgICAgICAgICAgICAgICBjb250ZXh0OiBgcm91dGUuZXJyb3IgKCR7ZXZlbnQuZGF0YS5yb3V0ZX0pYCxcbiAgICAgICAgICAgICAgICAgIH0pO1xuICAgICAgICAgICAgICAgICAgXG4gICAgICAgICAgICAgICAgICBpZiAoZGV0ZWN0ZWRQYW5pY0Vycm9yICE9IG51bGwpIHtcbiAgICAgICAgICAgICAgICAgICAgLy8gVGhpcyBpcyBhIHBhbmljIHRocmVzaG9sZCBlcnJvciwgYWRkIGl0IHRvIGZhaWxlZCByb3V0ZXMgdG8gYmUgeWllbGRlZFxuICAgICAgICAgICAgICAgICAgICBvcHRpb25zLmxvZ2dlcj8uZXJyb3IoXG4gICAgICAgICAgICAgICAgICAgICAgYFtyZW5kZXJQYWdlc10gUGFuaWMgZXJyb3IgZm9yIHJvdXRlICR7ZXZlbnQuZGF0YS5yb3V0ZX06ICR7ZXZlbnQuZGF0YS5lcnJvci5tZXNzYWdlfWBcbiAgICAgICAgICAgICAgICAgICAgKTtcbiAgICAgICAgICAgICAgICAgICAgZmFpbGVkUm91dGVzLnNldChldmVudC5kYXRhLnJvdXRlLCBldmVudC5kYXRhLmVycm9yKTtcbiAgICAgICAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgICAgIC8vIEZvciBub24tcGFuaWMgZXJyb3JzLCBqdXN0IGxvZyBhbmQgY29udGludWVcbiAgICAgICAgICAgICAgICAgICAgb3B0aW9ucy5sb2dnZXI/Lndhcm4oXG4gICAgICAgICAgICAgICAgICAgICAgYFtyZW5kZXJQYWdlc10gTm9uLXBhbmljIGVycm9yIGZvciByb3V0ZSAke2V2ZW50LmRhdGEucm91dGV9OiAke2V2ZW50LmRhdGEuZXJyb3IubWVzc2FnZX1gXG4gICAgICAgICAgICAgICAgICAgICk7XG4gICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgICAgLy8gSGFuZGxlIG1ldHJpY3MgY29sbGVjdGlvbiBoZXJlIHNpbmNlIHRoZSByZW5kZXJQYWdlIGZ1bmN0aW9uJ3MgZXZlbnQgaGFuZGxlciBpcyBub3QgYmVpbmcgY2FsbGVkXG4gICAgICAgICAgICAgICAgaWYgKFxuICAgICAgICAgICAgICAgICAgZXZlbnQudHlwZSA9PT0gXCJmaWxlLndyaXRlLmRvbmVcIiAmJlxuICAgICAgICAgICAgICAgICAgZXZlbnQuZGF0YS5yb3V0ZSA9PT0gcm91dGVcbiAgICAgICAgICAgICAgICApIHtcbiAgICAgICAgICAgICAgICAgIGNvbnN0IHJvdXRlUmVzdWx0ID0gcmVzdWx0cy5nZXQocm91dGUpO1xuICAgICAgICAgICAgICAgICAgaWYgKHJvdXRlUmVzdWx0ICYmIHJvdXRlUmVzdWx0LnR5cGUgPT09IFwic3VjY2Vzc1wiKSB7XG4gICAgICAgICAgICAgICAgICAgIGlmIChldmVudC5kYXRhLmZpbGVUeXBlID09PSBcImh0bWxcIikge1xuICAgICAgICAgICAgICAgICAgICAgIC8vIFVwZGF0ZSBIVE1MIG1ldHJpY3Mgd2l0aCBhY3R1YWwgZmlsZSBkYXRhXG4gICAgICAgICAgICAgICAgICAgICAgY29uc3QgZW5kVGltZSA9IHBlcmZvcm1hbmNlLm5vdygpO1xuICAgICAgICAgICAgICAgICAgICAgIGNvbnN0IGh0bWxNZXRyaWNzID0gY3JlYXRlUmVuZGVyTWV0cmljcyh7XG4gICAgICAgICAgICAgICAgICAgICAgICByb3V0ZTogcm91dGUsXG4gICAgICAgICAgICAgICAgICAgICAgICB0eXBlOiByb3V0ZVJlc3VsdC5tZXRyaWNzLmh0bWwudHlwZSxcbiAgICAgICAgICAgICAgICAgICAgICAgIGZyb21NYWluVGhyZWFkOiByb3V0ZVJlc3VsdC5tZXRyaWNzLmh0bWwuZnJvbU1haW5UaHJlYWQsXG4gICAgICAgICAgICAgICAgICAgICAgICBmcm9tUnNjV29ya2VyOiByb3V0ZVJlc3VsdC5tZXRyaWNzLmh0bWwuZnJvbVJzY1dvcmtlcixcbiAgICAgICAgICAgICAgICAgICAgICAgIGZyb21IdG1sV29ya2VyOiByb3V0ZVJlc3VsdC5tZXRyaWNzLmh0bWwuZnJvbUh0bWxXb3JrZXIsXG4gICAgICAgICAgICAgICAgICAgICAgICBmaWxlU2l6ZTogZXZlbnQuZGF0YS5jb250ZW50Lmxlbmd0aCxcbiAgICAgICAgICAgICAgICAgICAgICAgIGNodW5rczogZXZlbnQuZGF0YS5jaHVua3MgfHwgMCxcbiAgICAgICAgICAgICAgICAgICAgICAgIHByb2Nlc3NpbmdUaW1lOlxuICAgICAgICAgICAgICAgICAgICAgICAgICBlbmRUaW1lIC1cbiAgICAgICAgICAgICAgICAgICAgICAgICAgcm91dGVSZXN1bHQubWV0cmljcy5odG1sLnN0cmVhbU1ldHJpY3Muc3RhcnRUaW1lLFxuICAgICAgICAgICAgICAgICAgICAgICAgY2h1bmtSYXRlOlxuICAgICAgICAgICAgICAgICAgICAgICAgICAoZXZlbnQuZGF0YS5jaHVua3MgfHwgMCkgL1xuICAgICAgICAgICAgICAgICAgICAgICAgICAoKGVuZFRpbWUgLVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJvdXRlUmVzdWx0Lm1ldHJpY3MuaHRtbC5zdHJlYW1NZXRyaWNzLnN0YXJ0VGltZSkgL1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIDEwMDApLFxuICAgICAgICAgICAgICAgICAgICAgICAgZmlsZU5hbWU6IGV2ZW50LmRhdGEuZmlsZU5hbWUsXG4gICAgICAgICAgICAgICAgICAgICAgICBvdXRwdXRQYXRoOiBldmVudC5kYXRhLnBhdGgsXG4gICAgICAgICAgICAgICAgICAgICAgICBiYXNlRGlyOiBldmVudC5kYXRhLmJhc2VEaXIsXG4gICAgICAgICAgICAgICAgICAgICAgICByb3V0ZVBhdGg6IGV2ZW50LmRhdGEucm91dGVQYXRoLFxuICAgICAgICAgICAgICAgICAgICAgICAgc3RyZWFtTWV0cmljczogY3JlYXRlU3RyZWFtTWV0cmljcyh7XG4gICAgICAgICAgICAgICAgICAgICAgICAgIC4uLnJvdXRlUmVzdWx0Lm1ldHJpY3MuaHRtbC5zdHJlYW1NZXRyaWNzLFxuICAgICAgICAgICAgICAgICAgICAgICAgICBjaHVua3M6IGV2ZW50LmRhdGEuY2h1bmtzIHx8IDAsXG4gICAgICAgICAgICAgICAgICAgICAgICAgIGJ5dGVzOiBldmVudC5kYXRhLmNvbnRlbnQubGVuZ3RoLFxuICAgICAgICAgICAgICAgICAgICAgICAgICBkdXJhdGlvbjpcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBlbmRUaW1lIC1cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICByb3V0ZVJlc3VsdC5tZXRyaWNzLmh0bWwuc3RyZWFtTWV0cmljcy5zdGFydFRpbWUsXG4gICAgICAgICAgICAgICAgICAgICAgICAgIGVuZFRpbWU6IGVuZFRpbWUsXG4gICAgICAgICAgICAgICAgICAgICAgICB9KSxcbiAgICAgICAgICAgICAgICAgICAgICB9KTtcblxuICAgICAgICAgICAgICAgICAgICAgIGlmIChvcHRpb25zLm9uTWV0cmljcykge1xuICAgICAgICAgICAgICAgICAgICAgICAgb3B0aW9ucy5vbk1ldHJpY3MoaHRtbE1ldHJpY3MpO1xuICAgICAgICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgICAgICAgIC8vIEFsc28gZW1pdCBSU0MgRnVsbCBtZXRyaWNzICh0aGUgUlNDIGNodW5rcyBzZW50IHRvIEhUTUwgd29ya2VyKVxuICAgICAgICAgICAgICAgICAgICAgIC8vIE9ubHkgaWYgbWV0cmljcy5yc2NGdWxsIGV4aXN0cyAobWlnaHQgYmUgbWlzc2luZyBvbiBlcnJvcnMpXG4gICAgICAgICAgICAgICAgICAgICAgaWYgKHJvdXRlUmVzdWx0Lm1ldHJpY3M/LnJzY0Z1bGwpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIGNvbnN0IHJzY0Z1bGxFbmRUaW1lID0gcGVyZm9ybWFuY2Uubm93KCk7XG4gICAgICAgICAgICAgICAgICAgICAgICBjb25zdCByc2NGdWxsTWV0cmljcyA9IGNyZWF0ZVJlbmRlck1ldHJpY3Moe1xuICAgICAgICAgICAgICAgICAgICAgICAgICByb3V0ZTogcm91dGUsXG4gICAgICAgICAgICAgICAgICAgICAgICAgIHR5cGU6IHJvdXRlUmVzdWx0Lm1ldHJpY3MucnNjRnVsbC50eXBlLFxuICAgICAgICAgICAgICAgICAgICAgICAgICBmcm9tTWFpblRocmVhZDpcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICByb3V0ZVJlc3VsdC5tZXRyaWNzLnJzY0Z1bGwuZnJ