vite-plugin-react-server
Version:
Vite plugin for React Server Components (RSC)
481 lines (479 loc) • 69.3 kB
JavaScript
/**
* vite-plugin-react-server
* Copyright (c) Nico Brinkkemper
* MIT License
*/
import { createRenderMetrics } from '../metrics/createRenderMetrics.js';
import { routeToURL } from '../utils/routeToURL.js';
import { handleError } from '../error/handleError.js';
import { assertNonReactServer } from '../config/getCondition.js';
import { createRscStream } from '../stream/createRscStream.client.js';
import { resolveComponents } from '../helpers/resolveComponents.client.js';
import { join } from 'node:path';
import { createStreamMetrics } from '../metrics/createStreamMetrics.js';
import { performance } from 'node:perf_hooks';
import { createRscToHtmlStream } from './rscToHtmlStream.client.js';
assertNonReactServer();
const renderPage = async function* _renderPageClient(handlerOptions) {
if (handlerOptions.verbose) {
handlerOptions.logger?.info(
`[renderPage.client] onEvent callback exists: ${!!handlerOptions.onEvent}`
);
handlerOptions.logger?.info(
`[renderPage.client] onMetrics callback exists: ${!!handlerOptions.onMetrics}`
);
}
let hasYielded = false;
let errorResult = null;
const wrappedOnEvent = (event) => {
if (handlerOptions.onEvent) {
handlerOptions.onEvent(event);
}
if (event.type === "route.error" && !hasYielded) {
hasYielded = true;
const panicError = handleError({
error: event.data.error,
logger: handlerOptions.logger,
panicThreshold: event.data.panicThreshold,
context: `route.error (${event.data.route})`
});
if (panicError != null) {
errorResult = {
type: "error",
error: panicError,
metrics: {
rscHeadless: { duration: 0, chunks: 0, bytes: 0 },
html: { duration: 0, chunks: 0, bytes: 0 }
}
};
} else {
errorResult = {
type: "skip",
reason: event.data.error.message || "Non-panic error occurred",
html: { duration: 0, chunks: 0, bytes: 0 },
rsc: { duration: 0, chunks: 0, bytes: 0 },
metrics: {
rscHeadless: { duration: 0, chunks: 0, bytes: 0 },
html: { duration: 0, chunks: 0, bytes: 0 }
}
};
}
}
};
if (!handlerOptions.pagePath && !handlerOptions.PageComponent) {
const emptyStreamWrapper = {
pipe: (destination) => {
destination.end();
return destination;
},
abort: () => {
}
};
yield {
type: "skip",
reason: "No pagePath and no PageComponent provided",
html: emptyStreamWrapper,
rsc: emptyStreamWrapper,
metrics: {
rscFull: createRenderMetrics({
route: handlerOptions.route,
type: "rsc-full",
fromMainThread: false,
fromRscWorker: true,
fromHtmlWorker: false
}),
rscHeadless: createRenderMetrics({
route: handlerOptions.route,
type: "rsc-headless",
fromMainThread: false,
fromRscWorker: true,
fromHtmlWorker: false
}),
html: createRenderMetrics({
route: handlerOptions.route,
type: "html",
fromMainThread: true,
fromRscWorker: false,
fromHtmlWorker: false
})
}
};
return;
}
if (!handlerOptions.url) {
handlerOptions.url = routeToURL(
handlerOptions.route,
handlerOptions.moduleBaseURL,
handlerOptions.build.rscOutputPath
);
}
const baseDir = join(
handlerOptions.build.outDir,
handlerOptions.build.static
);
const routePath = handlerOptions.route.replace(/^\//, "");
const htmlMetrics = createRenderMetrics({
route: handlerOptions.route,
type: "html",
fromMainThread: true,
// Client: HTML rendered on main thread
fromRscWorker: false,
fromHtmlWorker: false,
baseDir,
routePath,
fileName: handlerOptions.build.htmlOutputPath,
outputPath: join(baseDir, routePath, handlerOptions.build.htmlOutputPath)
});
const rscFullMetrics = createRenderMetrics({
route: handlerOptions.route,
type: "rsc-full",
fromMainThread: false,
fromRscWorker: true,
// Client: RSC rendered on RSC worker
fromHtmlWorker: false
});
const rscHeadlessMetrics = createRenderMetrics({
route: handlerOptions.route,
type: "rsc-headless",
fromMainThread: false,
fromRscWorker: true,
// Client: RSC rendered on RSC worker
fromHtmlWorker: false,
baseDir,
routePath,
fileName: handlerOptions.build.rscOutputPath,
outputPath: join(baseDir, routePath, handlerOptions.build.rscOutputPath)
});
let headlessRscStream = null;
let fullRscStream = null;
let htmlHandler = null;
try {
if (handlerOptions.verbose) {
handlerOptions.logger?.info(
`[renderPage.client] Client-side rendering for route: ${handlerOptions.route}`
);
}
const resolvePathWithManifest = (path, manifest2) => {
const entry = manifest2[path];
if (entry && entry.file) {
return entry.file;
}
return path;
};
const manifest = handlerOptions.manifest || {};
const resolvedPagePath = handlerOptions.pagePath ? resolvePathWithManifest(handlerOptions.pagePath, manifest) : void 0;
const resolvedPropsPath = handlerOptions.propsPath ? resolvePathWithManifest(handlerOptions.propsPath, manifest) : void 0;
const resolvedRootPath = handlerOptions.rootPath ? resolvePathWithManifest(handlerOptions.rootPath, manifest) : void 0;
const resolvedHtmlPath = handlerOptions.htmlPath ? resolvePathWithManifest(handlerOptions.htmlPath, manifest) : void 0;
if (handlerOptions.verbose) {
handlerOptions.logger?.info(`[renderPage.client] Resolved paths for route ${handlerOptions.route}:`);
handlerOptions.logger?.info(` page: ${handlerOptions.pagePath} -> ${resolvedPagePath}`);
handlerOptions.logger?.info(` props: ${handlerOptions.propsPath} -> ${resolvedPropsPath}`);
handlerOptions.logger?.info(` root: ${handlerOptions.rootPath} -> ${resolvedRootPath}`);
handlerOptions.logger?.info(` html: ${handlerOptions.htmlPath} -> ${resolvedHtmlPath}`);
handlerOptions.logger?.info(` manifest keys: ${Object.keys(manifest).join(", ")}`);
handlerOptions.logger?.info(` HTML path issue: htmlPath='${handlerOptions.htmlPath}', resolved='${resolvedHtmlPath}', manifest has Html entry: ${!!manifest[handlerOptions.htmlPath || ""]}`);
handlerOptions.logger?.info(` About to pass htmlPath='${resolvedHtmlPath}' to RSC stream`);
}
const worker = handlerOptions.worker ?? handlerOptions.rscWorker;
if (!worker) {
throw new Error("RSC worker is required for client-side component resolution");
}
try {
await resolveComponents({
route: handlerOptions.route,
pagePath: resolvedPagePath,
propsPath: resolvedPropsPath,
rootPath: resolvedRootPath,
htmlPath: resolvedHtmlPath,
pageExportName: handlerOptions.pageExportName,
propsExportName: handlerOptions.propsExportName,
rootExportName: handlerOptions.rootExportName,
htmlExportName: handlerOptions.htmlExportName,
worker,
rscWorker: worker,
onMetrics: handlerOptions.onMetrics,
logger: handlerOptions.logger,
verbose: handlerOptions.verbose
});
} catch (componentResolutionError) {
const error = componentResolutionError instanceof Error ? componentResolutionError : new Error(String(componentResolutionError));
const panicError = handleError({
error,
critical: false,
logger: handlerOptions.logger,
panicThreshold: handlerOptions.panicThreshold,
context: `Component resolution failed for route ${handlerOptions.route}`
});
if (panicError) {
yield {
type: "error",
error: panicError,
metrics: {
rscFull: rscFullMetrics,
rscHeadless: rscHeadlessMetrics,
html: htmlMetrics
}
};
return;
}
handlerOptions.logger?.warn(
`[renderPage.client] Component resolution failed for route ${handlerOptions.route}, continuing with client-only HTML: ${error.message}`
);
const clientOnlyHtmlStreamWrapper = {
pipe: (destination) => {
const minimalHtml = `<!DOCTYPE html><html><head><link rel="expect" href="#«R»" blocking="render"/></head><body><div id="root"></div><template id="«R»"></template></body></html>`;
destination.write(minimalHtml);
destination.end();
return destination;
},
abort: () => {
}
};
const emptyRscStreamWrapper = {
pipe: (destination) => {
destination.end();
return destination;
},
abort: () => {
}
};
yield {
type: "skip",
reason: error,
html: clientOnlyHtmlStreamWrapper,
rsc: emptyRscStreamWrapper,
metrics: {
rscFull: rscFullMetrics,
rscHeadless: rscHeadlessMetrics,
html: htmlMetrics
}
};
return;
}
const newHandlerOptions = {
...handlerOptions,
// Pass page paths to the RSC worker so it knows what to render
pagePath: resolvedPagePath,
propsPath: resolvedPropsPath,
rootPath: resolvedRootPath,
htmlPath: resolvedHtmlPath
};
if (handlerOptions.verbose) {
handlerOptions.logger?.info(
`[renderPage.client] handlerOptions.clientPipeableStreamOptions: ${JSON.stringify(handlerOptions.clientPipeableStreamOptions)}`
);
handlerOptions.logger?.info(
`[renderPage.client] newHandlerOptions.clientPipeableStreamOptions: ${JSON.stringify(newHandlerOptions.clientPipeableStreamOptions)}`
);
handlerOptions.logger?.info(
`[renderPage.client] newHandlerOptions page paths: pagePath=${newHandlerOptions.pagePath}, propsPath=${newHandlerOptions.propsPath}, rootPath=${newHandlerOptions.rootPath}, htmlPath=${newHandlerOptions.htmlPath}`
);
}
const uniqueId = handlerOptions.id ?? `${handlerOptions.route}-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
const headlessRscStreamLocal = createRscStream({
...newHandlerOptions,
id: `${handlerOptions.route}-headless-${uniqueId}`,
rscTimeout: handlerOptions.rscTimeout || 5e3,
onMetrics: handlerOptions.onMetrics,
// Headless RSC stream: page content only (for .rsc file)
htmlPath: "",
// No HTML wrapper - just page content
pagePath: newHandlerOptions.pagePath || "",
// Ensure pagePath is always a string
url: newHandlerOptions.url || "",
// Ensure url is always a string
pageProps: newHandlerOptions.pageProps || {},
// Ensure pageProps is always an object
onEvent: wrappedOnEvent
});
const fullRscStreamLocal = createRscStream({
...newHandlerOptions,
id: `${handlerOptions.route}-full-${uniqueId}`,
rscTimeout: handlerOptions.rscTimeout || 5e3,
onMetrics: handlerOptions.onMetrics,
// Full RSC stream: include HTML wrapper (for HTML generation)
// Pass through the resolved htmlPath so custom Html components work in client mode
htmlPath: resolvedHtmlPath,
pagePath: newHandlerOptions.pagePath || "",
// Ensure pagePath is always a string
url: newHandlerOptions.url || "",
// Ensure url is always a string
pageProps: newHandlerOptions.pageProps || {},
// Ensure pageProps is always an object
// Reuse headless stream elements - the worker will handle this with the unique ID
reuseHeadlessStreamId: headlessRscStreamLocal.id,
onEvent: wrappedOnEvent
});
headlessRscStream = headlessRscStreamLocal;
fullRscStream = fullRscStreamLocal;
if (handlerOptions.verbose) {
handlerOptions.logger?.info(
`[renderPage.client] Creating HTML transform stream with clientPipeableStreamOptions: ${JSON.stringify(newHandlerOptions.clientPipeableStreamOptions)}`
);
}
const htmlTransformStream = createRscToHtmlStream({
...newHandlerOptions,
htmlTimeout: handlerOptions.htmlTimeout || 15e3,
route: handlerOptions.route,
logger: handlerOptions.logger,
verbose: handlerOptions.verbose,
rscStream: fullRscStreamLocal.rscStream
});
htmlHandler = {
htmlStream: htmlTransformStream,
abort: () => {
htmlTransformStream.abort();
}
};
const rscStreamWrapper = {
pipe: (destination) => {
const streamMetrics = createStreamMetrics();
streamMetrics.startTime = performance.now();
const rscFileStream = headlessRscStream.rscStream;
rscFileStream.on("data", (chunk) => {
streamMetrics.chunks++;
streamMetrics.bytes += chunk.length;
});
rscFileStream.on("end", () => {
streamMetrics.duration = performance.now() - streamMetrics.startTime;
streamMetrics.endTime = performance.now();
rscHeadlessMetrics.streamMetrics = streamMetrics;
rscHeadlessMetrics.chunkRate = streamMetrics.chunks / (streamMetrics.duration / 1e3);
rscHeadlessMetrics.processingTime = streamMetrics.duration;
rscHeadlessMetrics.memoryUsage = process.memoryUsage();
rscHeadlessMetrics.chunks = streamMetrics.chunks;
});
rscFileStream.pipe(destination);
return destination;
},
abort: () => headlessRscStream.abort()
};
const htmlStreamWrapper = {
pipe: (destination) => {
if (handlerOptions.verbose) {
handlerOptions.logger?.info(
`[renderPage.client] Piping HTML stream to destination for route: ${handlerOptions.route}`
);
}
return htmlTransformStream.pipe(destination);
},
abort: () => {
fullRscStream.abort();
if (htmlHandler.abort) {
htmlHandler.abort();
}
},
on: (event, listener) => {
if (event === "error") {
const htmlStream = htmlTransformStream.htmlStream;
if (htmlStream && typeof htmlStream.on === "function") {
htmlStream.on("error", listener);
}
}
return htmlStreamWrapper;
}
};
await new Promise((resolve) => setTimeout(resolve, 100));
if (errorResult) {
yield errorResult;
return;
}
yield {
type: "success",
html: htmlStreamWrapper,
rsc: rscStreamWrapper,
metrics: {
rscFull: rscFullMetrics,
rscHeadless: rscHeadlessMetrics,
html: htmlMetrics
}
};
} catch (error) {
try {
if (headlessRscStream) headlessRscStream.abort();
if (fullRscStream) fullRscStream.abort();
if (htmlHandler?.abort) htmlHandler.abort();
} catch (cleanupError) {
handlerOptions.logger?.warn(`Failed to cleanup streams on error: ${cleanupError}`);
}
const panicError = handleError({
error,
logger: handlerOptions.logger,
panicThreshold: handlerOptions.panicThreshold
});
if (panicError != null) {
yield {
type: "error",
error: panicError,
metrics: {
rscFull: rscFullMetrics,
rscHeadless: rscHeadlessMetrics,
html: htmlMetrics
}
};
} else {
const fallbackRscStream = createRscStream({
...handlerOptions,
url: `${handlerOptions.url}`,
route: `${handlerOptions.route}`,
cssFiles: handlerOptions.cssFiles || /* @__PURE__ */ new Map(),
globalCss: handlerOptions.globalCss || /* @__PURE__ */ new Map(),
id: `${handlerOptions.route}-fallback-${Date.now()}`,
rscTimeout: handlerOptions.rscTimeout || 5e3,
onMetrics: handlerOptions.onMetrics,
// Use React.Fragment as fallback (same as server environment)
pagePath: "",
// This will cause the default page to be used, but we'll override it
pageProps: {}
// Ensure pageProps is always an object
});
const fallbackHtmlStream = createRscToHtmlStream({
id: handlerOptions.id,
route: handlerOptions.route,
url: handlerOptions.url,
moduleRootPath: handlerOptions.moduleRootPath,
moduleBasePath: handlerOptions.moduleBasePath,
moduleBaseURL: handlerOptions.moduleBaseURL,
projectRoot: handlerOptions.projectRoot,
panicThreshold: handlerOptions.panicThreshold,
verbose: handlerOptions.verbose,
signal: handlerOptions.signal,
logger: handlerOptions.logger,
htmlTimeout: handlerOptions.htmlTimeout,
clientPipeableStreamOptions: handlerOptions.clientPipeableStreamOptions,
onMetrics: handlerOptions.onMetrics,
build: handlerOptions.build
});
const clientOnlyHtmlStreamWrapper = {
pipe: (destination) => {
return fallbackHtmlStream.pipe(destination);
},
abort: () => {
fallbackRscStream.abort();
}
};
const emptyRscStreamWrapper = {
pipe: (destination) => {
destination.end();
return destination;
},
abort: () => {
}
};
yield {
type: "skip",
reason: error,
html: clientOnlyHtmlStreamWrapper,
rsc: emptyRscStreamWrapper,
metrics: {
rscFull: rscFullMetrics,
rscHeadless: rscHeadlessMetrics,
html: htmlMetrics
}
};
}
}
};
export { renderPage };
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVuZGVyUGFnZS5jbGllbnQuanMiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3BsdWdpbi9yZWFjdC1zdGF0aWMvcmVuZGVyUGFnZS5jbGllbnQudHMiXSwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiByZW5kZXJQYWdlLmNsaWVudC50c1xuICpcbiAqIFBVUlBPU0U6IENsaWVudC1zaWRlIHN0YXRpYyBwYWdlIHJlbmRlcmluZyBmb3IgUmVhY3QgU2VydmVyIENvbXBvbmVudHNcbiAqXG4gKiBBUkNISVRFQ1RVUkUgT1ZFUlZJRVc6XG4gKiBcbiAqIENMSUVOVC1TSURFIHZzIFNFUlZFUi1TSURFOlxuICogLSBTZXJ2ZXItc2lkZTogUlNDIGdlbmVyYXRpb24gaW4gbWFpbiB0aHJlYWQsIEhUTUwgZ2VuZXJhdGlvbiBpbiB3b3JrZXJcbiAqIC0gQ2xpZW50LXNpZGU6IFJTQyBnZW5lcmF0aW9uIGluIHdvcmtlciwgSFRNTCBnZW5lcmF0aW9uIGluIG1haW4gdGhyZWFkXG4gKiBcbiAqIEZMT1c6XG4gKiAxLiBSU0MgV29ya2VyIGdlbmVyYXRlcyBSU0MgY29udGVudCB3aXRoIEhUTUwgd3JhcHBlclxuICogMi4gUlNDIGNvbnRlbnQgaXMgYnVmZmVyZWQgdG8gYWxsb3cgZHVhbCBjb25zdW1wdGlvblxuICogMy4gQnVmZmVyZWQgUlNDIHN0cmVhbSBpcyBjb25zdW1lZCB0d2ljZTpcbiAqICAgIC0gRm9yIFJTQyBmaWxlIHdyaXRpbmcgKGluZGV4LnJzYylcbiAqICAgIC0gRm9yIEhUTUwgdHJhbnNmb3JtYXRpb24gKGluZGV4Lmh0bWwpXG4gKiA0LiBIVE1MIHRyYW5zZm9ybSBwcm9jZXNzZXMgUlNDIGNvbnRlbnQgaW4gbWFpbiB0aHJlYWRcbiAqIDUuIEJvdGggZmlsZXMgYXJlIHdyaXR0ZW4gdG8gZmlsZXN5c3RlbVxuICogXG4gKiBLRVkgSU5TSUdIVDogTm9kZS5qcyBzdHJlYW1zIGNhbiBvbmx5IGJlIGNvbnN1bWVkIG9uY2UsIHNvIHdlIGJ1ZmZlciB0aGUgUlNDXG4gKiBjb250ZW50IHRvIGFsbG93IGl0IHRvIGJlIHVzZWQgZm9yIGJvdGggUlNDIGZpbGUgZ2VuZXJhdGlvbiBhbmQgSFRNTCB0cmFuc2Zvcm1hdGlvbi5cbiAqIFRoaXMgZm9sbG93cyB0aGUgcGF0dGVybiBmcm9tIGNvbGxlY3RSc2NDb250ZW50LnRzLlxuICogXG4gKiBIRUxQRVIgRlVOQ1RJT05TOlxuICogLSBjcmVhdGVCdWZmZXJlZFJzY1N0cmVhbTogQ3JlYXRlcyBhIGJ1ZmZlcmVkIHN0cmVhbSBmb3IgZHVhbCBjb25zdW1wdGlvblxuICogLSBjcmVhdGVSc2NUb0h0bWxTdHJlYW06IFRyYW5zZm9ybXMgUlNDIGNvbnRlbnQgdG8gSFRNTCBpbiBtYWluIHRocmVhZFxuICogXG4gKiBVU0FHRTpcbiAqIGBgYHR5cGVzY3JpcHRcbiAqIGNvbnN0IHJlc3VsdCA9IGF3YWl0IHJlbmRlclBhZ2Uoe1xuICogICByb3V0ZTogXCIvXCIsXG4gKiAgIHBhZ2VQYXRoOiBcInNyYy9wYWdlL3BhZ2UudHN4XCIsXG4gKiAgIC8vIC4uLiBvdGhlciBvcHRpb25zXG4gKiB9KTtcbiAqIFxuICogLy8gcmVzdWx0Lmh0bWwucGlwZShodG1sRmlsZVdyaXRlcik7XG4gKiAvLyByZXN1bHQucnNjLnBpcGUocnNjRmlsZVdyaXRlcik7XG4gKiBgYGBcbiAqL1xuXG5pbXBvcnQgeyBjcmVhdGVSZW5kZXJNZXRyaWNzIH0gZnJvbSBcIi4uL21ldHJpY3MvY3JlYXRlUmVuZGVyTWV0cmljcy5qc1wiO1xuaW1wb3J0IHR5cGUgeyBSZW5kZXJNZXRyaWNzIH0gZnJvbSBcIi4uL21ldHJpY3MvdHlwZXMuanNcIjtcbmltcG9ydCB7IHJvdXRlVG9VUkwgfSBmcm9tIFwiLi4vdXRpbHMvcm91dGVUb1VSTC5qc1wiO1xuaW1wb3J0IHR5cGUgeyBSZW5kZXJQYWdlRm4gfSBmcm9tIFwiLi90eXBlcy5qc1wiO1xuaW1wb3J0IHsgaGFuZGxlRXJyb3IgfSBmcm9tIFwiLi4vZXJyb3IvaGFuZGxlRXJyb3IuanNcIjtcbmltcG9ydCB7IGFzc2VydE5vblJlYWN0U2VydmVyIH0gZnJvbSBcIi4uL2NvbmZpZy9nZXRDb25kaXRpb24uanNcIjtcblxuaW1wb3J0IHsgY3JlYXRlUnNjU3RyZWFtIH0gZnJvbSBcIi4uL3N0cmVhbS9jcmVhdGVSc2NTdHJlYW0uY2xpZW50LmpzXCI7XG5pbXBvcnQgeyByZXNvbHZlQ29tcG9uZW50cyB9IGZyb20gXCIuLi9oZWxwZXJzL3Jlc29sdmVDb21wb25lbnRzLmNsaWVudC5qc1wiO1xuXG5pbXBvcnQgeyBqb2luIH0gZnJvbSBcIm5vZGU6cGF0aFwiO1xuXG5pbXBvcnQgeyBjcmVhdGVTdHJlYW1NZXRyaWNzIH0gZnJvbSBcIi4uL21ldHJpY3MvY3JlYXRlU3RyZWFtTWV0cmljcy5qc1wiO1xuaW1wb3J0IHsgcGVyZm9ybWFuY2UgfSBmcm9tIFwibm9kZTpwZXJmX2hvb2tzXCI7XG5pbXBvcnQgeyBjcmVhdGVSc2NUb0h0bWxTdHJlYW0gfSBmcm9tIFwiLi9yc2NUb0h0bWxTdHJlYW0uY2xpZW50LmpzXCI7XG5cblxuXG5hc3NlcnROb25SZWFjdFNlcnZlcigpO1xuXG4vKipcbiAqIENsaWVudCB2ZXJzaW9uIG9mIHJlbmRlclBhZ2UgdGhhdCB1c2VzIHRoZSByZWFjdC1jbGllbnQgcGF0dGVyblxuICogVGhpcyB3b3JrcyBpbiBSRVZFUlNFIGZyb20gdGhlIHNlcnZlciBwbHVnaW46XG4gKiAtIFNlcnZlcjogTWFpbiB0aHJlYWQgKFJTQykgKyBIVE1MIHdvcmtlciAoSFRNTClcbiAqIC0gQ2xpZW50OiBSU0Mgd29ya2VyIChSU0MpICsgTWFpbiB0aHJlYWQgKEhUTUwpXG4gKi9cbmV4cG9ydCBjb25zdCByZW5kZXJQYWdlOiBSZW5kZXJQYWdlRm4gPSBhc3luYyBmdW5jdGlvbiogX3JlbmRlclBhZ2VDbGllbnQoXG4gIGhhbmRsZXJPcHRpb25zXG4pIHtcbiAgaWYgKGhhbmRsZXJPcHRpb25zLnZlcmJvc2UpIHtcbiAgICBoYW5kbGVyT3B0aW9ucy5sb2dnZXI/LmluZm8oXG4gICAgICBgW3JlbmRlclBhZ2UuY2xpZW50XSBvbkV2ZW50IGNhbGxiYWNrIGV4aXN0czogJHshIWhhbmRsZXJPcHRpb25zLm9uRXZlbnR9YFxuICAgICk7XG4gICAgaGFuZGxlck9wdGlvbnMubG9nZ2VyPy5pbmZvKFxuICAgICAgYFtyZW5kZXJQYWdlLmNsaWVudF0gb25NZXRyaWNzIGNhbGxiYWNrIGV4aXN0czogJHshIWhhbmRsZXJPcHRpb25zLm9uTWV0cmljc31gXG4gICAgKTtcbiAgfVxuXG4gIC8vIFRyYWNrIGlmIHdlJ3ZlIHlpZWxkZWQgYSByZXN1bHQgdG8gcHJldmVudCBtdWx0aXBsZSB5aWVsZHNcbiAgbGV0IGhhc1lpZWxkZWQgPSBmYWxzZTtcbiAgbGV0IGVycm9yUmVzdWx0OiBhbnkgPSBudWxsO1xuXG4gIC8vIENyZWF0ZSBhIHdyYXBwZXIgYXJvdW5kIG9uRXZlbnQgdG8gaGFuZGxlIHJvdXRlLmVycm9yIGV2ZW50c1xuICBjb25zdCB3cmFwcGVkT25FdmVudCA9IChldmVudDogYW55KSA9PiB7XG4gICAgLy8gQ2FsbCB0aGUgb3JpZ2luYWwgb25FdmVudCBmaXJzdFxuICAgIGlmIChoYW5kbGVyT3B0aW9ucy5vbkV2ZW50KSB7XG4gICAgICBoYW5kbGVyT3B0aW9ucy5vbkV2ZW50KGV2ZW50KTtcbiAgICB9XG4gICAgXG4gICAgLy8gSGFuZGxlIHJvdXRlLmVycm9yIGV2ZW50cyBieSBzdG9yaW5nIHJlc3VsdCBmb3IgbGF0ZXIgeWllbGRpbmdcbiAgICBpZiAoZXZlbnQudHlwZSA9PT0gXCJyb3V0ZS5lcnJvclwiICYmICFoYXNZaWVsZGVkKSB7XG4gICAgICBoYXNZaWVsZGVkID0gdHJ1ZTtcbiAgICAgIFxuICAgICAgLy8gQ2hlY2sgaWYgdGhpcyBzaG91bGQgY2F1c2UgYSBwYW5pY1xuICAgICAgY29uc3QgcGFuaWNFcnJvciA9IGhhbmRsZUVycm9yKHtcbiAgICAgICAgZXJyb3I6IGV2ZW50LmRhdGEuZXJyb3IsXG4gICAgICAgIGxvZ2dlcjogaGFuZGxlck9wdGlvbnMubG9nZ2VyLFxuICAgICAgICBwYW5pY1RocmVzaG9sZDogZXZlbnQuZGF0YS5wYW5pY1RocmVzaG9sZCxcbiAgICAgICAgY29udGV4dDogYHJvdXRlLmVycm9yICgke2V2ZW50LmRhdGEucm91dGV9KWAsXG4gICAgICB9KTtcbiAgICAgIFxuICAgICAgaWYgKHBhbmljRXJyb3IgIT0gbnVsbCkge1xuICAgICAgICAvLyBUaGlzIGlzIGEgcGFuaWMgZXJyb3IsIHN0b3JlIGVycm9yIHJlc3VsdFxuICAgICAgICBlcnJvclJlc3VsdCA9IHtcbiAgICAgICAgICB0eXBlOiBcImVycm9yXCIsXG4gICAgICAgICAgZXJyb3I6IHBhbmljRXJyb3IsXG4gICAgICAgICAgbWV0cmljczoge1xuICAgICAgICAgICAgcnNjSGVhZGxlc3M6IHsgZHVyYXRpb246IDAsIGNodW5rczogMCwgYnl0ZXM6IDAgfSxcbiAgICAgICAgICAgIGh0bWw6IHsgZHVyYXRpb246IDAsIGNodW5rczogMCwgYnl0ZXM6IDAgfSxcbiAgICAgICAgICB9LFxuICAgICAgICB9O1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgLy8gVGhpcyBpcyBhIG5vbi1wYW5pYyBlcnJvciwgc3RvcmUgc2tpcCByZXN1bHRcbiAgICAgICAgZXJyb3JSZXN1bHQgPSB7XG4gICAgICAgICAgdHlwZTogXCJza2lwXCIsXG4gICAgICAgICAgcmVhc29uOiBldmVudC5kYXRhLmVycm9yLm1lc3NhZ2UgfHwgXCJOb24tcGFuaWMgZXJyb3Igb2NjdXJyZWRcIixcbiAgICAgICAgICBodG1sOiB7IGR1cmF0aW9uOiAwLCBjaHVua3M6IDAsIGJ5dGVzOiAwIH0sXG4gICAgICAgICAgcnNjOiB7IGR1cmF0aW9uOiAwLCBjaHVua3M6IDAsIGJ5dGVzOiAwIH0sXG4gICAgICAgICAgbWV0cmljczoge1xuICAgICAgICAgICAgcnNjSGVhZGxlc3M6IHsgZHVyYXRpb246IDAsIGNodW5rczogMCwgYnl0ZXM6IDAgfSxcbiAgICAgICAgICAgIGh0bWw6IHsgZHVyYXRpb246IDAsIGNodW5rczogMCwgYnl0ZXM6IDAgfSxcbiAgICAgICAgICB9LFxuICAgICAgICB9O1xuICAgICAgfVxuICAgIH1cbiAgfTtcblxuICAvLyBTa2lwIGlmIG5vIHBhZ2VQYXRoIEFORCBubyBQYWdlQ29tcG9uZW50IHByb3ZpZGVkIChmYWxsYmFjayBjYXNlKVxuICBpZiAoIWhhbmRsZXJPcHRpb25zLnBhZ2VQYXRoICYmICFoYW5kbGVyT3B0aW9ucy5QYWdlQ29tcG9uZW50KSB7XG4gICAgLy8gQ3JlYXRlIGVtcHR5IHN0cmVhbSB3cmFwcGVycyBmb3Igc2tpcCBjYXNlXG4gICAgY29uc3QgZW1wdHlTdHJlYW1XcmFwcGVyID0ge1xuICAgICAgcGlwZTogPFdyaXRhYmxlIGV4dGVuZHMgTm9kZUpTLldyaXRhYmxlU3RyZWFtPihkZXN0aW5hdGlvbjogV3JpdGFibGUpID0+IHtcbiAgICAgICAgZGVzdGluYXRpb24uZW5kKCk7XG4gICAgICAgIHJldHVybiBkZXN0aW5hdGlvbjtcbiAgICAgIH0sXG4gICAgICBhYm9ydDogKCkgPT4ge1xuICAgICAgICAvLyBObyBjbGVhbnVwIG5lZWRlZFxuICAgICAgfSxcbiAgICB9O1xuXG4gICAgeWllbGQge1xuICAgICAgdHlwZTogXCJza2lwXCIsXG4gICAgICByZWFzb246IFwiTm8gcGFnZVBhdGggYW5kIG5vIFBhZ2VDb21wb25lbnQgcHJvdmlkZWRcIixcbiAgICAgIGh0bWw6IGVtcHR5U3RyZWFtV3JhcHBlcixcbiAgICAgIHJzYzogZW1wdHlTdHJlYW1XcmFwcGVyLFxuICAgICAgbWV0cmljczoge1xuICAgICAgICByc2NGdWxsOiBjcmVhdGVSZW5kZXJNZXRyaWNzKHtcbiAgICAgICAgICByb3V0ZTogaGFuZGxlck9wdGlvbnMucm91dGUsXG4gICAgICAgICAgdHlwZTogXCJyc2MtZnVsbFwiLFxuICAgICAgICAgIGZyb21NYWluVGhyZWFkOiBmYWxzZSxcbiAgICAgICAgICBmcm9tUnNjV29ya2VyOiB0cnVlLFxuICAgICAgICAgIGZyb21IdG1sV29ya2VyOiBmYWxzZSxcbiAgICAgICAgfSkgYXMgUmVuZGVyTWV0cmljcyAmIHsgdHlwZTogXCJyc2MtZnVsbFwiIH0sXG4gICAgICAgIHJzY0hlYWRsZXNzOiBjcmVhdGVSZW5kZXJNZXRyaWNzKHtcbiAgICAgICAgICByb3V0ZTogaGFuZGxlck9wdGlvbnMucm91dGUsXG4gICAgICAgICAgdHlwZTogXCJyc2MtaGVhZGxlc3NcIixcbiAgICAgICAgICBmcm9tTWFpblRocmVhZDogZmFsc2UsXG4gICAgICAgICAgZnJvbVJzY1dvcmtlcjogdHJ1ZSxcbiAgICAgICAgICBmcm9tSHRtbFdvcmtlcjogZmFsc2UsXG4gICAgICAgIH0pIGFzIFJlbmRlck1ldHJpY3MgJiB7IHR5cGU6IFwicnNjLWhlYWRsZXNzXCIgfSxcbiAgICAgICAgaHRtbDogY3JlYXRlUmVuZGVyTWV0cmljcyh7XG4gICAgICAgICAgcm91dGU6IGhhbmRsZXJPcHRpb25zLnJvdXRlLFxuICAgICAgICAgIHR5cGU6IFwiaHRtbFwiLFxuICAgICAgICAgIGZyb21NYWluVGhyZWFkOiB0cnVlLFxuICAgICAgICAgIGZyb21Sc2NXb3JrZXI6IGZhbHNlLFxuICAgICAgICAgIGZyb21IdG1sV29ya2VyOiBmYWxzZSxcbiAgICAgICAgfSkgYXMgUmVuZGVyTWV0cmljcyAmIHsgdHlwZTogXCJodG1sXCIgfSxcbiAgICAgIH0sXG4gICAgfTtcbiAgICByZXR1cm47XG4gIH1cblxuICBpZiAoIWhhbmRsZXJPcHRpb25zLnVybCkge1xuICAgIGhhbmRsZXJPcHRpb25zLnVybCA9IHJvdXRlVG9VUkwoXG4gICAgICBoYW5kbGVyT3B0aW9ucy5yb3V0ZSxcbiAgICAgIGhhbmRsZXJPcHRpb25zLm1vZHVsZUJhc2VVUkwsXG4gICAgICBoYW5kbGVyT3B0aW9ucy5idWlsZC5yc2NPdXRwdXRQYXRoXG4gICAgKTtcbiAgfVxuXG4gIGNvbnN0IGJhc2VEaXIgPSBqb2luKFxuICAgIGhhbmRsZXJPcHRpb25zLmJ1aWxkLm91dERpcixcbiAgICBoYW5kbGVyT3B0aW9ucy5idWlsZC5zdGF0aWNcbiAgKTtcbiAgY29uc3Qgcm91dGVQYXRoID0gaGFuZGxlck9wdGlvbnMucm91dGUucmVwbGFjZSgvXlxcLy8sIFwiXCIpO1xuXG4gIC8vIENyZWF0ZSBtZXRyaWNzIHVwZnJvbnQgd2l0aCBwcm9wZXIgdHlwZXMgLSBSRVZFUlNFIGZyb20gc2VydmVyXG4gIGNvbnN0IGh0bWxNZXRyaWNzID0gY3JlYXRlUmVuZGVyTWV0cmljcyh7XG4gICAgcm91dGU6IGhhbmRsZXJPcHRpb25zLnJvdXRlLFxuICAgIHR5cGU6IFwiaHRtbFwiLFxuICAgIGZyb21NYWluVGhyZWFkOiB0cnVlLCAvLyBDbGllbnQ6IEhUTUwgcmVuZGVyZWQgb24gbWFpbiB0aHJlYWRcbiAgICBmcm9tUnNjV29ya2VyOiBmYWxzZSxcbiAgICBmcm9tSHRtbFdvcmtlcjogZmFsc2UsXG4gICAgYmFzZURpcixcbiAgICByb3V0ZVBhdGgsXG4gICAgZmlsZU5hbWU6IGhhbmRsZXJPcHRpb25zLmJ1aWxkLmh0bWxPdXRwdXRQYXRoLFxuICAgIG91dHB1dFBhdGg6IGpvaW4oYmFzZURpciwgcm91dGVQYXRoLCBoYW5kbGVyT3B0aW9ucy5idWlsZC5odG1sT3V0cHV0UGF0aCksXG4gIH0pO1xuICBcbiAgY29uc3QgcnNjRnVsbE1ldHJpY3MgPSBjcmVhdGVSZW5kZXJNZXRyaWNzKHtcbiAgICByb3V0ZTogaGFuZGxlck9wdGlvbnMucm91dGUsXG4gICAgdHlwZTogXCJyc2MtZnVsbFwiLFxuICAgIGZyb21NYWluVGhyZWFkOiBmYWxzZSxcbiAgICBmcm9tUnNjV29ya2VyOiB0cnVlLCAvLyBDbGllbnQ6IFJTQyByZW5kZXJlZCBvbiBSU0Mgd29ya2VyXG4gICAgZnJvbUh0bWxXb3JrZXI6IGZhbHNlLFxuICB9KTtcbiAgXG4gIGNvbnN0IHJzY0hlYWRsZXNzTWV0cmljcyA9IGNyZWF0ZVJlbmRlck1ldHJpY3Moe1xuICAgIHJvdXRlOiBoYW5kbGVyT3B0aW9ucy5yb3V0ZSxcbiAgICB0eXBlOiBcInJzYy1oZWFkbGVzc1wiLFxuICAgIGZyb21NYWluVGhyZWFkOiBmYWxzZSxcbiAgICBmcm9tUnNjV29ya2VyOiB0cnVlLCAvLyBDbGllbnQ6IFJTQyByZW5kZXJlZCBvbiBSU0Mgd29ya2VyXG4gICAgZnJvbUh0bWxXb3JrZXI6IGZhbHNlLFxuICAgIGJhc2VEaXIsXG4gICAgcm91dGVQYXRoLFxuICAgIGZpbGVOYW1lOiBoYW5kbGVyT3B0aW9ucy5idWlsZC5yc2NPdXRwdXRQYXRoLFxuICAgIG91dHB1dFBhdGg6IGpvaW4oYmFzZURpciwgcm91dGVQYXRoLCBoYW5kbGVyT3B0aW9ucy5idWlsZC5yc2NPdXRwdXRQYXRoKSxcbiAgfSk7XG5cbiAgLy8gRGVjbGFyZSB2YXJpYWJsZXMgb3V0c2lkZSB0cnkgYmxvY2sgc28gdGhleSBjYW4gYmUgYWNjZXNzZWQgaW4gY2F0Y2ggYmxvY2tcbiAgbGV0IGhlYWRsZXNzUnNjU3RyZWFtOiBhbnkgPSBudWxsO1xuICBsZXQgZnVsbFJzY1N0cmVhbTogYW55ID0gbnVsbDtcbiAgbGV0IGh0bWxIYW5kbGVyOiBhbnkgPSBudWxsO1xuICBcblxuXG4gIHRyeSB7XG4gICAgaWYgKGhhbmRsZXJPcHRpb25zLnZlcmJvc2UpIHtcbiAgICAgIGhhbmRsZXJPcHRpb25zLmxvZ2dlcj8uaW5mbyhcbiAgICAgICAgYFtyZW5kZXJQYWdlLmNsaWVudF0gQ2xpZW50LXNpZGUgcmVuZGVyaW5nIGZvciByb3V0ZTogJHtoYW5kbGVyT3B0aW9ucy5yb3V0ZX1gXG4gICAgICApO1xuICAgIH1cblxuICAgIC8vIFN0ZXAgMTogUmVzb2x2ZSBwYXRocyB0byBidWlsdCBwYXRocyB1c2luZyB0aGUgc2VydmVyIG1hbmlmZXN0XG4gICAgLy8gVGhlIGNsaWVudCB2ZXJzaW9uIG5lZWRzIHRvIHVzZSB0aGUgc2VydmVyIG1hbmlmZXN0IHRvIGdldCB0aGUgYnVpbHQgcGF0aHNcbiAgICAvLyBmb3IgdGhlIHBhZ2UgY29tcG9uZW50cywgbm90IHRoZSBzdGF0aWMgbWFuaWZlc3RcbiAgICBjb25zdCByZXNvbHZlUGF0aFdpdGhNYW5pZmVzdCA9IChwYXRoOiBzdHJpbmcsIG1hbmlmZXN0OiBhbnkpOiBzdHJpbmcgPT4ge1xuICAgICAgY29uc3QgZW50cnkgPSBtYW5pZmVzdFtwYXRoXTtcbiAgICAgIGlmIChlbnRyeSAmJiBlbnRyeS5maWxlKSB7XG4gICAgICAgIHJldHVybiBlbnRyeS5maWxlO1xuICAgICAgfVxuICAgICAgcmV0dXJuIHBhdGg7XG4gICAgfTtcblxuICAgIC8vIFVzZSBtYW5pZmVzdCBmb3IgcGFnZSBjb21wb25lbnQgcmVzb2x1dGlvbiAoY2xpZW50IHZlcnNpb24gd29ya3MgaW4gcmV2ZXJzZSlcbiAgICBjb25zdCBtYW5pZmVzdCA9IGhhbmRsZXJPcHRpb25zLm1hbmlmZXN0IHx8IHt9O1xuICAgIGNvbnN0IHJlc29sdmVkUGFnZVBhdGggPSBoYW5kbGVyT3B0aW9ucy5wYWdlUGF0aCA/IHJlc29sdmVQYXRoV2l0aE1hbmlmZXN0KGhhbmRsZXJPcHRpb25zLnBhZ2VQYXRoLCBtYW5pZmVzdCkgOiB1bmRlZmluZWQ7XG4gICAgY29uc3QgcmVzb2x2ZWRQcm9wc1BhdGggPSBoYW5kbGVyT3B0aW9ucy5wcm9wc1BhdGggPyByZXNvbHZlUGF0aFdpdGhNYW5pZmVzdChoYW5kbGVyT3B0aW9ucy5wcm9wc1BhdGgsIG1hbmlmZXN0KSA6IHVuZGVmaW5lZDtcbiAgICBjb25zdCByZXNvbHZlZFJvb3RQYXRoID0gaGFuZGxlck9wdGlvbnMucm9vdFBhdGggPyByZXNvbHZlUGF0aFdpdGhNYW5pZmVzdChoYW5kbGVyT3B0aW9ucy5yb290UGF0aCwgbWFuaWZlc3QpIDogdW5kZWZpbmVkO1xuICAgIGNvbnN0IHJlc29sdmVkSHRtbFBhdGggPSBoYW5kbGVyT3B0aW9ucy5odG1sUGF0aCA/IHJlc29sdmVQYXRoV2l0aE1hbmlmZXN0KGhhbmRsZXJPcHRpb25zLmh0bWxQYXRoLCBtYW5pZmVzdCkgOiB1bmRlZmluZWQ7XG5cbiAgICBpZiAoaGFuZGxlck9wdGlvbnMudmVyYm9zZSkge1xuICAgICAgaGFuZGxlck9wdGlvbnMubG9nZ2VyPy5pbmZvKGBbcmVuZGVyUGFnZS5jbGllbnRdIFJlc29sdmVkIHBhdGhzIGZvciByb3V0ZSAke2hhbmRsZXJPcHRpb25zLnJvdXRlfTpgKTtcbiAgICAgIGhhbmRsZXJPcHRpb25zLmxvZ2dlcj8uaW5mbyhgICBwYWdlOiAke2hhbmRsZXJPcHRpb25zLnBhZ2VQYXRofSAtPiAke3Jlc29sdmVkUGFnZVBhdGh9YCk7XG4gICAgICBoYW5kbGVyT3B0aW9ucy5sb2dnZXI/LmluZm8oYCAgcHJvcHM6ICR7aGFuZGxlck9wdGlvbnMucHJvcHNQYXRofSAtPiAke3Jlc29sdmVkUHJvcHNQYXRofWApO1xuICAgICAgaGFuZGxlck9wdGlvbnMubG9nZ2VyPy5pbmZvKGAgIHJvb3Q6ICR7aGFuZGxlck9wdGlvbnMucm9vdFBhdGh9IC0+ICR7cmVzb2x2ZWRSb290UGF0aH1gKTtcbiAgICAgIGhhbmRsZXJPcHRpb25zLmxvZ2dlcj8uaW5mbyhgICBodG1sOiAke2hhbmRsZXJPcHRpb25zLmh0bWxQYXRofSAtPiAke3Jlc29sdmVkSHRtbFBhdGh9YCk7XG4gICAgICBoYW5kbGVyT3B0aW9ucy5sb2dnZXI/LmluZm8oYCAgbWFuaWZlc3Qga2V5czogJHtPYmplY3Qua2V5cyhtYW5pZmVzdCkuam9pbignLCAnKX1gKTtcbiAgICAgIGhhbmRsZXJPcHRpb25zLmxvZ2dlcj8uaW5mbyhgICBIVE1MIHBhdGggaXNzdWU6IGh0bWxQYXRoPScke2hhbmRsZXJPcHRpb25zLmh0bWxQYXRofScsIHJlc29sdmVkPScke3Jlc29sdmVkSHRtbFBhdGh9JywgbWFuaWZlc3QgaGFzIEh0bWwgZW50cnk6ICR7ISFtYW5pZmVzdFtoYW5kbGVyT3B0aW9ucy5odG1sUGF0aCB8fCAnJ119YCk7XG4gICAgICBoYW5kbGVyT3B0aW9ucy5sb2dnZXI/LmluZm8oYCAgQWJvdXQgdG8gcGFzcyBodG1sUGF0aD0nJHtyZXNvbHZlZEh0bWxQYXRofScgdG8gUlNDIHN0cmVhbWApO1xuICAgIH1cbiAgICBjb25zdCB3b3JrZXIgPSBoYW5kbGVyT3B0aW9ucy53b3JrZXIgPz8gaGFuZGxlck9wdGlvbnMucnNjV29ya2VyO1xuXG4gICAgLy8gU3RlcCAyOiBSZXNvbHZlIGNvbXBvbmVudHMgdXNpbmcgdGhlIFJTQyB3b3JrZXIgd2l0aCBidWlsdCBwYXRoc1xuICAgIC8vIFRoaXMgc2VwYXJhdGVzIGNvbXBvbmVudCByZXNvbHV0aW9uIGZyb20gUlNDIGdlbmVyYXRpb24sIG1ha2luZyB0aGVcbiAgICAvLyBzdWJzZXF1ZW50IFJTQyByZW5kZXIgY29tcGxldGVseSBzeW5jaHJvbm91c1xuICAgIGlmICghd29ya2VyKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoXCJSU0Mgd29ya2VyIGlzIHJlcXVpcmVkIGZvciBjbGllbnQtc2lkZSBjb21wb25lbnQgcmVzb2x1dGlvblwiKTtcbiAgICB9XG4gICAgXG4gICAgLy8gUHJlbG9hZCBjb21wb25lbnRzIGluIHRoZSB3b3JrZXIgZm9yIGZhc3RlciBzdWJzZXF1ZW50IFJTQyBzdHJlYW0gZ2VuZXJhdGlvblxuICAgIHRyeSB7XG4gICAgICBhd2FpdCByZXNvbHZlQ29tcG9uZW50cyh7XG4gICAgICAgIHJvdXRlOiBoYW5kbGVyT3B0aW9ucy5yb3V0ZSxcbiAgICAgICAgcGFnZVBhdGg6IHJlc29sdmVkUGFnZVBhdGgsXG4gICAgICAgIHByb3BzUGF0aDogcmVzb2x2ZWRQcm9wc1BhdGgsXG4gICAgICAgIHJvb3RQYXRoOiByZXNvbHZlZFJvb3RQYXRoLFxuICAgICAgICBodG1sUGF0aDogcmVzb2x2ZWRIdG1sUGF0aCxcbiAgICAgICAgcGFnZUV4cG9ydE5hbWU6IGhhbmRsZXJPcHRpb25zLnBhZ2VFeHBvcnROYW1lLFxuICAgICAgICBwcm9wc0V4cG9ydE5hbWU6IGhhbmRsZXJPcHRpb25zLnByb3BzRXhwb3J0TmFtZSxcbiAgICAgICAgcm9vdEV4cG9ydE5hbWU6IGhhbmRsZXJPcHRpb25zLnJvb3RFeHBvcnROYW1lLFxuICAgICAgICBodG1sRXhwb3J0TmFtZTogaGFuZGxlck9wdGlvbnMuaHRtbEV4cG9ydE5hbWUsXG4gICAgICAgIHdvcmtlcjogd29ya2VyLFxuICAgICAgICByc2NXb3JrZXI6IHdvcmtlcixcbiAgICAgICAgb25NZXRyaWNzOiBoYW5kbGVyT3B0aW9ucy5vbk1ldHJpY3MsXG4gICAgICAgIGxvZ2dlcjogaGFuZGxlck9wdGlvbnMubG9nZ2VyLFxuICAgICAgICB2ZXJib3NlOiBoYW5kbGVyT3B0aW9ucy52ZXJib3NlLFxuICAgICAgfSk7XG4gICAgfSBjYXRjaCAoY29tcG9uZW50UmVzb2x1dGlvbkVycm9yKSB7XG4gICAgICAvLyBIYW5kbGUgY29tcG9uZW50IHJlc29sdXRpb24gZmFpbHVyZXMgZ3JhY2VmdWxseVxuICAgICAgY29uc3QgZXJyb3IgPSBjb21wb25lbnRSZXNvbHV0aW9uRXJyb3IgaW5zdGFuY2VvZiBFcnJvciBcbiAgICAgICAgPyBjb21wb25lbnRSZXNvbHV0aW9uRXJyb3IgXG4gICAgICAgIDogbmV3IEVycm9yKFN0cmluZyhjb21wb25lbnRSZXNvbHV0aW9uRXJyb3IpKTtcbiAgICAgIFxuICAgICAgLy8gQ2hlY2sgaWYgdGhpcyBjb21wb25lbnQgcmVzb2x1dGlvbiBlcnJvciBzaG91bGQgY2F1c2UgYSBwYW5pYyBiYXNlZCBvbiBwYW5pY1RocmVzaG9sZFxuICAgICAgY29uc3QgcGFuaWNFcnJvciA9IGhhbmRsZUVycm9yKHtcbiAgICAgICAgZXJyb3IsXG4gICAgICAgIGNyaXRpY2FsOiBmYWxzZSxcbiAgICAgICAgbG9nZ2VyOiBoYW5kbGVyT3B0aW9ucy5sb2dnZXIsXG4gICAgICAgIHBhbmljVGhyZXNob2xkOiBoYW5kbGVyT3B0aW9ucy5wYW5pY1RocmVzaG9sZCxcbiAgICAgICAgY29udGV4dDogYENvbXBvbmVudCByZXNvbHV0aW9uIGZhaWxlZCBmb3Igcm91dGUgJHtoYW5kbGVyT3B0aW9ucy5yb3V0ZX1gLFxuICAgICAgfSk7XG4gICAgICBcbiAgICAgICAgICAgICAvLyBJZiB0aGlzIHNob3VsZCBjYXVzZSBhIHBhbmljLCB5aWVsZCBlcnJvciBhbmQgcmV0dXJuXG4gICAgICAgaWYgKHBhbmljRXJyb3IpIHtcbiAgICAgICAgIHlpZWxkIHtcbiAgICAgICAgICAgdHlwZTogXCJlcnJvclwiLFxuICAgICAgICAgICBlcnJvcjogcGFuaWNFcnJvcixcbiAgICAgICAgICAgbWV0cmljczoge1xuICAgICAgICAgICAgIHJzY0Z1bGw6IHJzY0Z1bGxNZXRyaWNzLFxuICAgICAgICAgICAgIHJzY0hlYWRsZXNzOiByc2NIZWFkbGVzc01ldHJpY3MsXG4gICAgICAgICAgICAgaHRtbDogaHRtbE1ldHJpY3MsXG4gICAgICAgICAgIH0sXG4gICAgICAgICB9O1xuICAgICAgICAgcmV0dXJuO1xuICAgICAgIH1cbiAgICAgICBcbiAgICAgICAvLyBPdGhlcndpc2UsIHRyZWF0IHRoaXMgYXMgYSBub24tY3JpdGljYWwgZXJyb3IgYW5kIGNvbnRpbnVlIHdpdGggY2xpZW50LW9ubHkgSFRNTFxuICAgICAgIC8vIFRoaXMgYWxsb3dzIHRoZSBidWlsZCB0byBjb21wbGV0ZSB3aXRoIGEgY2xpZW50LW9ubHkgcGFnZVxuICAgICAgIGhhbmRsZXJPcHRpb25zLmxvZ2dlcj8ud2FybihcbiAgICAgICAgIGBbcmVuZGVyUGFnZS5jbGllbnRdIENvbXBvbmVudCByZXNvbHV0aW9uIGZhaWxlZCBmb3Igcm91dGUgJHtoYW5kbGVyT3B0aW9ucy5yb3V0ZX0sIGNvbnRpbnVpbmcgd2l0aCBjbGllbnQtb25seSBIVE1MOiAke2Vycm9yLm1lc3NhZ2V9YFxuICAgICAgICk7XG4gICAgICAgXG4gICAgICAgLy8gQ3JlYXRlIGEgY2xpZW50LW9ubHkgSFRNTCBzdHJlYW0gd3JhcHBlciB3aXRoIG1pbmltYWwgSFRNTFxuICAgICAgIGNvbnN0IGNsaWVudE9ubHlIdG1sU3RyZWFtV3JhcHBlciA9IHtcbiAgICAgICAgIHBpcGU6IDxXcml0YWJsZSBleHRlbmRzIE5vZGVKUy5Xcml0YWJsZVN0cmVhbT4oZGVzdGluYXRpb246IFdyaXRhYmxlKSA9PiB7XG4gICAgICAgICAgIC8vIFdyaXRlIGEgbWluaW1hbCBjbGllbnQtb25seSBIVE1MIHN0cnVjdHVyZVxuICAgICAgICAgICBjb25zdCBtaW5pbWFsSHRtbCA9IGA8IURPQ1RZUEUgaHRtbD48aHRtbD48aGVhZD48bGluayByZWw9XCJleHBlY3RcIiBocmVmPVwiI8KrUsK7XCIgYmxvY2tpbmc9XCJyZW5kZXJcIi8+PC9oZWFkPjxib2R5PjxkaXYgaWQ9XCJyb290XCI+PC9kaXY+PHRlbXBsYXRlIGlkPVwiwqtSwrtcIj48L3RlbXBsYXRlPjwvYm9keT48L2h0bWw+YDtcbiAgICAgICAgICAgZGVzdGluYXRpb24ud3JpdGUobWluaW1hbEh0bWwpO1xuICAgICAgICAgICBkZXN0aW5hdGlvbi5lbmQoKTtcbiAgICAgICAgICAgcmV0dXJuIGRlc3RpbmF0aW9uO1xuICAgICAgICAgfSxcbiAgICAgICAgIGFib3J0OiAoKSA9PiB7XG4gICAgICAgICAgIC8vIE5vIGNsZWFudXAgbmVlZGVkIGZvciBzaW1wbGUgSFRNTCBzdHJpbmdcbiAgICAgICAgIH0sXG4gICAgICAgfTtcbiAgICAgICBcbiAgICAgICAvLyBDcmVhdGUgYW4gZW1wdHkgUlNDIHN0cmVhbSB3cmFwcGVyXG4gICAgICAgY29uc3QgZW1wdHlSc2NTdHJlYW1XcmFwcGVyID0ge1xuICAgICAgICAgcGlwZTogPFdyaXRhYmxlIGV4dGVuZHMgTm9kZUpTLldyaXRhYmxlU3RyZWFtPihkZXN0aW5hdGlvbjogV3JpdGFibGUpID0+IHtcbiAgICAgICAgICAgLy8gTm8gUlNDIGNvbnRlbnQgZm9yIGZhaWxlZCBjb21wb25lbnQgcmVzb2x1dGlvblxuICAgICAgICAgICBkZXN0aW5hdGlvbi5lbmQoKTtcbiAgICAgICAgICAgcmV0dXJuIGRlc3RpbmF0aW9uO1xuICAgICAgICAgfSxcbiAgICAgICAgIGFib3J0OiAoKSA9PiB7XG4gICAgICAgICAgIC8vIE5vIGNsZWFudXAgbmVlZGVkXG4gICAgICAgICB9LFxuICAgICAgIH07XG4gICAgICAgXG4gICAgICAgLy8gWWllbGQgc2tpcCByZXN1bHQgd2l0aCBjbGllbnQtb25seSBIVE1MIGFuZCBlbXB0eSBSU0NcbiAgICAgICB5aWVsZCB7XG4gICAgICAgICB0eXBlOiBcInNraXBcIixcbiAgICAgICAgIHJlYXNvbjogZXJyb3IsXG4gICAgICAgICBodG1sOiBjbGllbnRPbmx5SHRtbFN0cmVhbVdyYXBwZXIsXG4gICAgICAgICByc2M6IGVtcHR5UnNjU3RyZWFtV3JhcHBlcixcbiAgICAgICAgIG1ldHJpY3M6IHtcbiAgICAgICAgICAgcnNjRnVsbDogcnNjRnVsbE1ldHJpY3MsXG4gICAgICAgICAgIHJzY0hlYWRsZXNzOiByc2NIZWFkbGVzc01ldHJpY3MsXG4gICAgICAgICAgIGh0bWw6IGh0bWxNZXRyaWNzLFxuICAgICAgICAgfSxcbiAgICAgICB9O1xuICAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICAvLyBTdGVwIDI6IENyZWF0ZSBoYW5kbGVyIG9wdGlvbnNcbiAgICAvLyBDb21wb25lbnRzIGFyZSBub3cgcHJlbG9hZGVkIGluIHRoZSB3b3JrZXIsIHNvIHdlIGNhbiB1c2UgdGhlIG9yaWdpbmFsIGhhbmRsZXIgb3B0aW9uc1xuICAgIGNvbnN0IG5ld0hhbmRsZXJPcHRpb25zID0ge1xuICAgICAgLi4uaGFuZGxlck9wdGlvbnMsXG4gICAgICAvLyBQYXNzIHBhZ2UgcGF0aHMgdG8gdGhlIFJTQyB3b3JrZXIgc28gaXQga25vd3Mgd2hhdCB0byByZW5kZXJcbiAgICAgIHBhZ2VQYXRoOiByZXNvbHZlZFBhZ2VQYXRoLFxuICAgICAgcHJvcHNQYXRoOiByZXNvbHZlZFByb3BzUGF0aCxcbiAgICAgIHJvb3RQYXRoOiByZXNvbHZlZFJvb3RQYXRoLFxuICAgICAgaHRtbFBhdGg6IHJlc29sdmVkSHRtbFBhdGgsXG4gICAgfTtcblxuICAgIGlmIChoYW5kbGVyT3B0aW9ucy52ZXJib3NlKSB7XG4gICAgICBoYW5kbGVyT3B0aW9ucy5sb2dnZXI/LmluZm8oXG4gICAgICAgIGBbcmVuZGVyUGFnZS5jbGllbnRdIGhhbmRsZXJPcHRpb25zLmNsaWVudFBpcGVhYmxlU3RyZWFtT3B0aW9uczogJHtKU09OLnN0cmluZ2lmeShoYW5kbGVyT3B0aW9ucy5jbGllbnRQaXBlYWJsZVN0cmVhbU9wdGlvbnMpfWBcbiAgICAgICk7XG4gICAgICBoYW5kbGVyT3B0aW9ucy5sb2dnZXI/LmluZm8oXG4gICAgICAgIGBbcmVuZGVyUGFnZS5jbGllbnRdIG5ld0hhbmRsZXJPcHRpb25zLmNsaWVudFBpcGVhYmxlU3RyZWFtT3B0aW9uczogJHtKU09OLnN0cmluZ2lmeShuZXdIYW5kbGVyT3B0aW9ucy5jbGllbnRQaXBlYWJsZVN0cmVhbU9wdGlvbnMpfWBcbiAgICAgICk7XG4gICAgICBoYW5kbGVyT3B0aW9ucy5sb2dnZXI/LmluZm8oXG4gICAgICAgIGBbcmVuZGVyUGFnZS5jbGllbnRdIG5ld0hhbmRsZXJPcHRpb25zIHBhZ2UgcGF0aHM6IHBhZ2VQYXRoPSR7bmV3SGFuZGxlck9wdGlvbnMucGFnZVBhdGh9LCBwcm9wc1BhdGg9JHtuZXdIYW5kbGVyT3B0aW9ucy5wcm9wc1BhdGh9LCByb290UGF0aD0ke25ld0hhbmRsZXJPcHRpb25zLnJvb3RQYXRofSwgaHRtbFBhdGg9JHtuZXdIYW5kbGVyT3B0aW9ucy5odG1sUGF0aH1gXG4gICAgICApO1xuICAgIH1cblxuICAgIC8vIENvbXBvbmVudCByZXNvbHV0aW9uIGlzIGFscmVhZHkgbWVhc3VyZWQgaW4gcmVzb2x2ZUNvbXBvbmVudHNcbiAgICAvLyBObyBuZWVkIHRvIG1lYXN1cmUgbW9kdWxlIHJlc29sdXRpb24gdGltZSBoZXJlIGFueW1vcmVcblxuICAgIC8vIENyZWF0ZSBoZWFkbGVzcyBSU0Mgc3RyZWFtIGZpcnN0IChmb3IgLnJzYyBmaWxlKVxuICAgIGNvbnN0IHVuaXF1ZUlkID0gaGFuZGxlck9wdGlvbnMuaWQgPz8gYCR7aGFuZGxlck9wdGlvbnMucm91dGV9LSR7RGF0ZS5ub3coKX0tJHtNYXRoLnJhbmRvbSgpLnRvU3RyaW5nKDM2KS5zdWJzdHJpbmcoMiwgMTEpfWA7XG4gICAgXG4gICAgY29uc3QgaGVhZGxlc3NSc2NTdHJlYW1Mb2NhbCA9IGNyZWF0ZVJzY1N0cmVhbSh7XG4gICAgICAuLi5uZXdIYW5kbGVyT3B0aW9ucyxcbiAgICAgIGlkOiBgJHtoYW5kbGVyT3B0aW9ucy5yb3V0ZX0taGVhZGxlc3MtJHt1bmlxdWVJZH1gLFxuICAgICAgcnNjVGltZW91dDogaGFuZGxlck9wdGlvbnMucnNjVGltZW91dCB8fCA1MDAwLFxuICAgICAgb25NZXRyaWNzOiBoYW5kbGVyT3B0aW9ucy5vbk1ldHJpY3MsXG4gICAgICAvLyBIZWFkbGVzcyBSU0Mgc3RyZWFtOiBwYWdlIGNvbnRlbnQgb25seSAoZm9yIC5yc2MgZmlsZSlcbiAgICAgIGh0bWxQYXRoOiAnJywgLy8gTm8gSFRNTCB3cmFwcGVyIC0ganVzdCBwYWdlIGNvbnRlbnRcbiAgICAgIHBhZ2VQYXRoOiBuZXdIYW5kbGVyT3B0aW9ucy5wYWdlUGF0aCB8fCAnJywgLy8gRW5zdXJlIHBhZ2VQYXRoIGlzIGFsd2F5cyBhIHN0cmluZ1xuICAgICAgdXJsOiBuZXdIYW5kbGVyT3B0aW9ucy51cmwgfHwgJycsIC8vIEVuc3VyZSB1cmwgaXMgYWx3YXlzIGEgc3RyaW5nXG4gICAgICBwYWdlUHJvcHM6IG5ld0hhbmRsZXJPcHRpb25zLnBhZ2VQcm9wcyB8fCB7fSwgLy8gRW5zdXJlIHBhZ2VQcm9wcyBpcyBhbHdheXMgYW4gb2JqZWN0XG4gICAgICBvbkV2ZW50OiB3cmFwcGVkT25FdmVudCxcbiAgICB9KTtcblxuICAgIC8vIENyZWF0ZSBmdWxsIFJTQyBzdHJlYW0gdGhhdCByZXVzZXMgdGhlIGhlYWRsZXNzIHN0cmVhbSBlbGVtZW50c1xuICAgIGNvbnN0IGZ1bGxSc2NTdHJlYW1Mb2NhbCA9IGNyZWF0ZVJzY1N0cmVhbSh7XG4gICAgICAuLi5uZXdIYW5kbGVyT3B0aW9ucyxcbiAgICAgIGlkOiBgJHtoYW5kbGVyT3B0aW9ucy5yb3V0ZX0tZnVsbC0ke3VuaXF1ZUlkfWAsXG4gICAgICByc2NUaW1lb3V0OiBoYW5kbGVyT3B0aW9ucy5yc2NUaW1lb3V0IHx8IDUwMDAsXG4gICAgICBvbk1ldHJpY3M6IGhhbmRsZXJPcHRpb25zLm9uTWV0cmljcyxcbiAgICAgIC8vIEZ1bGwgUlNDIHN0cmVhbTogaW5jbHVkZSBIVE1MIHdyYXBwZXIgKGZvciBIVE1MIGdlbmVyYXRpb24pXG4gICAgICAvLyBQYXNzIHRocm91Z2ggdGhlIHJlc29sdmVkIGh0bWxQYXRoIHNvIGN1c3RvbSBIdG1sIGNvbXBvbmVudHMgd29yayBpbiBjbGllbnQgbW9kZVxuICAgICAgaHRtbFBhdGg6IHJlc29sdmVkSHRtbFBhdGgsXG4gICAgICBwYWdlUGF0aDogbmV3SGFuZGxlck9wdGlvbnMucGFnZVBhdGggfHwgJycsIC8vIEVuc3VyZSBwYWdlUGF0aCBpcyBhbHdheXMgYSBzdHJpbmdcbiAgICAgIHVybDogbmV3SGFuZGxlck9wdGlvbnMudXJsIHx8ICcnLCAvLyBFbnN1cmUgdXJsIGlzIGFsd2F5cyBhIHN0cmluZ1xuICAgICAgcGFnZVByb3BzOiBuZXdIYW5kbGVyT3B0aW9ucy5wYWdlUHJvcHMgfHwge30sIC8vIEVuc3VyZSBwYWdlUHJvcHMgaXMgYWx3YXlzIGFuIG9iamVjdFxuICAgICAgLy8gUmV1c2UgaGVhZGxlc3Mgc3RyZWFtIGVsZW1lbnRzIC0gdGhlIHdvcmtlciB3aWxsIGhhbmRsZSB0aGlzIHdpdGggdGhlIHVuaXF1ZSBJRFxuICAgICAgcmV1c2VIZWFkbGVzc1N0cmVhbUlkOiBoZWFkbGVzc1JzY1N0cmVhbUxvY2FsLmlkLFxuICAgICAgb25FdmVudDogd3JhcHBlZE9uRXZlbnQsXG4gICAgfSk7XG5cbiAgICAvLyBBc3NpZ24gdG8gdGhlIG91dGVyIHZhcmlhYmxlc1xuICAgIGhlYWRsZXNzUnNjU3RyZWFtID0gaGVhZGxlc3NSc2NTdHJlYW1Mb2NhbDtcbiAgICBmdWxsUnNjU3RyZWFtID0gZnVsbFJzY1N0cmVhbUxvY2FsO1xuXG4gICAgLy8gVGhlIGhlYWRsZXNzIHN0cmVhbSB3aWxsIGJlIGNvbnN1bWVkIG5hdHVyYWxseSBieSB0aGUgZmlsZSB3cml0aW5nXG4gICAgLy8gVGhlIGZ1bGwgc3RyZWFtIHdpbGwgcmV1c2UgdGhlIGhlYWRsZXNzIHN0cmVhbSBlbGVtZW50cyBmb3IgSFRNTCBnZW5lcmF0aW9uXG5cbiAgICAvLyBTdGVwIDM6IENyZWF0ZSBIVE1MIHRyYW5zZm9ybSBzdHJlYW1cbiAgICBpZiAoaGFuZGxlck9wdGlvbnMudmVyYm9zZSkge1xuICAgICAgaGFuZGxlck9wdGlvbnMubG9nZ2VyPy5pbmZvKFxuICAgICAgICBgW3JlbmRlclBhZ2UuY2xpZW50XSBDcmVhdGluZyBIVE1MIHRyYW5zZm9ybSBzdHJlYW0gd2l0aCBjbGllbnRQaXBlYWJsZVN0cmVhbU9wdGlvbnM6ICR7SlNPTi5zdHJpbmdpZnkobmV3SGFuZGxlck9wdGlvbnMuY2xpZW50UGlwZWFibGVTdHJlYW1PcHRpb25zKX1gXG4gICAgICApO1xuICAgIH1cbiAgICAvLyBDcmVhdGUgSFRNTCBzdHJlYW0gdXNpbmcgdGhlIGZ1bGwgUlNDIHN0cmVhbSAod2hpY2ggcmV1c2VzIGhlYWRsZXNzIHN0cmVhbSBlbGVtZW50cylcbiAgICBjb25zdCBodG1sVHJhbnNmb3JtU3RyZWFtID0gY3JlYXRlUnNjVG9IdG1sU3RyZWFtKHtcbiAgICAgIC4uLm5ld0hhbmRsZXJPcHRpb25zLFxuICAgICAgaHRtbFRpbWVvdXQ6IGhhbmRsZXJPcHRpb25zLmh0bWxUaW1lb3V0IHx8IDE1MDAwLFxuICAgICAgcm91dGU6IGhhbmRsZXJPcHRpb25zLnJvdXRlLFxuICAgICAgbG9nZ2VyOiBoYW5kbGVyT3B0aW9ucy5sb2dnZXIsXG4gICAgICB2ZXJib3NlOiBoYW5kbGVyT3B0aW9ucy52ZXJib3NlLFxuICAgICAgcnNjU3RyZWFtOiBmdWxsUnNjU3RyZWFtTG9jYWwucnNjU3RyZWFtLFxuICAgIH0pO1xuXG4gICAgaHRtbEhhbmRsZXIgPSB7XG4gICAgICBodG1sU3RyZWFtOiBodG1sVHJhbnNmb3JtU3RyZWFtLFxuICAgICAgYWJvcnQ6ICgpID0+IHtcbiAgICAgICAgaHRtbFRyYW5zZm9ybVN0cmVhbS5hYm9ydCgpO1xuICAgICAgfVxuICAgIH07XG5cbiAgICAvLyBDcmVhdGUgc3RyZWFtIHdyYXBwZXJzIGZvciBmaWxlIHdyaXRpbmdcbiAgICBjb25zdCByc2NTdHJlYW1XcmFwcGVyID0ge1xuICAgICAgcGlwZTogPFdyaXRhYmxlIGV4dGVuZHMgTm9kZUpTLldyaXRhYmxlU3RyZWFtPihkZXN0aW5hdGlvbjogV3JpdGFibGUpID0+IHtcbiAgICAgICAgY29uc3Qgc3RyZWFtTWV0cmljcyA9IGNyZWF0ZVN0cmVhbU1ldHJpY3MoKTtcbiAgICAgICAgc3RyZWFtTWV0cmljcy5zdGFydFRpbWUgPSBwZXJmb3JtYW5jZS5ub3coKTtcblxuICAgICAgICAvLyBVc2UgdGhlIGhlYWRsZXNzIFJTQyBzdHJlYW0gZGlyZWN0bHkgZm9yIHRoZSAucnNjIGZpbGVcbiAgICAgICAgY29uc3QgcnNjRmlsZVN0cmVhbSA9IGhlYWRsZXNzUnNjU3RyZWFtLnJzY1N0cmVhbTtcblxuICAgICAgICByc2NGaWxlU3RyZWFtLm9uKFwiZGF0YVwiLCAoY2h1bms6IEJ1ZmZlcikgPT4ge1xuICAgICAgICAgIHN0cmVhbU1ldHJpY3MuY2h1bmtzKys7XG4gICAgICAgICAgc3RyZWFtTWV0cmljcy5ieXRlcyArPSBjaHVuay5sZW5ndGg7XG4gICAgICAgIH0pO1xuXG4gICAgICAgIHJzY0ZpbGVTdHJlYW0ub24oXCJlbmRcIiwgKCkgPT4ge1xuICAgICAgICAgIHN0cmVhbU1ldHJpY3MuZHVyYXRpb24gPSBwZXJmb3JtYW5jZS5ub3coKSAtIHN0cmVhbU1ldHJpY3Muc3RhcnRUaW1lO1xuICAgICAgICAgIHN0cmVhbU1ldHJpY3MuZW5kVGltZSA9IHBlcmZvcm1hbmNlLm5vdygpO1xuXG4gICAgICAgICAgcnNjSGVhZGxlc3NNZXRyaWNzLnN0cmVhbU1ldHJpY3MgPSBzdHJlYW1NZXRyaWNzO1xuICAgICAgICAgIHJzY0hlYWRsZXNzTWV0cmljcy5jaHVua1JhdGUgPSBzdHJlYW1NZXRyaWNzLmNodW5rcyAvIChzdHJlYW1NZXRyaWNzLmR1cmF0aW9uIC8gMTAwMCk7XG4gICAgICAgICAgcnNjSGVhZGxlc3NNZXRyaWNzLnByb2Nlc3NpbmdUaW1lID0gc3RyZWFtTWV0cmljcy5kdXJhdGlvbjtcbiAgICAgICAgICByc2NIZWFkbGVzc01ldHJpY3MubWVtb3J5VXNhZ2UgPSBwcm9jZXNzLm1lbW9yeVVzYWdlKCk7XG4gICAgICAgICAgcnNjSGVhZGxlc3NNZXRyaWNzLmNodW5rcyA9IHN0cmVhbU1ldHJpY3MuY2h1bmtzO1xuICAgICAgICB9KTtcblxuICAgICAgICByc2NGaWxlU3RyZWFtLnBpcGUoZGVzdGluYXRpb24pO1xuICAgICAgICByZXR1cm4gZGVzdGluYXRpb247XG4gICAgICB9LFxuICAgICAgYWJvcnQ6ICgpID0+IGhlYWRsZXNzUnNjU3RyZWFtLmFib3J0KCksXG4gICAgfTtcblxuICAgIGNvbnN0IGh0bWxTdHJlYW1XcmFwcGVyID0ge1xuICAgICAgcGlwZTogPFdyaXRhYmxlIGV4dGVuZHMgTm9kZUpTLldyaXRhYmxlU3RyZWFtPihkZXN0aW5hdGlvbjogV3JpdGFibGUpID0+IHtcbiAgICAgICAgaWYgKGhhbmRsZXJPcHRpb25zLnZlcmJvc2UpIHtcbiAgICAgICAgICBoYW5kbGVyT3B0aW9ucy5sb2dnZXI/LmluZm8oXG4gICAgICAgICAgICBgW3JlbmRlclBhZ2UuY2xpZW50XSBQaXBpbmcgSFRNTCBzdHJlYW0gdG8gZGVzdGluYXRpb24gZm9yIHJvdXRlOiAke2hhbmRsZXJPcHRpb25zLnJvdXRlfWBcbiAgICAgICAgICApO1xuICAgICAgICB9XG4gICAgICAgIFxuICAgICAgICAvLyBVc2UgdGhlIEhUTUwgdHJhbnNmb3JtIHN0cmVhbSdzIHBpcGUgbWV0aG9kIGRpcmVjdGx5IChzYW1lIGFzIHNlcnZlciBzaWRlKVxuICAgICAgICByZXR1cm4gaHRtbFRyYW5zZm9ybVN0cmVhbS5waXBlKGRlc3RpbmF0aW9uKTtcbiAgICAgIH0sXG4gICAgICBhYm9ydDogKCkgPT4ge1xuICAgICAgICBmdWxsUnNjU3RyZWFtLmFib3J0KCk7XG4gICAgICAgIGlmIChodG1sSGFuZGxlci5hYm9ydCkge1xuICAgICAgICAgIGh0bWxIYW5kbGVyLmFib3J0KCk7XG4gICAgICAgIH1cbiAgICAgIH0sXG4gICAgICBvbjogKGV2ZW50OiBzdHJpbmcsIGxpc3RlbmVyOiAoLi4uYXJnczogYW55W10pID0+IHZvaWQpID0+IHtcbiAgICAgICAgLy8gRm9yd2FyZCBlcnJvciBldmVudHMgZnJvbSB0aGUgSFRNTCB0cmFuc2Zvcm0gc3RyZWFtIHRvIHRoZSB3cmFwcGVyXG4gICAgICAgIGlmIChldmVudCA9PT0gJ2Vycm9yJykge1xuICAgICAgICAgIC8vIEFjY2VzcyB0aGUgYWN0dWFsIHN0cmVhbSBmcm9tIHRoZSB0cmFuc2Zvcm0gcmVzdWx0XG4gICAgICAgICAgY29uc3QgaHRtbFN0cmVhbSA9IChodG1sVHJhbnNmb3JtU3RyZWFtIGFzIGFueSkuaHRtbFN0cmVhbTtcbiAgICAgICAgICBpZiAoaHRtbFN0cmVhbSAmJiB0eXBlb2YgaHRtbFN0cmVhbS5vbiA9PT0gJ2Z1bmN0aW9uJykge1xuICAgICAgICAgICAgaHRtbFN0cmVhbS5vbignZXJyb3InLCBsaXN0ZW5lcik7XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIHJldHVybiBodG1sU3RyZWFtV3JhcHBlcjtcbiAgICAgIH0sXG4gICAgfTtcblxuICAgIC8vIERvbid0IGVtaXQgaW5pdGlhbCBtZXRyaWNzIC0gd2FpdCBmb3IgZmlsZSB3cml0ZXMgdG8gY29tcGxldGVcbiAgICAvLyBUaGUgb25NZXRyaWNzIGNhbGxiYWNrIHdpbGwgYmUgY2FsbGVkIGFmdGVyIGJvdGggZmlsZS53cml0ZS5kb25lIGV2ZW50c1xuXG4gICAgLy8gQ2hlY2sgaWYgd2UgaGF2ZSBhbiBlcnJvciByZXN1bHQgdG8geWllbGQgKHdpdGggdGltZW91dCBwcm90ZWN0aW9uKVxuICAgIC8vIFdhaXQgYSBzaG9ydCB0aW1lIGZvciBhbnkgcGVuZGluZyByb3V0ZS5lcnJvciBldmVudHNcbiAgICBhd2FpdCBuZXcgUHJvbWlzZShyZXNvbHZlID0+IHNldFRpbWVvdXQocmVzb2x2ZSwgMTAwKSk7XG4gICAgXG4gICAgaWYgKGVycm9yUmVzdWx0KSB7XG4gICAgICB5aWVsZCBlcnJvclJlc3VsdDtcbiAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICB5aWVsZCB7XG4gICAgICB0eXBlOiBcInN1Y2Nlc3NcIixcbiAgICAgIGh0bWw6IGh0bWxTdHJlYW1XcmFwcGVyLFxuICAgICAgcnNjOiByc2NTdHJlYW1XcmFwcGVyLFxuICAgICAgbWV0cmljczoge1xuICAgICAgICByc2NGdWxsOiByc2NGdWxsTWV0cmljcyxcbiAgICAgICAgcnNjSGVhZGxlc3M6IHJzY0hlYWRsZXNzTWV0cmljcyxcbiAgICAgICAgaHRtbDogaHRtbE1ldHJpY3MsXG4gICAgICB9LFxuICAgIH0gYXMgY29uc3Q7XG4gIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgLy8gQ2xlYW4gdXAgcmVzb3VyY2VzXG4gICAgdHJ5IHtcbiAgICAgIGlmIChoZWFkbGVzc1JzY1N0cmVhbSkgaGVhZGxlc3NSc2NTdHJlYW0uYWJvcnQoKTtcbiAgICAgIGlmIChmdWxsUnNjU3RyZWFtKSBmdWxsUnNjU3RyZWFtLmFib3J0KCk7XG4gICAgICBpZiAoaHRtbEhhbmRsZXI/LmFib3J0KSBodG1sSGFuZGxlci5hYm9ydCgpO1xuICAgIH0gY2F0Y2ggKGNsZWFudXBFcnJvcjogdW5rbm93bikge1xuICAgICAgaGFuZGxlck9wdGlvbnMubG9nZ2VyPy53YXJuKGBGYWlsZWQgdG8gY2xlYW51cCBzdHJlYW1zIG9uIGVycm9yOiAke2NsZWFudXBFcnJvcn1gKTtcbiAgICB9XG5cbiAgICBjb25zdCBwYW5pY0Vycm9yID0gaGFuZGxlRXJyb3Ioe1xuICAgICAgZXJyb3IsXG4gICAgICBsb2dnZXI6IGhhbmRsZXJPcHRpb25zLmxvZ2dlcixcbiAgICAgIGNvbnRleHQ6IFwicmVuZGVyUGFnZUNsaWVudFwiLFxuICAgICAgcGFuaWNUaHJlc2hvbGQ6IGhhbmRsZXJPcHRpb25zLnBhbmljVGhyZXNob2xkLFxuICAgIH0pO1xuXG4gICAgaWYgKHBhbmljRXJyb3IgIT0gbnVsbCkge1xuICAgICAgeWllbGQge1xuICAgICAgICB0eXBlOiBcImVycm9yXCIsXG4gICAgICAgIGVycm9yOiBwYW5pY0Vycm9yLFxuICAgICAgICBtZXRyaWNzOiB7XG4gICAgICAgICAgcnNjRnVsbDogcnNjRnVsbE1ldHJpY3MsXG4gICAgICAgICAgcnNjSGVhZGxlc3M6IHJzY0hlYWRsZXNzTWV0cmljcyxcbiAgICAgICAgICBodG1sOiBodG1sTWV0cmljcyxcbiAgICAgICAgfSxcbiAgICAgIH07XG4gICAgfSBlbHNlIHtcbiAgICAgIC8vIEZvciBub24tcGFuaWMgZXJyb3JzLCB3ZSBzdGlsbCB3YW50IHRvIHdyaXRlIHRoZSBIVE1MIGZpbGUgKGNsaWVudC1vbmx5KVxuICAgICAgLy8gYnV0IHNraXAgdGhlIFJTQyBmaWxlIHNpbmNlIHRoZXJlIHdhcyBhIHNlcnZlciBlcnJvclxuICAgICAgXG4gICAgICAvLyBDcmVhdGUgYSBmYWxsYmFjayBSU0Mgc3RyZWFtIHdpdGggUmVhY3QuRnJhZ21lbnQgKHNhbWUgYXMgc2VydmVyIGVudmlyb25tZW50KVxuICAgICAgY29uc3QgZmFsbGJhY2tSc2NTdHJlYW0gPSBjcmVhdGVSc2NTdHJlYW0oe1xuICAgICAgICAuLi5oYW5kbGVyT3B0aW9ucyxcbiAgICAgICAgdXJsOiBgJHtoYW5kbGVyT3B0aW9ucy51cmx9YCxcbiAgICAgICAgcm91dGU6IGAke2hhbmRsZXJPcHRpb25zLnJvdXRlfWAsXG4gICAgICAgIGNzc0ZpbGVzOiBoYW5kbGVyT3B0aW9ucy5jc3NGaWxlcyB8fCBuZXcgTWFwKCksXG4gICAgICAgIGdsb2JhbENzczogaGFuZGxlck9wdGlvbnMuZ2xvYmFsQ3NzIHx8IG5ldyBNYXAoKSxcbiAgICAgICAgaWQ6IGAke2hhbmRsZXJPcHRpb25zLnJvdXRlfS1mYWxsYmFjay0ke0RhdGUubm93KCl9YCxcbiAgICAgICAgcnNjVGltZW91dDogaGFuZGxlck9wdGlvbnMucnNjVGltZW91dCB8fCA1MDAwLFxuICAgICAgICBvbk1ldHJpY3M6IGhhbmRsZXJPcHRpb25zLm9uTWV0cmljcyxcbiAgICAgICAgLy8gVXNlIFJlYWN0LkZyYWdtZW50IGFzIGZhbGxiYWNrIChzYW1lIGFzIHNlcnZlciBlbnZpcm9ubWVudClcbiAgICAgICAgcGFnZVBhdGg6ICcnLCAvLyBUaGlzIHdpbGwgY2F1c2UgdGhlIGRlZmF1bHQgcGFnZSB0byBiZSB1c2VkLCBidXQgd2UnbGwgb3ZlcnJpZGUgaXRcbiAgICAgICAgcGFnZVByb3BzOiB7fSwgLy8gRW5zdXJlIHBhZ2VQcm9wcyBpcyBhbHdheXMgYW4gb2JqZWN0XG4gICAgICB9KTtcbiAgICAgIFxuICAgICAgLy8gQ3JlYXRlIEhUTUwgc3RyZWFtIHRoYXQgcHJvY2Vzc2VzIHRoZSBmYWxsYmFjayBSU0Mgc3RyZWFtIHRvIGVuc3VyZSBwZXJmb3JtYW5jZSB0aW1pbmcgc2NyaXB0IGlzIGluamVjdGVkXG4gICAgICBjb25zdCBmYWxsYmFja0h0bWxTdHJlYW0gPSBjcmVhdGVSc2NUb0h0bWxTdHJlYW0oe1xuICAgICAgICBpZDogaGFuZGxlck9wdGlvbnMuaWQsXG4gICAgICAgIHJvdXRlOiBoYW5kbGVyT3B0aW9ucy5yb3V0ZSxcbiAgICAgICAgdXJsOiBoYW5kbGVyT3B0aW9ucy51cmwsXG4gICAgICAgIG1vZHVsZVJvb3RQYXRoOiBoYW5kbGVyT3B0aW9ucy5tb2R1bGVSb290UGF0aCxcbiAgICAgICAgbW9kdWxlQmFzZVBhdGg6IGhhbmRsZXJPcHRpb25zLm1vZHVsZUJhc2VQYXRoLFxuICAgICAgICBtb2R1bGVCYXNlVVJMOiBoYW5kbGVyT3B0aW9ucy5tb2R1bGVCYXNlVVJMLFxuICAgICAgICBwcm9qZWN0Um9vdDogaGFuZGxlck9wdGlvbnMucHJvamVjdFJvb3QsXG4gICAgICAgIHBhbmljVGhyZXNob2xkOiBoYW5kbGVyT3B0aW9ucy5wYW5pY1RocmVzaG9sZCxcbiAgICAgICAgdmVyYm9zZTogaGFuZGxlck9wdGlvbnMudmVyYm9zZSxcbiAgICAgICAgc2lnbmFsOiBoYW5kbGVyT3B0aW9ucy5zaWduYWwsXG4gICAgICAgIGxvZ2dlcjogaGFuZGxlck9wdGlvbnMubG9nZ2VyLFxuICAgICAgICBodG1sVGltZW91dDogaGFuZGxlck9wdGlvbnMuaHRtbFRpbWVvdXQsXG4gICAgICAgIGNsaWVudFBpcGVhYmxlU3RyZWFtT3B0aW9uczogaGFuZGxlck9wdGlvbnMuY2xpZW50UGlwZWFibGVTdHJlYW1PcHRpb25zLFxuICAgICAgICBvbk1ldHJpY3M6IGhhbmRsZXJPcHRpb25zLm9uTWV0cmljcyxcbiAgICAgICAgYnVpbGQ6IGhhbmRsZXJPcHRpb25zLmJ1aWxkLFxuICAgICAgfSk7XG4gICAgICBcbiAgICAgIC8vIENyZWF0ZSBhIHdyYXBwZXIgdGhhdCBwaXBlcyB0aGUgZmFsbGJhY2sgUlNDIHN0cmVhbSB0aHJvdWdoIHRoZSBIVE1MIHRyYW5zZm9ybVxuICAgICAgY29uc3QgY2xpZW50T25seUh0bWxTdHJlYW1XcmFwcGVyID0ge1xuICAgICAgICBwaXBlOiA8V3JpdGFibGUgZXh0ZW5kcyBOb2RlSlMuV3JpdGFibGVTdHJlYW0+KGRlc3RpbmF0aW9uOiBXcml0YWJsZSkgPT4ge1xuICAgICAgICAgIC8vIFBpcGUgdGhlIGZhbGxiYWNrIFJTQyBzdHJlYW0gdGhyb3VnaCB0aGUgSFRNTCB0cmFuc2Zvcm0gdG8gZW5zdXJlIHBlcmZvcm1hbmNlIHRpbWluZyBzY3JpcHQgaXMgaW5qZWN0ZWRcbiAgICAgICAgICByZXR1cm4gZmFsbGJhY2tIdG1sU3RyZWFtLnBpcGUoZGVzdGluYXRpb24pO1xuICAgICAgIC