UNPKG

vite-plugin-react-server

Version:
481 lines (479 loc) 69.3 kB
/** * 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