diff --git a/cypress/platform/knsv2.html b/cypress/platform/knsv2.html index 786ee2692..6e69533eb 100644 --- a/cypress/platform/knsv2.html +++ b/cypress/platform/knsv2.html @@ -69,12 +69,15 @@ block-beta id2("2") block id3["I am a wide one"] - id4 + block + id44("A final one") + id45("B final one") + end end - %% id4("A final one") + id4("Another final one") -
+block-beta id3["I am a wide one"] @@ -84,7 +87,9 @@ block-betaflowchart RL + subgraph "`one`" id + endflowchart RL diff --git a/packages/mermaid/src/dagre-wrapper/nodes.js b/packages/mermaid/src/dagre-wrapper/nodes.js index 9e57fb2b0..b95265f31 100644 --- a/packages/mermaid/src/dagre-wrapper/nodes.js +++ b/packages/mermaid/src/dagre-wrapper/nodes.js @@ -27,7 +27,7 @@ const question = async (parent, node) => { questionElem.attr('style', node.style); updateNodeBounds(node, questionElem); - node.intersect = function(point) { + node.intersect = function (point) { log.warn('Intersect called'); return intersect.polygon(node, points, point); }; @@ -52,7 +52,7 @@ const choice = (parent, node) => { const choice = shapeSvg.insert('polygon', ':first-child').attr( 'points', points - .map(function(d) { + .map(function (d) { return d.x + ',' + d.y; }) .join(' ') @@ -62,7 +62,7 @@ const choice = (parent, node) => { node.width = 28; node.height = 28; - node.intersect = function(point) { + node.intersect = function (point) { return intersect.circle(node, 14, point); }; @@ -89,7 +89,7 @@ const hexagon = async (parent, node) => { hex.attr('style', node.style); updateNodeBounds(node, hex); - node.intersect = function(point) { + node.intersect = function (point) { return intersect.polygon(node, points, point); }; @@ -115,7 +115,7 @@ const rect_left_inv_arrow = async (parent, node) => { node.width = w + h; node.height = h; - node.intersect = function(point) { + node.intersect = function (point) { return intersect.polygon(node, points, point); }; @@ -138,7 +138,7 @@ const lean_right = async (parent, node) => { el.attr('style', node.style); updateNodeBounds(node, el); - node.intersect = function(point) { + node.intersect = function (point) { return intersect.polygon(node, points, point); }; @@ -161,7 +161,7 @@ const lean_left = async (parent, node) => { el.attr('style', node.style); updateNodeBounds(node, el); - node.intersect = function(point) { + node.intersect = function (point) { return intersect.polygon(node, points, point); }; @@ -184,7 +184,7 @@ const trapezoid = async (parent, node) => { el.attr('style', node.style); updateNodeBounds(node, el); - node.intersect = function(point) { + node.intersect = function (point) { return intersect.polygon(node, points, point); }; @@ -207,7 +207,7 @@ const inv_trapezoid = async (parent, node) => { el.attr('style', node.style); updateNodeBounds(node, el); - node.intersect = function(point) { + node.intersect = function (point) { return intersect.polygon(node, points, point); }; @@ -231,7 +231,7 @@ const rect_right_inv_arrow = async (parent, node) => { el.attr('style', node.style); updateNodeBounds(node, el); - node.intersect = function(point) { + node.intersect = function (point) { return intersect.polygon(node, points, point); }; @@ -281,7 +281,7 @@ const cylinder = async (parent, node) => { updateNodeBounds(node, el); - node.intersect = function(point) { + node.intersect = function (point) { const pos = intersect.rect(node, point); const x = pos.x - node.x; @@ -350,7 +350,55 @@ const rect = async (parent, node) => { updateNodeBounds(node, rect); - node.intersect = function(point) { + node.intersect = function (point) { + return intersect.rect(node, point); + }; + + return shapeSvg; +}; + +const composite = async (parent, node) => { + console.log('This got called'); + const { shapeSvg, bbox, halfPadding } = await labelHelper( + parent, + node, + 'node ' + node.classes, + true + ); + + // add the rect + const rect = shapeSvg.insert('rect', ':first-child'); + + // const totalWidth = bbox.width + node.padding * 2; + // const totalHeight = bbox.height + node.padding * 2; + const totalWidth = node.positioned ? node.width : bbox.width + node.padding; + const totalHeight = node.positioned ? node.height : bbox.height + node.padding; + const x = node.positioned ? -totalWidth / 2 : -bbox.width / 2 - halfPadding; + const y = node.positioned ? -totalHeight / 2 : -bbox.height / 2 - halfPadding; + rect + .attr('class', 'basic cluster composite label-container') + .attr('style', node.style) + .attr('rx', node.rx) + .attr('ry', node.ry) + .attr('x', x) + .attr('y', y) + .attr('width', totalWidth) + .attr('height', totalHeight); + + if (node.props) { + const propKeys = new Set(Object.keys(node.props)); + if (node.props.borders) { + applyNodePropertyBorders(rect, node.props.borders, totalWidth, totalHeight); + propKeys.delete('borders'); + } + propKeys.forEach((propKey) => { + log.warn(`Unknown node property ${propKey}`); + }); + } + + updateNodeBounds(node, rect); + + node.intersect = function (point) { return intersect.rect(node, point); }; @@ -383,7 +431,7 @@ const labelRect = async (parent, node) => { updateNodeBounds(node, rect); - node.intersect = function(point) { + node.intersect = function (point) { return intersect.rect(node, point); }; @@ -495,20 +543,20 @@ const rectWithTitle = (parent, node) => { select(descr).attr( 'transform', 'translate( ' + - // (titleBox.width - bbox.width) / 2 + - (bbox.width > titleBox.width ? 0 : (titleBox.width - bbox.width) / 2) + - ', ' + - (titleBox.height + halfPadding + 5) + - ')' + // (titleBox.width - bbox.width) / 2 + + (bbox.width > titleBox.width ? 0 : (titleBox.width - bbox.width) / 2) + + ', ' + + (titleBox.height + halfPadding + 5) + + ')' ); select(text).attr( 'transform', 'translate( ' + - // (titleBox.width - bbox.width) / 2 + - (bbox.width < titleBox.width ? 0 : -(titleBox.width - bbox.width) / 2) + - ', ' + - 0 + - ')' + // (titleBox.width - bbox.width) / 2 + + (bbox.width < titleBox.width ? 0 : -(titleBox.width - bbox.width) / 2) + + ', ' + + 0 + + ')' ); // Get the size of the label @@ -537,7 +585,7 @@ const rectWithTitle = (parent, node) => { updateNodeBounds(node, rect); - node.intersect = function(point) { + node.intersect = function (point) { return intersect.rect(node, point); }; @@ -563,7 +611,7 @@ const stadium = async (parent, node) => { updateNodeBounds(node, rect); - node.intersect = function(point) { + node.intersect = function (point) { return intersect.rect(node, point); }; @@ -587,7 +635,7 @@ const circle = async (parent, node) => { updateNodeBounds(node, circle); - node.intersect = function(point) { + node.intersect = function (point) { log.info('Circle intersect', node, bbox.width / 2 + halfPadding, point); return intersect.circle(node, bbox.width / 2 + halfPadding, point); }; @@ -625,7 +673,7 @@ const doublecircle = async (parent, node) => { updateNodeBounds(node, outerCircle); - node.intersect = function(point) { + node.intersect = function (point) { log.info('DoubleCircle intersect', node, bbox.width / 2 + halfPadding + gap, point); return intersect.circle(node, bbox.width / 2 + halfPadding + gap, point); }; @@ -655,7 +703,7 @@ const subroutine = async (parent, node) => { el.attr('style', node.style); updateNodeBounds(node, el); - node.intersect = function(point) { + node.intersect = function (point) { return intersect.polygon(node, points, point); }; @@ -674,7 +722,7 @@ const start = (parent, node) => { updateNodeBounds(node, circle); - node.intersect = function(point) { + node.intersect = function (point) { return intersect.circle(node, 7, point); }; @@ -706,7 +754,7 @@ const forkJoin = (parent, node, dir) => { updateNodeBounds(node, shape); node.height = node.height + node.padding / 2; node.width = node.width + node.padding / 2; - node.intersect = function(point) { + node.intersect = function (point) { return intersect.rect(node, point); }; @@ -727,7 +775,7 @@ const end = (parent, node) => { updateNodeBounds(node, circle); - node.intersect = function(point) { + node.intersect = function (point) { return intersect.circle(node, 7, point); }; @@ -892,10 +940,10 @@ const class_box = (parent, node) => { select(classTitleLabel).attr( 'transform', 'translate( ' + - ((-1 * maxWidth) / 2 + diffX) + - ', ' + - ((-1 * maxHeight) / 2 + verticalPos) + - ')' + ((-1 * maxWidth) / 2 + diffX) + + ', ' + + ((-1 * maxHeight) / 2 + verticalPos) + + ')' ); verticalPos += classTitleBBox.height + rowPadding; @@ -912,10 +960,10 @@ const class_box = (parent, node) => { select(lbl).attr( 'transform', 'translate( ' + - -maxWidth / 2 + - ', ' + - ((-1 * maxHeight) / 2 + verticalPos + lineHeight / 2) + - ')' + -maxWidth / 2 + + ', ' + + ((-1 * maxHeight) / 2 + verticalPos + lineHeight / 2) + + ')' ); //get the height of the bounding box of each member if exists const memberBBox = lbl?.getBBox(); @@ -950,7 +998,7 @@ const class_box = (parent, node) => { updateNodeBounds(node, rect); - node.intersect = function(point) { + node.intersect = function (point) { return intersect.rect(node, point); }; @@ -959,6 +1007,7 @@ const class_box = (parent, node) => { const shapes = { rhombus: question, + composite, question, rect, labelRect, @@ -1040,10 +1089,10 @@ export const positionNode = (node) => { el.attr( 'transform', 'translate(' + - (node.x + diff - node.width / 2) + - ', ' + - (node.y - node.height / 2 - padding) + - ')' + (node.x + diff - node.width / 2) + + ', ' + + (node.y - node.height / 2 - padding) + + ')' ); } else { el.attr('transform', 'translate(' + node.x + ', ' + node.y + ')'); diff --git a/packages/mermaid/src/diagrams/block/blockDB.ts b/packages/mermaid/src/diagrams/block/blockDB.ts index 50af7965f..2dce9e323 100644 --- a/packages/mermaid/src/diagrams/block/blockDB.ts +++ b/packages/mermaid/src/diagrams/block/blockDB.ts @@ -27,7 +27,7 @@ const populateBlockDatabase = (blockList: Block[], parent: Block): void => { } else { if (!block.label) { if (block.type === 'composite') { - block.label = 'x'; + block.label = ''; } else { block.label = block.id; } diff --git a/packages/mermaid/src/diagrams/block/layout.ts b/packages/mermaid/src/diagrams/block/layout.ts index e008d7882..8756646ef 100644 --- a/packages/mermaid/src/diagrams/block/layout.ts +++ b/packages/mermaid/src/diagrams/block/layout.ts @@ -2,15 +2,17 @@ import { BlockDB } from './blockDB.js'; import type { Block } from './blockTypes.js'; function calcBlockSizes(block: Block, db: BlockDB) { - let totalWidth = 0; - let totalHeight = 0; + const totalWidth = 0; + const totalHeight = 0; + let maxWidth = 0; + let maxHeight = 0; + const padding = 20; + if (block.children) { for (const child of block.children) { calcBlockSizes(child, db); } // find max width of children - let maxWidth = 0; - let maxHeight = 0; for (const child of block.children) { const { width, height, x, y } = child.size || { width: 0, height: 0, x: 0, y: 0 }; if (width > maxWidth) { @@ -30,39 +32,41 @@ function calcBlockSizes(block: Block, db: BlockDB) { } // Position items relative to self - let x = 0; + let x = -padding / 2; const y = 0; - const padding = 10; + + let accumulatedPaddingX = 0; for (const child of block.children) { if (child.size) { child.size.x = x; child.size.y = y; x += maxWidth + padding; } - if (x > totalWidth) { - totalWidth = x; - } - if (y > totalHeight) { - totalHeight = y; - } + accumulatedPaddingX += padding; } } if (block.children?.length > 0) { - block.size = { width: totalWidth, height: totalHeight, x: 0, y: 0 }; + const numChildren = block.children.length; + block.size = { + width: numChildren * (maxWidth + padding) + padding, + height: totalHeight + 4 * padding, + x: 0, + y: 0, + }; } console.log('layoutBlock (done)', block); } function positionBlock(parent: Block, block: Block, db: BlockDB) { console.log('layout position block', parent.id, parent?.size?.x, block.id, block?.size?.x); - let x = 0; + let parentX = 0; let y = 0; if (parent) { - x = parent?.size?.x || 0; + parentX = parent?.size?.x || 0; y = parent?.size?.y || 0; } - if (block.size) { - block.size.x = block.size.x + x - block.size.width / 2; + if (block.size && block.id !== 'root') { + block.size.x = parentX + block.size.x + -block.size.width / 2; block.size.y = block.size.y + y; } if (block.children) { @@ -104,9 +108,9 @@ export function layout(db: BlockDB) { const blocks = db.getBlocks(); const root = { id: 'root', type: 'composite', children: blocks } as Block; calcBlockSizes(root, db); - console.log('layout getBlocks', db.getBlocks()); // Position blocks relative to parents positionBlock(root, root, db); + console.log('getBlocks', JSON.stringify(db.getBlocks(), null, 2)); minX = 0; minY = 0; diff --git a/packages/mermaid/src/diagrams/block/renderHelpers.ts b/packages/mermaid/src/diagrams/block/renderHelpers.ts index a17cda607..5bbe279e7 100644 --- a/packages/mermaid/src/diagrams/block/renderHelpers.ts +++ b/packages/mermaid/src/diagrams/block/renderHelpers.ts @@ -26,12 +26,17 @@ function getNodeFromBlock(block: Block, db: BlockDB, positioned = false) { let radious = 0; let _shape = ''; let layoutOptions = {}; + console.log('This is the type:', vertex.type); // Set the shape based parameters switch (vertex.type) { case 'round': radious = 5; _shape = 'rect'; break; + case 'composite': + radious = 4; + _shape = 'composite'; + break; case 'square': _shape = 'rect'; break; diff --git a/packages/mermaid/src/diagrams/block/styles.ts b/packages/mermaid/src/diagrams/block/styles.ts index a4af4f128..e1194f0d1 100644 --- a/packages/mermaid/src/diagrams/block/styles.ts +++ b/packages/mermaid/src/diagrams/block/styles.ts @@ -42,6 +42,8 @@ const getStyles = (options: FlowChartStyleOptions) => color: ${options.titleColor}; } + + .label text,span,p { fill: ${options.nodeTextColor || options.textColor}; color: ${options.nodeTextColor || options.textColor}; @@ -103,9 +105,11 @@ const getStyles = (options: FlowChartStyleOptions) => // background-color: } - .cluster rect { - fill: ${options.clusterBkg}; - stroke: ${options.clusterBorder}; + .node .cluster { + // fill: ${fade(options.mainBkg, 0.5)}; + fill: ${fade(options.clusterBkg, 0.5)}; + stroke: ${fade(options.clusterBorder, 0.2)}; + box-shadow: rgba(50, 50, 93, 0.25) 0px 13px 27px -5px, rgba(0, 0, 0, 0.3) 0px 8px 16px -8px; stroke-width: 1px; } diff --git a/vite.config.ts.timestamp-1696335530501-05072b5e79635.mjs b/vite.config.ts.timestamp-1696335530501-05072b5e79635.mjs new file mode 100644 index 000000000..e020937df --- /dev/null +++ b/vite.config.ts.timestamp-1696335530501-05072b5e79635.mjs @@ -0,0 +1,190 @@ +// .vite/jisonTransformer.ts +import jison from "file:///Users/knsv/source/git/mermaid/node_modules/.pnpm/jison@0.4.18/node_modules/jison/lib/jison.js"; +var transformJison = (src) => { + const parser = new jison.Generator(src, { + moduleType: "js", + "token-stack": true + }); + const source = parser.generate({ moduleMain: "() => {}" }); + const exporter = ` + parser.parser = parser; + export { parser }; + export default parser; + `; + return `${source} ${exporter}`; +}; + +// .vite/jisonPlugin.ts +var fileRegex = /\.(jison)$/; +function jison2() { + return { + name: "jison", + transform(src, id) { + if (fileRegex.test(id)) { + return { + code: transformJison(src), + map: null + // provide source map if available + }; + } + } + }; +} + +// .vite/jsonSchemaPlugin.ts +import { load, JSON_SCHEMA } from "file:///Users/knsv/source/git/mermaid/node_modules/.pnpm/js-yaml@4.1.0/node_modules/js-yaml/dist/js-yaml.mjs"; +import assert from "node:assert"; +import Ajv2019 from "file:///Users/knsv/source/git/mermaid/node_modules/.pnpm/ajv@8.12.0/node_modules/ajv/dist/2019.js"; +var MERMAID_CONFIG_DIAGRAM_KEYS = [ + "flowchart", + "sequence", + "gantt", + "journey", + "class", + "state", + "er", + "pie", + "quadrantChart", + "requirement", + "mindmap", + "timeline", + "gitGraph", + "c4", + "sankey" +]; +function generateDefaults(mermaidConfigSchema) { + const ajv = new Ajv2019({ + useDefaults: true, + allowUnionTypes: true, + strict: true + }); + ajv.addKeyword({ + keyword: "meta:enum", + // used by jsonschema2md + errors: false + }); + ajv.addKeyword({ + keyword: "tsType", + // used by json-schema-to-typescript + errors: false + }); + const mermaidDefaultConfig = {}; + assert.ok(mermaidConfigSchema.$defs); + const baseDiagramConfig = mermaidConfigSchema.$defs.BaseDiagramConfig; + for (const key of MERMAID_CONFIG_DIAGRAM_KEYS) { + const subSchemaRef = mermaidConfigSchema.properties[key].$ref; + const [root, defs, defName] = subSchemaRef.split("/"); + assert.strictEqual(root, "#"); + assert.strictEqual(defs, "$defs"); + const subSchema = { + $schema: mermaidConfigSchema.$schema, + $defs: mermaidConfigSchema.$defs, + ...mermaidConfigSchema.$defs[defName] + }; + const validate2 = ajv.compile(subSchema); + mermaidDefaultConfig[key] = {}; + for (const required of subSchema.required ?? []) { + if (subSchema.properties[required] === void 0 && baseDiagramConfig.properties[required]) { + mermaidDefaultConfig[key][required] = baseDiagramConfig.properties[required].default; + } + } + if (!validate2(mermaidDefaultConfig[key])) { + throw new Error( + `schema for subconfig ${key} does not have valid defaults! Errors were ${JSON.stringify( + validate2.errors, + void 0, + 2 + )}` + ); + } + } + const validate = ajv.compile(mermaidConfigSchema); + if (!validate(mermaidDefaultConfig)) { + throw new Error( + `Mermaid config JSON Schema does not have valid defaults! Errors were ${JSON.stringify( + validate.errors, + void 0, + 2 + )}` + ); + } + return mermaidDefaultConfig; +} +function jsonSchemaPlugin() { + return { + name: "json-schema-plugin", + transform(src, id) { + const idAsUrl = new URL(id, "file:///"); + if (!idAsUrl.pathname.endsWith("schema.yaml")) { + return; + } + if (idAsUrl.searchParams.get("only-defaults")) { + const jsonSchema = load(src, { + filename: idAsUrl.pathname, + // only allow JSON types in our YAML doc (will probably be default in YAML 1.3) + // e.g. `true` will be parsed a boolean `true`, `True` will be parsed as string `"True"`. + schema: JSON_SCHEMA + }); + return { + code: `export default ${JSON.stringify(generateDefaults(jsonSchema), void 0, 2)};`, + map: null + // no source map + }; + } else { + return { + code: `export default ${JSON.stringify( + load(src, { + filename: idAsUrl.pathname, + // only allow JSON types in our YAML doc (will probably be default in YAML 1.3) + // e.g. `true` will be parsed a boolean `true`, `True` will be parsed as string `"True"`. + schema: JSON_SCHEMA + }), + void 0, + 2 + )};`, + map: null + // provide source map if available + }; + } + } + }; +} + +// vite.config.ts +import typescript from "file:///Users/knsv/source/git/mermaid/node_modules/.pnpm/@rollup+plugin-typescript@11.1.1_typescript@5.1.3/node_modules/@rollup/plugin-typescript/dist/es/index.js"; +import { defineConfig } from "file:///Users/knsv/source/git/mermaid/node_modules/.pnpm/vitest@0.33.0_@vitest+ui@0.33.0_jsdom@22.0.0/node_modules/vitest/dist/config.js"; +var vite_config_default = defineConfig({ + resolve: { + extensions: [".js"] + }, + plugins: [ + jison2(), + jsonSchemaPlugin(), + // handles .schema.yaml JSON Schema files + // @ts-expect-error According to the type definitions, rollup plugins are incompatible with vite + typescript({ compilerOptions: { declaration: false } }) + ], + test: { + environment: "jsdom", + globals: true, + // TODO: should we move this to a mermaid-core package? + setupFiles: ["packages/mermaid/src/tests/setup.ts"], + coverage: { + provider: "v8", + reporter: ["text", "json", "html", "lcov"], + reportsDirectory: "./coverage/vitest", + exclude: ["**/node_modules/**", "**/tests/**", "**/__mocks__/**"] + } + }, + build: { + /** If you set esmExternals to true, this plugins assumes that + all external dependencies are ES modules */ + commonjsOptions: { + esmExternals: true + } + } +}); +export { + vite_config_default as default +}; +//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLnZpdGUvamlzb25UcmFuc2Zvcm1lci50cyIsICIudml0ZS9qaXNvblBsdWdpbi50cyIsICIudml0ZS9qc29uU2NoZW1hUGx1Z2luLnRzIiwgInZpdGUuY29uZmlnLnRzIl0sCiAgInNvdXJjZXNDb250ZW50IjogWyJjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZGlybmFtZSA9IFwiL1VzZXJzL2tuc3Yvc291cmNlL2dpdC9tZXJtYWlkLy52aXRlXCI7Y29uc3QgX192aXRlX2luamVjdGVkX29yaWdpbmFsX2ZpbGVuYW1lID0gXCIvVXNlcnMva25zdi9zb3VyY2UvZ2l0L21lcm1haWQvLnZpdGUvamlzb25UcmFuc2Zvcm1lci50c1wiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9pbXBvcnRfbWV0YV91cmwgPSBcImZpbGU6Ly8vVXNlcnMva25zdi9zb3VyY2UvZ2l0L21lcm1haWQvLnZpdGUvamlzb25UcmFuc2Zvcm1lci50c1wiO2ltcG9ydCBqaXNvbiBmcm9tICdqaXNvbic7XG5cbmV4cG9ydCBjb25zdCB0cmFuc2Zvcm1KaXNvbiA9IChzcmM6IHN0cmluZyk6IHN0cmluZyA9PiB7XG4gIGNvbnN0IHBhcnNlciA9IG5ldyBqaXNvbi5HZW5lcmF0b3Ioc3JjLCB7XG4gICAgbW9kdWxlVHlwZTogJ2pzJyxcbiAgICAndG9rZW4tc3RhY2snOiB0cnVlLFxuICB9KTtcbiAgY29uc3Qgc291cmNlID0gcGFyc2VyLmdlbmVyYXRlKHsgbW9kdWxlTWFpbjogJygpID0+IHt9JyB9KTtcbiAgY29uc3QgZXhwb3J0ZXIgPSBgXG5cdHBhcnNlci5wYXJzZXIgPSBwYXJzZXI7XG5cdGV4cG9ydCB7IHBhcnNlciB9O1xuXHRleHBvcnQgZGVmYXVsdCBwYXJzZXI7XG5cdGA7XG4gIHJldHVybiBgJHtzb3VyY2V9ICR7ZXhwb3J0ZXJ9YDtcbn07XG4iLCAiY29uc3QgX192aXRlX2luamVjdGVkX29yaWdpbmFsX2Rpcm5hbWUgPSBcIi9Vc2Vycy9rbnN2L3NvdXJjZS9naXQvbWVybWFpZC8udml0ZVwiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9maWxlbmFtZSA9IFwiL1VzZXJzL2tuc3Yvc291cmNlL2dpdC9tZXJtYWlkLy52aXRlL2ppc29uUGx1Z2luLnRzXCI7Y29uc3QgX192aXRlX2luamVjdGVkX29yaWdpbmFsX2ltcG9ydF9tZXRhX3VybCA9IFwiZmlsZTovLy9Vc2Vycy9rbnN2L3NvdXJjZS9naXQvbWVybWFpZC8udml0ZS9qaXNvblBsdWdpbi50c1wiO2ltcG9ydCB7IHRyYW5zZm9ybUppc29uIH0gZnJvbSAnLi9qaXNvblRyYW5zZm9ybWVyLmpzJztcbmNvbnN0IGZpbGVSZWdleCA9IC9cXC4oamlzb24pJC87XG5cbmV4cG9ydCBkZWZhdWx0IGZ1bmN0aW9uIGppc29uKCkge1xuICByZXR1cm4ge1xuICAgIG5hbWU6ICdqaXNvbicsXG5cbiAgICB0cmFuc2Zvcm0oc3JjOiBzdHJpbmcsIGlkOiBzdHJpbmcpIHtcbiAgICAgIGlmIChmaWxlUmVnZXgudGVzdChpZCkpIHtcbiAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICBjb2RlOiB0cmFuc2Zvcm1KaXNvbihzcmMpLFxuICAgICAgICAgIG1hcDogbnVsbCwgLy8gcHJvdmlkZSBzb3VyY2UgbWFwIGlmIGF2YWlsYWJsZVxuICAgICAgICB9O1xuICAgICAgfVxuICAgIH0sXG4gIH07XG59XG4iLCAiY29uc3QgX192aXRlX2luamVjdGVkX29yaWdpbmFsX2Rpcm5hbWUgPSBcIi9Vc2Vycy9rbnN2L3NvdXJjZS9naXQvbWVybWFpZC8udml0ZVwiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9maWxlbmFtZSA9IFwiL1VzZXJzL2tuc3Yvc291cmNlL2dpdC9tZXJtYWlkLy52aXRlL2pzb25TY2hlbWFQbHVnaW4udHNcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfaW1wb3J0X21ldGFfdXJsID0gXCJmaWxlOi8vL1VzZXJzL2tuc3Yvc291cmNlL2dpdC9tZXJtYWlkLy52aXRlL2pzb25TY2hlbWFQbHVnaW4udHNcIjtpbXBvcnQgeyBsb2FkLCBKU09OX1NDSEVNQSB9IGZyb20gJ2pzLXlhbWwnO1xuaW1wb3J0IGFzc2VydCBmcm9tICdub2RlOmFzc2VydCc7XG5pbXBvcnQgQWp2MjAxOSwgeyB0eXBlIEpTT05TY2hlbWFUeXBlIH0gZnJvbSAnYWp2L2Rpc3QvMjAxOS5qcyc7XG5pbXBvcnQgeyBQbHVnaW5PcHRpb24gfSBmcm9tICd2aXRlJztcblxuaW1wb3J0IHR5cGUgeyBNZXJtYWlkQ29uZmlnLCBCYXNlRGlhZ3JhbUNvbmZpZyB9IGZyb20gJy4uL3BhY2thZ2VzL21lcm1haWQvc3JjL2NvbmZpZy50eXBlLmpzJztcblxuLyoqXG4gKiBBbGwgb2YgdGhlIGtleXMgaW4gdGhlIG1lcm1haWQgY29uZmlnIHRoYXQgaGF2ZSBhIG1lcm1haWQgZGlhZ3JhbSBjb25maWcuXG4gKi9cbmNvbnN0IE1FUk1BSURfQ09ORklHX0RJQUdSQU1fS0VZUyA9IFtcbiAgJ2Zsb3djaGFydCcsXG4gICdzZXF1ZW5jZScsXG4gICdnYW50dCcsXG4gICdqb3VybmV5JyxcbiAgJ2NsYXNzJyxcbiAgJ3N0YXRlJyxcbiAgJ2VyJyxcbiAgJ3BpZScsXG4gICdxdWFkcmFudENoYXJ0JyxcbiAgJ3JlcXVpcmVtZW50JyxcbiAgJ21pbmRtYXAnLFxuICAndGltZWxpbmUnLFxuICAnZ2l0R3JhcGgnLFxuICAnYzQnLFxuICAnc2Fua2V5Jyxcbl0gYXMgY29uc3Q7XG5cbi8qKlxuICogR2VuZXJhdGUgZGVmYXVsdCB2YWx1ZXMgZnJvbSB0aGUgSlNPTiBTY2hlbWEuXG4gKlxuICogQUpWIGRvZXMgbm90IHN1cHBvcnQgbmVzdGVkIGRlZmF1bHQgdmFsdWVzIHlldCAob3IgZGVmYXVsdCB2YWx1ZXMgd2l0aCAkcmVmKSxcbiAqIHNvIHdlIG5lZWQgdG8gbWFudWFsbHkgZmluZCB0aGVtICh0aGlzIG1heSBiZSBmaXhlZCBpbiBhanYgdjkpLlxuICpcbiAqIEBwYXJhbSBtZXJtYWlkQ29uZmlnU2NoZW1hIC0gVGhlIE1lcm1haWQgSlNPTiBTY2hlbWEgdG8gdXNlLlxuICogQHJldHVybnMgVGhlIGRlZmF1bHQgbWVybWFpZCBjb25maWcgb2JqZWN0LlxuICovXG5mdW5jdGlvbiBnZW5lcmF0ZURlZmF1bHRzKG1lcm1haWRDb25maWdTY2hlbWE6IEpTT05TY2hlbWFUeXBlPE1lcm1haWRDb25maWc+KSB7XG4gIGNvbnN0IGFqdiA9IG5ldyBBanYyMDE5KHtcbiAgICB1c2VEZWZhdWx0czogdHJ1ZSxcbiAgICBhbGxvd1VuaW9uVHlwZXM6IHRydWUsXG4gICAgc3RyaWN0OiB0cnVlLFxuICB9KTtcblxuICBhanYuYWRkS2V5d29yZCh7XG4gICAga2V5d29yZDogJ21ldGE6ZW51bScsIC8vIHVzZWQgYnkganNvbnNjaGVtYTJtZFxuICAgIGVycm9yczogZmFsc2UsXG4gIH0pO1xuICBhanYuYWRkS2V5d29yZCh7XG4gICAga2V5d29yZDogJ3RzVHlwZScsIC8vIHVzZWQgYnkganNvbi1zY2hlbWEtdG8tdHlwZXNjcmlwdFxuICAgIGVycm9yczogZmFsc2UsXG4gIH0pO1xuXG4gIC8vIGFqdiBjdXJyZW50bHkgZG9lc24ndCBzdXBwb3J0IG5lc3RlZCBkZWZhdWx0IHZhbHVlcywgc2VlIGh0dHBzOi8vZ2l0aHViLmNvbS9hanYtdmFsaWRhdG9yL2Fqdi9pc3N1ZXMvMTcxOFxuICAvLyAobWF5IGJlIGZpeGVkIGluIHY5KSBzbyB3ZSBuZWVkIHRvIG1hbnVhbGx5IHVzZSBzdWItc2NoZW1hc1xuICBjb25zdCBtZXJtYWlkRGVmYXVsdENvbmZpZyA9IHt9O1xuXG4gIGFzc2VydC5vayhtZXJtYWlkQ29uZmlnU2NoZW1hLiRkZWZzKTtcbiAgY29uc3QgYmFzZURpYWdyYW1Db25maWcgPSBtZXJtYWlkQ29uZmlnU2NoZW1hLiRkZWZzLkJhc2VEaWFncmFtQ29uZmlnO1xuXG4gIGZvciAoY29uc3Qga2V5IG9mIE1FUk1BSURfQ09ORklHX0RJQUdSQU1fS0VZUykge1xuICAgIGNvbnN0IHN1YlNjaGVtYVJlZiA9IG1lcm1haWRDb25maWdTY2hlbWEucHJvcGVydGllc1trZXldLiRyZWY7XG4gICAgY29uc3QgW3Jvb3QsIGRlZnMsIGRlZk5hbWVdID0gc3ViU2NoZW1hUmVmLnNwbGl0KCcvJyk7XG4gICAgYXNzZXJ0LnN0cmljdEVxdWFsKHJvb3QsICcjJyk7XG4gICAgYXNzZXJ0LnN0cmljdEVxdWFsKGRlZnMsICckZGVmcycpO1xuICAgIGNvbnN0IHN1YlNjaGVtYSA9IHtcbiAgICAgICRzY2hlbWE6IG1lcm1haWRDb25maWdTY2hlbWEuJHNjaGVtYSxcbiAgICAgICRkZWZzOiBtZXJtYWlkQ29uZmlnU2NoZW1hLiRkZWZzLFxuICAgICAgLi4ubWVybWFpZENvbmZpZ1NjaGVtYS4kZGVmc1tkZWZOYW1lXSxcbiAgICB9IGFzIEpTT05TY2hlbWFUeXBlPEJhc2VEaWFncmFtQ29uZmlnPjtcblxuICAgIGNvbnN0IHZhbGlkYXRlID0gYWp2LmNvbXBpbGUoc3ViU2NoZW1hKTtcblxuICAgIG1lcm1haWREZWZhdWx0Q29uZmlnW2tleV0gPSB7fTtcblxuICAgIGZvciAoY29uc3QgcmVxdWlyZWQgb2Ygc3ViU2NoZW1hLnJlcXVpcmVkID8/IFtdKSB7XG4gICAgICBpZiAoc3ViU2NoZW1hLnByb3BlcnRpZXNbcmVxdWlyZWRdID09PSB1bmRlZmluZWQgJiYgYmFzZURpYWdyYW1Db25maWcucHJvcGVydGllc1tyZXF1aXJlZF0pIHtcbiAgICAgICAgbWVybWFpZERlZmF1bHRDb25maWdba2V5XVtyZXF1aXJlZF0gPSBiYXNlRGlhZ3JhbUNvbmZpZy5wcm9wZXJ0aWVzW3JlcXVpcmVkXS5kZWZhdWx0O1xuICAgICAgfVxuICAgIH1cbiAgICBpZiAoIXZhbGlkYXRlKG1lcm1haWREZWZhdWx0Q29uZmlnW2tleV0pKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICAgIGBzY2hlbWEgZm9yIHN1YmNvbmZpZyAke2tleX0gZG9lcyBub3QgaGF2ZSB2YWxpZCBkZWZhdWx0cyEgRXJyb3JzIHdlcmUgJHtKU09OLnN0cmluZ2lmeShcbiAgICAgICAgICB2YWxpZGF0ZS5lcnJvcnMsXG4gICAgICAgICAgdW5kZWZpbmVkLFxuICAgICAgICAgIDJcbiAgICAgICAgKX1gXG4gICAgICApO1xuICAgIH1cbiAgfVxuXG4gIGNvbnN0IHZhbGlkYXRlID0gYWp2LmNvbXBpbGUobWVybWFpZENvbmZpZ1NjaGVtYSk7XG5cbiAgaWYgKCF2YWxpZGF0ZShtZXJtYWlkRGVmYXVsdENvbmZpZykpIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICBgTWVybWFpZCBjb25maWcgSlNPTiBTY2hlbWEgZG9lcyBub3QgaGF2ZSB2YWxpZCBkZWZhdWx0cyEgRXJyb3JzIHdlcmUgJHtKU09OLnN0cmluZ2lmeShcbiAgICAgICAgdmFsaWRhdGUuZXJyb3JzLFxuICAgICAgICB1bmRlZmluZWQsXG4gICAgICAgIDJcbiAgICAgICl9YFxuICAgICk7XG4gIH1cblxuICByZXR1cm4gbWVybWFpZERlZmF1bHRDb25maWc7XG59XG5cbi8qKlxuICogVml0ZSBwbHVnaW4gdGhhdCBoYW5kbGVzIEpTT04gU2NoZW1hcyBzYXZlZCBhcyBhIGAuc2NoZW1hLnlhbWxgIGZpbGUuXG4gKlxuICogVXNlIGBteS1leGFtcGxlLnNjaGVtYS55YW1sP29ubHktZGVmYXVsdHM9dHJ1ZWAgdG8gb25seSBsb2FkIHRoZSBkZWZhdWx0IHZhbHVlcy5cbiAqL1xuZXhwb3J0IGRlZmF1bHQgZnVuY3Rpb24ganNvblNjaGVtYVBsdWdpbigpOiBQbHVnaW5PcHRpb24ge1xuICByZXR1cm4ge1xuICAgIG5hbWU6ICdqc29uLXNjaGVtYS1wbHVnaW4nLFxuICAgIHRyYW5zZm9ybShzcmM6IHN0cmluZywgaWQ6IHN0cmluZykge1xuICAgICAgY29uc3QgaWRBc1VybCA9IG5ldyBVUkwoaWQsICdmaWxlOi8vLycpO1xuXG4gICAgICBpZiAoIWlkQXNVcmwucGF0aG5hbWUuZW5kc1dpdGgoJ3NjaGVtYS55YW1sJykpIHtcbiAgICAgICAgcmV0dXJuO1xuICAgICAgfVxuXG4gICAgICBpZiAoaWRBc1VybC5zZWFyY2hQYXJhbXMuZ2V0KCdvbmx5LWRlZmF1bHRzJykpIHtcbiAgICAgICAgY29uc3QganNvblNjaGVtYSA9IGxvYWQoc3JjLCB7XG4gICAgICAgICAgZmlsZW5hbWU6IGlkQXNVcmwucGF0aG5hbWUsXG4gICAgICAgICAgLy8gb25seSBhbGxvdyBKU09OIHR5cGVzIGluIG91ciBZQU1MIGRvYyAod2lsbCBwcm9iYWJseSBiZSBkZWZhdWx0IGluIFlBTUwgMS4zKVxuICAgICAgICAgIC8vIGUuZy4gYHRydWVgIHdpbGwgYmUgcGFyc2VkIGEgYm9vbGVhbiBgdHJ1ZWAsIGBUcnVlYCB3aWxsIGJlIHBhcnNlZCBhcyBzdHJpbmcgYFwiVHJ1ZVwiYC5cbiAgICAgICAgICBzY2hlbWE6IEpTT05fU0NIRU1BLFxuICAgICAgICB9KSBhcyBKU09OU2NoZW1hVHlwZTxNZXJtYWlkQ29uZmlnPjtcbiAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICBjb2RlOiBgZXhwb3J0IGRlZmF1bHQgJHtKU09OLnN0cmluZ2lmeShnZW5lcmF0ZURlZmF1bHRzKGpzb25TY2hlbWEpLCB1bmRlZmluZWQsIDIpfTtgLFxuICAgICAgICAgIG1hcDogbnVsbCwgLy8gbm8gc291cmNlIG1hcFxuICAgICAgICB9O1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICBjb2RlOiBgZXhwb3J0IGRlZmF1bHQgJHtKU09OLnN0cmluZ2lmeShcbiAgICAgICAgICAgIGxvYWQoc3JjLCB7XG4gICAgICAgICAgICAgIGZpbGVuYW1lOiBpZEFzVXJsLnBhdGhuYW1lLFxuICAgICAgICAgICAgICAvLyBvbmx5IGFsbG93IEpTT04gdHlwZXMgaW4gb3VyIFlBTUwgZG9jICh3aWxsIHByb2JhYmx5IGJlIGRlZmF1bHQgaW4gWUFNTCAxLjMpXG4gICAgICAgICAgICAgIC8vIGUuZy4gYHRydWVgIHdpbGwgYmUgcGFyc2VkIGEgYm9vbGVhbiBgdHJ1ZWAsIGBUcnVlYCB3aWxsIGJlIHBhcnNlZCBhcyBzdHJpbmcgYFwiVHJ1ZVwiYC5cbiAgICAgICAgICAgICAgc2NoZW1hOiBKU09OX1NDSEVNQSxcbiAgICAgICAgICAgIH0pLFxuICAgICAgICAgICAgdW5kZWZpbmVkLFxuICAgICAgICAgICAgMlxuICAgICAgICAgICl9O2AsXG4gICAgICAgICAgbWFwOiBudWxsLCAvLyBwcm92aWRlIHNvdXJjZSBtYXAgaWYgYXZhaWxhYmxlXG4gICAgICAgIH07XG4gICAgICB9XG4gICAgfSxcbiAgfTtcbn1cbiIsICJjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZGlybmFtZSA9IFwiL1VzZXJzL2tuc3Yvc291cmNlL2dpdC9tZXJtYWlkXCI7Y29uc3QgX192aXRlX2luamVjdGVkX29yaWdpbmFsX2ZpbGVuYW1lID0gXCIvVXNlcnMva25zdi9zb3VyY2UvZ2l0L21lcm1haWQvdml0ZS5jb25maWcudHNcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfaW1wb3J0X21ldGFfdXJsID0gXCJmaWxlOi8vL1VzZXJzL2tuc3Yvc291cmNlL2dpdC9tZXJtYWlkL3ZpdGUuY29uZmlnLnRzXCI7aW1wb3J0IGppc29uIGZyb20gJy4vLnZpdGUvamlzb25QbHVnaW4uanMnO1xuaW1wb3J0IGpzb25TY2hlbWFQbHVnaW4gZnJvbSAnLi8udml0ZS9qc29uU2NoZW1hUGx1Z2luLmpzJztcbmltcG9ydCB0eXBlc2NyaXB0IGZyb20gJ0Byb2xsdXAvcGx1Z2luLXR5cGVzY3JpcHQnO1xuaW1wb3J0IHsgZGVmaW5lQ29uZmlnIH0gZnJvbSAndml0ZXN0L2NvbmZpZyc7XG5cbmV4cG9ydCBkZWZhdWx0IGRlZmluZUNvbmZpZyh7XG4gIHJlc29sdmU6IHtcbiAgICBleHRlbnNpb25zOiBbJy5qcyddLFxuICB9LFxuICBwbHVnaW5zOiBbXG4gICAgamlzb24oKSxcbiAgICBqc29uU2NoZW1hUGx1Z2luKCksIC8vIGhhbmRsZXMgLnNjaGVtYS55YW1sIEpTT04gU2NoZW1hIGZpbGVzXG4gICAgLy8gQHRzLWV4cGVjdC1lcnJvciBBY2NvcmRpbmcgdG8gdGhlIHR5cGUgZGVmaW5pdGlvbnMsIHJvbGx1cCBwbHVnaW5zIGFyZSBpbmNvbXBhdGlibGUgd2l0aCB2aXRlXG4gICAgdHlwZXNjcmlwdCh7IGNvbXBpbGVyT3B0aW9uczogeyBkZWNsYXJhdGlvbjogZmFsc2UgfSB9KSxcbiAgXSxcbiAgdGVzdDoge1xuICAgIGVudmlyb25tZW50OiAnanNkb20nLFxuICAgIGdsb2JhbHM6IHRydWUsXG4gICAgLy8gVE9ETzogc2hvdWxkIHdlIG1vdmUgdGhpcyB0byBhIG1lcm1haWQtY29yZSBwYWNrYWdlP1xuICAgIHNldHVwRmlsZXM6IFsncGFja2FnZXMvbWVybWFpZC9zcmMvdGVzdHMvc2V0dXAudHMnXSxcbiAgICBjb3ZlcmFnZToge1xuICAgICAgcHJvdmlkZXI6ICd2OCcsXG4gICAgICByZXBvcnRlcjogWyd0ZXh0JywgJ2pzb24nLCAnaHRtbCcsICdsY292J10sXG4gICAgICByZXBvcnRzRGlyZWN0b3J5OiAnLi9jb3ZlcmFnZS92aXRlc3QnLFxuICAgICAgZXhjbHVkZTogWycqKi9ub2RlX21vZHVsZXMvKionLCAnKiovdGVzdHMvKionLCAnKiovX19tb2Nrc19fLyoqJ10sXG4gICAgfSxcbiAgfSxcbiAgYnVpbGQ6IHtcbiAgICAvKiogSWYgeW91IHNldCBlc21FeHRlcm5hbHMgdG8gdHJ1ZSwgdGhpcyBwbHVnaW5zIGFzc3VtZXMgdGhhdFxuICAgICBhbGwgZXh0ZXJuYWwgZGVwZW5kZW5jaWVzIGFyZSBFUyBtb2R1bGVzICovXG5cbiAgICBjb21tb25qc09wdGlvbnM6IHtcbiAgICAgIGVzbUV4dGVybmFsczogdHJ1ZSxcbiAgICB9LFxuICB9LFxufSk7XG4iXSwKICAibWFwcGluZ3MiOiAiO0FBQXdTLE9BQU8sV0FBVztBQUVuVCxJQUFNLGlCQUFpQixDQUFDLFFBQXdCO0FBQ3JELFFBQU0sU0FBUyxJQUFJLE1BQU0sVUFBVSxLQUFLO0FBQUEsSUFDdEMsWUFBWTtBQUFBLElB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QUEsSUFDTixVQUFVLEtBQWEsSUFBWTtBQUNqQyxZQUFNLFVBQVUsSUFBSSxJQUFJLElBQUksVUFBVTtBQUV0QyxVQUFJLENBQUMsUUFBUSxTQUFTLFNBQVMsYUFBYSxHQUFHO0FBQzdDO0FBQUEsTUFDRjtBQUVBLFVBQUksUUFBUSxhQUFhLElBQUksZUFBZSxHQUFHO0FBQzdDLGNBQU0sYUFBYSxLQUFLLEtBQUs7QUFBQSxVQUMzQixVQUFVLFFBQVE7QUFBQTtBQUFBO0FBQUEsVUFHbEIsUUFBUTtBQUFBLFFBQ1YsQ0FBQztBQUNELGVBQU87QUFBQSxVQUNMLE1BQU0sa0JBQWtCLEtBQUssVUFBVSxpQkFBaUIsVUFBVSxHQUFHLFFBQVcsQ0FBQztBQUFBLFVBQ2pGLEtBQUs7QUFBQTtBQUFBLFFBQ1A7QUFBQSxNQUNGLE9BQU87QUFDTCxlQUFPO0FBQUEsVUFDTCxNQUFNLGtCQUFrQixLQUFLO0FBQUEsWUFDM0IsS0FBSyxLQUFLO0FBQUEsY0FDUixVQUFVLFFBQVE7QUFBQTtBQUFBO0FBQUEsY0FHbEIsUUFBUTtBQUFBLFlBQ1YsQ0FBQztBQUFBLFlBQ0Q7QUFBQSxZQUNBO0FBQUEsVUFDRjtBQUFBLFVBQ0EsS0FBSztBQUFBO0FBQUEsUUFDUDtBQUFBLE1BQ0Y7QUFBQSxJQUNGO0FBQUEsRUFDRjtBQUNGOzs7QUNuSkEsT0FBTyxnQkFBZ0I7QUFDdkIsU0FBUyxvQkFBb0I7QUFFN0IsSUFBTyxzQkFBUSxhQUFhO0FBQUEsRUFDMUIsU0FBUztBQUFBLElBQ1AsWUFBWSxDQUFDLEtBQUs7QUFBQSxFQUNwQjtBQUFBLEVBQ0EsU0FBUztBQUFBLElBQ1BDLE9BQU07QUFBQSxJQUNOLGlCQUFpQjtBQUFBO0FBQUE7QUFBQSxJQUVqQixXQUFXLEVBQUUsaUJBQWlCLEVBQUUsYUFBYSxNQUFNLEVBQUUsQ0FBQztBQUFBLEVBQ3hEO0FBQUEsRUFDQSxNQUFNO0FBQUEsSUFDSixhQUFhO0FBQUEsSUFDYixTQUFTO0FBQUE7QUFBQSxJQUVULFlBQVksQ0FBQyxxQ0FBcUM7QUFBQSxJQUNsRCxVQUFVO0FBQUEsTUFDUixVQUFVO0FBQUEsTUFDVixVQUFVLENBQUMsUUFBUSxRQUFRLFFBQVEsTUFBTTtBQUFBLE1BQ3pDLGtCQUFrQjtBQUFBLE1BQ2xCLFNBQVMsQ0FBQyxzQkFBc0IsZUFBZSxpQkFBaUI7QUFBQSxJQUNsRTtBQUFBLEVBQ0Y7QUFBQSxFQUNBLE9BQU87QUFBQTtBQUFBO0FBQUEsSUFJTCxpQkFBaUI7QUFBQSxNQUNmLGNBQWM7QUFBQSxJQUNoQjtBQUFBLEVBQ0Y7QUFDRixDQUFDOyIsCiAgIm5hbWVzIjogWyJqaXNvbiIsICJ2YWxpZGF0ZSIsICJqaXNvbiJdCn0K