UNPKG

@testplane/wdio-config

Version:
594 lines (587 loc) 84.4 kB
// src/node/ConfigParser.ts import path3 from "node:path"; import logger from "@testplane/wdio-logger"; import { deepmerge, deepmergeCustom } from "deepmerge-ts"; // src/node/FileSystemPathService.ts import fs from "node:fs"; import url from "node:url"; import path from "node:path"; import { sync as globSync } from "glob"; // src/node/RequireLibrary.ts var RequireLibrary = class { import(module) { return import(module); } }; // src/node/FileSystemPathService.ts function lowercaseWinDriveLetter(p) { return p.replace(/^[A-Za-z]:\\/, (match) => match.toLowerCase()); } var FileSystemPathService = class { #moduleRequireService = new RequireLibrary(); loadFile(path4) { if (!path4) { throw new Error("A path is required"); } return this.#moduleRequireService.import(path4); } isFile(filepath) { return fs.existsSync(filepath) && fs.lstatSync(filepath).isFile(); } /** * find test files based on a glob pattern * @param pattern file pattern to glob * @param rootDir directory of wdio config file * @returns files matching the glob pattern */ glob(pattern, rootDir) { const globResult = globSync(pattern, { cwd: rootDir, matchBase: true }) || []; const fileName = pattern.startsWith(path.sep) ? pattern : path.resolve(rootDir, pattern); if (!pattern.includes("*") && !globResult.includes(pattern) && !globResult.map(lowercaseWinDriveLetter).includes(lowercaseWinDriveLetter(fileName)) && fs.existsSync(fileName)) { globResult.push(fileName); } return globResult.sort(); } ensureAbsolutePath(filepath, rootDir) { if (filepath.startsWith("file://")) { return filepath; } const p = path.isAbsolute(filepath) ? path.normalize(filepath) : path.resolve(rootDir, filepath); return url.pathToFileURL(p).href; } }; // src/node/utils.ts import url2 from "node:url"; import path2 from "node:path"; function makeRelativeToCWD(files = []) { const returnFiles = []; for (const file of files) { if (Array.isArray(file)) { returnFiles.push(makeRelativeToCWD(file)); continue; } returnFiles.push( file.startsWith("file:///") ? url2.fileURLToPath(file) : file.includes("/") && !file.includes("*") ? path2.resolve(process.cwd(), file) : file ); } return returnFiles; } // src/utils.ts var validObjectOrArray = (object) => Array.isArray(object) && object.length > 0 || typeof object === "object" && Object.keys(object).length > 0; function removeLineNumbers(filePath) { const matcher = filePath.match(/:\d+(:\d+$|$)/); if (matcher) { filePath = filePath.substring(0, matcher.index); } return filePath; } function isCucumberFeatureWithLineNumber(spec) { const specs = Array.isArray(spec) ? spec : [spec]; return specs.some((s) => /:\d+(:\d+$|$)/.test(s)); } // src/constants.ts var DEFAULT_TIMEOUT = 1e4; var DEFAULT_CONFIGS = () => ({ specs: [], suites: {}, exclude: [], outputDir: void 0, logLevel: "info", logLevels: {}, groupLogsByTestSpec: false, excludeDriverLogs: [], bail: 0, waitforInterval: 500, waitforTimeout: 5e3, framework: "mocha", reporters: [], services: [], maxInstances: 100, maxInstancesPerCapability: 100, injectGlobals: true, filesToWatch: [], connectionRetryTimeout: 12e4, connectionRetryCount: 3, execArgv: [], runnerEnv: {}, runner: "local", shard: { current: 1, total: 1 }, specFileRetries: 0, specFileRetriesDelay: 0, specFileRetriesDeferred: false, reporterSyncInterval: 100, reporterSyncTimeout: 5e3, cucumberFeaturesWithLineNumbers: [], /** * framework defaults */ mochaOpts: { timeout: DEFAULT_TIMEOUT }, jasmineOpts: { defaultTimeoutInterval: DEFAULT_TIMEOUT }, cucumberOpts: { timeout: DEFAULT_TIMEOUT }, /** * hooks */ onPrepare: [], onWorkerStart: [], onWorkerEnd: [], before: [], beforeSession: [], beforeSuite: [], beforeHook: [], beforeTest: [], beforeCommand: [], afterCommand: [], afterTest: [], afterHook: [], afterSuite: [], afterSession: [], after: [], onComplete: [], onReload: [], beforeAssertion: [], afterAssertion: [], /** * cucumber specific hooks */ beforeFeature: [], beforeScenario: [], beforeStep: [], afterStep: [], afterScenario: [], afterFeature: [] }); var SUPPORTED_HOOKS = [ "before", "beforeSession", "beforeSuite", "beforeHook", "beforeTest", "beforeCommand", "afterCommand", "afterTest", "afterHook", "afterSuite", "afterSession", "after", "beforeAssertion", "afterAssertion", // @ts-ignore not defined in core hooks but added with cucumber "beforeFeature", "beforeScenario", "beforeStep", "afterStep", "afterScenario", "afterFeature", "onReload", "onPrepare", "onWorkerStart", "onWorkerEnd", "onComplete" ]; var SUPPORTED_FILE_EXTENSIONS = [ ".js", ".jsx", ".mjs", ".mts", ".es6", ".ts", ".tsx", ".feature", ".coffee", ".cjs" ]; var NO_NAMED_CONFIG_EXPORT = 'No named export object called "config" found. Make sure you export the config object via `export.config = { ... }` when using CommonJS or `export const config = { ... }` when using ESM. Read more on this on https://webdriver.io/docs/configurationfile !'; // src/node/ConfigParser.ts var log = logger("@testplane/wdio-config:ConfigParser"); var MERGE_DUPLICATION = ["services", "reporters", "capabilities"]; var ConfigParser = class _ConfigParser { constructor(configFilePath, _initialConfig = {}, _pathService = new FileSystemPathService()) { this._initialConfig = _initialConfig; this._pathService = _pathService; this.#configFilePath = configFilePath; this._config = Object.assign( { rootDir: path3.dirname(configFilePath) }, DEFAULT_CONFIGS() ); if (_initialConfig.spec) { _initialConfig.spec = makeRelativeToCWD(_initialConfig.spec); } this.merge(_initialConfig, false); } #isInitialised = false; #configFilePath; _config; _capabilities = []; /** * initializes the config object */ async initialize(object = {}) { if (!this.#isInitialised) { await this.addConfigFile(this.#configFilePath); } this.merge({ ...object }); if (Object.keys(this._initialConfig || {}).includes("coverage")) { if (this._config.runner === "browser") { this._config.runner = ["browser", { coverage: { enabled: this._initialConfig.coverage } }]; } else if (Array.isArray(this._config.runner) && this._config.runner[0] === "browser") { this._config.runner[1].coverage = { // eslint-disable-next-line @typescript-eslint/no-explicit-any ...this._config.runner[1].coverage, enabled: this._initialConfig.coverage }; } } this.#isInitialised = true; } /** * merges config file with default values * @param {string} filename path of file relative to current directory */ async addConfigFile(filename) { if (typeof filename !== "string") { throw new Error("addConfigFile requires filepath"); } const filePath = this._pathService.ensureAbsolutePath(filename, process.cwd()); try { const importedModule = await this._pathService.loadFile(filePath); const config = importedModule.config || importedModule.default?.config; if (typeof config !== "object") { throw new Error(NO_NAMED_CONFIG_EXPORT); } const configFileCapabilities = config.capabilities; if (!configFileCapabilities) { throw new Error(`No \`capabilities\` property found in WebdriverIO.Config defined in file: ${filePath}`); } const fileConfig = Object.assign({}, config); const defaultTo = Array.isArray(this._capabilities) ? [] : {}; this._capabilities = deepmerge(this._capabilities, fileConfig.capabilities || defaultTo); delete fileConfig.capabilities; this.addService(fileConfig); for (const hookName of SUPPORTED_HOOKS) { delete fileConfig[hookName]; } this._config = deepmerge(this._config, fileConfig); delete this._config.watch; } catch (e) { log.error(`Failed loading configuration file: ${filePath}:`, e.message); throw e; } } /** * merge external object with config object * @param {Object} object desired object to merge into the config object * @param {boolean} [addPathToSpecs=true] this flag determines whether it is necessary to find paths to specs if the --spec parameter was passed in CLI */ merge(object = {}, addPathToSpecs = true) { const spec = Array.isArray(object.spec) ? object.spec : []; const exclude = Array.isArray(object.exclude) ? object.exclude : []; const customDeepMerge = deepmergeCustom({ mergeArrays: ([oldValue, newValue], utils, meta) => { const key = meta?.key; if (meta && MERGE_DUPLICATION.includes(key)) { const origWithoutObjectEntries = oldValue.filter((value) => typeof value !== "object"); return Array.from(new Set(deepmerge(newValue, origWithoutObjectEntries))); } return utils.actions.defaultMerge; } }); this._config = customDeepMerge(this._config, object); if (object["wdio:specs"] && object["wdio:specs"].length > 0) { this._config.specs = object["wdio:specs"]; } else if (object.specs && object.specs.length > 0) { this._config.specs = object.specs; } if (object["wdio:exclude"] && object["wdio:exclude"].length > 0) { this._config.exclude = object["wdio:exclude"]; } else if (object.exclude && object.exclude.length > 0) { this._config.exclude = object.exclude; } if (object.suite && object.suite.length > 0) { this._config.suite = this._config.suite?.filter((suite, idx, suites) => suites.indexOf(suite) === idx); } this._capabilities = validObjectOrArray(this._config.capabilities) ? this._config.capabilities : this._capabilities; if (this._config.spec && isCucumberFeatureWithLineNumber(this._config.spec)) { this._config.cucumberFeaturesWithLineNumbers = Array.isArray(this._config.spec) ? [...new Set(this._config.spec)] : [this._config.spec]; } if (addPathToSpecs && spec.length > 0) { this._config.specs = this.setFilePathToFilterOptions(spec, this._config.specs, object.group); } if (exclude.length > 0 && allKeywordsContainPath(exclude)) { this._config.exclude = this.setFilePathToFilterOptions(exclude, this._config.exclude); } else if (exclude.length > 0) { this._config.exclude = exclude; } } /** * Add hooks from an existing service to the runner config. * @param {object} service - an object that contains hook methods. */ addService(service) { const addHook = (hookName, hook) => { const existingHooks = this._config[hookName]; if (!existingHooks) { this._config[hookName] = hook.bind(service); } else if (typeof existingHooks === "function") { this._config[hookName] = [existingHooks, hook.bind(service)]; } else { this._config[hookName] = [...existingHooks, hook.bind(service)]; } }; for (const hookName of SUPPORTED_HOOKS) { const hooksToBeAdded = service[hookName]; if (!hooksToBeAdded) { continue; } if (typeof hooksToBeAdded === "function") { addHook(hookName, hooksToBeAdded); } else if (Array.isArray(hooksToBeAdded)) { for (const hookToAdd of hooksToBeAdded) { if (typeof hookToAdd === "function") { addHook(hookName, hookToAdd); } } } } } /** * determine what specs to run based on the spec(s), suite(s), exclude * attributes from CLI, config and capabilities */ getSpecs(capSpecs, capExclude) { const isSpecParamPassed = Array.isArray(this._config.spec) && this._config.spec.length > 0; const repeat = this._config.repeat; let specs = _ConfigParser.getFilePaths(this._config.specs, this._config.rootDir, this._pathService); let exclude = allKeywordsContainPath(this._config.exclude) ? _ConfigParser.getFilePaths(this._config.exclude, this._config.rootDir, this._pathService) : this._config.exclude || []; const suites = Array.isArray(this._config.suite) ? this._config.suite : []; if (Array.isArray(capExclude) && exclude.length === 0) { exclude = _ConfigParser.getFilePaths(capExclude, this._config.rootDir, this._pathService); } if (!isSpecParamPassed && Array.isArray(capSpecs)) { specs = _ConfigParser.getFilePaths(capSpecs, this._config.rootDir, this._pathService); } if (suites.length > 0) { let suiteSpecs = []; for (const suiteName of suites) { const suite = this._config.suites?.[suiteName]; if (!suite) { log.warn(`No suite was found with name "${suiteName}"`); } if (Array.isArray(suite)) { suiteSpecs = suiteSpecs.concat(_ConfigParser.getFilePaths(suite, this._config.rootDir, this._pathService)); } } if (suiteSpecs.length === 0) { throw new Error(`The suite(s) "${suites.join('", "')}" you specified don't exist in your config file or doesn't contain any files!`); } specs = isSpecParamPassed ? [...specs, ...suiteSpecs] : suiteSpecs; } specs = filterDublicationArrayItems(specs); const hasSubsetOfSpecsDefined = isSpecParamPassed || suites.length > 0; if (repeat && hasSubsetOfSpecsDefined) { specs = Array.from({ length: repeat }, () => specs).flat(); } else if (repeat && !hasSubsetOfSpecsDefined) { throw new Error("The --repeat flag requires that either the --spec or --suite flag is also set"); } return this.shard( this.filterSpecs(specs, exclude) ); } /** * sets config attribute with file paths from filtering * options from cli argument * * @param {string[]} cliArgFileList list of files in a string form * @param {Object} config config object that stores the spec and exclude attributes * cli argument * @return {String[]} List of files that should be included or excluded */ setFilePathToFilterOptions(cliArgFileList, specs, group) { const filesToFilter = /* @__PURE__ */ new Set(); const fileList = _ConfigParser.getFilePaths(specs, this._config.rootDir, this._pathService); cliArgFileList.forEach((filteredFile) => { filteredFile = removeLineNumbers(filteredFile); const globMatchedFiles = _ConfigParser.getFilePaths( group ? [[filteredFile]] : [filteredFile], this._config.rootDir, this._pathService ); if (this._pathService.isFile(filteredFile)) { filesToFilter.add( this._pathService.ensureAbsolutePath( filteredFile, path3.dirname(this.#configFilePath) ) ); } else if (globMatchedFiles.length) { globMatchedFiles.forEach((file) => filesToFilter.add(file)); } else { fileList.forEach((file) => { if (typeof file === "string") { if (isValidRegex(filteredFile) && file.match(filteredFile)) { filesToFilter.add(file); } } else if (Array.isArray(file)) { file.forEach((subFile) => { if (isValidRegex(filteredFile) && subFile.match(filteredFile)) { filesToFilter.add(subFile); } }); } else { log.warn("Unexpected entry in specs that is neither string nor array: ", file); } }); } }); if (filesToFilter.size === 0) { throw new Error(`spec file(s) ${cliArgFileList.join(", ")} not found`); } return [...filesToFilter]; } /** * return configs */ getConfig() { if (!this.#isInitialised) { throw new Error('ConfigParser was not initialized, call "await config.initialize()" first!'); } return this._config; } /** * return capabilities */ getCapabilities(i) { if (!this.#isInitialised) { throw new Error('ConfigParser was not initialized, call "await config.initialize()" first!'); } if (typeof i === "number" && Array.isArray(this._capabilities) && this._capabilities[i]) { return this._capabilities[i]; } return this._capabilities; } /** * returns a flattened list of globbed files * * @param {String[] | String[][]} patterns list of files to glob * @param {Boolean} omitWarnings to indicate omission of warnings * @param {FileSystemPathService} findAndGlob system path service for expanding globbed file names * @param {number} hierarchyDepth depth to prevent recursive calling beyond a depth of 1 * @return {String[] | String[][]} list of files */ static getFilePaths(patterns, rootDir, findAndGlob = new FileSystemPathService(), hierarchyDepth) { let files = []; let groupedFiles = []; if (typeof patterns === "string") { patterns = [patterns]; } if (!Array.isArray(patterns)) { throw new Error("specs or exclude property should be an array of strings, specs may also be an array of string arrays"); } patterns = patterns.map((pattern) => { if (Array.isArray(pattern)) { return pattern.map((subPattern) => removeLineNumbers(subPattern)); } return removeLineNumbers(pattern); }); for (let pattern of patterns) { if (Array.isArray(pattern) && !hierarchyDepth) { groupedFiles = _ConfigParser.getFilePaths(pattern, rootDir, findAndGlob, 1); files.push(groupedFiles); } else if (Array.isArray(pattern) && hierarchyDepth) { log.error("Unexpected depth of hierarchical arrays"); } else if (pattern.startsWith("file://")) { files.push(pattern); } else { pattern = pattern.toString().replace(/\\/g, "/"); let filenames = findAndGlob.glob(pattern, rootDir); filenames = filenames.filter( (filename) => SUPPORTED_FILE_EXTENSIONS.find( (ext) => filename.endsWith(ext) ) ); filenames = filenames.map((filename) => findAndGlob.ensureAbsolutePath(filename, rootDir)); if (filenames.length === 0) { log.warn("pattern", pattern, "did not match any file"); } files = [...files, ...new Set(filenames)]; } } return files; } /** * returns specs files with the excludes filtered * * @param {String[] | String[][]} spec files - list of spec files * @param {string[]} excludeList files - list of exclude files * @return {String[] | String[][]} list of spec files with excludes removed */ filterSpecs(specs, excludeList) { if (allKeywordsContainPath(excludeList)) { const filteredSpec2 = specs.reduce((returnVal, currSpec) => { if (Array.isArray(currSpec)) { returnVal.push(currSpec.filter((specItem) => !excludeList.includes(specItem))); } else if (excludeList.indexOf(currSpec) === -1) { returnVal.push(currSpec); } return returnVal; }, []); return filterEmptyArrayItems(filteredSpec2); } const filteredSpec = specs.reduce((returnVal, currSpec) => { if (Array.isArray(currSpec)) { returnVal.push(currSpec.filter((specItem) => !excludeList.some((excludeVal) => specItem.includes(excludeVal)))); } const isSpecExcluded = excludeList.some((excludedVal) => currSpec.includes(excludedVal)); if (!isSpecExcluded) { returnVal.push(currSpec); } return returnVal; }, []); return filterEmptyArrayItems(filteredSpec); } shard(specs) { if (!this._config.shard || this._config.shard.total === 1) { return specs; } const { total, current } = this._config.shard; const totalSpecs = specs.length; const specsPerShard = Math.max(Math.round(totalSpecs / total), 1); const end = current === total ? void 0 : specsPerShard * current; return specs.slice(current * specsPerShard - specsPerShard, end); } }; function allKeywordsContainPath(excludedSpecList) { return excludedSpecList.every((val) => val.includes("/") || val.includes("\\") || val.includes("*")); } function filterEmptyArrayItems(specList) { return specList.filter((item) => Array.isArray(item) && item.length || !Array.isArray(item)); } function filterDublicationArrayItems(specList) { return [...new Set(specList.map((item) => Array.isArray(item) ? [...new Set(item)] : item))]; } function isValidRegex(expression) { try { new RegExp(expression); return true; } catch { return false; } } export { ConfigParser, FileSystemPathService }; //# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vc3JjL25vZGUvQ29uZmlnUGFyc2VyLnRzIiwgIi4uLy4uL3NyYy9ub2RlL0ZpbGVTeXN0ZW1QYXRoU2VydmljZS50cyIsICIuLi8uLi9zcmMvbm9kZS9SZXF1aXJlTGlicmFyeS50cyIsICIuLi8uLi9zcmMvbm9kZS91dGlscy50cyIsICIuLi8uLi9zcmMvdXRpbHMudHMiLCAiLi4vLi4vc3JjL2NvbnN0YW50cy50cyJdLAogICJzb3VyY2VSb290IjogIi9Vc2Vycy9kdWRrZXZpY2gvam9iL3Byb2plY3RzL2dlbWluaS10ZXN0aW5nL3dlYmRyaXZlcmlvL3BhY2thZ2VzL3dkaW8tY29uZmlnIiwKICAic291cmNlc0NvbnRlbnQiOiBbImltcG9ydCBwYXRoIGZyb20gJ25vZGU6cGF0aCdcblxuaW1wb3J0IGxvZ2dlciBmcm9tICdAdGVzdHBsYW5lL3dkaW8tbG9nZ2VyJ1xuaW1wb3J0IHsgZGVlcG1lcmdlLCBkZWVwbWVyZ2VDdXN0b20gfSBmcm9tICdkZWVwbWVyZ2UtdHMnXG5pbXBvcnQgdHlwZSB7IENhcGFiaWxpdGllcywgT3B0aW9ucywgUmVwb3J0ZXJzLCBTZXJ2aWNlcyB9IGZyb20gJ0B0ZXN0cGxhbmUvd2Rpby10eXBlcydcblxuaW1wb3J0IEZpbGVTeXN0ZW1QYXRoU2VydmljZSBmcm9tICcuL0ZpbGVTeXN0ZW1QYXRoU2VydmljZS5qcydcbmltcG9ydCB7IG1ha2VSZWxhdGl2ZVRvQ1dEIH0gZnJvbSAnLi91dGlscy5qcydcbmltcG9ydCB7IHJlbW92ZUxpbmVOdW1iZXJzLCBpc0N1Y3VtYmVyRmVhdHVyZVdpdGhMaW5lTnVtYmVyLCB2YWxpZE9iamVjdE9yQXJyYXkgfSBmcm9tICcuLi91dGlscy5qcydcbmltcG9ydCB7IFNVUFBPUlRFRF9IT09LUywgU1VQUE9SVEVEX0ZJTEVfRVhURU5TSU9OUywgREVGQVVMVF9DT05GSUdTLCBOT19OQU1FRF9DT05GSUdfRVhQT1JUIH0gZnJvbSAnLi4vY29uc3RhbnRzLmpzJ1xuXG5pbXBvcnQgdHlwZSB7IFBhdGhTZXJ2aWNlIH0gZnJvbSAnLi4vdHlwZXMuanMnXG5cbmNvbnN0IGxvZyA9IGxvZ2dlcignQHRlc3RwbGFuZS93ZGlvLWNvbmZpZzpDb25maWdQYXJzZXInKVxuY29uc3QgTUVSR0VfRFVQTElDQVRJT04gPSBbJ3NlcnZpY2VzJywgJ3JlcG9ydGVycycsICdjYXBhYmlsaXRpZXMnXSBhcyBjb25zdFxuXG50eXBlIEtleVdpdGhNZXJnZUR1cGxpY2F0aW9uID0gKHR5cGVvZiBNRVJHRV9EVVBMSUNBVElPTilbbnVtYmVyXVxudHlwZSBTcGVjID0gc3RyaW5nIHwgc3RyaW5nW11cbnR5cGUgRVNNSW1wb3J0ID0geyBjb25maWc/OiBUZXN0cnVubmVyT3B0aW9uc1dpdGhQYXJhbWV0ZXJzIH1cbnR5cGUgRGVmYXVsdEltcG9ydCA9IHsgZGVmYXVsdD86IHsgY29uZmlnPzogVGVzdHJ1bm5lck9wdGlvbnNXaXRoUGFyYW1ldGVycyB9IH1cbnR5cGUgSW1wb3J0ZWRDb25maWdNb2R1bGUgPSBFU01JbXBvcnQgfCBEZWZhdWx0SW1wb3J0XG5cbmludGVyZmFjZSBUZXN0cnVubmVyT3B0aW9uc1dpdGhQYXJhbWV0ZXJzIGV4dGVuZHMgT3B0aW9ucy5UZXN0cnVubmVyIHtcbiAgICB3YXRjaD86IGJvb2xlYW5cbiAgICBjb3ZlcmFnZT86IGJvb2xlYW5cbiAgICBzcGVjPzogc3RyaW5nW11cbiAgICBzdWl0ZT86IHN0cmluZ1tdXG4gICAgcmVwZWF0PzogbnVtYmVyXG4gICAgY2FwYWJpbGl0aWVzPzogQ2FwYWJpbGl0aWVzLlRlc3RydW5uZXJDYXBhYmlsaXRpZXNcbiAgICByb290RGlyOiBzdHJpbmdcbiAgICB0c0NvbmZpZ1BhdGg/OiBzdHJpbmdcbn1cblxuaW50ZXJmYWNlIE1lcmdlQ29uZmlnIGV4dGVuZHMgT21pdDxQYXJ0aWFsPFRlc3RydW5uZXJPcHRpb25zV2l0aFBhcmFtZXRlcnM+LCAnc3BlY3MnIHwgJ2V4Y2x1ZGUnPiB7XG4gICAgc3BlY3M/OiBTcGVjW11cbiAgICAnd2RpbzpzcGVjcyc/OiBTcGVjW11cbiAgICBleGNsdWRlPzogc3RyaW5nW11cbiAgICAnd2RpbzpleGNsdWRlJz86IHN0cmluZ1tdXG4gICAgZ3JvdXA/OiBib29sZWFuXG59XG5cbmV4cG9ydCBkZWZhdWx0IGNsYXNzIENvbmZpZ1BhcnNlciB7XG4gICAgI2lzSW5pdGlhbGlzZWQgPSBmYWxzZVxuICAgICNjb25maWdGaWxlUGF0aDogc3RyaW5nXG4gICAgcHJpdmF0ZSBfY29uZmlnOiBUZXN0cnVubmVyT3B0aW9uc1dpdGhQYXJhbWV0ZXJzXG4gICAgcHJpdmF0ZSBfY2FwYWJpbGl0aWVzPzogQ2FwYWJpbGl0aWVzLlRlc3RydW5uZXJDYXBhYmlsaXRpZXMgPSBbXVxuXG4gICAgY29uc3RydWN0b3IoXG4gICAgICAgIGNvbmZpZ0ZpbGVQYXRoOiBzdHJpbmcsXG4gICAgICAgIC8qKlxuICAgICAgICAgKiBjb25maWcgb3B0aW9ucyBwYXJzZWQgaW4gdmlhIENMSSBhcmd1bWVudHMgYW5kIGFwcGxpZWQgYmVmb3JlXG4gICAgICAgICAqIHRyeWluZyB0byBjb21waWxlIGNvbmZpZyBmaWxlXG4gICAgICAgICAqL1xuICAgICAgICBwcml2YXRlIF9pbml0aWFsQ29uZmlnOiBQYXJ0aWFsPFRlc3RydW5uZXJPcHRpb25zV2l0aFBhcmFtZXRlcnM+ID0ge30sXG4gICAgICAgIHByaXZhdGUgX3BhdGhTZXJ2aWNlOiBQYXRoU2VydmljZSA9IG5ldyBGaWxlU3lzdGVtUGF0aFNlcnZpY2UoKVxuICAgICkge1xuICAgICAgICB0aGlzLiNjb25maWdGaWxlUGF0aCA9IGNvbmZpZ0ZpbGVQYXRoXG4gICAgICAgIHRoaXMuX2NvbmZpZyA9IE9iamVjdC5hc3NpZ24oXG4gICAgICAgICAgICB7IHJvb3REaXI6IHBhdGguZGlybmFtZShjb25maWdGaWxlUGF0aCkgfSxcbiAgICAgICAgICAgIERFRkFVTFRfQ09ORklHUygpXG4gICAgICAgIClcblxuICAgICAgICAvKipcbiAgICAgICAgICogc3BlY3MgYXBwbGllZCBhcyBDTEkgYXJndW1lbnRzIHNob3VsZCBiZSByZWxhdGl2ZSBmcm9tIENXRFxuICAgICAgICAgKiByYXRoZXIgdGhhbiByZWxhdGl2ZSB0byB0aGUgY29uZmlnIGZpbGVcbiAgICAgICAgICovXG4gICAgICAgIGlmIChfaW5pdGlhbENvbmZpZy5zcGVjKSB7XG4gICAgICAgICAgICBfaW5pdGlhbENvbmZpZy5zcGVjID0gbWFrZVJlbGF0aXZlVG9DV0QoX2luaXRpYWxDb25maWcuc3BlYykgYXMgc3RyaW5nW11cbiAgICAgICAgfVxuXG4gICAgICAgIHRoaXMubWVyZ2UoX2luaXRpYWxDb25maWcsIGZhbHNlKVxuICAgIH1cblxuICAgIC8qKlxuICAgICAqIGluaXRpYWxpemVzIHRoZSBjb25maWcgb2JqZWN0XG4gICAgICovXG4gICAgYXN5bmMgaW5pdGlhbGl6ZShvYmplY3Q6IE1lcmdlQ29uZmlnID0ge30pIHtcbiAgICAgICAgLyoqXG4gICAgICAgICAqIG9ubHkgcnVuIGF1dG8gY29tcGlsZSBmdW5jdGlvbmFsaXR5IG9uY2UgYnV0IGFsbG93IHRoZSBjb25maWcgcGFyc2UgdG8gYmUgaW5pdGlhbGl6ZWRcbiAgICAgICAgICogbXVsdGlwbGUgdGltZXMsIGUuZy4gd2hlbiB1c2VkIHdpdGggdGhlIHBhY2thZ2VzL3dkaW8tY2xpL3NyYy93YXRjaGVyLnRzXG4gICAgICAgICAqL1xuICAgICAgICBpZiAoIXRoaXMuI2lzSW5pdGlhbGlzZWQpIHtcbiAgICAgICAgICAgIGF3YWl0IHRoaXMuYWRkQ29uZmlnRmlsZSh0aGlzLiNjb25maWdGaWxlUGF0aClcbiAgICAgICAgfVxuXG4gICAgICAgIHRoaXMubWVyZ2UoeyAuLi5vYmplY3QgfSlcblxuICAgICAgICAvKipcbiAgICAgICAgICogZW5hYmxlL2Rpc2FibGUgY292ZXJhZ2UgcmVwb3J0aW5nXG4gICAgICAgICAqL1xuICAgICAgICBpZiAoT2JqZWN0LmtleXModGhpcy5faW5pdGlhbENvbmZpZyB8fCB7fSkuaW5jbHVkZXMoJ2NvdmVyYWdlJykpIHtcbiAgICAgICAgICAgIGlmICh0aGlzLl9jb25maWcucnVubmVyID09PSAnYnJvd3NlcicpIHtcbiAgICAgICAgICAgICAgICB0aGlzLl9jb25maWcucnVubmVyID0gWydicm93c2VyJywge1xuICAgICAgICAgICAgICAgICAgICBjb3ZlcmFnZTogeyBlbmFibGVkOiB0aGlzLl9pbml0aWFsQ29uZmlnLmNvdmVyYWdlIH1cbiAgICAgICAgICAgICAgICB9XVxuICAgICAgICAgICAgfSBlbHNlIGlmIChBcnJheS5pc0FycmF5KHRoaXMuX2NvbmZpZy5ydW5uZXIpICYmIHRoaXMuX2NvbmZpZy5ydW5uZXJbMF0gPT09ICdicm93c2VyJykge1xuICAgICAgICAgICAgICAgIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBAdHlwZXNjcmlwdC1lc2xpbnQvbm8tZXhwbGljaXQtYW55XG4gICAgICAgICAgICAgICAgKHRoaXMuX2NvbmZpZy5ydW5uZXJbMV0gYXMgYW55KS5jb3ZlcmFnZSA9IHtcbiAgICAgICAgICAgICAgICAgICAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIEB0eXBlc2NyaXB0LWVzbGludC9uby1leHBsaWNpdC1hbnlcbiAgICAgICAgICAgICAgICAgICAgLi4uKHRoaXMuX2NvbmZpZy5ydW5uZXJbMV0gYXMgYW55KS5jb3ZlcmFnZSxcbiAgICAgICAgICAgICAgICAgICAgZW5hYmxlZDogdGhpcy5faW5pdGlhbENvbmZpZy5jb3ZlcmFnZVxuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIHRoaXMuI2lzSW5pdGlhbGlzZWQgPSB0cnVlXG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogbWVyZ2VzIGNvbmZpZyBmaWxlIHdpdGggZGVmYXVsdCB2YWx1ZXNcbiAgICAgKiBAcGFyYW0ge3N0cmluZ30gZmlsZW5hbWUgcGF0aCBvZiBmaWxlIHJlbGF0aXZlIHRvIGN1cnJlbnQgZGlyZWN0b3J5XG4gICAgICovXG4gICAgcHJpdmF0ZSBhc3luYyBhZGRDb25maWdGaWxlKGZpbGVuYW1lOiBzdHJpbmcpIHtcbiAgICAgICAgaWYgKHR5cGVvZiBmaWxlbmFtZSAhPT0gJ3N0cmluZycpIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcignYWRkQ29uZmlnRmlsZSByZXF1aXJlcyBmaWxlcGF0aCcpXG4gICAgICAgIH1cblxuICAgICAgICAvKipcbiAgICAgICAgICogcmVzb2x2ZSBjb25maWcgZmlsZSBwYXRoIGFsd2F5cyByZWxhdGl2ZSB0byB3b3JraW5nIGRpcmVjdG9yeVxuICAgICAgICAgKi9cbiAgICAgICAgY29uc3QgZmlsZVBhdGggPSB0aGlzLl9wYXRoU2VydmljZS5lbnN1cmVBYnNvbHV0ZVBhdGgoZmlsZW5hbWUsIHByb2Nlc3MuY3dkKCkpXG5cbiAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgIC8qKlxuICAgICAgICAgICAgICogQ2hlY2sgaWYgZGlyZWN0IGV4cG9ydHMgZ290IGFzc2lnbmVkIGFzIGRlZmF1bHQgZXhwb3J0cyBhbmQgaWYgc29cbiAgICAgICAgICAgICAqIGJlIG1vcmUgZmxleGlibGUgYW5kIHBpY2sgYWxsb3cgZm9yIHRoZXNlIGFzIHdlbGwuXG4gICAgICAgICAgICAgKi9cbiAgICAgICAgICAgIGNvbnN0IGltcG9ydGVkTW9kdWxlID0gYXdhaXQgdGhpcy5fcGF0aFNlcnZpY2UubG9hZEZpbGU8SW1wb3J0ZWRDb25maWdNb2R1bGU+KGZpbGVQYXRoKVxuICAgICAgICAgICAgY29uc3QgY29uZmlnID0gKGltcG9ydGVkTW9kdWxlIGFzIEVTTUltcG9ydCkuY29uZmlnIHx8IChpbXBvcnRlZE1vZHVsZSBhcyBEZWZhdWx0SW1wb3J0KS5kZWZhdWx0Py5jb25maWdcbiAgICAgICAgICAgIGlmICh0eXBlb2YgY29uZmlnICE9PSAnb2JqZWN0Jykge1xuICAgICAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcihOT19OQU1FRF9DT05GSUdfRVhQT1JUKVxuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBjb25zdCBjb25maWdGaWxlQ2FwYWJpbGl0aWVzID0gY29uZmlnLmNhcGFiaWxpdGllc1xuICAgICAgICAgICAgaWYgKCFjb25maWdGaWxlQ2FwYWJpbGl0aWVzKSB7XG4gICAgICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKGBObyBcXGBjYXBhYmlsaXRpZXNcXGAgcHJvcGVydHkgZm91bmQgaW4gV2ViZHJpdmVySU8uQ29uZmlnIGRlZmluZWQgaW4gZmlsZTogJHtmaWxlUGF0aH1gKVxuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAvKipcbiAgICAgICAgICAgICAqIGNsb25lIHRoZSBvcmlnaW5hbCBjb25maWdcbiAgICAgICAgICAgICAqL1xuICAgICAgICAgICAgY29uc3QgZmlsZUNvbmZpZyA9IE9iamVjdC5hc3NpZ24oe30sIGNvbmZpZylcblxuICAgICAgICAgICAgLyoqXG4gICAgICAgICAgICAgKiBtZXJnZSBjYXBhYmlsaXRpZXNcbiAgICAgICAgICAgICAqL1xuICAgICAgICAgICAgY29uc3QgZGVmYXVsdFRvID0gKEFycmF5LmlzQXJyYXkodGhpcy5fY2FwYWJpbGl0aWVzKSA/IFtdIDoge30pIGFzIENhcGFiaWxpdGllcy5UZXN0cnVubmVyQ2FwYWJpbGl0aWVzXG4gICAgICAgICAgICB0aGlzLl9jYXBhYmlsaXRpZXMgPSBkZWVwbWVyZ2UodGhpcy5fY2FwYWJpbGl0aWVzLCBmaWxlQ29uZmlnLmNhcGFiaWxpdGllcyB8fCBkZWZhdWx0VG8pXG4gICAgICAgICAgICBkZWxldGUgZmlsZUNvbmZpZy5jYXBhYmlsaXRpZXNcblxuICAgICAgICAgICAgLyoqXG4gICAgICAgICAgICAgKiBBZGQgaG9va3MgZnJvbSB0aGUgZmlsZSBjb25maWcgYW5kIHJlbW92ZSB0aGVtIGZyb20gZmlsZSBjb25maWcgb2JqZWN0IHRvIGF2b2lkXG4gICAgICAgICAgICAgKiBjb21wbGljYXRpb25zIHdoZW4gdXNpbmcgbWVyZ2UgZnVuY3Rpb25cbiAgICAgICAgICAgICAqL1xuICAgICAgICAgICAgdGhpcy5hZGRTZXJ2aWNlKGZpbGVDb25maWcpXG4gICAgICAgICAgICBmb3IgKGNvbnN0IGhvb2tOYW1lIG9mIFNVUFBPUlRFRF9IT09LUykge1xuICAgICAgICAgICAgICAgIGRlbGV0ZSBmaWxlQ29uZmlnW2hvb2tOYW1lXVxuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICB0aGlzLl9jb25maWcgPSBkZWVwbWVyZ2UodGhpcy5fY29uZmlnLCBmaWxlQ29uZmlnKVxuXG4gICAgICAgICAgICAvKipcbiAgICAgICAgICAgICAqIHJlbW92ZSBgd2F0Y2hgIGZyb20gY29uZmlnIGFzIGZhciBhcyBpdCBjYW4gYmUgb25seSBwYXNzZWQgYXMgY29tbWFuZCBsaW5lIGFyZ3VtZW50XG4gICAgICAgICAgICAgKi9cbiAgICAgICAgICAgIGRlbGV0ZSB0aGlzLl9jb25maWcud2F0Y2hcbiAgICAgICAgfSBjYXRjaCAoZTogdW5rbm93bikge1xuICAgICAgICAgICAgbG9nLmVycm9yKGBGYWlsZWQgbG9hZGluZyBjb25maWd1cmF0aW9uIGZpbGU6ICR7ZmlsZVBhdGh9OmAsIChlIGFzIEVycm9yKS5tZXNzYWdlKVxuICAgICAgICAgICAgdGhyb3cgZVxuICAgICAgICB9XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogbWVyZ2UgZXh0ZXJuYWwgb2JqZWN0IHdpdGggY29uZmlnIG9iamVjdFxuICAgICAqIEBwYXJhbSAge09iamVjdH0gb2JqZWN0ICBkZXNpcmVkIG9iamVjdCB0byBtZXJnZSBpbnRvIHRoZSBjb25maWcgb2JqZWN0XG4gICAgICogQHBhcmFtIHtib29sZWFufSBbYWRkUGF0aFRvU3BlY3M9dHJ1ZV0gdGhpcyBmbGFnIGRldGVybWluZXMgd2hldGhlciBpdCBpcyBuZWNlc3NhcnkgdG8gZmluZCBwYXRocyB0byBzcGVjcyBpZiB0aGUgLS1zcGVjIHBhcmFtZXRlciB3YXMgcGFzc2VkIGluIENMSVxuICAgICAqL1xuICAgIHByaXZhdGUgbWVyZ2Uob2JqZWN0OiBNZXJnZUNvbmZpZyA9IHt9LCBhZGRQYXRoVG9TcGVjcyA9IHRydWUpIHtcbiAgICAgICAgY29uc3Qgc3BlYyA9IEFycmF5LmlzQXJyYXkob2JqZWN0LnNwZWMpID8gb2JqZWN0LnNwZWMgOiBbXVxuICAgICAgICBjb25zdCBleGNsdWRlID0gQXJyYXkuaXNBcnJheShvYmplY3QuZXhjbHVkZSkgPyBvYmplY3QuZXhjbHVkZSA6IFtdXG5cbiAgICAgICAgLyoqXG4gICAgICAgICAqIEFkZCBkZWVwbWVyZ2VDdXN0b20gdG8gcmVtb3ZlIGFycmF5KCdzZXJ2aWNlcycsICdyZXBvcnRlcnMnLCAnY2FwYWJpbGl0aWVzJykgZHVwbGljYXRpb24gaW4gdGhlIGNvbmZpZyBvYmplY3RcbiAgICAgICAgICovXG4gICAgICAgIGNvbnN0IGN1c3RvbURlZXBNZXJnZSA9IGRlZXBtZXJnZUN1c3RvbSh7XG4gICAgICAgICAgICBtZXJnZUFycmF5czogKFtvbGRWYWx1ZSwgbmV3VmFsdWVdLCB1dGlscywgbWV0YSkgPT4ge1xuICAgICAgICAgICAgICAgIGNvbnN0IGtleSA9IG1ldGE/LmtleSBhcyBLZXlXaXRoTWVyZ2VEdXBsaWNhdGlvblxuICAgICAgICAgICAgICAgIGlmIChtZXRhICYmIE1FUkdFX0RVUExJQ0FUSU9OLmluY2x1ZGVzKGtleSkpIHtcbiAgICAgICAgICAgICAgICAgICAgY29uc3Qgb3JpZ1dpdGhvdXRPYmplY3RFbnRyaWVzID0gb2xkVmFsdWUuZmlsdGVyKCh2YWx1ZTogW1NlcnZpY2VzLlNlcnZpY2VDbGFzcywgV2ViZHJpdmVySU8uU2VydmljZU9wdGlvbl0gfCBbUmVwb3J0ZXJzLlJlcG9ydGVyQ2xhc3MsIFdlYmRyaXZlcklPLlJlcG9ydGVyT3B0aW9uXSkgPT4gdHlwZW9mIHZhbHVlICE9PSAnb2JqZWN0JylcbiAgICAgICAgICAgICAgICAgICAgcmV0dXJuIEFycmF5LmZyb20obmV3IFNldChkZWVwbWVyZ2UobmV3VmFsdWUsIG9yaWdXaXRob3V0T2JqZWN0RW50cmllcykpKVxuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICByZXR1cm4gdXRpbHMuYWN0aW9ucy5kZWZhdWx0TWVyZ2VcbiAgICAgICAgICAgIH1cbiAgICAgICAgfSlcbiAgICAgICAgdGhpcy5fY29uZmlnID0gY3VzdG9tRGVlcE1lcmdlKHRoaXMuX2NvbmZpZywgb2JqZWN0KSBhcyBUZXN0cnVubmVyT3B0aW9uc1dpdGhQYXJhbWV0ZXJzXG4gICAgICAgIC8qKlxuICAgICAgICAgKiBvdmVyd3JpdGUgY29uZmlnIHNwZWNzIHRoYXQgZ290IHBpcGVkIGludG8gdGhlIHdkaW8gY29tbWFuZCxcbiAgICAgICAgICogYWxzbyBhZGhlcmluZyB0byB0aGUgd2Rpby1wcmVmaXhlcyBmcm9tIGEgY2FwYWJpbGl0eVxuICAgICAgICAgKi9cbiAgICAgICAgaWYgKG9iamVjdFsnd2RpbzpzcGVjcyddICYmIG9iamVjdFsnd2RpbzpzcGVjcyddLmxlbmd0aCA+IDApIHtcbiAgICAgICAgICAgIHRoaXMuX2NvbmZpZy5zcGVjcyA9IG9iamVjdFsnd2RpbzpzcGVjcyddIGFzIFNwZWNbXVxuICAgICAgICB9IGVsc2UgaWYgKG9iamVjdC5zcGVjcyAmJiBvYmplY3Quc3BlY3MubGVuZ3RoID4gMCkge1xuICAgICAgICAgICAgdGhpcy5fY29uZmlnLnNwZWNzID0gb2JqZWN0LnNwZWNzIGFzIHN0cmluZ1tdXG4gICAgICAgIH1cbiAgICAgICAgaWYgKG9iamVjdFsnd2RpbzpleGNsdWRlJ10gJiYgb2JqZWN0Wyd3ZGlvOmV4Y2x1ZGUnXS5sZW5ndGggPiAwKSB7XG4gICAgICAgICAgICB0aGlzLl9jb25maWcuZXhjbHVkZSA9IG9iamVjdFsnd2RpbzpleGNsdWRlJ10gYXMgc3RyaW5nW11cbiAgICAgICAgfSBlbHNlIGlmIChvYmplY3QuZXhjbHVkZSAmJiBvYmplY3QuZXhjbHVkZS5sZW5ndGggPiAwKSB7XG4gICAgICAgICAgICB0aGlzLl9jb25maWcuZXhjbHVkZSA9IG9iamVjdC5leGNsdWRlIGFzIHN0cmluZ1tdXG4gICAgICAgIH1cblxuICAgICAgICAvKipcbiAgICAgICAgICogY2xlYW51cCBkdXBsaWNhdGVkIFwic3VpdGVcIiBpZiB0aGUgc2FtZSB2YWx1ZSB3YXMgcHJvdmlkZWRcbiAgICAgICAgICovXG4gICAgICAgIGlmIChvYmplY3Quc3VpdGUgJiYgb2JqZWN0LnN1aXRlLmxlbmd0aCA+IDApIHtcbiAgICAgICAgICAgIHRoaXMuX2NvbmZpZy5zdWl0ZSA9IHRoaXMuX2NvbmZpZy5zdWl0ZT8uZmlsdGVyKChzdWl0ZSwgaWR4LCBzdWl0ZXMpID0+IHN1aXRlcy5pbmRleE9mKHN1aXRlKSA9PT0gaWR4KVxuICAgICAgICB9XG5cbiAgICAgICAgLyoqXG4gICAgICAgICAqIG92ZXJ3cml0ZSBjYXBhYmlsaXRpZXNcbiAgICAgICAgICovXG4gICAgICAgIHRoaXMuX2NhcGFiaWxpdGllcyA9IHZhbGlkT2JqZWN0T3JBcnJheSh0aGlzLl9jb25maWcuY2FwYWJpbGl0aWVzIGFzIG9iamVjdCkgPyB0aGlzLl9jb25maWcuY2FwYWJpbGl0aWVzIDogdGhpcy5fY2FwYWJpbGl0aWVzXG5cbiAgICAgICAgLyoqXG4gICAgICAgICAqIHNhdmUgb3JpZ2luYWwgc3BlY3MgaWYgQ3VjdW1iZXIncyBmZWF0dXJlIGxpbmUgbnVtYmVyIGlzIHByb3ZpZGVkXG4gICAgICAgICAqL1xuICAgICAgICBpZiAodGhpcy5fY29uZmlnLnNwZWMgJiYgaXNDdWN1bWJlckZlYXR1cmVXaXRoTGluZU51bWJlcih0aGlzLl9jb25maWcuc3BlYykpIHtcbiAgICAgICAgICAgIC8qKlxuICAgICAgICAgICAgICogYHRoaXMuX2NvbmZpZy5zcGVjYCBpcyBzdHJpbmcgaW5zdGVhZCBvZiBBcnJheSBpbiB3YXRjaCBtb2RlXG4gICAgICAgICAgICAgKi9cbiAgICAgICAgICAgIHRoaXMuX2NvbmZpZy5jdWN1bWJlckZlYXR1cmVzV2l0aExpbmVOdW1iZXJzID0gQXJyYXkuaXNBcnJheSh0aGlzLl9jb25maWcuc3BlYykgPyBbLi4ubmV3IFNldCh0aGlzLl9jb25maWcuc3BlYyldIDogW3RoaXMuX2NvbmZpZy5zcGVjXVxuICAgICAgICB9XG5cbiAgICAgICAgLyoqXG4gICAgICAgICAqIHJ1biBzaW5nbGUgc3BlYyBmaWxlIG9ubHksIHJlZ2FyZGxlc3Mgb2YgbXVsdGlwbGUtc3BlYyBzcGVjaWZpY2F0aW9uXG4gICAgICAgICAqL1xuICAgICAgICBpZiAoYWRkUGF0aFRvU3BlY3MgJiYgc3BlYy5sZW5ndGggPiAwKSB7XG4gICAgICAgICAgICB0aGlzLl9jb25maWcuc3BlY3MgPSB0aGlzLnNldEZpbGVQYXRoVG9GaWx0ZXJPcHRpb25zKHNwZWMsIHRoaXMuX2NvbmZpZy5zcGVjcyEsIG9iamVjdC5ncm91cClcbiAgICAgICAgfVxuICAgICAgICAvKipcbiAgICAgICAgICogQXQgdGhpcyBzdGVwIGZ1bmN0aW9uIGFsbEtleXdvcmRzQ29udGFpblBhdGgoKSBhbGxvd3MgdXMgdG8gbWFrZSBzdXJlXG4gICAgICAgICAqIHRoYXQgYWxsIGFyZ3VtZW50cywgcGFzc2VkIHRvICctLWV4Y2x1ZGUnIHBhcmFtLCBhcmUgcGF0aHMgdG8gc3BlY3MuXG4gICAgICAgICAqIFNvIHRoZXkgY2FuIGJlIHByb2Nlc3NlZCBpbiBzZXRGaWxlUGF0aFRvRmlsdGVyT3B0aW9ucygpXG4gICAgICAgICAqIE90aGVyd2lzZSwgdGhlIGFwcGxpY2F0aW9uIGNyYXNoZXMgd2l0aCBhbiBlcnJvci5cbiAgICAgICAgICogVGhlcmVmb3JlLCBpZiAtLWV4Y2x1ZGUgY29udGFpbnMgbm90IHBhdGhzLCBidXQga2V5d29yZHMsIGUuZy4gJ2RpYWxvZycsICd0ZXN0LmNvbXBvbmVudCcgZXRjLixcbiAgICAgICAgICogdGhlbiBmaWx0ZXJpbmcgb2YgZXhjbHVkZWQgc3BlY3Mgb2NjdXJzIGluIHRoZSBmaWx0ZXJTcGVjcygpIG1ldGhvZFxuICAgICAgICAgKi9cbiAgICAgICAgaWYgKGV4Y2x1ZGUubGVuZ3RoID4gMCAmJiBhbGxLZXl3b3Jkc0NvbnRhaW5QYXRoKGV4Y2x1ZGUpKSB7XG4gICAgICAgICAgICB0aGlzLl9jb25maWcuZXhjbHVkZSA9IHRoaXMuc2V0RmlsZVBhdGhUb0ZpbHRlck9wdGlvbnMoZXhjbHVkZSwgdGhpcy5fY29uZmlnLmV4Y2x1ZGUhKVxuICAgICAgICB9IGVsc2UgaWYgKGV4Y2x1ZGUubGVuZ3RoID4gMCkge1xuICAgICAgICAgICAgdGhpcy5fY29uZmlnLmV4Y2x1ZGUgPSBleGNsdWRlXG4gICAgICAgIH1cbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBBZGQgaG9va3MgZnJvbSBhbiBleGlzdGluZyBzZXJ2aWNlIHRvIHRoZSBydW5uZXIgY29uZmlnLlxuICAgICAqIEBwYXJhbSB7b2JqZWN0fSBzZXJ2aWNlIC0gYW4gb2JqZWN0IHRoYXQgY29udGFpbnMgaG9vayBtZXRob2RzLlxuICAgICAqL1xuICAgIGFkZFNlcnZpY2Uoc2VydmljZTogU2VydmljZXMuSG9va3MpIHtcbiAgICAgICAgY29uc3QgYWRkSG9vayA9IChob29rTmFtZTogc3RyaW5nLCBob29rOiBGdW5jdGlvbikgPT4ge1xuICAgICAgICAgICAgLy8gQHRzLWlnbm9yZSBFeHByZXNzaW9uIHByb2R1Y2VzIGEgdW5pb24gdHlwZSB0aGF0IGlzIHRvbyBjb21wbGV4IHRvIHJlcHJlc2VudFxuICAgICAgICAgICAgY29uc3QgZXhpc3RpbmdIb29rcyA9IHRoaXMuX2NvbmZpZ1tob29rTmFtZV1cbiAgICAgICAgICAgIGlmICghZXhpc3RpbmdIb29rcykge1xuICAgICAgICAgICAgICAgIC8vIEB0cy1pZ25vcmUgRXhwcmVzc2lvbiBwcm9kdWNlcyBhIHVuaW9uIHR5cGUgdGhhdCBpcyB0b28gY29tcGxleCB0byByZXByZXNlbnRcbiAgICAgICAgICAgICAgICB0aGlzLl9jb25maWdbaG9va05hbWVdID0gaG9vay5iaW5kKHNlcnZpY2UpXG4gICAgICAgICAgICB9IGVsc2UgaWYgKHR5cGVvZiBleGlzdGluZ0hvb2tzID09PSAnZnVuY3Rpb24nKSB7XG4gICAgICAgICAgICAgICAgLy8gQHRzLWlnbm9yZSBFeHByZXNzaW9uIHByb2R1Y2VzIGEgdW5pb24gdHlwZSB0aGF0IGlzIHRvbyBjb21wbGV4IHRvIHJlcHJlc2VudFxuICAgICAgICAgICAgICAgIHRoaXMuX2NvbmZpZ1tob29rTmFtZV0gPSBbZXhpc3RpbmdIb29rcywgaG9vay5iaW5kKHNlcnZpY2UpXVxuICAgICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICAgICAvLyBAdHMtaWdub3JlIEV4cHJlc3Npb24gcHJvZHVjZXMgYSB1bmlvbiB0eXBlIHRoYXQgaXMgdG9vIGNvbXBsZXggdG8gcmVwcmVzZW50XG4gICAgICAgICAgICAgICAgdGhpcy5fY29uZmlnW2hvb2tOYW1lXSA9IFsuLi5leGlzdGluZ0hvb2tzLCBob29rLmJpbmQoc2VydmljZSldXG4gICAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICBmb3IgKGNvbnN0IGhvb2tOYW1lIG9mIFNVUFBPUlRFRF9IT09LUykge1xuICAgICAgICAgICAgY29uc3QgaG9va3NUb0JlQWRkZWQgPSBzZXJ2aWNlW2hvb2tOYW1lXVxuICAgICAgICAgICAgaWYgKCFob29rc1RvQmVBZGRlZCkge1xuICAgICAgICAgICAgICAgIGNvbnRpbnVlXG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGlmICh0eXBlb2YgaG9va3NUb0JlQWRkZWQgPT09ICdmdW5jdGlvbicpIHtcbiAgICAgICAgICAgICAgICBhZGRIb29rKGhvb2tOYW1lLCBob29rc1RvQmVBZGRlZClcbiAgICAgICAgICAgIH0gZWxzZSBpZiAoQXJyYXkuaXNBcnJheShob29rc1RvQmVBZGRlZCkpIHtcbiAgICAgICAgICAgICAgICBmb3IgKGNvbnN0IGhvb2tUb0FkZCBvZiBob29rc1RvQmVBZGRlZCkge1xuICAgICAgICAgICAgICAgICAgICBpZiAodHlwZW9mIGhvb2tUb0FkZCA9PT0gJ2Z1bmN0aW9uJykge1xuICAgICAgICAgICAgICAgICAgICAgICAgYWRkSG9vayhob29rTmFtZSwgaG9va1RvQWRkKVxuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogZGV0ZXJtaW5lIHdoYXQgc3BlY3MgdG8gcnVuIGJhc2VkIG9uIHRoZSBzcGVjKHMpLCBzdWl0ZShzKSwgZXhjbHVkZVxuICAgICAqIGF0dHJpYnV0ZXMgZnJvbSBDTEksIGNvbmZpZyBhbmQgY2FwYWJpbGl0aWVzXG4gICAgICovXG4gICAgZ2V0U3BlY3MoY2FwU3BlY3M/OiBTcGVjW10sIGNhcEV4Y2x1ZGU/OiBTcGVjW10pIHtcbiAgICAgICAgY29uc3QgaXNTcGVjUGFyYW1QYXNzZWQgPSBBcnJheS5pc0FycmF5KHRoaXMuX2NvbmZpZy5zcGVjKSAmJiB0aGlzLl9jb25maWcuc3BlYy5sZW5ndGggPiAwXG4gICAgICAgIGNvbnN0IHJlcGVhdCA9IHRoaXMuX2NvbmZpZy5yZXBlYXRcbiAgICAgICAgLy8gd2hlbiBDTEkgLS1zcGVjIGlzIGV4cGxpY2l0bHkgc3BlY2lmaWVkLCB0aGlzLl9jb25maWcuc3BlY3MgY29udGFpbnMgdGhlIGZpbHRlcmVkXG4gICAgICAgIC8vIHNwZWNzIG1hdGNoaW5nIHRoZSBwYXNzZWQgcGF0dGVybiBlbHNlIHRoZSBzcGVjcyBkZWZpbmVkIGluc2lkZSB0aGUgY29uZmlnIGFyZSByZXR1cm5lZFxuICAgICAgICBsZXQgc3BlY3MgPSBDb25maWdQYXJzZXIuZ2V0RmlsZVBhdGhzKHRoaXMuX2NvbmZpZy5zcGVjcyEsIHRoaXMuX2NvbmZpZy5yb290RGlyLCB0aGlzLl9wYXRoU2VydmljZSlcbiAgICAgICAgbGV0IGV4Y2x1ZGUgPSBhbGxLZXl3b3Jkc0NvbnRhaW5QYXRoKHRoaXMuX2NvbmZpZy5leGNsdWRlISlcbiAgICAgICAgICAgID8gQ29uZmlnUGFyc2VyLmdldEZpbGVQYXRocyh0aGlzLl9jb25maWcuZXhjbHVkZSEsIHRoaXMuX2NvbmZpZy5yb290RGlyLCB0aGlzLl9wYXRoU2VydmljZSlcbiAgICAgICAgICAgIDogdGhpcy5fY29uZmlnLmV4Y2x1ZGUgfHwgW11cbiAgICAgICAgY29uc3Qgc3VpdGVzID0gQXJyYXkuaXNBcnJheSh0aGlzLl9jb25maWcuc3VpdGUpID8gdGhpcy5fY29uZmlnLnN1aXRlIDogW11cblxuICAgICAgICAvLyBvbmx5IHVzZSBjYXBhYmlsaXR5IGV4Y2x1ZGVzIGlmIChDTEkpIC0tZXhjbHVkZSBvciBjb25maWcgZXhjbHVkZSBhcmUgbm90IGRlZmluZWRcbiAgICAgICAgaWYgKEFycmF5LmlzQXJyYXkoY2FwRXhjbHVkZSkgJiYgZXhjbHVkZS5sZW5ndGggPT09IDApIHtcbiAgICAgICAgICAgIGV4Y2x1ZGUgPSBDb25maWdQYXJzZXIuZ2V0RmlsZVBhdGhzKGNhcEV4Y2x1ZGUsIHRoaXMuX2NvbmZpZy5yb290RGlyLCB0aGlzLl9wYXRoU2VydmljZSlcbiAgICAgICAgfVxuXG4gICAgICAgIC8vIG9ubHkgdXNlIGNhcGFiaWxpdHkgc3BlY3MgaWYgKENMSSkgLS1zcGVjIGlzIG5vdCBkZWZpbmVkXG4gICAgICAgIGlmICghaXNTcGVjUGFyYW1QYXNzZWQgJiYgQXJyYXkuaXNBcnJheShjYXBTcGVjcykpIHtcbiAgICAgICAgICAgIHNwZWNzID0gQ29uZmlnUGFyc2VyLmdldEZpbGVQYXRocyhjYXBTcGVjcywgdGhpcy5fY29uZmlnLnJvb3REaXIsIHRoaXMuX3BhdGhTZXJ2aWNlKVxuICAgICAgICB9XG5cbiAgICAgICAgLy8gaGFuZGxlIGNhc2Ugd2hlcmUgdXNlciBwYXNzZXMgLS1zdWl0ZSB2aWEgQ0xJXG4gICAgICAgIGlmIChzdWl0ZXMubGVuZ3RoID4gMCkge1xuICAgICAgICAgICAgbGV0IHN1aXRlU3BlY3M6IFNwZWNbXSA9IFtdXG4gICAgICAgICAgICBmb3IgKGNvbnN0IHN1aXRlTmFtZSBvZiBzdWl0ZXMpIHtcbiAgICAgICAgICAgICAgICBjb25zdCBzdWl0ZSA9IHRoaXMuX2NvbmZpZy5zdWl0ZXM/LltzdWl0ZU5hbWVdXG4gICAgICAgICAgICAgICAgaWYgKCFzdWl0ZSkge1xuICAgICAgICAgICAgICAgICAgICBsb2cud2FybihgTm8gc3VpdGUgd2FzIGZvdW5kIHdpdGggbmFtZSBcIiR7c3VpdGVOYW1lfVwiYClcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgaWYgKEFycmF5LmlzQXJyYXkoc3VpdGUpKSB7XG4gICAgICAgICAgICAgICAgICAgIHN1aXRlU3BlY3MgPSBzdWl0ZVNwZWNzLmNvbmNhdChDb25maWdQYXJzZXIuZ2V0RmlsZVBhdGhzKHN1aXRlLCB0aGlzLl9jb25maWcucm9vdERpciwgdGhpcy5fcGF0aFNlcnZpY2UpKVxuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgaWYgKHN1aXRlU3BlY3MubGVuZ3RoID09PSAwKSB7XG4gICAgICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKGBUaGUgc3VpdGUocykgXCIke3N1aXRlcy5qb2luKCdcIiwgXCInKX1cIiB5b3Ugc3BlY2lmaWVkIGRvbid0IGV4aXN0IGAgK1xuICAgICAgICAgICAgICAgICAgICAnaW4geW91ciBjb25maWcgZmlsZSBvciBkb2VzblxcJ3QgY29udGFpbiBhbnkgZmlsZXMhJylcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgLy8gQWxsb3cgLS1zdWl0ZSBhbmQgLS1zcGVjIHRvIGJvdGggYmUgZGVmaW5lZCBvbiB0aGUgY29tbWFuZCBsaW5lXG4gICAgICAgICAgICBzcGVjcyA9IGlzU3BlY1BhcmFtUGFzc2VkID8gWy4uLnNwZWNzLCAuLi5zdWl0ZVNwZWNzXSA6IHN1aXRlU3BlY3NcbiAgICAgICAgfVxuXG4gICAgICAgIC8vIFJlbW92ZSBhbnkgZHVwbGljYXRlIHRlc3RzIGZyb20gdGhlIGZpbmFsIHNwZWNzIGFycmF5XG4gICAgICAgIHNwZWNzID0gZmlsdGVyRHVibGljYXRpb25BcnJheUl0ZW1zKHNwZWNzKVxuXG4gICAgICAgIC8vIElmIHRoZSAtLXJlcGVhdCBmbGFnIGlzIHNldCwgZHVwbGljYXRlIHRoZSBzcGVjcyBhcnJheSBOIHRpbWVzXG4gICAgICAgIC8vIEVuc3VyZSB0aGF0IHdoZW4gLS1yZXBlYXQgaXMgdXNlZCB0aGF0IGVpdGhlciAtLXNwZWMgb3IgLS1zdWl0ZSBpcyBhbHNvIHVzZWRcbiAgICAgICAgY29uc3QgaGFzU3Vic2V0T2ZTcGVjc0RlZmluZWQgPSBpc1NwZWNQYXJhbVBhc3NlZCB8fCBzdWl0ZXMubGVuZ3RoID4gMFxuICAgICAgICBpZiAocmVwZWF0ICYmIGhhc1N1YnNldE9mU3BlY3NEZWZpbmVkKSB7XG4gICAgICAgICAgICBzcGVjcyA9IEFycmF5LmZyb20oeyBsZW5ndGg6IHJlcGVhdCB9LCAoKSA9PiBzcGVjcykuZmxhdCgpXG4gICAgICAgIH0gZWxzZSBpZiAocmVwZWF0ICYmICFoYXNTdWJzZXRPZlNwZWNzRGVmaW5lZCkge1xuICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKCdUaGUgLS1yZXBlYXQgZmxhZyByZXF1aXJlcyB0aGF0IGVpdGhlciB0aGUgLS1zcGVjIG9yIC0tc3VpdGUgZmxhZyBpcyBhbHNvIHNldCcpXG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gdGhpcy5zaGFyZChcbiAgICAgICAgICAgIHRoaXMuZmlsdGVyU3BlY3Moc3BlY3MsIDxzdHJpbmdbXT5leGNsdWRlKVxuICAgICAgICApXG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogc2V0cyBjb25maWcgYXR0cmlidXRlIHdpdGggZmlsZSBwYXRocyBmcm9tIGZpbHRlcmluZ1xuICAgICAqIG9wdGlvbnMgZnJvbSBjbGkgYXJndW1lbnRcbiAgICAgKlxuICAgICAqIEBwYXJhbSAge3N0cmluZ1tdfSBjbGlBcmdGaWxlTGlzdCAgbGlzdCBvZiBmaWxlcyBpbiBhIHN0cmluZyBmb3JtXG4gICAgICogQHBhcmFtICB7T2JqZWN0fSBjb25maWcgIGNvbmZpZyBvYmplY3QgdGhhdCBzdG9yZXMgdGhlIHNwZWMgYW5kIGV4Y2x1ZGUgYXR0cmlidXRlc1xuICAgICAqIGNsaSBhcmd1bWVudFxuICAgICAqIEByZXR1cm4ge1N0cmluZ1tdfSBMaXN0IG9mIGZpbGVzIHRoYXQgc2hvdWxkIGJlIGluY2x1ZGVkIG9yIGV4Y2x1ZGVkXG4gICAgICovXG4gICAgc2V0RmlsZVBhdGhUb0ZpbHRlck9wdGlvbnMoY2xpQXJnRmlsZUxpc3Q6IHN0cmluZ1tdLCBzcGVjczogU3BlY1tdLCBncm91cD86IGJvb2xlYW4pIHtcbiAgICAgICAgY29uc3QgZmlsZXNUb0ZpbHRlciA9IG5ldyBTZXQ8c3RyaW5nPigpXG4gICAgICAgIGNvbnN0IGZpbGVMaXN0ID0gQ29uZmlnUGFyc2VyLmdldEZpbGVQYXRocyhzcGVjcywgdGhpcy5fY29uZmlnLnJvb3REaXIsIHRoaXMuX3BhdGhTZXJ2aWNlKVxuICAgICAgICBjbGlBcmdGaWxlTGlzdC5mb3JFYWNoKGZpbHRlcmVkRmlsZSA9PiB7XG4gICAgICAgICAgICBmaWx0ZXJlZEZpbGUgPSByZW1vdmVMaW5lTnVtYmVycyhmaWx0ZXJlZEZpbGUpXG4gICAgICAgICAgICAvLyBTZW5kIHdpbGRjYXJkIG9yIHNpbmdsZSBmaWxlIGdsb2IgdG8gZ2V0RmlsZVBhdGhzXG4gICAgICAgICAgICAvLyBSZXR1cm4gdmFsdWUgd2lsbCBhbHdheXMgYmUgc3RyaW5nW10gb3Igc3RyaW5nIFtdW11cbiAgICAgICAgICAgIGNvbnN0IGdsb2JNYXRjaGVkRmlsZXMgPSA8c3RyaW5nW10+Q29uZmlnUGFyc2VyLmdldEZpbGVQYXRocyhcbiAgICAgICAgICAgICAgICBncm91cCA/IFtbZmlsdGVyZWRGaWxlXV0gOiBbZmlsdGVyZWRGaWxlXSxcbiAgICAgICAgICAgICAgICB0aGlzLl9jb25maWcucm9vdERpcixcbiAgICAgICAgICAgICAgICB0aGlzLl9wYXRoU2VydmljZVxuICAgICAgICAgICAgKVxuICAgICAgICAgICAgaWYgKHRoaXMuX3BhdGhTZXJ2aWNlLmlzRmlsZShmaWx0ZXJlZEZpbGUpKSB7XG4gICAgICAgICAgICAgICAgZmlsZXNUb0ZpbHRlci5hZGQoXG4gICAgICAgICAgICAgICAgICAgIHRoaXMuX3BhdGhTZXJ2aWNlLmVuc3VyZUFic29sdXRlUGF0aChcbiAgICAgICAgICAgICAgICAgICAgICAgIGZpbHRlcmVkRmlsZSxcbiAgICAgICAgICAgICAgICAgICAgICAgIHBhdGguZGlybmFtZSh0aGlzLiNjb25maWdGaWxlUGF0aClcbiAgICAgICAgICAgICAgICAgICAgKVxuICAgICAgICAgICAgICAgIClcbiAgICAgICAgICAgIH0gZWxzZSBpZiAoZ2xvYk1hdGNoZWRGaWxlcy5sZW5ndGgpIHtcbiAgICAgICAgICAgICAgICBnbG9iTWF0Y2hlZEZpbGVzLmZvckVhY2goZmlsZSA9PiBmaWxlc1RvRmlsdGVyLmFkZChmaWxlKSlcbiAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgLy8gZmlsZUxpc3QgY2FuIGJlIGEgc3RyaW5nW10gb3IgYSBzdHJpbmdbXVtdXG4gICAgICAgICAgICAgICAgZmlsZUxpc3QuZm9yRWFjaChmaWxlID0+IHtcbiAgICAgICAgICAgICAgICAgICAgaWYgKHR5cGVvZiBmaWxlID09PSAnc3RyaW5nJykge1xuICAgICAgICAgICAgICAgICAgICAgICAgLy8gVE9ETzogZmlsdGVyZWRGaWxlIGlzIG5vdCBhIHJlZ2V4IGFuZCB0aHVzIHRoaXMgaXMgYSBmYWxzZSBwb3NpdGl2ZVxuICAgICAgICAgICAgICAgICAgICAgICAgaWYgKGlzVmFsaWRSZWdleChmaWx0ZXJlZEZpbGUpICYmIGZpbGUubWF0Y2goZmlsdGVyZWRGaWxlKSkge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpbGVzVG9GaWx0ZXIuYWRkKGZpbGUpXG4gICAgICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgIH0gZWxzZSBpZiAoQXJyYXkuaXNBcnJheShmaWxlKSkge1xuICAgICAgICAgICAgICAgICAgICAgICAgZmlsZS5mb3JFYWNoKHN1YkZpbGUgPT4ge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIC8vIFRPRE86IGZpbHRlcmVkRmlsZSBpcyBub3QgYSByZWdleCBhbmQgdGh1cyB0aGlzIGlzIGEgZmFsc2UgcG9zaXRpdmVcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZiAoaXNWYWxpZFJlZ2V4KGZpbHRlcmVkRmlsZSkgJiYgc3ViRmlsZS5tYXRjaChmaWx0ZXJlZEZpbGUpKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpbGVzVG9GaWx0ZXIuYWRkKHN1YkZpbGUpXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICAgICAgfSlcbiAgICAgICAgICAgICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIGxvZy53YXJuKCdVbmV4cGVjdGVkIGVudHJ5IGluIHNwZWNzIHRoYXQgaXMgbmVpdGhlciBzdHJpbmcgbm9yIGFycmF5OiAnLCBmaWxlKVxuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgfSlcbiAgICAgICAgICAgIH1cbiAgICAgICAgfSlcbiAgICAgICAgaWYgKGZpbGVzVG9GaWx0ZXIuc2l6ZSA9PT0gMCkge1xuICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKGBzcGVjIGZpbGUocykgJHtjbGlBcmdGaWxlTGlzdC5qb2luKCcsICcpfSBub3QgZm91bmRgKVxuICAgICAgICB9XG4gICAgICAgIHJldHVybiBbLi4uZmlsZXNUb0ZpbHRlcl1cbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiByZXR1cm4gY29uZmlnc1xuICAgICAqL1xuICAgIGdldENvbmZpZygpIHtcbiAgICAgICAgaWYgKCF0aGlzLiNpc0luaXRpYWxpc2VkKSB7XG4gICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJ0NvbmZpZ1BhcnNlciB3YXMgbm90IGluaXRpYWxpemVkLCBjYWxsIFwiYXdhaXQgY29uZmlnLmluaXRpYWxpemUoKVwiIGZpcnN0IScpXG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIHRoaXMuX2NvbmZpZyBhcyBSZXF1aXJlZDxPcHRpb25zLlRlc3RydW5uZXI+XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogcmV0dXJuIGNhcGFiaWxpdGllc1xuICAgICAqL1xuICAgIGdldENhcGFiaWxpdGllcyhpPzogbnVtYmVyKTogQ2FwYWJpbGl0aWVzLlRlc3RydW5uZXJDYXBhYmlsaXRpZXMgfCBDYXBhYmlsaXRpZXMuUmVxdWVzdGVkU3RhbmRhbG9uZUNhcGFiaWxpdGllcyB7XG4gICAgICAgIGlmICghdGhpcy4jaXNJbml0aWFsaXNlZCkge1xuICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKCdDb25maWdQYXJzZXIgd2FzIG5vdCBpbml0aWFsaXplZCwgY2FsbCBcImF3YWl0IGNvbmZpZy5pbml0aWFsaXplKClcIiBmaXJzdCEnKVxuICAgICAgICB9XG5cbiAgICAgICAgaWYgKHR5cGVvZiBpID09PSAnbnVtYmVyJyAmJiBBcnJheS5pc0FycmF5KHRoaXMuX2NhcGFiaWxpdGllcykgJiYgdGhpcy5fY2FwYWJpbGl0aWVzW2ldKSB7XG4gICAgICAgICAgICByZXR1cm4gdGhpcy5fY2FwYWJpbGl0aWVzW2ldXG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gdGhpcy5fY2FwYWJpbGl0aWVzIVxuICAgIH1cblxuICAgIC8qKlxuICAgICAqIHJldHVybnMgYSBmbGF0dGVuZWQgbGlzdCBvZiBnbG9iYmVkIGZpbGVzXG4gICAgICpcbiAgICAgKiBAcGFyYW0gIHtTdHJpbmdbXSB8IFN0cmluZ1tdW119IHBhdHRlcm5zIGxpc3Qgb2YgZmlsZXMgdG8gZ2xvYlxuICAgICAqIEBwYXJhbSAge0Jvb2xlYW59IG9taXRXYXJuaW5ncyB0byBpbmRpY2F0ZSBvbWlzc2lvbiBvZiB3YXJuaW5nc1xuICAgICAqIEBwYXJhbSAge0ZpbGVTeXN0ZW1QYXRoU2VydmljZX0gZmluZEFuZEdsb2Igc3lzdGVtIHBhdGggc2VydmljZSBmb3IgZXhwYW5kaW5nIGdsb2JiZWQgZmlsZSBuYW1lc1xuICAgICAqIEBwYXJhbSAge251bWJlcn0gaGllcmFyY2h5RGVwdGggZGVwdGggdG8gcHJldmVudCByZWN1cnNpdmUgY2FsbGluZyBiZXlvbmQgYSBkZXB0aCBvZiAxXG4gICAgICogQHJldHVybiB7U3RyaW5nW10gfCBTdHJpbmdbXVtdfSBsaXN0IG9mIGZpbGVzXG4gICAgICovXG4gICAgc3RhdGljIGdldEZpbGVQYXRocyhwYXR0ZXJuczogU3BlY1tdLCByb290RGlyOiBzdHJpbmcsIGZpbmRBbmRHbG9iOiBQYXRoU2VydmljZSA9IG5ldyBGaWxlU3lzdGVtUGF0aFNlcnZpY2UoKSwgaGllcmFyY2h5RGVwdGg/OiBudW1iZXIpIHtcbiAgICAgICAgbGV0IGZpbGVzOiBTcGVjW10gPSBbXVxuICAgICAgICBsZXQgZ3JvdXBlZEZpbGVzOiBzdHJpbmdbXSA9IFtdXG5cbiAgICAgICAgaWYgKHR5cGVvZiBwYXR0ZXJucyA9PT0gJ3N0cmluZycpIHtcbiAgICAgICAgICAgIHBhdHRlcm5zID0gW3BhdHRlcm5zXVxuICAgICAgICB9XG5cbiAgICAgICAgLy8gcGF0dGVybnMgbXVzdCBiZSBhbiBhcnJheSBvZiBzdHJpbmdzIGFuZC9vciBzdHJpbmcgYXJyYXlzXG4gICAgICAgIGlmICghQXJyYXkuaXNBcnJheShwYXR0ZXJucykpIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcignc3BlY3Mgb3IgZXhjbHVkZSBwcm9wZXJ0eSBzaG91bGQgYmUgYW4gYXJyYXkgb2Ygc3RyaW5ncywgc3BlY3MgbWF5IGFsc28gYmUgYW4gYXJyYXkgb2Ygc3RyaW5nIGFycmF5cycpXG4gICAgICAgIH1cblxuICAgICAgICBwYXR0ZXJucyA9IHBhdHRlcm5zLm1hcChwYXR0ZXJuID0+IHtcbiAgICAgICAgICAgIGlmIChBcnJheS5pc0FycmF5KHBhdHRlcm4pKSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuIHBhdHRlcm4ubWFwKHN1YlBhdHRlcm4gPT4gcmVtb3ZlTGluZU51bWJlcnMoc3ViUGF0dGVybikpXG4gICAgICAgICAgICB9XG4gICAgICAgICAgICByZXR1cm4gcmVtb3ZlTGluZU51bWJlcnMocGF0dGVybilcbiAgICAgICAgfSlcblxuICAgICAgICBmb3IgKGxldCBwYXR0ZXJuIG9mIHBhdHRlcm5zKSB7XG4gICAgICAgICAgICAvLyBJZiBwYXR0ZXJuIGlzIGFuIGFycmF5LCB0aGVuIGNhbGwgZ2V0RmlsZVBhdGhzIGFnYWluXG4gICAgICAgICAgICAvLyBCdXQgb25seSBjYWxsIG9uZSBsZXZlbCBkZWVwLCBjYW4ndCBoYXZlIG11bHRpcGxlIGxldmVscyBvZiBoaWVyYXJjaHlcbiAgICAgICAgICAgIGlmIChBcnJheS5pc0FycmF5KHBhdHRlcm4pICYmICFoaWVyYXJjaHlEZXB0aCkge1xuICAgICAgICAgICAgICAgIC8vIFdpbGwgYWx3YXlzIG9ubHkgZ2V0IGEgc3RyaW5nIGFycmF5IGJhY2tcbiAgICAgICAgICAgICAgICBncm91cGVkRmlsZXMgPSA8c3RyaW5nW10+Q29uZmlnUGFyc2VyLmdldEZpbGVQYXRocyhwYXR0ZXJuLCByb290RGlyLCBmaW5kQW5kR2xvYiwgMSlcbiAgICAgICAgICAgICAgICBmaWxlcy5wdXNoKGdyb3VwZWRGaWxlcylcbiAgICAgICAgICAgIH0gZWxzZSBpZiAoQXJyYXkuaXNBcnJheShwYXR0ZXJuKSAmJiBoaWVyYXJjaHlEZXB0aCkge1xuICAgICAgICAgICAgICAgIGxvZy5lcnJvcignVW5leHBlY3RlZCBkZXB0aCBvZiBoaWVyYXJjaGljYWwgYXJyYXlzJylcbiAgICAgICAgICAgIH0gZWxzZSBpZiAoKHBhdHRlcm4gYXMgc3RyaW5nKS5zdGFydHNXaXRoKCdmaWxlOi8vJykpIHtcbiAgICAgICAgICAgICAgICAvLyBmaWxlcyBhcmUgYWxyZWFkeSBhYnNvbHV0ZSwgbm8gbmVlZCB0byBnbG9iIHRoZW1cbiAgICAgICAgICAgICAgICBmaWxlcy5wdXNoKHBhdHRlcm4pXG4gICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICAgIHBhdHRlcm4gPSBwYXR0ZXJuLnRvU3RyaW5nKCkucmVwbGFjZSgvXFxcXC9nLCAnLycpXG4gICAgICAgICAgICAgICAgbGV0IGZpbGVuYW1lcyA9IGZpbmRBbmRHbG9iLmdsb2IoPHN0cmluZz5wYXR0ZXJuLCByb290RGlyKVxuICAgICAgICAgICAgICAgIGZpbGVuYW1lcyA9IGZpbGVuYW1lcy5maWx0ZXIoXG4gICAgICAgICAgICAgICAgICAgIChmaWxlbmFtZSkgPT4gU1VQUE9SVEVEX0ZJTEVfRVhURU5TSU9OUy5maW5kKFxuICAgICAgICAgICAgICAgICAgICAgICAgKGV4dCkgPT4gZmlsZW5hbWUuZW5kc1dpdGgoZXh0KSkpXG5cbiAgICAgICAgICAgICAgICBmaWxlbmFtZXMgPS