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