UNPKG

vite-plugin-react-server

Version:
715 lines (713 loc) 106 kB
/** * vite-plugin-react-server * Copyright (c) Nico Brinkkemper * MIT License */ import { createLogger } from 'vite'; import { resolveOptions } from '../config/resolveOptions.js'; import { renderPagesBatched } from './renderPagesBatched.js'; import { performance } from 'node:perf_hooks'; import { renderPage } from './renderPage.client.js'; import { createWorker } from '../worker/createWorker.js'; import { serializeResolvedConfig, serializedOptions } from '../helpers/serializeUserOptions.js'; import { getBundleManifest } from '../helpers/getBundleManifest.js'; import { handleError } from '../error/handleError.js'; import { shouldCausePanic } from '../error/panicThresholdHandler.js'; import { configurePreviewServer } from './configurePreviewServer.js'; import { assertNonReactServer } from '../config/getCondition.js'; import { envPrefixFromConfig } from '../config/envPrefixFromConfig.js'; import { createWorkerStartupMetrics } from '../metrics/createWorkerStartupMetrics.js'; import { processCssFilesForPages } from './processCssFilesForPages.js'; import { createBuildLoader } from './createBuildLoader.client.js'; import { getNodeEnv } from '../config/getNodeEnv.js'; import { toError } from '../error/toError.js'; import { addStaticManifest, manifests, getSharedManifestStore } from '../bundle/manifests.js'; import { deferStaticGeneration } from '../bundle/deferredStaticGeneration.js'; import { resolveAutoDiscover } from '../config/autoDiscover/resolveAutoDiscover.js'; import { join } from 'node:path'; import { baseURL } from '../utils/envUrls.node.js'; import { tryManifest } from '../helpers/tryManifest.js'; assertNonReactServer(); const reactStaticPlugin = function _reactStaticPlugin(options) { let logger; let autoDiscoveredFiles = null; let rscWorker = void 0; let resolvedConfig = null; let serverManifest = void 0; let staticBundle = void 0; let serverBundle = void 0; let configEnv; const timing = { start: performance.now(), configResolved: 0, buildStart: 0, renderStart: 0 }; const resolvedOptions = resolveOptions(options); if (resolvedOptions.type === "error") { throw resolvedOptions.error; } const userOptions = resolvedOptions.userOptions; return { name: "vite:plugin-react-server/client-static", enforce: "post", apply: "build", // Apply to build mode api: { meta: { timing } }, async config(_config, viteConfigEnv) { configEnv = viteConfigEnv; }, applyToEnvironment(partialEnvironment) { const envName = partialEnvironment.name; if (["static", "client"].includes(envName)) { return true; } return false; }, async configResolved(config) { timing.configResolved = performance.now(); logger = config.customLogger || createLogger(); resolvedConfig = config; const autoDiscoverResult = await resolveAutoDiscover({ config, configEnv: configEnv || { mode: config.mode, command: config.command}, userOptions, logger }); if (autoDiscoverResult.type === "error") { throw autoDiscoverResult.error; } autoDiscoveredFiles = autoDiscoverResult.autoDiscoveredFiles; if (userOptions.verbose) { logger?.info(`Auto-discovery ${autoDiscoverResult.type === "success" ? "completed" : "skipped"}`); } }, async buildStart() { timing.buildStart = performance.now(); if (userOptions.verbose) { logger?.info("[react-static-client] Build started"); } if (userOptions.onEvent && autoDiscoveredFiles) { try { userOptions.onEvent({ type: "build.start", data: { pages: Array.from(autoDiscoveredFiles.urlMap.keys()), files: autoDiscoveredFiles } }); } catch (error) { const panicError = handleError({ error, logger, panicThreshold: userOptions.panicThreshold}); if (panicError != null) { rscWorker?.terminate(); throw panicError; } } } }, async renderStart() { timing.renderStart = performance.now(); if (userOptions.verbose) { logger?.info("[react-static-client] Render started"); } }, // the preview server helps to view the generated static folder, but only when the static plugin is enabled // if no build.pages, then the preview server will instead use default vite preview server // it works the same under both conditions async configurePreviewServer(server) { logger = server.config.customLogger || server.config.logger; configurePreviewServer({ server, userOptions }); }, async writeBundle(_options, bundle) { try { if (!autoDiscoveredFiles?.urlMap) { return; } const bundleManifest = getBundleManifest({ bundle, normalizer: userOptions.normalizer }); if (this.environment.name === "static") { addStaticManifest(bundleManifest); staticBundle = bundle; } else if (this.environment.name === "client") { if (manifests.static) { const staticManifest = manifests.static; for (const [, chunk] of Object.entries(bundle)) { if (chunk.type === "chunk" && chunk.fileName) { const normalized = userOptions.normalizer(chunk.fileName); let value = normalized[1]; if (value.startsWith(userOptions.moduleBasePath)) { value = value.slice(userOptions.moduleBasePath.length); } const entry = staticManifest[value]; if (entry && entry.file !== chunk.fileName) { chunk.fileName = entry.file; } } } } } else if (this.environment.name === "server") { serverBundle = bundle; } return; } catch (error) { const panicError = handleError({ error, logger, panicThreshold: userOptions.panicThreshold}); if (panicError != null) { throw panicError; } } }, async closeBundle() { const envName = this.environment.name; const isSsr = this.environment.config.build?.ssr === true; if (userOptions.verbose) { logger?.info(`[react-static-client] closeBundle called for environment: ${envName}, ssr: ${isSsr}`); } if (envName === "ssr" || envName === "server" || isSsr) { if (userOptions.verbose) { logger?.info(`[react-static-client] Skipping static generation for environment: ${envName} (ssr: ${isSsr})`); } return; } if (envName === "static" || envName === "client" && !isSsr) { try { const { rmSync, existsSync } = await import('node:fs'); const { join: join2, resolve } = await import('node:path'); const resolvedOutDir = this.environment.config.build?.outDir ? resolve(this.environment.config.root || userOptions.projectRoot, this.environment.config.build.outDir) : resolve(userOptions.projectRoot, userOptions.build.outDir); const outputDirs = [ join2(resolvedOutDir, userOptions.build.static || "static"), join2(resolvedOutDir, userOptions.build.client || "client") ]; for (const outDir of outputDirs) { const virtualDir = join2(outDir, "_virtual"); if (existsSync(virtualDir)) { rmSync(virtualDir, { recursive: true, force: true }); if (userOptions.verbose) { logger?.info(`[react-static-client] Cleaned up _virtual directory: ${virtualDir}`); } } } } catch (error) { if (userOptions.verbose) { logger?.warn(`[react-static-client] Failed to clean up _virtual directory: ${error}`); } } } if (envName === "ssr" || envName === "server" || isSsr) { if (userOptions.verbose) { logger?.info(`[react-static-client] Skipping static generation - not in static environment (${envName}, ssr: ${isSsr})`); } return; } const closeBundleContext = this; deferStaticGeneration(async () => { try { if (!autoDiscoveredFiles) { if (userOptions.verbose) { logger?.warn("[react-static-client] autoDiscoveredFiles not set, attempting to re-discover"); } const { getStashedUserOptions, getEnvironmentId } = await import('../config/stashedOptionsState.js'); const { getCondition } = await import('../config/getCondition.js'); const envId = getEnvironmentId(getCondition(), resolvedConfig?.mode || "production"); const stashedOptions = getStashedUserOptions(envId); if (stashedOptions && resolvedConfig) { const autoDiscoverResult = await resolveAutoDiscover({ config: resolvedConfig, configEnv: configEnv || { mode: resolvedConfig.mode || "production", command: resolvedConfig.command || "build", isSsrBuild: false, isPreview: false }, userOptions, logger }); if (autoDiscoverResult.type === "success") { autoDiscoveredFiles = autoDiscoverResult.autoDiscoveredFiles; if (userOptions.verbose) { logger?.info(`[react-static-client] Re-discovered ${autoDiscoveredFiles.urlMap.size} pages`); } } else { if (userOptions.verbose) { logger?.warn(`[react-static-client] Failed to re-discover pages: ${autoDiscoverResult.error}`); } } } } if (!autoDiscoveredFiles?.urlMap || autoDiscoveredFiles?.urlMap.size === 0) { if (userOptions.verbose) { logger?.warn(`[react-static-client] No pages to generate - urlMap is empty (size: ${autoDiscoveredFiles?.urlMap?.size || 0})`); logger?.warn(`[react-static-client] autoDiscoveredFiles exists: ${!!autoDiscoveredFiles}, urlMap exists: ${!!autoDiscoveredFiles?.urlMap}`); } return; } if (userOptions.verbose) { logger?.info(`[react-static-client] Starting static generation with ${autoDiscoveredFiles.urlMap.size} pages`); } try { if (userOptions.verbose) { logger?.info(`[react-static-client] Attempting to get server manifest from shared state`); } const sharedState = getSharedManifestStore(closeBundleContext); if (sharedState.server) { serverManifest = sharedState.server; if (userOptions.verbose) { logger?.info(`[react-static-client] Got server manifest from shared state`); } } else { throw new Error("No server manifest in shared state"); } } catch (error) { if (userOptions.verbose) { logger?.info(`[react-static-client] Failed to get server manifest from shared state, trying filesystem: ${error}`); } const serverManifestPath = join( userOptions.build.outDir, userOptions.build.server ); const manifestPath = typeof resolvedConfig?.build.manifest === "string" ? resolvedConfig.build.manifest : ".vite/manifest.json"; if (userOptions.verbose) { logger?.info(`[react-static-client] Loading server manifest from: ${join(serverManifestPath, manifestPath)}`); } const serverManifestResult = await tryManifest({ root: userOptions.projectRoot, outDir: serverManifestPath, manifestPath, ssrManifest: false }); if (serverManifestResult.type === "error") { if (userOptions.verbose) { logger?.warn(`[react-static-client] Failed to load server manifest: ${serverManifestResult.error}`); } serverManifest = {}; if (userOptions.verbose) { logger?.warn(`[react-static-client] Using empty server manifest as fallback`); } } else if (serverManifestResult.type === "skip") { if (userOptions.verbose) { logger?.warn(`[react-static-client] Server manifest not found, using empty manifest as fallback`); } serverManifest = {}; } else { serverManifest = serverManifestResult.manifest; if (userOptions.verbose) { logger?.info(`[react-static-client] Loaded server manifest from filesystem`); } } } const staticManifestResult = await tryManifest({ root: userOptions.projectRoot, outDir: join(userOptions.build.outDir, userOptions.build.static), manifestPath: resolvedConfig?.build.manifest ?? ".vite/manifest.json", ssrManifest: false }); if (staticManifestResult.type === "error") { throw staticManifestResult.error; } const staticManifest = staticManifestResult.manifest; const indexHtml = staticManifest?.["index.html"]?.file; const serverPipeableStreamOptions = { ...userOptions.serverPipeableStreamOptions, bootstrapModules: [ ...indexHtml ? [baseURL(indexHtml)] : [], ...userOptions.serverPipeableStreamOptions?.bootstrapModules ?? [] ] }; userOptions.serverPipeableStreamOptions = serverPipeableStreamOptions; const clientPipeableStreamOptions = { ...userOptions.clientPipeableStreamOptions, bootstrapModules: [ ...indexHtml ? [baseURL(indexHtml)] : [], ...userOptions.clientPipeableStreamOptions?.bootstrapModules ?? [] ] }; const { cssFilesByPage, globalCss } = processCssFilesForPages({ userOptions, autoDiscoveredFiles, serverManifest, staticManifest, bundle: staticBundle || {}, logger }); if (userOptions.verbose) { for (const [route, cssMap] of cssFilesByPage.entries()) { logger.info( `[react-static-client] Route ${route}: ${cssMap.size} CSS files` ); for (const [key, value] of cssMap.entries()) { logger.info( `[react-static-client] CSS file: ${key} -> ${value.as} (${value.children ? "inline" : "link"})` ); } } } const routes = Array.from( autoDiscoveredFiles.urlMap.keys() ); if (routes.length === 0) { if (userOptions.verbose) { logger?.info( "[react-static-client] No pages to generate, skipping static generation" ); } return; } if (userOptions.verbose) { logger?.info(`[react-static-client] Creating build loader`); } const buildLoader = createBuildLoader(); if (userOptions.verbose) { logger?.info(`[react-static-client] Build loader created`); } if (userOptions.verbose) { logger?.info( `[react-static-client] Creating RSC worker with path: ${userOptions.rscWorkerPath}` ); } const workerStartTime = performance.now(); let rscWorkerResult; try { rscWorkerResult = await createWorker({ projectRoot: userOptions.projectRoot, workerPath: userOptions.rscWorkerPath, currentCondition: "react-client", reverseCondition: "react-server", maxListeners: Math.max(routes.length * 3, 10), // Account for multiple listeners per route envPrefix: envPrefixFromConfig(resolvedConfig), logger, verbose: userOptions.verbose, mode: getNodeEnv(), workerData: { userOptions: serializedOptions(userOptions, autoDiscoveredFiles), resolvedConfig: serializeResolvedConfig(resolvedConfig), configEnv: (() => { const fallback = resolvedConfig ? { command: resolvedConfig.command, mode: resolvedConfig.mode, isSsrBuild: false, isPreview: false } : void 0; const finalConfigEnv = configEnv || fallback; return finalConfigEnv; })(), serverManifest: serverManifest || {}, // Use server manifest for page component resolution bundle: staticBundle || {}, // Use static bundle (client build) for page component resolution staticBundle: staticBundle || {}, // Pass static bundle separately for path resolution id: "static-client-rsc-worker" } }); } catch (workerError) { if (userOptions.verbose) { logger?.error(`[react-static-client] Error creating RSC worker: ${workerError}`); } throw workerError; } if (rscWorkerResult.type !== "success") { const err = rscWorkerResult.error ?? new Error(`Failed to create RSC worker`); if (userOptions.verbose) { logger?.error( `[react-static-client] RSC worker creation failed, throwing error`, { error: err } ); } throw err; } rscWorker = rscWorkerResult.worker; if (userOptions.verbose) { logger?.info(`[react-static-client] RSC worker created successfully`); } const workerStartupTime = performance.now() - workerStartTime; if (userOptions.onMetrics) { const workerStartupMetric = createWorkerStartupMetrics({ route: "/", // Worker startup is global, not route-specific workerType: "rsc", // This is the RSC worker for client-side static generation startupTime: workerStartupTime, fromMainThread: true, fromRscWorker: false, fromHtmlWorker: false, description: `RSC worker startup for client-side static generation` }); userOptions.onMetrics(workerStartupMetric); } const { onEvent, onMetrics, ...handlerOptions } = userOptions; if (userOptions.verbose) { logger?.info(`[react-static-client] Extracted onEvent: ${typeof onEvent}, userOptions.onEvent: ${typeof userOptions.onEvent}`); } if (!serverBundle && onEvent) { const originalOnEvent = onEvent; const tempOnEvent = (event) => { if (event.type === "build.writeBundle.server") { serverBundle = event.data.bundle; logger?.info( "[react-static-client] Captured server bundle from build event" ); } originalOnEvent(event); }; userOptions.onEvent = tempOnEvent; } const eventHandler = onEvent || userOptions.onEvent; if (typeof eventHandler === "function") { try { if (userOptions.verbose) { logger?.info(`[react-static-client] Emitting build.ssg.start event`); } const r = eventHandler({ type: "build.ssg.start", data: { pages: Array.from(autoDiscoveredFiles?.urlMap.keys() ?? []), options: null, // No specific rollup output options for static generation bundle: staticBundle || {} } }); if (r != null && typeof r === "object" && "then" in r) { await r; } } catch (error) { const eventPanicError = handleError({ error, logger, panicThreshold: userOptions.panicThreshold, context: "onEvent(build.ssg.start)" }); if (eventPanicError != null) { throw eventPanicError; } } } else if (userOptions.verbose) { logger?.warn(`[react-static-client] No onEvent handler available to emit build.ssg.start`); } const renderPagesGenerator = renderPagesBatched( routes, { ...handlerOptions, // Use the clean options instead of the original handlerOptions worker: rscWorker, // Pass the RSC worker for RSC rendering only rscWorker, // Pass the RSC worker for RSC rendering only loader: buildLoader, // Use proper build loader instead of no-op logger, autoDiscoveredFiles, cssFilesByPage, // Pass CSS files by page serverPipeableStreamOptions, // Pass server options to RSC worker clientPipeableStreamOptions, // Pass client options to RSC worker globalCss, // Pass global CSS manifest: serverManifest || {}, // Server manifest for RSC worker staticManifest, // Static manifest for consistent module IDs onEvent, onMetrics // Pass through the onMetrics callback (metric watcher) }, renderPage ); let finalResult = void 0; try { for await (const result of renderPagesGenerator) { if (result.type === "error") { if (userOptions.verbose) { logger?.error(`[react-static-client] Render error: ${result.error}`); } throw result.error; } if (result.type === "success" && result.failedRoutes && result.failedRoutes.size > 0) { const firstError = result.failedRoutes.values().next().value; if (firstError != null && shouldCausePanic(firstError, { panicThreshold: userOptions.panicThreshold })) { throw firstError; } for (const [route, error] of result.failedRoutes) { const err = error instanceof Error ? error : toError(error); closeBundleContext.warn( new Error( "Failed to render route: " + route + "\n" + err.message + "\n" + err.stack, { cause: err } ) ); } } finalResult = result; } } catch (renderError) { if (userOptions.verbose) { logger?.error(`[react-static-client] Error during renderPages: ${renderError}`); } throw renderError; } if (!finalResult) { const errorMsg = "No render result produced"; if (userOptions.verbose) { logger?.error(`[react-static-client] ${errorMsg}`); } throw new Error(errorMsg); } if (userOptions.verbose) { logger?.info(`[react-static-client] Render completed: ${finalResult.completedRoutes.size} pages, ${finalResult.failedRoutes?.size || 0} failed`); } const duration = Math.round( performance.now() - (timing.renderStart || timing.start) ); closeBundleContext.info( `Rendered ${finalResult.completedRoutes.size} pages in ${duration}ms` ); if (process.env["NODE_ENV"] !== "production") { closeBundleContext.warn( `THIS BUILD IS NOT INTENDED FOR PRODUCTION (${process.env["NODE_ENV"]})` ); } timing.render = performance.now() - (timing.renderStart ?? timing.start); if (userOptions.verbose) { logger?.info("[react-static-client] Static generation completed"); } if (typeof userOptions.onEvent === "function") { try { const r = userOptions.onEvent({ type: "build.ssg.end", data: { pages: Array.from(autoDiscoveredFiles?.urlMap.keys() ?? []), options: null, // No specific rollup output options for static generation bundle: staticBundle || {} } }); if (r != null && typeof r === "object" && "then" in r) { await r; } } catch (error) { const eventPanicError = handleError({ error, logger, panicThreshold: userOptions.panicThreshold, context: "onEvent(build.ssg.end)" }); if (eventPanicError != null) { throw eventPanicError; } } } } catch (error) { const panicError = handleError({ error, logger, panicThreshold: userOptions.panicThreshold }); if (rscWorker) { const workerToCleanup = rscWorker; try { await Promise.race([ new Promise((resolve) => { const timeoutId = setTimeout(() => { workerToCleanup.removeAllListeners(); workerToCleanup.terminate(); resolve(); }, 1e3); const messageHandler = (message) => { if (message.type === "SHUTDOWN_COMPLETE") { clearTimeout(timeoutId); workerToCleanup.removeListener("message", messageHandler); resolve(); } }; workerToCleanup.on("message", messageHandler); workerToCleanup.postMessage({ type: "SHUTDOWN" }); }) ]); rscWorker = void 0; } catch (cleanupError) { logger.warn(`Failed to cleanup worker on error: ${cleanupError}`); try { workerToCleanup.removeAllListeners(); workerToCleanup.terminate(); } catch (terminateError) { } rscWorker = void 0; } } if (panicError != null) { const errorToThrow = panicError instanceof Error ? panicError : new Error(String(panicError)); const finalError = new Error(errorToThrow.message); finalError.stack = errorToThrow.stack; finalError.cause = errorToThrow.cause; if (errorToThrow.name) finalError.name = errorToThrow.name; throw finalError; } } finally { if (rscWorker) { try { await Promise.race([ new Promise((resolve, reject) => { const timeout = setTimeout(() => { reject(new Error("Worker shutdown timeout")); }, userOptions.workerShutdownTimeout); const backupTimeout = setTimeout(() => { reject(new Error("Worker shutdown backup timeout")); }, Math.floor(userOptions.workerShutdownTimeout * 0.6)); const shutdownMessageHandler = (message) => { if (message.type === "SHUTDOWN_COMPLETE") { clearTimeout(timeout); clearTimeout(backupTimeout); rscWorker?.removeListener( "message", shutdownMessageHandler ); rscWorker?.removeAllListeners(); resolve(); } }; rscWorker?.on("message", shutdownMessageHandler); rscWorker?.postMessage({ type: "SHUTDOWN", id: "*" }); }) ]); } catch { } finally { if (rscWorker) { try { rscWorker.removeAllListeners(); await rscWorker.terminate(); } catch { } rscWorker = void 0; } } } autoDiscoveredFiles = null; serverManifest = void 0; } }); } }; }; export { reactStaticPlugin }; //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGx1Z2luLmNsaWVudC5qcyIsInNvdXJjZXMiOlsiLi4vLi4vLi4vcGx1Z2luL3JlYWN0LXN0YXRpYy9wbHVnaW4uY2xpZW50LnRzIl0sInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogcGx1Z2luLmNsaWVudC50c1xuICpcbiAqIFBVUlBPU0U6IENsaWVudC1zaWRlIHN0YXRpYyBwbHVnaW4gZm9yIFJlYWN0IFNlcnZlciBDb21wb25lbnRzXG4gKlxuICogVGhpcyBtb2R1bGU6XG4gKiAxLiBIYW5kbGVzIHN0YXRpYyBzaXRlIGdlbmVyYXRpb24gaW4gdGhlIGNsaWVudCBlbnZpcm9ubWVudFxuICogMi4gVXNlcyBSU0Mgd29ya2VyIGZvciBSU0MgcmVuZGVyaW5nIGFuZCBtYWluLXRocmVhZCBmb3IgSFRNTCByZW5kZXJpbmdcbiAqIDMuIEdlbmVyYXRlcyBib3RoIFJTQyBhbmQgSFRNTCBmaWxlcyBmb3Igc3RhdGljIHBhZ2VzXG4gKiA0LiBJbnRlZ3JhdGVzIHdpdGggVml0ZSdzIGJ1aWxkIHByb2Nlc3NcbiAqXG4gKiBGZWF0dXJlIHBhcml0eSB3aXRoIG1haW4gcmVhY3Qtc3RhdGljIHBsdWdpbiwgYnV0IGluIHJldmVyc2UuIFVzZXMgcnNjLXdvcmtlciB0byByZW5kZXIgcnNjLCBhbmQgbWFpbiB0aHJlYWQgZm9yIGh0bWwuXG4gKiBUaGlzIGlzIG5vdCB0aGUgZGVmYXVsdCBiZWhhdmlvciwgYnV0IGlzIHN1cHBvcnRlZCBmb3IgdGVzdGluZyBhbmQgY3VzdG9tIGFwcCBkZXZlbG9wbWVudCBwdXJwb3Nlcy5cbiAqIEFkZGl0aW9uYWxseSwgdGhpcyBjYW4gbWFrZSBpdCBlYXNpZXIgdG8gdXNlIHRoZSAtLWFwcCBmbGFnIHRvIGJ1aWxkIGFsbCB0aGUgbW9kdWxlcyArIHN0YXRpYyBnZW5lcmF0aW9uIGF0IG9uY2UuXG4gKi9cblxuaW1wb3J0IHtcbiAgY3JlYXRlTG9nZ2VyLFxuICB0eXBlIFJlc29sdmVkQ29uZmlnLFxuICB0eXBlIE1hbmlmZXN0LFxuICB0eXBlIENvbmZpZ0Vudixcbn0gZnJvbSBcInZpdGVcIjtcbmltcG9ydCB7IHJlc29sdmVPcHRpb25zIH0gZnJvbSBcIi4uL2NvbmZpZy9yZXNvbHZlT3B0aW9ucy5qc1wiO1xuaW1wb3J0IHR5cGUge1xuICBCdWlsZFRpbWluZyxcbiAgVml0ZVBsdWdpbkZuLFxuICBBdXRvRGlzY292ZXJlZEZpbGVzLFxufSBmcm9tIFwiLi4vdHlwZXMuanNcIjtcbmltcG9ydCB0eXBlIHsgT3V0cHV0QnVuZGxlIH0gZnJvbSBcInJvbGx1cFwiO1xuaW1wb3J0IHsgcmVuZGVyUGFnZXNCYXRjaGVkIH0gZnJvbSBcIi4vcmVuZGVyUGFnZXNCYXRjaGVkLmpzXCI7XG5pbXBvcnQgeyBwZXJmb3JtYW5jZSB9IGZyb20gXCJub2RlOnBlcmZfaG9va3NcIjtcbmltcG9ydCB7IHJlbmRlclBhZ2UgfSBmcm9tIFwiLi9yZW5kZXJQYWdlLmNsaWVudC5qc1wiO1xuXG5pbXBvcnQgeyBjcmVhdGVXb3JrZXIgfSBmcm9tIFwiLi4vd29ya2VyL2NyZWF0ZVdvcmtlci5qc1wiO1xuaW1wb3J0IHtcbiAgc2VyaWFsaXplZE9wdGlvbnMsXG4gIHNlcmlhbGl6ZVJlc29sdmVkQ29uZmlnLFxufSBmcm9tIFwiLi4vaGVscGVycy9zZXJpYWxpemVVc2VyT3B0aW9ucy5qc1wiO1xuaW1wb3J0IHsgZ2V0QnVuZGxlTWFuaWZlc3QgfSBmcm9tIFwiLi4vaGVscGVycy9nZXRCdW5kbGVNYW5pZmVzdC5qc1wiO1xuXG5pbXBvcnQgeyBoYW5kbGVFcnJvciB9IGZyb20gXCIuLi9lcnJvci9oYW5kbGVFcnJvci5qc1wiO1xuaW1wb3J0IHsgc2hvdWxkQ2F1c2VQYW5pYyB9IGZyb20gXCIuLi9lcnJvci9wYW5pY1RocmVzaG9sZEhhbmRsZXIuanNcIjtcbmltcG9ydCB7IGNvbmZpZ3VyZVByZXZpZXdTZXJ2ZXIgfSBmcm9tIFwiLi9jb25maWd1cmVQcmV2aWV3U2VydmVyLmpzXCI7XG5pbXBvcnQgeyBhc3NlcnROb25SZWFjdFNlcnZlciB9IGZyb20gXCIuLi9jb25maWcvZ2V0Q29uZGl0aW9uLmpzXCI7XG5pbXBvcnQgeyBlbnZQcmVmaXhGcm9tQ29uZmlnIH0gZnJvbSBcIi4uL2NvbmZpZy9lbnZQcmVmaXhGcm9tQ29uZmlnLmpzXCI7XG5pbXBvcnQgeyBjcmVhdGVXb3JrZXJTdGFydHVwTWV0cmljcyB9IGZyb20gXCIuLi9tZXRyaWNzL2NyZWF0ZVdvcmtlclN0YXJ0dXBNZXRyaWNzLmpzXCI7XG5pbXBvcnQgeyBwcm9jZXNzQ3NzRmlsZXNGb3JQYWdlcyB9IGZyb20gXCIuL3Byb2Nlc3NDc3NGaWxlc0ZvclBhZ2VzLmpzXCI7XG5pbXBvcnQgeyBjcmVhdGVCdWlsZExvYWRlciB9IGZyb20gXCIuL2NyZWF0ZUJ1aWxkTG9hZGVyLmNsaWVudC5qc1wiO1xuaW1wb3J0IHsgZ2V0Tm9kZUVudiB9IGZyb20gXCIuLi9jb25maWcvZ2V0Tm9kZUVudi5qc1wiO1xuaW1wb3J0IHsgdG9FcnJvciB9IGZyb20gXCIuLi9lcnJvci90b0Vycm9yLmpzXCI7XG5pbXBvcnQge1xuICBhZGRTdGF0aWNNYW5pZmVzdCxcbiAgbWFuaWZlc3RzLFxuICBnZXRTaGFyZWRNYW5pZmVzdFN0b3JlLFxufSBmcm9tIFwiLi4vYnVuZGxlL21hbmlmZXN0cy5qc1wiO1xuaW1wb3J0IHsgZGVmZXJTdGF0aWNHZW5lcmF0aW9uIH0gZnJvbSBcIi4uL2J1bmRsZS9kZWZlcnJlZFN0YXRpY0dlbmVyYXRpb24uanNcIjtcbmltcG9ydCB0eXBlIHsgV29ya2VyIH0gZnJvbSBcIm5vZGU6d29ya2VyX3RocmVhZHNcIjtcbmltcG9ydCB7IHJlc29sdmVBdXRvRGlzY292ZXIgfSBmcm9tIFwiLi4vY29uZmlnL2F1dG9EaXNjb3Zlci9yZXNvbHZlQXV0b0Rpc2NvdmVyLmpzXCI7XG5pbXBvcnQgeyBqb2luIH0gZnJvbSBcIm5vZGU6cGF0aFwiO1xuXG5pbXBvcnQgeyBiYXNlVVJMIH0gZnJvbSBcIi4uL3V0aWxzL2VudlVybHMubm9kZS5qc1wiO1xuaW1wb3J0IHsgdHJ5TWFuaWZlc3QgfSBmcm9tIFwiLi4vaGVscGVycy90cnlNYW5pZmVzdC5qc1wiO1xuLy8gY3NzQ29sbGVjdG9yIHJlbW92ZWQgLSB1c2luZyBmaWxlc3lzdGVtLWJhc2VkIENTUyBwcm9jZXNzaW5nXG5cbmFzc2VydE5vblJlYWN0U2VydmVyKCk7XG5cbi8qKlxuICogcGx1Z2luLmNsaWVudC50c1xuICpcbiAqIFBVUlBPU0U6IENsaWVudC1zaWRlIHN0YXRpYyBwbHVnaW4gZm9yIFJlYWN0IFNlcnZlciBDb21wb25lbnRzXG4gKlxuICogVGhpcyBtb2R1bGU6XG4gKiAxLiBIYW5kbGVzIHN0YXRpYyBzaXRlIGdlbmVyYXRpb24gaW4gdGhlIGNsaWVudCBlbnZpcm9ubWVudFxuICogMi4gVXNlcyBSU0Mgd29ya2VyIGZvciBSU0MgcmVuZGVyaW5nIGFuZCBtYWluLXRocmVhZCBmb3IgSFRNTCByZW5kZXJpbmdcbiAqIDMuIEdlbmVyYXRlcyBib3RoIFJTQyBhbmQgSFRNTCBmaWxlcyBmb3Igc3RhdGljIHBhZ2VzXG4gKiA0LiBJbnRlZ3JhdGVzIHdpdGggVml0ZSdzIGJ1aWxkIHByb2Nlc3NcbiAqXG4gKiBAcGFyYW0gb3B0aW9uc1xuICogQHJldHVybnNcbiAqL1xuZXhwb3J0IGNvbnN0IHJlYWN0U3RhdGljUGx1Z2luOiBWaXRlUGx1Z2luRm4gPSBmdW5jdGlvbiBfcmVhY3RTdGF0aWNQbHVnaW4oXG4gIG9wdGlvbnNcbikge1xuICBsZXQgbG9nZ2VyOiBSZXR1cm5UeXBlPHR5cGVvZiBjcmVhdGVMb2dnZXI+O1xuICBsZXQgYXV0b0Rpc2NvdmVyZWRGaWxlczogQXV0b0Rpc2NvdmVyZWRGaWxlcyB8IG51bGwgPSBudWxsO1xuICBsZXQgcnNjV29ya2VyOiBXb3JrZXIgfCB1bmRlZmluZWQgPSB1bmRlZmluZWQ7XG4gIGxldCByZXNvbHZlZENvbmZpZzogUmVzb2x2ZWRDb25maWcgfCBudWxsID0gbnVsbDtcbiAgbGV0IHNlcnZlck1hbmlmZXN0OiBNYW5pZmVzdCB8IHVuZGVmaW5lZCA9IHVuZGVmaW5lZDtcbiAgbGV0IHN0YXRpY0J1bmRsZTogT3V0cHV0QnVuZGxlIHwgdW5kZWZpbmVkID0gdW5kZWZpbmVkO1xuICBsZXQgc2VydmVyQnVuZGxlOiBPdXRwdXRCdW5kbGUgfCB1bmRlZmluZWQgPSB1bmRlZmluZWQ7XG5cbiAgbGV0IGNvbmZpZ0VudjogQ29uZmlnRW52IHwgdW5kZWZpbmVkO1xuICBjb25zdCB0aW1pbmc6IEJ1aWxkVGltaW5nID0ge1xuICAgIHN0YXJ0OiBwZXJmb3JtYW5jZS5ub3coKSxcbiAgICBjb25maWdSZXNvbHZlZDogMCxcbiAgICBidWlsZFN0YXJ0OiAwLFxuICAgIHJlbmRlclN0YXJ0OiAwLFxuICB9O1xuXG4gIGNvbnN0IHJlc29sdmVkT3B0aW9ucyA9IHJlc29sdmVPcHRpb25zKG9wdGlvbnMpO1xuICBpZiAocmVzb2x2ZWRPcHRpb25zLnR5cGUgPT09IFwiZXJyb3JcIikge1xuICAgIHRocm93IHJlc29sdmVkT3B0aW9ucy5lcnJvcjtcbiAgfVxuICBjb25zdCB1c2VyT3B0aW9ucyA9IHJlc29sdmVkT3B0aW9ucy51c2VyT3B0aW9ucztcblxuICByZXR1cm4ge1xuICAgIG5hbWU6IFwidml0ZTpwbHVnaW4tcmVhY3Qtc2VydmVyL2NsaWVudC1zdGF0aWNcIixcbiAgICBlbmZvcmNlOiBcInBvc3RcIixcbiAgICBhcHBseTogXCJidWlsZFwiLCAvLyBBcHBseSB0byBidWlsZCBtb2RlXG4gICAgYXBpOiB7XG4gICAgICBtZXRhOiB7IHRpbWluZyB9LFxuICAgIH0sXG4gICAgYXN5bmMgY29uZmlnKF9jb25maWcsIHZpdGVDb25maWdFbnYpIHtcbiAgICAgIGNvbmZpZ0VudiA9IHZpdGVDb25maWdFbnY7XG4gICAgfSxcbiAgICBhcHBseVRvRW52aXJvbm1lbnQocGFydGlhbEVudmlyb25tZW50KSB7XG4gICAgICAvLyBDbGllbnQgc3RhdGljIHBsdWdpbiBzaG91bGQgYXBwbHkgdG8gc3RhdGljIGVudmlyb25tZW50IChicm93c2VyL0VTTSBidWlsZHMpXG4gICAgICAvLyBUaGlzIGlzIHdoZXJlIHdlIHdhbnQgdG8gYnVuZGxlIGV2ZXJ5dGhpbmcgYW5kIGZpbHRlciBvdXQgX3ZpcnR1YWwgZmlsZXNcbiAgICAgIC8vIEFwcGx5IHRvIGJvdGggXCJzdGF0aWNcIiBhbmQgXCJjbGllbnRcIiBlbnZpcm9ubWVudHMgLSB3ZSdsbCBoYW5kbGUgd2hpY2ggb25lIHJ1bnMgc3RhdGljIGdlbmVyYXRpb24gaW4gY2xvc2VCdW5kbGVcbiAgICAgIGNvbnN0IGVudk5hbWUgPSBwYXJ0aWFsRW52aXJvbm1lbnQubmFtZSBhcyBcImNsaWVudFwiIHwgXCJzZXJ2ZXJcIiB8IFwic3NyXCIgfCBcInN0YXRpY1wiO1xuICAgICAgaWYgKFxuICAgICAgICBbXCJzdGF0aWNcIiwgXCJjbGllbnRcIl0uaW5jbHVkZXMoZW52TmFtZSlcbiAgICAgICkge1xuICAgICAgICByZXR1cm4gdHJ1ZTtcbiAgICAgIH1cbiAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9LFxuXG4gICAgYXN5bmMgY29uZmlnUmVzb2x2ZWQoY29uZmlnKSB7XG4gICAgICB0aW1pbmcuY29uZmlnUmVzb2x2ZWQgPSBwZXJmb3JtYW5jZS5ub3coKTtcbiAgICAgIGxvZ2dlciA9IGNvbmZpZy5jdXN0b21Mb2dnZXIgfHwgY3JlYXRlTG9nZ2VyKCk7XG4gICAgICByZXNvbHZlZENvbmZpZyA9IGNvbmZpZztcblxuICAgICAgLy8gUGVyZm9ybSBhdXRvLWRpc2NvdmVyeSB0byBwb3B1bGF0ZSBhdXRvRGlzY292ZXJlZEZpbGVzXG4gICAgICBjb25zdCBhdXRvRGlzY292ZXJSZXN1bHQgPSBhd2FpdCByZXNvbHZlQXV0b0Rpc2NvdmVyKHtcbiAgICAgICAgY29uZmlnOiBjb25maWcsXG4gICAgICAgIGNvbmZpZ0VudjogY29uZmlnRW52IHx8IHtcbiAgICAgICAgICBtb2RlOiBjb25maWcubW9kZSxcbiAgICAgICAgICBjb21tYW5kOiBjb25maWcuY29tbWFuZCxcbiAgICAgICAgICBpc1NzckJ1aWxkOiBmYWxzZSxcbiAgICAgICAgICBpc1ByZXZpZXc6IGZhbHNlLFxuICAgICAgICB9LFxuICAgICAgICB1c2VyT3B0aW9ucyxcbiAgICAgICAgbG9nZ2VyLFxuICAgICAgfSk7XG4gICAgICBpZiAoYXV0b0Rpc2NvdmVyUmVzdWx0LnR5cGUgPT09IFwiZXJyb3JcIikge1xuICAgICAgICB0aHJvdyBhdXRvRGlzY292ZXJSZXN1bHQuZXJyb3I7XG4gICAgICB9XG4gICAgICBhdXRvRGlzY292ZXJlZEZpbGVzID0gYXV0b0Rpc2NvdmVyUmVzdWx0LmF1dG9EaXNjb3ZlcmVkRmlsZXM7XG4gICAgICBpZih1c2VyT3B0aW9ucy52ZXJib3NlKSB7XG4gICAgICAgIGxvZ2dlcj8uaW5mbyhgQXV0by1kaXNjb3ZlcnkgJHthdXRvRGlzY292ZXJSZXN1bHQudHlwZSA9PT0gXCJzdWNjZXNzXCIgPyBcImNvbXBsZXRlZFwiIDogXCJza2lwcGVkXCJ9YCk7XG4gICAgICB9XG4gICAgfSxcblxuICAgIGFzeW5jIGJ1aWxkU3RhcnQoKSB7XG4gICAgICB0aW1pbmcuYnVpbGRTdGFydCA9IHBlcmZvcm1hbmNlLm5vdygpO1xuICAgICAgaWYodXNlck9wdGlvbnMudmVyYm9zZSkge1xuICAgICAgICBsb2dnZXI/LmluZm8oXCJbcmVhY3Qtc3RhdGljLWNsaWVudF0gQnVpbGQgc3RhcnRlZFwiKTtcbiAgICAgIH1cblxuICAgICAgaWYgKHVzZXJPcHRpb25zLm9uRXZlbnQgJiYgYXV0b0Rpc2NvdmVyZWRGaWxlcykge1xuICAgICAgICB0cnkge1xuICAgICAgICAgIHVzZXJPcHRpb25zLm9uRXZlbnQoe1xuICAgICAgICAgICAgdHlwZTogXCJidWlsZC5zdGFydFwiLFxuICAgICAgICAgICAgZGF0YToge1xuICAgICAgICAgICAgICBwYWdlczogQXJyYXkuZnJvbShhdXRvRGlzY292ZXJlZEZpbGVzLnVybE1hcC5rZXlzKCkpLFxuICAgICAgICAgICAgICBmaWxlczogYXV0b0Rpc2NvdmVyZWRGaWxlcyxcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgfSk7XG4gICAgICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICAgICAgY29uc3QgcGFuaWNFcnJvciA9IGhhbmRsZUVycm9yKHtcbiAgICAgICAgICAgIGVycm9yLFxuICAgICAgICAgICAgbG9nZ2VyOiBsb2dnZXIsXG4gICAgICAgICAgICBwYW5pY1RocmVzaG9sZDogdXNlck9wdGlvbnMucGFuaWNUaHJlc2hvbGQsXG4gICAgICAgICAgICBjb250ZXh0OiBcImJ1aWxkU3RhcnRcIixcbiAgICAgICAgICB9KTtcbiAgICAgICAgICBpZiAocGFuaWNFcnJvciAhPSBudWxsKSB7XG4gICAgICAgICAgICByc2NXb3JrZXI/LnRlcm1pbmF0ZSgpO1xuICAgICAgICAgIHRocm93IHBhbmljRXJyb3I7XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9XG4gICAgfSxcblxuICAgIGFzeW5jIHJlbmRlclN0YXJ0KCkge1xuICAgICAgdGltaW5nLnJlbmRlclN0YXJ0ID0gcGVyZm9ybWFuY2Uubm93KCk7XG4gICAgICBpZih1c2VyT3B0aW9ucy52ZXJib3NlKSB7IFxuICAgICAgICBsb2dnZXI/LmluZm8oXCJbcmVhY3Qtc3RhdGljLWNsaWVudF0gUmVuZGVyIHN0YXJ0ZWRcIik7XG4gICAgICB9XG4gICAgfSxcblxuICAgIC8vIHRoZSBwcmV2aWV3IHNlcnZlciBoZWxwcyB0byB2aWV3IHRoZSBnZW5lcmF0ZWQgc3RhdGljIGZvbGRlciwgYnV0IG9ubHkgd2hlbiB0aGUgc3RhdGljIHBsdWdpbiBpcyBlbmFibGVkXG4gICAgLy8gaWYgbm8gYnVpbGQucGFnZXMsIHRoZW4gdGhlIHByZXZpZXcgc2VydmVyIHdpbGwgaW5zdGVhZCB1c2UgZGVmYXVsdCB2aXRlIHByZXZpZXcgc2VydmVyXG4gICAgLy8gaXQgd29ya3MgdGhlIHNhbWUgdW5kZXIgYm90aCBjb25kaXRpb25zXG4gICAgYXN5bmMgY29uZmlndXJlUHJldmlld1NlcnZlcihzZXJ2ZXIpIHtcbiAgICAgIGxvZ2dlciA9IHNlcnZlci5jb25maWcuY3VzdG9tTG9nZ2VyIHx8IHNlcnZlci5jb25maWcubG9nZ2VyO1xuICAgICAgY29uZmlndXJlUHJldmlld1NlcnZlcih7XG4gICAgICAgIHNlcnZlcixcbiAgICAgICAgdXNlck9wdGlvbnMsXG4gICAgICB9KTtcbiAgICB9LFxuXG5cblxuICAgIGFzeW5jIHdyaXRlQnVuZGxlKF9vcHRpb25zLCBidW5kbGUpIHtcblxuICAgICAgLy8gQ2FwdHVyZSBtYW5pZmVzdHMgZnJvbSBhbGwgZW52aXJvbm1lbnRzXG4gICAgICB0cnkge1xuICAgICAgICBpZiAoIWF1dG9EaXNjb3ZlcmVkRmlsZXM/LnVybE1hcCkge1xuICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuXG4gICAgICAgIGNvbnN0IGJ1bmRsZU1hbmlmZXN0ID0gZ2V0QnVuZGxlTWFuaWZlc3Q8ZmFsc2U+KHtcbiAgICAgICAgICBidW5kbGUsXG4gICAgICAgICAgbm9ybWFsaXplcjogdXNlck9wdGlvbnMubm9ybWFsaXplcixcbiAgICAgICAgfSk7XG5cbiAgICAgICAgLy8gU3RvcmUgbWFuaWZlc3QgYmFzZWQgb24gZW52aXJvbm1lbnRcbiAgICAgICAgaWYgKHRoaXMuZW52aXJvbm1lbnQubmFtZSA9PT0gXCJzdGF0aWNcIikge1xuICAgICAgICAgIC8vIFN0b3JlIGluIGdsb2JhbCBtYW5pZmVzdCBzdG9yZSBmb3IgZW52aXJvbm1lbnQgcGx1Z2luIGFjY2Vzc1xuICAgICAgICAgIGFkZFN0YXRpY01hbmlmZXN0KGJ1bmRsZU1hbmlmZXN0KTtcblxuICAgICAgICAgIHN0YXRpY0J1bmRsZSA9IGJ1bmRsZTtcbiAgICAgICAgfSBlbHNlIGlmICh0aGlzLmVudmlyb25tZW50Lm5hbWUgPT09IFwiY2xpZW50XCIpIHtcbiAgICAgICAgICAvLyBDbGllbnQgYnVpbGQgbWFuaWZlc3QgKFNTUiBtb2R1bGVzKSAtIHN0b3JlZCBnbG9iYWxseSBub3dcblxuICAgICAgICAgIGlmIChtYW5pZmVzdHMuc3RhdGljKSB7XG4gICAgICAgICAgICBjb25zdCBzdGF0aWNNYW5pZmVzdCA9IG1hbmlmZXN0cy5zdGF0aWM7XG5cbiAgICAgICAgICAgIC8vIFVwZGF0ZSBidW5kbGUgZmlsZW5hbWVzIHRvIG1hdGNoIHN0YXRpYyBtYW5pZmVzdFxuICAgICAgICAgICAgZm9yIChjb25zdCBbLCBjaHVua10gb2YgT2JqZWN0LmVudHJpZXMoYnVuZGxlKSkge1xuICAgICAgICAgICAgICBpZiAoY2h1bmsudHlwZSA9PT0gXCJjaHVua1wiICYmIGNodW5rLmZpbGVOYW1lKSB7XG4gICAgICAgICAgICAgICAgY29uc3Qgbm9ybWFsaXplZCA9IHVzZXJPcHRpb25zLm5vcm1hbGl6ZXIoY2h1bmsuZmlsZU5hbWUpO1xuICAgICAgICAgICAgICAgIGxldCB2YWx1ZSA9IG5vcm1hbGl6ZWRbMV07XG4gICAgICAgICAgICAgICAgaWYgKHZhbHVlLnN0YXJ0c1dpdGgodXNlck9wdGlvbnMubW9kdWxlQmFzZVBhdGgpKSB7XG4gICAgICAgICAgICAgICAgICB2YWx1ZSA9IHZhbHVlLnNsaWNlKHVzZXJPcHRpb25zLm1vZHVsZUJhc2VQYXRoLmxlbmd0aCk7XG4gICAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgICAgY29uc3QgZW50cnkgPSBzdGF0aWNNYW5pZmVzdFt2YWx1ZV07XG4gICAgICAgICAgICAgICAgaWYgKGVudHJ5ICYmIGVudHJ5LmZpbGUgIT09IGNodW5rLmZpbGVOYW1lKSB7XG4gICAgICAgICAgICAgICAgICAvLyBVcGRhdGUgdGhlIGZpbGVuYW1lIHRvIG1hdGNoIHN0YXRpYyBtYW5pZmVzdFxuICAgICAgICAgICAgICAgICAgY2h1bmsuZmlsZU5hbWUgPSBlbnRyeS5maWxlO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSBlbHNlIGlmICh0aGlzLmVudmlyb25tZW50Lm5hbWUgPT09IFwic2VydmVyXCIpIHtcbiAgICAgICAgICAvLyBTZXJ2ZXIgYnVpbGQgbWFuaWZlc3QgKHNlcnZlciBjb21wb25lbnRzKSAtIHN0b3JlZCBnbG9iYWxseSBub3dcbiAgICAgICAgICBzZXJ2ZXJCdW5kbGUgPSBidW5kbGU7XG4gICAgICAgIH1cblxuICAgICAgICAvLyBTa2lwIHRoZSBzdGF0aWMgZ2VuZXJhdGlvbiBoZXJlIC0gaXQgd2lsbCBoYXBwZW4gaW4gY2xvc2VCdW5kbGVcbiAgICAgICAgcmV0dXJuO1xuICAgICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgICAgY29uc3QgcGFuaWNFcnJvciA9IGhhbmRsZUVycm9yKHtcbiAgICAgICAgICBlcnJvcixcbiAgICAgICAgICBsb2dnZXI6IGxvZ2dlcixcbiAgICAgICAgICBwYW5pY1RocmVzaG9sZDogdXNlck9wdGlvbnMucGFuaWNUaHJlc2hvbGQsXG4gICAgICAgICAgY29udGV4dDogXCJ3cml0ZUJ1bmRsZVwiLFxuICAgICAgICB9KTtcbiAgICAgICAgaWYgKHBhbmljRXJyb3IgIT0gbnVsbCkge1xuICAgICAgICAgIHRocm93IHBhbmljRXJyb3I7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9LFxuXG4gICAgYXN5bmMgY2xvc2VCdW5kbGUoKSB7XG4gICAgICBjb25zdCBlbnZOYW1lID0gdGhpcy5lbnZpcm9ubWVudC5uYW1lO1xuICAgICAgY29uc3QgaXNTc3IgPSB0aGlzLmVudmlyb25tZW50LmNvbmZpZy5idWlsZD8uc3NyID09PSB0cnVlO1xuICAgICAgXG4gICAgICBpZiAodXNlck9wdGlvbnMudmVyYm9zZSkge1xuICAgICAgICBsb2dnZXI/LmluZm8oYFtyZWFjdC1zdGF0aWMtY2xpZW50XSBjbG9zZUJ1bmRsZSBjYWxsZWQgZm9yIGVudmlyb25tZW50OiAke2Vudk5hbWV9LCBzc3I6ICR7aXNTc3J9YCk7XG4gICAgICB9XG4gICAgICBcbiAgICAgIC8vIE9ubHkgcnVuIHN0YXRpYyBnZW5lcmF0aW9uIGluIHRoZSBub24tU1NSIGNsaWVudCBlbnZpcm9ubWVudCAoc3RhdGljIGJ1aWxkcylcbiAgICAgIC8vIFNraXAgU1NSIGNsaWVudCBidWlsZHMgYW5kIHNlcnZlciBidWlsZHNcbiAgICAgIGlmIChlbnZOYW1lID09PSBcInNzclwiIHx8IGVudk5hbWUgPT09IFwic2VydmVyXCIgfHwgaXNTc3IpIHtcbiAgICAgICAgaWYgKHVzZXJPcHRpb25zLnZlcmJvc2UpIHtcbiAgICAgICAgICBsb2dnZXI/LmluZm8oYFtyZWFjdC1zdGF0aWMtY2xpZW50XSBTa2lwcGluZyBzdGF0aWMgZ2VuZXJhdGlvbiBmb3IgZW52aXJvbm1lbnQ6ICR7ZW52TmFtZX0gKHNzcjogJHtpc1Nzcn0pYCk7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuO1xuICAgICAgfVxuXG4gICAgICAvLyBDbGVhbiB1cCBfdmlydHVhbCBmaWxlcyBhZnRlciBidWlsZCBjb21wbGV0ZXNcbiAgICAgIC8vIFRoZXNlIGFyZSBWaXRlJ3MgaW50ZXJuYWwgdmlydHVhbCBtb2R1bGVzIGFuZCBhcmVuJ3QgbmVlZGVkIGluIHRoZSBmaW5hbCBvdXRwdXRcbiAgICAgIGlmIChlbnZOYW1lID09PSBcInN0YXRpY1wiIHx8IChlbnZOYW1lID09PSBcImNsaWVudFwiICYmICFpc1NzcikpIHtcbiAgICAgICAgdHJ5IHtcbiAgICAgICAgICBjb25zdCB7IHJtU3luYywgZXhpc3RzU3luYyB9ID0gYXdhaXQgaW1wb3J0KFwibm9kZTpmc1wiKTtcbiAgICAgICAgICBjb25zdCB7IGpvaW4sIHJlc29sdmUgfSA9IGF3YWl0IGltcG9ydChcIm5vZGU6cGF0aFwiKTtcbiAgICAgICAgICBcbiAgICAgICAgICAvLyBVc2UgdGhlIHJlc29sdmVkIG91dHB1dCBkaXJlY3RvcnkgZnJvbSB0aGUgZW52aXJvbm1lbnQgY29uZmlnXG4gICAgICAgICAgY29uc3QgcmVzb2x2ZWRPdXREaXIgPSB0aGlzLmVudmlyb25tZW50LmNvbmZpZy5idWlsZD8ub3V0RGlyIFxuICAgICAgICAgICAgPyByZXNvbHZlKHRoaXMuZW52aXJvbm1lbnQuY29uZmlnLnJvb3QgfHwgdXNlck9wdGlvbnMucHJvamVjdFJvb3QsIHRoaXMuZW52aXJvbm1lbnQuY29uZmlnLmJ1aWxkLm91dERpcilcbiAgICAgICAgICAgIDogcmVzb2x2ZSh1c2VyT3B0aW9ucy5wcm9qZWN0Um9vdCwgdXNlck9wdGlvbnMuYnVpbGQub3V0RGlyKTtcbiAgICAgICAgICBcbiAgICAgICAgICAvLyBDbGVhbiB1cCBfdmlydHVhbCBmcm9tIGNsaWVudC9zdGF0aWMgb3V0cHV0IGRpcmVjdG9yaWVzIG9ubHlcbiAgICAgICAgICAvLyBEb24ndCBjbGVhbiB1cCBzZXJ2ZXIvX3ZpcnR1YWwgc2luY2Ugd2UgbmVlZCBkeW5hbWljLWltcG9ydC1oZWxwZXIuanMgdGhlcmVcbiAgICAgICAgICBjb25zdCBvdXRwdXREaXJzID0gW1xuICAgICAgICAgICAgam9pbihyZXNvbHZlZE91dERpciwgdXNlck9wdGlvbnMuYnVpbGQuc3RhdGljIHx8IFwic3RhdGljXCIpLFxuICAgICAgICAgICAgam9pbihyZXNvbHZlZE91dERpciwgdXNlck9wdGlvbnMuYnVpbGQuY2xpZW50IHx8IFwiY2xpZW50XCIpLFxuICAgICAgICAgIF07XG4gICAgICAgICAgXG4gICAgICAgICAgZm9yIChjb25zdCBvdXREaXIgb2Ygb3V0cHV0RGlycykge1xuICAgICAgICAgICAgY29uc3QgdmlydHVhbERpciA9IGpvaW4ob3V0RGlyLCBcIl92aXJ0dWFsXCIpO1xuICAgICAgICAgICAgaWYgKGV4aXN0c1N5bmModmlydHVhbERpcikpIHtcbiAgICAgICAgICAgICAgcm1TeW5jKHZpcnR1YWxEaXIsIHsgcmVjdXJzaXZlOiB0cnVlLCBmb3JjZTogdHJ1ZSB9KTtcbiAgICAgICAgICAgICAgaWYgKHVzZXJPcHRpb25zLnZlcmJvc2UpIHtcbiAgICAgICAgICAgICAgICBsb2dnZXI/LmluZm8oYFtyZWFjdC1zdGF0aWMtY2xpZW50XSBDbGVhbmVkIHVwIF92aXJ0dWFsIGRpcmVjdG9yeTogJHt2aXJ0dWFsRGlyfWApO1xuICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgICAgIC8vIE5vbi1jcml0aWNhbCAtIGxvZyBidXQgZG9uJ3QgZmFpbCB0aGUgYnVpbGRcbiAgICAgICAgICBpZiAodXNlck9wdGlvbnMudmVyYm9zZSkge1xuICAgICAgICAgICAgbG9nZ2VyPy53YXJuKGBbcmVhY3Qtc3RhdGljLWNsaWVudF0gRmFpbGVkIHRvIGNsZWFuIHVwIF92aXJ0dWFsIGRpcmVjdG9yeTogJHtlcnJvcn1gKTtcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIH1cblxuICAgICAgLy8gVGhpcyBydW5zIGFmdGVyIGFsbCB3cml0ZUJ1bmRsZSBob29rcyBhcmUgY29tcGxldGVcbiAgICAgIC8vIFJ1biBzdGF0aWMgZ2VuZXJhdGlvbiBpbiB0aGUgbm9uLVNTUiBjbGllbnQgZW52aXJvbm1lbnQgKHN0YXRpYyBidWlsZHMpXG4gICAgICAvLyBUaGlzIGNvdWxkIGJlIFwic3RhdGljXCIgb3IgXCJjbGllbnRcIiBkZXBlbmRpbmcgb24gaG93IGVudmlyb25tZW50cyBhcmUgY29uZmlndXJlZFxuICAgICAgaWYgKGVudk5hbWUgPT09IFwic3NyXCIgfHwgZW52TmFtZSA9PT0gXCJzZXJ2ZXJcIiB8fCBpc1Nzcikge1xuICAgICAgICBpZiAodXNlck9wdGlvbnMudmVyYm9zZSkge1xuICAgICAgICAgIGxvZ2dlcj8uaW5mbyhgW3JlYWN0LXN0YXRpYy1jbGllbnRdIFNraXBwaW5nIHN0YXRpYyBnZW5lcmF0aW9uIC0gbm90IGluIHN0YXRpYyBlbnZpcm9ubWVudCAoJHtlbnZOYW1lfSwgc3NyOiAke2lzU3NyfSlgKTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm47XG4gICAgICB9XG5cbiAgICAgIC8vIERlZmVyIHN0YXRpYyBnZW5lcmF0aW9uIHRvIHJ1biBhZnRlciBBTEwgZW52aXJvbm1lbnRzIGNvbXBsZXRlIHRoZWlyIGJ1aWxkcy5cbiAgICAgIC8vIFRoaXMgaXMgbmVjZXNzYXJ5IGJlY2F1c2Ugd2UgbmVlZCB0aGUgc2VydmVyIG1hbmlmZXN0IChmcm9tIHNlcnZlciBlbnYncyB3cml0ZUJ1bmRsZSlcbiAgICAgIC8vIHRvIHJlc29sdmUgZnVuY3Rpb24tYmFzZWQgY29tcG9uZW50IHBhdGhzIGxpa2UgUm9vdDogKHVybCkgPT4gJ3NyYy9DdXN0b21Sb290LnRzeCcuXG4gICAgICAvLyBUaGUgYnVpbGRBcHAgaG9vayBpbiBjcmVhdGVFbnZpcm9ubWVudFBsdWdpbiB3aWxsIGNhbGwgcnVuRGVmZXJyZWRTdGF0aWNHZW5lcmF0aW9uKCkuXG4gICAgICBjb25zdCBjbG9zZUJ1bmRsZUNvbnRleHQgPSB0aGlzO1xuICAgICAgZGVmZXJTdGF0aWNHZW5lcmF0aW9uKGFzeW5jICgpID0+IHtcblxuICAgICAgdHJ5IHtcbiAgICAgICAgLy8gUmUtY2hlY2sgYXV0b0Rpc2NvdmVyZWRGaWxlcyAtIGl0IG1pZ2h0IG5vdCBiZSBzZXQgaWYgY29uZmlnUmVzb2x2ZWQgZGlkbid0IHJ1blxuICAgICAgICAvLyBvciBpZiBpdCB3YXMgY2xlYXJlZC4gVHJ5IHRvIGdldCBpdCBmcm9tIHN0YXNoZWQgb3B0aW9ucyBpZiBuZWVkZWRcbiAgICAgICAgaWYgKCFhdXRvRGlzY292ZXJlZEZpbGVzKSB7XG4gICAgICAgICAgaWYgKHVzZXJPcHRpb25zLnZlcmJvc2UpIHtcbiAgICAgICAgICAgIGxvZ2dlcj8ud2FybihcIltyZWFjdC1zdGF0aWMtY2xpZW50XSBhdXRvRGlzY292ZXJlZEZpbGVzIG5vdCBzZXQsIGF0dGVtcHRpbmcgdG8gcmUtZGlzY292ZXJcIik7XG4gICAgICAgICAgfVxuICAgICAgICAgIGNvbnN0IHsgZ2V0U3Rhc2hlZFVzZXJPcHRpb25zLCBnZXRFbnZpcm9ubWVudElkIH0gPSBhd2FpdCBpbXBvcnQoXCIuLi9jb25maWcvc3Rhc2hlZE9wdGlvbnNTdGF0ZS5qc1wiKTtcbiAgICAgICAgICBjb25zdCB7IGdldENvbmRpdGlvbiB9ID0gYXdhaXQgaW1wb3J0KFwiLi4vY29uZmlnL2dldENvbmRpdGlvbi5qc1wiKTtcbiAgICAgICAgICBjb25zdCBlbnZJZCA9IGdldEVudmlyb25tZW50SWQoZ2V0Q29uZGl0aW9uKCksIHJlc29sdmVkQ29uZmlnPy5tb2RlIHx8IFwicHJvZHVjdGlvblwiKTtcbiAgICAgICAgICBjb25zdCBzdGFzaGVkT3B0aW9ucyA9IGdldFN0YXNoZWRVc2VyT3B0aW9ucyhlbnZJZCk7XG4gICAgICAgICAgaWYgKHN0YXNoZWRPcHRpb25zICYmIHJlc29sdmVkQ29uZmlnKSB7XG4gICAgICAgICAgICAvLyBUcnkgdG8gcmUtcnVuIGF1dG8tZGlzY292ZXJ5IGlmIHdlIGhhdmUgdGhlIGNvbmZpZ1xuICAgICAgICAgICAgY29uc3QgYXV0b0Rpc2NvdmVyUmVzdWx0ID0gYXdhaXQgcmVzb2x2ZUF1dG9EaXNjb3Zlcih7XG4gICAgICAgICAgICAgIGNvbmZpZzogcmVzb2x2ZWRDb25maWcsXG4gICAgICAgICAgICAgIGNvbmZpZ0VudjogY29uZmlnRW52IHx8IHtcbiAgICAgICAgICAgICAgICBtb2RlOiByZXNvbHZlZENvbmZpZy5tb2RlIHx8IFwicHJvZHVjdGlvblwiLFxuICAgICAgICAgICAgICAgIGNvbW1hbmQ6IHJlc29sdmVkQ29uZmlnLmNvbW1hbmQgfHwgXCJidWlsZFwiLFxuICAgICAgICAgICAgICAgIGlzU3NyQnVpbGQ6IGZhbHNlLFxuICAgICAgICAgICAgICAgIGlzUHJldmlldzogZmFsc2UsXG4gICAgICAgICAgICAgIH0sXG4gICAgICAgICAgICAgIHVzZXJPcHRpb25zLFxuICAgICAgICAgICAgICBsb2dnZXIsXG4gICAgICAgICAgICB9KTtcbiAgICAgICAgICAgIGlmIChhdXRvRGlzY292ZXJSZXN1bHQudHlwZSA9PT0gXCJzdWNjZXNzXCIpIHtcbiAgICAgICAgICAgICAgYXV0b0Rpc2NvdmVyZWRGaWxlcyA9IGF1dG9EaXNjb3ZlclJlc3VsdC5hdXRvRGlzY292ZXJlZEZpbGVzO1xuICAgICAgICAgICAgICBpZiAodXNlck9wdGlvbnMudmVyYm9zZSkge1xuICAgICAgICAgICAgICAgIGxvZ2dlcj8uaW5mbyhgW3JlYWN0LXN0YXRpYy1jbGllbnRdIFJlLWRpc2NvdmVyZWQgJHthdXRvRGlzY292ZXJlZEZpbGVzLnVybE1hcC5zaXplfSBwYWdlc2ApO1xuICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICBpZiAodXNlck9wdGlvbnMudmVyYm9zZSkge1xuICAgICAgICAgICAgICAgIGxvZ2dlcj8ud2FybihgW3JlYWN0LXN0YXRpYy1jbGllbnRdIEZhaWxlZCB0byByZS1kaXNjb3ZlciBwYWdlczogJHthdXRvRGlzY292ZXJSZXN1bHQuZXJyb3J9YCk7XG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICBpZiAoXG4gICAgICAgICAgIWF1dG9EaXNjb3ZlcmVkRmlsZXM/LnVybE1hcCB8fFxuICAgICAgICAgIGF1dG9EaXNjb3ZlcmVkRmlsZXM/LnVybE1hcC5zaXplID09PSAwXG4gICAgICAgICkge1xuICAgICAgICAgIGlmICh1c2VyT3B0aW9ucy52ZXJib3NlKSB7XG4gICAgICAgICAgICBsb2dnZXI/Lndhcm4oYFtyZWFjdC1zdGF0aWMtY2xpZW50XSBObyBwYWdlcyB0byBnZW5lcmF0ZSAtIHVybE1hcCBpcyBlbXB0eSAoc2l6ZTogJHthdXRvRGlzY292ZXJlZEZpbGVzPy51cmxNYXA/LnNpemUgfHwgMH0pYCk7XG4gICAgICAgICAgICBsb2dnZXI/Lndhcm4oYFtyZWFjdC1zdGF0aWMtY2xpZW50XSBhdXRvRGlzY292ZXJlZEZpbGVzIGV4aXN0czogJHshIWF1dG9EaXNjb3ZlcmVkRmlsZXN9LCB1cmxNYXAgZXhpc3RzOiAkeyEhYXV0b0Rpc2NvdmVyZWRGaWxlcz8udXJsTWFwfWApO1xuICAgICAgICAgIH1cbiAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cblxuICAgICAgICBpZiAodXNl