UNPKG

creevey

Version:

creevey is a tool for automated visual testing, that tightly integrated with storybook

391 lines (319 loc) 54 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.convertStories = convertStories; exports.loadStorybookConfig = loadStorybookConfig; exports.loadTestsFromStories = loadTestsFromStories; var _fs = require("fs"); var _path = _interopRequireDefault(require("path")); var _crypto = require("crypto"); var _globBase = _interopRequireDefault(require("glob-base")); var _micromatch = require("micromatch"); var _chokidar = _interopRequireDefault(require("chokidar")); var _channelPostmessage = _interopRequireDefault(require("@storybook/channel-postmessage")); var _addons = _interopRequireDefault(require("@storybook/addons")); var _csf = require("@storybook/csf"); var _coreEvents = _interopRequireDefault(require("@storybook/core-events")); var _clientLogger = require("@storybook/client-logger"); var _types = require("./types"); var _utils = require("./utils"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function storyTestFabric(delay) { return async function storyTest() { delay ? await new Promise(resolve => setTimeout(resolve, delay)) : void 0; await this.expect((await this.takeScreenshot())).to.matchImage(); }; } function createCreeveyTest(meta, skipOptions, testName) { const { browser, kind, story } = meta; const path = [browser, testName, story, kind].filter(_types.isDefined); const skip = skipOptions ? (0, _utils.shouldSkip)(meta, skipOptions, testName) : false; const id = (0, _crypto.createHash)('sha1').update(path.join('/')).digest('hex'); return { id, skip, path }; } function convertStories(browsers, stories) { const tests = {}; (Array.isArray(stories) ? stories : Object.values(stories)).forEach(story => { browsers.forEach(browserName => { var _story$parameters$cre; const { delay, tests: storyTests, skip } = (_story$parameters$cre = story.parameters.creevey) !== null && _story$parameters$cre !== void 0 ? _story$parameters$cre : {}; const meta = { browser: browserName, story: story.name, kind: story.kind }; // typeof tests === "undefined" => rootSuite -> kindSuite -> storyTest -> [browsers.png] // typeof tests === "function" => rootSuite -> kindSuite -> storyTest -> browser -> [images.png] // typeof tests === "object" => rootSuite -> kindSuite -> storySuite -> test -> [browsers.png] // typeof tests === "object" => rootSuite -> kindSuite -> storySuite -> test -> browser -> [images.png] if (!storyTests) { const test = createCreeveyTest(meta, skip); tests[test.id] = { ...test, story, fn: storyTestFabric(delay) }; return; } Object.entries(storyTests).forEach(([testName, testFn]) => { const test = createCreeveyTest(meta, skip, testName); tests[test.id] = { ...test, story, fn: testFn }; }); }); }); return tests; } function initStorybookEnvironment() { // eslint-disable-next-line @typescript-eslint/no-var-requires require('jsdom-global')(undefined, { url: 'http://localhost' }); // NOTE Cutoff `jsdom` part from userAgent, because storybook check enviroment and create events channel if runs in browser // https://github.com/storybookjs/storybook/blob/v5.2.8/lib/core/src/client/preview/start.js#L98 // Example: "Mozilla/5.0 (linux) AppleWebKit/537.36 (KHTML, like Gecko) jsdom/15.2.1" Object.defineProperty(window.navigator, 'userAgent', { value: window.navigator.userAgent.replace(/jsdom\/(\d+\.?)+/, '').trim() }); // NOTE Disable storybook debug output due issue https://github.com/storybookjs/storybook/issues/8461 _clientLogger.logger.debug = _types.noop; } // TODO Use AST Transformation to exclude all unneeded stuff except tests and stories meta function optimizeStoriesLoading(storybookDir) { // eslint-disable-next-line @typescript-eslint/ban-ts-ignore // @ts-ignore const { wrap } = module.constructor; // eslint-disable-next-line @typescript-eslint/ban-ts-ignore // @ts-ignore module.constructor.wrap = function (script) { return wrap(`const shouldSkip = !(${function (storybookDir) { var _require$cache$__file; const { filename: parentFilename } = (_require$cache$__file = require.cache[__filename].parent) !== null && _require$cache$__file !== void 0 ? _require$cache$__file : {}; return __filename.includes(storybookDir) || __filename.includes('@storybook') || __filename.includes('@babel') || __filename.includes('creevey') || (parentFilename === null || parentFilename === void 0 ? void 0 : parentFilename.includes('node_modules')) || (parentFilename === null || parentFilename === void 0 ? void 0 : parentFilename.includes('creevey')) || (parentFilename === null || parentFilename === void 0 ? void 0 : parentFilename.includes(storybookDir)) && !__filename.includes('node_modules'); }.toString()})(${JSON.stringify(storybookDir)}); if (shouldSkip) return module.exports = (${function () { const proxy = new Proxy(function () { /* noop */ }, { apply: () => proxy, construct: () => proxy, get: () => proxy }); return proxy; }.toString()})(); ${script}`); }; } const isRequireContextArgs = val => 'path' in val && 'recursive' in val && 'match' in val && typeof val.path == 'string' && typeof val.recursive == 'boolean' && val.match instanceof RegExp; function toRequireContext(input) { if (typeof input == 'string') { // TODO Check if string parsed as glob correctly. call isGlob const { base, glob } = (0, _globBase.default)(input); return { path: base, recursive: glob.startsWith('**'), match: (0, _micromatch.makeRe)(glob) }; } if ((0, _types.isObject)(input) && isRequireContextArgs(input)) return input; throw new Error('the provided input cannot be transformed into a require.context'); } function getStorybookApi() { if (typeof window.__STORYBOOK_CLIENT_API__ !== 'undefined' && typeof window.__STORYBOOK_STORY_STORE__ !== 'undefined') { return { clientApi: window.__STORYBOOK_CLIENT_API__, storyStore: window.__STORYBOOK_STORY_STORE__ }; } } function loadStoriesFromFile(fileName, clientApi) { // eslint-disable-next-line @typescript-eslint/no-var-requires const fileExports = require(fileName); // NOTE Copy some stories initialization logic from https://github.com/storybookjs/storybook/blob/v5.3.14/lib/core/src/client/preview/start.js // NOTE An old-style story file if (!fileExports.default) return; if (!fileExports.default.title) return console.log(`Unexpected default export from '${fileName}' without title: ${JSON.stringify(fileExports.default)}`); // eslint-disable-next-line @typescript-eslint/no-unused-vars const { default: meta, __namedExportsOrder, ...namedExports } = fileExports; const { title: kindName, id: componentId, parameters: params, decorators: decos = [], component, subcomponents } = meta; // We pass true here to avoid the warning about HMR. It's cool clientApi, we got this const kind = clientApi.storiesOf(kindName, true); // NOTE In Creevey we don't have framework variable, so should be unknown kind.addParameters({ framework: 'unknown', component, subcomponents, fileName, ...params }); decos.forEach(decorator => kind.addDecorator(decorator)); // TODO Improve typings Object.entries(namedExports).forEach(([exportName, storyFn]) => { if ((0, _csf.isExportStory)(exportName, meta)) { var _storyFn$story; const storyName = (0, _csf.storyNameFromExport)(exportName); const { name = storyName, parameters, decorators } = (_storyFn$story = storyFn === null || storyFn === void 0 ? void 0 : storyFn.story) !== null && _storyFn$story !== void 0 ? _storyFn$story : {}; const decoratorParams = decorators ? { decorators } : null; kind.add(name, storyFn, { ...parameters, ...decoratorParams, __id: (0, _csf.toId)(componentId || kindName, storyName) }); } }); } function mapKindsToFiles(stories) { return Object.values(stories).reduce((kinds, { kind, parameters: { fileName } }) => { var _kinds$get; return kinds.set(fileName, ((_kinds$get = kinds.get(fileName)) !== null && _kinds$get !== void 0 ? _kinds$get : new Set()).add(kind)); }, new Map()); } function removeOldStories(filename, storyStore, kinds) { delete require.cache[filename]; if (!kinds || kinds.size == 0) return; // NOTE If user has more than one file with same kind. And one that file has been changed, we remove all stories on that kind kinds.forEach(kind => storyStore.removeStoryKind(kind)); storyStore.incrementRevision(); } function watchStories(storybookDir, stories, filesOrBlobs) { var _getStorybookApi; const { clientApi, storyStore } = (_getStorybookApi = getStorybookApi()) !== null && _getStorybookApi !== void 0 ? _getStorybookApi : {}; if (!clientApi || !storyStore) { console.log(`[Creevey:${process.pid}]:`, `Can't get Storybook client API. Hot reload for stories are not working, please check your storybook config files`); return; } let storiesByFiles = new Map(); let kindsByFiles = mapKindsToFiles(stories); // NOTE Update kinds after file with stories was changed _addons.default.getChannel().on(_coreEvents.default.SET_STORIES, data => { kindsByFiles = mapKindsToFiles(data.stories); Object.values(data.stories).forEach(story => { var _storiesByFiles$get; return (_storiesByFiles$get = storiesByFiles.get(story.parameters.fileName)) === null || _storiesByFiles$get === void 0 ? void 0 : _storiesByFiles$get.push(story); }); _addons.default.getChannel().emit('storiesUpdated', storiesByFiles); storiesByFiles = new Map(); }); // NOTE We don't support RequireContextArgs objects to pass it into chokidar const watcher = _chokidar.default.watch(filesOrBlobs, { ignoreInitial: true, cwd: storybookDir }); watcher.on('all', (event, filename) => { const absoluteFilePath = _path.default.resolve(storybookDir, filename); if (event == 'addDir' || event == 'unlinkDir') return; storiesByFiles.set(absoluteFilePath, []); if (event != 'add') removeOldStories(absoluteFilePath, storyStore, kindsByFiles.get(absoluteFilePath)); if (event != 'unlink') loadStoriesFromFile(absoluteFilePath, clientApi); }); } function loadOldStorybookConfig(storybookDir) { _addons.default.getChannel().once(_coreEvents.default.SET_STORIES, data => { // NOTE Known limitations, we can't get basePath and regex from require.context call inside `configure`, // so we have only certain list of files according by stories. // If user add new file with stories, that file can't be loaded watchStories(storybookDir, data.stories, Array.from(new Set(Object.values(data.stories).map(story => story.parameters.fileName)))); }); (0, _utils.requireConfig)(_path.default.join(storybookDir, 'config')); } function loadNewStorybookConfigs(storybookDir) { var _getStorybookApi2; (0, _utils.requireConfig)(_path.default.join(storybookDir, 'preview')); const { clientApi } = (_getStorybookApi2 = getStorybookApi()) !== null && _getStorybookApi2 !== void 0 ? _getStorybookApi2 : {}; if (!clientApi) return console.log(`[Creevey:${process.pid}]:`, `Can't get Storybook client API. Hot reload for stories are not working, please check your storybook config files`); const { stories = [] } = (0, _utils.requireConfig)(_path.default.join(storybookDir, 'main')); _addons.default.getChannel().once(_coreEvents.default.SET_STORIES, data => { if (!stories.every(_types.isString)) console.log("Your stories array defined in 'main' config files contain non-string entities. Creevey can watch files only by glob patterns"); watchStories(storybookDir, data.stories, stories.filter(_types.isString)); }); stories.map(toRequireContext).map(({ path: basePath, recursive, match }) => require.context(_path.default.resolve(storybookDir, basePath), recursive, match)).forEach(req => req.keys().forEach(filename => loadStoriesFromFile(filename, clientApi))); } function loadStorybookConfig(storybookDir, enableFastStoriesLoading, storiesListener) { return new Promise(resolve => { initStorybookEnvironment(); const channel = (0, _channelPostmessage.default)({ page: 'preview' }); channel.once(_coreEvents.default.SET_STORIES, data => resolve(data.stories)); channel.on('storiesUpdated', storiesByFiles => storiesListener(storiesByFiles)); _addons.default.setChannel(channel); // TODO Check if need reset optimization if (enableFastStoriesLoading) optimizeStoriesLoading(storybookDir); const storybookFiles = (0, _fs.readdirSync)(storybookDir); const hasOldConfig = Boolean(storybookFiles.find(filename => filename.startsWith('config'))); const hasNewConfig = Boolean(storybookFiles.find(filename => filename.startsWith('main'))); if (hasOldConfig && hasNewConfig) throw new Error("You can't use both old and new storybook configs in the same time"); if (!hasOldConfig && !hasNewConfig) throw new Error(`We can't find any of supported storybook configs in '${storybookDir}' directory`); if (hasNewConfig) loadNewStorybookConfigs(storybookDir); if (hasOldConfig) loadOldStorybookConfig(storybookDir); }); } async function loadTestsFromStories(config, browsers, applyTestsDiff) { const testIdsByFiles = new Map(); const stories = await loadStorybookConfig(config.storybookDir, config.enableFastStoriesLoading, storiesByFiles => { const testsDiff = {}; Array.from(storiesByFiles.entries()).forEach(([filename, stories]) => { var _testIdsByFiles$get$f, _testIdsByFiles$get; const tests = convertStories(browsers, stories); const changed = Object.keys(tests); const removed = (_testIdsByFiles$get$f = (_testIdsByFiles$get = testIdsByFiles.get(filename)) === null || _testIdsByFiles$get === void 0 ? void 0 : _testIdsByFiles$get.filter(testId => !tests[testId])) !== null && _testIdsByFiles$get$f !== void 0 ? _testIdsByFiles$get$f : []; if (changed.length == 0) testIdsByFiles.delete(filename);else testIdsByFiles.set(filename, changed); Object.assign(testsDiff, tests); removed.forEach(testId => testsDiff[testId] = undefined); }); applyTestsDiff(testsDiff); }); const tests = convertStories(browsers, stories); Object.values(tests).filter(_types.isDefined).forEach(({ id, story: { parameters: { fileName } } }) => { var _testIdsByFiles$get2; return testIdsByFiles.set(fileName, [...((_testIdsByFiles$get2 = testIdsByFiles.get(fileName)) !== null && _testIdsByFiles$get2 !== void 0 ? _testIdsByFiles$get2 : []), id]); }); return tests; } //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uL3NyYy9zdG9yaWVzLnRzIl0sIm5hbWVzIjpbInN0b3J5VGVzdEZhYnJpYyIsImRlbGF5Iiwic3RvcnlUZXN0IiwiUHJvbWlzZSIsInJlc29sdmUiLCJzZXRUaW1lb3V0IiwiZXhwZWN0IiwidGFrZVNjcmVlbnNob3QiLCJ0byIsIm1hdGNoSW1hZ2UiLCJjcmVhdGVDcmVldmV5VGVzdCIsIm1ldGEiLCJza2lwT3B0aW9ucyIsInRlc3ROYW1lIiwiYnJvd3NlciIsImtpbmQiLCJzdG9yeSIsInBhdGgiLCJmaWx0ZXIiLCJpc0RlZmluZWQiLCJza2lwIiwiaWQiLCJ1cGRhdGUiLCJqb2luIiwiZGlnZXN0IiwiY29udmVydFN0b3JpZXMiLCJicm93c2VycyIsInN0b3JpZXMiLCJ0ZXN0cyIsIkFycmF5IiwiaXNBcnJheSIsIk9iamVjdCIsInZhbHVlcyIsImZvckVhY2giLCJicm93c2VyTmFtZSIsInN0b3J5VGVzdHMiLCJwYXJhbWV0ZXJzIiwiY3JlZXZleSIsIm5hbWUiLCJ0ZXN0IiwiZm4iLCJlbnRyaWVzIiwidGVzdEZuIiwiaW5pdFN0b3J5Ym9va0Vudmlyb25tZW50IiwicmVxdWlyZSIsInVuZGVmaW5lZCIsInVybCIsImRlZmluZVByb3BlcnR5Iiwid2luZG93IiwibmF2aWdhdG9yIiwidmFsdWUiLCJ1c2VyQWdlbnQiLCJyZXBsYWNlIiwidHJpbSIsImxvZ2dlciIsImRlYnVnIiwibm9vcCIsIm9wdGltaXplU3Rvcmllc0xvYWRpbmciLCJzdG9yeWJvb2tEaXIiLCJ3cmFwIiwibW9kdWxlIiwiY29uc3RydWN0b3IiLCJzY3JpcHQiLCJmaWxlbmFtZSIsInBhcmVudEZpbGVuYW1lIiwiY2FjaGUiLCJfX2ZpbGVuYW1lIiwicGFyZW50IiwiaW5jbHVkZXMiLCJ0b1N0cmluZyIsIkpTT04iLCJzdHJpbmdpZnkiLCJwcm94eSIsIlByb3h5IiwiYXBwbHkiLCJjb25zdHJ1Y3QiLCJnZXQiLCJpc1JlcXVpcmVDb250ZXh0QXJncyIsInZhbCIsInJlY3Vyc2l2ZSIsIm1hdGNoIiwiUmVnRXhwIiwidG9SZXF1aXJlQ29udGV4dCIsImlucHV0IiwiYmFzZSIsImdsb2IiLCJzdGFydHNXaXRoIiwiRXJyb3IiLCJnZXRTdG9yeWJvb2tBcGkiLCJfX1NUT1JZQk9PS19DTElFTlRfQVBJX18iLCJfX1NUT1JZQk9PS19TVE9SWV9TVE9SRV9fIiwiY2xpZW50QXBpIiwic3RvcnlTdG9yZSIsImxvYWRTdG9yaWVzRnJvbUZpbGUiLCJmaWxlTmFtZSIsImZpbGVFeHBvcnRzIiwiZGVmYXVsdCIsInRpdGxlIiwiY29uc29sZSIsImxvZyIsIl9fbmFtZWRFeHBvcnRzT3JkZXIiLCJuYW1lZEV4cG9ydHMiLCJraW5kTmFtZSIsImNvbXBvbmVudElkIiwicGFyYW1zIiwiZGVjb3JhdG9ycyIsImRlY29zIiwiY29tcG9uZW50Iiwic3ViY29tcG9uZW50cyIsInN0b3JpZXNPZiIsImFkZFBhcmFtZXRlcnMiLCJmcmFtZXdvcmsiLCJkZWNvcmF0b3IiLCJhZGREZWNvcmF0b3IiLCJleHBvcnROYW1lIiwic3RvcnlGbiIsInN0b3J5TmFtZSIsImRlY29yYXRvclBhcmFtcyIsImFkZCIsIl9faWQiLCJtYXBLaW5kc1RvRmlsZXMiLCJyZWR1Y2UiLCJraW5kcyIsInNldCIsIlNldCIsIk1hcCIsInJlbW92ZU9sZFN0b3JpZXMiLCJzaXplIiwicmVtb3ZlU3RvcnlLaW5kIiwiaW5jcmVtZW50UmV2aXNpb24iLCJ3YXRjaFN0b3JpZXMiLCJmaWxlc09yQmxvYnMiLCJwcm9jZXNzIiwicGlkIiwic3Rvcmllc0J5RmlsZXMiLCJraW5kc0J5RmlsZXMiLCJhZGRvbnMiLCJnZXRDaGFubmVsIiwib24iLCJFdmVudHMiLCJTRVRfU1RPUklFUyIsImRhdGEiLCJwdXNoIiwiZW1pdCIsIndhdGNoZXIiLCJjaG9raWRhciIsIndhdGNoIiwiaWdub3JlSW5pdGlhbCIsImN3ZCIsImV2ZW50IiwiYWJzb2x1dGVGaWxlUGF0aCIsImxvYWRPbGRTdG9yeWJvb2tDb25maWciLCJvbmNlIiwiZnJvbSIsIm1hcCIsImxvYWROZXdTdG9yeWJvb2tDb25maWdzIiwiZXZlcnkiLCJpc1N0cmluZyIsImJhc2VQYXRoIiwiY29udGV4dCIsInJlcSIsImtleXMiLCJsb2FkU3Rvcnlib29rQ29uZmlnIiwiZW5hYmxlRmFzdFN0b3JpZXNMb2FkaW5nIiwic3Rvcmllc0xpc3RlbmVyIiwiY2hhbm5lbCIsInBhZ2UiLCJzZXRDaGFubmVsIiwic3Rvcnlib29rRmlsZXMiLCJoYXNPbGRDb25maWciLCJCb29sZWFuIiwiZmluZCIsImhhc05ld0NvbmZpZyIsImxvYWRUZXN0c0Zyb21TdG9yaWVzIiwiY29uZmlnIiwiYXBwbHlUZXN0c0RpZmYiLCJ0ZXN0SWRzQnlGaWxlcyIsInRlc3RzRGlmZiIsImNoYW5nZWQiLCJyZW1vdmVkIiwidGVzdElkIiwibGVuZ3RoIiwiZGVsZXRlIiwiYXNzaWduIl0sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7QUFBQTs7QUFDQTs7QUFDQTs7QUFFQTs7QUFDQTs7QUFDQTs7QUFDQTs7QUFDQTs7QUFDQTs7QUFFQTs7QUFDQTs7QUFDQTs7QUFhQTs7OztBQUVBLFNBQVNBLGVBQVQsQ0FBeUJDLEtBQXpCLEVBQXlDO0FBQ3ZDLFNBQU8sZUFBZUMsU0FBZixHQUF3QztBQUM3Q0QsSUFBQUEsS0FBSyxHQUFHLE1BQU0sSUFBSUUsT0FBSixDQUFhQyxPQUFELElBQWFDLFVBQVUsQ0FBQ0QsT0FBRCxFQUFVSCxLQUFWLENBQW5DLENBQVQsR0FBZ0UsS0FBSyxDQUExRTtBQUNBLFVBQU0sS0FBS0ssTUFBTCxFQUFZLE1BQU0sS0FBS0MsY0FBTCxFQUFsQixHQUF5Q0MsRUFBekMsQ0FBNENDLFVBQTVDLEVBQU47QUFDRCxHQUhEO0FBSUQ7O0FBRUQsU0FBU0MsaUJBQVQsQ0FDRUMsSUFERixFQU1FQyxXQU5GLEVBT0VDLFFBUEYsRUFRUTtBQUNOLFFBQU07QUFBRUMsSUFBQUEsT0FBRjtBQUFXQyxJQUFBQSxJQUFYO0FBQWlCQyxJQUFBQTtBQUFqQixNQUEyQkwsSUFBakM7QUFDQSxRQUFNTSxJQUFJLEdB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QUEsY0FBYyxDQUFDRSxTQUFELENBQWQ7QUFDRCxHQWJ3QyxDQUF6QztBQWVBLFFBQU1ySixLQUFLLEdBQUdILGNBQWMsQ0FBQ0MsUUFBRCxFQUFXQyxPQUFYLENBQTVCO0FBRUFJLEVBQUFBLE1BQU0sQ0FBQ0MsTUFBUCxDQUFjSixLQUFkLEVBQ0dWLE1BREgsQ0FDVUMsZ0JBRFYsRUFFR2MsT0FGSCxDQUVXLENBQUM7QUFBRVosSUFBQUEsRUFBRjtBQUFNTCxJQUFBQSxLQUFLLEVBQUU7QUFBRW9CLE1BQUFBLFVBQVUsRUFBRTtBQUFFMEQsUUFBQUE7QUFBRjtBQUFkO0FBQWIsR0FBRDtBQUFBOztBQUFBLFdBQ1BrRixjQUFjLENBQUNyRCxHQUFmLENBQW1CN0IsUUFBbkIsRUFBNkIsQ0FBQyw0QkFBSWtGLGNBQWMsQ0FBQ3BHLEdBQWYsQ0FBbUJrQixRQUFuQixDQUFKLHVFQUFvQyxFQUFwQyxDQUFELEVBQTBDekUsRUFBMUMsQ0FBN0IsQ0FETztBQUFBLEdBRlg7QUFNQSxTQUFPTyxLQUFQO0FBQ0QiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyByZWFkZGlyU3luYyB9IGZyb20gJ2ZzJztcbmltcG9ydCBwYXRoIGZyb20gJ3BhdGgnO1xuaW1wb3J0IHsgY3JlYXRlSGFzaCB9IGZyb20gJ2NyeXB0byc7XG5pbXBvcnQgeyBDb250ZXh0IH0gZnJvbSAnbW9jaGEnO1xuaW1wb3J0IGdsb2JCYXNlIGZyb20gJ2dsb2ItYmFzZSc7XG5pbXBvcnQgeyBtYWtlUmUgfSBmcm9tICdtaWNyb21hdGNoJztcbmltcG9ydCBjaG9raWRhciBmcm9tICdjaG9raWRhcic7XG5pbXBvcnQgY3JlYXRlQ2hhbm5lbCBmcm9tICdAc3Rvcnlib29rL2NoYW5uZWwtcG9zdG1lc3NhZ2UnO1xuaW1wb3J0IGFkZG9ucywgeyBEZWNvcmF0b3JGdW5jdGlvbiwgU3RvcnlGbiB9IGZyb20gJ0BzdG9yeWJvb2svYWRkb25zJztcbmltcG9ydCB7IHRvSWQsIHN0b3J5TmFtZUZyb21FeHBvcnQsIGlzRXhwb3J0U3RvcnkgfSBmcm9tICdAc3Rvcnlib29rL2NzZic7XG5pbXBvcnQgeyBDbGllbnRBcGksIFN0b3J5U3RvcmUgfSBmcm9tICdAc3Rvcnlib29rL2NsaWVudC1hcGknO1xuaW1wb3J0IEV2ZW50cyBmcm9tICdAc3Rvcnlib29rL2NvcmUtZXZlbnRzJztcbmltcG9ydCB7IGxvZ2dlciB9IGZyb20gJ0BzdG9yeWJvb2svY2xpZW50LWxvZ2dlcic7XG5pbXBvcnQge1xuICBpc0RlZmluZWQsXG4gIFRlc3QsXG4gIENyZWV2ZXlTdG9yeVBhcmFtcyxcbiAgU3Rvcmllc1JhdyxcbiAgbm9vcCxcbiAgU2tpcE9wdGlvbnMsXG4gIGlzT2JqZWN0LFxuICBpc1N0cmluZyxcbiAgU2VydmVyVGVzdCxcbiAgU3RvcnlJbnB1dCxcbiAgQ29uZmlnLFxufSBmcm9tICcuL3R5cGVzJztcbmltcG9ydCB7IHNob3VsZFNraXAsIHJlcXVpcmVDb25maWcgfSBmcm9tICcuL3V0aWxzJztcblxuZnVuY3Rpb24gc3RvcnlUZXN0RmFicmljKGRlbGF5PzogbnVtYmVyKSB7XG4gIHJldHVybiBhc3luYyBmdW5jdGlvbiBzdG9yeVRlc3QodGhpczogQ29udGV4dCkge1xuICAgIGRlbGF5ID8gYXdhaXQgbmV3IFByb21pc2UoKHJlc29sdmUpID0+IHNldFRpbWVvdXQocmVzb2x2ZSwgZGVsYXkpKSA6IHZvaWQgMDtcbiAgICBhd2FpdCB0aGlzLmV4cGVjdChhd2FpdCB0aGlzLnRha2VTY3JlZW5zaG90KCkpLnRvLm1hdGNoSW1hZ2UoKTtcbiAgfTtcbn1cblxuZnVuY3Rpb24gY3JlYXRlQ3JlZXZleVRlc3QoXG4gIG1ldGE6IHtcbiAgICBicm93c2VyOiBzdHJpbmc7XG4gICAga2luZDogc3RyaW5nO1xuICAgIHN0b3J5OiBzdHJpbmc7XG4gIH0sXG4gIHNraXBPcHRpb25zPzogU2tpcE9wdGlvbnMsXG4gIHRlc3ROYW1lPzogc3RyaW5nLFxuKTogVGVzdCB7XG4gIGNvbnN0IHsgYnJvd3Nlciwga2luZCwgc3RvcnkgfSA9IG1ldGE7XG4gIGNvbnN0IHBhdGggPSBbYnJvd3NlciwgdGVzdE5hbWUsIHN0b3J5LCBraW5kXS5maWx0ZXIoaXNEZWZpbmVkKTtcbiAgY29uc3Qgc2tpcCA9IHNraXBPcHRpb25zID8gc2hvdWxkU2tpcChtZXRhLCBza2lwT3B0aW9ucywgdGVzdE5hbWUpIDogZmFsc2U7XG4gIGNvbnN0IGlkID0gY3JlYXRlSGFzaCgnc2hhMScpLnVwZGF0ZShwYXRoLmpvaW4oJy8nKSkuZGlnZXN0KCdoZXgnKTtcbiAgcmV0dXJuIHsgaWQsIHNraXAsIHBhdGggfTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGNvbnZlcnRTdG9yaWVzKFxuICBicm93c2Vyczogc3RyaW5nW10sXG4gIHN0b3JpZXM6IFN0b3JpZXNSYXcgfCBTdG9yeUlucHV0W10sXG4pOiBQYXJ0aWFsPHsgW3Rlc3RJZDogc3RyaW5nXTogU2VydmVyVGVzdCB9PiB7XG4gIGNvbnN0IHRlc3RzOiB7IFt0ZXN0SWQ6IHN0cmluZ106IFNlcnZlclRlc3QgfSA9IHt9O1xuXG4gIChBcnJheS5pc0FycmF5KHN0b3JpZXMpID8gc3RvcmllcyA6IE9iamVjdC52YWx1ZXMoc3RvcmllcykpLmZvckVhY2goKHN0b3J5KSA9PiB7XG4gICAgYnJvd3NlcnMuZm9yRWFjaCgoYnJvd3Nlck5hbWUpID0+IHtcbiAgICAgIGNvbnN0IHsgZGVsYXksIHRlc3RzOiBzdG9yeVRlc3RzLCBza2lwIH06IENyZWV2ZXlTdG9yeVBhcmFtcyA9IHN0b3J5LnBhcmFtZXRlcnMuY3JlZXZleSA/PyB7fTtcbiAgICAgIGNvbnN0IG1ldGEgPSB7IGJyb3dzZXI6IGJyb3dzZXJOYW1lLCBzdG9yeTogc3RvcnkubmFtZSwga2luZDogc3Rvcnkua2luZCB9O1xuXG4gICAgICAvLyB0eXBlb2YgdGVzdHMgPT09IFwidW5kZWZpbmVkXCIgPT4gcm9vdFN1aXRlIC0+IGtpbmRTdWl0ZSAtPiBzdG9yeVRlc3QgLT4gW2Jyb3dzZXJzLnBuZ11cbiAgICAgIC8vIHR5cGVvZiB0ZXN0cyA9PT0gXCJmdW5jdGlvblwiICA9PiByb290U3VpdGUgLT4ga2luZFN1aXRlIC0+IHN0b3J5VGVzdCAtPiBicm93c2VyIC0+IFtpbWFnZXMucG5nXVxuICAgICAgLy8gdHlwZW9mIHRlc3RzID09PSBcIm9iamVjdFwiICAgID0+IHJvb3RTdWl0ZSAtPiBraW5kU3VpdGUgLT4gc3RvcnlTdWl0ZSAtPiB0ZXN0IC0+IFticm93c2Vycy5wbmddXG4gICAgICAvLyB0eXBlb2YgdGVzdHMgPT09IFwib2JqZWN0XCIgICAgPT4gcm9vdFN1aXRlIC0+IGtpbmRTdWl0ZSAtPiBzdG9yeVN1aXRlIC0+IHRlc3QgLT4gYnJvd3NlciAtPiBbaW1hZ2VzLnBuZ11cblxuICAgICAgaWYgKCFzdG9yeVRlc3RzKSB7XG4gICAgICAgIGNvbnN0IHRlc3QgPSBjcmVhdGVDcmVldmV5VGVzdChtZXRhLCBza2lwKTtcbiAgICAgICAgdGVzdHNbdGVzdC5pZF0gPSB7IC4uLnRlc3QsIHN0b3J5LCBmbjogc3RvcnlUZXN0RmFicmljKGRlbGF5KSB9O1xuICAgICAgICByZXR1cm47XG4gICAgICB9XG5cbiAgICAgIE9iamVjdC5lbnRyaWVzKHN0b3J5VGVzdHMpLmZvckVhY2goKFt0ZXN0TmFtZSwgdGVzdEZuXSkgPT4ge1xuICAgICAgICBjb25zdCB0ZXN0ID0gY3JlYXRlQ3JlZXZleVRlc3QobWV0YSwgc2tpcCwgdGVzdE5hbWUpO1xuICAgICAgICB0ZXN0c1t0ZXN0LmlkXSA9IHsgLi4udGVzdCwgc3RvcnksIGZuOiB0ZXN0Rm4gfTtcbiAgICAgIH0pO1xuICAgIH0pO1xuICB9KTtcblxuICByZXR1cm4gdGVzdHM7XG59XG5cbmZ1bmN0aW9uIGluaXRTdG9yeWJvb2tFbnZpcm9ubWVudCgpOiB2b2lkIHtcbiAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIEB0eXBlc2NyaXB0LWVzbGludC9uby12YXItcmVxdWlyZXNcbiAgcmVxdWlyZSgnanNkb20tZ2xvYmFsJykodW5kZWZpbmVkLCB7IHVybDogJ2h0dHA6Ly9sb2NhbGhvc3QnIH0pO1xuXG4gIC8vIE5PVEUgQ3V0b2ZmIGBqc2RvbWAgcGFydCBmcm9tIHVzZXJBZ2VudCwgYmVjYXVzZSBzdG9yeWJvb2sgY2hlY2sgZW52aXJvbWVudCBhbmQgY3JlYXRlIGV2ZW50cyBjaGFubmVsIGlmIHJ1bnMgaW4gYnJvd3NlclxuICAvLyBodHRwczovL2dpdGh1Yi5jb20vc3Rvcnlib29ranMvc3Rvcnlib29rL2Jsb2IvdjUuMi44L2xpYi9jb3JlL3NyYy9jbGllbnQvcHJldmlldy9zdGFydC5qcyNMOThcbiAgLy8gRXhhbXBsZTogXCJNb3ppbGxhLzUuMCAobGludXgpIEFwcGxlV2ViS2l0LzUzNy4zNiAoS0hUTUwsIGxpa2UgR2Vja28pIGpzZG9tLzE1LjIuMVwiXG4gIE9iamVjdC5kZWZpbmVQcm9wZXJ0eSh3aW5kb3cubmF2aWdhdG9yLCAndXNlckFnZW50Jywge1xuICAgIHZhbHVlOiB3aW5kb3cubmF2aWdhdG9yLnVzZXJBZ2VudC5yZXBsYWNlKC9qc2RvbVxcLyhcXGQrXFwuPykrLywgJycpLnRyaW0oKSxcbiAgfSk7XG5cbiAgLy8gTk9URSBEaXNhYmxlIHN0b3J5Ym9vayBkZWJ1ZyBvdXRwdXQgZHVlIGlzc3VlIGh0dHBzOi8vZ2l0aHViLmNvbS9zdG9yeWJvb2tqcy9zdG9yeWJvb2svaXNzdWVzLzg0NjFcbiAgbG9nZ2VyLmRlYnVnID0gbm9vcDtcbn1cblxuLy8gVE9ETyBVc2UgQVNUIFRyYW5zZm9ybWF0aW9uIHRvIGV4Y2x1ZGUgYWxsIHVubmVlZGVkIHN0dWZmIGV4Y2VwdCB0ZXN0cyBhbmQgc3RvcmllcyBtZXRhXG5mdW5jdGlvbiBvcHRpbWl6ZVN0b3JpZXNMb2FkaW5nKHN0b3J5Ym9va0Rpcjogc3RyaW5nKTogdm9pZCB7XG4gIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBAdHlwZXNjcmlwdC1lc2xpbnQvYmFuLXRzLWlnbm9yZVxuICAvLyBAdHMtaWdub3JlXG4gIGNvbnN0IHsgd3JhcCB9ID0gbW9kdWxlLmNvbnN0cnVjdG9yO1xuXG4gIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBAdHlwZXNjcmlwdC1lc2xpbnQvYmFuLXRzLWlnbm9yZVxuICAvLyBAdHMtaWdub3JlXG4gIG1vZHVsZS5jb25zdHJ1Y3Rvci53cmFwID0gZnVuY3Rpb24gKHNjcmlwdDogc3RyaW5nKSB7XG4gICAgcmV0dXJuIHdyYXAoXG4gICAgICBgY29uc3Qgc2hvdWxkU2tpcCA9ICEoJHtmdW5jdGlvbiAoc3Rvcnlib29rRGlyOiBzdHJpbmcpIHtcbiAgICAgICAgY29uc3QgeyBmaWxlbmFtZTogcGFyZW50RmlsZW5hbWUgfSA9IHJlcXVpcmUuY2FjaGVbX19maWxlbmFtZV0ucGFyZW50ID8/IHt9O1xuXG4gICAgICAgIHJldHVybiAoXG4gICAgICAgICAgX19maWxlbmFtZS5pbmNsdWRlcyhzdG9yeWJvb2tEaXIpIHx8XG4gICAgICAgICAgX19maWxlbmFtZS5pbmNsdWRlcygnQHN0b3J5Ym9vaycpIHx8XG4gICAgICAgICAgX19maWxlbmFtZS5pbmNsdWRlcygnQGJhYmVsJykgfHxcbiAgICAgICAgICBfX2ZpbGVuYW1lLmluY2x1ZGVzKCdjcmVldmV5JykgfHxcbiAgICAgICAgICBwYXJlbnRGaWxlbmFtZT8uaW5jbHVkZXMoJ25vZGVfbW9kdWxlcycpIHx8XG4gICAgICAgICAgcGFyZW50RmlsZW5hbWU/LmluY2x1ZGVzKCdjcmVldmV5JykgfHxcbiAgICAgICAgICAocGFyZW50RmlsZW5hbWU/LmluY2x1ZGVzKHN0b3J5Ym9va0RpcikgJiYgIV9fZmlsZW5hbWUuaW5jbHVkZXMoJ25vZGVfbW9kdWxlcycpKVxuICAgICAgICApO1xuICAgICAgfS50b1N0cmluZygpfSkoJHtKU09OLnN0cmluZ2lmeShzdG9yeWJvb2tEaXIpfSk7XG5cbiAgICAgIGlmIChzaG91bGRTa2lwKSByZXR1cm4gbW9kdWxlLmV4cG9ydHMgPSAoJHtmdW5jdGlvbiAoKSB7XG4gICAgICAgIGNvbnN0IHByb3h5OiBGdW5jdGlvbiA9IG5ldyBQcm94eShcbiAgICAgICAgICBmdW5jdGlvbiAoKSB7XG4gICAgICAgICAgICAvKiBub29wICovXG4gICAgICAgICAgfSxcbiAgICAgICAgICB7XG4gICAgICAgICAgICBhcHBseTogKCkgPT4gcHJveHksXG4gICAgICAgICAgICBjb25zdHJ1Y3Q6ICgpID0+IHByb3h5LFxuICAgICAgICAgICAgZ2V0OiAoKSA9PiBwcm94eSxcbiAgICAgICAgICB9LFxuICAgICAgICApO1xuICAgICAgICByZXR1cm4gcHJveHk7XG4gICAgICB9LnRvU3RyaW5nKCl9KSgpO1xuXG4gICAgICAke3NjcmlwdH1gLFxuICAgICk7XG4gIH07XG59XG5cbmludGVyZmFjZSBSZXF1aXJlQ29udGV4dEFyZ3Mge1xuICBwYXRoOiBzdHJpbmc7XG4gIHJlY3Vyc2l2ZTogYm9vbGVhbjtcbiAgbWF0Y2g6IFJlZ0V4cDtcbn1cblxuY29uc3QgaXNSZXF1aXJlQ29udGV4dEFyZ3MgPSAodmFsOiBSZXF1aXJlQ29udGV4dEFyZ3MgfCBvYmplY3QpOiB2YWwgaXMgUmVxdWlyZUNvbnRleHRBcmdzID0+XG4gICdwYXRoJyBpbiB2YWwgJiZcbiAgJ3JlY3Vyc2l2ZScgaW4gdmFsICYmXG4gICdtYXRjaCcgaW4gdmFsICYmXG4gIHR5cGVvZiB2YWwucGF0aCA9PSAnc3RyaW5nJyAmJlxuICB0eXBlb2YgdmFsLnJlY3Vyc2l2ZSA9PSAnYm9vbGVhbicgJiZcbiAgdmFsLm1hdGNoIGluc3RhbmNlb2YgUmVnRXhwO1xuXG5mdW5jdGlvbiB0b1JlcXVpcmVDb250ZXh0KGlucHV0OiB1bmtub3duKTogUmVxdWlyZUNvbnRleHRBcmdzIHtcbiAgaWYgKHR5cGVvZiBpbnB1dCA9PSAnc3RyaW5nJykge1xuICAgIC8vIFRPRE8gQ2hlY2sgaWYgc3RyaW5nIHBhcnNlZCBhcyBnbG9iIGNvcnJlY3RseS4gY2FsbCBpc0dsb2JcbiAgICBjb25zdCB7IGJhc2UsIGdsb2IgfSA9IGdsb2JCYXNlKGlucHV0KTtcblxuICAgIHJldHVybiB7IHBhdGg6IGJhc2UsIHJlY3Vyc2l2ZTogZ2xvYi5zdGFydHNXaXRoKCcqKicpLCBtYXRjaDogbWFrZVJlKGdsb2IpIH07XG4gIH1cbiAgaWYgKGlzT2JqZWN0KGlucHV0KSAmJiBpc1JlcXVpcmVDb250ZXh0QXJncyhpbnB1dCkpIHJldHVybiBpbnB1dDtcblxuICB0aHJvdyBuZXcgRXJyb3IoJ3RoZSBwcm92aWRlZCBpbnB1dCBjYW5ub3QgYmUgdHJhbnNmb3JtZWQgaW50byBhIHJlcXVpcmUuY29udGV4dCcpO1xufVxuXG5kZWNsYXJlIGdsb2JhbCB7XG4gIGludGVyZmFjZSBXaW5kb3cge1xuICAgIF9fU1RPUllCT09LX0NMSUVOVF9BUElfXzogQ2xpZW50QXBpIHwgdW5kZWZpbmVkO1xuICAgIF9fU1RPUllCT09LX1NUT1JZX1NUT1JFX186IFN0b3J5U3RvcmUgfCB1bmRlZmluZWQ7XG4gIH1cbn1cbmZ1bmN0aW9uIGdldFN0b3J5Ym9va0FwaSgpOiB7IGNsaWVudEFwaTogQ2xpZW50QXBpOyBzdG9yeVN0b3JlOiBTdG9yeVN0b3JlIH0gfCB1bmRlZmluZWQge1xuICBpZiAoXG4gICAgdHlwZW9mIHdpbmRvdy5fX1NUT1JZQk9PS19DTElFTlRfQVBJX18gIT09ICd1bmRlZmluZWQnICYmXG4gICAgdHlwZW9mIHdpbmRvdy5fX1NUT1JZQk9PS19TVE9SWV9TVE9SRV9fICE9PSAndW5kZWZpbmVkJ1xuICApIHtcbiAgICByZXR1cm4geyBjbGllbnRBcGk6IHdpbmRvdy5fX1NUT1JZQk9PS19DTElFTlRfQVBJX18sIHN0b3J5U3RvcmU6IHdpbmRvdy5fX1NUT1JZQk9PS19TVE9SWV9TVE9SRV9fIH07XG4gIH1cbn1cblxuZnVuY3Rpb24gbG9hZFN0b3JpZXNGcm9tRmlsZShmaWxlTmFtZTogc3RyaW5nLCBjbGllbnRBcGk6IENsaWVudEFwaSk6IHZvaWQge1xuICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgQHR5cGVzY3JpcHQtZXNsaW50L25vLXZhci1yZXF1aXJlc1xuICBjb25zdCBmaWxlRXhwb3J0cyA9IHJlcXVpcmUoZmlsZU5hbWUpO1xuXG4gIC8vIE5PVEUgQ29weSBzb21lIHN0b3JpZXMgaW5pdGlhbGl6YXRpb24gbG9naWMgZnJvbSBodHRwczovL2dpdGh1Yi5jb20vc3Rvcnlib29ranMvc3Rvcnlib29rL2Jsb2IvdjUuMy4xNC9saWIvY29yZS9zcmMvY2xpZW50L3ByZXZpZXcvc3RhcnQuanNcbiAgLy8gTk9URSBBbiBvbGQtc3R5bGUgc3RvcnkgZmlsZVxuICBpZiAoIWZpbGVFeHBvcnRzLmRlZmF1bHQpIHJldHVybjtcbiAgaWYgKCFmaWxlRXhwb3J0cy5kZWZhdWx0LnRpdGxlKVxuICAgIHJldHVybiBjb25zb2xlLmxvZyhcbiAgICAgIGBVbmV4cGVjdGVkIGRlZmF1bHQgZXhwb3J0IGZyb20gJyR7ZmlsZU5hbWV9JyB3aXRob3V0IHRpdGxlOiAke0pTT04uc3RyaW5naWZ5KGZpbGVFeHBvcnRzLmRlZmF1bHQpfWAsXG4gICAgKTtcblxuICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgQHR5cGVzY3JpcHQtZXNsaW50L25vLXVudXNlZC12YXJzXG4gIGNvbnN0IHsgZGVmYXVsdDogbWV0YSwgX19uYW1lZEV4cG9ydHNPcmRlciwgLi4ubmFtZWRFeHBvcnRzIH0gPSBmaWxlRXhwb3J0cztcblxuICBjb25zdCB7XG4gICAgdGl0bGU6IGtpbmROYW1lLFxuICAgIGlkOiBjb21wb25lbnRJZCxcbiAgICBwYXJhbWV0ZXJzOiBwYXJhbXMsXG4gICAgZGVjb3JhdG9yczogZGVjb3MgPSBbXSxcbiAgICBjb21wb25lbnQsXG4gICAgc3ViY29tcG9uZW50cyxcbiAgfSA9IG1ldGE7XG4gIC8vIFdlIHBhc3MgdHJ1ZSBoZXJlIHRvIGF2b2lkIHRoZSB3YXJuaW5nIGFib3V0IEhNUi4gSXQncyBjb29sIGNsaWVudEFwaSwgd2UgZ290IHRoaXNcbiAgY29uc3Qga2luZCA9IGNsaWVudEFwaS5zdG9yaWVzT2Yoa2luZE5hbWUsIHRydWUgYXMgbmV2ZXIpO1xuXG4gIC8vIE5PVEUgSW4gQ3JlZXZleSB3ZSBkb24ndCBoYXZlIGZyYW1ld29yayB2YXJpYWJsZSwgc28gc2hvdWxkIGJlIHVua25vd25cbiAga2luZC5hZGRQYXJhbWV0ZXJzKHtcbiAgICBmcmFtZXdvcms6ICd1bmtub3duJyxcbiAgICBjb21wb25lbnQsXG4gICAgc3ViY29tcG9uZW50cyxcbiAgICBmaWxlTmFtZSxcbiAgICAuLi5wYXJhbXMsXG4gIH0pO1xuXG4gIGRlY29zLmZvckVhY2goKGRlY29yYXRvcjogRGVjb3JhdG9yRnVuY3Rpb24pID0+IGtpbmQuYWRkRGVjb3JhdG9yKGRlY29yYXRvcikpO1xuXG4gIC8vIFRPRE8gSW1wcm92ZSB0eXBpbmdzXG4gIE9iamVjdC5lbnRyaWVzKFxuICAgIG5hbWVkRXhwb3J0cyBhcyB7XG4gICAgICBbZXhwb3J0TmFtZTogc3RyaW5nXTogU3RvcnlGbiAmIHsgc3Rvcnk/OiB7IG5hbWU/OiBzdHJpbmc7IHBhcmFtZXRlcnM/OiB7fTsgZGVjb3JhdG9ycz86IERlY29yYXRvckZ1bmN0aW9uW10gfSB9O1xuICAgIH0sXG4gICkuZm9yRWFjaCgoW2V4cG9ydE5hbWUsIHN0b3J5Rm5dKSA9PiB7XG4gICAgaWYgKGlzRXhwb3J0U3RvcnkoZXhwb3J0TmFtZSwgbWV0YSkpIHtcbiAgICAgIGNvbnN0IHN0b3J5TmFtZSA9IHN0b3J5TmFtZUZyb21FeHBvcnQoZXhwb3J0TmFtZSk7XG4gICAgICBjb25zdCB7IG5hbWUgPSBzdG9yeU5hbWUsIHBhcmFtZXRlcnMsIGRlY29yYXRvcnMgfSA9IHN0b3J5Rm4/LnN0b3J5ID8/IHt9O1xuICAgICAgY29uc3QgZGVjb3JhdG9yUGFyYW1zID0gZGVjb3JhdG9ycyA/IHsgZGVjb3JhdG9ycyB9IDogbnVsbDtcblxuICAgICAga2luZC5hZGQobmFtZSwgc3RvcnlGbiwge1xuICAgICAgICAuLi5wYXJhbWV0ZXJzLFxuICAgICAgICAuLi5kZWNvcmF0b3JQYXJhbXMsXG4gICAgICAgIF9faWQ6IHRvSWQoY29tcG9uZW50SWQgfHwga2luZE5hbWUsIHN0b3J5TmFtZSksXG4gICAgICB9KTtcbiAgICB9XG4gIH0pO1xufVxuXG5mdW5jdGlvbiBtYXBLaW5kc1RvRmlsZXMoc3RvcmllczogU3Rvcmllc1Jhdyk6IE1hcDxzdHJpbmcsIFNldDxzdHJpbmc+PiB7XG4gIHJldHVybiBPYmplY3QudmFsdWVzKHN0b3JpZXMpLnJlZHVjZShcbiAgICAoa2luZHMsIHsga2luZCwgcGFyYW1ldGVyczogeyBmaWxlTmFtZSB9IH0pID0+IGtpbmRzLnNldChmaWxlTmFtZSwgKGtpbmRzLmdldChmaWxlTmFtZSkgPz8gbmV3IFNldCgpKS5hZGQoa2luZCkpLFxuICAgIG5ldyBNYXA8c3RyaW5nLCBTZXQ8c3RyaW5nPj4oKSxcbiAgKTtcbn1cblxuZnVuY3Rpb24gcmVtb3ZlT2xkU3RvcmllcyhmaWxlbmFtZTogc3RyaW5nLCBzdG9yeVN0b3JlOiBTdG9yeVN0b3JlLCBraW5kcz86IFNldDxzdHJpbmc+KTogdm9pZCB7XG4gIGRlbGV0ZSByZXF1aXJlLmNhY2hlW2ZpbGVuYW1lXTtcblxuICBpZiAoIWtpbmRzIHx8IGtpbmRzLnNpemUgPT0gMCkgcmV0dXJuO1xuICAvLyBOT1RFIElmIHVzZXIgaGFzIG1vcmUgdGhhbiBvbmUgZmlsZSB3aXRoIHNhbWUga2luZC4gQW5kIG9uZSB0aGF0IGZpbGUgaGFzIGJlZW4gY2hhbmdlZCwgd2UgcmVtb3ZlIGFsbCBzdG9yaWVzIG9uIHRoYXQga2luZFxuICBraW5kcy5mb3JFYWNoKChraW5kKSA9PiBzdG9yeVN0b3JlLnJlbW92ZVN0b3J5S2luZChraW5kKSk7XG4gIHN0b3J5U3RvcmUuaW5jcmVtZW50UmV2aXNpb24oKTtcbn1cblxuZnVuY3Rpb24gd2F0Y2hTdG9yaWVzKHN0b3J5Ym9va0Rpcjogc3RyaW5nLCBzdG9yaWVzOiBTdG9yaWVzUmF3LCBmaWxlc09yQmxvYnM6IHN0cmluZ1tdKTogdm9pZCB7XG4gIGNvbnN0IHsgY2xpZW50QXBpLCBzdG9yeVN0b3JlIH0gPSBnZXRTdG9yeWJvb2tBcGkoKSA/PyB7fTtcbiAgaWYgKCFjbGllbnRBcGkgfHwgIXN0b3J5U3RvcmUpIHtcbiAgICBjb25zb2xlLmxvZyhcbiAgICAgIGBbQ3JlZXZleToke3Byb2Nlc3MucGlkfV06YCxcbiAgICAgIGBDYW4ndCBnZXQgU3Rvcnlib29rIGNsaWVudCBBUEkuIEhvdCByZWxvYWQgZm9yIHN0b3JpZXMgYXJlIG5vdCB3b3JraW5nLCBwbGVhc2UgY2hlY2sgeW91ciBzdG9yeWJvb2sgY29uZmlnIGZpbGVzYCxcbiAgICApO1xuICAgIHJldHVybjtcbiAgfVxuXG4gIGxldCBzdG9yaWVzQnlGaWxlczogTWFwPHN0cmluZywgU3RvcnlJbnB1dFtdPiA9IG5ldyBNYXAoKTtcbiAgbGV0IGtpbmRzQnlGaWxlcyA9IG1hcEtpbmRzVG9GaWxlcyhzdG9yaWVzKTtcblxuICAvLyBOT1RFIFVwZGF0ZSBraW5kcyBhZnRlciBmaWxlIHdpdGggc3RvcmllcyB3YXMgY2hhbmdlZFxuICBhZGRvbnMuZ2V0Q2hhbm5lbCgpLm9uKEV2ZW50cy5TRVRfU1RPUklFUywgKGRhdGE6IHsgc3RvcmllczogU3Rvcmllc1JhdyB9KSA9PiB7XG4gICAga2luZHNCeUZpbGVzID0gbWFwS2luZHNUb0ZpbGVzKGRhdGEuc3Rvcmllcyk7XG5cbiAgICBPYmplY3QudmFsdWVzKGRhdGEuc3RvcmllcykuZm9yRWFjaCgoc3RvcnkpID0+IHN0b3JpZXNCeUZpbGVzLmdldChzdG9yeS5wYXJhbWV0ZXJzLmZpbGVOYW1lKT8ucHVzaChzdG9yeSkpO1xuICAgIGFkZG9ucy5nZXRDaGFubmVsKCkuZW1pdCgnc3Rvcmllc1VwZGF0ZWQnLCBzdG9yaWVzQnlGaWxlcyk7XG4gICAgc3Rvcmllc0J5RmlsZXMgPSBuZXcgTWFwKCk7XG4gIH0pO1xuXG4gIC8vIE5PVEUgV2UgZG9uJ3Qgc3VwcG9ydCBSZXF1aXJlQ29udGV4dEFyZ3Mgb2JqZWN0cyB0byBwYXNzIGl0IGludG8gY2hva2lkYXJcbiAgY29uc3Qgd2F0Y2hlciA9IGNob2tpZGFyLndhdGNoKGZpbGVzT3JCbG9icywgeyBpZ25vcmVJbml0aWFsOiB0cnVlLCBjd2Q6IHN0b3J5Ym9va0RpciB9KTtcblxuICB3YXRjaGVyLm9uKCdhbGwnLCAoZXZlbnQsIGZpbGVuYW1lKSA9PiB7XG4gICAgY29uc3QgYWJzb2x1dGVGaWxlUGF0aCA9IHBhdGgucmVzb2x2ZShzdG9yeWJvb2tEaXIsIGZpbGVuYW1lKTtcbiAgICBpZiAoZXZlbnQgPT0gJ2FkZERpcicgfHwgZXZlbnQgPT0gJ3VubGlua0RpcicpIHJldHVybjtcblxuICAgIHN0b3JpZXNCeUZpbGVzLnNldChhYnNvbHV0ZUZpbGVQYXRoLCBbXSk7XG5cbiAgICBpZiAoZXZlbnQgIT0gJ2FkZCcpIHJlbW92ZU9sZFN0b3JpZXMoYWJzb2x1dGVGaWxlUGF0aCwgc3RvcnlTdG9yZSwga2luZHNCeUZpbGVzLmdldChhYnNvbHV0ZUZpbGVQYXRoKSk7XG4gICAgaWYgKGV2ZW50ICE9ICd1bmxpbmsnKSBsb2FkU3Rvcmllc0Zyb21GaWxlKGFic29sdXRlRmlsZVBhdGgsIGNsaWVudEFwaSk7XG4gIH0pO1xufVxuXG5mdW5jdGlvbiBsb2FkT2xkU3Rvcnlib29rQ29uZmlnKHN0b3J5Ym9va0Rpcjogc3RyaW5nKTogdm9pZCB7XG4gIGFkZG9ucy5nZXRDaGFubmVsKCkub25jZShFdmVudHMuU0VUX1NUT1JJRVMsIChkYXRhOiB7IHN0b3JpZXM6IFN0b3JpZXNSYXcgfSkgPT4ge1xuICAgIC8vIE5PVEUgS25vd24gbGltaXRhdGlvbnMsIHdlIGNhbid0IGdldCBiYXNlUGF0aCBhbmQgcmVnZXggZnJvbSByZXF1aXJlLmNvbnRleHQgY2FsbCBpbnNpZGUgYGNvbmZpZ3VyZWAsXG4gICAgLy8gc28gd2UgaGF2ZSBvbmx5IGNlcnRhaW4gbGlzdCBvZiBmaWxlcyBhY2NvcmRpbmcgYnkgc3Rvcmllcy5cbiAgICAvLyBJZiB1c2VyIGFkZCBuZXcgZmlsZSB3aXRoIHN0b3JpZXMsIHRoYXQgZmlsZSBjYW4ndCBiZSBsb2FkZWRcbiAgICB3YXRjaFN0b3JpZXMoXG4gICAgICBzdG9yeWJvb2tEaXIsXG4gICAgICBkYXRhLnN0b3JpZXMsXG4gICAgICBBcnJheS5mcm9tKG5ldyBTZXQoT2JqZWN0LnZhbHVlcyhkYXRhLnN0b3JpZXMpLm1hcCgoc3RvcnkpID0+IHN0b3J5LnBhcmFtZXRlcnMuZmlsZU5hbWUpKSksXG4gICAgKTtcbiAgfSk7XG4gIHJlcXVpcmVDb25maWcocGF0aC5qb2luKHN0b3J5Ym9va0RpciwgJ2NvbmZpZycpKTtcbn1cblxuZnVuY3Rpb24gbG9hZE5ld1N0b3J5Ym9va0NvbmZpZ3Moc3Rvcnlib29rRGlyOiBzdHJpbmcpOiB2b2lkIHtcbiAgcmVxdWlyZUNvbmZpZyhwYXRoLmpvaW4oc3Rvcnlib29rRGlyLCAncHJldmlldycpKTtcblxuICBjb25zdCB7IGNsaWVudEFwaSB9ID0gZ2V0U3Rvcnlib29rQXBpKCkgPz8ge307XG4gIGlmICghY2xpZW50QXBpKVxuICAgIHJldHVybiBjb25zb2xlLmxvZyhcbiAgICAgIGBbQ3JlZXZleToke3Byb2Nlc3MucGlkfV06YCxcbiAgICAgIGBDYW4ndCBnZXQgU3Rvcnlib29rIGNsaWVudCBBUEkuIEhvdCByZWxvYWQgZm9yIHN0b3JpZXMgYXJlIG5vdCB3b3JraW5nLCBwbGVhc2UgY2hlY2sgeW91ciBzdG9yeWJvb2sgY29uZmlnIGZpbGVzYCxcbiAgICApO1xuXG4gIGNvbnN0IHsgc3RvcmllcyA9IFtdIH06IHsgc3Rvcmllcz86IHVua25vd25bXSB9ID0gcmVxdWlyZUNvbmZpZyhwYXRoLmpvaW4oc3Rvcnlib29rRGlyLCAnbWFpbicpKTtcblxuICBhZGRvbnMuZ2V0Q2hhbm5lbCgpLm9uY2UoRXZlbnRzLlNFVF9TVE9SSUVTLCAoZGF0YTogeyBzdG9yaWVzOiBTdG9yaWVzUmF3IH0pID0+IHtcbiAgICBpZiAoIXN0b3JpZXMuZXZlcnkoaXNTdHJpbmcpKVxuICAgICAgY29uc29sZS5sb2coXG4gICAgICAgIFwiWW91ciBzdG9yaWVzIGFycmF5IGRlZmluZWQgaW4gJ21haW4nIGNvbmZpZyBmaWxlcyBjb250YWluIG5vbi1zdHJpbmcgZW50aXRpZXMuIENyZWV2ZXkgY2FuIHdhdGNoIGZpbGVzIG9ubHkgYnkgZ2xvYiBwYXR0ZXJuc1wiLFxuICAgICAgKTtcblxuICAgIHdhdGNoU3Rvcmllcyhzd