From bf1775ded6a5ee9f68bb6e1e5c91d5e1ffbad540 Mon Sep 17 00:00:00 2001 From: Alejandro Date: Tue, 17 Dec 2024 19:03:39 +0100 Subject: [PATCH 01/66] feat(plugin-doc-coverage): add mvp version of a plugin doc coverage based on compodoc --- packages/plugin-doc-coverage/README.md | 205 ++++++++++++++++++ packages/plugin-doc-coverage/eslint.config.js | 21 ++ .../mocks/documentation.json | 11 + packages/plugin-doc-coverage/package.json | 47 ++++ packages/plugin-doc-coverage/project.json | 42 ++++ packages/plugin-doc-coverage/src/bin.ts | 3 + packages/plugin-doc-coverage/src/index.ts | 4 + .../plugin-doc-coverage/src/lib/config.ts | 25 +++ .../src/lib/config.unit.test.ts | 52 +++++ .../src/lib/doc-coverage-plugin.ts | 76 +++++++ .../src/lib/doc-coverage-plugin.unit.test.ts | 87 ++++++++ .../src/lib/runner/constants.ts | 11 + .../src/lib/runner/index.ts | 82 +++++++ .../src/lib/runner/runner.integration.test.ts | 80 +++++++ packages/plugin-doc-coverage/tsconfig.json | 23 ++ .../plugin-doc-coverage/tsconfig.lib.json | 16 ++ .../plugin-doc-coverage/tsconfig.test.json | 13 ++ .../vite.config.integration.ts | 29 +++ .../plugin-doc-coverage/vite.config.unit.ts | 31 +++ 19 files changed, 858 insertions(+) create mode 100644 packages/plugin-doc-coverage/README.md create mode 100644 packages/plugin-doc-coverage/eslint.config.js create mode 100644 packages/plugin-doc-coverage/mocks/documentation.json create mode 100644 packages/plugin-doc-coverage/package.json create mode 100644 packages/plugin-doc-coverage/project.json create mode 100644 packages/plugin-doc-coverage/src/bin.ts create mode 100644 packages/plugin-doc-coverage/src/index.ts create mode 100644 packages/plugin-doc-coverage/src/lib/config.ts create mode 100644 packages/plugin-doc-coverage/src/lib/config.unit.test.ts create mode 100644 packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.ts create mode 100644 packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.unit.test.ts create mode 100644 packages/plugin-doc-coverage/src/lib/runner/constants.ts create mode 100644 packages/plugin-doc-coverage/src/lib/runner/index.ts create mode 100644 packages/plugin-doc-coverage/src/lib/runner/runner.integration.test.ts create mode 100644 packages/plugin-doc-coverage/tsconfig.json create mode 100644 packages/plugin-doc-coverage/tsconfig.lib.json create mode 100644 packages/plugin-doc-coverage/tsconfig.test.json create mode 100644 packages/plugin-doc-coverage/vite.config.integration.ts create mode 100644 packages/plugin-doc-coverage/vite.config.unit.ts diff --git a/packages/plugin-doc-coverage/README.md b/packages/plugin-doc-coverage/README.md new file mode 100644 index 000000000..0b4fb33e6 --- /dev/null +++ b/packages/plugin-doc-coverage/README.md @@ -0,0 +1,205 @@ +# @code-pushup/doc-coverage-plugin + +[![npm](https://img.shields.io/npm/v/%40code-pushup%2Fdoc-coverage-plugin.svg)](https://www.npmjs.com/package/@code-pushup/doc-coverage-plugin) +[![downloads](https://img.shields.io/npm/dm/%40code-pushup%2Fdoc-coverage-plugin)](https://npmtrends.com/@code-pushup/doc-coverage-plugin) +[![dependencies](https://img.shields.io/librariesio/release/npm/%40code-pushup%2Fdoc-coverage-plugin)](https://www.npmjs.com/package/@code-pushup/doc-coverage-plugin?activeTab=dependencies) + +๐Ÿ“š **Code PushUp plugin for tracking documentation coverage.** ๐Ÿ“ + +This plugin allows you to measure and track documentation coverage in your TypeScript/JavaScript project. +It analyzes your codebase and checks for documentation on different code elements like classes, functions, interfaces, types, and variables. + +Measured documentation types are mapped to Code PushUp audits in the following way: + +- The value is in range 0-100 and represents the documentation coverage for all passed results (_documented / total_) +- The score is value converted to 0-1 range +- Missing documentation is mapped to issues in the audit details (undocumented classes, functions, interfaces, etc.) + +## Getting started + +1. If you haven't already, install [@code-pushup/cli](../cli/README.md) and create a configuration file. + +2. Install as a dev dependency with your package manager: + + ```sh + npm install --save-dev @code-pushup/doc-coverage-plugin + ``` + + ```sh + yarn add --dev @code-pushup/doc-coverage-plugin + ``` + + ```sh + pnpm add --save-dev @code-pushup/doc-coverage-plugin + ``` + +3. Add Compodoc to your project. You can follow the instructions [here](https://compodoc.app/guides/installation.html). + +4. Add this plugin to the `plugins` array in your Code PushUp CLI config file (e.g. `code-pushup.config.js`). + + Pass the target files to analyze and optionally specify which types of documentation you want to track. + All documentation types are measured by default. If you wish to focus on a subset of offered types, define them in `docTypes`. + + The configuration will look similarly to the following: + + ```js + import docCoveragePlugin from '@code-pushup/doc-coverage-plugin'; + + export default { + // ... + plugins: [ + // ... + await docCoveragePlugin({ + coverageToolCommand: { + command: 'npx', + args: ['compodoc', '-p', 'tsconfig.doc.json', '-e', 'json'], + }, + }), + ], + }; + ``` + +5. (Optional) Reference individual audits or the provided plugin group which you wish to include in custom categories (use `npx code-pushup print-config` to list audits and groups). + + ๐Ÿ’ก Assign weights based on what influence each documentation type should have on the overall category score (assign weight 0 to only include as extra info, without influencing category score). + + ```js + export default { + // ... + categories: [ + { + slug: 'documentation', + title: 'Documentation', + refs: [ + { + type: 'group', + plugin: 'doc-coverage', + slug: 'doc-coverage', + weight: 1, + }, + // ... + ], + }, + // ... + ], + }; + ``` + +6. Run the CLI with `npx code-pushup collect` and view or upload report (refer to [CLI docs](../cli/README.md)). + +## About documentation coverage + +Documentation coverage is a metric that indicates what percentage of your code elements have proper documentation. It helps ensure your codebase is well-documented and maintainable. + +The plugin provides a single audit that measures the overall percentage of documentation coverage across your codebase: + +- **Percentage coverage**: Measures how many percent of the codebase have documentation. + +## Plugin architecture + +### Plugin configuration specification + +The plugin accepts the following parameters: + +- (optional) `coverageToolCommand`: If you wish to run your documentation coverage tool (compodoc) to generate the results first, you may define it here. + - `command`: Command to run coverage tool (e.g. `npx`). + - `args`: Arguments to be passed to the coverage tool (e.g. `['compodoc', '-p', 'tsconfig.doc.json', '-e', 'json']`). +- `outputPath`: Path to the documentation.json file. Defaults to `'documentation/documentation.json'`. + +### Audits and group + +This plugin provides a group for convenient declaration in your config. When defined this way, all measured documentation type audits have the same weight. + +```ts + // ... + categories: [ + { + slug: 'documentation', + title: 'Documentation', + refs: [ + { + type: 'group', + plugin: 'doc-coverage', + slug: 'doc-coverage', + weight: 1, + }, + // ... + ], + }, + // ... + ], +``` + +Each documentation type still has its own audit. So when you want to include a subset of documentation types or assign different weights to them, you can do so in the following way: + +```ts + // ... + categories: [ + { + slug: 'documentation', + title: 'Documentation', + refs: [ + { + type: 'audit', + plugin: 'doc-coverage', + slug: 'class-doc-coverage', + weight: 2, + }, + { + type: 'audit', + plugin: 'doc-coverage', + slug: 'function-doc-coverage', + weight: 1, + }, + // ... + ], + }, + // ... + ], +``` + +### Audit output + +The plugin outputs a single audit that measures the overall documentation coverage percentage of your codebase. + +For instance, this is an example of the plugin output: + +```json +{ + "packageName": "@code-pushup/doc-coverage-plugin", + "version": "0.57.0", + "title": "Documentation coverage", + "slug": "doc-coverage", + "icon": "folder-src", + "duration": 920, + "date": "2024-12-17T16:45:28.581Z", + "audits": [ + { + "slug": "percentage-coverage", + "displayValue": "16 %", + "value": 16, + "score": 0.16, + "details": { + "issues": [] + }, + "title": "Percentage of codebase with documentation", + "description": "Measures how many % of the codebase have documentation." + } + ], + "description": "Official Code PushUp documentation coverage plugin.", + "docsUrl": "https://www.npmjs.com/package/@code-pushup/doc-coverage-plugin/", + "groups": [ + { + "slug": "doc-coverage", + "refs": [ + { + "slug": "percentage-coverage", + "weight": 1 + } + ], + "title": "Documentation coverage metrics", + "description": "Group containing all defined documentation coverage types as audits." + } + ] +} +``` diff --git a/packages/plugin-doc-coverage/eslint.config.js b/packages/plugin-doc-coverage/eslint.config.js new file mode 100644 index 000000000..40165321a --- /dev/null +++ b/packages/plugin-doc-coverage/eslint.config.js @@ -0,0 +1,21 @@ +import tseslint from 'typescript-eslint'; +import baseConfig from '../../eslint.config.js'; + +export default tseslint.config( + ...baseConfig, + { + files: ['**/*.ts'], + languageOptions: { + parserOptions: { + projectService: true, + tsconfigRootDir: import.meta.dirname, + }, + }, + }, + { + files: ['**/*.json'], + rules: { + '@nx/dependency-checks': 'error', + }, + }, +); diff --git a/packages/plugin-doc-coverage/mocks/documentation.json b/packages/plugin-doc-coverage/mocks/documentation.json new file mode 100644 index 000000000..7a608253c --- /dev/null +++ b/packages/plugin-doc-coverage/mocks/documentation.json @@ -0,0 +1,11 @@ +{ + "coverage": { + "count": 85, + "files": { + "src/app/services/my.service.ts": { + "documented": 17, + "total": 20 + } + } + } +} diff --git a/packages/plugin-doc-coverage/package.json b/packages/plugin-doc-coverage/package.json new file mode 100644 index 000000000..1c2ebe241 --- /dev/null +++ b/packages/plugin-doc-coverage/package.json @@ -0,0 +1,47 @@ +{ + "name": "@code-pushup/doc-coverage-plugin", + "version": "0.57.0", + "description": "Code PushUp plugin for tracking documentation coverage ๐Ÿ“š", + "license": "MIT", + "homepage": "https://github.com/code-pushup/cli/tree/main/packages/plugin-doc-coverage#readme", + "bugs": { + "url": "https://github.com/code-pushup/cli/issues?q=is%3Aissue%20state%3Aopen%20type%3ABug%20label%3A\"๐Ÿงฉ%20doc-coverage-plugin\"" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/code-pushup/cli.git", + "directory": "packages/plugin-doc-coverage" + }, + "keywords": [ + "CLI", + "Code PushUp", + "plugin", + "automation", + "developer tools", + "conformance", + "documentation coverage", + "documentation", + "docs", + "KPI tracking", + "automated feedback", + "regression guard", + "actionable feedback", + "audit", + "score monitoring" + ], + "publishConfig": { + "access": "public" + }, + "type": "module", + "dependencies": { + "@code-pushup/models": "0.57.0", + "@code-pushup/utils": "0.57.0", + "ansis": "^3.3.0", + "zod": "^3.22.4" + }, + "peerDependenciesMeta": { + "@nx/devkit": { + "optional": true + } + } +} diff --git a/packages/plugin-doc-coverage/project.json b/packages/plugin-doc-coverage/project.json new file mode 100644 index 000000000..ea971b133 --- /dev/null +++ b/packages/plugin-doc-coverage/project.json @@ -0,0 +1,42 @@ +{ + "name": "plugin-doc-coverage", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/plugin-doc-coverage/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": ["{options.outputPath}"], + "options": { + "outputPath": "dist/packages/plugin-doc-coverage", + "main": "packages/plugin-doc-coverage/src/index.ts", + "tsConfig": "packages/plugin-doc-coverage/tsconfig.lib.json", + "additionalEntryPoints": ["packages/plugin-doc-coverage/src/bin.ts"], + "assets": ["packages/plugin-doc-coverage/*.md"] + } + }, + "lint": { + "executor": "@nx/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": [ + "packages/plugin-doc-coverage/**/*.ts", + "packages/plugin-doc-coverage/package.json" + ] + } + }, + "unit-test": { + "executor": "@nx/vite:test", + "options": { + "configFile": "packages/plugin-doc-coverage/vite.config.unit.ts" + } + }, + "integration-test": { + "executor": "@nx/vite:test", + "options": { + "configFile": "packages/plugin-doc-coverage/vite.config.integration.ts" + } + } + }, + "tags": ["scope:plugin", "type:feature", "publishable"] +} diff --git a/packages/plugin-doc-coverage/src/bin.ts b/packages/plugin-doc-coverage/src/bin.ts new file mode 100644 index 000000000..bf6572a76 --- /dev/null +++ b/packages/plugin-doc-coverage/src/bin.ts @@ -0,0 +1,3 @@ +import { executeRunner } from './lib/runner/index.js'; + +await executeRunner(); diff --git a/packages/plugin-doc-coverage/src/index.ts b/packages/plugin-doc-coverage/src/index.ts new file mode 100644 index 000000000..d26f57375 --- /dev/null +++ b/packages/plugin-doc-coverage/src/index.ts @@ -0,0 +1,4 @@ +import { docCoveragePlugin } from './lib/doc-coverage-plugin.js'; + +export default docCoveragePlugin; +export type { DocCoveragePluginConfig } from './lib/config.js'; diff --git a/packages/plugin-doc-coverage/src/lib/config.ts b/packages/plugin-doc-coverage/src/lib/config.ts new file mode 100644 index 000000000..139f2d77a --- /dev/null +++ b/packages/plugin-doc-coverage/src/lib/config.ts @@ -0,0 +1,25 @@ +import { z } from 'zod'; + +export type DocType = 'percentage-coverage'; + +export const docCoveragePluginConfigSchema = z.object({ + coverageToolCommand: z + .object({ + command: z + .string({ description: 'Command to run coverage tool (compodoc).' }) + .min(1), + args: z + .array(z.string(), { + description: 'Arguments to be passed to the coverage tool.', + }) + .optional(), + }) + .optional(), + outputPath: z + .string({ description: 'Path to the documentation.json file.' }) + .default('documentation/documentation.json'), +}); + +export type DocCoveragePluginConfig = z.infer< + typeof docCoveragePluginConfigSchema +>; diff --git a/packages/plugin-doc-coverage/src/lib/config.unit.test.ts b/packages/plugin-doc-coverage/src/lib/config.unit.test.ts new file mode 100644 index 000000000..3964fa186 --- /dev/null +++ b/packages/plugin-doc-coverage/src/lib/config.unit.test.ts @@ -0,0 +1,52 @@ +import { describe, expect, it } from 'vitest'; +import { + type DocCoveragePluginConfig, + docCoveragePluginConfigSchema, +} from './config.js'; + +describe('docCoveragePluginConfigSchema', () => { + it('accepts a documentation coverage configuration with all entities', () => { + expect(() => + docCoveragePluginConfigSchema.parse({ + coverageToolCommand: { + command: 'npx @compodoc/compodoc', + args: ['-p', 'tsconfig.json'], + }, + outputPath: 'documentation/custom-doc.json', + } satisfies DocCoveragePluginConfig), + ).not.toThrow(); + }); + + it('accepts a minimal documentation coverage configuration', () => { + expect(() => + docCoveragePluginConfigSchema.parse({} satisfies DocCoveragePluginConfig), + ).not.toThrow(); + }); + + it('uses default output path when not provided', () => { + const config = {} satisfies DocCoveragePluginConfig; + const parsed = docCoveragePluginConfigSchema.parse(config); + + expect(parsed.outputPath).toBe('documentation/documentation.json'); + }); + + it('throws for missing command in coverageToolCommand', () => { + expect(() => + docCoveragePluginConfigSchema.parse({ + coverageToolCommand: { + args: ['-p', 'tsconfig.json'], + }, + }), + ).toThrow('invalid_type'); + }); + + it('accepts empty args in coverageToolCommand', () => { + expect(() => + docCoveragePluginConfigSchema.parse({ + coverageToolCommand: { + command: 'npx @compodoc/compodoc', + }, + } satisfies DocCoveragePluginConfig), + ).not.toThrow(); + }); +}); diff --git a/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.ts b/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.ts new file mode 100644 index 000000000..0519eed82 --- /dev/null +++ b/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.ts @@ -0,0 +1,76 @@ +import { createRequire } from 'node:module'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import type { Group, PluginConfig } from '@code-pushup/models'; +import { + type DocCoveragePluginConfig, + docCoveragePluginConfigSchema, +} from './config.js'; +import { createRunnerConfig } from './runner/index.js'; + +/** + * Instantiates Code PushUp documentation coverage plugin for core config. + * + * @example + * import docCoveragePlugin from '@code-pushup/doc-coverage-plugin' + * + * export default { + * // ... core config ... + * plugins: [ + * // ... other plugins ... + * await docCoveragePlugin({ + * + * docTypes: ['class', 'function'] + * }) + * ] + * } + * + * @returns Plugin configuration. + */ +export async function docCoveragePlugin( + config: DocCoveragePluginConfig, +): Promise { + const docCoverageConfig = docCoveragePluginConfigSchema.parse(config); + + const audits = [ + { + slug: 'percentage-coverage', + title: 'Percentage of codebase with documentation', + description: 'Measures how many % of the codebase have documentation.', + }, + ]; + + const group: Group = { + slug: 'doc-coverage', + title: 'Documentation coverage metrics', + description: + 'Group containing all defined documentation coverage types as audits.', + refs: audits.map(audit => ({ + ...audit, + weight: 1, + })), + }; + + const runnerScriptPath = path.join( + fileURLToPath(path.dirname(import.meta.url)), + '..', + 'bin.js', + ); + + const packageJson = createRequire(import.meta.url)( + '../../package.json', + ) as typeof import('../../package.json'); + + return { + slug: 'doc-coverage', + title: 'Documentation coverage', + icon: 'folder-src', + description: 'Official Code PushUp documentation coverage plugin.', + docsUrl: 'https://www.npmjs.com/package/@code-pushup/doc-coverage-plugin/', + packageName: packageJson.name, + version: packageJson.version, + audits, + groups: [group], + runner: await createRunnerConfig(runnerScriptPath, docCoverageConfig), + }; +} diff --git a/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.unit.test.ts b/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.unit.test.ts new file mode 100644 index 000000000..fbb2c452a --- /dev/null +++ b/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.unit.test.ts @@ -0,0 +1,87 @@ +import { describe, expect, it } from 'vitest'; +import type { RunnerConfig } from '@code-pushup/models'; +import { docCoveragePlugin } from './doc-coverage-plugin.js'; + +vi.mock('./runner/index.ts', () => ({ + createRunnerConfig: vi.fn().mockReturnValue({ + command: 'node', + outputFile: 'runner-output.json', + } satisfies RunnerConfig), +})); + +describe('docCoveragePlugin', () => { + it('should initialise a Documentation coverage plugin', async () => { + await expect( + docCoveragePlugin({ + outputPath: 'documentation/documentation.json', + }), + ).resolves.toStrictEqual( + expect.objectContaining({ + slug: 'doc-coverage', + title: 'Documentation coverage', + audits: expect.any(Array), + groups: expect.any(Array), + runner: expect.any(Object), + }), + ); + }); + + it('should generate percentage coverage audit', async () => { + await expect( + docCoveragePlugin({ + outputPath: 'documentation/documentation.json', + }), + ).resolves.toStrictEqual( + expect.objectContaining({ + audits: [ + { + slug: 'percentage-coverage', + title: 'Percentage of codebase with documentation', + description: expect.stringContaining( + 'how many % of the codebase have documentation', + ), + }, + ], + }), + ); + }); + + it('should provide a documentation coverage group', async () => { + await expect( + docCoveragePlugin({ + outputPath: 'documentation/documentation.json', + }), + ).resolves.toStrictEqual( + expect.objectContaining({ + groups: [ + expect.objectContaining({ + slug: 'doc-coverage', + title: 'Documentation coverage metrics', + refs: [ + expect.objectContaining({ + slug: 'percentage-coverage', + weight: 1, + }), + ], + }), + ], + }), + ); + }); + + it('should include package metadata', async () => { + await expect( + docCoveragePlugin({ + outputPath: 'documentation/documentation.json', + }), + ).resolves.toStrictEqual( + expect.objectContaining({ + icon: 'folder-src', + description: expect.stringContaining('documentation coverage plugin'), + docsUrl: expect.stringContaining('npmjs.com'), + packageName: expect.any(String), + version: expect.any(String), + }), + ); + }); +}); diff --git a/packages/plugin-doc-coverage/src/lib/runner/constants.ts b/packages/plugin-doc-coverage/src/lib/runner/constants.ts new file mode 100644 index 000000000..8b9843ee6 --- /dev/null +++ b/packages/plugin-doc-coverage/src/lib/runner/constants.ts @@ -0,0 +1,11 @@ +import path from 'node:path'; +import { pluginWorkDir } from '@code-pushup/utils'; + +export const WORKDIR = pluginWorkDir('doc-coverage'); + +export const PLUGIN_CONFIG_PATH = path.join( + process.cwd(), + WORKDIR, + 'plugin-config.json', +); +export const RUNNER_OUTPUT_PATH = path.join(WORKDIR, 'runner-output.json'); diff --git a/packages/plugin-doc-coverage/src/lib/runner/index.ts b/packages/plugin-doc-coverage/src/lib/runner/index.ts new file mode 100644 index 000000000..638777aaa --- /dev/null +++ b/packages/plugin-doc-coverage/src/lib/runner/index.ts @@ -0,0 +1,82 @@ +import { bold } from 'ansis'; +import { writeFile } from 'node:fs/promises'; +import path from 'node:path'; +import type { AuditOutput, RunnerConfig } from '@code-pushup/models'; +import { + ProcessError, + ensureDirectoryExists, + executeProcess, + filePathToCliArg, + readJsonFile, + ui, +} from '@code-pushup/utils'; +import type { DocCoveragePluginConfig } from '../config.js'; +import { PLUGIN_CONFIG_PATH, RUNNER_OUTPUT_PATH } from './constants.js'; + +export { PLUGIN_CONFIG_PATH, RUNNER_OUTPUT_PATH } from './constants.js'; + +export async function executeRunner(): Promise { + const { outputPath, coverageToolCommand } = + await readJsonFile(PLUGIN_CONFIG_PATH); + if (coverageToolCommand != null) { + const { command, args = [] } = coverageToolCommand; + try { + await executeProcess({ command, args }); + } catch (error) { + if (error instanceof ProcessError) { + ui().logger.error(bold('stdout from failed Compodoc process:')); + ui().logger.error(error.stdout); + ui().logger.error(bold('stderr from failed Compodoc process:')); + ui().logger.error(error.stderr); + } + throw new Error( + 'Doc Coverage plugin: Running Compodoc failed. Please check the error above.', + ); + } + } + + try { + // From the output of Compodoc, we can get the coverage percentage. + const docData: { coverage: { count: number } } = + await readJsonFile(outputPath); + const coverage = docData.coverage.count || 0; + + const auditOutputs: AuditOutput[] = [ + { + slug: 'percentage-coverage', + value: coverage, + score: coverage / 100, + displayValue: `${coverage} %`, + }, + ]; + + await ensureDirectoryExists(path.dirname(RUNNER_OUTPUT_PATH)); + await writeFile(RUNNER_OUTPUT_PATH, JSON.stringify(auditOutputs)); + } catch (error) { + if (error instanceof ProcessError) { + ui().logger.error(bold('stdout from failed coverage tool process:')); + ui().logger.error(error.stdout); + ui().logger.error(bold('stderr from failed coverage tool process:')); + ui().logger.error(error.stderr); + + throw new Error( + 'Doc Coverage plugin: Running Compodoc failed. Please check the error above.', + ); + } + } +} + +export async function createRunnerConfig( + scriptPath: string, + config: DocCoveragePluginConfig, +): Promise { + // Create JSON config for executeRunner + await ensureDirectoryExists(path.dirname(PLUGIN_CONFIG_PATH)); + await writeFile(PLUGIN_CONFIG_PATH, JSON.stringify(config)); + + return { + command: 'node', + args: [filePathToCliArg(scriptPath)], + outputFile: RUNNER_OUTPUT_PATH, + }; +} diff --git a/packages/plugin-doc-coverage/src/lib/runner/runner.integration.test.ts b/packages/plugin-doc-coverage/src/lib/runner/runner.integration.test.ts new file mode 100644 index 000000000..f67e5791c --- /dev/null +++ b/packages/plugin-doc-coverage/src/lib/runner/runner.integration.test.ts @@ -0,0 +1,80 @@ +import { writeFile } from 'node:fs/promises'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { describe, it } from 'vitest'; +import type { + AuditOutput, + AuditOutputs, + RunnerConfig, +} from '@code-pushup/models'; +import { readJsonFile, removeDirectoryIfExists } from '@code-pushup/utils'; +import type { DocCoveragePluginConfig } from '../config.js'; +import { + PLUGIN_CONFIG_PATH, + RUNNER_OUTPUT_PATH, + WORKDIR, +} from './constants.js'; +import { createRunnerConfig, executeRunner } from './index.js'; + +describe('createRunnerConfig', () => { + it('should create a valid runner config', async () => { + const runnerConfig = await createRunnerConfig('executeRunner.ts', { + coverageToolCommand: { + command: 'npx', + args: ['@compodoc/compodoc', '-p', 'tsconfig.json'], + }, + outputPath: 'documentation/documentation.json', + }); + expect(runnerConfig).toStrictEqual({ + command: 'node', + args: ['"executeRunner.ts"'], + outputFile: expect.stringContaining('runner-output.json'), + }); + }); + + it('should provide plugin config to runner in JSON file', async () => { + await removeDirectoryIfExists(WORKDIR); + + const pluginConfig: DocCoveragePluginConfig = { + coverageToolCommand: { + command: 'npx', + args: ['@compodoc/compodoc', '-p', 'tsconfig.json'], + }, + outputPath: 'documentation/documentation.json', + }; + + await createRunnerConfig('executeRunner.ts', pluginConfig); + + const config = + await readJsonFile(PLUGIN_CONFIG_PATH); + expect(config).toStrictEqual(pluginConfig); + }); +}); + +describe('executeRunner', () => { + it('should successfully execute runner', async () => { + const config: DocCoveragePluginConfig = { + outputPath: path.join( + fileURLToPath(path.dirname(import.meta.url)), + '..', + '..', + '..', + 'mocks', + 'documentation.json', + ), + }; + + await writeFile(PLUGIN_CONFIG_PATH, JSON.stringify(config)); + await executeRunner(); + + const results = await readJsonFile(RUNNER_OUTPUT_PATH); + expect(results).toStrictEqual([ + expect.objectContaining({ + slug: 'percentage-coverage', + score: 0.85, + value: 85, + displayValue: '85 %', + } satisfies AuditOutput), + ]); + }); +}); diff --git a/packages/plugin-doc-coverage/tsconfig.json b/packages/plugin-doc-coverage/tsconfig.json new file mode 100644 index 000000000..893f9a925 --- /dev/null +++ b/packages/plugin-doc-coverage/tsconfig.json @@ -0,0 +1,23 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "module": "ESNext", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "types": ["vitest"] + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.test.json" + } + ] +} diff --git a/packages/plugin-doc-coverage/tsconfig.lib.json b/packages/plugin-doc-coverage/tsconfig.lib.json new file mode 100644 index 000000000..ef2f7e2b3 --- /dev/null +++ b/packages/plugin-doc-coverage/tsconfig.lib.json @@ -0,0 +1,16 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "include": ["src/**/*.ts"], + "exclude": [ + "vite.config.unit.ts", + "vite.config.integration.ts", + "src/**/*.test.ts", + "src/**/*.mock.ts", + "mocks/**/*.ts" + ] +} diff --git a/packages/plugin-doc-coverage/tsconfig.test.json b/packages/plugin-doc-coverage/tsconfig.test.json new file mode 100644 index 000000000..9f29d6bb0 --- /dev/null +++ b/packages/plugin-doc-coverage/tsconfig.test.json @@ -0,0 +1,13 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "types": ["vitest/globals", "vitest/importMeta", "vite/client", "node"] + }, + "include": [ + "vite.config.unit.ts", + "vite.config.integration.ts", + "mocks/**/*.ts", + "src/**/*.test.ts" + ] +} diff --git a/packages/plugin-doc-coverage/vite.config.integration.ts b/packages/plugin-doc-coverage/vite.config.integration.ts new file mode 100644 index 000000000..b04d62c56 --- /dev/null +++ b/packages/plugin-doc-coverage/vite.config.integration.ts @@ -0,0 +1,29 @@ +/// +import { defineConfig } from 'vite'; +import { tsconfigPathAliases } from '../../tools/vitest-tsconfig-path-aliases.js'; + +export default defineConfig({ + cacheDir: '../../node_modules/.vite/plugin-coverage', + test: { + reporters: ['basic'], + globals: true, + cache: { + dir: '../../node_modules/.vitest', + }, + alias: tsconfigPathAliases(), + pool: 'threads', + poolOptions: { threads: { singleThread: true } }, + coverage: { + reporter: ['text', 'lcov'], + reportsDirectory: '../../coverage/plugin-doc-coverage/integration-tests', + exclude: ['mocks/**', '**/types.ts'], + }, + environment: 'node', + include: ['src/**/*.integration.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + globalSetup: ['../../global-setup.ts'], + setupFiles: [ + '../../testing/test-setup/src/lib/console.mock.ts', + '../../testing/test-setup/src/lib/reset.mocks.ts', + ], + }, +}); diff --git a/packages/plugin-doc-coverage/vite.config.unit.ts b/packages/plugin-doc-coverage/vite.config.unit.ts new file mode 100644 index 000000000..3cae73a58 --- /dev/null +++ b/packages/plugin-doc-coverage/vite.config.unit.ts @@ -0,0 +1,31 @@ +/// +import { defineConfig } from 'vite'; +import { tsconfigPathAliases } from '../../tools/vitest-tsconfig-path-aliases.js'; + +export default defineConfig({ + cacheDir: '../../node_modules/.vite/plugin-coverage', + test: { + reporters: ['basic'], + globals: true, + cache: { + dir: '../../node_modules/.vitest', + }, + alias: tsconfigPathAliases(), + pool: 'threads', + poolOptions: { threads: { singleThread: true } }, + coverage: { + reporter: ['text', 'lcov'], + reportsDirectory: '../../coverage/plugin-doc-coverage/unit-tests', + exclude: ['mocks/**', '**/types.ts'], + }, + environment: 'node', + include: ['src/**/*.unit.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + globalSetup: ['../../global-setup.ts'], + setupFiles: [ + '../../testing/test-setup/src/lib/cliui.mock.ts', + '../../testing/test-setup/src/lib/fs.mock.ts', + '../../testing/test-setup/src/lib/console.mock.ts', + '../../testing/test-setup/src/lib/reset.mocks.ts', + ], + }, +}); From b07eb8abc972d14aaa24915d7de41da3f39812cc Mon Sep 17 00:00:00 2001 From: Alejandro Date: Tue, 17 Dec 2024 22:49:48 +0100 Subject: [PATCH 02/66] feat: change from compodoc to typedoc --- package-lock.json | 162 ++++++++++++++++++ package.json | 2 + .../mocks/component-mock.ts | 4 + .../plugin-doc-coverage/src/lib/config.ts | 26 ++- .../src/lib/config.unit.test.ts | 50 ++++-- .../src/lib/doc-coverage-plugin.ts | 3 +- .../src/lib/doc-coverage-plugin.unit.test.ts | 8 +- .../src/lib/runner/constants.ts | 33 +++- .../src/lib/runner/index.ts | 93 +++++++--- .../src/lib/runner/runner.integration.test.ts | 62 +++---- .../plugin-doc-coverage/tsconfig.test.json | 3 +- 11 files changed, 346 insertions(+), 100 deletions(-) create mode 100644 packages/plugin-doc-coverage/mocks/component-mock.ts diff --git a/package-lock.json b/package-lock.json index 0459e1180..9138feee2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -97,6 +97,8 @@ "tsconfig-paths": "^4.2.0", "tsx": "^4.19.0", "type-fest": "^4.26.1", + "typedoc": "^0.27.5", + "typedoc-plugin-coverage": "^3.4.0", "typescript": "5.5.4", "typescript-eslint": "^8.18.0", "verdaccio": "^5.32.2", @@ -3233,6 +3235,17 @@ "tslib": "^2.4.0" } }, + "node_modules/@gerrit0/mini-shiki": { + "version": "1.24.4", + "resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-1.24.4.tgz", + "integrity": "sha512-YEHW1QeAg6UmxEmswiQbOVEg1CW22b1XUD/lNTliOsu0LD0wqoyleFMnmbTp697QE0pcadQiR5cVtbbAPncvpw==", + "dev": true, + "dependencies": { + "@shikijs/engine-oniguruma": "^1.24.2", + "@shikijs/types": "^1.24.2", + "@shikijs/vscode-textmate": "^9.3.1" + } + }, "node_modules/@graphql-typed-document-node/core": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.2.0.tgz", @@ -6159,6 +6172,32 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "1.24.2", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-1.24.2.tgz", + "integrity": "sha512-ZN6k//aDNWRJs1uKB12pturKHh7GejKugowOFGAuG7TxDRLod1Bd5JhpOikOiFqPmKjKEPtEA6mRCf7q3ulDyQ==", + "dev": true, + "dependencies": { + "@shikijs/types": "1.24.2", + "@shikijs/vscode-textmate": "^9.3.0" + } + }, + "node_modules/@shikijs/types": { + "version": "1.24.2", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-1.24.2.tgz", + "integrity": "sha512-bdeWZiDtajGLG9BudI0AHet0b6e7FbR0EsE4jpGaI0YwHm/XJunI9+3uZnzFtX65gsyJ6ngCIWUfA4NWRPnBkQ==", + "dev": true, + "dependencies": { + "@shikijs/vscode-textmate": "^9.3.0", + "@types/hast": "^3.0.4" + } + }, + "node_modules/@shikijs/vscode-textmate": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-9.3.1.tgz", + "integrity": "sha512-79QfK1393x9Ho60QFyLti+QfdJzRQCVLFb97kOIV7Eo9vQU/roINgk7m24uv0a7AUvN//RDH36FLjjK48v0s9g==", + "dev": true + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -7296,6 +7335,15 @@ "@types/node": "*" } }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dev": true, + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/http-cache-semantics": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", @@ -7427,6 +7475,12 @@ "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", "dev": true }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "dev": true + }, "node_modules/@types/yargs": { "version": "17.0.33", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", @@ -19654,6 +19708,15 @@ "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "dev": true, + "dependencies": { + "uc.micro": "^2.0.0" + } + }, "node_modules/load-tsconfig": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/load-tsconfig/-/load-tsconfig-0.2.5.tgz", @@ -20146,6 +20209,12 @@ "yallist": "^3.0.2" } }, + "node_modules/lunr": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", + "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", + "dev": true + }, "node_modules/luxon": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.5.0.tgz", @@ -20214,6 +20283,29 @@ "tmpl": "1.0.5" } }, + "node_modules/markdown-it": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/markdown-it/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, "node_modules/marky": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/marky/-/marky-1.2.5.tgz", @@ -20234,6 +20326,12 @@ "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", "dev": true }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "dev": true + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -22243,6 +22341,15 @@ "node": ">=6" } }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/puppeteer-core": { "version": "22.15.0", "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-22.15.0.tgz", @@ -25192,6 +25299,55 @@ "is-typedarray": "^1.0.0" } }, + "node_modules/typedoc": { + "version": "0.27.5", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.27.5.tgz", + "integrity": "sha512-x+fhKJtTg4ozXwKayh/ek4wxZQI/+2hmZUdO2i2NGDBRUflDble70z+ewHod3d4gRpXSO6fnlnjbDTnJk7HlkQ==", + "dev": true, + "dependencies": { + "@gerrit0/mini-shiki": "^1.24.0", + "lunr": "^2.3.9", + "markdown-it": "^14.1.0", + "minimatch": "^9.0.5", + "yaml": "^2.6.1" + }, + "bin": { + "typedoc": "bin/typedoc" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "typescript": "5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x" + } + }, + "node_modules/typedoc-plugin-coverage": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/typedoc-plugin-coverage/-/typedoc-plugin-coverage-3.4.0.tgz", + "integrity": "sha512-I8fLeQEERncGn4sUlGZ+B1ehx4L7VRwqa3i6AP+PFfvZK0ToXBGkh9sK7xs8l8FLPXq7Cv0yVy4YCEGgWNzDBw==", + "dev": true, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "typedoc": "0.25.x || 0.26.x || 0.27.x" + } + }, + "node_modules/typedoc/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/typescript": { "version": "5.5.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", @@ -25279,6 +25435,12 @@ "typescript": ">=4.8.4 <5.8.0" } }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "dev": true + }, "node_modules/ufo": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.4.tgz", diff --git a/package.json b/package.json index 2782021cf..de4938f30 100644 --- a/package.json +++ b/package.json @@ -110,6 +110,8 @@ "tsconfig-paths": "^4.2.0", "tsx": "^4.19.0", "type-fest": "^4.26.1", + "typedoc": "^0.27.5", + "typedoc-plugin-coverage": "^3.4.0", "typescript": "5.5.4", "typescript-eslint": "^8.18.0", "verdaccio": "^5.32.2", diff --git a/packages/plugin-doc-coverage/mocks/component-mock.ts b/packages/plugin-doc-coverage/mocks/component-mock.ts new file mode 100644 index 000000000..f8008c421 --- /dev/null +++ b/packages/plugin-doc-coverage/mocks/component-mock.ts @@ -0,0 +1,4 @@ +/** Dummy function */ +export function DUMMY_FUNCTION() { + return 'Hello World'; +} diff --git a/packages/plugin-doc-coverage/src/lib/config.ts b/packages/plugin-doc-coverage/src/lib/config.ts index 139f2d77a..052f35c47 100644 --- a/packages/plugin-doc-coverage/src/lib/config.ts +++ b/packages/plugin-doc-coverage/src/lib/config.ts @@ -3,23 +3,21 @@ import { z } from 'zod'; export type DocType = 'percentage-coverage'; export const docCoveragePluginConfigSchema = z.object({ - coverageToolCommand: z - .object({ - command: z - .string({ description: 'Command to run coverage tool (compodoc).' }) - .min(1), - args: z - .array(z.string(), { - description: 'Arguments to be passed to the coverage tool.', - }) - .optional(), + language: z.enum(['javascript', 'typescript'], { + description: 'Programming language of the source code to analyze', + }), + sourceGlob: z + .string({ + description: 'Glob pattern to find source files', + }) + .optional(), + outputFolderPath: z + .string({ + description: 'Path to the output folder', }) .optional(), - outputPath: z - .string({ description: 'Path to the documentation.json file.' }) - .default('documentation/documentation.json'), }); -export type DocCoveragePluginConfig = z.infer< +export type DocCoveragePluginConfig = z.input< typeof docCoveragePluginConfigSchema >; diff --git a/packages/plugin-doc-coverage/src/lib/config.unit.test.ts b/packages/plugin-doc-coverage/src/lib/config.unit.test.ts index 3964fa186..36840ece8 100644 --- a/packages/plugin-doc-coverage/src/lib/config.unit.test.ts +++ b/packages/plugin-doc-coverage/src/lib/config.unit.test.ts @@ -8,45 +8,57 @@ describe('docCoveragePluginConfigSchema', () => { it('accepts a documentation coverage configuration with all entities', () => { expect(() => docCoveragePluginConfigSchema.parse({ - coverageToolCommand: { - command: 'npx @compodoc/compodoc', - args: ['-p', 'tsconfig.json'], - }, - outputPath: 'documentation/custom-doc.json', + language: 'typescript', + sourceGlob: 'src/**/*.{ts,tsx}', } satisfies DocCoveragePluginConfig), ).not.toThrow(); }); - it('accepts a minimal documentation coverage configuration', () => { + it('accepts minimal configuration with only language', () => { expect(() => - docCoveragePluginConfigSchema.parse({} satisfies DocCoveragePluginConfig), + docCoveragePluginConfigSchema.parse({ + language: 'javascript', + } satisfies DocCoveragePluginConfig), ).not.toThrow(); }); - it('uses default output path when not provided', () => { - const config = {} satisfies DocCoveragePluginConfig; + it('accepts configuration without sourceGlob', () => { + const config = { + language: 'typescript', + } satisfies DocCoveragePluginConfig; const parsed = docCoveragePluginConfigSchema.parse(config); - expect(parsed.outputPath).toBe('documentation/documentation.json'); + expect(parsed.sourceGlob).toBeUndefined(); }); - it('throws for missing command in coverageToolCommand', () => { + it('throws for missing language', () => { expect(() => docCoveragePluginConfigSchema.parse({ - coverageToolCommand: { - args: ['-p', 'tsconfig.json'], - }, + sourceGlob: 'src/**/*.ts', }), ).toThrow('invalid_type'); }); - it('accepts empty args in coverageToolCommand', () => { + it('throws for invalid language', () => { expect(() => docCoveragePluginConfigSchema.parse({ - coverageToolCommand: { - command: 'npx @compodoc/compodoc', - }, - } satisfies DocCoveragePluginConfig), + language: 'python', + sourceGlob: 'src/**/*.py', + }), + ).toThrow('Invalid enum value'); + }); + + it('accepts both typescript and javascript as valid languages', () => { + expect(() => + docCoveragePluginConfigSchema.parse({ + language: 'typescript', + }), + ).not.toThrow(); + + expect(() => + docCoveragePluginConfigSchema.parse({ + language: 'javascript', + }), ).not.toThrow(); }); }); diff --git a/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.ts b/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.ts index 0519eed82..c2e55412b 100644 --- a/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.ts +++ b/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.ts @@ -19,8 +19,7 @@ import { createRunnerConfig } from './runner/index.js'; * plugins: [ * // ... other plugins ... * await docCoveragePlugin({ - * - * docTypes: ['class', 'function'] + * language: 'typescript' * }) * ] * } diff --git a/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.unit.test.ts b/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.unit.test.ts index fbb2c452a..29c45bcf3 100644 --- a/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.unit.test.ts +++ b/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.unit.test.ts @@ -13,7 +13,7 @@ describe('docCoveragePlugin', () => { it('should initialise a Documentation coverage plugin', async () => { await expect( docCoveragePlugin({ - outputPath: 'documentation/documentation.json', + language: 'typescript', }), ).resolves.toStrictEqual( expect.objectContaining({ @@ -29,7 +29,7 @@ describe('docCoveragePlugin', () => { it('should generate percentage coverage audit', async () => { await expect( docCoveragePlugin({ - outputPath: 'documentation/documentation.json', + language: 'typescript', }), ).resolves.toStrictEqual( expect.objectContaining({ @@ -49,7 +49,7 @@ describe('docCoveragePlugin', () => { it('should provide a documentation coverage group', async () => { await expect( docCoveragePlugin({ - outputPath: 'documentation/documentation.json', + language: 'typescript', }), ).resolves.toStrictEqual( expect.objectContaining({ @@ -72,7 +72,7 @@ describe('docCoveragePlugin', () => { it('should include package metadata', async () => { await expect( docCoveragePlugin({ - outputPath: 'documentation/documentation.json', + language: 'typescript', }), ).resolves.toStrictEqual( expect.objectContaining({ diff --git a/packages/plugin-doc-coverage/src/lib/runner/constants.ts b/packages/plugin-doc-coverage/src/lib/runner/constants.ts index 8b9843ee6..9cf24d169 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/constants.ts +++ b/packages/plugin-doc-coverage/src/lib/runner/constants.ts @@ -3,9 +3,40 @@ import { pluginWorkDir } from '@code-pushup/utils'; export const WORKDIR = pluginWorkDir('doc-coverage'); +export const RUNNER_OUTPUT_PATH = path.join(WORKDIR, 'runner-output.json'); + export const PLUGIN_CONFIG_PATH = path.join( process.cwd(), WORKDIR, 'plugin-config.json', ); -export const RUNNER_OUTPUT_PATH = path.join(WORKDIR, 'runner-output.json'); + +export const enum ProgrammingLanguage { + JavaScript = 'javascript', + TypeScript = 'typescript', +} + +export const DEFAULT_SOURCE_GLOB = { + [ProgrammingLanguage.JavaScript]: '"src/**/*.js"', + [ProgrammingLanguage.TypeScript]: '"src/**/*.ts"', +}; + +export const DEFAULT_OUTPUT_FOLDER_PATH = './documentation'; + +export const COMMANDS_FOR_LANGUAGES: Readonly< + Record +> = { + [ProgrammingLanguage.JavaScript]: { + command: 'npx', + args: 'typedoc $sourceGlob --entryPointStrategy expand --plugin typedoc-plugin-coverage --coverageOutputType json --skipErrorChecking --out $outputFolderPath', + }, + [ProgrammingLanguage.TypeScript]: { + command: 'npx', + args: 'typedoc $sourceGlob --entryPointStrategy expand --plugin typedoc-plugin-coverage --coverageOutputType json --skipErrorChecking --out $outputFolderPath', + }, +} as const; + +export type TypedocResult = { + percent: number; + notDocumented: string[]; +}; diff --git a/packages/plugin-doc-coverage/src/lib/runner/index.ts b/packages/plugin-doc-coverage/src/lib/runner/index.ts index 638777aaa..d5f83fc39 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/index.ts +++ b/packages/plugin-doc-coverage/src/lib/runner/index.ts @@ -11,42 +11,79 @@ import { ui, } from '@code-pushup/utils'; import type { DocCoveragePluginConfig } from '../config.js'; -import { PLUGIN_CONFIG_PATH, RUNNER_OUTPUT_PATH } from './constants.js'; +import { + COMMANDS_FOR_LANGUAGES, + DEFAULT_OUTPUT_FOLDER_PATH, + DEFAULT_SOURCE_GLOB, + PLUGIN_CONFIG_PATH, + ProgrammingLanguage, + RUNNER_OUTPUT_PATH, + type TypedocResult, +} from './constants.js'; export { PLUGIN_CONFIG_PATH, RUNNER_OUTPUT_PATH } from './constants.js'; -export async function executeRunner(): Promise { - const { outputPath, coverageToolCommand } = - await readJsonFile(PLUGIN_CONFIG_PATH); - if (coverageToolCommand != null) { - const { command, args = [] } = coverageToolCommand; - try { - await executeProcess({ command, args }); - } catch (error) { - if (error instanceof ProcessError) { - ui().logger.error(bold('stdout from failed Compodoc process:')); - ui().logger.error(error.stdout); - ui().logger.error(bold('stderr from failed Compodoc process:')); - ui().logger.error(error.stderr); - } - throw new Error( - 'Doc Coverage plugin: Running Compodoc failed. Please check the error above.', - ); +/** + * Execute the Typedoc process. + * @param config - The configuration for the Typedoc process. + */ +async function _executeTypedocProcess( + config: DocCoveragePluginConfig, +): Promise { + const { + sourceGlob, + language, + outputFolderPath = DEFAULT_OUTPUT_FOLDER_PATH, + } = config; + const { args: originalArgs } = COMMANDS_FOR_LANGUAGES[language]; + const processedArgs = + language === ProgrammingLanguage.TypeScript + ? originalArgs + .replace('$outputFolderPath', outputFolderPath) + .replace('$sourceGlob', sourceGlob || DEFAULT_SOURCE_GLOB[language]) + : originalArgs; + + try { + await executeProcess({ + command: COMMANDS_FOR_LANGUAGES[language].command, + args: processedArgs.split(' '), + }); + } catch (error) { + if (error instanceof ProcessError) { + ui().logger.error(bold('stdout from failed Typedoc process:')); + ui().logger.error(error.stdout); + ui().logger.error(bold('stderr from failed Typedoc process:')); + ui().logger.error(error.stderr); } + throw new Error( + 'Doc Coverage plugin: Running Typedoc failed. Please check the error above.', + ); } +} +/** + * Process the Typedoc results. + * @param outputFolderPath - The path to the output folder. + */ +async function _processTypedocResults(outputFolderPath: string): Promise { try { - // From the output of Compodoc, we can get the coverage percentage. - const docData: { coverage: { count: number } } = - await readJsonFile(outputPath); - const coverage = docData.coverage.count || 0; - + const docData: TypedocResult = await readJsonFile( + path.join(outputFolderPath, 'coverage.json'), + ); + const coverage = docData.percent || 0; const auditOutputs: AuditOutput[] = [ { slug: 'percentage-coverage', value: coverage, score: coverage / 100, displayValue: `${coverage} %`, + details: { + issues: docData.notDocumented.map(file => ({ + message: 'Missing documentation', + source: { file }, + severity: 'warning', + })), + }, }, ]; @@ -58,7 +95,6 @@ export async function executeRunner(): Promise { ui().logger.error(error.stdout); ui().logger.error(bold('stderr from failed coverage tool process:')); ui().logger.error(error.stderr); - throw new Error( 'Doc Coverage plugin: Running Compodoc failed. Please check the error above.', ); @@ -66,6 +102,15 @@ export async function executeRunner(): Promise { } } +export async function executeRunner(): Promise { + const config = + await readJsonFile(PLUGIN_CONFIG_PATH); + await _executeTypedocProcess(config); + await _processTypedocResults( + config.outputFolderPath || DEFAULT_OUTPUT_FOLDER_PATH, + ); +} + export async function createRunnerConfig( scriptPath: string, config: DocCoveragePluginConfig, diff --git a/packages/plugin-doc-coverage/src/lib/runner/runner.integration.test.ts b/packages/plugin-doc-coverage/src/lib/runner/runner.integration.test.ts index f67e5791c..d7fc8e5e9 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/runner.integration.test.ts +++ b/packages/plugin-doc-coverage/src/lib/runner/runner.integration.test.ts @@ -1,6 +1,4 @@ import { writeFile } from 'node:fs/promises'; -import path from 'node:path'; -import { fileURLToPath } from 'node:url'; import { describe, it } from 'vitest'; import type { AuditOutput, @@ -19,11 +17,9 @@ import { createRunnerConfig, executeRunner } from './index.js'; describe('createRunnerConfig', () => { it('should create a valid runner config', async () => { const runnerConfig = await createRunnerConfig('executeRunner.ts', { - coverageToolCommand: { - command: 'npx', - args: ['@compodoc/compodoc', '-p', 'tsconfig.json'], - }, - outputPath: 'documentation/documentation.json', + language: 'typescript', + sourceGlob: 'src/**/*.ts', + outputFolderPath: 'documentation', }); expect(runnerConfig).toStrictEqual({ command: 'node', @@ -36,11 +32,9 @@ describe('createRunnerConfig', () => { await removeDirectoryIfExists(WORKDIR); const pluginConfig: DocCoveragePluginConfig = { - coverageToolCommand: { - command: 'npx', - args: ['@compodoc/compodoc', '-p', 'tsconfig.json'], - }, - outputPath: 'documentation/documentation.json', + language: 'typescript', + sourceGlob: 'src/**/*.ts', + outputFolderPath: 'documentation', }; await createRunnerConfig('executeRunner.ts', pluginConfig); @@ -52,29 +46,27 @@ describe('createRunnerConfig', () => { }); describe('executeRunner', () => { - it('should successfully execute runner', async () => { - const config: DocCoveragePluginConfig = { - outputPath: path.join( - fileURLToPath(path.dirname(import.meta.url)), - '..', - '..', - '..', - 'mocks', - 'documentation.json', - ), - }; + it( + 'should successfully execute runner', + async () => { + const config: DocCoveragePluginConfig = { + language: 'typescript', + sourceGlob: '"packages/plugin-doc-coverage/mocks/component-mock.ts"', + }; - await writeFile(PLUGIN_CONFIG_PATH, JSON.stringify(config)); - await executeRunner(); + await writeFile(PLUGIN_CONFIG_PATH, JSON.stringify(config)); + await executeRunner(); - const results = await readJsonFile(RUNNER_OUTPUT_PATH); - expect(results).toStrictEqual([ - expect.objectContaining({ - slug: 'percentage-coverage', - score: 0.85, - value: 85, - displayValue: '85 %', - } satisfies AuditOutput), - ]); - }); + const results = await readJsonFile(RUNNER_OUTPUT_PATH); + expect(results).toStrictEqual([ + expect.objectContaining({ + slug: 'percentage-coverage', + score: 1, + value: 100, + displayValue: '100 %', + } satisfies AuditOutput), + ]); + }, + { timeout: 60 * 1000 }, + ); }); diff --git a/packages/plugin-doc-coverage/tsconfig.test.json b/packages/plugin-doc-coverage/tsconfig.test.json index 9f29d6bb0..05637ee6e 100644 --- a/packages/plugin-doc-coverage/tsconfig.test.json +++ b/packages/plugin-doc-coverage/tsconfig.test.json @@ -8,6 +8,7 @@ "vite.config.unit.ts", "vite.config.integration.ts", "mocks/**/*.ts", - "src/**/*.test.ts" + "src/**/*.test.ts", + "mocks/component-mock.ts" ] } From 0de6d3fb3767c3065990108dbc16902bca00374b Mon Sep 17 00:00:00 2001 From: Alejandro Date: Wed, 18 Dec 2024 03:01:40 +0100 Subject: [PATCH 03/66] feat(plugin-doc-coverage): change to use ts-morph instead of typedoc. MVP --- code-pushup.config.ts | 2 + code-pushup.preset.ts | 28 ++ package-lock.json | 242 ++++++------------ package.json | 3 +- .../mocks/component-mock.ts | 5 +- packages/plugin-doc-coverage/package.json | 3 +- .../plugin-doc-coverage/src/lib/config.ts | 5 - .../src/lib/doc-coverage-plugin.ts | 34 +-- .../src/lib/doc-coverage-plugin.unit.test.ts | 24 -- .../plugin-doc-coverage/src/lib/models.ts | 7 + .../src/lib/runner/doc-processer.ts | 198 ++++++++++++++ .../src/lib/runner/index.ts | 55 ++-- .../src/lib/runner/runner.integration.test.ts | 4 +- .../plugin-doc-coverage/tsconfig.lib.json | 2 +- tsconfig.base.json | 3 + 15 files changed, 362 insertions(+), 253 deletions(-) create mode 100644 packages/plugin-doc-coverage/src/lib/models.ts create mode 100644 packages/plugin-doc-coverage/src/lib/runner/doc-processer.ts diff --git a/code-pushup.config.ts b/code-pushup.config.ts index bd089d884..d1b674a1d 100644 --- a/code-pushup.config.ts +++ b/code-pushup.config.ts @@ -2,6 +2,7 @@ import 'dotenv/config'; import { z } from 'zod'; import { coverageCoreConfigNx, + docCoverageCoreConfig, eslintCoreConfigNx, jsPackagesCoreConfig, lighthouseCoreConfig, @@ -39,4 +40,5 @@ export default mergeConfigs( 'https://github.com/code-pushup/cli?tab=readme-ov-file#code-pushup-cli/', ), await eslintCoreConfigNx(), + await docCoverageCoreConfig(), ); diff --git a/code-pushup.preset.ts b/code-pushup.preset.ts index 74e6b51ce..979610544 100644 --- a/code-pushup.preset.ts +++ b/code-pushup.preset.ts @@ -5,6 +5,8 @@ import type { import coveragePlugin, { getNxCoveragePaths, } from './packages/plugin-coverage/src/index.js'; +import docCoveragePlugin from './packages/plugin-doc-coverage/src/index.js'; +import { docCoverageAudits } from './packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.js'; import eslintPlugin, { eslintConfigFromAllNxProjects, eslintConfigFromNxProject, @@ -82,6 +84,20 @@ export const eslintCategories: CategoryConfig[] = [ }, ]; +export const docCoverageCategories: CategoryConfig[] = [ + { + slug: 'doc-coverage', + title: 'Documentation coverage', + description: 'Measures how much of your code is **documented**.', + refs: docCoverageAudits.map(audit => ({ + weight: 1, + type: 'audit', + plugin: 'doc-coverage', + slug: audit.slug, + })), + }, +]; + export const coverageCategories: CategoryConfig[] = [ { slug: 'code-coverage', @@ -114,6 +130,18 @@ export const lighthouseCoreConfig = async ( }; }; +export const docCoverageCoreConfig = async (): Promise => { + return { + plugins: [ + await docCoveragePlugin({ + language: 'typescript', + sourceGlob: 'packages/**/*.ts', + }), + ], + categories: docCoverageCategories, + }; +}; + export const eslintCoreConfigNx = async ( projectName?: string, ): Promise => { diff --git a/package-lock.json b/package-lock.json index 9138feee2..206de309d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,6 +27,7 @@ "parse-lcov": "^1.0.4", "semver": "^7.6.3", "simple-git": "^3.26.0", + "ts-morph": "^24.0.0", "tslib": "^2.6.2", "vscode-material-icons": "^0.1.1", "yaml": "^2.5.1", @@ -97,8 +98,6 @@ "tsconfig-paths": "^4.2.0", "tsx": "^4.19.0", "type-fest": "^4.26.1", - "typedoc": "^0.27.5", - "typedoc-plugin-coverage": "^3.4.0", "typescript": "5.5.4", "typescript-eslint": "^8.18.0", "verdaccio": "^5.32.2", @@ -3235,17 +3234,6 @@ "tslib": "^2.4.0" } }, - "node_modules/@gerrit0/mini-shiki": { - "version": "1.24.4", - "resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-1.24.4.tgz", - "integrity": "sha512-YEHW1QeAg6UmxEmswiQbOVEg1CW22b1XUD/lNTliOsu0LD0wqoyleFMnmbTp697QE0pcadQiR5cVtbbAPncvpw==", - "dev": true, - "dependencies": { - "@shikijs/engine-oniguruma": "^1.24.2", - "@shikijs/types": "^1.24.2", - "@shikijs/vscode-textmate": "^9.3.1" - } - }, "node_modules/@graphql-typed-document-node/core": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.2.0.tgz", @@ -6172,32 +6160,6 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, - "node_modules/@shikijs/engine-oniguruma": { - "version": "1.24.2", - "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-1.24.2.tgz", - "integrity": "sha512-ZN6k//aDNWRJs1uKB12pturKHh7GejKugowOFGAuG7TxDRLod1Bd5JhpOikOiFqPmKjKEPtEA6mRCf7q3ulDyQ==", - "dev": true, - "dependencies": { - "@shikijs/types": "1.24.2", - "@shikijs/vscode-textmate": "^9.3.0" - } - }, - "node_modules/@shikijs/types": { - "version": "1.24.2", - "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-1.24.2.tgz", - "integrity": "sha512-bdeWZiDtajGLG9BudI0AHet0b6e7FbR0EsE4jpGaI0YwHm/XJunI9+3uZnzFtX65gsyJ6ngCIWUfA4NWRPnBkQ==", - "dev": true, - "dependencies": { - "@shikijs/vscode-textmate": "^9.3.0", - "@types/hast": "^3.0.4" - } - }, - "node_modules/@shikijs/vscode-textmate": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-9.3.1.tgz", - "integrity": "sha512-79QfK1393x9Ho60QFyLti+QfdJzRQCVLFb97kOIV7Eo9vQU/roINgk7m24uv0a7AUvN//RDH36FLjjK48v0s9g==", - "dev": true - }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -7184,6 +7146,30 @@ "node": ">=10.13.0" } }, + "node_modules/@ts-morph/common": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.25.0.tgz", + "integrity": "sha512-kMnZz+vGGHi4GoHnLmMhGNjm44kGtKUXGnOvrKmMwAuvNjM/PgKVGfUnL7IDvK7Jb2QQ82jq3Zmp04Gy+r3Dkg==", + "dependencies": { + "minimatch": "^9.0.4", + "path-browserify": "^1.0.1", + "tinyglobby": "^0.2.9" + } + }, + "node_modules/@ts-morph/common/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@tsconfig/node10": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", @@ -7335,15 +7321,6 @@ "@types/node": "*" } }, - "node_modules/@types/hast": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", - "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", - "dev": true, - "dependencies": { - "@types/unist": "*" - } - }, "node_modules/@types/http-cache-semantics": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", @@ -7475,12 +7452,6 @@ "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", "dev": true }, - "node_modules/@types/unist": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", - "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", - "dev": true - }, "node_modules/@types/yargs": { "version": "17.0.33", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", @@ -10852,6 +10823,11 @@ "node": ">= 0.12.0" } }, + "node_modules/code-block-writer": { + "version": "13.0.3", + "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-13.0.3.tgz", + "integrity": "sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==" + }, "node_modules/collect-v8-coverage": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", @@ -19708,15 +19684,6 @@ "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, - "node_modules/linkify-it": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", - "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", - "dev": true, - "dependencies": { - "uc.micro": "^2.0.0" - } - }, "node_modules/load-tsconfig": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/load-tsconfig/-/load-tsconfig-0.2.5.tgz", @@ -20209,12 +20176,6 @@ "yallist": "^3.0.2" } }, - "node_modules/lunr": { - "version": "2.3.9", - "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", - "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", - "dev": true - }, "node_modules/luxon": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.5.0.tgz", @@ -20283,29 +20244,6 @@ "tmpl": "1.0.5" } }, - "node_modules/markdown-it": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", - "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1", - "entities": "^4.4.0", - "linkify-it": "^5.0.0", - "mdurl": "^2.0.0", - "punycode.js": "^2.3.1", - "uc.micro": "^2.1.0" - }, - "bin": { - "markdown-it": "bin/markdown-it.mjs" - } - }, - "node_modules/markdown-it/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, "node_modules/marky": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/marky/-/marky-1.2.5.tgz", @@ -20326,12 +20264,6 @@ "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", "dev": true }, - "node_modules/mdurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", - "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", - "dev": true - }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -21772,6 +21704,11 @@ "node": ">= 0.8" } }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==" + }, "node_modules/path-exists": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", @@ -22341,15 +22278,6 @@ "node": ">=6" } }, - "node_modules/punycode.js": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", - "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/puppeteer-core": { "version": "22.15.0", "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-22.15.0.tgz", @@ -24740,6 +24668,42 @@ "integrity": "sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==", "dev": true }, + "node_modules/tinyglobby": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.10.tgz", + "integrity": "sha512-Zc+8eJlFMvgatPZTl6A9L/yht8QqdmUNtURHaKZLmKBE12hNPSrqNkUp2cs3M/UKmNVVAMFQYSjYIVHDjW5zew==", + "dependencies": { + "fdir": "^6.4.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.2.tgz", + "integrity": "sha512-KnhMXsKSPZlAhp7+IjUkRZKPb4fUyccpDrdFXbi4QL1qkmFh9kVY09Yox+n4MaOb3lHZ1Tv829C3oaaXoMYPDQ==", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/tinypool": { "version": "0.8.4", "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.4.tgz", @@ -24963,6 +24927,15 @@ "typescript": ">=4.0.0" } }, + "node_modules/ts-morph": { + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-24.0.0.tgz", + "integrity": "sha512-2OAOg/Ob5yx9Et7ZX4CvTCc0UFoZHwLEJ+dpDPSUi5TgwwlTlX47w+iFRrEwzUZwYACjq83cgjS/Da50Ga37uw==", + "dependencies": { + "@ts-morph/common": "~0.25.0", + "code-block-writer": "^13.0.3" + } + }, "node_modules/ts-node": { "version": "10.9.1", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", @@ -25299,55 +25272,6 @@ "is-typedarray": "^1.0.0" } }, - "node_modules/typedoc": { - "version": "0.27.5", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.27.5.tgz", - "integrity": "sha512-x+fhKJtTg4ozXwKayh/ek4wxZQI/+2hmZUdO2i2NGDBRUflDble70z+ewHod3d4gRpXSO6fnlnjbDTnJk7HlkQ==", - "dev": true, - "dependencies": { - "@gerrit0/mini-shiki": "^1.24.0", - "lunr": "^2.3.9", - "markdown-it": "^14.1.0", - "minimatch": "^9.0.5", - "yaml": "^2.6.1" - }, - "bin": { - "typedoc": "bin/typedoc" - }, - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "typescript": "5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x" - } - }, - "node_modules/typedoc-plugin-coverage": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/typedoc-plugin-coverage/-/typedoc-plugin-coverage-3.4.0.tgz", - "integrity": "sha512-I8fLeQEERncGn4sUlGZ+B1ehx4L7VRwqa3i6AP+PFfvZK0ToXBGkh9sK7xs8l8FLPXq7Cv0yVy4YCEGgWNzDBw==", - "dev": true, - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "typedoc": "0.25.x || 0.26.x || 0.27.x" - } - }, - "node_modules/typedoc/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/typescript": { "version": "5.5.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", @@ -25435,12 +25359,6 @@ "typescript": ">=4.8.4 <5.8.0" } }, - "node_modules/uc.micro": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", - "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", - "dev": true - }, "node_modules/ufo": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.4.tgz", diff --git a/package.json b/package.json index de4938f30..6366530e8 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "parse-lcov": "^1.0.4", "semver": "^7.6.3", "simple-git": "^3.26.0", + "ts-morph": "^24.0.0", "tslib": "^2.6.2", "vscode-material-icons": "^0.1.1", "yaml": "^2.5.1", @@ -110,8 +111,6 @@ "tsconfig-paths": "^4.2.0", "tsx": "^4.19.0", "type-fest": "^4.26.1", - "typedoc": "^0.27.5", - "typedoc-plugin-coverage": "^3.4.0", "typescript": "5.5.4", "typescript-eslint": "^8.18.0", "verdaccio": "^5.32.2", diff --git a/packages/plugin-doc-coverage/mocks/component-mock.ts b/packages/plugin-doc-coverage/mocks/component-mock.ts index f8008c421..e66f4f859 100644 --- a/packages/plugin-doc-coverage/mocks/component-mock.ts +++ b/packages/plugin-doc-coverage/mocks/component-mock.ts @@ -1,4 +1,7 @@ -/** Dummy function */ +/** + * Dummy function that returns 'Hello World'. + * @returns {string} - The string 'Hello World'. + */ export function DUMMY_FUNCTION() { return 'Hello World'; } diff --git a/packages/plugin-doc-coverage/package.json b/packages/plugin-doc-coverage/package.json index 1c2ebe241..ec8b984a9 100644 --- a/packages/plugin-doc-coverage/package.json +++ b/packages/plugin-doc-coverage/package.json @@ -37,7 +37,8 @@ "@code-pushup/models": "0.57.0", "@code-pushup/utils": "0.57.0", "ansis": "^3.3.0", - "zod": "^3.22.4" + "zod": "^3.22.4", + "ts-morph": "^24.0.0" }, "peerDependenciesMeta": { "@nx/devkit": { diff --git a/packages/plugin-doc-coverage/src/lib/config.ts b/packages/plugin-doc-coverage/src/lib/config.ts index 052f35c47..a55a46137 100644 --- a/packages/plugin-doc-coverage/src/lib/config.ts +++ b/packages/plugin-doc-coverage/src/lib/config.ts @@ -11,11 +11,6 @@ export const docCoveragePluginConfigSchema = z.object({ description: 'Glob pattern to find source files', }) .optional(), - outputFolderPath: z - .string({ - description: 'Path to the output folder', - }) - .optional(), }); export type DocCoveragePluginConfig = z.input< diff --git a/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.ts b/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.ts index c2e55412b..0504c8218 100644 --- a/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.ts +++ b/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.ts @@ -1,7 +1,7 @@ import { createRequire } from 'node:module'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; -import type { Group, PluginConfig } from '@code-pushup/models'; +import type { PluginConfig } from '@code-pushup/models'; import { type DocCoveragePluginConfig, docCoveragePluginConfigSchema, @@ -26,30 +26,20 @@ import { createRunnerConfig } from './runner/index.js'; * * @returns Plugin configuration. */ + +export const docCoverageAudits = [ + { + slug: 'percentage-coverage', + title: 'Percentage of codebase with documentation', + description: 'Measures how many % of the codebase have documentation.', + }, +]; + export async function docCoveragePlugin( config: DocCoveragePluginConfig, ): Promise { const docCoverageConfig = docCoveragePluginConfigSchema.parse(config); - const audits = [ - { - slug: 'percentage-coverage', - title: 'Percentage of codebase with documentation', - description: 'Measures how many % of the codebase have documentation.', - }, - ]; - - const group: Group = { - slug: 'doc-coverage', - title: 'Documentation coverage metrics', - description: - 'Group containing all defined documentation coverage types as audits.', - refs: audits.map(audit => ({ - ...audit, - weight: 1, - })), - }; - const runnerScriptPath = path.join( fileURLToPath(path.dirname(import.meta.url)), '..', @@ -68,8 +58,8 @@ export async function docCoveragePlugin( docsUrl: 'https://www.npmjs.com/package/@code-pushup/doc-coverage-plugin/', packageName: packageJson.name, version: packageJson.version, - audits, - groups: [group], + audits: docCoverageAudits, + // groups: [group], runner: await createRunnerConfig(runnerScriptPath, docCoverageConfig), }; } diff --git a/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.unit.test.ts b/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.unit.test.ts index 29c45bcf3..20e44b1da 100644 --- a/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.unit.test.ts +++ b/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.unit.test.ts @@ -20,7 +20,6 @@ describe('docCoveragePlugin', () => { slug: 'doc-coverage', title: 'Documentation coverage', audits: expect.any(Array), - groups: expect.any(Array), runner: expect.any(Object), }), ); @@ -46,29 +45,6 @@ describe('docCoveragePlugin', () => { ); }); - it('should provide a documentation coverage group', async () => { - await expect( - docCoveragePlugin({ - language: 'typescript', - }), - ).resolves.toStrictEqual( - expect.objectContaining({ - groups: [ - expect.objectContaining({ - slug: 'doc-coverage', - title: 'Documentation coverage metrics', - refs: [ - expect.objectContaining({ - slug: 'percentage-coverage', - weight: 1, - }), - ], - }), - ], - }), - ); - }); - it('should include package metadata', async () => { await expect( docCoveragePlugin({ diff --git a/packages/plugin-doc-coverage/src/lib/models.ts b/packages/plugin-doc-coverage/src/lib/models.ts new file mode 100644 index 000000000..ff4605b8c --- /dev/null +++ b/packages/plugin-doc-coverage/src/lib/models.ts @@ -0,0 +1,7 @@ +export type UndocumentedItem = { + file: string; + type: string; + name: string; + line: number; + class?: string; +}; diff --git a/packages/plugin-doc-coverage/src/lib/runner/doc-processer.ts b/packages/plugin-doc-coverage/src/lib/runner/doc-processer.ts new file mode 100644 index 000000000..86d75b343 --- /dev/null +++ b/packages/plugin-doc-coverage/src/lib/runner/doc-processer.ts @@ -0,0 +1,198 @@ +import { Project } from 'ts-morph'; +import type { UndocumentedItem } from '../models.js'; + +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable functional/immutable-data */ +/* eslint-disable @typescript-eslint/max-params */ +/* eslint-disable functional/no-let */ + +export function processDocCoverage(toInclude: string): { + undocumentedItems: UndocumentedItem[]; + coverage: number; +} { + const project = new Project(); + project.addSourceFilesAtPaths(toInclude); + + let itsDocumented = 0; + const undocumentedItems: UndocumentedItem[] = []; + + project.getSourceFiles().forEach(sourceFile => { + if (isTestFile(sourceFile.getFilePath())) { + return; + } + + processClassDeclarations( + sourceFile, + undocumentedItems, + count => (itsDocumented += count), + ); + processDeclarations( + sourceFile, + undocumentedItems, + count => (itsDocumented += count), + ); + }); + + return calculateCoverage(undocumentedItems, itsDocumented); +} + +function isTestFile(filePath: string): boolean { + return filePath.includes('.spec.') || filePath.includes('.test.'); +} + +function addUndocumentedItem( + file: string, + type: string, + name: string, + line: number, +): UndocumentedItem { + return { + file, + type, + name, + line, + }; +} + +function processClassDeclarations( + sourceFile: any, + undocumentedItems: UndocumentedItem[], + onDocumented: (count: number) => void, +): void { + sourceFile.getClasses().forEach((classDeclaration: any) => { + const className = classDeclaration.getName() || 'Anonymous Class'; + const filePath = sourceFile.getFilePath(); + + // Process class itself + if (classDeclaration.getJsDocs().length === 0) { + undocumentedItems.push( + addUndocumentedItem( + filePath, + 'class', + className, + classDeclaration.getStartLineNumber(), + ), + ); + } else { + onDocumented(1); + } + + // Process properties + classDeclaration.getProperties().forEach((property: any) => { + if (property.getJsDocs().length === 0) { + undocumentedItems.push( + addUndocumentedItem( + filePath, + 'property', + property.getName(), + property.getStartLineNumber(), + ), + ); + } else { + onDocumented(1); + } + }); + + // Process methods + classDeclaration.getMethods().forEach((method: any) => { + if (method.getJsDocs().length === 0) { + undocumentedItems.push( + addUndocumentedItem( + filePath, + 'method', + method.getName(), + method.getStartLineNumber(), + ), + ); + } else { + onDocumented(1); + } + }); + }); +} + +function processDeclarations( + sourceFile: any, + undocumentedItems: UndocumentedItem[], + onDocumented: (count: number) => void, +): void { + const filePath = sourceFile.getFilePath(); + + // Process functions + processItems( + sourceFile.getFunctions(), + 'function', + item => item.getName() || 'Anonymous Function', + filePath, + undocumentedItems, + onDocumented, + ); + + // Process variables + sourceFile.getVariableStatements().forEach((statement: any) => { + statement.getDeclarations().forEach((declaration: any) => { + if (statement.getJsDocs().length === 0) { + undocumentedItems.push( + addUndocumentedItem( + filePath, + 'variable', + declaration.getName(), + declaration.getStartLineNumber(), + ), + ); + } else { + onDocumented(1); + } + }); + }); + + // Process interfaces and types + processItems( + sourceFile.getInterfaces(), + 'interface', + item => item.getName(), + filePath, + undocumentedItems, + onDocumented, + ); + processItems( + sourceFile.getTypeAliases(), + 'type', + item => item.getName(), + filePath, + undocumentedItems, + onDocumented, + ); +} + +function processItems( + items: any[], + type: string, + getName: (item: any) => string, + filePath: string, + undocumentedItems: UndocumentedItem[], + onDocumented: (count: number) => void, +): void { + items.forEach(item => { + if (item.getJsDocs().length === 0) { + undocumentedItems.push( + addUndocumentedItem( + filePath, + type, + getName(item), + item.getStartLineNumber(), + ), + ); + } else { + onDocumented(1); + } + }); +} + +function calculateCoverage( + undocumentedItems: UndocumentedItem[], + documented: number, +) { + const coverage = (documented / (documented + undocumentedItems.length)) * 100; + return { undocumentedItems, coverage }; +} diff --git a/packages/plugin-doc-coverage/src/lib/runner/index.ts b/packages/plugin-doc-coverage/src/lib/runner/index.ts index d5f83fc39..23aed7a92 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/index.ts +++ b/packages/plugin-doc-coverage/src/lib/runner/index.ts @@ -5,21 +5,18 @@ import type { AuditOutput, RunnerConfig } from '@code-pushup/models'; import { ProcessError, ensureDirectoryExists, - executeProcess, filePathToCliArg, readJsonFile, ui, } from '@code-pushup/utils'; import type { DocCoveragePluginConfig } from '../config.js'; +import type { UndocumentedItem } from '../models.js'; import { - COMMANDS_FOR_LANGUAGES, - DEFAULT_OUTPUT_FOLDER_PATH, DEFAULT_SOURCE_GLOB, PLUGIN_CONFIG_PATH, - ProgrammingLanguage, RUNNER_OUTPUT_PATH, - type TypedocResult, } from './constants.js'; +import { processDocCoverage } from './doc-processer.js'; export { PLUGIN_CONFIG_PATH, RUNNER_OUTPUT_PATH } from './constants.js'; @@ -29,25 +26,19 @@ export { PLUGIN_CONFIG_PATH, RUNNER_OUTPUT_PATH } from './constants.js'; */ async function _executeTypedocProcess( config: DocCoveragePluginConfig, -): Promise { - const { - sourceGlob, - language, - outputFolderPath = DEFAULT_OUTPUT_FOLDER_PATH, - } = config; - const { args: originalArgs } = COMMANDS_FOR_LANGUAGES[language]; - const processedArgs = - language === ProgrammingLanguage.TypeScript - ? originalArgs - .replace('$outputFolderPath', outputFolderPath) - .replace('$sourceGlob', sourceGlob || DEFAULT_SOURCE_GLOB[language]) - : originalArgs; +): Promise<{ + undocumentedItems: UndocumentedItem[]; + coverage: number; +}> { + const { sourceGlob, language } = config; try { - await executeProcess({ - command: COMMANDS_FOR_LANGUAGES[language].command, - args: processedArgs.split(' '), - }); + return processDocCoverage(sourceGlob || DEFAULT_SOURCE_GLOB[language]); + // console.table(undocumentedItems); + // await executeProcess({ + // command: COMMANDS_FOR_LANGUAGES[language].command, + // args: processedArgs.split(' '), + // }); } catch (error) { if (error instanceof ProcessError) { ui().logger.error(bold('stdout from failed Typedoc process:')); @@ -65,12 +56,11 @@ async function _executeTypedocProcess( * Process the Typedoc results. * @param outputFolderPath - The path to the output folder. */ -async function _processTypedocResults(outputFolderPath: string): Promise { +async function _processTypedocResults( + undocumentedItems: UndocumentedItem[], + coverage: number, +): Promise { try { - const docData: TypedocResult = await readJsonFile( - path.join(outputFolderPath, 'coverage.json'), - ); - const coverage = docData.percent || 0; const auditOutputs: AuditOutput[] = [ { slug: 'percentage-coverage', @@ -78,9 +68,9 @@ async function _processTypedocResults(outputFolderPath: string): Promise { score: coverage / 100, displayValue: `${coverage} %`, details: { - issues: docData.notDocumented.map(file => ({ - message: 'Missing documentation', - source: { file }, + issues: undocumentedItems.map(item => ({ + message: `Missing documentation for a ${item.type}`, + source: { file: item.file, position: { startLine: item.line } }, severity: 'warning', })), }, @@ -105,9 +95,10 @@ async function _processTypedocResults(outputFolderPath: string): Promise { export async function executeRunner(): Promise { const config = await readJsonFile(PLUGIN_CONFIG_PATH); - await _executeTypedocProcess(config); + const processResult = await _executeTypedocProcess(config); await _processTypedocResults( - config.outputFolderPath || DEFAULT_OUTPUT_FOLDER_PATH, + processResult.undocumentedItems, + processResult.coverage, ); } diff --git a/packages/plugin-doc-coverage/src/lib/runner/runner.integration.test.ts b/packages/plugin-doc-coverage/src/lib/runner/runner.integration.test.ts index d7fc8e5e9..49cd966d3 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/runner.integration.test.ts +++ b/packages/plugin-doc-coverage/src/lib/runner/runner.integration.test.ts @@ -19,7 +19,6 @@ describe('createRunnerConfig', () => { const runnerConfig = await createRunnerConfig('executeRunner.ts', { language: 'typescript', sourceGlob: 'src/**/*.ts', - outputFolderPath: 'documentation', }); expect(runnerConfig).toStrictEqual({ command: 'node', @@ -34,7 +33,6 @@ describe('createRunnerConfig', () => { const pluginConfig: DocCoveragePluginConfig = { language: 'typescript', sourceGlob: 'src/**/*.ts', - outputFolderPath: 'documentation', }; await createRunnerConfig('executeRunner.ts', pluginConfig); @@ -51,7 +49,7 @@ describe('executeRunner', () => { async () => { const config: DocCoveragePluginConfig = { language: 'typescript', - sourceGlob: '"packages/plugin-doc-coverage/mocks/component-mock.ts"', + sourceGlob: 'packages/plugin-doc-coverage/mocks/*.ts', }; await writeFile(PLUGIN_CONFIG_PATH, JSON.stringify(config)); diff --git a/packages/plugin-doc-coverage/tsconfig.lib.json b/packages/plugin-doc-coverage/tsconfig.lib.json index ef2f7e2b3..37e86c560 100644 --- a/packages/plugin-doc-coverage/tsconfig.lib.json +++ b/packages/plugin-doc-coverage/tsconfig.lib.json @@ -5,7 +5,7 @@ "declaration": true, "types": ["node"] }, - "include": ["src/**/*.ts"], + "include": ["src/**/*.ts", "src/lib/runner/doc-processer.js"], "exclude": [ "vite.config.unit.ts", "vite.config.integration.ts", diff --git a/tsconfig.base.json b/tsconfig.base.json index d088eca5a..c5cddb98c 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -25,6 +25,9 @@ "@code-pushup/core": ["packages/core/src/index.ts"], "@code-pushup/coverage-plugin": ["packages/plugin-coverage/src/index.ts"], "@code-pushup/eslint-plugin": ["packages/plugin-eslint/src/index.ts"], + "@code-pushup/doc-coverage-plugin": [ + "packages/plugin-doc-coverage/src/index.ts" + ], "@code-pushup/js-packages-plugin": [ "packages/plugin-js-packages/src/index.ts" ], From 164a86a660053563fa101c3b9faf829335e6d884 Mon Sep 17 00:00:00 2001 From: Alejandro Date: Thu, 19 Dec 2024 00:08:02 +0100 Subject: [PATCH 04/66] feat: doc-processer give coverage by type of property. Some tests enhanced and some code improved --- .../mocks/component-mock.ts | 42 ++++ .../plugin-doc-coverage/src/lib/config.ts | 7 +- .../src/lib/config.unit.test.ts | 49 +---- .../src/lib/doc-coverage-plugin.ts | 2 +- .../src/lib/doc-coverage-plugin.unit.test.ts | 12 +- .../plugin-doc-coverage/src/lib/models.ts | 23 +++ .../runner/doc-processer.integration.test.ts | 21 ++ .../src/lib/runner/doc-processer.ts | 191 +++++++++++++----- .../src/lib/runner/index.ts | 110 ++++------ .../src/lib/runner/runner.integration.test.ts | 22 +- 10 files changed, 279 insertions(+), 200 deletions(-) create mode 100644 packages/plugin-doc-coverage/src/lib/runner/doc-processer.integration.test.ts diff --git a/packages/plugin-doc-coverage/mocks/component-mock.ts b/packages/plugin-doc-coverage/mocks/component-mock.ts index e66f4f859..acd80f441 100644 --- a/packages/plugin-doc-coverage/mocks/component-mock.ts +++ b/packages/plugin-doc-coverage/mocks/component-mock.ts @@ -5,3 +5,45 @@ export function DUMMY_FUNCTION() { return 'Hello World'; } + +export function DUMMY_FUNCTION_2() { + return 'Hello World 2'; +} + +class DummyClass { + /** + * Dummy property that returns 'Hello World 3'. + * @returns {string} - The string 'Hello World 3'. + */ + dummyProperty = 'Hello World 3'; + + /** + * Dummy method that returns 'Hello World 4'. + * @returns {string} - The string 'Hello World 4'. + */ + dummyMethod() { + return 'Hello World 4'; + } + + constructor() { + this.dummyProperty = 'Hello World 3'; + } +} + +export default DummyClass; + +export const variableDummy = 'Hello World 5'; + +export const variableDummy2 = 'Hello World 6'; + +/** Dummy variable that returns 'Hello World 7'. */ +export const variableDummy3 = 'Hello World 7'; + +/** Dummy interface that returns 'Hello World 8'. */ +export interface DummyInterface { + dummyProperty: string; + dummyMethod(): string; +} + +/** Dummy type that returns 'Hello World 9'. */ +export type DummyType = string; diff --git a/packages/plugin-doc-coverage/src/lib/config.ts b/packages/plugin-doc-coverage/src/lib/config.ts index a55a46137..853099796 100644 --- a/packages/plugin-doc-coverage/src/lib/config.ts +++ b/packages/plugin-doc-coverage/src/lib/config.ts @@ -3,16 +3,13 @@ import { z } from 'zod'; export type DocType = 'percentage-coverage'; export const docCoveragePluginConfigSchema = z.object({ - language: z.enum(['javascript', 'typescript'], { - description: 'Programming language of the source code to analyze', - }), sourceGlob: z .string({ description: 'Glob pattern to find source files', }) - .optional(), + .default('src/**/*.{ts,tsx}'), }); -export type DocCoveragePluginConfig = z.input< +export type DocCoveragePluginConfig = z.infer< typeof docCoveragePluginConfigSchema >; diff --git a/packages/plugin-doc-coverage/src/lib/config.unit.test.ts b/packages/plugin-doc-coverage/src/lib/config.unit.test.ts index 36840ece8..4cb85ed8a 100644 --- a/packages/plugin-doc-coverage/src/lib/config.unit.test.ts +++ b/packages/plugin-doc-coverage/src/lib/config.unit.test.ts @@ -5,60 +5,23 @@ import { } from './config.js'; describe('docCoveragePluginConfigSchema', () => { - it('accepts a documentation coverage configuration with all entities', () => { + it('accepts a valid source glob pattern', () => { expect(() => docCoveragePluginConfigSchema.parse({ - language: 'typescript', sourceGlob: 'src/**/*.{ts,tsx}', } satisfies DocCoveragePluginConfig), ).not.toThrow(); }); - it('accepts minimal configuration with only language', () => { - expect(() => - docCoveragePluginConfigSchema.parse({ - language: 'javascript', - } satisfies DocCoveragePluginConfig), - ).not.toThrow(); - }); - - it('accepts configuration without sourceGlob', () => { - const config = { - language: 'typescript', - } satisfies DocCoveragePluginConfig; - const parsed = docCoveragePluginConfigSchema.parse(config); - - expect(parsed.sourceGlob).toBeUndefined(); - }); - - it('throws for missing language', () => { - expect(() => - docCoveragePluginConfigSchema.parse({ - sourceGlob: 'src/**/*.ts', - }), - ).toThrow('invalid_type'); + it('not throws for missing sourceGlob', () => { + expect(() => docCoveragePluginConfigSchema.parse({})).not.toThrow(); }); - it('throws for invalid language', () => { + it('throws for invalid sourceGlob type', () => { expect(() => docCoveragePluginConfigSchema.parse({ - language: 'python', - sourceGlob: 'src/**/*.py', + sourceGlob: 123, }), - ).toThrow('Invalid enum value'); - }); - - it('accepts both typescript and javascript as valid languages', () => { - expect(() => - docCoveragePluginConfigSchema.parse({ - language: 'typescript', - }), - ).not.toThrow(); - - expect(() => - docCoveragePluginConfigSchema.parse({ - language: 'javascript', - }), - ).not.toThrow(); + ).toThrow('Expected string'); }); }); diff --git a/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.ts b/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.ts index 0504c8218..37d54858e 100644 --- a/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.ts +++ b/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.ts @@ -19,7 +19,7 @@ import { createRunnerConfig } from './runner/index.js'; * plugins: [ * // ... other plugins ... * await docCoveragePlugin({ - * language: 'typescript' + * sourceGlob: 'src/**/*.{ts,tsx}' * }) * ] * } diff --git a/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.unit.test.ts b/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.unit.test.ts index 20e44b1da..992f6de6c 100644 --- a/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.unit.test.ts +++ b/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.unit.test.ts @@ -12,9 +12,7 @@ vi.mock('./runner/index.ts', () => ({ describe('docCoveragePlugin', () => { it('should initialise a Documentation coverage plugin', async () => { await expect( - docCoveragePlugin({ - language: 'typescript', - }), + docCoveragePlugin({ sourceGlob: 'src/**/*.ts' }), ).resolves.toStrictEqual( expect.objectContaining({ slug: 'doc-coverage', @@ -27,9 +25,7 @@ describe('docCoveragePlugin', () => { it('should generate percentage coverage audit', async () => { await expect( - docCoveragePlugin({ - language: 'typescript', - }), + docCoveragePlugin({ sourceGlob: 'src/**/*.ts' }), ).resolves.toStrictEqual( expect.objectContaining({ audits: [ @@ -47,9 +43,7 @@ describe('docCoveragePlugin', () => { it('should include package metadata', async () => { await expect( - docCoveragePlugin({ - language: 'typescript', - }), + docCoveragePlugin({ sourceGlob: 'src/**/*.ts' }), ).resolves.toStrictEqual( expect.objectContaining({ icon: 'folder-src', diff --git a/packages/plugin-doc-coverage/src/lib/models.ts b/packages/plugin-doc-coverage/src/lib/models.ts index ff4605b8c..dd61e1bdc 100644 --- a/packages/plugin-doc-coverage/src/lib/models.ts +++ b/packages/plugin-doc-coverage/src/lib/models.ts @@ -5,3 +5,26 @@ export type UndocumentedItem = { line: number; class?: string; }; + +export type CoverageByType = { + functions: number; + variables: number; + classes: number; + methods: number; + properties: number; + interfaces: number; + types: number; +}; + +export type CoverageKey = keyof CoverageByType; + +export type DocumentationStats = { + documented: number; + total: number; +}; + +export type CoverageResult = { + undocumentedItems: UndocumentedItem[]; + currentCoverage: number; + coverageByType: CoverageByType; +}; diff --git a/packages/plugin-doc-coverage/src/lib/runner/doc-processer.integration.test.ts b/packages/plugin-doc-coverage/src/lib/runner/doc-processer.integration.test.ts new file mode 100644 index 000000000..94bebac27 --- /dev/null +++ b/packages/plugin-doc-coverage/src/lib/runner/doc-processer.integration.test.ts @@ -0,0 +1,21 @@ +import { processDocCoverage } from './doc-processer'; + +describe('docProcesser', () => { + it('should successfully get documentation coverage', () => { + const results = processDocCoverage( + 'packages/plugin-doc-coverage/mocks/**/*.ts', + ); + console.log(results); + expect(results).toBeDefined(); + expect(results.currentCoverage).toBe(60); + expect(results.coverageByType).toEqual({ + functions: 50, + variables: 33.33, + classes: 0, + methods: 100, + properties: 100, + interfaces: 100, + types: 100, + }); + }); +}); diff --git a/packages/plugin-doc-coverage/src/lib/runner/doc-processer.ts b/packages/plugin-doc-coverage/src/lib/runner/doc-processer.ts index 86d75b343..46a2cbacf 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/doc-processer.ts +++ b/packages/plugin-doc-coverage/src/lib/runner/doc-processer.ts @@ -1,19 +1,36 @@ import { Project } from 'ts-morph'; -import type { UndocumentedItem } from '../models.js'; +import type { + CoverageByType, + CoverageKey, + CoverageResult, + DocumentationStats, + UndocumentedItem, +} from '../models.js'; /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable functional/immutable-data */ /* eslint-disable @typescript-eslint/max-params */ /* eslint-disable functional/no-let */ -export function processDocCoverage(toInclude: string): { - undocumentedItems: UndocumentedItem[]; - coverage: number; -} { +/** + * Processes documentation coverage for TypeScript files in the specified path + * @param toInclude - The file path pattern to include for documentation analysis + * @returns {CoverageResult} Object containing coverage statistics and undocumented items + */ +export function processDocCoverage(toInclude: string): CoverageResult { const project = new Project(); project.addSourceFilesAtPaths(toInclude); - let itsDocumented = 0; + const stats: Record = { + functions: { documented: 0, total: 0 }, + variables: { documented: 0, total: 0 }, + classes: { documented: 0, total: 0 }, + methods: { documented: 0, total: 0 }, + properties: { documented: 0, total: 0 }, + interfaces: { documented: 0, total: 0 }, + types: { documented: 0, total: 0 }, + }; + const undocumentedItems: UndocumentedItem[] = []; project.getSourceFiles().forEach(sourceFile => { @@ -21,127 +38,146 @@ export function processDocCoverage(toInclude: string): { return; } - processClassDeclarations( - sourceFile, - undocumentedItems, - count => (itsDocumented += count), - ); - processDeclarations( - sourceFile, - undocumentedItems, - count => (itsDocumented += count), - ); + processClassDeclarations(sourceFile, undocumentedItems, stats); + processDeclarations(sourceFile, undocumentedItems, stats); }); - return calculateCoverage(undocumentedItems, itsDocumented); + return { + undocumentedItems, + currentCoverage: calculateOverallCoverage(stats), + coverageByType: calculateCoverageByType(stats), + }; } +/** + * Checks if a file is a test file based on its path + * @param filePath - The path of the file to check + * @returns {boolean} True if the file is a test file, false otherwise + */ function isTestFile(filePath: string): boolean { return filePath.includes('.spec.') || filePath.includes('.test.'); } +/** + * Creates an undocumented item entry + * @param file - The file path where the item was found + * @param type - The type of the undocumented item + * @param name - The name of the undocumented item + * @param line - The line number where the item appears + * @returns {UndocumentedItem} The undocumented item entry + */ function addUndocumentedItem( file: string, - type: string, + type: CoverageKey, name: string, line: number, ): UndocumentedItem { - return { - file, - type, - name, - line, - }; + return { file, type, name, line }; } +/** + * Processes class declarations in a source file and updates documentation statistics + * @param sourceFile - The source file to process + * @param undocumentedItems - Array to store undocumented items found + * @param stats - Object to track documentation statistics + */ function processClassDeclarations( sourceFile: any, undocumentedItems: UndocumentedItem[], - onDocumented: (count: number) => void, + stats: Record, ): void { sourceFile.getClasses().forEach((classDeclaration: any) => { const className = classDeclaration.getName() || 'Anonymous Class'; const filePath = sourceFile.getFilePath(); + stats.classes.total++; - // Process class itself if (classDeclaration.getJsDocs().length === 0) { undocumentedItems.push( addUndocumentedItem( filePath, - 'class', + 'classes', className, classDeclaration.getStartLineNumber(), ), ); } else { - onDocumented(1); + stats.classes.documented++; } // Process properties classDeclaration.getProperties().forEach((property: any) => { + stats.properties.total++; if (property.getJsDocs().length === 0) { undocumentedItems.push( addUndocumentedItem( filePath, - 'property', + 'properties', property.getName(), property.getStartLineNumber(), ), ); } else { - onDocumented(1); + stats.properties.documented++; } }); // Process methods classDeclaration.getMethods().forEach((method: any) => { + stats.methods.total++; if (method.getJsDocs().length === 0) { undocumentedItems.push( addUndocumentedItem( filePath, - 'method', + 'methods', method.getName(), method.getStartLineNumber(), ), ); } else { - onDocumented(1); + stats.methods.documented++; } }); }); } +/** + * Processes declarations (functions, variables, interfaces, and types) in a source file + * @param sourceFile - The source file to process + * @param undocumentedItems - Array to store undocumented items found + * @param stats - Object to track documentation statistics + */ function processDeclarations( sourceFile: any, undocumentedItems: UndocumentedItem[], - onDocumented: (count: number) => void, + stats: Record, ): void { const filePath = sourceFile.getFilePath(); // Process functions processItems( sourceFile.getFunctions(), - 'function', + 'functions', item => item.getName() || 'Anonymous Function', filePath, undocumentedItems, - onDocumented, + stats, ); // Process variables sourceFile.getVariableStatements().forEach((statement: any) => { statement.getDeclarations().forEach((declaration: any) => { + stats.variables.total++; if (statement.getJsDocs().length === 0) { undocumentedItems.push( addUndocumentedItem( filePath, - 'variable', + 'variables', declaration.getName(), declaration.getStartLineNumber(), ), ); } else { - onDocumented(1); + stats.variables.documented++; } }); }); @@ -149,31 +185,41 @@ function processDeclarations( // Process interfaces and types processItems( sourceFile.getInterfaces(), - 'interface', + 'interfaces', item => item.getName(), filePath, undocumentedItems, - onDocumented, + stats, ); processItems( sourceFile.getTypeAliases(), - 'type', + 'types', item => item.getName(), filePath, undocumentedItems, - onDocumented, + stats, ); } +/** + * Generic function to process a collection of items and update documentation statistics + * @param items - Array of items to process + * @param type - The type of items being processed + * @param getName - Function to extract the name from an item + * @param filePath - The path of the file being processed + * @param undocumentedItems - Array to store undocumented items found + * @param stats - Object to track documentation statistics + */ function processItems( items: any[], - type: string, + type: CoverageKey, getName: (item: any) => string, filePath: string, undocumentedItems: UndocumentedItem[], - onDocumented: (count: number) => void, + stats: Record, ): void { items.forEach(item => { + stats[type].total++; if (item.getJsDocs().length === 0) { undocumentedItems.push( addUndocumentedItem( @@ -184,15 +230,60 @@ function processItems( ), ); } else { - onDocumented(1); + stats[type].documented++; } }); } -function calculateCoverage( - undocumentedItems: UndocumentedItem[], - documented: number, -) { - const coverage = (documented / (documented + undocumentedItems.length)) * 100; - return { undocumentedItems, coverage }; +/** + * Calculates the overall documentation coverage percentage + * @param stats - Object containing documentation statistics + * @returns {number} The overall coverage percentage (0-100) + */ +function calculateOverallCoverage( + stats: Record, +): number { + let totalDocumented = 0; + let totalItems = 0; + + Object.values(stats).forEach(({ documented, total }) => { + totalDocumented += documented; + totalItems += total; + }); + + return totalItems === 0 ? 0 : (totalDocumented / totalItems) * 100; +} + +/** + * Calculates documentation coverage percentage for each type + * @param stats - Object containing documentation statistics + * @returns {CoverageByType} Object containing coverage percentages for each type + */ +function calculateCoverageByType( + stats: Record, +): CoverageByType { + const calculatePercentage = (documented: number, total: number) => + total === 0 ? 0 : Number(((documented / total) * 100).toFixed(2)); + + return { + functions: calculatePercentage( + stats.functions.documented, + stats.functions.total, + ), + variables: calculatePercentage( + stats.variables.documented, + stats.variables.total, + ), + classes: calculatePercentage(stats.classes.documented, stats.classes.total), + methods: calculatePercentage(stats.methods.documented, stats.methods.total), + properties: calculatePercentage( + stats.properties.documented, + stats.properties.total, + ), + interfaces: calculatePercentage( + stats.interfaces.documented, + stats.interfaces.total, + ), + types: calculatePercentage(stats.types.documented, stats.types.total), + }; } diff --git a/packages/plugin-doc-coverage/src/lib/runner/index.ts b/packages/plugin-doc-coverage/src/lib/runner/index.ts index 23aed7a92..c56713ada 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/index.ts +++ b/packages/plugin-doc-coverage/src/lib/runner/index.ts @@ -10,35 +10,19 @@ import { ui, } from '@code-pushup/utils'; import type { DocCoveragePluginConfig } from '../config.js'; -import type { UndocumentedItem } from '../models.js'; -import { - DEFAULT_SOURCE_GLOB, - PLUGIN_CONFIG_PATH, - RUNNER_OUTPUT_PATH, -} from './constants.js'; +import type { CoverageResult } from '../models.js'; +import { PLUGIN_CONFIG_PATH, RUNNER_OUTPUT_PATH } from './constants.js'; import { processDocCoverage } from './doc-processer.js'; export { PLUGIN_CONFIG_PATH, RUNNER_OUTPUT_PATH } from './constants.js'; -/** - * Execute the Typedoc process. - * @param config - The configuration for the Typedoc process. - */ -async function _executeTypedocProcess( - config: DocCoveragePluginConfig, -): Promise<{ - undocumentedItems: UndocumentedItem[]; - coverage: number; -}> { - const { sourceGlob, language } = config; - +export async function executeRunner(): Promise { try { - return processDocCoverage(sourceGlob || DEFAULT_SOURCE_GLOB[language]); - // console.table(undocumentedItems); - // await executeProcess({ - // command: COMMANDS_FOR_LANGUAGES[language].command, - // args: processedArgs.split(' '), - // }); + const config = + await readJsonFile(PLUGIN_CONFIG_PATH); + console.log(config.sourceGlob, 'dadawdawd'); + const processResult = processDocCoverage(config.sourceGlob); + await _createFinalReport(processResult); } catch (error) { if (error instanceof ProcessError) { ui().logger.error(bold('stdout from failed Typedoc process:')); @@ -52,61 +36,10 @@ async function _executeTypedocProcess( } } -/** - * Process the Typedoc results. - * @param outputFolderPath - The path to the output folder. - */ -async function _processTypedocResults( - undocumentedItems: UndocumentedItem[], - coverage: number, -): Promise { - try { - const auditOutputs: AuditOutput[] = [ - { - slug: 'percentage-coverage', - value: coverage, - score: coverage / 100, - displayValue: `${coverage} %`, - details: { - issues: undocumentedItems.map(item => ({ - message: `Missing documentation for a ${item.type}`, - source: { file: item.file, position: { startLine: item.line } }, - severity: 'warning', - })), - }, - }, - ]; - - await ensureDirectoryExists(path.dirname(RUNNER_OUTPUT_PATH)); - await writeFile(RUNNER_OUTPUT_PATH, JSON.stringify(auditOutputs)); - } catch (error) { - if (error instanceof ProcessError) { - ui().logger.error(bold('stdout from failed coverage tool process:')); - ui().logger.error(error.stdout); - ui().logger.error(bold('stderr from failed coverage tool process:')); - ui().logger.error(error.stderr); - throw new Error( - 'Doc Coverage plugin: Running Compodoc failed. Please check the error above.', - ); - } - } -} - -export async function executeRunner(): Promise { - const config = - await readJsonFile(PLUGIN_CONFIG_PATH); - const processResult = await _executeTypedocProcess(config); - await _processTypedocResults( - processResult.undocumentedItems, - processResult.coverage, - ); -} - export async function createRunnerConfig( scriptPath: string, config: DocCoveragePluginConfig, ): Promise { - // Create JSON config for executeRunner await ensureDirectoryExists(path.dirname(PLUGIN_CONFIG_PATH)); await writeFile(PLUGIN_CONFIG_PATH, JSON.stringify(config)); @@ -116,3 +49,30 @@ export async function createRunnerConfig( outputFile: RUNNER_OUTPUT_PATH, }; } + +/** + * Create the final report. + * @param coverageResult - The coverage result. + */ +async function _createFinalReport( + coverageResult: CoverageResult, +): Promise { + const auditOutputs: AuditOutput[] = [ + { + slug: 'percentage-coverage', + value: coverageResult.currentCoverage, + score: coverageResult.currentCoverage / 100, + displayValue: `${coverageResult.currentCoverage} %`, + details: { + issues: coverageResult.undocumentedItems.map(item => ({ + message: `Missing documentation for a ${item.type}`, + source: { file: item.file, position: { startLine: item.line } }, + severity: 'warning', + })), + }, + }, + ]; + + await ensureDirectoryExists(path.dirname(RUNNER_OUTPUT_PATH)); + await writeFile(RUNNER_OUTPUT_PATH, JSON.stringify(auditOutputs)); +} diff --git a/packages/plugin-doc-coverage/src/lib/runner/runner.integration.test.ts b/packages/plugin-doc-coverage/src/lib/runner/runner.integration.test.ts index 49cd966d3..8d8fe081a 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/runner.integration.test.ts +++ b/packages/plugin-doc-coverage/src/lib/runner/runner.integration.test.ts @@ -1,10 +1,6 @@ import { writeFile } from 'node:fs/promises'; import { describe, it } from 'vitest'; -import type { - AuditOutput, - AuditOutputs, - RunnerConfig, -} from '@code-pushup/models'; +import type { AuditOutputs, RunnerConfig } from '@code-pushup/models'; import { readJsonFile, removeDirectoryIfExists } from '@code-pushup/utils'; import type { DocCoveragePluginConfig } from '../config.js'; import { @@ -17,7 +13,6 @@ import { createRunnerConfig, executeRunner } from './index.js'; describe('createRunnerConfig', () => { it('should create a valid runner config', async () => { const runnerConfig = await createRunnerConfig('executeRunner.ts', { - language: 'typescript', sourceGlob: 'src/**/*.ts', }); expect(runnerConfig).toStrictEqual({ @@ -31,7 +26,6 @@ describe('createRunnerConfig', () => { await removeDirectoryIfExists(WORKDIR); const pluginConfig: DocCoveragePluginConfig = { - language: 'typescript', sourceGlob: 'src/**/*.ts', }; @@ -46,9 +40,11 @@ describe('createRunnerConfig', () => { describe('executeRunner', () => { it( 'should successfully execute runner', + { + timeout: 60 * 1000, + }, async () => { const config: DocCoveragePluginConfig = { - language: 'typescript', sourceGlob: 'packages/plugin-doc-coverage/mocks/*.ts', }; @@ -56,15 +52,7 @@ describe('executeRunner', () => { await executeRunner(); const results = await readJsonFile(RUNNER_OUTPUT_PATH); - expect(results).toStrictEqual([ - expect.objectContaining({ - slug: 'percentage-coverage', - score: 1, - value: 100, - displayValue: '100 %', - } satisfies AuditOutput), - ]); + expect(results).toBeDefined(); }, - { timeout: 60 * 1000 }, ); }); From 3fe6e3b8cca195650eb6e42645147ac80e121f6c Mon Sep 17 00:00:00 2001 From: Alejandro Date: Fri, 20 Dec 2024 03:35:13 +0100 Subject: [PATCH 05/66] feat(plugin-doc-coverage): improve code fragmentation, tests and models. Solid version working --- code-pushup.config.bundled_oq2x2csd2at.mjs | 1210 +++++++++++++++++ code-pushup.config.ts | 19 +- code-pushup.preset.ts | 53 +- .../mocks/component-mock.spec.ts | 16 + .../mocks/component-mock.ts | 74 +- .../mocks/fixtures/angular/app.component.css | 4 + .../mocks/fixtures/angular/app.component.html | 1 + .../fixtures/angular/app.component.spec.ts | 3 + .../mocks/fixtures/angular/app.component.ts | 18 + .../fixtures/angular/map-event.function.ts | 8 + .../mocks/source-files.mock.ts | 114 ++ .../plugin-doc-coverage/src/lib/config.ts | 9 +- .../src/lib/config.unit.test.ts | 2 +- .../plugin-doc-coverage/src/lib/constants.ts | 68 + .../src/lib/doc-coverage-plugin.ts | 53 +- .../src/lib/doc-coverage-plugin.unit.test.ts | 12 +- .../plugin-doc-coverage/src/lib/models.ts | 31 +- .../doc-processer.integration.test.ts.snap | 139 ++ .../doc-processer.unit.test.ts.snap | 92 ++ .../src/lib/runner/constants.ts | 42 - .../runner/doc-processer.integration.test.ts | 34 +- .../src/lib/runner/doc-processer.ts | 350 ++--- .../src/lib/runner/doc-processer.unit.test.ts | 198 +++ .../runner/doc-processer.unit.test.ts.snap | 125 ++ .../src/lib/runner/index.ts | 78 -- .../src/lib/runner/models.ts | 37 + .../src/lib/runner/runner.integration.test.ts | 58 - .../src/lib/runner/runner.ts | 49 + .../src/lib/runner/utils.ts | 61 + packages/plugin-doc-coverage/src/lib/utils.ts | 46 + .../plugin-doc-coverage/tsconfig.test.json | 3 +- 31 files changed, 2417 insertions(+), 590 deletions(-) create mode 100644 code-pushup.config.bundled_oq2x2csd2at.mjs create mode 100644 packages/plugin-doc-coverage/mocks/component-mock.spec.ts create mode 100644 packages/plugin-doc-coverage/mocks/fixtures/angular/app.component.css create mode 100644 packages/plugin-doc-coverage/mocks/fixtures/angular/app.component.html create mode 100644 packages/plugin-doc-coverage/mocks/fixtures/angular/app.component.spec.ts create mode 100644 packages/plugin-doc-coverage/mocks/fixtures/angular/app.component.ts create mode 100644 packages/plugin-doc-coverage/mocks/fixtures/angular/map-event.function.ts create mode 100644 packages/plugin-doc-coverage/mocks/source-files.mock.ts create mode 100644 packages/plugin-doc-coverage/src/lib/constants.ts create mode 100644 packages/plugin-doc-coverage/src/lib/runner/__snapshots__/doc-processer.integration.test.ts.snap create mode 100644 packages/plugin-doc-coverage/src/lib/runner/__snapshots__/doc-processer.unit.test.ts.snap delete mode 100644 packages/plugin-doc-coverage/src/lib/runner/constants.ts create mode 100644 packages/plugin-doc-coverage/src/lib/runner/doc-processer.unit.test.ts create mode 100644 packages/plugin-doc-coverage/src/lib/runner/doc-processer.unit.test.ts.snap delete mode 100644 packages/plugin-doc-coverage/src/lib/runner/index.ts create mode 100644 packages/plugin-doc-coverage/src/lib/runner/models.ts delete mode 100644 packages/plugin-doc-coverage/src/lib/runner/runner.integration.test.ts create mode 100644 packages/plugin-doc-coverage/src/lib/runner/runner.ts create mode 100644 packages/plugin-doc-coverage/src/lib/runner/utils.ts create mode 100644 packages/plugin-doc-coverage/src/lib/utils.ts diff --git a/code-pushup.config.bundled_oq2x2csd2at.mjs b/code-pushup.config.bundled_oq2x2csd2at.mjs new file mode 100644 index 000000000..4c033e42d --- /dev/null +++ b/code-pushup.config.bundled_oq2x2csd2at.mjs @@ -0,0 +1,1210 @@ +// code-pushup.config.ts +// packages/utils/src/lib/logging.ts +import isaacs_cliui from '@isaacs/cliui'; +import { cliui } from '@poppinss/cliui'; +// packages/plugin-coverage/src/lib/runner/index.ts +import { bold } from 'ansis'; +// packages/plugin-coverage/src/lib/nx/coverage-paths.ts +import { bold as bold2 } from 'ansis'; +// packages/plugin-lighthouse/src/lib/normalize-flags.ts +import { bold as bold3, yellow } from 'ansis'; +// packages/plugin-lighthouse/src/lib/runner/utils.ts +import { bold as bold7 } from 'ansis'; +// packages/plugin-lighthouse/src/lib/runner/details/details.ts +import { bold as bold6, yellow as yellow2 } from 'ansis'; +// packages/plugin-lighthouse/src/lib/runner/details/item-value.ts +import { bold as bold4 } from 'ansis'; +// packages/plugin-lighthouse/src/lib/runner/details/utils.ts +import { bold as bold5 } from 'ansis'; +// packages/utils/src/lib/reports/utils.ts +import ansis from 'ansis'; +// packages/utils/src/lib/file-system.ts +import { bold as bold8, gray } from 'ansis'; +import { underline } from 'ansis'; +// packages/utils/src/lib/progress.ts +import { black, bold as bold9, gray as gray2, green } from 'ansis'; +// packages/utils/src/lib/reports/log-stdout-summary.ts +import { bold as bold11, cyan, cyanBright, green as green2, red } from 'ansis'; +// packages/utils/src/lib/zod-validation.ts +import { bold as bold12, red as red2 } from 'ansis'; +// packages/plugin-js-packages/src/lib/runner/audit/transform.ts +import { md } from 'build-md'; +// packages/plugin-js-packages/src/lib/runner/outdated/transform.ts +import { md as md2 } from 'build-md'; +import { md as md3 } from 'build-md'; +// packages/utils/src/lib/reports/generate-md-report.ts +import { MarkdownDocument as MarkdownDocument3, md as md6 } from 'build-md'; +// packages/utils/src/lib/reports/formatting.ts +import { MarkdownDocument, md as md4 } from 'build-md'; +// packages/utils/src/lib/reports/generate-md-report-categoy-section.ts +import { MarkdownDocument as MarkdownDocument2, md as md5 } from 'build-md'; +// packages/utils/src/lib/reports/generate-md-reports-diff.ts +import { MarkdownDocument as MarkdownDocument5, md as md8 } from 'build-md'; +// packages/utils/src/lib/reports/generate-md-reports-diff-utils.ts +import { MarkdownDocument as MarkdownDocument4, md as md7 } from 'build-md'; +import { bundleRequire } from 'bundle-require'; +// packages/plugin-lighthouse/src/lib/constants.ts +import { DEFAULT_FLAGS } from 'chrome-launcher/dist/flags.js'; +import 'dotenv/config'; +// packages/plugin-eslint/src/lib/setup.ts +import { ESLint } from 'eslint'; +// packages/plugin-eslint/src/lib/meta/versions/detect.ts +import { ESLint as ESLint2 } from 'eslint'; +// packages/plugin-eslint/src/lib/meta/versions/flat.ts +import { builtinRules } from 'eslint/use-at-your-own-risk'; +// packages/plugin-lighthouse/src/lib/runner/constants.ts +import { defaultConfig } from 'lighthouse'; +import log from 'lighthouse-logger'; +// packages/plugin-lighthouse/src/lib/runner/runner.ts +import { runLighthouse } from 'lighthouse/cli/run.js'; +import desktopConfig from 'lighthouse/core/config/desktop-config.js'; +import experimentalConfig from 'lighthouse/core/config/experimental-config.js'; +import perfConfig from 'lighthouse/core/config/perf-config.js'; +import { MultiProgressBars } from 'multi-progress-bars'; +// packages/utils/src/lib/execute-process.ts +import { spawn } from 'node:child_process'; +// packages/plugin-eslint/src/lib/meta/hash.ts +import { createHash } from 'node:crypto'; +import { writeFile } from 'node:fs/promises'; +// packages/plugin-eslint/src/lib/runner/index.ts +import { writeFile as writeFile2 } from 'node:fs/promises'; +// packages/plugin-js-packages/src/lib/runner/index.ts +import { writeFile as writeFile3 } from 'node:fs/promises'; +// packages/plugin-js-packages/src/lib/package-managers/derive-package-manager.ts +import { readFile } from 'node:fs/promises'; +import { + mkdir, + readFile as readFile2, + readdir, + rm, + stat, +} from 'node:fs/promises'; +// packages/plugin-coverage/src/lib/coverage-plugin.ts +import { createRequire } from 'node:module'; +// packages/plugin-eslint/src/lib/eslint-plugin.ts +import { createRequire as createRequire2 } from 'node:module'; +// packages/plugin-js-packages/src/lib/js-packages-plugin.ts +import { createRequire as createRequire3 } from 'node:module'; +// packages/plugin-lighthouse/src/lib/lighthouse-plugin.ts +import { createRequire as createRequire4 } from 'node:module'; +// packages/plugin-eslint/src/lib/runner/lint.ts +import { platform } from 'node:os'; +// packages/utils/src/lib/transform.ts +import { platform as platform2 } from 'node:os'; +import path4 from 'node:path'; +import path3 from 'node:path'; +// packages/plugin-coverage/src/lib/runner/constants.ts +import path from 'node:path'; +// packages/plugin-coverage/src/lib/runner/lcov/lcov-runner.ts +import path2 from 'node:path'; +import path5 from 'node:path'; +import path8 from 'node:path'; +import path6 from 'node:path'; +import path7 from 'node:path'; +// packages/plugin-eslint/src/lib/nx/utils.ts +import path9 from 'node:path'; +import path14 from 'node:path'; +// packages/plugin-js-packages/src/lib/runner/utils.ts +import path10 from 'node:path'; +import path12 from 'node:path'; +// packages/plugin-js-packages/src/lib/runner/constants.ts +import path11 from 'node:path'; +import path13 from 'node:path'; +import path15 from 'node:path'; +import path16 from 'node:path'; +import path17 from 'node:path'; +import path18 from 'node:path'; +// packages/utils/src/lib/git/git.ts +import path19 from 'node:path'; +import path20 from 'node:path'; +// packages/utils/src/lib/reports/load-report.ts +import path21 from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { fileURLToPath as fileURLToPath2 } from 'node:url'; +import { pathToFileURL } from 'node:url'; +import { fileURLToPath as fileURLToPath3 } from 'node:url'; +// packages/plugin-coverage/src/lib/runner/lcov/parse-lcov.ts +import parseLcovExport from 'parse-lcov'; +import { clean, diff, neq } from 'semver'; +// packages/utils/src/lib/semver.ts +import { rcompare, valid } from 'semver'; +// packages/utils/src/lib/git/git.commits-and-tags.ts +import { simpleGit } from 'simple-git'; +import { simpleGit as simpleGit2 } from 'simple-git'; +// packages/plugin-doc-coverage/src/lib/runner/doc-processer.ts +import { Project } from 'ts-morph'; +// packages/plugin-doc-coverage/src/lib/utils.ts +import { SyntaxKind } from 'typescript'; +import { z as z5 } from 'zod'; +// packages/plugin-coverage/src/lib/config.ts +import { z } from 'zod'; +// packages/plugin-doc-coverage/src/lib/config.ts +import { z as z2 } from 'zod'; +// packages/plugin-eslint/src/lib/config.ts +import { z as z3 } from 'zod'; +// packages/plugin-js-packages/src/lib/config.ts +import { z as z4 } from 'zod'; +import { issueSeveritySchema } from '@code-pushup/models'; +import { DEFAULT_PERSIST_OUTPUT_DIR } from '@code-pushup/models'; +// packages/plugin-lighthouse/src/lib/runner/details/opportunity.type.ts +import { tableSchema as tableSchema2 } from '@code-pushup/models'; +// packages/plugin-lighthouse/src/lib/runner/details/table.type.ts +import { tableSchema } from '@code-pushup/models'; +// packages/utils/src/index.ts +import { exists as exists4 } from '@code-pushup/models'; +// packages/utils/src/lib/formatting.ts +import { + MAX_DESCRIPTION_LENGTH, + MAX_ISSUE_MESSAGE_LENGTH, + MAX_TITLE_LENGTH, +} from '@code-pushup/models'; +import { commitSchema } from '@code-pushup/models'; +import { reportSchema } from '@code-pushup/models'; +import { capitalize } from '@code-pushup/utils'; +import { + ProcessError, + ensureDirectoryExists, + executeProcess, + filePathToCliArg, + readJsonFile, + ui as ui2, +} from '@code-pushup/utils'; +import { pluginWorkDir } from '@code-pushup/utils'; +import { exists, readTextFile, toUnixNewlines, ui } from '@code-pushup/utils'; +// packages/plugin-coverage/src/lib/runner/lcov/transform.ts +import { toNumberPrecision, toOrdinal } from '@code-pushup/utils'; +import { importModule, ui as ui3 } from '@code-pushup/utils'; +import { toArray } from '@code-pushup/utils'; +// packages/plugin-eslint/src/lib/meta/groups.ts +import { objectToKeys, slugify as slugify2 } from '@code-pushup/utils'; +import { slugify } from '@code-pushup/utils'; +// packages/plugin-eslint/src/lib/meta/parse.ts +import { toArray as toArray2 } from '@code-pushup/utils'; +import { + exists as exists2, + findNearestFile, + toArray as toArray3, + ui as ui4, +} from '@code-pushup/utils'; +// packages/plugin-eslint/src/lib/meta/versions/legacy.ts +import { + distinct, + exists as exists3, + toArray as toArray4, + ui as ui5, +} from '@code-pushup/utils'; +import { fileExists } from '@code-pushup/utils'; +// packages/plugin-eslint/src/lib/meta/transform.ts +import { truncateDescription, truncateTitle } from '@code-pushup/utils'; +import { + ensureDirectoryExists as ensureDirectoryExists2, + filePathToCliArg as filePathToCliArg3, + pluginWorkDir as pluginWorkDir2, + readJsonFile as readJsonFile2, +} from '@code-pushup/utils'; +import { + distinct as distinct2, + executeProcess as executeProcess2, + filePathToCliArg as filePathToCliArg2, + toArray as toArray5, +} from '@code-pushup/utils'; +// packages/plugin-eslint/src/lib/runner/transform.ts +import { + compareIssueSeverity, + countOccurrences, + objectToEntries, + pluralizeToken, + truncateIssueMessage, + ui as ui6, +} from '@code-pushup/utils'; +import { + fileExists as fileExists2, + toArray as toArray6, +} from '@code-pushup/utils'; +// packages/plugin-js-packages/src/lib/package-managers/npm/npm.ts +import { objectToKeys as objectToKeys3 } from '@code-pushup/utils'; +import { + crawlFileSystem, + objectFromEntries, + objectToKeys as objectToKeys2, + readJsonFile as readJsonFile3, +} from '@code-pushup/utils'; +// packages/plugin-js-packages/src/lib/package-managers/npm/audit-result.ts +import { objectToEntries as objectToEntries2 } from '@code-pushup/utils'; +// packages/plugin-js-packages/src/lib/package-managers/npm/outdated-result.ts +import { objectToEntries as objectToEntries3 } from '@code-pushup/utils'; +// packages/plugin-js-packages/src/lib/package-managers/pnpm/pnpm.ts +import { objectToKeys as objectToKeys4 } from '@code-pushup/utils'; +// packages/plugin-js-packages/src/lib/package-managers/pnpm/outdated-result.ts +import { objectToEntries as objectToEntries4 } from '@code-pushup/utils'; +// packages/plugin-js-packages/src/lib/package-managers/yarn-classic/audit-result.ts +import { fromJsonLines } from '@code-pushup/utils'; +// packages/plugin-js-packages/src/lib/package-managers/yarn-classic/outdated-result.ts +import { + fromJsonLines as fromJsonLines2, + objectFromEntries as objectFromEntries2, + objectToEntries as objectToEntries5, + objectToKeys as objectToKeys5, +} from '@code-pushup/utils'; +import { + ensureDirectoryExists as ensureDirectoryExists3, + executeProcess as executeProcess3, + filePathToCliArg as filePathToCliArg4, + isPromiseFulfilledResult, + isPromiseRejectedResult, + objectFromEntries as objectFromEntries4, + readJsonFile as readJsonFile4, +} from '@code-pushup/utils'; +import { objectToEntries as objectToEntries6 } from '@code-pushup/utils'; +import { pluginWorkDir as pluginWorkDir3 } from '@code-pushup/utils'; +import { + objectFromEntries as objectFromEntries3, + pluralize, +} from '@code-pushup/utils'; +// packages/plugin-js-packages/src/lib/runner/outdated/constants.ts +import { objectToKeys as objectToKeys6 } from '@code-pushup/utils'; +import { fileExists as fileExists3 } from '@code-pushup/utils'; +// packages/plugin-js-packages/src/lib/package-managers/derive-yarn.ts +import { executeProcess as executeProcess4 } from '@code-pushup/utils'; +import { ui as ui7 } from '@code-pushup/utils'; +import { ensureDirectoryExists as ensureDirectoryExists4 } from '@code-pushup/utils'; +import { + formatReportScore, + importModule as importModule2, + readJsonFile as readJsonFile5, + ui as ui10, +} from '@code-pushup/utils'; +import { ui as ui9 } from '@code-pushup/utils'; +import { + formatBytes as formatBytes2, + formatDuration as formatDuration2, + html as html2, +} from '@code-pushup/utils'; +import { + formatBytes, + formatDuration, + html, + truncateText, + ui as ui8, +} from '@code-pushup/utils'; +// packages/plugin-lighthouse/src/lib/utils.ts +import { filterItemRefsBy, toArray as toArray7 } from '@code-pushup/utils'; + +var coverageTypeSchema = z.enum(['function', 'branch', 'line']); +var coverageResultSchema = z.union([ + z.object({ + resultsPath: z + .string({ + description: 'Path to coverage results for Nx setup.', + }) + .includes('lcov'), + pathToProject: z + .string({ + description: + 'Path from workspace root to project root. Necessary for LCOV reports which provide a relative path.', + }) + .optional(), + }), + z + .string({ + description: 'Path to coverage results for a single project setup.', + }) + .includes('lcov'), +]); +var coveragePluginConfigSchema = z.object({ + coverageToolCommand: z + .object({ + command: z + .string({ description: 'Command to run coverage tool.' }) + .min(1), + args: z + .array(z.string(), { + description: 'Arguments to be passed to the coverage tool.', + }) + .optional(), + }) + .optional(), + coverageTypes: z + .array(coverageTypeSchema, { + description: 'Coverage types measured. Defaults to all available types.', + }) + .min(1) + .default(['function', 'branch', 'line']), + reports: z + .array(coverageResultSchema, { + description: + 'Path to all code coverage report files. Only LCOV format is supported for now.', + }) + .min(1), + perfectScoreThreshold: z + .number({ + description: + 'Score will be 1 (perfect) for this coverage and above. Score range is 0 - 1.', + }) + .gt(0) + .max(1) + .optional(), +}); + +var WORKDIR = pluginWorkDir('coverage'); +var RUNNER_OUTPUT_PATH = path.join(WORKDIR, 'runner-output.json'); +var PLUGIN_CONFIG_PATH = path.join( + process.cwd(), + WORKDIR, + 'plugin-config.json', +); + +var godKnows = parseLcovExport; +var parseLcov = 'default' in godKnows ? godKnows.default : godKnows; + +var docCoveragePluginConfigSchema = z2.object({ + onlyAudits: z2.array(z2.string()).optional(), + sourceGlob: z2 + .array(z2.string()) + .default(['src/**/*.{ts,tsx}', '!**/*.spec.ts', '!**/*.test.ts']), +}); + +// packages/plugin-doc-coverage/src/lib/constants.ts +var PLUGIN_SLUG = 'doc-coverage'; +var AUDITS_MAP = { + 'classes-coverage': { + slug: 'classes-coverage', + title: 'Classes coverage', + description: 'Coverage of classes', + }, + 'methods-coverage': { + slug: 'methods-coverage', + title: 'Methods coverage', + description: 'Coverage of methods', + }, + 'functions-coverage': { + slug: 'functions-coverage', + title: 'Functions coverage', + description: 'Coverage of functions', + }, + 'interfaces-coverage': { + slug: 'interfaces-coverage', + title: 'Interfaces coverage', + description: 'Coverage of interfaces', + }, + 'variables-coverage': { + slug: 'variables-coverage', + title: 'Variables coverage', + description: 'Coverage of variables', + }, + 'properties-coverage': { + slug: 'properties-coverage', + title: 'Properties coverage', + description: 'Coverage of properties', + }, + 'types-coverage': { + slug: 'types-coverage', + title: 'Types coverage', + description: 'Coverage of types', + }, + 'enums-coverage': { + slug: 'enums-coverage', + title: 'Enums coverage', + description: 'Coverage of enums', + }, +}; +var groups = [ + { + slug: 'documentation-coverage', + title: 'Documentation coverage', + description: 'Documentation coverage', + refs: Object.keys(AUDITS_MAP).map(slug => { + switch (slug) { + case 'classes-coverage': + case 'functions-coverage': + case 'methods-coverage': + return { slug, weight: 2 }; + case 'interfaces-coverage': + case 'properties-coverage': + case 'types-coverage': + default: + return { slug, weight: 1 }; + } + }), + }, +]; + +function filterAuditsByPluginConfig(config2) { + const { onlyAudits } = config2; + if (!onlyAudits || onlyAudits.length === 0) { + return Object.values(AUDITS_MAP); + } + return Object.values(AUDITS_MAP).filter(audit => + onlyAudits.includes(audit.slug), + ); +} +function filterGroupsByOnlyAudits(groups2, options) { + const audits2 = filterAuditsByPluginConfig(options); + return groups2 + .map(group => ({ + ...group, + refs: group.refs.filter(ref => + audits2.some(audit => audit.slug === ref.slug), + ), + })) + .filter(group => group.refs.length > 0); +} +function trasformCoverageReportToAudits(coverageResult, options) { + return Object.entries(coverageResult) + .filter( + ([type]) => + !options.onlyAudits?.length || + options.onlyAudits.includes(`${type}-coverage`), + ) + .map(([type, items]) => { + const coverageType = type; + const coverage = items.coverage; + return { + slug: `${coverageType}-coverage`, + value: coverage, + score: coverage / 100, + displayValue: `${coverage} %`, + details: { + issues: items.issues.map(({ file, line }) => ({ + message: 'Missing documentation', + source: { file, position: { startLine: line } }, + severity: 'warning', + })), + }, + }; + }); +} +function getCoverageTypeFromKind(kind) { + switch (kind) { + case SyntaxKind.ClassDeclaration: + return 'classes'; + case SyntaxKind.MethodDeclaration: + return 'methods'; + case SyntaxKind.FunctionDeclaration: + return 'functions'; + case SyntaxKind.InterfaceDeclaration: + return 'interfaces'; + case SyntaxKind.EnumDeclaration: + return 'enums'; + case SyntaxKind.VariableDeclaration: + return 'variables'; + case SyntaxKind.PropertyDeclaration: + return 'properties'; + case SyntaxKind.TypeAliasDeclaration: + return 'types'; + default: + throw new Error(`Unsupported syntax kind: ${kind}`); + } +} + +// packages/plugin-doc-coverage/src/lib/runner/utils.ts +function createEmptyUnprocessedCoverageReport() { + return { + enums: { nodesCount: 0, issues: [] }, + interfaces: { nodesCount: 0, issues: [] }, + types: { nodesCount: 0, issues: [] }, + functions: { nodesCount: 0, issues: [] }, + variables: { nodesCount: 0, issues: [] }, + classes: { nodesCount: 0, issues: [] }, + methods: { nodesCount: 0, issues: [] }, + properties: { nodesCount: 0, issues: [] }, + }; +} +function calculateCoverage2(result) { + return Object.fromEntries( + Object.entries(result).map(([key, value]) => { + const type = key; + return [ + type, + { + coverage: + value.nodesCount === 0 + ? 100 + : (1 - value.issues.length / value.nodesCount) * 100, + issues: value.issues, + nodesCount: value.nodesCount, + }, + ]; + }), + ); +} + +// packages/plugin-doc-coverage/src/lib/runner/doc-processer.ts +function processDocCoverage(config2) { + const project = new Project(); + project.addSourceFilesAtPaths(config2.sourceGlob); + return getUnprocessedCoverageReport(project.getSourceFiles()); +} +function getUnprocessedCoverageReport(sourceFiles) { + const unprocessedCoverageReport = sourceFiles.reduce( + (coverageReportOfAllFiles, sourceFile) => { + const filePath = sourceFile.getFilePath(); + const classes = sourceFile.getClasses(); + const allNodesFromFile = [ + ...sourceFile.getFunctions(), + ...classes, + ...getClassNodes(classes), + ...sourceFile.getTypeAliases(), + ...sourceFile.getEnums(), + ...sourceFile.getInterfaces(), + // ...sourceFile.getVariableStatements().flatMap(statement => statement.getDeclarations()) + ]; + const coverageReportOfCurrentFile = allNodesFromFile.reduce( + (acc, node) => { + const nodeType = getCoverageTypeFromKind(node.getKind()); + acc[nodeType].nodesCount++; + if (node.getJsDocs().length === 0) { + acc[nodeType].issues.push( + getUndocumentedNode( + filePath, + nodeType, + node.getName() || '', + node.getStartLineNumber(), + ), + ); + } + return acc; + }, + createEmptyUnprocessedCoverageReport(), + ); + return mergeCoverageResults( + coverageReportOfAllFiles, + coverageReportOfCurrentFile, + ); + }, + createEmptyUnprocessedCoverageReport(), + ); + return calculateCoverage2(unprocessedCoverageReport); +} +function mergeCoverageResults(results, current) { + return { + ...Object.fromEntries( + Object.entries(results).map(([key, value]) => { + const node = value; + const type = key; + return [ + type, + { + nodesCount: node.nodesCount + current[type].nodesCount, + issues: [...node.issues, ...current[type].issues], + }, + ]; + }), + ), + }; +} +function getClassNodes(classNodes) { + return classNodes.flatMap(classNode => [ + ...classNode.getMethods(), + ...classNode.getProperties(), + ]); +} +function getUndocumentedNode(file, type, name, line) { + return { file, type, name, line }; +} + +// packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.ts +var PLUGIN_TITLE = 'Documentation coverage'; +var PLUGIN_DESCRIPTION = 'Official Code PushUp documentation coverage plugin.'; +var PLUGIN_DOCS_URL = + 'https://www.npmjs.com/package/@code-pushup/doc-coverage-plugin/'; +async function docCoveragePlugin(config2) { + const docCoverageConfig = docCoveragePluginConfigSchema.parse(config2); + const groupsC = filterGroupsByOnlyAudits(groups, docCoverageConfig); + const auditsC = filterAuditsByPluginConfig(docCoverageConfig); + return { + slug: PLUGIN_SLUG, + title: PLUGIN_TITLE, + icon: 'folder-src', + description: PLUGIN_DESCRIPTION, + docsUrl: PLUGIN_DOCS_URL, + groups: filterGroupsByOnlyAudits(groups, docCoverageConfig), + audits: filterAuditsByPluginConfig(docCoverageConfig), + runner: createRunnerFunction(docCoverageConfig), + }; +} +function createRunnerFunction(config2) { + return () => { + const coverageResult = processDocCoverage(config2); + return trasformCoverageReportToAudits(coverageResult, config2); + }; +} + +// packages/plugin-doc-coverage/src/index.ts +var src_default = docCoveragePlugin; + +var patternsSchema = z3.union([z3.string(), z3.array(z3.string()).min(1)], { + description: + 'Lint target files. May contain file paths, directory paths or glob patterns', +}); +var eslintrcSchema = z3.string({ description: 'Path to ESLint config file' }); +var eslintTargetObjectSchema = z3.object({ + eslintrc: eslintrcSchema.optional(), + patterns: patternsSchema, +}); +var eslintTargetSchema = z3 + .union([patternsSchema, eslintTargetObjectSchema]) + .transform(target => + typeof target === 'string' || Array.isArray(target) + ? { patterns: target } + : target, + ); +var eslintPluginConfigSchema = z3 + .union([eslintTargetSchema, z3.array(eslintTargetSchema).min(1)]) + .transform(toArray); + +// packages/plugin-eslint/src/lib/runner/index.ts +var WORKDIR2 = pluginWorkDir2('eslint'); +var RUNNER_OUTPUT_PATH2 = path7.join(WORKDIR2, 'runner-output.json'); +var PLUGIN_CONFIG_PATH2 = path7.join( + process.cwd(), + WORKDIR2, + 'plugin-config.json', +); + +// packages/plugin-js-packages/src/lib/constants.ts +var defaultAuditLevelMapping = { + critical: 'error', + high: 'error', + moderate: 'warning', + low: 'warning', + info: 'info', +}; + +// packages/plugin-js-packages/src/lib/config.ts +var dependencyGroups = ['prod', 'dev', 'optional']; +var dependencyGroupSchema = z4.enum(dependencyGroups); +var packageCommandSchema = z4.enum(['audit', 'outdated']); +var packageManagerIdSchema = z4.enum([ + 'npm', + 'yarn-classic', + 'yarn-modern', + 'pnpm', +]); +var packageJsonPathSchema = z4 + .union([ + z4.array(z4.string()).min(1), + z4.object({ autoSearch: z4.literal(true) }), + ]) + .describe( + 'File paths to package.json. Looks only at root package.json by default', + ) + .default(['package.json']); +var packageAuditLevels = ['critical', 'high', 'moderate', 'low', 'info']; +var packageAuditLevelSchema = z4.enum(packageAuditLevels); +function fillAuditLevelMapping(mapping) { + return { + critical: mapping.critical ?? defaultAuditLevelMapping.critical, + high: mapping.high ?? defaultAuditLevelMapping.high, + moderate: mapping.moderate ?? defaultAuditLevelMapping.moderate, + low: mapping.low ?? defaultAuditLevelMapping.low, + info: mapping.info ?? defaultAuditLevelMapping.info, + }; +} +var jsPackagesPluginConfigSchema = z4.object({ + checks: z4 + .array(packageCommandSchema, { + description: + 'Package manager commands to be run. Defaults to both audit and outdated.', + }) + .min(1) + .default(['audit', 'outdated']), + packageManager: packageManagerIdSchema + .describe('Package manager to be used.') + .optional(), + dependencyGroups: z4 + .array(dependencyGroupSchema) + .min(1) + .default(['prod', 'dev']), + auditLevelMapping: z4 + .record(packageAuditLevelSchema, issueSeveritySchema, { + description: + 'Mapping of audit levels to issue severity. Custom mapping or overrides may be entered manually, otherwise has a default preset.', + }) + .default(defaultAuditLevelMapping) + .transform(fillAuditLevelMapping), + packageJsonPaths: packageJsonPathSchema, +}); + +function filterAuditResult(result, key, referenceResult) { + if (result.vulnerabilities.length === 0) { + return result; + } + const uniqueResult = result.vulnerabilities.reduce( + (acc, ref) => { + const matchReference = referenceResult ?? acc; + const isMatch = matchReference.vulnerabilities + .map(vulnerability => vulnerability[key]) + .includes(ref[key]); + if (isMatch) { + return { + vulnerabilities: acc.vulnerabilities, + summary: { + ...acc.summary, + [ref.severity]: acc.summary[ref.severity] - 1, + total: acc.summary.total - 1, + }, + }; + } + return { + vulnerabilities: [...acc.vulnerabilities, ref], + summary: acc.summary, + }; + }, + { vulnerabilities: [], summary: result.summary }, + ); + return { + vulnerabilities: uniqueResult.vulnerabilities, + summary: uniqueResult.summary, + }; +} + +// packages/plugin-js-packages/src/lib/package-managers/constants.ts +var COMMON_AUDIT_ARGS = ['audit', '--json']; +var COMMON_OUTDATED_ARGS = ['outdated', '--json']; + +function npmToAuditResult(output) { + const npmAudit = JSON.parse(output); + const vulnerabilities = objectToEntries2(npmAudit.vulnerabilities).map( + ([name, detail]) => { + const advisory = npmToAdvisory(name, npmAudit.vulnerabilities); + return { + name: name.toString(), + severity: detail.severity, + versionRange: detail.range, + directDependency: detail.isDirect ? true : (detail.effects[0] ?? ''), + fixInformation: npmToFixInformation(detail.fixAvailable), + ...(advisory != null && { + title: advisory.title, + url: advisory.url, + }), + }; + }, + ); + return { + vulnerabilities, + summary: npmAudit.metadata.vulnerabilities, + }; +} +function npmToFixInformation(fixAvailable) { + if (typeof fixAvailable === 'boolean') { + return fixAvailable ? 'Fix is available.' : ''; + } + return `Fix available: Update \`${fixAvailable.name}\` to version **${fixAvailable.version}**${fixAvailable.isSemVerMajor ? ' (breaking change).' : '.'}`; +} +function npmToAdvisory( + name, + vulnerabilities, + prevNodes = /* @__PURE__ */ new Set(), +) { + const advisory = vulnerabilities[name]?.via; + if ( + Array.isArray(advisory) && + advisory.length > 0 && + typeof advisory[0] === 'object' + ) { + return { title: advisory[0].title, url: advisory[0].url }; + } + if ( + Array.isArray(advisory) && + advisory.length > 0 && + advisory.every(value => typeof value === 'string') + ) { + let advisoryInfo = null; + let newReferences = []; + let advisoryInfoFound = false; + for (const via of advisory) { + if (!prevNodes.has(via)) { + newReferences.push(via); + } + } + while (newReferences.length > 0 && !advisoryInfoFound) { + const ref = newReferences.pop(); + prevNodes.add(ref); + const result = npmToAdvisory(ref, vulnerabilities, prevNodes); + if (result != null) { + advisoryInfo = { title: result.title, url: result.url }; + advisoryInfoFound = true; + } + } + return advisoryInfo; + } + return null; +} + +function npmToOutdatedResult(output) { + const npmOutdated = JSON.parse(output); + return objectToEntries3(npmOutdated) + .filter(entry => entry[1].current != null) + .map(([name, overview]) => ({ + name, + current: overview.current, + latest: overview.latest, + type: overview.type, + ...(overview.homepage != null && { url: overview.homepage }), + })); +} + +// packages/plugin-js-packages/src/lib/package-managers/npm/npm.ts +var npmDependencyOptions = { + prod: ['--omit=dev', '--omit=optional'], + dev: ['--include=dev', '--omit=optional'], + optional: ['--include=optional', '--omit=dev'], +}; +var npmPackageManager = { + slug: 'npm', + name: 'NPM', + command: 'npm', + icon: 'npm', + docs: { + homepage: 'https://docs.npmjs.com/', + audit: 'https://docs.npmjs.com/cli/commands/npm-audit', + outdated: 'https://docs.npmjs.com/cli/commands/npm-outdated', + }, + audit: { + getCommandArgs: groupDep => [ + ...COMMON_AUDIT_ARGS, + ...npmDependencyOptions[groupDep], + '--audit-level=none', + ], + unifyResult: npmToAuditResult, + // prod dependencies need to be filtered out manually since v10 + postProcessResult: results => { + const depGroups = objectToKeys3(results); + const devFilter = + results.dev && results.prod + ? filterAuditResult(results.dev, 'name', results.prod) + : results.dev; + const optionalFilter = + results.optional && results.prod + ? filterAuditResult(results.optional, 'name', results.prod) + : results.optional; + return { + ...(depGroups.includes('prod') && { prod: results.prod }), + ...(depGroups.includes('dev') && { dev: devFilter }), + ...(depGroups.includes('optional') && { optional: optionalFilter }), + }; + }, + }, + outdated: { + commandArgs: [...COMMON_OUTDATED_ARGS, '--long'], + unifyResult: npmToOutdatedResult, + }, +}; + +var WORKDIR3 = pluginWorkDir3('js-packages'); +var RUNNER_OUTPUT_PATH3 = path11.join(WORKDIR3, 'runner-output.json'); +var PLUGIN_CONFIG_PATH3 = path11.join( + process.cwd(), + WORKDIR3, + 'plugin-config.json', +); + +var outdatedSeverity = { + major: 'error', + premajor: 'info', + minor: 'warning', + preminor: 'info', + patch: 'info', + prepatch: 'info', + prerelease: 'info', +}; +var RELEASE_TYPES = objectToKeys6(outdatedSeverity); + +var DEFAULT_CHROME_FLAGS = [...DEFAULT_FLAGS, '--headless']; +var LIGHTHOUSE_PLUGIN_SLUG = 'lighthouse'; +var LIGHTHOUSE_OUTPUT_PATH = path15.join( + DEFAULT_PERSIST_OUTPUT_DIR, + LIGHTHOUSE_PLUGIN_SLUG, +); + +var { audits, categories } = defaultConfig; +var allRawLighthouseAudits = await Promise.all( + (audits ?? []).map(loadLighthouseAudit), +); +var LIGHTHOUSE_NAVIGATION_AUDITS = allRawLighthouseAudits + .filter( + audit => + audit.meta.supportedModes == null || + (Array.isArray(audit.meta.supportedModes) && + audit.meta.supportedModes.includes('navigation')), + ) + .map(audit => ({ + slug: audit.meta.id, + title: getMetaString(audit.meta.title), + description: getMetaString(audit.meta.description), + })); +var navigationAuditSlugs = new Set( + LIGHTHOUSE_NAVIGATION_AUDITS.map(({ slug }) => slug), +); +var LIGHTHOUSE_GROUPS = Object.entries(categories ?? {}).map( + ([id, category]) => ({ + slug: id, + title: getMetaString(category.title), + ...(category.description && { + description: getMetaString(category.description), + }), + refs: category.auditRefs + .filter(({ id: auditSlug }) => navigationAuditSlugs.has(auditSlug)) + .map(ref => ({ + slug: ref.id, + weight: ref.weight, + })), + }), +); +function getMetaString(value) { + if (typeof value === 'string') { + return value; + } + return value.formattedDefault; +} +async function loadLighthouseAudit(value) { + if (typeof value === 'object' && 'implementation' in value) { + return value.implementation; + } + if (typeof value === 'function') { + return value; + } + const file = typeof value === 'string' ? value : value.path; + const module = await import(`lighthouse/core/audits/${file}.js`); + return module.default; +} +var LIGHTHOUSE_REPORT_NAME = 'lighthouse-report.json'; +var DEFAULT_CLI_FLAGS = { + // default values extracted from + // https://github.com/GoogleChrome/lighthouse/blob/7d80178c37a1b600ea8f092fc0b098029799a659/cli/cli-flags.js#L80 + verbose: false, + saveAssets: false, + chromeFlags: DEFAULT_CHROME_FLAGS, + port: 0, + hostname: '127.0.0.1', + view: false, + channel: 'cli', + // custom overwrites in favour of the plugin + // hide logs by default + quiet: true, + onlyAudits: [], + skipAudits: [], + onlyCategories: [], + output: ['json'], + outputPath: path16.join(LIGHTHOUSE_OUTPUT_PATH, LIGHTHOUSE_REPORT_NAME), +}; + +// packages/plugin-lighthouse/src/lib/normalize-flags.ts +var { onlyCategories, ...originalDefaultCliFlags } = DEFAULT_CLI_FLAGS; +var DEFAULT_LIGHTHOUSE_OPTIONS = { + ...originalDefaultCliFlags, + onlyGroups: onlyCategories, +}; +var lighthouseUnsupportedCliFlags = [ + 'precomputedLanternDataPath', + // Path to the file where precomputed lantern data should be read from. + 'chromeIgnoreDefaultFlags', + // ignore default flags from Lighthouse CLI + // No error reporting implemented as in the source Sentry was involved + // See: https://github.com/GoogleChrome/lighthouse/blob/d8ccf70692216b7fa047a4eaa2d1277b0b7fe947/cli/bin.js#L124 + 'enableErrorReporting', + // enable error reporting + // lighthouse CLI specific debug logs + 'list-all-audits', + // Prints a list of all available audits and exits. + 'list-locales', + // Prints a list of all supported locales and exits. + 'list-trace-categories', + // Prints a list of all required trace categories and exits. +]; +var LIGHTHOUSE_UNSUPPORTED_CLI_FLAGS = new Set(lighthouseUnsupportedCliFlags); + +function lighthouseGroupRef(groupSlug, weight = 1) { + return { + plugin: LIGHTHOUSE_PLUGIN_SLUG, + slug: groupSlug, + type: 'group', + weight, + }; +} + +// code-pushup.preset.ts +var lighthouseCategories = [ + { + slug: 'performance', + title: 'Performance', + refs: [lighthouseGroupRef('performance')], + }, + { + slug: 'a11y', + title: 'Accessibility', + refs: [lighthouseGroupRef('accessibility')], + }, + { + slug: 'best-practices', + title: 'Best Practices', + refs: [lighthouseGroupRef('best-practices')], + }, + { + slug: 'seo', + title: 'SEO', + refs: [lighthouseGroupRef('seo')], + }, +]; +function getDocCoverageCategories(config2) { + return [ + { + slug: 'doc-coverage-cat', + title: 'Documentation coverage', + description: 'Measures how much of your code is **documented**.', + refs: filterGroupsByOnlyAudits(groups, config2).map(group => ({ + weight: 1, + type: 'group', + plugin: PLUGIN_SLUG, + slug: group.slug, + })), + }, + ]; +} +var docCoverageCoreConfig = async config2 => { + return { + plugins: [await src_default(config2)], + categories: getDocCoverageCategories(config2), + }; +}; + +// packages/utils/src/lib/merge-configs.ts +function mergeConfigs(config2, ...configs) { + return configs.reduce( + (acc, obj) => ({ + ...acc, + ...mergeCategories(acc.categories, obj.categories), + ...mergePlugins(acc.plugins, obj.plugins), + ...mergePersist(acc.persist, obj.persist), + ...mergeUpload(acc.upload, obj.upload), + }), + config2, + ); +} +function mergeCategories(a, b) { + if (!a && !b) { + return {}; + } + const mergedMap = /* @__PURE__ */ new Map(); + const addToMap = categories2 => { + categories2.forEach(newObject => { + if (mergedMap.has(newObject.slug)) { + const existingObject = mergedMap.get(newObject.slug); + mergedMap.set(newObject.slug, { + ...existingObject, + ...newObject, + refs: mergeByUniqueCategoryRefCombination( + existingObject?.refs, + newObject.refs, + ), + }); + } else { + mergedMap.set(newObject.slug, newObject); + } + }); + }; + if (a) { + addToMap(a); + } + if (b) { + addToMap(b); + } + return { categories: [...mergedMap.values()] }; +} +function mergePlugins(a, b) { + if (!a && !b) { + return { plugins: [] }; + } + const mergedMap = /* @__PURE__ */ new Map(); + const addToMap = plugins => { + plugins.forEach(newObject => { + mergedMap.set(newObject.slug, newObject); + }); + }; + if (a) { + addToMap(a); + } + if (b) { + addToMap(b); + } + return { plugins: [...mergedMap.values()] }; +} +function mergePersist(a, b) { + if (!a && !b) { + return {}; + } + if (a) { + return b ? { persist: { ...a, ...b } } : {}; + } else { + return { persist: b }; + } +} +function mergeByUniqueCategoryRefCombination(a, b) { + const map = /* @__PURE__ */ new Map(); + const addToMap = refs => { + refs.forEach(ref => { + const uniqueIdentification = `${ref.type}:${ref.plugin}:${ref.slug}`; + if (map.has(uniqueIdentification)) { + map.set(uniqueIdentification, { + ...map.get(uniqueIdentification), + ...ref, + }); + } else { + map.set(uniqueIdentification, ref); + } + }); + }; + if (a) { + addToMap(a); + } + if (b) { + addToMap(b); + } + return [...map.values()]; +} +function mergeUpload(a, b) { + if (!a && !b) { + return {}; + } + if (a) { + return b ? { upload: { ...a, ...b } } : {}; + } else { + return { upload: b }; + } +} + +// code-pushup.config.ts +var envSchema = z5.object({ + CP_SERVER: z5.string().url(), + CP_API_KEY: z5.string().min(1), + CP_ORGANIZATION: z5.string().min(1), + CP_PROJECT: z5.string().min(1), +}); +var { data: env } = await envSchema.safeParseAsync(process.env); +var config = { + ...(env && { + upload: { + server: env.CP_SERVER, + apiKey: env.CP_API_KEY, + organization: env.CP_ORGANIZATION, + project: env.CP_PROJECT, + }, + }), + plugins: [], +}; +var code_pushup_config_default = mergeConfigs( + config, + // await coverageCoreConfigNx(), + // await jsPackagesCoreConfig(), + // await lighthouseCoreConfig( + // 'https://github.com/code-pushup/cli?tab=readme-ov-file#code-pushup-cli/', + // ), + // await eslintCoreConfigNx(), + await docCoverageCoreConfig({ + sourceGlob: ['packages/**/*.ts', '!**/*.spec.ts', '!**/*.test.ts'], + onlyAudits: ['methods-coverage', 'functions-coverage'], + }), +); +export { code_pushup_config_default as default }; +//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiY29kZS1wdXNodXAuY29uZmlnLnRzIiwgInBhY2thZ2VzL3BsdWdpbi1jb3ZlcmFnZS9zcmMvbGliL2NvdmVyYWdlLXBsdWdpbi50cyIsICJwYWNrYWdlcy9wbHVnaW4tY292ZXJhZ2Uvc3JjL2xpYi9jb25maWcudHMiLCAicGFja2FnZXMvcGx1Z2luLWNvdmVyYWdlL3NyYy9saWIvcnVubmVyL2luZGV4LnRzIiwgInBhY2thZ2VzL3BsdWdpbi1jb3ZlcmFnZS9zcmMvbGliL3J1bm5lci9jb25zdGFudHMudHMiLCAicGFja2FnZXMvcGx1Z2luLWNvdmVyYWdlL3NyYy9saWIvcnVubmVyL2xjb3YvbGNvdi1ydW5uZXIudHMiLCAicGFja2FnZXMvcGx1Z2luLWNvdmVyYWdlL3NyYy9saWIvcnVubmVyL2xjb3YvcGFyc2UtbGNvdi50cyIsICJwYWNrYWdlcy9wbHVnaW4tY292ZXJhZ2Uvc3JjL2xpYi9ydW5uZXIvbGNvdi90cmFuc2Zvcm0udHMiLCAicGFja2FnZXMvcGx1Z2luLWNvdmVyYWdlL3NyYy9saWIvbngvY292ZXJhZ2UtcGF0aHMudHMiLCAicGFja2FnZXMvcGx1Z2luLWRvYy1jb3ZlcmFnZS9zcmMvbGliL2NvbmZpZy50cyIsICJwYWNrYWdlcy9wbHVnaW4tZG9jLWNvdmVyYWdlL3NyYy9saWIvY29uc3RhbnRzLnRzIiwgInBhY2thZ2VzL3BsdWdpbi1kb2MtY292ZXJhZ2Uvc3JjL2xpYi9ydW5uZXIvZG9jLXByb2Nlc3Nlci50cyIsICJwYWNrYWdlcy9wbHVnaW4tZG9jLWNvdmVyYWdlL3NyYy9saWIvdXRpbHMudHMiLCAicGFja2FnZXMvcGx1Z2luLWRvYy1jb3ZlcmFnZS9zcmMvbGliL3J1bm5lci91dGlscy50cyIsICJwYWNrYWdlcy9wbHVnaW4tZG9jLWNvdmVyYWdlL3NyYy9saWIvZG9jLWNvdmVyYWdlLXBsdWdpbi50cyIsICJwYWNrYWdlcy9wbHVnaW4tZG9jLWNvdmVyYWdlL3NyYy9pbmRleC50cyIsICJwYWNrYWdlcy9wbHVnaW4tZXNsaW50L3NyYy9saWIvZXNsaW50LXBsdWdpbi50cyIsICJwYWNrYWdlcy9wbHVnaW4tZXNsaW50L3NyYy9saWIvY29uZmlnLnRzIiwgInBhY2thZ2VzL3BsdWdpbi1lc2xpbnQvc3JjL2xpYi9tZXRhL2dyb3Vwcy50cyIsICJwYWNrYWdlcy9wbHVnaW4tZXNsaW50L3NyYy9saWIvbWV0YS9oYXNoLnRzIiwgInBhY2thZ2VzL3BsdWdpbi1lc2xpbnQvc3JjL2xpYi9tZXRhL3BhcnNlLnRzIiwgInBhY2thZ2VzL3BsdWdpbi1lc2xpbnQvc3JjL2xpYi9tZXRhL3ZlcnNpb25zL2ZsYXQudHMiLCAicGFja2FnZXMvcGx1Z2luLWVzbGludC9zcmMvbGliL21ldGEvdmVyc2lvbnMvbGVnYWN5LnRzIiwgInBhY2thZ2VzL3BsdWdpbi1lc2xpbnQvc3JjL2xpYi9zZXR1cC50cyIsICJwYWNrYWdlcy9wbHVnaW4tZXNsaW50L3NyYy9saWIvbWV0YS92ZXJzaW9ucy9kZXRlY3QudHMiLCAicGFja2FnZXMvcGx1Z2luLWVzbGludC9zcmMvbGliL21ldGEvdHJhbnNmb3JtLnRzIiwgInBhY2thZ2VzL3BsdWdpbi1lc2xpbnQvc3JjL2xpYi9ydW5uZXIvaW5kZXgudHMiLCAicGFja2FnZXMvcGx1Z2luLWVzbGludC9zcmMvbGliL3J1bm5lci9saW50LnRzIiwgInBhY2thZ2VzL3BsdWdpbi1lc2xpbnQvc3JjL2xpYi9ydW5uZXIvdHJhbnNmb3JtLnRzIiwgInBhY2thZ2VzL3BsdWdpbi1lc2xpbnQvc3JjL2xpYi9ueC91dGlscy50cyIsICJwYWNrYWdlcy9wbHVnaW4tanMtcGFja2FnZXMvc3JjL2xpYi9qcy1wYWNrYWdlcy1wbHVnaW4udHMiLCAicGFja2FnZXMvcGx1Z2luLWpzLXBhY2thZ2VzL3NyYy9saWIvY29uZmlnLnRzIiwgInBhY2thZ2VzL3BsdWdpbi1qcy1wYWNrYWdlcy9zcmMvbGliL2NvbnN0YW50cy50cyIsICJwYWNrYWdlcy9wbHVnaW4tanMtcGFja2FnZXMvc3JjL2xpYi9wYWNrYWdlLW1hbmFnZXJzL25wbS9ucG0udHMiLCAicGFja2FnZXMvcGx1Z2luLWpzLXBhY2thZ2VzL3NyYy9saWIvcnVubmVyL3V0aWxzLnRzIiwgInBhY2thZ2VzL3BsdWdpbi1qcy1wYWNrYWdlcy9zcmMvbGliL3BhY2thZ2UtbWFuYWdlcnMvY29uc3RhbnRzLnRzIiwgInBhY2thZ2VzL3BsdWdpbi1qcy1wYWNrYWdlcy9zcmMvbGliL3BhY2thZ2UtbWFuYWdlcnMvbnBtL2F1ZGl0LXJlc3VsdC50cyIsICJwYWNrYWdlcy9wbHVnaW4tanMtcGFja2FnZXMvc3JjL2xpYi9wYWNrYWdlLW1hbmFnZXJzL25wbS9vdXRkYXRlZC1yZXN1bHQudHMiLCAicGFja2FnZXMvcGx1Z2luLWpzLXBhY2thZ2VzL3NyYy9saWIvcGFja2FnZS1tYW5hZ2Vycy9wbnBtL3BucG0udHMiLCAicGFja2FnZXMvcGx1Z2luLWpzLXBhY2thZ2VzL3NyYy9saWIvcGFja2FnZS1tYW5hZ2Vycy9wbnBtL291dGRhdGVkLXJlc3VsdC50cyIsICJwYWNrYWdlcy9wbHVnaW4tanMtcGFja2FnZXMvc3JjL2xpYi9wYWNrYWdlLW1hbmFnZXJzL3lhcm4tY2xhc3NpYy9hdWRpdC1yZXN1bHQudHMiLCAicGFja2FnZXMvcGx1Z2luLWpzLXBhY2thZ2VzL3NyYy9saWIvcGFja2FnZS1tYW5hZ2Vycy95YXJuLWNsYXNzaWMvb3V0ZGF0ZWQtcmVzdWx0LnRzIiwgInBhY2thZ2VzL3BsdWdpbi1qcy1wYWNrYWdlcy9zcmMvbGliL3J1bm5lci9pbmRleC50cyIsICJwYWNrYWdlcy9wbHVnaW4tanMtcGFja2FnZXMvc3JjL2xpYi9ydW5uZXIvYXVkaXQvdHJhbnNmb3JtLnRzIiwgInBhY2thZ2VzL3BsdWdpbi1qcy1wYWNrYWdlcy9zcmMvbGliL3J1bm5lci9jb25zdGFudHMudHMiLCAicGFja2FnZXMvcGx1Z2luLWpzLXBhY2thZ2VzL3NyYy9saWIvcnVubmVyL291dGRhdGVkL3RyYW5zZm9ybS50cyIsICJwYWNrYWdlcy9wbHVnaW4tanMtcGFja2FnZXMvc3JjL2xpYi9ydW5uZXIvb3V0ZGF0ZWQvY29uc3RhbnRzLnRzIiwgInBhY2thZ2VzL3BsdWdpbi1qcy1wYWNrYWdlcy9zcmMvbGliL3BhY2thZ2UtbWFuYWdlcnMvZGVyaXZlLXBhY2thZ2UtbWFuYWdlci50cyIsICJwYWNrYWdlcy9wbHVnaW4tanMtcGFja2FnZXMvc3JjL2xpYi9wYWNrYWdlLW1hbmFnZXJzL2Rlcml2ZS15YXJuLnRzIiwgInBhY2thZ2VzL3BsdWdpbi1saWdodGhvdXNlL3NyYy9saWIvbGlnaHRob3VzZS1wbHVnaW4udHMiLCAicGFja2FnZXMvcGx1Z2luLWxpZ2h0aG91c2Uvc3JjL2xpYi9jb25zdGFudHMudHMiLCAicGFja2FnZXMvcGx1Z2luLWxpZ2h0aG91c2Uvc3JjL2xpYi9ub3JtYWxpemUtZmxhZ3MudHMiLCAicGFja2FnZXMvcGx1Z2luLWxpZ2h0aG91c2Uvc3JjL2xpYi9ydW5uZXIvY29uc3RhbnRzLnRzIiwgInBhY2thZ2VzL3BsdWdpbi1saWdodGhvdXNlL3NyYy9saWIvcnVubmVyL3J1bm5lci50cyIsICJwYWNrYWdlcy9wbHVnaW4tbGlnaHRob3VzZS9zcmMvbGliL3J1bm5lci91dGlscy50cyIsICJwYWNrYWdlcy9wbHVnaW4tbGlnaHRob3VzZS9zcmMvbGliL3J1bm5lci9kZXRhaWxzL2RldGFpbHMudHMiLCAicGFja2FnZXMvcGx1Z2luLWxpZ2h0aG91c2Uvc3JjL2xpYi9ydW5uZXIvZGV0YWlscy9vcHBvcnR1bml0eS50eXBlLnRzIiwgInBhY2thZ2VzL3BsdWdpbi1saWdodGhvdXNlL3NyYy9saWIvcnVubmVyL2RldGFpbHMvdGFibGUudHlwZS50cyIsICJwYWNrYWdlcy9wbHVnaW4tbGlnaHRob3VzZS9zcmMvbGliL3J1bm5lci9kZXRhaWxzL2l0ZW0tdmFsdWUudHMiLCAicGFja2FnZXMvcGx1Z2luLWxpZ2h0aG91c2Uvc3JjL2xpYi9ydW5uZXIvZGV0YWlscy91dGlscy50cyIsICJwYWNrYWdlcy9wbHVnaW4tbGlnaHRob3VzZS9zcmMvbGliL3V0aWxzLnRzIiwgImNvZGUtcHVzaHVwLnByZXNldC50cyIsICJwYWNrYWdlcy91dGlscy9zcmMvaW5kZXgudHMiLCAicGFja2FnZXMvdXRpbHMvc3JjL2xpYi9leGVjdXRlLXByb2Nlc3MudHMiLCAicGFja2FnZXMvdXRpbHMvc3JjL2xpYi9yZXBvcnRzL3V0aWxzLnRzIiwgInBhY2thZ2VzL3V0aWxzL3NyYy9saWIvZmlsZS1zeXN0ZW0udHMiLCAicGFja2FnZXMvdXRpbHMvc3JjL2xpYi9mb3JtYXR0aW5nLnRzIiwgInBhY2thZ2VzL3V0aWxzL3NyYy9saWIvbG9nZ2luZy50cyIsICJwYWNrYWdlcy91dGlscy9zcmMvbGliL2dpdC9naXQuY29tbWl0cy1hbmQtdGFncy50cyIsICJwYWNrYWdlcy91dGlscy9zcmMvbGliL3NlbXZlci50cyIsICJwYWNrYWdlcy91dGlscy9zcmMvbGliL2dpdC9naXQudHMiLCAicGFja2FnZXMvdXRpbHMvc3JjL2xpYi90cmFuc2Zvcm0udHMiLCAicGFja2FnZXMvdXRpbHMvc3JjL2xpYi9tZXJnZS1jb25maWdzLnRzIiwgInBhY2thZ2VzL3V0aWxzL3NyYy9saWIvcHJvZ3Jlc3MudHMiLCAicGFja2FnZXMvdXRpbHMvc3JjL2xpYi9yZXBvcnRzL2dlbmVyYXRlLW1kLXJlcG9ydC50cyIsICJwYWNrYWdlcy91dGlscy9zcmMvbGliL3JlcG9ydHMvZm9ybWF0dGluZy50cyIsICJwYWNrYWdlcy91dGlscy9zcmMvbGliL3JlcG9ydHMvZ2VuZXJhdGUtbWQtcmVwb3J0LWNhdGVnb3ktc2VjdGlvbi50cyIsICJwYWNrYWdlcy91dGlscy9zcmMvbGliL3JlcG9ydHMvZ2VuZXJhdGUtbWQtcmVwb3J0cy1kaWZmLnRzIiwgInBhY2thZ2VzL3V0aWxzL3NyYy9saWIvcmVwb3J0cy9nZW5lcmF0ZS1tZC1yZXBvcnRzLWRpZmYtdXRpbHMudHMiLCAicGFja2FnZXMvdXRpbHMvc3JjL2xpYi9yZXBvcnRzL2xvYWQtcmVwb3J0LnRzIiwgInBhY2thZ2VzL3V0aWxzL3NyYy9saWIvcmVwb3J0cy9sb2ctc3Rkb3V0LXN1bW1hcnkudHMiLCAicGFja2FnZXMvdXRpbHMvc3JjL2xpYi96b2QtdmFsaWRhdGlvbi50cyJdLAogICJzb3VyY2VzQ29udGVudCI6IFsiY29uc3QgX19pbmplY3RlZF9maWxlbmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9jb2RlLXB1c2h1cC5jb25maWcudHNcIjtjb25zdCBfX2luamVjdGVkX2Rpcm5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGlcIjtjb25zdCBfX2luamVjdGVkX2ltcG9ydF9tZXRhX3VybF9fID0gXCJmaWxlOi8vL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvY29kZS1wdXNodXAuY29uZmlnLnRzXCI7aW1wb3J0ICdkb3RlbnYvY29uZmlnJztcbmltcG9ydCB7IHogfSBmcm9tICd6b2QnO1xuaW1wb3J0IHsgZG9jQ292ZXJhZ2VDb3JlQ29uZmlnIH0gZnJvbSAnLi9jb2RlLXB1c2h1cC5wcmVzZXQuanMnO1xuaW1wb3J0IHR5cGUgeyBDb3JlQ29uZmlnIH0gZnJvbSAnLi9wYWNrYWdlcy9tb2RlbHMvc3JjL2luZGV4LmpzJztcbmltcG9ydCB7IG1lcmdlQ29uZmlncyB9IGZyb20gJy4vcGFja2FnZXMvdXRpbHMvc3JjL2luZGV4LmpzJztcbi8vIGxvYWQgdXBsb2FkIGNvbmZpZ3VyYXRpb24gZnJvbSBlbnZpcm9ubWVudFxuY29uc3QgZW52U2NoZW1hID0gei5vYmplY3Qoe1xuICBDUF9TRVJWRVI6IHouc3RyaW5nKCkudXJsKCksXG4gIENQX0FQSV9LRVk6IHouc3RyaW5nKCkubWluKDEpLFxuICBDUF9PUkdBTklaQVRJT046IHouc3RyaW5nKCkubWluKDEpLFxuICBDUF9QUk9KRUNUOiB6LnN0cmluZygpLm1pbigxKSxcbn0pO1xuY29uc3QgeyBkYXRhOiBlbnYgfSA9IGF3YWl0IGVudlNjaGVtYS5zYWZlUGFyc2VBc3luYyhwcm9jZXNzLmVudik7XG5cbmNvbnN0IGNvbmZpZzogQ29yZUNvbmZpZyA9IHtcbiAgLi4uKGVudiAmJiB7XG4gICAgdXBsb2FkOiB7XG4gICAgICBzZXJ2ZXI6IGVudi5DUF9TRVJWRVIsXG4gICAgICBhcGlLZXk6IGVudi5DUF9BUElfS0VZLFxuICAgICAgb3JnYW5pemF0aW9uOiBlbnYuQ1BfT1JHQU5JWkFUSU9OLFxuICAgICAgcHJvamVjdDogZW52LkNQX1BST0pFQ1QsXG4gICAgfSxcbiAgfSksXG5cbiAgcGx1Z2luczogW10sXG59O1xuXG5leHBvcnQgZGVmYXVsdCBtZXJnZUNvbmZpZ3MoXG4gIGNvbmZpZyxcbiAgLy8gYXdhaXQgY292ZXJhZ2VDb3JlQ29uZmlnTngoKSxcbiAgLy8gYXdhaXQganNQYWNrYWdlc0NvcmVDb25maWcoKSxcbiAgLy8gYXdhaXQgbGlnaHRob3VzZUNvcmVDb25maWcoXG4gIC8vICAgJ2h0dHBzOi8vZ2l0aHViLmNvbS9jb2RlLXB1c2h1cC9jbGk/dGFiPXJlYWRtZS1vdi1maWxlI2NvZGUtcHVzaHVwLWNsaS8nLFxuICAvLyApLFxuICAvLyBhd2FpdCBlc2xpbnRDb3JlQ29uZmlnTngoKSxcbiAgYXdhaXQgZG9jQ292ZXJhZ2VDb3JlQ29uZmlnKHtcbiAgICBzb3VyY2VHbG9iOiBbJ3BhY2thZ2VzLyoqLyoudHMnLCAnISoqLyouc3BlYy50cycsICchKiovKi50ZXN0LnRzJ10sXG4gICAgb25seUF1ZGl0czogWydtZXRob2RzLWNvdmVyYWdlJywgJ2Z1bmN0aW9ucy1jb3ZlcmFnZSddXG4gIH0pLFxuKTtcbiIsICJjb25zdCBfX2luamVjdGVkX2ZpbGVuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1jb3ZlcmFnZS9zcmMvbGliL2NvdmVyYWdlLXBsdWdpbi50c1wiO2NvbnN0IF9faW5qZWN0ZWRfZGlybmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tY292ZXJhZ2Uvc3JjL2xpYlwiO2NvbnN0IF9faW5qZWN0ZWRfaW1wb3J0X21ldGFfdXJsX18gPSBcImZpbGU6Ly8vaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tY292ZXJhZ2Uvc3JjL2xpYi9jb3ZlcmFnZS1wbHVnaW4udHNcIjtpbXBvcnQgeyBjcmVhdGVSZXF1aXJlIH0gZnJvbSAnbm9kZTptb2R1bGUnO1xuaW1wb3J0IHBhdGggZnJvbSAnbm9kZTpwYXRoJztcbmltcG9ydCB7IGZpbGVVUkxUb1BhdGggfSBmcm9tICdub2RlOnVybCc7XG5pbXBvcnQgdHlwZSB7IEF1ZGl0LCBHcm91cCwgUGx1Z2luQ29uZmlnIH0gZnJvbSAnQGNvZGUtcHVzaHVwL21vZGVscyc7XG5pbXBvcnQgeyBjYXBpdGFsaXplIH0gZnJvbSAnQGNvZGUtcHVzaHVwL3V0aWxzJztcbmltcG9ydCB7XG4gIHR5cGUgQ292ZXJhZ2VQbHVnaW5Db25maWcsXG4gIHR5cGUgQ292ZXJhZ2VUeXBlLFxuICBjb3ZlcmFnZVBsdWdpbkNvbmZpZ1NjaGVtYSxcbn0gZnJvbSAnLi9jb25maWcuanMnO1xuaW1wb3J0IHsgY3JlYXRlUnVubmVyQ29uZmlnIH0gZnJvbSAnLi9ydW5uZXIvaW5kZXguanMnO1xuaW1wb3J0IHsgY292ZXJhZ2VEZXNjcmlwdGlvbiwgY292ZXJhZ2VUeXBlV2VpZ2h0TWFwcGVyIH0gZnJvbSAnLi91dGlscy5qcyc7XG5cbi8qKlxuICogSW5zdGFudGlhdGVzIENvZGUgUHVzaFVwIGNvZGUgY292ZXJhZ2UgcGx1Z2luIGZvciBjb3JlIGNvbmZpZy5cbiAqXG4gKiBAZXhhbXBsZVxuICogaW1wb3J0IGNvdmVyYWdlUGx1Z2luIGZyb20gJ0Bjb2RlLXB1c2h1cC9jb3ZlcmFnZS1wbHVnaW4nXG4gKlxuICogZXhwb3J0IGRlZmF1bHQge1xuICogICAvLyAuLi4gY29yZSBjb25maWcgLi4uXG4gKiAgIHBsdWdpbnM6IFtcbiAqICAgICAvLyAuLi4gb3RoZXIgcGx1Z2lucyAuLi5cbiAqICAgICBhd2FpdCBjb3ZlcmFnZVBsdWdpbih7XG4gKiAgICAgICByZXBvcnRzOiBbeyByZXN1bHRzUGF0aDogJ2NvdmVyYWdlL2NsaS9sY292LmluZm8nLCBwYXRoVG9Qcm9qZWN0OiAncGFja2FnZXMvY2xpJyB9XVxuICogICAgIH0pXG4gKiAgIF1cbiAqIH1cbiAqXG4gKiBAcmV0dXJucyBQbHVnaW4gY29uZmlndXJhdGlvbi5cbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGNvdmVyYWdlUGx1Z2luKFxuICBjb25maWc6IENvdmVyYWdlUGx1Z2luQ29uZmlnLFxuKTogUHJvbWlzZTxQbHVnaW5Db25maWc+IHtcbiAgY29uc3QgY292ZXJhZ2VDb25maWcgPSBjb3ZlcmFnZVBsdWdpbkNvbmZpZ1NjaGVtYS5wYXJzZShjb25maWcpO1xuXG4gIGNvbnN0IGF1ZGl0cyA9IGNvdmVyYWdlQ29uZmlnLmNvdmVyYWdlVHlwZXMubWFwKFxuICAgICh0eXBlKTogQXVkaXQgPT4gKHtcbiAgICAgIHNsdWc6IGAke3R5cGV9LWNvdmVyYWdlYCxcbiAgICAgIHRpdGxlOiBgJHtjYXBpdGFsaXplKHR5cGUpfSBjb3ZlcmFnZWAsXG4gICAgICBkZXNjcmlwdGlvbjogY292ZXJhZ2VEZXNjcmlwdGlvblt0eXBlXSxcbiAgICB9KSxcbiAgKTtcblxuICBjb25zdCBncm91cDogR3JvdXAgPSB7XG4gICAgc2x1ZzogJ2NvdmVyYWdlJyxcbiAgICB0aXRsZTogJ0NvZGUgY292ZXJhZ2UgbWV0cmljcycsXG4gICAgZGVzY3JpcHRpb246ICdHcm91cCBjb250YWluaW5nIGFsbCBkZWZpbmVkIGNvdmVyYWdlIHR5cGVzIGFzIGF1ZGl0cy4nLFxuICAgIHJlZnM6IGF1ZGl0cy5tYXAoYXVkaXQgPT4gKHtcbiAgICAgIC4uLmF1ZGl0LFxuICAgICAgd2VpZ2h0OlxuICAgICAgICBjb3ZlcmFnZVR5cGVXZWlnaHRNYXBwZXJbXG4gICAgICAgICAgYXVkaXQuc2x1Zy5zbGljZSgwLCBhdWRpdC5zbHVnLmluZGV4T2YoJy0nKSkgYXMgQ292ZXJhZ2VUeXBlXG4gICAgICAgIF0sXG4gICAgfSkpLFxuICB9O1xuXG4gIGNvbnN0IHJ1bm5lclNjcmlwdFBhdGggPSBwYXRoLmpvaW4oXG4gICAgZmlsZVVSTFRvUGF0aChwYXRoLmRpcm5hbWUoaW1wb3J0Lm1ldGEudXJsKSksXG4gICAgJy4uJyxcbiAgICAnYmluLmpzJyxcbiAgKTtcblxuICBjb25zdCBwYWNrYWdlSnNvbiA9IGNyZWF0ZVJlcXVpcmUoaW1wb3J0Lm1ldGEudXJsKShcbiAgICAnLi4vLi4vcGFja2FnZS5qc29uJyxcbiAgKSBhcyB0eXBlb2YgaW1wb3J0KCcuLi8uLi9wYWNrYWdlLmpzb24nKTtcblxuICByZXR1cm4ge1xuICAgIHNsdWc6ICdjb3ZlcmFnZScsXG4gICAgdGl0bGU6ICdDb2RlIGNvdmVyYWdlJyxcbiAgICBpY29uOiAnZm9sZGVyLWNvdmVyYWdlLW9wZW4nLFxuICAgIGRlc2NyaXB0aW9uOiAnT2ZmaWNpYWwgQ29kZSBQdXNoVXAgY29kZSBjb3ZlcmFnZSBwbHVnaW4uJyxcbiAgICBkb2NzVXJsOiAnaHR0cHM6Ly93d3cubnBtanMuY29tL3BhY2thZ2UvQGNvZGUtcHVzaHVwL2NvdmVyYWdlLXBsdWdpbi8nLFxuICAgIHBhY2thZ2VOYW1lOiBwYWNrYWdlSnNvbi5uYW1lLFxuICAgIHZlcnNpb246IHBhY2thZ2VKc29uLnZlcnNpb24sXG4gICAgYXVkaXRzLFxuICAgIGdyb3VwczogW2dyb3VwXSxcbiAgICBydW5uZXI6IGF3YWl0IGNyZWF0ZVJ1bm5lckNvbmZpZyhydW5uZXJTY3JpcHRQYXRoLCBjb3ZlcmFnZUNvbmZpZyksXG4gIH07XG59XG4iLCAiY29uc3QgX19pbmplY3RlZF9maWxlbmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tY292ZXJhZ2Uvc3JjL2xpYi9jb25maWcudHNcIjtjb25zdCBfX2luamVjdGVkX2Rpcm5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWNvdmVyYWdlL3NyYy9saWJcIjtjb25zdCBfX2luamVjdGVkX2ltcG9ydF9tZXRhX3VybF9fID0gXCJmaWxlOi8vL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWNvdmVyYWdlL3NyYy9saWIvY29uZmlnLnRzXCI7aW1wb3J0IHsgeiB9IGZyb20gJ3pvZCc7XG5cbmV4cG9ydCBjb25zdCBjb3ZlcmFnZVR5cGVTY2hlbWEgPSB6LmVudW0oWydmdW5jdGlvbicsICdicmFuY2gnLCAnbGluZSddKTtcbmV4cG9ydCB0eXBlIENvdmVyYWdlVHlwZSA9IHouaW5mZXI8dHlwZW9mIGNvdmVyYWdlVHlwZVNjaGVtYT47XG5cbmV4cG9ydCBjb25zdCBjb3ZlcmFnZVJlc3VsdFNjaGVtYSA9IHoudW5pb24oW1xuICB6Lm9iamVjdCh7XG4gICAgcmVzdWx0c1BhdGg6IHpcbiAgICAgIC5zdHJpbmcoe1xuICAgICAgICBkZXNjcmlwdGlvbjogJ1BhdGggdG8gY292ZXJhZ2UgcmVzdWx0cyBmb3IgTnggc2V0dXAuJyxcbiAgICAgIH0pXG4gICAgICAuaW5jbHVkZXMoJ2xjb3YnKSxcbiAgICBwYXRoVG9Qcm9qZWN0OiB6XG4gICAgICAuc3RyaW5nKHtcbiAgICAgICAgZGVzY3JpcHRpb246XG4gICAgICAgICAgJ1BhdGggZnJvbSB3b3Jrc3BhY2Ugcm9vdCB0byBwcm9qZWN0IHJvb3QuIE5lY2Vzc2FyeSBmb3IgTENPViByZXBvcnRzIHdoaWNoIHByb3ZpZGUgYSByZWxhdGl2ZSBwYXRoLicsXG4gICAgICB9KVxuICAgICAgLm9wdGlvbmFsKCksXG4gIH0pLFxuICB6XG4gICAgLnN0cmluZyh7XG4gICAgICBkZXNjcmlwdGlvbjogJ1BhdGggdG8gY292ZXJhZ2UgcmVzdWx0cyBmb3IgYSBzaW5nbGUgcHJvamVjdCBzZXR1cC4nLFxuICAgIH0pXG4gICAgLmluY2x1ZGVzKCdsY292JyksXG5dKTtcbmV4cG9ydCB0eXBlIENvdmVyYWdlUmVzdWx0ID0gei5pbmZlcjx0eXBlb2YgY292ZXJhZ2VSZXN1bHRTY2hlbWE+O1xuXG5leHBvcnQgY29uc3QgY292ZXJhZ2VQbHVnaW5Db25maWdTY2hlbWEgPSB6Lm9iamVjdCh7XG4gIGNvdmVyYWdlVG9vbENvbW1hbmQ6IHpcbiAgICAub2JqZWN0KHtcbiAgICAgIGNvbW1hbmQ6IHpcbiAgICAgICAgLnN0cmluZyh7IGRlc2NyaXB0aW9uOiAnQ29tbWFuZCB0byBydW4gY292ZXJhZ2UgdG9vbC4nIH0pXG4gICAgICAgIC5taW4oMSksXG4gICAgICBhcmdzOiB6XG4gICAgICAgIC5hcnJheSh6LnN0cmluZygpLCB7XG4gICAgICAgICAgZGVzY3JpcHRpb246ICdBcmd1bWVudHMgdG8gYmUgcGFzc2VkIHRvIHRoZSBjb3ZlcmFnZSB0b29sLicsXG4gICAgICAgIH0pXG4gICAgICAgIC5vcHRpb25hbCgpLFxuICAgIH0pXG4gICAgLm9wdGlvbmFsKCksXG4gIGNvdmVyYWdlVHlwZXM6IHpcbiAgICAuYXJyYXkoY292ZXJhZ2VUeXBlU2NoZW1hLCB7XG4gICAgICBkZXNjcmlwdGlvbjogJ0NvdmVyYWdlIHR5cGVzIG1lYXN1cmVkLiBEZWZhdWx0cyB0byBhbGwgYXZhaWxhYmxlIHR5cGVzLicsXG4gICAgfSlcbiAgICAubWluKDEpXG4gICAgLmRlZmF1bHQoWydmdW5jdGlvbicsICdicmFuY2gnLCAnbGluZSddKSxcbiAgcmVwb3J0czogelxuICAgIC5hcnJheShjb3ZlcmFnZVJlc3VsdFNjaGVtYSwge1xuICAgICAgZGVzY3JpcHRpb246XG4gICAgICAgICdQYXRoIHRvIGFsbCBjb2RlIGNvdmVyYWdlIHJlcG9ydCBmaWxlcy4gT25seSBMQ09WIGZvcm1hdCBpcyBzdXBwb3J0ZWQgZm9yIG5vdy4nLFxuICAgIH0pXG4gICAgLm1pbigxKSxcbiAgcGVyZmVjdFNjb3JlVGhyZXNob2xkOiB6XG4gICAgLm51bWJlcih7XG4gICAgICBkZXNjcmlwdGlvbjpcbiAgICAgICAgJ1Njb3JlIHdpbGwgYmUgMSAocGVyZmVjdCkgZm9yIHRoaXMgY292ZXJhZ2UgYW5kIGFib3ZlLiBTY29yZSByYW5nZSBpcyAwIC0gMS4nLFxuICAgIH0pXG4gICAgLmd0KDApXG4gICAgLm1heCgxKVxuICAgIC5vcHRpb25hbCgpLFxufSk7XG5leHBvcnQgdHlwZSBDb3ZlcmFnZVBsdWdpbkNvbmZpZyA9IHouaW5wdXQ8dHlwZW9mIGNvdmVyYWdlUGx1Z2luQ29uZmlnU2NoZW1hPjtcbmV4cG9ydCB0eXBlIEZpbmFsQ292ZXJhZ2VQbHVnaW5Db25maWcgPSB6LmluZmVyPFxuICB0eXBlb2YgY292ZXJhZ2VQbHVnaW5Db25maWdTY2hlbWFcbj47XG4iLCAiY29uc3QgX19pbmplY3RlZF9maWxlbmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tY292ZXJhZ2Uvc3JjL2xpYi9ydW5uZXIvaW5kZXgudHNcIjtjb25zdCBfX2luamVjdGVkX2Rpcm5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWNvdmVyYWdlL3NyYy9saWIvcnVubmVyXCI7Y29uc3QgX19pbmplY3RlZF9pbXBvcnRfbWV0YV91cmxfXyA9IFwiZmlsZTovLy9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1jb3ZlcmFnZS9zcmMvbGliL3J1bm5lci9pbmRleC50c1wiO2ltcG9ydCB7IGJvbGQgfSBmcm9tICdhbnNpcyc7XG5pbXBvcnQgeyB3cml0ZUZpbGUgfSBmcm9tICdub2RlOmZzL3Byb21pc2VzJztcbmltcG9ydCBwYXRoIGZyb20gJ25vZGU6cGF0aCc7XG5pbXBvcnQgdHlwZSB7IEF1ZGl0T3V0cHV0cywgUnVubmVyQ29uZmlnIH0gZnJvbSAnQGNvZGUtcHVzaHVwL21vZGVscyc7XG5pbXBvcnQge1xuICBQcm9jZXNzRXJyb3IsXG4gIGVuc3VyZURpcmVjdG9yeUV4aXN0cyxcbiAgZXhlY3V0ZVByb2Nlc3MsXG4gIGZpbGVQYXRoVG9DbGlBcmcsXG4gIHJlYWRKc29uRmlsZSxcbiAgdWksXG59IGZyb20gJ0Bjb2RlLXB1c2h1cC91dGlscyc7XG5pbXBvcnQgdHlwZSB7IEZpbmFsQ292ZXJhZ2VQbHVnaW5Db25maWcgfSBmcm9tICcuLi9jb25maWcuanMnO1xuaW1wb3J0IHsgYXBwbHlNYXhTY29yZUFib3ZlVGhyZXNob2xkIH0gZnJvbSAnLi4vdXRpbHMuanMnO1xuaW1wb3J0IHsgUExVR0lOX0NPTkZJR19QQVRILCBSVU5ORVJfT1VUUFVUX1BBVEggfSBmcm9tICcuL2NvbnN0YW50cy5qcyc7XG5pbXBvcnQgeyBsY292UmVzdWx0c1RvQXVkaXRPdXRwdXRzIH0gZnJvbSAnLi9sY292L2xjb3YtcnVubmVyLmpzJztcblxuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGV4ZWN1dGVSdW5uZXIoKTogUHJvbWlzZTx2b2lkPiB7XG4gIGNvbnN0IHsgcmVwb3J0cywgY292ZXJhZ2VUb29sQ29tbWFuZCwgY292ZXJhZ2VUeXBlcyB9ID1cbiAgICBhd2FpdCByZWFkSnNvbkZpbGU8RmluYWxDb3ZlcmFnZVBsdWdpbkNvbmZpZz4oUExVR0lOX0NPTkZJR19QQVRIKTtcblxuICAvLyBSdW4gY292ZXJhZ2UgdG9vbCBpZiBwcm92aWRlZFxuICBpZiAoY292ZXJhZ2VUb29sQ29tbWFuZCAhPSBudWxsKSB7XG4gICAgY29uc3QgeyBjb21tYW5kLCBhcmdzIH0gPSBjb3ZlcmFnZVRvb2xDb21tYW5kO1xuICAgIHRyeSB7XG4gICAgICBhd2FpdCBleGVjdXRlUHJvY2Vzcyh7IGNvbW1hbmQsIGFyZ3MgfSk7XG4gICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgIGlmIChlcnJvciBpbnN0YW5jZW9mIFByb2Nlc3NFcnJvcikge1xuICAgICAgICB1aSgpLmxvZ2dlci5lcnJvcihib2xkKCdzdGRvdXQgZnJvbSBmYWlsZWQgY292ZXJhZ2UgdG9vbCBwcm9jZXNzOicpKTtcbiAgICAgICAgdWkoKS5sb2dnZXIuZXJyb3IoZXJyb3Iuc3Rkb3V0KTtcbiAgICAgICAgdWkoKS5sb2dnZXIuZXJyb3IoYm9sZCgnc3RkZXJyIGZyb20gZmFpbGVkIGNvdmVyYWdlIHRvb2wgcHJvY2VzczonKSk7XG4gICAgICAgIHVpKCkubG9nZ2VyLmVycm9yKGVycm9yLnN0ZGVycik7XG4gICAgICB9XG5cbiAgICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgICAgJ0NvdmVyYWdlIHBsdWdpbjogUnVubmluZyBjb3ZlcmFnZSB0b29sIGZhaWxlZC4gTWFrZSBzdXJlIGFsbCB5b3VyIHByb3ZpZGVkIHRlc3RzIGFyZSBwYXNzaW5nLicsXG4gICAgICApO1xuICAgIH1cbiAgfVxuXG4gIC8vIENhbGN1bGF0ZSBjb3ZlcmFnZSBmcm9tIExDT1YgcmVzdWx0c1xuICBjb25zdCBhdWRpdE91dHB1dHMgPSBhd2FpdCBsY292UmVzdWx0c1RvQXVkaXRPdXRwdXRzKHJlcG9ydHMsIGNvdmVyYWdlVHlwZXMpO1xuXG4gIGF3YWl0IGVuc3VyZURpcmVjdG9yeUV4aXN0cyhwYXRoLmRpcm5hbWUoUlVOTkVSX09VVFBVVF9QQVRIKSk7XG4gIGF3YWl0IHdyaXRlRmlsZShSVU5ORVJfT1VUUFVUX1BBVEgsIEpTT04uc3RyaW5naWZ5KGF1ZGl0T3V0cHV0cykpO1xufVxuXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gY3JlYXRlUnVubmVyQ29uZmlnKFxuICBzY3JpcHRQYXRoOiBzdHJpbmcsXG4gIGNvbmZpZzogRmluYWxDb3ZlcmFnZVBsdWdpbkNvbmZpZyxcbik6IFByb21pc2U8UnVubmVyQ29uZmlnPiB7XG4gIC8vIENyZWF0ZSBKU09OIGNvbmZpZyBmb3IgZXhlY3V0ZVJ1bm5lclxuICBhd2FpdCBlbnN1cmVEaXJlY3RvcnlFeGlzdHMocGF0aC5kaXJuYW1lKFBMVUdJTl9DT05GSUdfUEFUSCkpO1xuICBhd2FpdCB3cml0ZUZpbGUoUExVR0lOX0NPTkZJR19QQVRILCBKU09OLnN0cmluZ2lmeShjb25maWcpKTtcblxuICBjb25zdCB0aHJlc2hvbGQgPSBjb25maWcucGVyZmVjdFNjb3JlVGhyZXNob2xkO1xuXG4gIHJldHVybiB7XG4gICAgY29tbWFuZDogJ25vZGUnLFxuICAgIGFyZ3M6IFtmaWxlUGF0aFRvQ2xpQXJnKHNjcmlwdFBhdGgpXSxcbiAgICBvdXRwdXRGaWxlOiBSVU5ORVJfT1VUUFVUX1BBVEgsXG4gICAgLi4uKHRocmVzaG9sZCAhPSBudWxsICYmIHtcbiAgICAgIG91dHB1dFRyYW5zZm9ybTogb3V0cHV0cyA9PlxuICAgICAgICBhcHBseU1heFNjb3JlQWJvdmVUaHJlc2hvbGQob3V0cHV0cyBhcyBBdWRpdE91dHB1dHMsIHRocmVzaG9sZCksXG4gICAgfSksXG4gIH07XG59XG4iLCAiY29uc3QgX19pbmplY3RlZF9maWxlbmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tY292ZXJhZ2Uvc3JjL2xpYi9ydW5uZXIvY29uc3RhbnRzLnRzXCI7Y29uc3QgX19pbmplY3RlZF9kaXJuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1jb3ZlcmFnZS9zcmMvbGliL3J1bm5lclwiO2NvbnN0IF9faW5qZWN0ZWRfaW1wb3J0X21ldGFfdXJsX18gPSBcImZpbGU6Ly8vaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tY292ZXJhZ2Uvc3JjL2xpYi9ydW5uZXIvY29uc3RhbnRzLnRzXCI7aW1wb3J0IHBhdGggZnJvbSAnbm9kZTpwYXRoJztcbmltcG9ydCB7IHBsdWdpbldvcmtEaXIgfSBmcm9tICdAY29kZS1wdXNodXAvdXRpbHMnO1xuXG5leHBvcnQgY29uc3QgV09SS0RJUiA9IHBsdWdpbldvcmtEaXIoJ2NvdmVyYWdlJyk7XG5leHBvcnQgY29uc3QgUlVOTkVSX09VVFBVVF9QQVRIID0gcGF0aC5qb2luKFdPUktESVIsICdydW5uZXItb3V0cHV0Lmpzb24nKTtcbmV4cG9ydCBjb25zdCBQTFVHSU5fQ09ORklHX1BBVEggPSBwYXRoLmpvaW4oXG4gIHByb2Nlc3MuY3dkKCksXG4gIFdPUktESVIsXG4gICdwbHVnaW4tY29uZmlnLmpzb24nLFxuKTtcblxuZXhwb3J0IGNvbnN0IElOVkFMSURfRlVOQ1RJT05fTkFNRSA9ICcoZW1wdHktcmVwb3J0KSc7XG4iLCAiY29uc3QgX19pbmplY3RlZF9maWxlbmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tY292ZXJhZ2Uvc3JjL2xpYi9ydW5uZXIvbGNvdi9sY292LXJ1bm5lci50c1wiO2NvbnN0IF9faW5qZWN0ZWRfZGlybmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tY292ZXJhZ2Uvc3JjL2xpYi9ydW5uZXIvbGNvdlwiO2NvbnN0IF9faW5qZWN0ZWRfaW1wb3J0X21ldGFfdXJsX18gPSBcImZpbGU6Ly8vaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tY292ZXJhZ2Uvc3JjL2xpYi9ydW5uZXIvbGNvdi9sY292LXJ1bm5lci50c1wiO2ltcG9ydCBwYXRoIGZyb20gJ25vZGU6cGF0aCc7XG5pbXBvcnQgdHlwZSB7IExDT1ZSZWNvcmQgfSBmcm9tICdwYXJzZS1sY292JztcbmltcG9ydCB0eXBlIHsgQXVkaXRPdXRwdXRzIH0gZnJvbSAnQGNvZGUtcHVzaHVwL21vZGVscyc7XG5pbXBvcnQgeyBleGlzdHMsIHJlYWRUZXh0RmlsZSwgdG9Vbml4TmV3bGluZXMsIHVpIH0gZnJvbSAnQGNvZGUtcHVzaHVwL3V0aWxzJztcbmltcG9ydCB0eXBlIHsgQ292ZXJhZ2VSZXN1bHQsIENvdmVyYWdlVHlwZSB9IGZyb20gJy4uLy4uL2NvbmZpZy5qcyc7XG5pbXBvcnQgeyBtZXJnZUxjb3ZSZXN1bHRzIH0gZnJvbSAnLi9tZXJnZS1sY292LmpzJztcbmltcG9ydCB7IHBhcnNlTGNvdiB9IGZyb20gJy4vcGFyc2UtbGNvdi5qcyc7XG5pbXBvcnQge1xuICBsY292Q292ZXJhZ2VUb0F1ZGl0T3V0cHV0LFxuICByZWNvcmRUb1N0YXRGdW5jdGlvbk1hcHBlcixcbn0gZnJvbSAnLi90cmFuc2Zvcm0uanMnO1xuaW1wb3J0IHR5cGUgeyBMQ09WU3RhdCwgTENPVlN0YXRzIH0gZnJvbSAnLi90eXBlcy5qcyc7XG5cbi8vIE5vdGU6IGNvbmRpdGlvbiBvciBzdGF0ZW1lbnQgY292ZXJhZ2UgaXMgbm90IHN1cHBvcnRlZCBpbiBMQ09WXG4vLyBodHRwczovL3N0YWNrb3ZlcmZsb3cuY29tL3F1ZXN0aW9ucy80ODI2MDQzNC9pcy1pdC1wb3NzaWJsZS10by1jaGVjay1jb25kaXRpb24tY292ZXJhZ2Utd2l0aC1nY292XG5cbi8qKlxuICpcbiAqIEBwYXJhbSByZXN1bHRzIFBhdGhzIHRvIExDT1YgcmVzdWx0c1xuICogQHBhcmFtIGNvdmVyYWdlVHlwZXMgdHlwZXMgb2YgY292ZXJhZ2UgdG8gYmUgY29uc2lkZXJlZFxuICogQHJldHVybnMgQXVkaXQgb3V0cHV0cyB3aXRoIGNvbXBsZXRlIGNvdmVyYWdlIGRhdGEuXG4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBsY292UmVzdWx0c1RvQXVkaXRPdXRwdXRzKFxuICByZXN1bHRzOiBDb3ZlcmFnZVJlc3VsdFtdLFxuICBjb3ZlcmFnZVR5cGVzOiBDb3ZlcmFnZVR5cGVbXSxcbik6IFByb21pc2U8QXVkaXRPdXRwdXRzPiB7XG4gIC8vIFBhcnNlIGxjb3YgZmlsZXNcbiAgY29uc3QgbGNvdlJlc3VsdHMgPSBhd2FpdCBwYXJzZUxjb3ZGaWxlcyhyZXN1bHRzKTtcblxuICAvLyBNZXJnZSBtdWx0aXBsZSBjb3ZlcmFnZSByZXBvcnRzIGZvciB0aGUgc2FtZSBmaWxlXG4gIGNvbnN0IG1lcmdlZFJlc3VsdHMgPSBtZXJnZUxjb3ZSZXN1bHRzKGxjb3ZSZXN1bHRzKTtcblxuICAvLyBDYWxjdWxhdGUgY29kZSBjb3ZlcmFnZSBmcm9tIGFsbCBjb3ZlcmFnZSByZXN1bHRzXG4gIGNvbnN0IHRvdGFsQ292ZXJhZ2VTdGF0cyA9IGdldFRvdGFsQ292ZXJhZ2VGcm9tTGNvdlJlY29yZHMoXG4gICAgbWVyZ2VkUmVzdWx0cyxcbiAgICBjb3ZlcmFnZVR5cGVzLFxuICApO1xuXG4gIHJldHVybiBjb3ZlcmFnZVR5cGVzXG4gICAgLm1hcChjb3ZlcmFnZVR5cGUgPT4ge1xuICAgICAgY29uc3Qgc3RhdHMgPSB0b3RhbENvdmVyYWdlU3RhdHNbY292ZXJhZ2VUeXBlXTtcbiAgICAgIGlmICghc3RhdHMpIHtcbiAgICAgICAgcmV0dXJuIG51bGw7XG4gICAgICB9XG4gICAgICByZXR1cm4gbGNvdkNvdmVyYWdlVG9BdWRpdE91dHB1dChzdGF0cywgY292ZXJhZ2VUeXBlKTtcbiAgICB9KVxuICAgIC5maWx0ZXIoZXhpc3RzKTtcbn1cblxuLyoqXG4gKlxuICogQHBhcmFtIHJlc3VsdHMgUGF0aHMgdG8gTENPViByZXN1bHRzXG4gKiBAcmV0dXJucyBBcnJheSBvZiBwYXJzZWQgTENPVlJlY29yZHMuXG4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBwYXJzZUxjb3ZGaWxlcyhcbiAgcmVzdWx0czogQ292ZXJhZ2VSZXN1bHRbXSxcbik6IFByb21pc2U8TENPVlJlY29yZFtdPiB7XG4gIGNvbnN0IHBhcnNlZFJlc3VsdHMgPSBhd2FpdCBQcm9taXNlLmFsbChcbiAgICByZXN1bHRzLm1hcChhc3luYyByZXN1bHQgPT4ge1xuICAgICAgY29uc3QgcmVzdWx0c1BhdGggPVxuICAgICAgICB0eXBlb2YgcmVzdWx0ID09PSAnc3RyaW5nJyA/IHJlc3VsdCA6IHJlc3VsdC5yZXN1bHRzUGF0aDtcbiAgICAgIGNvbnN0IGxjb3ZGaWxlQ29udGVudCA9IGF3YWl0IHJlYWRUZXh0RmlsZShyZXN1bHRzUGF0aCk7XG4gICAgICBpZiAobGNvdkZpbGVDb250ZW50LnRyaW0oKSA9PT0gJycpIHtcbiAgICAgICAgdWkoKS5sb2dnZXIud2FybmluZyhcbiAgICAgICAgICBgQ292ZXJhZ2UgcGx1Z2luOiBFbXB0eSBsY292IHJlcG9ydCBmaWxlIGRldGVjdGVkIGF0ICR7cmVzdWx0c1BhdGh9LmAsXG4gICAgICAgICk7XG4gICAgICB9XG4gICAgICBjb25zdCBwYXJzZWRSZWNvcmRzID0gcGFyc2VMY292KHRvVW5peE5ld2xpbmVzKGxjb3ZGaWxlQ29udGVudCkpO1xuICAgICAgcmV0dXJuIHBhcnNlZFJlY29yZHMubWFwPExDT1ZSZWNvcmQ+KHJlY29yZCA9PiAoe1xuICAgICAgICAuLi5yZWNvcmQsXG4gICAgICAgIGZpbGU6XG4gICAgICAgICAgdHlwZW9mIHJlc3VsdCA9PT0gJ3N0cmluZycgfHwgcmVzdWx0LnBhdGhUb1Byb2plY3QgPT0gbnVsbFxuICAgICAgICAgICAgPyByZWNvcmQuZmlsZVxuICAgICAgICAgICAgOiBwYXRoLmpvaW4ocmVzdWx0LnBhdGhUb1Byb2plY3QsIHJlY29yZC5maWxlKSxcbiAgICAgIH0pKTtcbiAgICB9KSxcbiAgKTtcbiAgaWYgKHBhcnNlZFJlc3VsdHMubGVuZ3RoICE9PSByZXN1bHRzLmxlbmd0aCkge1xuICAgIHRocm93IG5ldyBFcnJvcignU29tZSBwcm92aWRlZCBMQ09WIHJlc3VsdHMgd2VyZSBub3QgdmFsaWQuJyk7XG4gIH1cblxuICBjb25zdCBmbGF0UmVzdWx0cyA9IHBhcnNlZFJlc3VsdHMuZmxhdCgpO1xuXG4gIGlmIChmbGF0UmVzdWx0cy5sZW5ndGggPT09IDApIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoJ0FsbCBwcm92aWRlZCByZXN1bHRzIGFyZSBlbXB0eS4nKTtcbiAgfVxuXG4gIHJldHVybiBmbGF0UmVzdWx0cztcbn1cblxuLyoqXG4gKlxuICogQHBhcmFtIHJlY29yZHMgVGhpcyBmdW5jdGlvbiBhZ2dyZWdhdGVzIGNvdmVyYWdlIHN0YXRzIGZyb20gYWxsIGNvdmVyYWdlIGZpbGVzXG4gKiBAcGFyYW0gY292ZXJhZ2VUeXBlcyBUeXBlcyBvZiBjb3ZlcmFnZSB0byBiZSBnYXRoZXJlZFxuICogQHJldHVybnMgQ29tcGxldGUgY292ZXJhZ2Ugc3RhdHMgZm9yIGFsbCBkZWZpbmVkIHR5cGVzIG9mIGNvdmVyYWdlLlxuICovXG5mdW5jdGlvbiBnZXRUb3RhbENvdmVyYWdlRnJvbUxjb3ZSZWNvcmRzKFxuICByZWNvcmRzOiBMQ09WUmVjb3JkW10sXG4gIGNvdmVyYWdlVHlwZXM6IENvdmVyYWdlVHlwZVtdLFxuKTogTENPVlN0YXRzIHtcbiAgcmV0dXJuIHJlY29yZHMucmVkdWNlPExDT1ZTdGF0cz4oXG4gICAgKGFjYywgcmVwb3J0KSA9PlxuICAgICAgT2JqZWN0LmZyb21FbnRyaWVzKFtcbiAgICAgICAgLi4uT2JqZWN0LmVudHJpZXMoYWNjKSxcbiAgICAgICAgLi4uKFxuICAgICAgICAgIE9iamVjdC5lbnRyaWVzKFxuICAgICAgICAgICAgZ2V0Q292ZXJhZ2VTdGF0c0Zyb21MY292UmVjb3JkKHJlcG9ydCwgY292ZXJhZ2VUeXBlcyksXG4gICAgICAgICAgKSBhcyBbQ292ZXJhZ2VUeXBlLCBMQ09WU3RhdF1bXVxuICAgICAgICApLm1hcCgoW3R5cGUsIHN0YXRzXSk6IFtDb3ZlcmFnZVR5cGUsIExDT1ZTdGF0XSA9PiBbXG4gICAgICAgICAgdHlwZSxcbiAgICAgICAgICB7XG4gICAgICAgICAgICB0b3RhbEZvdW5kOiAoYWNjW3R5cGVdPy50b3RhbEZvdW5kID8/IDApICsgc3RhdHMudG90YWxGb3VuZCxcbiAgICAgICAgICAgIHRvdGFsSGl0OiAoYWNjW3R5cGVdPy50b3RhbEhpdCA/PyAwKSArIHN0YXRzLnRvdGFsSGl0LFxuICAgICAgICAgICAgaXNzdWVzOiBbLi4uKGFjY1t0eXBlXT8uaXNzdWVzID8/IFtdKSwgLi4uc3RhdHMuaXNzdWVzXSxcbiAgICAgICAgICB9LFxuICAgICAgICBdKSxcbiAgICAgIF0pLFxuICAgIHt9LFxuICApO1xufVxuXG4vKipcbiAqIEBwYXJhbSByZWNvcmQgcmVjb3JkIGZpbGUgZGF0YVxuICogQHBhcmFtIGNvdmVyYWdlVHlwZXMgdHlwZXMgb2YgY292ZXJhZ2UgdG8gYmUgZ2F0aGVyZWRcbiAqIEByZXR1cm5zIFJlbGV2YW50IGNvdmVyYWdlIGRhdGEgZnJvbSBvbmUgbGNvdiByZWNvcmQgZmlsZS5cbiAqL1xuZnVuY3Rpb24gZ2V0Q292ZXJhZ2VTdGF0c0Zyb21MY292UmVjb3JkKFxuICByZWNvcmQ6IExDT1ZSZWNvcmQsXG4gIGNvdmVyYWdlVHlwZXM6IENvdmVyYWdlVHlwZVtdLFxuKTogTENPVlN0YXRzIHtcbiAgcmV0dXJuIE9iamVjdC5mcm9tRW50cmllcyhcbiAgICBjb3ZlcmFnZVR5cGVzLm1hcCgoY292ZXJhZ2VUeXBlKTogW0NvdmVyYWdlVHlwZSwgTENPVlN0YXRdID0+IFtcbiAgICAgIGNvdmVyYWdlVHlwZSxcbiAgICAgIHJlY29yZFRvU3RhdEZ1bmN0aW9uTWFwcGVyW2NvdmVyYWdlVHlwZV0ocmVjb3JkKSxcbiAgICBdKSxcbiAgKTtcbn1cbiIsICJjb25zdCBfX2luamVjdGVkX2ZpbGVuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1jb3ZlcmFnZS9zcmMvbGliL3J1bm5lci9sY292L3BhcnNlLWxjb3YudHNcIjtjb25zdCBfX2luamVjdGVkX2Rpcm5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWNvdmVyYWdlL3NyYy9saWIvcnVubmVyL2xjb3ZcIjtjb25zdCBfX2luamVjdGVkX2ltcG9ydF9tZXRhX3VybF9fID0gXCJmaWxlOi8vL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWNvdmVyYWdlL3NyYy9saWIvcnVubmVyL2xjb3YvcGFyc2UtbGNvdi50c1wiO2ltcG9ydCBwYXJzZUxjb3ZFeHBvcnQgZnJvbSAncGFyc2UtbGNvdic7XG5cbnR5cGUgUGFyc2VMY292Rm4gPSB0eXBlb2YgcGFyc2VMY292RXhwb3J0O1xuXG4vLyB0aGUgcGFyc2UtbGNvdiBleHBvcnQgaXMgaW5jb25zaXN0ZW50IChzb21ldGltZXMgaXQncyAuZGVmYXVsdCwgc29tZXRpbWVzIGl0J3MgLmRlZmF1bHQuZGVmYXVsdClcbmNvbnN0IGdvZEtub3dzID0gcGFyc2VMY292RXhwb3J0IGFzIHVua25vd24gYXNcbiAgfCBQYXJzZUxjb3ZGblxuICB8IHsgZGVmYXVsdDogUGFyc2VMY292Rm4gfTtcblxuZXhwb3J0IGNvbnN0IHBhcnNlTGNvdjogUGFyc2VMY292Rm4gPVxuICAnZGVmYXVsdCcgaW4gZ29kS25vd3MgPyBnb2RLbm93cy5kZWZhdWx0IDogZ29kS25vd3M7XG4iLCAiY29uc3QgX19pbmplY3RlZF9maWxlbmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tY292ZXJhZ2Uvc3JjL2xpYi9ydW5uZXIvbGNvdi90cmFuc2Zvcm0udHNcIjtjb25zdCBfX2luamVjdGVkX2Rpcm5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWNvdmVyYWdlL3NyYy9saWIvcnVubmVyL2xjb3ZcIjtjb25zdCBfX2luamVjdGVkX2ltcG9ydF9tZXRhX3VybF9fID0gXCJmaWxlOi8vL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWNvdmVyYWdlL3NyYy9saWIvcnVubmVyL2xjb3YvdHJhbnNmb3JtLnRzXCI7aW1wb3J0IHR5cGUgeyBMQ09WUmVjb3JkIH0gZnJvbSAncGFyc2UtbGNvdic7XG5pbXBvcnQgdHlwZSB7IEF1ZGl0T3V0cHV0LCBJc3N1ZSB9IGZyb20gJ0Bjb2RlLXB1c2h1cC9tb2RlbHMnO1xuaW1wb3J0IHsgdG9OdW1iZXJQcmVjaXNpb24sIHRvT3JkaW5hbCB9IGZyb20gJ0Bjb2RlLXB1c2h1cC91dGlscyc7XG5pbXBvcnQgdHlwZSB7IENvdmVyYWdlVHlwZSB9IGZyb20gJy4uLy4uL2NvbmZpZy5qcyc7XG5pbXBvcnQgeyBJTlZBTElEX0ZVTkNUSU9OX05BTUUgfSBmcm9tICcuLi9jb25zdGFudHMuanMnO1xuaW1wb3J0IHR5cGUgeyBMQ09WU3RhdCB9IGZyb20gJy4vdHlwZXMuanMnO1xuaW1wb3J0IHsgY2FsY3VsYXRlQ292ZXJhZ2UsIG1lcmdlQ29uc2VjdXRpdmVOdW1iZXJzIH0gZnJvbSAnLi91dGlscy5qcyc7XG5cbmV4cG9ydCBmdW5jdGlvbiBsY292UmVwb3J0VG9GdW5jdGlvblN0YXQocmVjb3JkOiBMQ09WUmVjb3JkKTogTENPVlN0YXQge1xuICBjb25zdCB2YWxpZFJlY29yZCA9IHJlbW92ZUVtcHR5UmVwb3J0KHJlY29yZCk7XG5cbiAgcmV0dXJuIHtcbiAgICB0b3RhbEZvdW5kOiB2YWxpZFJlY29yZC5mdW5jdGlvbnMuZm91bmQsXG4gICAgdG90YWxIaXQ6IHZhbGlkUmVjb3JkLmZ1bmN0aW9ucy5oaXQsXG4gICAgaXNzdWVzOlxuICAgICAgdmFsaWRSZWNvcmQuZnVuY3Rpb25zLmhpdCA8IHZhbGlkUmVjb3JkLmZ1bmN0aW9ucy5mb3VuZFxuICAgICAgICA/IHZhbGlkUmVjb3JkLmZ1bmN0aW9ucy5kZXRhaWxzXG4gICAgICAgICAgICAuZmlsdGVyKGRldGFpbCA9PiAhZGV0YWlsLmhpdClcbiAgICAgICAgICAgIC5tYXAoXG4gICAgICAgICAgICAgIChkZXRhaWwpOiBJc3N1ZSA9PiAoe1xuICAgICAgICAgICAgICAgIG1lc3NhZ2U6IGBGdW5jdGlvbiAke2RldGFpbC5uYW1lfSBpcyBub3QgY2FsbGVkIGluIGFueSB0ZXN0IGNhc2UuYCxcbiAgICAgICAgICAgICAgICBzZXZlcml0eTogJ2Vycm9yJyxcbiAgICAgICAgICAgICAgICBzb3VyY2U6IHtcbiAgICAgICAgICAgICAgICAgIGZpbGU6IHZhbGlkUmVjb3JkLmZpbGUsXG4gICAgICAgICAgICAgICAgICBwb3NpdGlvbjogeyBzdGFydExpbmU6IGRldGFpbC5saW5lIH0sXG4gICAgICAgICAgICAgICAgfSxcbiAgICAgICAgICAgICAgfSksXG4gICAgICAgICAgICApXG4gICAgICAgIDogW10sXG4gIH07XG59XG5cbmZ1bmN0aW9uIHJlbW92ZUVtcHR5UmVwb3J0KHJlY29yZDogTENPVlJlY29yZCk6IExDT1ZSZWNvcmQge1xuICBjb25zdCB2YWxpZEZ1bmN0aW9ucyA9IHJlY29yZC5mdW5jdGlvbnMuZGV0YWlscy5maWx0ZXIoXG4gICAgZGV0YWlsID0+IGRldGFpbC5uYW1lICE9PSBJTlZBTElEX0ZVTkNUSU9OX05BTUUsXG4gICk7XG5cbiAgaWYgKHZhbGlkRnVuY3Rpb25zLmxlbmd0aCA9PT0gcmVjb3JkLmZ1bmN0aW9ucy5mb3VuZCkge1xuICAgIHJldHVybiByZWNvcmQ7XG4gIH1cblxuICByZXR1cm4ge1xuICAgIC4uLnJlY29yZCxcbiAgICBmdW5jdGlvbnM6IHtcbiAgICAgIGRldGFpbHM6IHZhbGlkRnVuY3Rpb25zLFxuICAgICAgZm91bmQ6IHZhbGlkRnVuY3Rpb25zLmxlbmd0aCxcbiAgICAgIGhpdDogdmFsaWRGdW5jdGlvbnMucmVkdWNlKFxuICAgICAgICAoYWNjLCBmbikgPT4gYWNjICsgKGZuLmhpdCAhPSBudWxsICYmIGZuLmhpdCA+IDAgPyAxIDogMCksXG4gICAgICAgIDAsXG4gICAgICApLFxuICAgIH0sXG4gIH07XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBsY292UmVwb3J0VG9MaW5lU3RhdChyZWNvcmQ6IExDT1ZSZWNvcmQpOiBMQ09WU3RhdCB7XG4gIGNvbnN0IG1pc3NpbmdDb3ZlcmFnZSA9IHJlY29yZC5saW5lcy5oaXQgPCByZWNvcmQubGluZXMuZm91bmQ7XG4gIGNvbnN0IGxpbmVzID0gbWlzc2luZ0NvdmVyYWdlXG4gICAgPyByZWNvcmQubGluZXMuZGV0YWlsc1xuICAgICAgICAuZmlsdGVyKGRldGFpbCA9PiAhZGV0YWlsLmhpdClcbiAgICAgICAgLm1hcChkZXRhaWwgPT4gZGV0YWlsLmxpbmUpXG4gICAgOiBbXTtcblxuICBjb25zdCBsaW5lUG9zaXRpb25zID0gbWVyZ2VDb25zZWN1dGl2ZU51bWJlcnMobGluZXMpO1xuXG4gIHJldHVybiB7XG4gICAgdG90YWxGb3VuZDogcmVjb3JkLmxpbmVzLmZvdW5kLFxuICAgIHRvdGFsSGl0OiByZWNvcmQubGluZXMuaGl0LFxuICAgIGlzc3VlczogbWlzc2luZ0NvdmVyYWdlXG4gICAgICA/IGxpbmVQb3NpdGlvbnMubWFwKChsaW5lUG9zaXRpb24pOiBJc3N1ZSA9PiB7XG4gICAgICAgICAgY29uc3QgbGluZVJlZmVyZW5jZSA9XG4gICAgICAgICAgICBsaW5lUG9zaXRpb24uZW5kID09IG51bGxcbiAgICAgICAgICAgICAgPyBgTGluZSAke2xpbmVQb3NpdGlvbi5zdGFydH0gaXNgXG4gICAgICAgICAgICAgIDogYExpbmVzICR7bGluZVBvc2l0aW9uLnN0YXJ0fS0ke2xpbmVQb3NpdGlvbi5lbmR9IGFyZWA7XG5cbiAgICAgICAgICByZXR1cm4ge1xuICAgICAgICAgICAgbWVzc2FnZTogYCR7bGluZVJlZmVyZW5jZX0gbm90IGNvdmVyZWQgaW4gYW55IHRlc3QgY2FzZS5gLFxuICAgICAgICAgICAgc2V2ZXJpdHk6ICd3YXJuaW5nJyxcbiAgICAgICAgICAgIHNvdXJjZToge1xuICAgICAgICAgICAgICBmaWxlOiByZWNvcmQuZmlsZSxcbiAgICAgICAgICAgICAgcG9zaXRpb246IHtcbiAgICAgICAgICAgICAgICBzdGFydExpbmU6IGxpbmVQb3NpdGlvbi5zdGFydCxcbiAgICAgICAgICAgICAgICBlbmRMaW5lOiBsaW5lUG9zaXRpb24uZW5kLFxuICAgICAgICAgICAgICB9LFxuICAgICAgICAgICAgfSxcbiAgICAgICAgICB9O1xuICAgICAgICB9KVxuICAgICAgOiBbXSxcbiAgfTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGxjb3ZSZXBvcnRUb0JyYW5jaFN0YXQocmVjb3JkOiBMQ09WUmVjb3JkKTogTENPVlN0YXQge1xuICByZXR1cm4ge1xuICAgIHRvdGFsRm91bmQ6IHJlY29yZC5icmFuY2hlcy5mb3VuZCxcbiAgICB0b3RhbEhpdDogcmVjb3JkLmJyYW5jaGVzLmhpdCxcbiAgICBpc3N1ZXM6XG4gICAgICByZWNvcmQuYnJhbmNoZXMuaGl0IDwgcmVjb3JkLmJyYW5jaGVzLmZvdW5kXG4gICAgICAgID8gcmVjb3JkLmJyYW5jaGVzLmRldGFpbHNcbiAgICAgICAgICAgIC5maWx0ZXIoZGV0YWlsID0+ICFkZXRhaWwudGFrZW4pXG4gICAgICAgICAgICAubWFwKFxuICAgICAgICAgICAgICAoZGV0YWlsKTogSXNzdWUgPT4gKHtcbiAgICAgICAgICAgICAgICBtZXNzYWdlOiBgJHt0b09yZGluYWwoXG4gICAgICAgICAgICAgICAgICBkZXRhaWwuYnJhbmNoICsgMSxcbiAgICAgICAgICAgICAgICApfSBicmFuY2ggaXMgbm90IHRha2VuIGluIGFueSB0ZXN0IGNhc2UuYCxcbiAgICAgICAgICAgICAgICBzZXZlcml0eTogJ2Vycm9yJyxcbiAgICAgICAgICAgICAgICBzb3VyY2U6IHtcbiAgICAgICAgICAgICAgICAgIGZpbGU6IHJlY29yZC5maWxlLFxuICAgICAgICAgICAgICAgICAgcG9zaXRpb246IHsgc3RhcnRMaW5lOiBkZXRhaWwubGluZSB9LFxuICAgICAgICAgICAgICAgIH0sXG4gICAgICAgICAgICAgIH0pLFxuICAgICAgICAgICAgKVxuICAgICAgICA6IFtdLFxuICB9O1xufVxuXG5leHBvcnQgY29uc3QgcmVjb3JkVG9TdGF0RnVuY3Rpb25NYXBwZXIgPSB7XG4gIGJyYW5jaDogbGNvdlJlcG9ydFRvQnJhbmNoU3RhdCxcbiAgbGluZTogbGNvdlJlcG9ydFRvTGluZVN0YXQsXG4gIGZ1bmN0aW9uOiBsY292UmVwb3J0VG9GdW5jdGlvblN0YXQsXG59O1xuXG4vKipcbiAqXG4gKiBAcGFyYW0gc3RhdCBjb2RlIGNvdmVyYWdlIHJlc3VsdCBmb3IgYSBnaXZlbiB0eXBlXG4gKiBAcGFyYW0gY292ZXJhZ2VUeXBlIGNvZGUgY292ZXJhZ2UgdHlwZVxuICogQHJldHVybnMgUmVzdWx0IG9mIGNvbXBsZXRlIGNvZGUgY2NvdmVyYWdlIGRhdGEgY292ZXJ0ZWQgdG8gQXVkaXRPdXRwdXRcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGxjb3ZDb3ZlcmFnZVRvQXVkaXRPdXRwdXQoXG4gIHN0YXQ6IExDT1ZTdGF0LFxuICBjb3ZlcmFnZVR5cGU6IENvdmVyYWdlVHlwZSxcbik6IEF1ZGl0T3V0cHV0IHtcbiAgY29uc3QgY292ZXJhZ2UgPSBjYWxjdWxhdGVDb3ZlcmFnZShzdGF0LnRvdGFsSGl0LCBzdGF0LnRvdGFsRm91bmQpO1xuICBjb25zdCBNQVhfREVDSU1BTF9QTEFDRVMgPSA0O1xuICBjb25zdCBjb3ZlcmFnZVBlcmNlbnRhZ2UgPSBjb3ZlcmFnZSAqIDEwMDtcblxuICByZXR1cm4ge1xuICAgIHNsdWc6IGAke2NvdmVyYWdlVHlwZX0tY292ZXJhZ2VgLFxuICAgIHNjb3JlOiB0b051bWJlclByZWNpc2lvbihjb3ZlcmFnZSwgTUFYX0RFQ0lNQUxfUExBQ0VTKSxcbiAgICB2YWx1ZTogY292ZXJhZ2VQZXJjZW50YWdlLFxuICAgIGRpc3BsYXlWYWx1ZTogYCR7dG9OdW1iZXJQcmVjaXNpb24oY292ZXJhZ2VQZXJjZW50YWdlLCAxKX0gJWAsXG4gICAgZGV0YWlsczoge1xuICAgICAgaXNzdWVzOiBzdGF0Lmlzc3VlcyxcbiAgICB9LFxuICB9O1xufVxuIiwgImNvbnN0IF9faW5qZWN0ZWRfZmlsZW5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWNvdmVyYWdlL3NyYy9saWIvbngvY292ZXJhZ2UtcGF0aHMudHNcIjtjb25zdCBfX2luamVjdGVkX2Rpcm5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWNvdmVyYWdlL3NyYy9saWIvbnhcIjtjb25zdCBfX2luamVjdGVkX2ltcG9ydF9tZXRhX3VybF9fID0gXCJmaWxlOi8vL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWNvdmVyYWdlL3NyYy9saWIvbngvY292ZXJhZ2UtcGF0aHMudHNcIjsvLy8gPHJlZmVyZW5jZSB0eXBlcz1cInZpdGVzdFwiIC8+XG5pbXBvcnQgdHlwZSB7XG4gIFByb2plY3RDb25maWd1cmF0aW9uLFxuICBQcm9qZWN0R3JhcGhQcm9qZWN0Tm9kZSxcbiAgVHJlZSxcbn0gZnJvbSAnQG54L2RldmtpdCc7XG5pbXBvcnQgdHlwZSB7IEplc3RFeGVjdXRvck9wdGlvbnMgfSBmcm9tICdAbngvamVzdC9zcmMvZXhlY3V0b3JzL2plc3Qvc2NoZW1hJztcbmltcG9ydCB0eXBlIHsgVml0ZXN0RXhlY3V0b3JPcHRpb25zIH0gZnJvbSAnQG54L3ZpdGUvZXhlY3V0b3JzJztcbmltcG9ydCB7IGJvbGQgfSBmcm9tICdhbnNpcyc7XG5pbXBvcnQgcGF0aCBmcm9tICdub2RlOnBhdGgnO1xuaW1wb3J0IHsgaW1wb3J0TW9kdWxlLCB1aSB9IGZyb20gJ0Bjb2RlLXB1c2h1cC91dGlscyc7XG5pbXBvcnQgdHlwZSB7IENvdmVyYWdlUmVzdWx0IH0gZnJvbSAnLi4vY29uZmlnLmpzJztcblxuLyoqXG4gKiBAcGFyYW0gdGFyZ2V0cyBueCB0YXJnZXRzIHRvIGJlIHVzZWQgZm9yIG1lYXN1cmluZyBjb3ZlcmFnZSwgdGVzdCBieSBkZWZhdWx0XG4gKiBAcmV0dXJucyBBbiBhcnJheSBvZiBjb3ZlcmFnZSByZXN1bHQgaW5mb3JtYXRpb24gZm9yIHRoZSBjb3ZlcmFnZSBwbHVnaW4uXG4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBnZXROeENvdmVyYWdlUGF0aHMoXG4gIHRhcmdldHM6IHN0cmluZ1tdID0gWyd0ZXN0J10sXG4gIHZlcmJvc2U/OiBib29sZWFuLFxuKTogUHJvbWlzZTxDb3ZlcmFnZVJlc3VsdFtdPiB7XG4gIGlmICh2ZXJib3NlKSB7XG4gICAgdWkoKS5sb2dnZXIuaW5mbyhcbiAgICAgIGJvbGQoJ1x1RDgzRFx1RENBMSBHYXRoZXJpbmcgY292ZXJhZ2UgZnJvbSB0aGUgZm9sbG93aW5nIG54IHByb2plY3RzOicpLFxuICAgICk7XG4gIH1cblxuICBjb25zdCB7IGNyZWF0ZVByb2plY3RHcmFwaEFzeW5jIH0gPSBhd2FpdCBpbXBvcnQoJ0BueC9kZXZraXQnKTtcbiAgY29uc3QgeyBub2RlcyB9ID0gYXdhaXQgY3JlYXRlUHJvamVjdEdyYXBoQXN5bmMoeyBleGl0T25FcnJvcjogZmFsc2UgfSk7XG5cbiAgY29uc3QgY292ZXJhZ2VSZXN1bHRzID0gYXdhaXQgUHJvbWlzZS5hbGwoXG4gICAgdGFyZ2V0cy5tYXAoYXN5bmMgdGFyZ2V0ID0+IHtcbiAgICAgIGNvbnN0IHJlbGV2YW50Tm9kZXMgPSBPYmplY3QudmFsdWVzKG5vZGVzKS5maWx0ZXIoZ3JhcGggPT5cbiAgICAgICAgaGFzTnhUYXJnZXQoZ3JhcGgsIHRhcmdldCksXG4gICAgICApO1xuXG4gICAgICByZXR1cm4gYXdhaXQgUHJvbWlzZS5hbGwoXG4gICAgICAgIHJlbGV2YW50Tm9kZXMubWFwPFByb21pc2U8Q292ZXJhZ2VSZXN1bHQ+Pihhc3luYyAoeyBuYW1lLCBkYXRhIH0pID0+IHtcbiAgICAgICAgICBjb25zdCBjb3ZlcmFnZVBhdGhzID0gYXdhaXQgZ2V0Q292ZXJhZ2VQYXRoc0ZvclRhcmdldChkYXRhLCB0YXJnZXQpO1xuICAgICAgICAgIGlmICh2ZXJib3NlKSB7XG4gICAgICAgICAgICB1aSgpLmxvZ2dlci5pbmZvKGAtICR7bmFtZX06ICR7dGFyZ2V0fWApO1xuICAgICAgICAgIH1cbiAgICAgICAgICByZXR1cm4gY292ZXJhZ2VQYXRocztcbiAgICAgICAgfSksXG4gICAgICApO1xuICAgIH0pLFxuICApO1xuXG4gIGlmICh2ZXJib3NlKSB7XG4gICAgdWkoKS5sb2dnZXIuaW5mbygnXFxuJyk7XG4gIH1cblxuICByZXR1cm4gY292ZXJhZ2VSZXN1bHRzLmZsYXQoKTtcbn1cblxuZnVuY3Rpb24gaGFzTnhUYXJnZXQoXG4gIHByb2plY3Q6IFByb2plY3RHcmFwaFByb2plY3ROb2RlLFxuICB0YXJnZXQ6IHN0cmluZyxcbik6IGJvb2xlYW4ge1xuICByZXR1cm4gcHJvamVjdC5kYXRhLnRhcmdldHMgIT0gbnVsbCAmJiB0YXJnZXQgaW4gcHJvamVjdC5kYXRhLnRhcmdldHM7XG59XG5cbmV4cG9ydCB0eXBlIFZpdGVzdENvdmVyYWdlQ29uZmlnID0ge1xuICB0ZXN0OiB7XG4gICAgY292ZXJhZ2U/OiB7XG4gICAgICByZXBvcnRlcj86IHN0cmluZ1tdO1xuICAgICAgcmVwb3J0c0RpcmVjdG9yeT86IHN0cmluZztcbiAgICB9O1xuICB9O1xufTtcblxuZXhwb3J0IHR5cGUgSmVzdENvdmVyYWdlQ29uZmlnID0ge1xuICBjb3ZlcmFnZURpcmVjdG9yeT86IHN0cmluZztcbiAgY292ZXJhZ2VSZXBvcnRlcnM/OiBzdHJpbmdbXTtcbn07XG5cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBnZXRDb3ZlcmFnZVBhdGhzRm9yVGFyZ2V0KFxuICBwcm9qZWN0OiBQcm9qZWN0Q29uZmlndXJhdGlvbixcbiAgdGFyZ2V0OiBzdHJpbmcsXG4pOiBQcm9taXNlPENvdmVyYWdlUmVzdWx0PiB7XG4gIGNvbnN0IHRhcmdldENvbmZpZyA9IHByb2plY3QudGFyZ2V0cz8uW3RhcmdldF07XG5cbiAgaWYgKCF0YXJnZXRDb25maWcpIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICBgTm8gY29uZmlndXJhdGlvbiBmb3VuZCBmb3IgdGFyZ2V0ICR7dGFyZ2V0fSBpbiBwcm9qZWN0ICR7cHJvamVjdC5uYW1lfWAsXG4gICAgKTtcbiAgfVxuXG4gIGlmICh0YXJnZXRDb25maWcuZXhlY3V0b3I/LmluY2x1ZGVzKCdAbngvdml0ZScpKSB7XG4gICAgcmV0dXJuIGdldENvdmVyYWdlUGF0aEZvclZpdGVzdChcbiAgICAgIHRhcmdldENvbmZpZy5vcHRpb25zIGFzIFZpdGVzdEV4ZWN1dG9yT3B0aW9ucyxcbiAgICAgIHByb2plY3QsXG4gICAgICB0YXJnZXQsXG4gICAgKTtcbiAgfVxuXG4gIGlmICh0YXJnZXRDb25maWcuZXhlY3V0b3I/LmluY2x1ZGVzKCdAbngvamVzdCcpKSB7XG4gICAgcmV0dXJuIGdldENvdmVyYWdlUGF0aEZvckplc3QoXG4gICAgICB0YXJnZXRDb25maWcub3B0aW9ucyBhcyBKZXN0RXhlY3V0b3JPcHRpb25zLFxuICAgICAgcHJvamVjdCxcbiAgICAgIHRhcmdldCxcbiAgICApO1xuICB9XG5cbiAgdGhyb3cgbmV3IEVycm9yKFxuICAgIGBVbnN1cHBvcnRlZCBleGVjdXRvciAke3RhcmdldENvbmZpZy5leGVjdXRvcn0uIE9ubHkgQG54L3ZpdGUgYW5kIEBueC9qZXN0IGFyZSBjdXJyZW50bHkgc3VwcG9ydGVkLmAsXG4gICk7XG59XG5cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBnZXRDb3ZlcmFnZVBhdGhGb3JWaXRlc3QoXG4gIG9wdGlvbnM6IFZpdGVzdEV4ZWN1dG9yT3B0aW9ucyxcbiAgcHJvamVjdDogUHJvamVjdENvbmZpZ3VyYXRpb24sXG4gIHRhcmdldDogc3RyaW5nLFxuKSB7XG4gIGNvbnN0IHtcbiAgICBkZWZhdWx0OiB7IG5vcm1hbGl6ZVZpdGVDb25maWdGaWxlUGF0aFdpdGhUcmVlIH0sXG4gIH0gPSBhd2FpdCBpbXBvcnQoJ0BueC92aXRlJyk7XG4gIGNvbnN0IGNvbmZpZyA9IG5vcm1hbGl6ZVZpdGVDb25maWdGaWxlUGF0aFdpdGhUcmVlKFxuICAgIC8vIEhBQ0s6IG9ubHkgdHJlZS5leGlzdHMgaXMgY2FsbGVkLCBzbyBpbmplY3RpbmcgZXhpc3RTeW5jIGZyb20gbm9kZTpmcyBpbnN0ZWFkXG4gICAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIEB0eXBlc2NyaXB0LWVzbGludC9jb25zaXN0ZW50LXR5cGUtYXNzZXJ0aW9uc1xuICAgIHsgZXhpc3RzOiAoYXdhaXQgaW1wb3J0KCdub2RlOmZzJykpLmV4aXN0c1N5bmMgfSBhcyBUcmVlLFxuICAgIHByb2plY3Qucm9vdCxcbiAgICBvcHRpb25zLmNvbmZpZ0ZpbGUsXG4gICk7XG4gIGlmICghY29uZmlnKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgYENvdWxkIG5vdCBmaW5kIFZpdGVzdCBjb25maWcgZmlsZSBmb3IgdGFyZ2V0ICR7dGFyZ2V0fSBpbiBwcm9qZWN0ICR7cHJvamVjdC5uYW1lfWAsXG4gICAgKTtcbiAgfVxuXG4gIGNvbnN0IHZpdGVzdENvbmZpZyA9IGF3YWl0IGltcG9ydE1vZHVsZTxWaXRlc3RDb3ZlcmFnZUNvbmZpZz4oe1xuICAgIGZpbGVwYXRoOiBjb25maWcsXG4gICAgZm9ybWF0OiAnZXNtJyxcbiAgfSk7XG5cbiAgY29uc3QgcmVwb3J0c0RpcmVjdG9yeSA9XG4gICAgb3B0aW9ucy5yZXBvcnRzRGlyZWN0b3J5ID8/IHZpdGVzdENvbmZpZy50ZXN0LmNvdmVyYWdlPy5yZXBvcnRzRGlyZWN0b3J5O1xuICBjb25zdCByZXBvcnRlciA9IHZpdGVzdENvbmZpZy50ZXN0LmNvdmVyYWdlPy5yZXBvcnRlcjtcblxuICBpZiAocmVwb3J0c0RpcmVjdG9yeSA9PSBudWxsKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgYFZpdGVzdCBjb3ZlcmFnZSBjb25maWd1cmF0aW9uIGF0ICR7Y29uZmlnfSBkb2VzIG5vdCBpbmNsdWRlIGNvdmVyYWdlIHBhdGggZm9yIHRhcmdldCAke3RhcmdldH0gaW4gcHJvamVjdCAke3Byb2plY3QubmFtZX0uIEFkZCB0aGUgcGF0aCB1bmRlciBjb3ZlcmFnZSA+IHJlcG9ydHNEaXJlY3RvcnkuYCxcbiAgICApO1xuICB9XG5cbiAgaWYgKCFyZXBvcnRlcj8uaW5jbHVkZXMoJ2xjb3YnKSkge1xuICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgIGBWaXRlc3QgY292ZXJhZ2UgY29uZmlndXJhdGlvbiBhdCAke2NvbmZpZ30gZG9lcyBub3QgaW5jbHVkZSBMQ09WIHJlcG9ydCBmb3JtYXQgZm9yIHRhcmdldCAke3RhcmdldH0gaW4gcHJvamVjdCAke3Byb2plY3QubmFtZX0uIEFkZCAnbGNvdicgZm9ybWF0IHVuZGVyIGNvdmVyYWdlID4gcmVwb3J0ZXIuYCxcbiAgICApO1xuICB9XG5cbiAgaWYgKHBhdGguaXNBYnNvbHV0ZShyZXBvcnRzRGlyZWN0b3J5KSkge1xuICAgIHJldHVybiBwYXRoLmpvaW4ocmVwb3J0c0RpcmVjdG9yeSwgJ2xjb3YuaW5mbycpO1xuICB9XG4gIHJldHVybiB7XG4gICAgcGF0aFRvUHJvamVjdDogcHJvamVjdC5yb290LFxuICAgIHJlc3VsdHNQYXRoOiBwYXRoLmpvaW4ocHJvamVjdC5yb290LCByZXBvcnRzRGlyZWN0b3J5LCAnbGNvdi5pbmZvJyksXG4gIH07XG59XG5cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBnZXRDb3ZlcmFnZVBhdGhGb3JKZXN0KFxuICBvcHRpb25zOiBKZXN0RXhlY3V0b3JPcHRpb25zLFxuICBwcm9qZWN0OiBQcm9qZWN0Q29uZmlndXJhdGlvbixcbiAgdGFyZ2V0OiBzdHJpbmcsXG4pIHtcbiAgY29uc3QgeyBqZXN0Q29uZmlnIH0gPSBvcHRpb25zO1xuXG4gIGNvbnN0IHRlc3RDb25maWcgPSBhd2FpdCBpbXBvcnRNb2R1bGU8SmVzdENvdmVyYWdlQ29uZmlnPih7XG4gICAgZmlsZXBhdGg6IGplc3RDb25maWcsXG4gIH0pO1xuICBjb25zdCB7IGNvdmVyYWdlRGlyZWN0b3J5LCBjb3ZlcmFnZVJlcG9ydGVycyB9ID0ge1xuICAgIC4uLnRlc3RDb25maWcsXG4gICAgLi4ub3B0aW9ucyxcbiAgfTtcblxuICBpZiAoY292ZXJhZ2VEaXJlY3RvcnkgPT0gbnVsbCkge1xuICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgIGBKZXN0IGNvdmVyYWdlIGNvbmZpZ3VyYXRpb24gYXQgJHtqZXN0Q29uZmlnfSBkb2VzIG5vdCBpbmNsdWRlIGNvdmVyYWdlIHBhdGggZm9yIHRhcmdldCAke3RhcmdldH0gaW4gJHtwcm9qZWN0Lm5hbWV9LiBBZGQgdGhlIHBhdGggdW5kZXIgY292ZXJhZ2VEaXJlY3RvcnkuYCxcbiAgICApO1xuICB9XG5cbiAgaWYgKCFjb3ZlcmFnZVJlcG9ydGVycz8uaW5jbHVkZXMoJ2xjb3YnKSAmJiAhKCdwcmVzZXQnIGluIHRlc3RDb25maWcpKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgYEplc3QgY292ZXJhZ2UgY29uZmlndXJhdGlvbiBhdCAke2plc3RDb25maWd9IGRvZXMgbm90IGluY2x1ZGUgTENPViByZXBvcnQgZm9ybWF0IGZvciB0YXJnZXQgJHt0YXJnZXR9IGluICR7cHJvamVjdC5uYW1lfS4gQWRkICdsY292JyBmb3JtYXQgdW5kZXIgY292ZXJhZ2VSZXBvcnRlcnMuYCxcbiAgICApO1xuICB9XG5cbiAgaWYgKHBhdGguaXNBYnNvbHV0ZShjb3ZlcmFnZURpcmVjdG9yeSkpIHtcbiAgICByZXR1cm4gcGF0aC5qb2luKGNvdmVyYWdlRGlyZWN0b3J5LCAnbGNvdi5pbmZvJyk7XG4gIH1cbiAgcmV0dXJuIHBhdGguam9pbihwcm9qZWN0LnJvb3QsIGNvdmVyYWdlRGlyZWN0b3J5LCAnbGNvdi5pbmZvJyk7XG59XG4iLCAiY29uc3QgX19pbmplY3RlZF9maWxlbmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tZG9jLWNvdmVyYWdlL3NyYy9saWIvY29uZmlnLnRzXCI7Y29uc3QgX19pbmplY3RlZF9kaXJuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1kb2MtY292ZXJhZ2Uvc3JjL2xpYlwiO2NvbnN0IF9faW5qZWN0ZWRfaW1wb3J0X21ldGFfdXJsX18gPSBcImZpbGU6Ly8vaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tZG9jLWNvdmVyYWdlL3NyYy9saWIvY29uZmlnLnRzXCI7aW1wb3J0IHsgeiB9IGZyb20gJ3pvZCc7XG5cbmV4cG9ydCBjb25zdCBkb2NDb3ZlcmFnZVBsdWdpbkNvbmZpZ1NjaGVtYSA9IHoub2JqZWN0KHtcbiAgb25seUF1ZGl0czogei5hcnJheSh6LnN0cmluZygpKS5vcHRpb25hbCgpLFxuICBzb3VyY2VHbG9iOiB6XG4gICAgLmFycmF5KHouc3RyaW5nKCkpXG4gICAgLmRlZmF1bHQoWydzcmMvKiovKi57dHMsdHN4fScsICchKiovKi5zcGVjLnRzJywgJyEqKi8qLnRlc3QudHMnXSksXG59KTtcblxuZXhwb3J0IHR5cGUgRG9jQ292ZXJhZ2VQbHVnaW5Db25maWcgPSB6LmluZmVyPFxuICB0eXBlb2YgZG9jQ292ZXJhZ2VQbHVnaW5Db25maWdTY2hlbWFcbj47IiwgImNvbnN0IF9faW5qZWN0ZWRfZmlsZW5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWRvYy1jb3ZlcmFnZS9zcmMvbGliL2NvbnN0YW50cy50c1wiO2NvbnN0IF9faW5qZWN0ZWRfZGlybmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tZG9jLWNvdmVyYWdlL3NyYy9saWJcIjtjb25zdCBfX2luamVjdGVkX2ltcG9ydF9tZXRhX3VybF9fID0gXCJmaWxlOi8vL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWRvYy1jb3ZlcmFnZS9zcmMvbGliL2NvbnN0YW50cy50c1wiO2ltcG9ydCB0eXBlIHsgQXVkaXQsIEdyb3VwIH0gZnJvbSBcIkBjb2RlLXB1c2h1cC9tb2RlbHNcIjtcbmltcG9ydCB0eXBlIHsgQXVkaXRTbHVnIH0gZnJvbSBcIi4vbW9kZWxzXCI7XG5cbmV4cG9ydCBjb25zdCBQTFVHSU5fU0xVRyA9ICdkb2MtY292ZXJhZ2UnO1xuXG5leHBvcnQgY29uc3QgQVVESVRTX01BUDogUmVjb3JkPEF1ZGl0U2x1ZywgQXVkaXQ+ID0ge1xuICAgICdjbGFzc2VzLWNvdmVyYWdlJzoge1xuICAgICAgICBzbHVnOiAnY2xhc3Nlcy1jb3ZlcmFnZScsXG4gICAgICAgIHRpdGxlOiAnQ2xhc3NlcyBjb3ZlcmFnZScsXG4gICAgICAgIGRlc2NyaXB0aW9uOiAnQ292ZXJhZ2Ugb2YgY2xhc3NlcycsXG4gICAgfSxcbiAgICAnbWV0aG9kcy1jb3ZlcmFnZSc6IHtcbiAgICAgICAgc2x1ZzogJ21ldGhvZHMtY292ZXJhZ2UnLFxuICAgICAgICB0aXRsZTogJ01ldGhvZHMgY292ZXJhZ2UnLFxuICAgICAgICBkZXNjcmlwdGlvbjogJ0NvdmVyYWdlIG9mIG1ldGhvZHMnLFxuICAgIH0sXG4gICAgJ2Z1bmN0aW9ucy1jb3ZlcmFnZSc6IHtcbiAgICAgICAgc2x1ZzogJ2Z1bmN0aW9ucy1jb3ZlcmFnZScsXG4gICAgICAgIHRpdGxlOiAnRnVuY3Rpb25zIGNvdmVyYWdlJyxcbiAgICAgICAgZGVzY3JpcHRpb246ICdDb3ZlcmFnZSBvZiBmdW5jdGlvbnMnLFxuICAgIH0sXG4gICAgJ2ludGVyZmFjZXMtY292ZXJhZ2UnOiB7XG4gICAgICAgIHNsdWc6ICdpbnRlcmZhY2VzLWNvdmVyYWdlJyxcbiAgICAgICAgdGl0bGU6ICdJbnRlcmZhY2VzIGNvdmVyYWdlJyxcbiAgICAgICAgZGVzY3JpcHRpb246ICdDb3ZlcmFnZSBvZiBpbnRlcmZhY2VzJyxcbiAgICB9LFxuICAgICd2YXJpYWJsZXMtY292ZXJhZ2UnOiB7XG4gICAgICAgIHNsdWc6ICd2YXJpYWJsZXMtY292ZXJhZ2UnLFxuICAgICAgICB0aXRsZTogJ1ZhcmlhYmxlcyBjb3ZlcmFnZScsXG4gICAgICAgIGRlc2NyaXB0aW9uOiAnQ292ZXJhZ2Ugb2YgdmFyaWFibGVzJyxcbiAgICB9LFxuICAgICdwcm9wZXJ0aWVzLWNvdmVyYWdlJzoge1xuICAgICAgICBzbHVnOiAncHJvcGVydGllcy1jb3ZlcmFnZScsXG4gICAgICAgIHRpdGxlOiAnUHJvcGVydGllcyBjb3ZlcmFnZScsXG4gICAgICAgIGRlc2NyaXB0aW9uOiAnQ292ZXJhZ2Ugb2YgcHJvcGVydGllcycsXG4gICAgfSxcbiAgICAndHlwZXMtY292ZXJhZ2UnOiB7XG4gICAgICAgIHNsdWc6ICd0eXBlcy1jb3ZlcmFnZScsXG4gICAgICAgIHRpdGxlOiAnVHlwZXMgY292ZXJhZ2UnLFxuICAgICAgICBkZXNjcmlwdGlvbjogJ0NvdmVyYWdlIG9mIHR5cGVzJyxcbiAgICB9LFxuICAgICdlbnVtcy1jb3ZlcmFnZSc6IHtcbiAgICAgICAgc2x1ZzogJ2VudW1zLWNvdmVyYWdlJyxcbiAgICAgICAgdGl0bGU6ICdFbnVtcyBjb3ZlcmFnZScsXG4gICAgICAgIGRlc2NyaXB0aW9uOiAnQ292ZXJhZ2Ugb2YgZW51bXMnLFxuICAgIH0sXG59IGFzIGNvbnN0O1xuXG5leHBvcnQgY29uc3QgZ3JvdXBzOiBHcm91cFtdID0gW1xuICAgIHtcbiAgICAgICAgc2x1ZzogJ2RvY3VtZW50YXRpb24tY292ZXJhZ2UnLFxuICAgICAgICB0aXRsZTogJ0RvY3VtZW50YXRpb24gY292ZXJhZ2UnLFxuICAgICAgICBkZXNjcmlwdGlvbjogJ0RvY3VtZW50YXRpb24gY292ZXJhZ2UnLFxuICAgICAgICByZWZzOiBPYmplY3Qua2V5cyhBVURJVFNfTUFQKS5tYXAoc2x1ZyA9PiB7XG4gICAgICAgICAgICBzd2l0Y2ggKHNsdWcgYXMgQXVkaXRTbHVnKSB7XG4gICAgICAgICAgICAgICAgY2FzZSAnY2xhc3Nlcy1jb3ZlcmFnZSc6XG4gICAgICAgICAgICAgICAgY2FzZSAnZnVuY3Rpb25zLWNvdmVyYWdlJzpcbiAgICAgICAgICAgICAgICBjYXNlICdtZXRob2RzLWNvdmVyYWdlJzpcbiAgICAgICAgICAgICAgICAgICAgcmV0dXJuIHsgc2x1Zywgd2VpZ2h0OiAyIH1cbiAgICAgICAgICAgICAgICBjYXNlICdpbnRlcmZhY2VzLWNvdmVyYWdlJzpcbiAgICAgICAgICAgICAgICBjYXNlICdwcm9wZXJ0aWVzLWNvdmVyYWdlJzpcbiAgICAgICAgICAgICAgICBjYXNlICd0eXBlcy1jb3ZlcmFnZSc6XG4gICAgICAgICAgICAgICAgZGVmYXVsdDpcbiAgICAgICAgICAgICAgICAgICAgcmV0dXJuIHsgc2x1Zywgd2VpZ2h0OiAxIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgfSksXG4gICAgfV07IiwgImNvbnN0IF9faW5qZWN0ZWRfZmlsZW5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWRvYy1jb3ZlcmFnZS9zcmMvbGliL3J1bm5lci9kb2MtcHJvY2Vzc2VyLnRzXCI7Y29uc3QgX19pbmplY3RlZF9kaXJuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1kb2MtY292ZXJhZ2Uvc3JjL2xpYi9ydW5uZXJcIjtjb25zdCBfX2luamVjdGVkX2ltcG9ydF9tZXRhX3VybF9fID0gXCJmaWxlOi8vL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWRvYy1jb3ZlcmFnZS9zcmMvbGliL3J1bm5lci9kb2MtcHJvY2Vzc2VyLnRzXCI7aW1wb3J0IHsgQ2xhc3NEZWNsYXJhdGlvbiwgUHJvamVjdCwgU291cmNlRmlsZSB9IGZyb20gJ3RzLW1vcnBoJztcbmltcG9ydCB0eXBlIHsgRG9jQ292ZXJhZ2VQbHVnaW5Db25maWcgfSBmcm9tICcuLi9jb25maWcuanMnO1xuaW1wb3J0IHR5cGUge1xuICBDb3ZlcmFnZVJlc3VsdCxcbiAgQ292ZXJhZ2VUeXBlLFxuICBVbmRvY3VtZW50ZWROb2RlLFxuICBVbnByb2Nlc3NlZENvdmVyYWdlUmVzdWx0XG59IGZyb20gJy4uL21vZGVscy5qcyc7XG5pbXBvcnQgeyBnZXRDb3ZlcmFnZVR5cGVGcm9tS2luZCB9IGZyb20gJy4uL3V0aWxzLmpzJztcbmltcG9ydCB7IGNhbGN1bGF0ZUNvdmVyYWdlLCBjcmVhdGVFbXB0eVVucHJvY2Vzc2VkQ292ZXJhZ2VSZXBvcnQgfSBmcm9tICcuL3V0aWxzLmpzJztcblxuXG4vKiBlc2xpbnQtZGlzYWJsZSBAdHlwZXNjcmlwdC1lc2xpbnQvbm8tZXhwbGljaXQtYW55ICovXG4vKiBlc2xpbnQtZGlzYWJsZSBmdW5jdGlvbmFsL2ltbXV0YWJsZS1kYXRhICovXG4vKiBlc2xpbnQtZGlzYWJsZSBAdHlwZXNjcmlwdC1lc2xpbnQvbWF4LXBhcmFtcyAqL1xuLyogZXNsaW50LWRpc2FibGUgZnVuY3Rpb25hbC9uby1sZXQgKi9cblxuXG4vKipcbiAqIFByb2Nlc3NlcyBkb2N1bWVudGF0aW9uIGNvdmVyYWdlIGZvciBUeXBlU2NyaXB0IGZpbGVzIGluIHRoZSBzcGVjaWZpZWQgcGF0aFxuICogQHBhcmFtIHRvSW5jbHVkZSAtIFRoZSBmaWxlIHBhdGggcGF0dGVybiB0byBpbmNsdWRlIGZvciBkb2N1bWVudGF0aW9uIGFuYWx5c2lzXG4gKiBAcmV0dXJucyB7Q292ZXJhZ2VSZXN1bHR9IE9iamVjdCBjb250YWluaW5nIGNvdmVyYWdlIHN0YXRpc3RpY3MgYW5kIHVuZG9jdW1lbnRlZCBpdGVtc1xuICovXG5leHBvcnQgZnVuY3Rpb24gcHJvY2Vzc0RvY0NvdmVyYWdlKGNvbmZpZzogRG9jQ292ZXJhZ2VQbHVnaW5Db25maWcpOiBDb3ZlcmFnZVJlc3VsdCB7XG4gIGNvbnN0IHByb2plY3QgPSBuZXcgUHJvamVjdCgpO1xuICBwcm9qZWN0LmFkZFNvdXJjZUZpbGVzQXRQYXRocyhjb25maWcuc291cmNlR2xvYik7XG5cbiAgcmV0dXJuIGdldFVucHJvY2Vzc2VkQ292ZXJhZ2VSZXBvcnQocHJvamVjdC5nZXRTb3VyY2VGaWxlcygpKTtcblxufVxuXG5leHBvcnQgZnVuY3Rpb24gZ2V0VW5wcm9jZXNzZWRDb3ZlcmFnZVJlcG9ydChzb3VyY2VGaWxlczogU291cmNlRmlsZVtdKSB7XG4gIGNvbnN0IHVucHJvY2Vzc2VkQ292ZXJhZ2VSZXBvcnQgPSBzb3VyY2VGaWxlc1xuICAgIC5yZWR1Y2UoKGNvdmVyYWdlUmVwb3J0T2ZBbGxGaWxlcywgc291cmNlRmlsZSkgPT4ge1xuXG4gICAgICAvLyBJbmZvIG9mIHRoZSBmaWxlXG4gICAgICBjb25zdCBmaWxlUGF0aCA9IHNvdXJjZUZpbGUuZ2V0RmlsZVBhdGgoKTtcbiAgICAgIGNvbnN0IGNsYXNzZXMgPSBzb3VyY2VGaWxlLmdldENsYXNzZXMoKTtcblxuICAgICAgLy8gQWxsIG5vZGVzIG9mIHRoZSBmaWxlXG4gICAgICBjb25zdCBhbGxOb2Rlc0Zyb21GaWxlID0gW1xuICAgICAgICAuLi5zb3VyY2VGaWxlLmdldEZ1bmN0aW9ucygpLFxuICAgICAgICAuLi5jbGFzc2VzLFxuICAgICAgICAuLi5nZXRDbGFzc05vZGVzKGNsYXNzZXMpLFxuICAgICAgICAuLi5zb3VyY2VGaWxlLmdldFR5cGVBbGlhc2VzKCksXG4gICAgICAgIC4uLnNvdXJjZUZpbGUuZ2V0RW51bXMoKSxcbiAgICAgICAgLi4uc291cmNlRmlsZS5nZXRJbnRlcmZhY2VzKCksXG4gICAgICAgIC8vIC4uLnNvdXJjZUZpbGUuZ2V0VmFyaWFibGVTdGF0ZW1lbnRzKCkuZmxhdE1hcChzdGF0ZW1lbnQgPT4gc3RhdGVtZW50LmdldERlY2xhcmF0aW9ucygpKVxuICAgICAgXTtcblxuICAgICAgY29uc3QgY292ZXJhZ2VSZXBvcnRPZkN1cnJlbnRGaWxlID0gYWxsTm9kZXNGcm9tRmlsZS5yZWR1Y2UoKGFjYywgbm9kZSkgPT4ge1xuICAgICAgICBjb25zdCBub2RlVHlwZSA9IGdldENvdmVyYWdlVHlwZUZyb21LaW5kKG5vZGUuZ2V0S2luZCgpKTtcbiAgICAgICAgYWNjW25vZGVUeXBlXS5ub2Rlc0NvdW50Kys7XG4gICAgICAgIGlmIChub2RlLmdldEpzRG9jcygpLmxlbmd0aCA9PT0gMCkge1xuICAgICAgICAgIGFjY1tub2RlVHlwZV0uaXNzdWVzLnB1c2goXG4gICAgICAgICAgICBnZXRVbmRvY3VtZW50ZWROb2RlKGZpbGVQYXRoLCBub2RlVHlwZSwgbm9kZS5nZXROYW1lKCkgfHwgJycsIG5vZGUuZ2V0U3RhcnRMaW5lTnVtYmVyKCkpXG4gICAgICAgICAgKTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gYWNjO1xuICAgICAgfSwgY3JlYXRlRW1wdHlVbnByb2Nlc3NlZENvdmVyYWdlUmVwb3J0KCkpO1xuXG4gICAgICByZXR1cm4gbWVyZ2VDb3ZlcmFnZVJlc3VsdHMoY292ZXJhZ2VSZXBvcnRPZkFsbEZpbGVzLCBjb3ZlcmFnZVJlcG9ydE9mQ3VycmVudEZpbGUpO1xuICAgIH0sIGNyZWF0ZUVtcHR5VW5wcm9jZXNzZWRDb3ZlcmFnZVJlcG9ydCgpKTtcblxuICByZXR1cm4gY2FsY3VsYXRlQ292ZXJhZ2UodW5wcm9jZXNzZWRDb3ZlcmFnZVJlcG9ydCk7XG5cbn1cblxuZnVuY3Rpb24gbWVyZ2VDb3ZlcmFnZVJlc3VsdHMocmVzdWx0czogVW5wcm9jZXNzZWRDb3ZlcmFnZVJlc3VsdCwgY3VycmVudDogVW5wcm9jZXNzZWRDb3ZlcmFnZVJlc3VsdCkge1xuICByZXR1cm4ge1xuICAgIC4uLk9iamVjdC5mcm9tRW50cmllcyhPYmplY3QuZW50cmllcyhyZXN1bHRzKS5tYXAoKFtrZXksIHZhbHVlXSkgPT4ge1xuICAgICAgY29uc3Qgbm9kZSA9IHZhbHVlIGFzIENvdmVyYWdlUmVzdWx0W0NvdmVyYWdlVHlwZV07XG4gICAgICBjb25zdCB0eXBlID0ga2V5IGFzIENvdmVyYWdlVHlwZTtcbiAgICAgIHJldHVybiBbdHlwZSwge1xuICAgICAgICBub2Rlc0NvdW50OiBub2RlLm5vZGVzQ291bnQgKyBjdXJyZW50W3R5cGVdLm5vZGVzQ291bnQsXG4gICAgICAgIGlzc3VlczogWy4uLm5vZGUuaXNzdWVzLCAuLi5jdXJyZW50W3R5cGVdLmlzc3Vlc10sXG4gICAgICB9XVxuICAgIH0pKVxuICB9IGFzIFVucHJvY2Vzc2VkQ292ZXJhZ2VSZXN1bHQ7XG59XG5cbmZ1bmN0aW9uIGdldENsYXNzTm9kZXMoY2xhc3NOb2RlczogQ2xhc3NEZWNsYXJhdGlvbltdKSB7XG4gIHJldHVybiBjbGFzc05vZGVzLmZsYXRNYXAoY2xhc3NOb2RlID0+IFsuLi5jbGFzc05vZGUuZ2V0TWV0aG9kcygpLCAuLi5jbGFzc05vZGUuZ2V0UHJvcGVydGllcygpXSlcbn1cblxuLyoqXG4gKiBDcmVhdGVzIGFuIHVuZG9jdW1lbnRlZCBpdGVtIGVudHJ5XG4gKiBAcGFyYW0gZmlsZSAtIFRoZSBmaWxlIHBhdGggd2hlcmUgdGhlIGl0ZW0gd2FzIGZvdW5kXG4gKiBAcGFyYW0gdHlwZSAtIFRoZSB0eXBlIG9mIHRoZSB1bmRvY3VtZW50ZWQgaXRlbVxuICogQHBhcmFtIG5hbWUgLSBUaGUgbmFtZSBvZiB0aGUgdW5kb2N1bWVudGVkIGl0ZW1cbiAqIEBwYXJhbSBsaW5lIC0gVGhlIGxpbmUgbnVtYmVyIHdoZXJlIHRoZSBpdGVtIGFwcGVhcnNcbiAqIEByZXR1cm5zIHtVbmRvY3VtZW50ZWROb2RlfSBUaGUgdW5kb2N1bWVudGVkIGl0ZW0gZW50cnlcbiAqL1xuZnVuY3Rpb24gZ2V0VW5kb2N1bWVudGVkTm9kZShmaWxlOiBzdHJpbmcsIHR5cGU6IENvdmVyYWdlVHlwZSwgbmFtZTogc3RyaW5nLCBsaW5lOiBudW1iZXIpOiBVbmRvY3VtZW50ZWROb2RlIHtcbiAgcmV0dXJuIHsgZmlsZSwgdHlwZSwgbmFtZSwgbGluZSB9O1xufVxuXG5cblxuXG4iLCAiY29uc3QgX19pbmplY3RlZF9maWxlbmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tZG9jLWNvdmVyYWdlL3NyYy9saWIvdXRpbHMudHNcIjtjb25zdCBfX2luamVjdGVkX2Rpcm5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWRvYy1jb3ZlcmFnZS9zcmMvbGliXCI7Y29uc3QgX19pbmplY3RlZF9pbXBvcnRfbWV0YV91cmxfXyA9IFwiZmlsZTovLy9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1kb2MtY292ZXJhZ2Uvc3JjL2xpYi91dGlscy50c1wiO2ltcG9ydCB0eXBlIHsgQXVkaXQsIEF1ZGl0T3V0cHV0cywgR3JvdXAgfSBmcm9tIFwiQGNvZGUtcHVzaHVwL21vZGVsc1wiO1xuaW1wb3J0IHsgU3ludGF4S2luZCB9IGZyb20gXCJ0eXBlc2NyaXB0XCI7XG5pbXBvcnQgdHlwZSB7IERvY0NvdmVyYWdlUGx1Z2luQ29uZmlnIH0gZnJvbSBcIi4vY29uZmlnXCI7XG5pbXBvcnQgeyBBVURJVFNfTUFQIH0gZnJvbSBcIi4vY29uc3RhbnRzXCI7XG5pbXBvcnQgdHlwZSB7IENvdmVyYWdlUmVzdWx0LCBDb3ZlcmFnZVR5cGUgfSBmcm9tIFwiLi9tb2RlbHNcIjtcblxuLyoqXG4gKiBHZXQgYXVkaXRzIGJhc2VkIG9uIHRoZSBjb25maWd1cmF0aW9uLlxuICogSWYgbm8gYXVkaXRzIGFyZSBzcGVjaWZpZWQsIHJldHVybiBhbGwgYXVkaXRzLlxuICogSWYgYXVkaXRzIGFyZSBzcGVjaWZpZWQsIHJldHVybiBvbmx5IHRoZSBzcGVjaWZpZWQgYXVkaXRzLlxuICogQHBhcmFtIGNvbmZpZyAtIFRoZSBjb25maWd1cmF0aW9uIG9iamVjdC5cbiAqIEByZXR1cm5zIFRoZSBhdWRpdHMuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBmaWx0ZXJBdWRpdHNCeVBsdWdpbkNvbmZpZyhjb25maWc6IFBpY2s8RG9jQ292ZXJhZ2VQbHVnaW5Db25maWcsICdvbmx5QXVkaXRzJz4pOiBBdWRpdFtdIHtcbiAgICBjb25zdCB7IG9ubHlBdWRpdHMgfSA9IGNvbmZpZ1xuXG4gICAgaWYgKCFvbmx5QXVkaXRzIHx8IG9ubHlBdWRpdHMubGVuZ3RoID09PSAwKSB7XG4gICAgICAgIHJldHVybiBPYmplY3QudmFsdWVzKEFVRElUU19NQVApO1xuICAgIH1cblxuICAgIHJldHVybiBPYmplY3QudmFsdWVzKEFVRElUU19NQVApLmZpbHRlcihhdWRpdCA9PiBvbmx5QXVkaXRzLmluY2x1ZGVzKGF1ZGl0LnNsdWcpKTtcblxufVxuXG4vLyByZXR1cm4gZ3JvdXBzLmZpbHRlcihncm91cCA9PiBncm91cC5yZWZzLnNvbWUocmVmID0+IGF1ZGl0cy5zb21lKGF1ZGl0ID0+IGF1ZGl0LnNsdWcgPT09IHJlZi5zbHVnKSkpO1xuXG4vKipcbiAqIEZpbHRlciBncm91cHMgYnkgdGhlIGF1ZGl0cyB0aGF0IGFyZSBzcGVjaWZpZWQgaW4gdGhlIGNvbmZpZ3VyYXRpb24uXG4gKiBUaGUgZ3JvdXBzIHJlZnMgYXJlIGZpbHRlcmVkIHRvIG9ubHkgaW5jbHVkZSB0aGUgYXVkaXRzIHRoYXQgYXJlIHNwZWNpZmllZCBpbiB0aGUgY29uZmlndXJhdGlvbi5cbiAqIEBwYXJhbSBncm91cHMgLSBUaGUgZ3JvdXBzIHRvIGZpbHRlci5cbiAqIEBwYXJhbSBvcHRpb25zIC0gVGhlIGNvbmZpZ3VyYXRpb24gb2JqZWN0LlxuICogQHJldHVybnMgVGhlIGZpbHRlcmVkIGdyb3Vwcy5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGZpbHRlckdyb3Vwc0J5T25seUF1ZGl0cyhncm91cHM6IEdyb3VwW10sIG9wdGlvbnM6IFBpY2s8RG9jQ292ZXJhZ2VQbHVnaW5Db25maWcsICdvbmx5QXVkaXRzJz4pOiBHcm91cFtdIHtcbiAgICBjb25zdCBhdWRpdHMgPSBmaWx0ZXJBdWRpdHNCeVBsdWdpbkNvbmZpZyhvcHRpb25zKTtcbiAgICByZXR1cm4gZ3JvdXBzXG4gICAgICAgIC5tYXAoZ3JvdXAgPT4gKHtcbiAgICAgICAgICAgIC4uLmdyb3VwLFxuICAgICAgICAgICAgcmVmczogZ3JvdXAucmVmcy5maWx0ZXIocmVmID0+IGF1ZGl0cy5zb21lKGF1ZGl0ID0+IGF1ZGl0LnNsdWcgPT09IHJlZi5zbHVnKSlcbiAgICAgICAgfSkpXG4gICAgICAgIC5maWx0ZXIoZ3JvdXAgPT4gZ3JvdXAucmVmcy5sZW5ndGggPiAwKTs7XG59XG5cblxuLyoqXG4gKiBUcmFuc2Zvcm1zIHRoZSBjb3ZlcmFnZSByZXBvcnQgaW50byBhdWRpdCBvdXRwdXRzLlxuICogQHBhcmFtIGNvdmVyYWdlUmVzdWx0IC0gVGhlIGNvdmVyYWdlIHJlc3VsdCBjb250YWluaW5nIHVuZG9jdW1lbnRlZCBpdGVtcyBhbmQgY292ZXJhZ2Ugc3RhdGlzdGljc1xuICogQHBhcmFtIG9wdGlvbnMgLSBDb25maWd1cmF0aW9uIG9wdGlvbnMgc3BlY2lmeWluZyB3aGljaCBhdWRpdHMgdG8gaW5jbHVkZVxuICogQHJldHVybnMgQXVkaXQgb3V0cHV0cyB3aXRoIGNvdmVyYWdlIHNjb3JlcyBhbmQgZGV0YWlscyBhYm91dCB1bmRvY3VtZW50ZWQgaXRlbXNcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHRyYXNmb3JtQ292ZXJhZ2VSZXBvcnRUb0F1ZGl0cyhjb3ZlcmFnZVJlc3VsdDogQ292ZXJhZ2VSZXN1bHQsIG9wdGlvbnM6IFBpY2s8RG9jQ292ZXJhZ2VQbHVnaW5Db25maWcsICdvbmx5QXVkaXRzJz4pOiBBdWRpdE91dHB1dHMge1xuXG4gICAgcmV0dXJuIE9iamVjdC5lbnRyaWVzKGNvdmVyYWdlUmVzdWx0KVxuICAgICAgICAuZmlsdGVyKChbdHlwZV0pID0+ICFvcHRpb25zLm9ubHlBdWRpdHM/Lmxlbmd0aCB8fCBvcHRpb25zLm9ubHlBdWRpdHMuaW5jbHVkZXMoYCR7dHlwZX0tY292ZXJhZ2VgKSlcbiAgICAgICAgLm1hcCgoW3R5cGUsIGl0ZW1zXSkgPT4ge1xuICAgICAgICAgICAgY29uc3QgY292ZXJhZ2VUeXBlID0gdHlwZSBhcyBDb3ZlcmFnZVR5cGU7XG4gICAgICAgICAgICBjb25zdCBjb3ZlcmFnZSA9IGl0ZW1zLmNvdmVyYWdlO1xuXG4gICAgICAgICAgICByZXR1cm4ge1xuICAgICAgICAgICAgICAgIHNsdWc6IGAke2NvdmVyYWdlVHlwZX0tY292ZXJhZ2VgLFxuICAgICAgICAgICAgICAgIHZhbHVlOiBjb3ZlcmFnZSxcbiAgICAgICAgICAgICAgICBzY29yZTogY292ZXJhZ2UgLyAxMDAsXG4gICAgICAgICAgICAgICAgZGlzcGxheVZhbHVlOiBgJHtjb3ZlcmFnZX0gJWAsXG4gICAgICAgICAgICAgICAgZGV0YWlsczoge1xuICAgICAgICAgICAgICAgICAgICBpc3N1ZXM6IGl0ZW1zLmlzc3Vlcy5tYXAoKHsgZmlsZSwgbGluZSB9KSA9PiAoe1xuICAgICAgICAgICAgICAgICAgICAgICAgbWVzc2FnZTogJ01pc3NpbmcgZG9jdW1lbnRhdGlvbicsXG4gICAgICAgICAgICAgICAgICAgICAgICBzb3VyY2U6IHsgZmlsZSwgcG9zaXRpb246IHsgc3RhcnRMaW5lOiBsaW5lIH0gfSxcbiAgICAgICAgICAgICAgICAgICAgICAgIHNldmVyaXR5OiAnd2FybmluZycsXG4gICAgICAgICAgICAgICAgICAgIH0pKSxcbiAgICAgICAgICAgICAgICB9LFxuICAgICAgICAgICAgfTtcbiAgICAgICAgfSk7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBnZXRDb3ZlcmFnZVR5cGVGcm9tS2luZChraW5kOiBTeW50YXhLaW5kKTogQ292ZXJhZ2VUeXBlIHtcbiAgICBzd2l0Y2ggKGtpbmQpIHtcbiAgICAgICAgY2FzZSBTeW50YXhLaW5kLkNsYXNzRGVjbGFyYXRpb246XG4gICAgICAgICAgICByZXR1cm4gJ2NsYXNzZXMnO1xuICAgICAgICBjYXNlIFN5bnRheEtpbmQuTWV0aG9kRGVjbGFyYXRpb246XG4gICAgICAgICAgICByZXR1cm4gJ21ldGhvZHMnO1xuICAgICAgICBjYXNlIFN5bnRheEtpbmQuRnVuY3Rpb25EZWNsYXJhdGlvbjpcbiAgICAgICAgICAgIHJldHVybiAnZnVuY3Rpb25zJztcbiAgICAgICAgY2FzZSBTeW50YXhLaW5kLkludGVyZmFjZURlY2xhcmF0aW9uOlxuICAgICAgICAgICAgcmV0dXJuICdpbnRlcmZhY2VzJztcbiAgICAgICAgY2FzZSBTeW50YXhLaW5kLkVudW1EZWNsYXJhdGlvbjpcbiAgICAgICAgICAgIHJldHVybiAnZW51bXMnO1xuICAgICAgICBjYXNlIFN5bnRheEtpbmQuVmFyaWFibGVEZWNsYXJhdGlvbjpcbiAgICAgICAgICAgIHJldHVybiAndmFyaWFibGVzJztcbiAgICAgICAgY2FzZSBTeW50YXhLaW5kLlByb3BlcnR5RGVjbGFyYXRpb246XG4gICAgICAgICAgICByZXR1cm4gJ3Byb3BlcnRpZXMnO1xuICAgICAgICBjYXNlIFN5bnRheEtpbmQuVHlwZUFsaWFzRGVjbGFyYXRpb246XG4gICAgICAgICAgICByZXR1cm4gJ3R5cGVzJztcbiAgICAgICAgZGVmYXVsdDpcbiAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcihgVW5zdXBwb3J0ZWQgc3ludGF4IGtpbmQ6ICR7a2luZH1gKTtcbiAgICB9XG59XG4iLCAiY29uc3QgX19pbmplY3RlZF9maWxlbmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tZG9jLWNvdmVyYWdlL3NyYy9saWIvcnVubmVyL3V0aWxzLnRzXCI7Y29uc3QgX19pbmplY3RlZF9kaXJuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1kb2MtY292ZXJhZ2Uvc3JjL2xpYi9ydW5uZXJcIjtjb25zdCBfX2luamVjdGVkX2ltcG9ydF9tZXRhX3VybF9fID0gXCJmaWxlOi8vL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWRvYy1jb3ZlcmFnZS9zcmMvbGliL3J1bm5lci91dGlscy50c1wiO2ltcG9ydCB0eXBlIHsgQ292ZXJhZ2VSZXN1bHQsIENvdmVyYWdlVHlwZSwgVW5wcm9jZXNzZWRDb3ZlcmFnZVJlc3VsdCB9IGZyb20gXCIuLi9tb2RlbHNcIjtcblxuZXhwb3J0IGZ1bmN0aW9uIGNyZWF0ZUVtcHR5VW5wcm9jZXNzZWRDb3ZlcmFnZVJlcG9ydCgpOiBVbnByb2Nlc3NlZENvdmVyYWdlUmVzdWx0IHtcbiAgICByZXR1cm4ge1xuICAgICAgICBlbnVtczogeyBub2Rlc0NvdW50OiAwLCBpc3N1ZXM6IFtdIH0sXG4gICAgICAgIGludGVyZmFjZXM6IHsgbm9kZXNDb3VudDogMCwgaXNzdWVzOiBbXSB9LFxuICAgICAgICB0eXBlczogeyBub2Rlc0NvdW50OiAwLCBpc3N1ZXM6IFtdIH0sXG4gICAgICAgIGZ1bmN0aW9uczogeyBub2Rlc0NvdW50OiAwLCBpc3N1ZXM6IFtdIH0sXG4gICAgICAgIHZhcmlhYmxlczogeyBub2Rlc0NvdW50OiAwLCBpc3N1ZXM6IFtdIH0sXG4gICAgICAgIGNsYXNzZXM6IHsgbm9kZXNDb3VudDogMCwgaXNzdWVzOiBbXSB9LFxuICAgICAgICBtZXRob2RzOiB7IG5vZGVzQ291bnQ6IDAsIGlzc3VlczogW10gfSxcbiAgICAgICAgcHJvcGVydGllczogeyBub2Rlc0NvdW50OiAwLCBpc3N1ZXM6IFtdIH0sXG4gICAgfVxufVxuXG5leHBvcnQgZnVuY3Rpb24gY2FsY3VsYXRlQ292ZXJhZ2UocmVzdWx0OiBVbnByb2Nlc3NlZENvdmVyYWdlUmVzdWx0KSB7XG4gICAgcmV0dXJuIE9iamVjdC5mcm9tRW50cmllcyhPYmplY3QuZW50cmllcyhyZXN1bHQpLm1hcCgoW2tleSwgdmFsdWVdKSA9PiB7XG4gICAgICAgIGNvbnN0IHR5cGUgPSBrZXkgYXMgQ292ZXJhZ2VUeXBlO1xuICAgICAgICByZXR1cm4gW3R5cGUsIHtcbiAgICAgICAgICAgIGNvdmVyYWdlOiB2YWx1ZS5ub2Rlc0NvdW50ID09PSAwID8gMTAwIDogKDEgLSB2YWx1ZS5pc3N1ZXMubGVuZ3RoIC8gdmFsdWUubm9kZXNDb3VudCkgKiAxMDAsXG4gICAgICAgICAgICBpc3N1ZXM6IHZhbHVlLmlzc3VlcyxcbiAgICAgICAgICAgIG5vZGVzQ291bnQ6IHZhbHVlLm5vZGVzQ291bnRcbiAgICAgICAgfV1cbiAgICB9KSkgYXMgQ292ZXJhZ2VSZXN1bHQ7XG59XG4iLCAiY29uc3QgX19pbmplY3RlZF9maWxlbmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tZG9jLWNvdmVyYWdlL3NyYy9saWIvZG9jLWNvdmVyYWdlLXBsdWdpbi50c1wiO2NvbnN0IF9faW5qZWN0ZWRfZGlybmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tZG9jLWNvdmVyYWdlL3NyYy9saWJcIjtjb25zdCBfX2luamVjdGVkX2ltcG9ydF9tZXRhX3VybF9fID0gXCJmaWxlOi8vL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWRvYy1jb3ZlcmFnZS9zcmMvbGliL2RvYy1jb3ZlcmFnZS1wbHVnaW4udHNcIjtpbXBvcnQgdHlwZSB7IEF1ZGl0T3V0cHV0cywgUGx1Z2luQ29uZmlnLCBSdW5uZXJGdW5jdGlvbiB9IGZyb20gJ0Bjb2RlLXB1c2h1cC9tb2RlbHMnO1xuaW1wb3J0IHtcbiAgdHlwZSBEb2NDb3ZlcmFnZVBsdWdpbkNvbmZpZyxcbiAgZG9jQ292ZXJhZ2VQbHVnaW5Db25maWdTY2hlbWEsXG59IGZyb20gJy4vY29uZmlnLmpzJztcbmltcG9ydCB7IGdyb3VwcywgUExVR0lOX1NMVUcgfSBmcm9tICcuL2NvbnN0YW50cy5qcyc7XG5pbXBvcnQgeyBwcm9jZXNzRG9jQ292ZXJhZ2UgfSBmcm9tICcuL3J1bm5lci9kb2MtcHJvY2Vzc2VyLmpzJztcbmltcG9ydCB7IGZpbHRlckF1ZGl0c0J5UGx1Z2luQ29uZmlnLCBmaWx0ZXJHcm91cHNCeU9ubHlBdWRpdHMsIHRyYXNmb3JtQ292ZXJhZ2VSZXBvcnRUb0F1ZGl0cyB9IGZyb20gJy4vdXRpbHMuanMnO1xuXG5jb25zdCBQTFVHSU5fVElUTEUgPSAnRG9jdW1lbnRhdGlvbiBjb3ZlcmFnZSc7XG5cbmNvbnN0IFBMVUdJTl9ERVNDUklQVElPTiA9ICdPZmZpY2lhbCBDb2RlIFB1c2hVcCBkb2N1bWVudGF0aW9uIGNvdmVyYWdlIHBsdWdpbi4nO1xuXG5jb25zdCBQTFVHSU5fRE9DU19VUkwgPSAnaHR0cHM6Ly93d3cubnBtanMuY29tL3BhY2thZ2UvQGNvZGUtcHVzaHVwL2RvYy1jb3ZlcmFnZS1wbHVnaW4vJztcblxuLyoqXG4gKiBJbnN0YW50aWF0ZXMgQ29kZSBQdXNoVXAgZG9jdW1lbnRhdGlvbiBjb3ZlcmFnZSBwbHVnaW4gZm9yIGNvcmUgY29uZmlnLlxuICpcbiAqIEBleGFtcGxlXG4gKiBpbXBvcnQgZG9jQ292ZXJhZ2VQbHVnaW4gZnJvbSAnQGNvZGUtcHVzaHVwL2RvYy1jb3ZlcmFnZS1wbHVnaW4nXG4gKlxuICogZXhwb3J0IGRlZmF1bHQge1xuICogICAvLyAuLi4gY29yZSBjb25maWcgLi4uXG4gKiAgIHBsdWdpbnM6IFtcbiAqICAgICAvLyAuLi4gb3RoZXIgcGx1Z2lucyAuLi5cbiAqICAgICBhd2FpdCBkb2NDb3ZlcmFnZVBsdWdpbih7XG4gKiAgICAgICBzb3VyY2VHbG9iOiAnc3JjJiM0NzsqKiYjNDc7Ki57dHMsdHN4fSdcbiAqICAgICB9KVxuICogICBdXG4gKiB9XG4gKlxuICogQHJldHVybnMgUGx1Z2luIGNvbmZpZ3VyYXRpb24uXG4gKi9cblxuXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gZG9jQ292ZXJhZ2VQbHVnaW4oY29uZmlnOiBEb2NDb3ZlcmFnZVBsdWdpbkNvbmZpZyk6IFByb21pc2U8UGx1Z2luQ29uZmlnPiB7XG5cblxuICBjb25zdCBkb2NDb3ZlcmFnZUNvbmZpZyA9IGRvY0NvdmVyYWdlUGx1Z2luQ29uZmlnU2NoZW1hLnBhcnNlKGNvbmZpZyk7XG5cblxuICBjb25zdCBncm91cHNDID0gZmlsdGVyR3JvdXBzQnlPbmx5QXVkaXRzKGdyb3VwcywgZG9jQ292ZXJhZ2VDb25maWcpO1xuICBjb25zdCBhdWRpdHNDID0gZmlsdGVyQXVkaXRzQnlQbHVnaW5Db25maWcoZG9jQ292ZXJhZ2VDb25maWcpO1xuXG4gIHJldHVybiB7XG4gICAgc2x1ZzogUExVR0lOX1NMVUcsXG4gICAgdGl0bGU6IFBMVUdJTl9USVRMRSxcbiAgICBpY29uOiAnZm9sZGVyLXNyYycsXG4gICAgZGVzY3JpcHRpb246IFBMVUdJTl9ERVNDUklQVElPTixcbiAgICBkb2NzVXJsOiBQTFVHSU5fRE9DU19VUkwsXG4gICAgZ3JvdXBzOiBmaWx0ZXJHcm91cHNCeU9ubHlBdWRpdHMoZ3JvdXBzLCBkb2NDb3ZlcmFnZUNvbmZpZyksXG4gICAgYXVkaXRzOiBmaWx0ZXJBdWRpdHNCeVBsdWdpbkNvbmZpZyhkb2NDb3ZlcmFnZUNvbmZpZyksXG4gICAgcnVubmVyOiBjcmVhdGVSdW5uZXJGdW5jdGlvbihkb2NDb3ZlcmFnZUNvbmZpZyksXG4gIH07XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBjcmVhdGVSdW5uZXJGdW5jdGlvbihjb25maWc6IERvY0NvdmVyYWdlUGx1Z2luQ29uZmlnKTogUnVubmVyRnVuY3Rpb24ge1xuICByZXR1cm4gKCk6IEF1ZGl0T3V0cHV0cyA9PiB7XG4gICAgY29uc3QgY292ZXJhZ2VSZXN1bHQgPSBwcm9jZXNzRG9jQ292ZXJhZ2UoY29uZmlnKVxuICAgIHJldHVybiB0cmFzZm9ybUNvdmVyYWdlUmVwb3J0VG9BdWRpdHMoY292ZXJhZ2VSZXN1bHQsIGNvbmZpZyk7XG4gIH07XG59XG4iLCAiY29uc3QgX19pbmplY3RlZF9maWxlbmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tZG9jLWNvdmVyYWdlL3NyYy9pbmRleC50c1wiO2NvbnN0IF9faW5qZWN0ZWRfZGlybmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tZG9jLWNvdmVyYWdlL3NyY1wiO2NvbnN0IF9faW5qZWN0ZWRfaW1wb3J0X21ldGFfdXJsX18gPSBcImZpbGU6Ly8vaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tZG9jLWNvdmVyYWdlL3NyYy9pbmRleC50c1wiO2ltcG9ydCB7IGRvY0NvdmVyYWdlUGx1Z2luIH0gZnJvbSAnLi9saWIvZG9jLWNvdmVyYWdlLXBsdWdpbi5qcyc7XG5cbmV4cG9ydCBkZWZhdWx0IGRvY0NvdmVyYWdlUGx1Z2luO1xuZXhwb3J0IHR5cGUgeyBEb2NDb3ZlcmFnZVBsdWdpbkNvbmZpZyB9IGZyb20gJy4vbGliL2NvbmZpZy5qcyc7XG4iLCAiY29uc3QgX19pbmplY3RlZF9maWxlbmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tZXNsaW50L3NyYy9saWIvZXNsaW50LXBsdWdpbi50c1wiO2NvbnN0IF9faW5qZWN0ZWRfZGlybmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tZXNsaW50L3NyYy9saWJcIjtjb25zdCBfX2luamVjdGVkX2ltcG9ydF9tZXRhX3VybF9fID0gXCJmaWxlOi8vL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWVzbGludC9zcmMvbGliL2VzbGludC1wbHVnaW4udHNcIjtpbXBvcnQgeyBjcmVhdGVSZXF1aXJlIH0gZnJvbSAnbm9kZTptb2R1bGUnO1xuaW1wb3J0IHBhdGggZnJvbSAnbm9kZTpwYXRoJztcbmltcG9ydCB7IGZpbGVVUkxUb1BhdGggfSBmcm9tICdub2RlOnVybCc7XG5pbXBvcnQgdHlwZSB7IFBsdWdpbkNvbmZpZyB9IGZyb20gJ0Bjb2RlLXB1c2h1cC9tb2RlbHMnO1xuaW1wb3J0IHsgdHlwZSBFU0xpbnRQbHVnaW5Db25maWcsIGVzbGludFBsdWdpbkNvbmZpZ1NjaGVtYSB9IGZyb20gJy4vY29uZmlnLmpzJztcbmltcG9ydCB7IGxpc3RBdWRpdHNBbmRHcm91cHMgfSBmcm9tICcuL21ldGEvaW5kZXguanMnO1xuaW1wb3J0IHsgY3JlYXRlUnVubmVyQ29uZmlnIH0gZnJvbSAnLi9ydW5uZXIvaW5kZXguanMnO1xuXG4vKipcbiAqIEluc3RhbnRpYXRlcyBDb2RlIFB1c2hVcCBFU0xpbnQgcGx1Z2luIGZvciB1c2UgaW4gY29yZSBjb25maWcuXG4gKlxuICogQGV4YW1wbGVcbiAqIGltcG9ydCBlc2xpbnRQbHVnaW4gZnJvbSAnQGNvZGUtcHVzaHVwL2VzbGludC1wbHVnaW4nXG4gKlxuICogZXhwb3J0IGRlZmF1bHQge1xuICogICAvLyAuLi4gY29yZSBjb25maWcgLi4uXG4gKiAgIHBsdWdpbnM6IFtcbiAqICAgICAvLyAuLi4gb3RoZXIgcGx1Z2lucyAuLi5cbiAqICAgICBhd2FpdCBlc2xpbnRQbHVnaW4oe1xuICogICAgICAgZXNsaW50cmM6ICcuZXNsaW50cmMuanNvbicsXG4gKiAgICAgICBwYXR0ZXJuczogWydzcmMnLCAndGVzdC8qLnNwZWMuanMnXVxuICogICAgIH0pXG4gKiAgIF1cbiAqIH1cbiAqXG4gKiBAcGFyYW0gY29uZmlnIENvbmZpZ3VyYXRpb24gb3B0aW9ucy5cbiAqIEByZXR1cm5zIFBsdWdpbiBjb25maWd1cmF0aW9uIGFzIGEgcHJvbWlzZS5cbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGVzbGludFBsdWdpbihcbiAgY29uZmlnOiBFU0xpbnRQbHVnaW5Db25maWcsXG4pOiBQcm9taXNlPFBsdWdpbkNvbmZpZz4ge1xuICBjb25zdCB0YXJnZXRzID0gZXNsaW50UGx1Z2luQ29uZmlnU2NoZW1hLnBhcnNlKGNvbmZpZyk7XG5cbiAgY29uc3QgeyBhdWRpdHMsIGdyb3VwcyB9ID0gYXdhaXQgbGlzdEF1ZGl0c0FuZEdyb3Vwcyh0YXJnZXRzKTtcblxuICBjb25zdCBydW5uZXJTY3JpcHRQYXRoID0gcGF0aC5qb2luKFxuICAgIGZpbGVVUkxUb1BhdGgocGF0aC5kaXJuYW1lKGltcG9ydC5tZXRhLnVybCkpLFxuICAgICcuLicsXG4gICAgJ2Jpbi5qcycsXG4gICk7XG5cbiAgY29uc3QgcGFja2FnZUpzb24gPSBjcmVhdGVSZXF1aXJlKGltcG9ydC5tZXRhLnVybCkoXG4gICAgJy4uLy4uL3BhY2thZ2UuanNvbicsXG4gICkgYXMgdHlwZW9mIGltcG9ydCgnLi4vLi4vcGFja2FnZS5qc29uJyk7XG5cbiAgcmV0dXJuIHtcbiAgICBzbHVnOiAnZXNsaW50JyxcbiAgICB0aXRsZTogJ0VTTGludCcsXG4gICAgaWNvbjogJ2VzbGludCcsXG4gICAgZGVzY3JpcHRpb246ICdPZmZpY2lhbCBDb2RlIFB1c2hVcCBFU0xpbnQgcGx1Z2luJyxcbiAgICBkb2NzVXJsOiAnaHR0cHM6Ly93d3cubnBtanMuY29tL3BhY2thZ2UvQGNvZGUtcHVzaHVwL2VzbGludC1wbHVnaW4nLFxuICAgIHBhY2thZ2VOYW1lOiBwYWNrYWdlSnNvbi5uYW1lLFxuICAgIHZlcnNpb246IHBhY2thZ2VKc29uLnZlcnNpb24sXG5cbiAgICBhdWRpdHMsXG4gICAgZ3JvdXBzLFxuXG4gICAgcnVubmVyOiBhd2FpdCBjcmVhdGVSdW5uZXJDb25maWcocnVubmVyU2NyaXB0UGF0aCwgYXVkaXRzLCB0YXJnZXRzKSxcbiAgfTtcbn1cbiIsICJjb25zdCBfX2luamVjdGVkX2ZpbGVuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1lc2xpbnQvc3JjL2xpYi9jb25maWcudHNcIjtjb25zdCBfX2luamVjdGVkX2Rpcm5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWVzbGludC9zcmMvbGliXCI7Y29uc3QgX19pbmplY3RlZF9pbXBvcnRfbWV0YV91cmxfXyA9IFwiZmlsZTovLy9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1lc2xpbnQvc3JjL2xpYi9jb25maWcudHNcIjtpbXBvcnQgeyB6IH0gZnJvbSAnem9kJztcbmltcG9ydCB7IHRvQXJyYXkgfSBmcm9tICdAY29kZS1wdXNodXAvdXRpbHMnO1xuXG5jb25zdCBwYXR0ZXJuc1NjaGVtYSA9IHoudW5pb24oW3ouc3RyaW5nKCksIHouYXJyYXkoei5zdHJpbmcoKSkubWluKDEpXSwge1xuICBkZXNjcmlwdGlvbjpcbiAgICAnTGludCB0YXJnZXQgZmlsZXMuIE1heSBjb250YWluIGZpbGUgcGF0aHMsIGRpcmVjdG9yeSBwYXRocyBvciBnbG9iIHBhdHRlcm5zJyxcbn0pO1xuXG5jb25zdCBlc2xpbnRyY1NjaGVtYSA9IHouc3RyaW5nKHsgZGVzY3JpcHRpb246ICdQYXRoIHRvIEVTTGludCBjb25maWcgZmlsZScgfSk7XG5cbmNvbnN0IGVzbGludFRhcmdldE9iamVjdFNjaGVtYSA9IHoub2JqZWN0KHtcbiAgZXNsaW50cmM6IGVzbGludHJjU2NoZW1hLm9wdGlvbmFsKCksXG4gIHBhdHRlcm5zOiBwYXR0ZXJuc1NjaGVtYSxcbn0pO1xudHlwZSBFU0xpbnRUYXJnZXRPYmplY3QgPSB6LmluZmVyPHR5cGVvZiBlc2xpbnRUYXJnZXRPYmplY3RTY2hlbWE+O1xuXG5leHBvcnQgY29uc3QgZXNsaW50VGFyZ2V0U2NoZW1hID0gelxuICAudW5pb24oW3BhdHRlcm5zU2NoZW1hLCBlc2xpbnRUYXJnZXRPYmplY3RTY2hlbWFdKVxuICAudHJhbnNmb3JtKFxuICAgICh0YXJnZXQpOiBFU0xpbnRUYXJnZXRPYmplY3QgPT5cbiAgICAgIHR5cGVvZiB0YXJnZXQgPT09ICdzdHJpbmcnIHx8IEFycmF5LmlzQXJyYXkodGFyZ2V0KVxuICAgICAgICA/IHsgcGF0dGVybnM6IHRhcmdldCB9XG4gICAgICAgIDogdGFyZ2V0LFxuICApO1xuZXhwb3J0IHR5cGUgRVNMaW50VGFyZ2V0ID0gei5pbmZlcjx0eXBlb2YgZXNsaW50VGFyZ2V0U2NoZW1hPjtcblxuZXhwb3J0IGNvbnN0IGVzbGludFBsdWdpbkNvbmZpZ1NjaGVtYSA9IHpcbiAgLnVuaW9uKFtlc2xpbnRUYXJnZXRTY2hlbWEsIHouYXJyYXkoZXNsaW50VGFyZ2V0U2NoZW1hKS5taW4oMSldKVxuICAudHJhbnNmb3JtKHRvQXJyYXkpO1xuZXhwb3J0IHR5cGUgRVNMaW50UGx1Z2luQ29uZmlnID0gei5pbnB1dDx0eXBlb2YgZXNsaW50UGx1Z2luQ29uZmlnU2NoZW1hPjtcblxuZXhwb3J0IHR5cGUgRVNMaW50UGx1Z2luUnVubmVyQ29uZmlnID0ge1xuICB0YXJnZXRzOiBFU0xpbnRUYXJnZXRbXTtcbiAgc2x1Z3M6IHN0cmluZ1tdO1xufTtcbiIsICJjb25zdCBfX2luamVjdGVkX2ZpbGVuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1lc2xpbnQvc3JjL2xpYi9tZXRhL2dyb3Vwcy50c1wiO2NvbnN0IF9faW5qZWN0ZWRfZGlybmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tZXNsaW50L3NyYy9saWIvbWV0YVwiO2NvbnN0IF9faW5qZWN0ZWRfaW1wb3J0X21ldGFfdXJsX18gPSBcImZpbGU6Ly8vaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tZXNsaW50L3NyYy9saWIvbWV0YS9ncm91cHMudHNcIjtpbXBvcnQgdHlwZSB7IFJ1bGUgfSBmcm9tICdlc2xpbnQnO1xuaW1wb3J0IHR5cGUgeyBHcm91cCwgR3JvdXBSZWYgfSBmcm9tICdAY29kZS1wdXNodXAvbW9kZWxzJztcbmltcG9ydCB7IG9iamVjdFRvS2V5cywgc2x1Z2lmeSB9IGZyb20gJ0Bjb2RlLXB1c2h1cC91dGlscyc7XG5pbXBvcnQgeyBydWxlVG9TbHVnIH0gZnJvbSAnLi9oYXNoLmpzJztcbmltcG9ydCB7IHR5cGUgUnVsZURhdGEsIHBhcnNlUnVsZUlkIH0gZnJvbSAnLi9wYXJzZS5qcyc7XG5cbnR5cGUgUnVsZVR5cGUgPSBOb25OdWxsYWJsZTxSdWxlLlJ1bGVNZXRhRGF0YVsndHlwZSddPjtcblxuLy8gZG9jcyBvbiBtZXRhLnR5cGU6IGh0dHBzOi8vZXNsaW50Lm9yZy9kb2NzL2xhdGVzdC9leHRlbmQvY3VzdG9tLXJ1bGVzI3J1bGUtc3RydWN0dXJlXG5jb25zdCB0eXBlR3JvdXBzOiBSZWNvcmQ8UnVsZVR5cGUsIE9taXQ8R3JvdXAsICdyZWZzJz4+ID0ge1xuICBwcm9ibGVtOiB7XG4gICAgc2x1ZzogJ3Byb2JsZW1zJyxcbiAgICB0aXRsZTogJ1Byb2JsZW1zJyxcbiAgICBkZXNjcmlwdGlvbjpcbiAgICAgICdDb2RlIHRoYXQgZWl0aGVyIHdpbGwgY2F1c2UgYW4gZXJyb3Igb3IgbWF5IGNhdXNlIGNvbmZ1c2luZyBiZWhhdmlvci4gRGV2ZWxvcGVycyBzaG91bGQgY29uc2lkZXIgdGhpcyBhIGhpZ2ggcHJpb3JpdHkgdG8gcmVzb2x2ZS4nLFxuICB9LFxuICBzdWdnZXN0aW9uOiB7XG4gICAgc2x1ZzogJ3N1Z2dlc3Rpb25zJyxcbiAgICB0aXRsZTogJ1N1Z2dlc3Rpb25zJyxcbiAgICBkZXNjcmlwdGlvbjpcbiAgICAgIFwiU29tZXRoaW5nIHRoYXQgY291bGQgYmUgZG9uZSBpbiBhIGJldHRlciB3YXkgYnV0IG5vIGVycm9ycyB3aWxsIG9jY3VyIGlmIHRoZSBjb2RlIGlzbid0IGNoYW5nZWQuXCIsXG4gIH0sXG4gIGxheW91dDoge1xuICAgIHNsdWc6ICdmb3JtYXR0aW5nJyxcbiAgICB0aXRsZTogJ0Zvcm1hdHRpbmcnLFxuICAgIGRlc2NyaXB0aW9uOlxuICAgICAgJ1ByaW1hcmlseSBhYm91dCB3aGl0ZXNwYWNlLCBzZW1pY29sb25zLCBjb21tYXMsIGFuZCBwYXJlbnRoZXNlcywgYWxsIHRoZSBwYXJ0cyBvZiB0aGUgcHJvZ3JhbSB0aGF0IGRldGVybWluZSBob3cgdGhlIGNvZGUgbG9va3MgcmF0aGVyIHRoYW4gaG93IGl0IGV4ZWN1dGVzLicsXG4gIH0sXG59O1xuXG5leHBvcnQgZnVuY3Rpb24gZ3JvdXBzRnJvbVJ1bGVUeXBlcyhydWxlczogUnVsZURhdGFbXSk6IEdyb3VwW10ge1xuICBjb25zdCBhbGxUeXBlcyA9IG9iamVjdFRvS2V5cyh0eXBlR3JvdXBzKTtcblxuICBjb25zdCBhdWRpdFNsdWdzTWFwID0gcnVsZXMucmVkdWNlPFBhcnRpYWw8UmVjb3JkPFJ1bGVUeXBlLCBzdHJpbmdbXT4+PihcbiAgICAoYWNjLCBydWxlKSA9PlxuICAgICAgcnVsZS5tZXRhLnR5cGUgPT0gbnVsbFxuICAgICAgICA/IGFjY1xuICAgICAgICA6IHtcbiAgICAgICAgICAgIC4uLmFjYyxcbiAgICAgICAgICAgIFtydWxlLm1ldGEudHlwZV06IFtcbiAgICAgICAgICAgICAgLi4uKGFjY1tydWxlLm1ldGEudHlwZV0gPz8gW10pLFxuICAgICAgICAgICAgICBydWxlVG9TbHVnKHJ1bGUpLFxuICAgICAgICAgICAgXSxcbiAgICAgICAgICB9LFxuICAgIHt9LFxuICApO1xuXG4gIHJldHVybiBhbGxUeXBlc1xuICAgIC5tYXAodHlwZSA9PiAoe1xuICAgICAgLi4udHlwZUdyb3Vwc1t0eXBlXSxcbiAgICAgIHJlZnM6XG4gICAgICAgIGF1ZGl0U2x1Z3NNYXBbdHlwZV0/Lm1hcCgoc2x1Zyk6IEdyb3VwUmVmID0+ICh7IHNsdWcsIHdlaWdodDogMSB9KSkgPz9cbiAgICAgICAgW10sXG4gICAgfSkpXG4gICAgLmZpbHRlcihncm91cCA9PiBncm91cC5yZWZzLmxlbmd0aCk7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBncm91cHNGcm9tUnVsZUNhdGVnb3JpZXMocnVsZXM6IFJ1bGVEYXRhW10pOiBHcm91cFtdIHtcbiAgY29uc3QgY2F0ZWdvcmllc01hcCA9IHJ1bGVzLnJlZHVjZTxSZWNvcmQ8c3RyaW5nLCBSZWNvcmQ8c3RyaW5nLCBzdHJpbmdbXT4+PihcbiAgICAoYWNjLCBydWxlKSA9PiB7XG4gICAgICAvLyBtZXRhLmRvY3MuY2F0ZWdvcnkgc3RpbGwgdXNlZCBieSBzb21lIHBvcHVsYXIgcGx1Z2lucyAoZS5nLiBpbXBvcnQsIHJlYWN0LCBmdW5jdGlvbmFsKVxuICAgICAgY29uc3QgY2F0ZWdvcnkgPSBydWxlLm1ldGEuZG9jcz8uY2F0ZWdvcnk7XG4gICAgICBpZiAoIWNhdGVnb3J5KSB7XG4gICAgICAgIHJldHVybiBhY2M7XG4gICAgICB9XG4gICAgICBjb25zdCB7IHBsdWdpbiA9ICcnIH0gPSBwYXJzZVJ1bGVJZChydWxlLmlkKTtcbiAgICAgIHJldHVybiB7XG4gICAgICAgIC4uLmFjYyxcbiAgICAgICAgW3BsdWdpbl06IHtcbiAgICAgICAgICAuLi5hY2NbcGx1Z2luXSxcbiAgICAgICAgICBbY2F0ZWdvcnldOiBbLi4uKGFjY1twbHVnaW5dPy5bY2F0ZWdvcnldID8/IFtdKSwgcnVsZVRvU2x1ZyhydWxlKV0sXG4gICAgICAgIH0sXG4gICAgICB9O1xuICAgIH0sXG4gICAge30sXG4gICk7XG5cbiAgY29uc3QgZ3JvdXBzID0gT2JqZWN0LmVudHJpZXMoY2F0ZWdvcmllc01hcCkuZmxhdE1hcCgoW3BsdWdpbiwgY2F0ZWdvcmllc10pID0+XG4gICAgT2JqZWN0LmVudHJpZXMoY2F0ZWdvcmllcykubWFwKFxuICAgICAgKFtjYXRlZ29yeSwgc2x1Z3NdKTogR3JvdXAgPT4gKHtcbiAgICAgICAgc2x1ZzogYCR7c2x1Z2lmeShwbHVnaW4pfS0ke3NsdWdpZnkoY2F0ZWdvcnkpfWAsXG4gICAgICAgIHRpdGxlOiBgJHtjYXRlZ29yeX0gKCR7cGx1Z2lufSlgLFxuICAgICAgICByZWZzOiBzbHVncy5tYXAoc2x1ZyA9PiAoeyBzbHVnLCB3ZWlnaHQ6IDEgfSkpLFxuICAgICAgfSksXG4gICAgKSxcbiAgKTtcblxuICByZXR1cm4gZ3JvdXBzLnRvU29ydGVkKChhLCBiKSA9PiBhLnNsdWcubG9jYWxlQ29tcGFyZShiLnNsdWcpKTtcbn1cbiIsICJjb25zdCBfX2luamVjdGVkX2ZpbGVuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1lc2xpbnQvc3JjL2xpYi9tZXRhL2hhc2gudHNcIjtjb25zdCBfX2luamVjdGVkX2Rpcm5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWVzbGludC9zcmMvbGliL21ldGFcIjtjb25zdCBfX2luamVjdGVkX2ltcG9ydF9tZXRhX3VybF9fID0gXCJmaWxlOi8vL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWVzbGludC9zcmMvbGliL21ldGEvaGFzaC50c1wiO2ltcG9ydCB7IGNyZWF0ZUhhc2ggfSBmcm9tICdub2RlOmNyeXB0byc7XG5pbXBvcnQgeyBzbHVnaWZ5IH0gZnJvbSAnQGNvZGUtcHVzaHVwL3V0aWxzJztcbmltcG9ydCB7IHR5cGUgUnVsZURhdGEsIHJlc29sdmVSdWxlT3B0aW9ucyB9IGZyb20gJy4vcGFyc2UuanMnO1xuXG5leHBvcnQgZnVuY3Rpb24gcnVsZVRvU2x1ZyhydWxlOiBSdWxlRGF0YSk6IHN0cmluZyB7XG4gIHJldHVybiBydWxlSWRUb1NsdWcocnVsZS5pZCwgcmVzb2x2ZVJ1bGVPcHRpb25zKHJ1bGUpKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHJ1bGVJZFRvU2x1ZyhcbiAgcnVsZUlkOiBzdHJpbmcsXG4gIG9wdGlvbnM6IHVua25vd25bXSB8IHVuZGVmaW5lZCxcbik6IHN0cmluZyB7XG4gIGNvbnN0IHNsdWcgPSBzbHVnaWZ5KHJ1bGVJZCk7XG4gIGlmICghb3B0aW9ucz8ubGVuZ3RoKSB7XG4gICAgcmV0dXJuIHNsdWc7XG4gIH1cbiAgcmV0dXJuIGAke3NsdWd9LSR7anNvbkhhc2gob3B0aW9ucyl9YDtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGpzb25IYXNoKGRhdGE6IHVua25vd24sIGJ5dGVzID0gOCk6IHN0cmluZyB7XG4gIHJldHVybiBjcmVhdGVIYXNoKCdzaGFrZTI1NicsIHsgb3V0cHV0TGVuZ3RoOiBieXRlcyB9KVxuICAgIC51cGRhdGUoSlNPTi5zdHJpbmdpZnkoZGF0YSkgfHwgJ251bGwnKVxuICAgIC5kaWdlc3QoJ2hleCcpO1xufVxuIiwgImNvbnN0IF9faW5qZWN0ZWRfZmlsZW5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWVzbGludC9zcmMvbGliL21ldGEvcGFyc2UudHNcIjtjb25zdCBfX2luamVjdGVkX2Rpcm5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWVzbGludC9zcmMvbGliL21ldGFcIjtjb25zdCBfX2luamVjdGVkX2ltcG9ydF9tZXRhX3VybF9fID0gXCJmaWxlOi8vL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWVzbGludC9zcmMvbGliL21ldGEvcGFyc2UudHNcIjtpbXBvcnQgdHlwZSB7IExpbnRlciwgUnVsZSB9IGZyb20gJ2VzbGludCc7XG5pbXBvcnQgeyB0b0FycmF5IH0gZnJvbSAnQGNvZGUtcHVzaHVwL3V0aWxzJztcblxuZXhwb3J0IHR5cGUgUnVsZURhdGEgPSB7XG4gIGlkOiBzdHJpbmc7XG4gIG1ldGE6IFJ1bGUuUnVsZU1ldGFEYXRhO1xuICBvcHRpb25zOiB1bmtub3duW10gfCB1bmRlZmluZWQ7XG59O1xuXG5leHBvcnQgZnVuY3Rpb24gcGFyc2VSdWxlSWQocnVsZUlkOiBzdHJpbmcpOiB7IHBsdWdpbj86IHN0cmluZzsgbmFtZTogc3RyaW5nIH0ge1xuICBjb25zdCBpID0gcnVsZUlkLnN0YXJ0c1dpdGgoJ0AnKVxuICAgID8gcnVsZUlkLmxhc3RJbmRleE9mKCcvJylcbiAgICA6IHJ1bGVJZC5pbmRleE9mKCcvJyk7XG4gIGlmIChpID09PSAtMSkge1xuICAgIHJldHVybiB7IG5hbWU6IHJ1bGVJZCB9O1xuICB9XG4gIHJldHVybiB7XG4gICAgcGx1Z2luOiBydWxlSWQuc2xpY2UoMCwgaSksXG4gICAgbmFtZTogcnVsZUlkLnNsaWNlKGkgKyAxKSxcbiAgfTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGlzUnVsZU9mZihlbnRyeTogTGludGVyLlJ1bGVFbnRyeTx1bmtub3duW10+KTogYm9vbGVhbiB7XG4gIGNvbnN0IGxldmVsID0gQXJyYXkuaXNBcnJheShlbnRyeSkgPyBlbnRyeVswXSA6IGVudHJ5O1xuXG4gIHN3aXRjaCAobGV2ZWwpIHtcbiAgICBjYXNlIDA6XG4gICAgY2FzZSAnb2ZmJzpcbiAgICAgIHJldHVybiB0cnVlO1xuICAgIGNhc2UgMTpcbiAgICBjYXNlIDI6XG4gICAgY2FzZSAnd2Fybic6XG4gICAgY2FzZSAnZXJyb3InOlxuICAgICAgcmV0dXJuIGZhbHNlO1xuICB9XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBvcHRpb25zRnJvbVJ1bGVFbnRyeShcbiAgZW50cnk6IExpbnRlci5SdWxlRW50cnk8dW5rbm93bltdPixcbik6IHVua25vd25bXSB7XG4gIHJldHVybiB0b0FycmF5KGVudHJ5KS5zbGljZSgxKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHJlc29sdmVSdWxlT3B0aW9ucyhydWxlOiBSdWxlRGF0YSk6IHVua25vd25bXSB8IHVuZGVmaW5lZCB7XG4gIGlmIChydWxlLm9wdGlvbnM/Lmxlbmd0aCkge1xuICAgIHJldHVybiBydWxlLm9wdGlvbnM7XG4gIH1cbiAgcmV0dXJuIHJ1bGUubWV0YS5kZWZhdWx0T3B0aW9ucztcbn1cbiIsICJjb25zdCBfX2luamVjdGVkX2ZpbGVuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1lc2xpbnQvc3JjL2xpYi9tZXRhL3ZlcnNpb25zL2ZsYXQudHNcIjtjb25zdCBfX2luamVjdGVkX2Rpcm5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWVzbGludC9zcmMvbGliL21ldGEvdmVyc2lvbnNcIjtjb25zdCBfX2luamVjdGVkX2ltcG9ydF9tZXRhX3VybF9fID0gXCJmaWxlOi8vL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWVzbGludC9zcmMvbGliL21ldGEvdmVyc2lvbnMvZmxhdC50c1wiO2ltcG9ydCB0eXBlIHsgTGludGVyLCBSdWxlIH0gZnJvbSAnZXNsaW50JztcbmltcG9ydCB7IGJ1aWx0aW5SdWxlcyB9IGZyb20gJ2VzbGludC91c2UtYXQteW91ci1vd24tcmlzayc7XG5pbXBvcnQgcGF0aCBmcm9tICdub2RlOnBhdGgnO1xuaW1wb3J0IHsgcGF0aFRvRmlsZVVSTCB9IGZyb20gJ25vZGU6dXJsJztcbmltcG9ydCB7IGV4aXN0cywgZmluZE5lYXJlc3RGaWxlLCB0b0FycmF5LCB1aSB9IGZyb20gJ0Bjb2RlLXB1c2h1cC91dGlscyc7XG5pbXBvcnQgdHlwZSB7IEVTTGludFRhcmdldCB9IGZyb20gJy4uLy4uL2NvbmZpZy5qcyc7XG5pbXBvcnQgeyBqc29uSGFzaCB9IGZyb20gJy4uL2hhc2guanMnO1xuaW1wb3J0IHtcbiAgdHlwZSBSdWxlRGF0YSxcbiAgaXNSdWxlT2ZmLFxuICBvcHRpb25zRnJvbVJ1bGVFbnRyeSxcbiAgcGFyc2VSdWxlSWQsXG59IGZyb20gJy4uL3BhcnNlLmpzJztcblxuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGxvYWRSdWxlc0ZvckZsYXRDb25maWcoe1xuICBlc2xpbnRyYyxcbn06IFBpY2s8RVNMaW50VGFyZ2V0LCAnZXNsaW50cmMnPik6IFByb21pc2U8UnVsZURhdGFbXT4ge1xuICBjb25zdCBjb25maWcgPSBlc2xpbnRyY1xuICAgID8gYXdhaXQgbG9hZENvbmZpZ0J5UGF0aChlc2xpbnRyYylcbiAgICA6IGF3YWl0IGxvYWRDb25maWdCeURlZmF1bHRMb2NhdGlvbigpO1xuICBjb25zdCBjb25maWdzID0gdG9BcnJheShjb25maWcpO1xuXG4gIGNvbnN0IHJ1bGVzID0gZmluZEVuYWJsZWRSdWxlc1dpdGhPcHRpb25zKGNvbmZpZ3MpO1xuICByZXR1cm4gcnVsZXNcbiAgICAubWFwKHJ1bGUgPT4ge1xuICAgICAgY29uc3QgbWV0YSA9IGZpbmRSdWxlTWV0YShydWxlLmlkLCBjb25maWdzKTtcbiAgICAgIGlmICghbWV0YSkge1xuICAgICAgICB1aSgpLmxvZ2dlci53YXJuaW5nKGBDYW5ub3QgZmluZCBtZXRhZGF0YSBmb3IgcnVsZSAke3J1bGUuaWR9YCk7XG4gICAgICAgIHJldHVybiBudWxsO1xuICAgICAgfVxuICAgICAgcmV0dXJuIHsgLi4ucnVsZSwgbWV0YSB9O1xuICAgIH0pXG4gICAgLmZpbHRlcihleGlzdHMpO1xufVxuXG50eXBlIEZsYXRDb25maWcgPSBMaW50ZXIuQ29uZmlnIHwgTGludGVyLkNvbmZpZ1tdO1xuXG5hc3luYyBmdW5jdGlvbiBsb2FkQ29uZmlnQnlEZWZhdWx0TG9jYXRpb24oKTogUHJvbWlzZTxGbGF0Q29uZmlnPiB7XG4gIGNvbnN0IGZsYXRDb25maWdGaWxlTmFtZXMgPSBbXG4gICAgJ2VzbGludC5jb25maWcuanMnLFxuICAgICdlc2xpbnQuY29uZmlnLm1qcycsXG4gICAgJ2VzbGludC5jb25maWcuY2pzJyxcbiAgXTtcbiAgY29uc3QgY29uZmlnUGF0aCA9IGF3YWl0IGZpbmROZWFyZXN0RmlsZShmbGF0Q29uZmlnRmlsZU5hbWVzKTtcbiAgaWYgKGNvbmZpZ1BhdGgpIHtcbiAgICByZXR1cm4gbG9hZENvbmZpZ0J5UGF0aChjb25maWdQYXRoKTtcbiAgfVxuICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgW1xuICAgICAgYEVTTGludCBjb25maWcgZmlsZSBub3QgZm91bmQgLSBleHBlY3RlZCAke2ZsYXRDb25maWdGaWxlTmFtZXMuam9pbignLycpfSBpbiAke3Byb2Nlc3MuY3dkKCl9IG9yIHNvbWUgcGFyZW50IGRpcmVjdG9yeWAsXG4gICAgICAnSWYgeW91ciBFU0xpbnQgY29uZmlnIGlzIGluIGEgbm9uLXN0YW5kYXJkIGxvY2F0aW9uLCB1c2UgdGhlIGBlc2xpbnRyY2AgcGFyYW1ldGVyIHRvIHNwZWNpZnkgdGhlIHBhdGguJyxcbiAgICBdLmpvaW4oJ1xcbicpLFxuICApO1xufVxuXG5hc3luYyBmdW5jdGlvbiBsb2FkQ29uZmlnQnlQYXRoKGNvbmZpZ1BhdGg6IHN0cmluZyk6IFByb21pc2U8RmxhdENvbmZpZz4ge1xuICBjb25zdCBhYnNvbHV0ZVBhdGggPSBwYXRoLmlzQWJzb2x1dGUoY29uZmlnUGF0aClcbiAgICA/IGNvbmZpZ1BhdGhcbiAgICA6IHBhdGguam9pbihwcm9jZXNzLmN3ZCgpLCBjb25maWdQYXRoKTtcbiAgY29uc3QgdXJsID0gcGF0aFRvRmlsZVVSTChhYnNvbHV0ZVBhdGgpLnRvU3RyaW5nKCk7XG4gIGNvbnN0IG1vZCA9IChhd2FpdCBpbXBvcnQodXJsKSkgYXMgRmxhdENvbmZpZyB8IHsgZGVmYXVsdDogRmxhdENvbmZpZyB9O1xuICByZXR1cm4gJ2RlZmF1bHQnIGluIG1vZCA/IG1vZC5kZWZhdWx0IDogbW9kO1xufVxuXG5mdW5jdGlvbiBmaW5kRW5hYmxlZFJ1bGVzV2l0aE9wdGlvbnMoXG4gIGNvbmZpZ3M6IExpbnRlci5Db25maWdbXSxcbik6IE9taXQ8UnVsZURhdGEsICdtZXRhJz5bXSB7XG4gIGNvbnN0IGVuYWJsZWRSdWxlcyA9IGNvbmZpZ3NcbiAgICAuZmxhdE1hcCgoeyBydWxlcyB9KSA9PiBPYmplY3QuZW50cmllcyhydWxlcyA/PyB7fSkpXG4gICAgLmZpbHRlcigoWywgZW50cnldKSA9PiBlbnRyeSAhPSBudWxsICYmICFpc1J1bGVPZmYoZW50cnkpKVxuICAgIC5tYXAoKFtpZCwgZW50cnldKSA9PiAoe1xuICAgICAgaWQsXG4gICAgICBvcHRpb25zOiBlbnRyeSA/IG9wdGlvbnNGcm9tUnVsZUVudHJ5KGVudHJ5KSA6IFtdLFxuICAgIH0pKTtcbiAgY29uc3QgdW5pcXVlUnVsZXNNYXAgPSBuZXcgTWFwKFxuICAgIGVuYWJsZWRSdWxlcy5tYXAoKHsgaWQsIG9wdGlvbnMgfSkgPT4gW1xuICAgICAgYCR7aWR9Ojoke2pzb25IYXNoKG9wdGlvbnMpfWAsXG4gICAgICB7IGlkLCBvcHRpb25zIH0sXG4gICAgXSksXG4gICk7XG4gIHJldHVybiBbLi4udW5pcXVlUnVsZXNNYXAudmFsdWVzKCldO1xufVxuXG5mdW5jdGlvbiBmaW5kUnVsZU1ldGEoXG4gIHJ1bGVJZDogc3RyaW5nLFxuICBjb25maWdzOiBMaW50ZXIuQ29uZmlnW10sXG4pOiBSdWxlLlJ1bGVNZXRhRGF0YSB8IHVuZGVmaW5lZCB7XG4gIGNvbnN0IHsgcGx1Z2luLCBuYW1lIH0gPSBwYXJzZVJ1bGVJZChydWxlSWQpO1xuICBpZiAoIXBsdWdpbikge1xuICAgIHJldHVybiBmaW5kQnVpbHRpblJ1bGVNZXRhKG5hbWUpO1xuICB9XG4gIHJldHVybiBmaW5kUGx1Z2luUnVsZU1ldGEocGx1Z2luLCBuYW1lLCBjb25maWdzKTtcbn1cblxuZnVuY3Rpb24gZmluZEJ1aWx0aW5SdWxlTWV0YShuYW1lOiBzdHJpbmcpOiBSdWxlLlJ1bGVNZXRhRGF0YSB8IHVuZGVmaW5lZCB7XG4gIGNvbnN0IHJ1bGUgPSBidWlsdGluUnVsZXMuZ2V0KG5hbWUpO1xuICByZXR1cm4gcnVsZT8ubWV0YTtcbn1cblxuZnVuY3Rpb24gZmluZFBsdWdpblJ1bGVNZXRhKFxuICBwbHVnaW46IHN0cmluZyxcbiAgbmFtZTogc3RyaW5nLFxuICBjb25maWdzOiBMaW50ZXIuQ29uZmlnW10sXG4pOiBSdWxlLlJ1bGVNZXRhRGF0YSB8IHVuZGVmaW5lZCB7XG4gIGNvbnN0IGNvbmZpZyA9IGNvbmZpZ3MuZmluZCgoeyBwbHVnaW5zID0ge30gfSkgPT4gcGx1Z2luIGluIHBsdWdpbnMpO1xuICBjb25zdCBydWxlID0gY29uZmlnPy5wbHVnaW5zPy5bcGx1Z2luXT8ucnVsZXM/LltuYW1lXTtcblxuICBpZiAodHlwZW9mIHJ1bGUgPT09ICdmdW5jdGlvbicpIHtcbiAgICB1aSgpLmxvZ2dlci53YXJuaW5nKFxuICAgICAgYENhbm5vdCBwYXJzZSBtZXRhZGF0YSBmb3IgcnVsZSAke3BsdWdpbn0vJHtuYW1lfSwgcGx1Z2luIHJlZ2lzdGVycyBpdCBhcyBhIGZ1bmN0aW9uYCxcbiAgICApO1xuICAgIHJldHVybiB1bmRlZmluZWQ7XG4gIH1cblxuICByZXR1cm4gcnVsZT8ubWV0YTtcbn1cbiIsICJjb25zdCBfX2luamVjdGVkX2ZpbGVuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1lc2xpbnQvc3JjL2xpYi9tZXRhL3ZlcnNpb25zL2xlZ2FjeS50c1wiO2NvbnN0IF9faW5qZWN0ZWRfZGlybmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tZXNsaW50L3NyYy9saWIvbWV0YS92ZXJzaW9uc1wiO2NvbnN0IF9faW5qZWN0ZWRfaW1wb3J0X21ldGFfdXJsX18gPSBcImZpbGU6Ly8vaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tZXNsaW50L3NyYy9saWIvbWV0YS92ZXJzaW9ucy9sZWdhY3kudHNcIjtpbXBvcnQgdHlwZSB7IEVTTGludCwgTGludGVyIH0gZnJvbSAnZXNsaW50JztcbmltcG9ydCB7IGRpc3RpbmN0LCBleGlzdHMsIHRvQXJyYXksIHVpIH0gZnJvbSAnQGNvZGUtcHVzaHVwL3V0aWxzJztcbmltcG9ydCB0eXBlIHsgRVNMaW50VGFyZ2V0IH0gZnJvbSAnLi4vLi4vY29uZmlnLmpzJztcbmltcG9ydCB7IHNldHVwRVNMaW50IH0gZnJvbSAnLi4vLi4vc2V0dXAuanMnO1xuaW1wb3J0IHsgdHlwZSBSdWxlRGF0YSwgaXNSdWxlT2ZmLCBvcHRpb25zRnJvbVJ1bGVFbnRyeSB9IGZyb20gJy4uL3BhcnNlLmpzJztcblxuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGxvYWRSdWxlc0ZvckxlZ2FjeUNvbmZpZyh7XG4gIGVzbGludHJjLFxuICBwYXR0ZXJucyxcbn06IEVTTGludFRhcmdldCk6IFByb21pc2U8UnVsZURhdGFbXT4ge1xuICBjb25zdCBlc2xpbnQgPSBhd2FpdCBzZXR1cEVTTGludChlc2xpbnRyYyk7XG5cbiAgY29uc3QgY29uZmlncyA9IGF3YWl0IHRvQXJyYXkocGF0dGVybnMpLnJlZHVjZShcbiAgICBhc3luYyAoYWNjLCBwYXR0ZXJuKSA9PiBbXG4gICAgICAuLi4oYXdhaXQgYWNjKSxcbiAgICAgIChhd2FpdCBlc2xpbnQuY2FsY3VsYXRlQ29uZmlnRm9yRmlsZShwYXR0ZXJuKSkgYXMgTGludGVyLkxlZ2FjeUNvbmZpZyxcbiAgICBdLFxuICAgIFByb21pc2UucmVzb2x2ZTxMaW50ZXIuTGVnYWN5Q29uZmlnW10+KFtdKSxcbiAgKTtcblxuICBjb25zdCBydWxlc0lkcyA9IGRpc3RpbmN0KFxuICAgIGNvbmZpZ3MuZmxhdE1hcChjb25maWcgPT4gT2JqZWN0LmtleXMoY29uZmlnLnJ1bGVzID8/IHt9KSksXG4gICk7XG4gIGNvbnN0IHJ1bGVzTWV0YSA9IGVzbGludC5nZXRSdWxlc01ldGFGb3JSZXN1bHRzKFtcbiAgICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgQHR5cGVzY3JpcHQtZXNsaW50L2NvbnNpc3RlbnQtdHlwZS1hc3NlcnRpb25zXG4gICAge1xuICAgICAgbWVzc2FnZXM6IHJ1bGVzSWRzLm1hcChydWxlSWQgPT4gKHsgcnVsZUlkIH0pKSxcbiAgICAgIHN1cHByZXNzZWRNZXNzYWdlczogW10gYXMgTGludGVyLlN1cHByZXNzZWRMaW50TWVzc2FnZVtdLFxuICAgIH0gYXMgRVNMaW50LkxpbnRSZXN1bHQsXG4gIF0pO1xuXG4gIHJldHVybiBjb25maWdzXG4gICAgLmZsYXRNYXAoY29uZmlnID0+IE9iamVjdC5lbnRyaWVzKGNvbmZpZy5ydWxlcyA/PyB7fSkpXG4gICAgLm1hcCgoW2lkLCBlbnRyeV0pOiBSdWxlRGF0YSB8IG51bGwgPT4ge1xuICAgICAgaWYgKGVudHJ5ID09IG51bGwgfHwgaXNSdWxlT2ZmKGVudHJ5KSkge1xuICAgICAgICByZXR1cm4gbnVsbDtcbiAgICAgIH1cbiAgICAgIGNvbnN0IHJ1bGVNZXRhID0gcnVsZXNNZXRhW2lkXTtcbiAgICAgIGlmICghcnVsZU1ldGEpIHtcbiAgICAgICAgdWkoKS5sb2dnZXIud2FybmluZyhgTWV0YWRhdGEgbm90IGZvdW5kIGZvciBFU0xpbnQgcnVsZSAke2lkfWApO1xuICAgICAgICByZXR1cm4gbnVsbDtcbiAgICAgIH1cbiAgICAgIC8vIGlnbm9yaW5nIG1ldGEuZGVmYXVsdE9wdGlvbnMgdG8gbWF0Y2ggbGVnYWN5IGNvbmZpZyBoYW5kbGluZyBpbiBjYWxjdWxhdGVDb25maWdGb3JGaWxlXG4gICAgICBjb25zdCB7IGRlZmF1bHRPcHRpb25zOiBfLCAuLi5tZXRhIH0gPSBydWxlTWV0YTtcbiAgICAgIGNvbnN0IG9wdGlvbnMgPSBvcHRpb25zRnJvbVJ1bGVFbnRyeShlbnRyeSk7XG4gICAgICByZXR1cm4geyBpZCwgbWV0YSwgb3B0aW9ucyB9O1xuICAgIH0pXG4gICAgLmZpbHRlcihleGlzdHMpO1xufVxuIiwgImNvbnN0IF9faW5qZWN0ZWRfZmlsZW5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWVzbGludC9zcmMvbGliL3NldHVwLnRzXCI7Y29uc3QgX19pbmplY3RlZF9kaXJuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1lc2xpbnQvc3JjL2xpYlwiO2NvbnN0IF9faW5qZWN0ZWRfaW1wb3J0X21ldGFfdXJsX18gPSBcImZpbGU6Ly8vaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tZXNsaW50L3NyYy9saWIvc2V0dXAudHNcIjtpbXBvcnQgeyBFU0xpbnQgfSBmcm9tICdlc2xpbnQnO1xuaW1wb3J0IHR5cGUgeyBFU0xpbnRUYXJnZXQgfSBmcm9tICcuL2NvbmZpZy5qcyc7XG5cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBzZXR1cEVTTGludChlc2xpbnRyYzogRVNMaW50VGFyZ2V0Wydlc2xpbnRyYyddKSB7XG4gIGNvbnN0IGVzbGludENvbnN0cnVjdG9yID0gYXdhaXQgbG9hZEVTTGludCgpO1xuICByZXR1cm4gbmV3IGVzbGludENvbnN0cnVjdG9yKHtcbiAgICBvdmVycmlkZUNvbmZpZ0ZpbGU6IGVzbGludHJjLFxuICAgIGVycm9yT25Vbm1hdGNoZWRQYXR0ZXJuOiBmYWxzZSxcbiAgfSk7XG59XG5cbmFzeW5jIGZ1bmN0aW9uIGxvYWRFU0xpbnQoKSB7XG4gIGNvbnN0IGVzbGludCA9IGF3YWl0IGltcG9ydCgnZXNsaW50Jyk7XG4gIC8vIGxvYWRFU0xpbnQgYWRkZWQgdG8gcHVibGljIEFQSSBpbiB2OSwgc2VsZWN0cyBFU0xpbnQgb3IgTGVnYWN5RVNMaW50IGJhc2VkIG9uIGVudmlyb25tZW50XG4gIGlmICgnbG9hZEVTTGludCcgaW4gZXNsaW50ICYmIHR5cGVvZiBlc2xpbnQubG9hZEVTTGludCA9PT0gJ2Z1bmN0aW9uJykge1xuICAgIHJldHVybiAoYXdhaXQgZXNsaW50LmxvYWRFU0xpbnQoKSkgYXMgdHlwZW9mIEVTTGludDtcbiAgfVxuICByZXR1cm4gRVNMaW50O1xufVxuIiwgImNvbnN0IF9faW5qZWN0ZWRfZmlsZW5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWVzbGludC9zcmMvbGliL21ldGEvdmVyc2lvbnMvZGV0ZWN0LnRzXCI7Y29uc3QgX19pbmplY3RlZF9kaXJuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1lc2xpbnQvc3JjL2xpYi9tZXRhL3ZlcnNpb25zXCI7Y29uc3QgX19pbmplY3RlZF9pbXBvcnRfbWV0YV91cmxfXyA9IFwiZmlsZTovLy9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1lc2xpbnQvc3JjL2xpYi9tZXRhL3ZlcnNpb25zL2RldGVjdC50c1wiO2ltcG9ydCB7IEVTTGludCB9IGZyb20gJ2VzbGludCc7XG5pbXBvcnQgeyBmaWxlRXhpc3RzIH0gZnJvbSAnQGNvZGUtcHVzaHVwL3V0aWxzJztcbmltcG9ydCB0eXBlIHsgQ29uZmlnRm9ybWF0IH0gZnJvbSAnLi9mb3JtYXRzLmpzJztcblxuLy8gcmVsZXZhbnQgRVNMaW50IGRvY3M6XG4vLyAtIGh0dHBzOi8vZXNsaW50Lm9yZy9kb2NzL2xhdGVzdC91c2UvY29uZmlndXJlL2NvbmZpZ3VyYXRpb24tZmlsZXNcbi8vIC0gaHR0cHM6Ly9lc2xpbnQub3JnL2RvY3MvbGF0ZXN0L3VzZS9jb25maWd1cmUvY29uZmlndXJhdGlvbi1maWxlcy1kZXByZWNhdGVkXG4vLyAtIGh0dHBzOi8vZXNsaW50Lm9yZy9kb2NzL3Y4LngvdXNlL2NvbmZpZ3VyZS9jb25maWd1cmF0aW9uLWZpbGVzLW5ld1xuXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gZGV0ZWN0Q29uZmlnVmVyc2lvbigpOiBQcm9taXNlPENvbmZpZ0Zvcm1hdD4ge1xuICBpZiAocHJvY2Vzcy5lbnZbJ0VTTElOVF9VU0VfRkxBVF9DT05GSUcnXSA9PT0gJ3RydWUnKSB7XG4gICAgcmV0dXJuICdmbGF0JztcbiAgfVxuICBpZiAocHJvY2Vzcy5lbnZbJ0VTTElOVF9VU0VfRkxBVF9DT05GSUcnXSA9PT0gJ2ZhbHNlJykge1xuICAgIHJldHVybiAnbGVnYWN5JztcbiAgfVxuICBpZiAoRVNMaW50LnZlcnNpb24uc3RhcnRzV2l0aCgnOC4nKSkge1xuICAgIGlmIChhd2FpdCBmaWxlRXhpc3RzKCdlc2xpbnQuY29uZmlnLmpzJykpIHtcbiAgICAgIHJldHVybiAnZmxhdCc7XG4gICAgfVxuICAgIHJldHVybiAnbGVnYWN5JztcbiAgfVxuICByZXR1cm4gJ2ZsYXQnO1xufVxuIiwgImNvbnN0IF9faW5qZWN0ZWRfZmlsZW5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWVzbGludC9zcmMvbGliL21ldGEvdHJhbnNmb3JtLnRzXCI7Y29uc3QgX19pbmplY3RlZF9kaXJuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1lc2xpbnQvc3JjL2xpYi9tZXRhXCI7Y29uc3QgX19pbmplY3RlZF9pbXBvcnRfbWV0YV91cmxfXyA9IFwiZmlsZTovLy9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1lc2xpbnQvc3JjL2xpYi9tZXRhL3RyYW5zZm9ybS50c1wiO2ltcG9ydCB0eXBlIHsgQXVkaXQgfSBmcm9tICdAY29kZS1wdXNodXAvbW9kZWxzJztcbmltcG9ydCB7IHRydW5jYXRlRGVzY3JpcHRpb24sIHRydW5jYXRlVGl0bGUgfSBmcm9tICdAY29kZS1wdXNodXAvdXRpbHMnO1xuaW1wb3J0IHsgcnVsZVRvU2x1ZyB9IGZyb20gJy4vaGFzaC5qcyc7XG5pbXBvcnQgdHlwZSB7IFJ1bGVEYXRhIH0gZnJvbSAnLi9wYXJzZS5qcyc7XG5cbmV4cG9ydCBmdW5jdGlvbiBydWxlVG9BdWRpdChydWxlOiBSdWxlRGF0YSk6IEF1ZGl0IHtcbiAgY29uc3QgbmFtZSA9IHJ1bGUuaWQuc3BsaXQoJy8nKS5hdCgtMSkgPz8gcnVsZS5pZDtcbiAgY29uc3QgcGx1Z2luID1cbiAgICBuYW1lID09PSBydWxlLmlkID8gbnVsbCA6IHJ1bGUuaWQuc2xpY2UoMCwgcnVsZS5pZC5sYXN0SW5kZXhPZignLycpKTtcbiAgY29uc3QgcGx1Z2luQ29udGV4dCA9IHBsdWdpbiA/IGAsIGZyb20gXyR7cGx1Z2lufV8gcGx1Z2luYCA6ICcnO1xuXG4gIGNvbnN0IGxpbmVzOiBzdHJpbmdbXSA9IFtcbiAgICBgRVNMaW50IHJ1bGUgKioke25hbWV9Kioke3BsdWdpbkNvbnRleHR9LmAsXG4gICAgLi4uKHJ1bGUub3B0aW9ucz8ubGVuZ3RoID8gWydDdXN0b20gb3B0aW9uczonXSA6IFtdKSxcbiAgICAuLi4ocnVsZS5vcHRpb25zPy5tYXAob3B0aW9uID0+XG4gICAgICBbJ2BgYGpzb24nLCBKU09OLnN0cmluZ2lmeShvcHRpb24sIG51bGwsIDIpLCAnYGBgJ10uam9pbignXFxuJyksXG4gICAgKSA/PyBbXSksXG4gIF07XG5cbiAgcmV0dXJuIHtcbiAgICBzbHVnOiBydWxlVG9TbHVnKHJ1bGUpLFxuICAgIHRpdGxlOiB0cnVuY2F0ZVRpdGxlKHJ1bGUubWV0YS5kb2NzPy5kZXNjcmlwdGlvbiA/PyBuYW1lKSxcbiAgICBkZXNjcmlwdGlvbjogdHJ1bmNhdGVEZXNjcmlwdGlvbihsaW5lcy5qb2luKCdcXG5cXG4nKSksXG4gICAgLi4uKHJ1bGUubWV0YS5kb2NzPy51cmwgJiYge1xuICAgICAgZG9jc1VybDogcnVsZS5tZXRhLmRvY3MudXJsLFxuICAgIH0pLFxuICB9O1xufVxuIiwgImNvbnN0IF9faW5qZWN0ZWRfZmlsZW5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWVzbGludC9zcmMvbGliL3J1bm5lci9pbmRleC50c1wiO2NvbnN0IF9faW5qZWN0ZWRfZGlybmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tZXNsaW50L3NyYy9saWIvcnVubmVyXCI7Y29uc3QgX19pbmplY3RlZF9pbXBvcnRfbWV0YV91cmxfXyA9IFwiZmlsZTovLy9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1lc2xpbnQvc3JjL2xpYi9ydW5uZXIvaW5kZXgudHNcIjtpbXBvcnQgeyB3cml0ZUZpbGUgfSBmcm9tICdub2RlOmZzL3Byb21pc2VzJztcbmltcG9ydCBwYXRoIGZyb20gJ25vZGU6cGF0aCc7XG5pbXBvcnQgdHlwZSB7IEF1ZGl0LCBBdWRpdE91dHB1dCwgUnVubmVyQ29uZmlnIH0gZnJvbSAnQGNvZGUtcHVzaHVwL21vZGVscyc7XG5pbXBvcnQge1xuICBlbnN1cmVEaXJlY3RvcnlFeGlzdHMsXG4gIGZpbGVQYXRoVG9DbGlBcmcsXG4gIHBsdWdpbldvcmtEaXIsXG4gIHJlYWRKc29uRmlsZSxcbn0gZnJvbSAnQGNvZGUtcHVzaHVwL3V0aWxzJztcbmltcG9ydCB0eXBlIHsgRVNMaW50UGx1Z2luUnVubmVyQ29uZmlnLCBFU0xpbnRUYXJnZXQgfSBmcm9tICcuLi9jb25maWcuanMnO1xuaW1wb3J0IHsgbGludCB9IGZyb20gJy4vbGludC5qcyc7XG5pbXBvcnQgeyBsaW50UmVzdWx0c1RvQXVkaXRzLCBtZXJnZUxpbnRlck91dHB1dHMgfSBmcm9tICcuL3RyYW5zZm9ybS5qcyc7XG5pbXBvcnQgdHlwZSB7IExpbnRlck91dHB1dCB9IGZyb20gJy4vdHlwZXMuanMnO1xuXG5leHBvcnQgY29uc3QgV09SS0RJUiA9IHBsdWdpbldvcmtEaXIoJ2VzbGludCcpO1xuZXhwb3J0IGNvbnN0IFJVTk5FUl9PVVRQVVRfUEFUSCA9IHBhdGguam9pbihXT1JLRElSLCAncnVubmVyLW91dHB1dC5qc29uJyk7XG5leHBvcnQgY29uc3QgUExVR0lOX0NPTkZJR19QQVRIID0gcGF0aC5qb2luKFxuICBwcm9jZXNzLmN3ZCgpLFxuICBXT1JLRElSLFxuICAncGx1Z2luLWNvbmZpZy5qc29uJyxcbik7XG5cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBleGVjdXRlUnVubmVyKCk6IFByb21pc2U8dm9pZD4ge1xuICBjb25zdCB7IHNsdWdzLCB0YXJnZXRzIH0gPVxuICAgIGF3YWl0IHJlYWRKc29uRmlsZTxFU0xpbnRQbHVnaW5SdW5uZXJDb25maWc+KFBMVUdJTl9DT05GSUdfUEFUSCk7XG5cbiAgY29uc3QgbGludGVyT3V0cHV0cyA9IGF3YWl0IHRhcmdldHMucmVkdWNlKFxuICAgIGFzeW5jIChhY2MsIHRhcmdldCkgPT4gWy4uLihhd2FpdCBhY2MpLCBhd2FpdCBsaW50KHRhcmdldCldLFxuICAgIFByb21pc2UucmVzb2x2ZTxMaW50ZXJPdXRwdXRbXT4oW10pLFxuICApO1xuICBjb25zdCBsaW50UmVzdWx0cyA9IG1lcmdlTGludGVyT3V0cHV0cyhsaW50ZXJPdXRwdXRzKTtcbiAgY29uc3QgZmFpbGVkQXVkaXRzID0gbGludFJlc3VsdHNUb0F1ZGl0cyhsaW50UmVzdWx0cyk7XG5cbiAgY29uc3QgYXVkaXRzID0gc2x1Z3MubWFwKFxuICAgIChzbHVnKTogQXVkaXRPdXRwdXQgPT5cbiAgICAgIGZhaWxlZEF1ZGl0cy5maW5kKGF1ZGl0ID0+IGF1ZGl0LnNsdWcgPT09IHNsdWcpID8/IHtcbiAgICAgICAgc2x1ZyxcbiAgICAgICAgc2NvcmU6IDEsXG4gICAgICAgIHZhbHVlOiAwLFxuICAgICAgICBkaXNwbGF5VmFsdWU6ICdwYXNzZWQnLFxuICAgICAgICBkZXRhaWxzOiB7IGlzc3VlczogW10gfSxcbiAgICAgIH0sXG4gICk7XG5cbiAgYXdhaXQgZW5zdXJlRGlyZWN0b3J5RXhpc3RzKHBhdGguZGlybmFtZShSVU5ORVJfT1VUUFVUX1BBVEgpKTtcbiAgYXdhaXQgd3JpdGVGaWxlKFJVTk5FUl9PVVRQVVRfUEFUSCwgSlNPTi5zdHJpbmdpZnkoYXVkaXRzKSk7XG59XG5cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBjcmVhdGVSdW5uZXJDb25maWcoXG4gIHNjcmlwdFBhdGg6IHN0cmluZyxcbiAgYXVkaXRzOiBBdWRpdFtdLFxuICB0YXJnZXRzOiBFU0xpbnRUYXJnZXRbXSxcbik6IFByb21pc2U8UnVubmVyQ29uZmlnPiB7XG4gIGNvbnN0IGNvbmZpZzogRVNMaW50UGx1Z2luUnVubmVyQ29uZmlnID0ge1xuICAgIHRhcmdldHMsXG4gICAgc2x1Z3M6IGF1ZGl0cy5tYXAoYXVkaXQgPT4gYXVkaXQuc2x1ZyksXG4gIH07XG4gIGF3YWl0IGVuc3VyZURpcmVjdG9yeUV4aXN0cyhwYXRoLmRpcm5hbWUoUExVR0lOX0NPTkZJR19QQVRIKSk7XG4gIGF3YWl0IHdyaXRlRmlsZShQTFVHSU5fQ09ORklHX1BBVEgsIEpTT04uc3RyaW5naWZ5KGNvbmZpZykpO1xuXG4gIHJldHVybiB7XG4gICAgY29tbWFuZDogJ25vZGUnLFxuICAgIGFyZ3M6IFtmaWxlUGF0aFRvQ2xpQXJnKHNjcmlwdFBhdGgpXSxcbiAgICBvdXRwdXRGaWxlOiBSVU5ORVJfT1VUUFVUX1BBVEgsXG4gIH07XG59XG4iLCAiY29uc3QgX19pbmplY3RlZF9maWxlbmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tZXNsaW50L3NyYy9saWIvcnVubmVyL2xpbnQudHNcIjtjb25zdCBfX2luamVjdGVkX2Rpcm5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWVzbGludC9zcmMvbGliL3J1bm5lclwiO2NvbnN0IF9faW5qZWN0ZWRfaW1wb3J0X21ldGFfdXJsX18gPSBcImZpbGU6Ly8vaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tZXNsaW50L3NyYy9saWIvcnVubmVyL2xpbnQudHNcIjtpbXBvcnQgdHlwZSB7IEVTTGludCwgTGludGVyIH0gZnJvbSAnZXNsaW50JztcbmltcG9ydCB7IHBsYXRmb3JtIH0gZnJvbSAnbm9kZTpvcyc7XG5pbXBvcnQge1xuICBkaXN0aW5jdCxcbiAgZXhlY3V0ZVByb2Nlc3MsXG4gIGZpbGVQYXRoVG9DbGlBcmcsXG4gIHRvQXJyYXksXG59IGZyb20gJ0Bjb2RlLXB1c2h1cC91dGlscyc7XG5pbXBvcnQgdHlwZSB7IEVTTGludFRhcmdldCB9IGZyb20gJy4uL2NvbmZpZy5qcyc7XG5pbXBvcnQgeyBzZXR1cEVTTGludCB9IGZyb20gJy4uL3NldHVwLmpzJztcbmltcG9ydCB0eXBlIHsgTGludGVyT3V0cHV0LCBSdWxlT3B0aW9uc1BlckZpbGUgfSBmcm9tICcuL3R5cGVzLmpzJztcblxuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGxpbnQoe1xuICBlc2xpbnRyYyxcbiAgcGF0dGVybnMsXG59OiBFU0xpbnRUYXJnZXQpOiBQcm9taXNlPExpbnRlck91dHB1dD4ge1xuICBjb25zdCByZXN1bHRzID0gYXdhaXQgZXhlY3V0ZUxpbnQoeyBlc2xpbnRyYywgcGF0dGVybnMgfSk7XG4gIGNvbnN0IGVzbGludCA9IGF3YWl0IHNldHVwRVNMaW50KGVzbGludHJjKTtcbiAgY29uc3QgcnVsZU9wdGlvbnNQZXJGaWxlID0gYXdhaXQgbG9hZFJ1bGVPcHRpb25zUGVyRmlsZShlc2xpbnQsIHJlc3VsdHMpO1xuICByZXR1cm4geyByZXN1bHRzLCBydWxlT3B0aW9uc1BlckZpbGUgfTtcbn1cblxuYXN5bmMgZnVuY3Rpb24gZXhlY3V0ZUxpbnQoe1xuICBlc2xpbnRyYyxcbiAgcGF0dGVybnMsXG59OiBFU0xpbnRUYXJnZXQpOiBQcm9taXNlPEVTTGludC5MaW50UmVzdWx0W10+IHtcbiAgLy8gcnVubmluZyBhcyBDTEkgYmVjYXVzZSBFU0xpbnQjbGludEZpbGVzKCkgcnVucyBvdXQgb2YgbWVtb3J5XG4gIGNvbnN0IHsgc3Rkb3V0IH0gPSBhd2FpdCBleGVjdXRlUHJvY2Vzcyh7XG4gICAgY29tbWFuZDogJ25weCcsXG4gICAgYXJnczogW1xuICAgICAgJ2VzbGludCcsXG4gICAgICAuLi4oZXNsaW50cmMgPyBbYC0tY29uZmlnPSR7ZmlsZVBhdGhUb0NsaUFyZyhlc2xpbnRyYyl9YF0gOiBbXSksXG4gICAgICAuLi4odHlwZW9mIGVzbGludHJjID09PSAnb2JqZWN0JyA/IFsnLS1uby1lc2xpbnRyYyddIDogW10pLFxuICAgICAgJy0tbm8tZXJyb3Itb24tdW5tYXRjaGVkLXBhdHRlcm4nLFxuICAgICAgJy0tZm9ybWF0PWpzb24nLFxuICAgICAgLi4udG9BcnJheShwYXR0ZXJucykubWFwKHBhdHRlcm4gPT5cbiAgICAgICAgLy8gZ2xvYnMgbmVlZCB0byBiZSBlc2NhcGVkIG9uIFVuaXhcbiAgICAgICAgcGxhdGZvcm0oKSA9PT0gJ3dpbjMyJyA/IHBhdHRlcm4gOiBgJyR7cGF0dGVybn0nYCxcbiAgICAgICksXG4gICAgXSxcbiAgICBpZ25vcmVFeGl0Q29kZTogdHJ1ZSxcbiAgICBjd2Q6IHByb2Nlc3MuY3dkKCksXG4gIH0pO1xuXG4gIHJldHVybiBKU09OLnBhcnNlKHN0ZG91dCkgYXMgRVNMaW50LkxpbnRSZXN1bHRbXTtcbn1cblxuZnVuY3Rpb24gbG9hZFJ1bGVPcHRpb25zUGVyRmlsZShcbiAgZXNsaW50OiBFU0xpbnQsXG4gIHJlc3VsdHM6IEVTTGludC5MaW50UmVzdWx0W10sXG4pOiBQcm9taXNlPFJ1bGVPcHRpb25zUGVyRmlsZT4ge1xuICByZXR1cm4gcmVzdWx0cy5yZWR1Y2UoYXN5bmMgKGFjYywgeyBmaWxlUGF0aCwgbWVzc2FnZXMgfSkgPT4ge1xuICAgIGNvbnN0IGZpbGVzTWFwID0gYXdhaXQgYWNjO1xuICAgIGNvbnN0IGNvbmZpZyA9IChhd2FpdCBlc2xpbnQuY2FsY3VsYXRlQ29uZmlnRm9yRmlsZShcbiAgICAgIGZpbGVQYXRoLFxuICAgICkpIGFzIExpbnRlci5Db25maWc7XG4gICAgY29uc3QgcnVsZUlkcyA9IGRpc3RpbmN0KFxuICAgICAgbWVzc2FnZXNcbiAgICAgICAgLm1hcCgoeyBydWxlSWQgfSkgPT4gcnVsZUlkKVxuICAgICAgICAuZmlsdGVyKChydWxlSWQpOiBydWxlSWQgaXMgc3RyaW5nID0+IHJ1bGVJZCAhPSBudWxsKSxcbiAgICApO1xuICAgIGNvbnN0IHJ1bGVzTWFwID0gT2JqZWN0LmZyb21FbnRyaWVzKFxuICAgICAgcnVsZUlkcy5tYXAocnVsZUlkID0+IFtcbiAgICAgICAgcnVsZUlkLFxuICAgICAgICB0b0FycmF5KGNvbmZpZy5ydWxlcz8uW3J1bGVJZF0gPz8gW10pLnNsaWNlKDEpLFxuICAgICAgXSksXG4gICAgKTtcbiAgICByZXR1cm4ge1xuICAgICAgLi4uZmlsZXNNYXAsXG4gICAgICBbZmlsZVBhdGhdOiB7XG4gICAgICAgIC4uLmZpbGVzTWFwW2ZpbGVQYXRoXSxcbiAgICAgICAgLi4ucnVsZXNNYXAsXG4gICAgICB9LFxuICAgIH07XG4gIH0sIFByb21pc2UucmVzb2x2ZTxSdWxlT3B0aW9uc1BlckZpbGU+KHt9KSk7XG59XG4iLCAiY29uc3QgX19pbmplY3RlZF9maWxlbmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tZXNsaW50L3NyYy9saWIvcnVubmVyL3RyYW5zZm9ybS50c1wiO2NvbnN0IF9faW5qZWN0ZWRfZGlybmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tZXNsaW50L3NyYy9saWIvcnVubmVyXCI7Y29uc3QgX19pbmplY3RlZF9pbXBvcnRfbWV0YV91cmxfXyA9IFwiZmlsZTovLy9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1lc2xpbnQvc3JjL2xpYi9ydW5uZXIvdHJhbnNmb3JtLnRzXCI7aW1wb3J0IHR5cGUgeyBMaW50ZXIgfSBmcm9tICdlc2xpbnQnO1xuaW1wb3J0IHR5cGUgeyBBdWRpdE91dHB1dCwgSXNzdWUsIElzc3VlU2V2ZXJpdHkgfSBmcm9tICdAY29kZS1wdXNodXAvbW9kZWxzJztcbmltcG9ydCB7XG4gIGNvbXBhcmVJc3N1ZVNldmVyaXR5LFxuICBjb3VudE9jY3VycmVuY2VzLFxuICBvYmplY3RUb0VudHJpZXMsXG4gIHBsdXJhbGl6ZVRva2VuLFxuICB0cnVuY2F0ZUlzc3VlTWVzc2FnZSxcbiAgdWksXG59IGZyb20gJ0Bjb2RlLXB1c2h1cC91dGlscyc7XG5pbXBvcnQgeyBydWxlSWRUb1NsdWcgfSBmcm9tICcuLi9tZXRhL2luZGV4LmpzJztcbmltcG9ydCB0eXBlIHsgTGludGVyT3V0cHV0IH0gZnJvbSAnLi90eXBlcy5qcyc7XG5cbnR5cGUgTGludElzc3VlID0gTGludGVyLkxpbnRNZXNzYWdlICYge1xuICBmaWxlUGF0aDogc3RyaW5nO1xufTtcblxuZXhwb3J0IGZ1bmN0aW9uIG1lcmdlTGludGVyT3V0cHV0cyhvdXRwdXRzOiBMaW50ZXJPdXRwdXRbXSk6IExpbnRlck91dHB1dCB7XG4gIHJldHVybiBvdXRwdXRzLnJlZHVjZTxMaW50ZXJPdXRwdXQ+KFxuICAgIChhY2MsIHsgcmVzdWx0cywgcnVsZU9wdGlvbnNQZXJGaWxlIH0pID0+ICh7XG4gICAgICByZXN1bHRzOiBbLi4uYWNjLnJlc3VsdHMsIC4uLnJlc3VsdHNdLFxuICAgICAgcnVsZU9wdGlvbnNQZXJGaWxlOiB7IC4uLmFjYy5ydWxlT3B0aW9uc1BlckZpbGUsIC4uLnJ1bGVPcHRpb25zUGVyRmlsZSB9LFxuICAgIH0pLFxuICAgIHsgcmVzdWx0czogW10sIHJ1bGVPcHRpb25zUGVyRmlsZToge30gfSxcbiAgKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGxpbnRSZXN1bHRzVG9BdWRpdHMoe1xuICByZXN1bHRzLFxuICBydWxlT3B0aW9uc1BlckZpbGUsXG59OiBMaW50ZXJPdXRwdXQpOiBBdWRpdE91dHB1dFtdIHtcbiAgY29uc3QgaXNzdWVzUGVyQXVkaXQgPSByZXN1bHRzXG4gICAgLmZsYXRNYXAoKHsgbWVzc2FnZXMsIGZpbGVQYXRoIH0pID0+XG4gICAgICBtZXNzYWdlcy5tYXAoKG1lc3NhZ2UpOiBMaW50SXNzdWUgPT4gKHsgLi4ubWVzc2FnZSwgZmlsZVBhdGggfSkpLFxuICAgIClcbiAgICAucmVkdWNlPFJlY29yZDxzdHJpbmcsIExpbnRJc3N1ZVtdPj4oKGFjYywgaXNzdWUpID0+IHtcbiAgICAgIGNvbnN0IHsgcnVsZUlkLCBtZXNzYWdlLCBmaWxlUGF0aCB9ID0gaXNzdWU7XG4gICAgICBpZiAoIXJ1bGVJZCkge1xuICAgICAgICB1aSgpLmxvZ2dlci53YXJuaW5nKFxuICAgICAgICAgIGBFU0xpbnQgY29yZSBlcnJvciAtICR7bWVzc2FnZX0gKGZpbGU6ICR7ZmlsZVBhdGh9KWAsXG4gICAgICAgICk7XG4gICAgICAgIHJldHVybiBhY2M7XG4gICAgICB9XG4gICAgICBjb25zdCBvcHRpb25zID0gcnVsZU9wdGlvbnNQZXJGaWxlW2ZpbGVQYXRoXT8uW3J1bGVJZF0gPz8gW107XG4gICAgICBjb25zdCBhdWRpdFNsdWcgPSBydWxlSWRUb1NsdWcocnVsZUlkLCBvcHRpb25zKTtcbiAgICAgIHJldHVybiB7IC4uLmFjYywgW2F1ZGl0U2x1Z106IFsuLi4oYWNjW2F1ZGl0U2x1Z10gPz8gW10pLCBpc3N1ZV0gfTtcbiAgICB9LCB7fSk7XG5cbiAgcmV0dXJuIE9iamVjdC5lbnRyaWVzKGlzc3Vlc1BlckF1ZGl0KS5tYXAoZW50cnkgPT4gdG9BdWRpdCguLi5lbnRyeSkpO1xufVxuXG5mdW5jdGlvbiB0b0F1ZGl0KHNsdWc6IHN0cmluZywgaXNzdWVzOiBMaW50SXNzdWVbXSk6IEF1ZGl0T3V0cHV0IHtcbiAgY29uc3QgYXVkaXRJc3N1ZXMgPSBpc3N1ZXMubWFwKGNvbnZlcnRJc3N1ZSk7XG4gIGNvbnN0IHNldmVyaXR5Q291bnRzID0gY291bnRPY2N1cnJlbmNlcyhcbiAgICBhdWRpdElzc3Vlcy5tYXAoKHsgc2V2ZXJpdHkgfSkgPT4gc2V2ZXJpdHkpLFxuICApO1xuICBjb25zdCBzZXZlcml0aWVzID0gb2JqZWN0VG9FbnRyaWVzKHNldmVyaXR5Q291bnRzKTtcbiAgY29uc3Qgc3VtbWFyeVRleHQgPSBzZXZlcml0aWVzXG4gICAgLnRvU29ydGVkKChhLCBiKSA9PiAtY29tcGFyZUlzc3VlU2V2ZXJpdHkoYVswXSwgYlswXSkpXG4gICAgLm1hcCgoW3NldmVyaXR5LCBjb3VudCA9IDBdKSA9PiBwbHVyYWxpemVUb2tlbihzZXZlcml0eSwgY291bnQpKVxuICAgIC5qb2luKCcsICcpO1xuXG4gIHJldHVybiB7XG4gICAgc2x1ZyxcbiAgICBzY29yZTogTnVtYmVyKGF1ZGl0SXNzdWVzLmxlbmd0aCA9PT0gMCksXG4gICAgdmFsdWU6IGF1ZGl0SXNzdWVzLmxlbmd0aCxcbiAgICBkaXNwbGF5VmFsdWU6IHN1bW1hcnlUZXh0LFxuICAgIGRldGFpbHM6IHtcbiAgICAgIGlzc3VlczogYXVkaXRJc3N1ZXMsXG4gICAgfSxcbiAgfTtcbn1cblxuZnVuY3Rpb24gY29udmVydElzc3VlKGlzc3VlOiBMaW50SXNzdWUpOiBJc3N1ZSB7XG4gIHJldHVybiB7XG4gICAgbWVzc2FnZTogdHJ1bmNhdGVJc3N1ZU1lc3NhZ2UoaXNzdWUubWVzc2FnZSksXG4gICAgc2V2ZXJpdHk6IGNvbnZlcnRTZXZlcml0eShpc3N1ZS5zZXZlcml0eSksXG4gICAgc291cmNlOiB7XG4gICAgICBmaWxlOiBpc3N1ZS5maWxlUGF0aCxcbiAgICAgIC4uLihpc3N1ZS5saW5lID4gMCAmJiB7XG4gICAgICAgIHBvc2l0aW9uOiB7XG4gICAgICAgICAgc3RhcnRMaW5lOiBpc3N1ZS5saW5lLFxuICAgICAgICAgIC4uLihpc3N1ZS5jb2x1bW4gPiAwICYmIHsgc3RhcnRDb2x1bW46IGlzc3VlLmNvbHVtbiB9KSxcbiAgICAgICAgICAuLi4oaXNzdWUuZW5kTGluZSAmJlxuICAgICAgICAgICAgaXNzdWUuZW5kTGluZSA+IDAgJiYge1xuICAgICAgICAgICAgICBlbmRMaW5lOiBpc3N1ZS5lbmRMaW5lLFxuICAgICAgICAgICAgfSksXG4gICAgICAgICAgLi4uKGlzc3VlLmVuZENvbHVtbiAmJlxuICAgICAgICAgICAgaXNzdWUuZW5kQ29sdW1uID4gMCAmJiB7IGVuZENvbHVtbjogaXNzdWUuZW5kQ29sdW1uIH0pLFxuICAgICAgICB9LFxuICAgICAgfSksXG4gICAgfSxcbiAgfTtcbn1cblxuZnVuY3Rpb24gY29udmVydFNldmVyaXR5KHNldmVyaXR5OiBMaW50ZXIuU2V2ZXJpdHkpOiBJc3N1ZVNldmVyaXR5IHtcbiAgc3dpdGNoIChzZXZlcml0eSkge1xuICAgIGNhc2UgMjpcbiAgICAgIHJldHVybiAnZXJyb3InO1xuICAgIGNhc2UgMTpcbiAgICAgIHJldHVybiAnd2FybmluZyc7XG4gICAgY2FzZSAwOlxuICAgICAgLy8gc2hvdWxkbid0IGhhcHBlblxuICAgICAgdGhyb3cgbmV3IEVycm9yKGBVbmV4cGVjdGVkIHNldmVyaXR5ICR7c2V2ZXJpdHl9IGluIEVTTGludCByZXN1bHRzYCk7XG4gIH1cbn1cbiIsICJjb25zdCBfX2luamVjdGVkX2ZpbGVuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1lc2xpbnQvc3JjL2xpYi9ueC91dGlscy50c1wiO2NvbnN0IF9faW5qZWN0ZWRfZGlybmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tZXNsaW50L3NyYy9saWIvbnhcIjtjb25zdCBfX2luamVjdGVkX2ltcG9ydF9tZXRhX3VybF9fID0gXCJmaWxlOi8vL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWVzbGludC9zcmMvbGliL254L3V0aWxzLnRzXCI7aW1wb3J0IHR5cGUgeyBQcm9qZWN0Q29uZmlndXJhdGlvbiB9IGZyb20gJ0BueC9kZXZraXQnO1xuaW1wb3J0IHBhdGggZnJvbSAnbm9kZTpwYXRoJztcbmltcG9ydCB7IGZpbGVFeGlzdHMsIHRvQXJyYXkgfSBmcm9tICdAY29kZS1wdXNodXAvdXRpbHMnO1xuaW1wb3J0IHR5cGUgeyBDb25maWdGb3JtYXQgfSBmcm9tICcuLi9tZXRhL2luZGV4LmpzJztcblxuY29uc3QgRVNMSU5UX0NPTkZJR19FWFRFTlNJT05TOiBSZWNvcmQ8Q29uZmlnRm9ybWF0LCBzdHJpbmdbXT4gPSB7XG4gIC8vIGh0dHBzOi8vZXNsaW50Lm9yZy9kb2NzL2xhdGVzdC91c2UvY29uZmlndXJlL2NvbmZpZ3VyYXRpb24tZmlsZXMjY29uZmlndXJhdGlvbi1maWxlLWZvcm1hdHNcbiAgZmxhdDogWydqcycsICdtanMnLCAnY2pzJ10sXG4gIC8vIGh0dHBzOi8vZXNsaW50Lm9yZy9kb2NzL2xhdGVzdC91c2UvY29uZmlndXJlL2NvbmZpZ3VyYXRpb24tZmlsZXMtZGVwcmVjYXRlZFxuICBsZWdhY3k6IFsnanNvbicsICdqcycsICdjanMnLCAneW1sJywgJ3lhbWwnXSxcbn07XG5jb25zdCBFU0xJTlRfQ09ORklHX05BTUVTOiBSZWNvcmQ8Q29uZmlnRm9ybWF0LCBzdHJpbmdbXT4gPSB7XG4gIC8vIGh0dHBzOi8vZXNsaW50Lm9yZy9kb2NzL2xhdGVzdC91c2UvY29uZmlndXJlL2NvbmZpZ3VyYXRpb24tZmlsZXMjY29uZmlndXJhdGlvbi1maWxlLWZvcm1hdHNcbiAgZmxhdDogWydlc2xpbnQuY29uZmlnJ10sXG4gIC8vIGh0dHBzOi8vZXNsaW50Lm9yZy9kb2NzL2xhdGVzdC91c2UvY29uZmlndXJlL2NvbmZpZ3VyYXRpb24tZmlsZXMtZGVwcmVjYXRlZFxuICBsZWdhY3k6IFsnLmVzbGludHJjJ10sXG59O1xuXG5jb25zdCBDUF9FU0xJTlRfQ09ORklHX05BTUVTOiBSZWNvcmQ8Q29uZmlnRm9ybWF0LCBzdHJpbmdbXT4gPSB7XG4gIGZsYXQ6IFtcbiAgICAnY29kZS1wdXNodXAuZXNsaW50LmNvbmZpZycsXG4gICAgJ2VzbGludC5jb2RlLXB1c2h1cC5jb25maWcnLFxuICAgICdlc2xpbnQuY29uZmlnLmNvZGUtcHVzaHVwJyxcbiAgICAnZXNsaW50LnN0cmljdC5jb25maWcnLFxuICAgICdlc2xpbnQuY29uZmlnLnN0cmljdCcsXG4gIF0sXG4gIGxlZ2FjeTogWydjb2RlLXB1c2h1cC5lc2xpbnRyYycsICcuZXNsaW50cmMuY29kZS1wdXNodXAnLCAnLmVzbGludHJjLnN0cmljdCddLFxufTtcblxuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGZpbmRDb2RlUHVzaHVwRXNsaW50Q29uZmlnKFxuICBwcm9qZWN0OiBQcm9qZWN0Q29uZmlndXJhdGlvbixcbiAgZm9ybWF0OiBDb25maWdGb3JtYXQsXG4pOiBQcm9taXNlPHN0cmluZyB8IHVuZGVmaW5lZD4ge1xuICByZXR1cm4gZmluZFByb2plY3RGaWxlKHByb2plY3QsIHtcbiAgICBuYW1lczogQ1BfRVNMSU5UX0NPTkZJR19OQU1FU1tmb3JtYXRdLFxuICAgIGV4dGVuc2lvbnM6IEVTTElOVF9DT05GSUdfRVhURU5TSU9OU1tmb3JtYXRdLFxuICB9KTtcbn1cblxuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGZpbmRFc2xpbnRDb25maWcoXG4gIHByb2plY3Q6IFByb2plY3RDb25maWd1cmF0aW9uLFxuICBmb3JtYXQ6IENvbmZpZ0Zvcm1hdCxcbik6IFByb21pc2U8c3RyaW5nIHwgdW5kZWZpbmVkPiB7XG4gIGNvbnN0IG9wdGlvbnMgPSBwcm9qZWN0LnRhcmdldHM/LlsnbGludCddPy5vcHRpb25zIGFzXG4gICAgfCB7IGVzbGludENvbmZpZz86IHN0cmluZyB9XG4gICAgfCB1bmRlZmluZWQ7XG4gIHJldHVybiAoXG4gICAgb3B0aW9ucz8uZXNsaW50Q29uZmlnID8/XG4gICAgKGF3YWl0IGZpbmRQcm9qZWN0RmlsZShwcm9qZWN0LCB7XG4gICAgICBuYW1lczogRVNMSU5UX0NPTkZJR19OQU1FU1tmb3JtYXRdLFxuICAgICAgZXh0ZW5zaW9uczogRVNMSU5UX0NPTkZJR19FWFRFTlNJT05TW2Zvcm1hdF0sXG4gICAgfSkpXG4gICk7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBnZXRMaW50RmlsZVBhdHRlcm5zKFxuICBwcm9qZWN0OiBQcm9qZWN0Q29uZmlndXJhdGlvbixcbiAgZm9ybWF0OiBDb25maWdGb3JtYXQsXG4pOiBzdHJpbmdbXSB7XG4gIGNvbnN0IG9wdGlvbnMgPSBwcm9qZWN0LnRhcmdldHM/LlsnbGludCddPy5vcHRpb25zIGFzXG4gICAgfCB7IGxpbnRGaWxlUGF0dGVybnM/OiBzdHJpbmcgfCBzdHJpbmdbXSB9XG4gICAgfCB1bmRlZmluZWQ7XG4gIC8vIGxpbnRGaWxlUGF0dGVybnMgZGVmYXVsdHMgdG8gW1wie3Byb2plY3RSb290fVwiXSAtIGh0dHBzOi8vZ2l0aHViLmNvbS9ucndsL254L3B1bGwvMjAzMTNcbiAgY29uc3QgZGVmYXVsdFBhdHRlcm5zID1cbiAgICBmb3JtYXQgPT09ICdsZWdhY3knXG4gICAgICA/IGAke3Byb2plY3Qucm9vdH0vKiovKmAgLy8gZmlsZXMgbm90IGZvbGRlciBuZWVkZWQgZm9yIGxlZ2FjeSBiZWNhdXNlIHJ1bGVzIGRldGVjdGVkIHdpdGggRVNMaW50LmNhbGN1bGF0ZUNvbmZpZ0ZvckZpbGVcbiAgICAgIDogcHJvamVjdC5yb290O1xuICBjb25zdCBwYXR0ZXJucyA9XG4gICAgb3B0aW9ucz8ubGludEZpbGVQYXR0ZXJucyA9PSBudWxsXG4gICAgICA/IFtkZWZhdWx0UGF0dGVybnNdXG4gICAgICA6IHRvQXJyYXkob3B0aW9ucy5saW50RmlsZVBhdHRlcm5zKTtcbiAgaWYgKGZvcm1hdCA9PT0gJ2xlZ2FjeScpIHtcbiAgICByZXR1cm4gW1xuICAgICAgLi4ucGF0dGVybnMsXG4gICAgICAvLyBIQUNLOiBFU0xpbnQuY2FsY3VsYXRlQ29uZmlnRm9yRmlsZSB3b24ndCBmaW5kIHJ1bGVzIGluY2x1ZGVkIG9ubHkgZm9yIHN1YnNldHMgb2YgKi50cyB3aGVuIGdsb2JzIHVzZWRcbiAgICAgIC8vIHNvIHdlIGV4cGxpY2l0bHkgcHJvdmlkZSBhZGRpdGlvbmFsIHBhdHRlcm5zIHVzZWQgYnkgQGNvZGUtcHVzaHVwL2VzbGludC1jb25maWcgdG8gZW5zdXJlIHRob3NlIHJ1bGVzIGFyZSBpbmNsdWRlZFxuICAgICAgLy8gdGhpcyB3b3JrYXJvdW5kIGlzIG9ubHkgbmVjZXNzYXJ5IGZvciBsZWdhY3kgY29uZmlncyAocnVsZXMgYXJlIGRldGVjdGVkIG1vcmUgcmVsaWFibHkgaW4gZmxhdCBjb25maWdzKVxuICAgICAgYCR7cHJvamVjdC5yb290fS8qLnNwZWMudHNgLCAvLyBqZXN0LyogYW5kIHZpdGVzdC8qIHJ1bGVzXG4gICAgICBgJHtwcm9qZWN0LnJvb3R9LyouY3kudHNgLCAvLyBjeXByZXNzLyogcnVsZXNcbiAgICAgIGAke3Byb2plY3Qucm9vdH0vKi5zdG9yaWVzLnRzYCwgLy8gc3Rvcnlib29rLyogcnVsZXNcbiAgICAgIGAke3Byb2plY3Qucm9vdH0vLnN0b3J5Ym9vay9tYWluLnRzYCwgLy8gc3Rvcnlib29rL25vLXVuaW5zdGFsbGVkLWFkZG9ucyBydWxlXG4gICAgXTtcbiAgfVxuICByZXR1cm4gcGF0dGVybnM7XG59XG5cbmFzeW5jIGZ1bmN0aW9uIGZpbmRQcm9qZWN0RmlsZShcbiAgcHJvamVjdDogUHJvamVjdENvbmZpZ3VyYXRpb24sXG4gIGZpbGU6IHtcbiAgICBuYW1lczogc3RyaW5nW107XG4gICAgZXh0ZW5zaW9uczogc3RyaW5nW107XG4gIH0sXG4pOiBQcm9taXNlPHN0cmluZyB8IHVuZGVmaW5lZD4ge1xuICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgZnVuY3Rpb25hbC9uby1sb29wLXN0YXRlbWVudHNcbiAgZm9yIChjb25zdCBuYW1lIG9mIGZpbGUubmFtZXMpIHtcbiAgICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgZnVuY3Rpb25hbC9uby1sb29wLXN0YXRlbWVudHNcbiAgICBmb3IgKGNvbnN0IGV4dCBvZiBmaWxlLmV4dGVuc2lvbnMpIHtcbiAgICAgIGNvbnN0IGZpbGVuYW1lID0gYC4vJHtwcm9qZWN0LnJvb3R9LyR7bmFtZX0uJHtleHR9YDtcbiAgICAgIGlmIChhd2FpdCBmaWxlRXhpc3RzKHBhdGguam9pbihwcm9jZXNzLmN3ZCgpLCBmaWxlbmFtZSkpKSB7XG4gICAgICAgIHJldHVybiBmaWxlbmFtZTtcbiAgICAgIH1cbiAgICB9XG4gIH1cbiAgcmV0dXJuIHVuZGVmaW5lZDtcbn1cbiIsICJjb25zdCBfX2luamVjdGVkX2ZpbGVuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1qcy1wYWNrYWdlcy9zcmMvbGliL2pzLXBhY2thZ2VzLXBsdWdpbi50c1wiO2NvbnN0IF9faW5qZWN0ZWRfZGlybmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tanMtcGFja2FnZXMvc3JjL2xpYlwiO2NvbnN0IF9faW5qZWN0ZWRfaW1wb3J0X21ldGFfdXJsX18gPSBcImZpbGU6Ly8vaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tanMtcGFja2FnZXMvc3JjL2xpYi9qcy1wYWNrYWdlcy1wbHVnaW4udHNcIjtpbXBvcnQgeyBjcmVhdGVSZXF1aXJlIH0gZnJvbSAnbm9kZTptb2R1bGUnO1xuaW1wb3J0IHBhdGggZnJvbSAnbm9kZTpwYXRoJztcbmltcG9ydCB7IGZpbGVVUkxUb1BhdGggfSBmcm9tICdub2RlOnVybCc7XG5pbXBvcnQgdHlwZSB7IEF1ZGl0LCBHcm91cCwgUGx1Z2luQ29uZmlnIH0gZnJvbSAnQGNvZGUtcHVzaHVwL21vZGVscyc7XG5pbXBvcnQge1xuICB0eXBlIERlcGVuZGVuY3lHcm91cCxcbiAgdHlwZSBKU1BhY2thZ2VzUGx1Z2luQ29uZmlnLFxuICB0eXBlIFBhY2thZ2VDb21tYW5kLFxuICB0eXBlIFBhY2thZ2VNYW5hZ2VySWQsXG4gIGRlcGVuZGVuY3lHcm91cHMsXG59IGZyb20gJy4vY29uZmlnLmpzJztcbmltcG9ydCB7IGRlcGVuZGVuY3lEb2NzLCBkZXBlbmRlbmN5R3JvdXBXZWlnaHRzIH0gZnJvbSAnLi9jb25zdGFudHMuanMnO1xuaW1wb3J0IHsgcGFja2FnZU1hbmFnZXJzIH0gZnJvbSAnLi9wYWNrYWdlLW1hbmFnZXJzL3BhY2thZ2UtbWFuYWdlcnMuanMnO1xuaW1wb3J0IHsgY3JlYXRlUnVubmVyQ29uZmlnIH0gZnJvbSAnLi9ydW5uZXIvaW5kZXguanMnO1xuaW1wb3J0IHsgbm9ybWFsaXplQ29uZmlnIH0gZnJvbSAnLi91dGlscy5qcyc7XG5cbi8qKlxuICogSW5zdGFudGlhdGVzIENvZGUgUHVzaFVwIEpTIHBhY2thZ2VzIHBsdWdpbiBmb3IgY29yZSBjb25maWcuXG4gKlxuICogQGV4YW1wbGVcbiAqIGltcG9ydCBqc1BhY2thZ2VzUGx1Z2luIGZyb20gJ0Bjb2RlLXB1c2h1cC9qcy1wYWNrYWdlcy1wbHVnaW4nXG4gKlxuICogZXhwb3J0IGRlZmF1bHQge1xuICogICAvLyAuLi4gY29yZSBjb25maWcgLi4uXG4gKiAgIHBsdWdpbnM6IFtcbiAqICAgICAvLyAuLi4gb3RoZXIgcGx1Z2lucyAuLi5cbiAqICAgICBhd2FpdCBqc1BhY2thZ2VzUGx1Z2luKHsgcGFja2FnZU1hbmFnZXI6ICducG0nIH0pXG4gKiAgIF1cbiAqIH1cbiAqXG4gKiBAcmV0dXJucyBQbHVnaW4gY29uZmlndXJhdGlvbi5cbiAqL1xuXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24ganNQYWNrYWdlc1BsdWdpbihcbiAgY29uZmlnPzogSlNQYWNrYWdlc1BsdWdpbkNvbmZpZyxcbik6IFByb21pc2U8UGx1Z2luQ29uZmlnPiB7XG4gIGNvbnN0IHsgcGFja2FnZU1hbmFnZXIsIGNoZWNrcywgZGVwR3JvdXBzLCAuLi5qc1BhY2thZ2VzUGx1Z2luQ29uZmlnUmVzdCB9ID1cbiAgICBhd2FpdCBub3JtYWxpemVDb25maWcoY29uZmlnKTtcblxuICBjb25zdCBydW5uZXJTY3JpcHRQYXRoID0gcGF0aC5qb2luKFxuICAgIGZpbGVVUkxUb1BhdGgocGF0aC5kaXJuYW1lKGltcG9ydC5tZXRhLnVybCkpLFxuICAgICcuLicsXG4gICAgJ2Jpbi5qcycsXG4gICk7XG5cbiAgY29uc3QgcGFja2FnZUpzb24gPSBjcmVhdGVSZXF1aXJlKGltcG9ydC5tZXRhLnVybCkoXG4gICAgJy4uLy4uL3BhY2thZ2UuanNvbicsXG4gICkgYXMgdHlwZW9mIGltcG9ydCgnLi4vLi4vcGFja2FnZS5qc29uJyk7XG5cbiAgcmV0dXJuIHtcbiAgICBzbHVnOiAnanMtcGFja2FnZXMnLFxuICAgIHRpdGxlOiAnSlMgUGFja2FnZXMnLFxuICAgIGljb246IHBhY2thZ2VNYW5hZ2VyLmljb24sXG4gICAgZGVzY3JpcHRpb246XG4gICAgICAnVGhpcyBwbHVnaW4gcnVucyBhdWRpdCB0byB1bmNvdmVyIHZ1bG5lcmFiaWxpdGllcyBhbmQgbGlzdHMgb3V0ZGF0ZWQgZGVwZW5kZW5jaWVzLiBJdCBzdXBwb3J0cyBucG0sIHlhcm4gY2xhc3NpYywgeWFybiBtb2Rlcm4sIGFuZCBwbnBtIHBhY2thZ2UgbWFuYWdlcnMuJyxcbiAgICBkb2NzVXJsOiBwYWNrYWdlTWFuYWdlci5kb2NzLmhvbWVwYWdlLFxuICAgIHBhY2thZ2VOYW1lOiBwYWNrYWdlSnNvbi5uYW1lLFxuICAgIHZlcnNpb246IHBhY2thZ2VKc29uLnZlcnNpb24sXG4gICAgYXVkaXRzOiBjcmVhdGVBdWRpdHMocGFja2FnZU1hbmFnZXIuc2x1ZywgY2hlY2tzLCBkZXBHcm91cHMpLFxuICAgIGdyb3VwczogY3JlYXRlR3JvdXBzKHBhY2thZ2VNYW5hZ2VyLnNsdWcsIGNoZWNrcywgZGVwR3JvdXBzKSxcbiAgICBydW5uZXI6IGF3YWl0IGNyZWF0ZVJ1bm5lckNvbmZpZyhydW5uZXJTY3JpcHRQYXRoLCB7XG4gICAgICAuLi5qc1BhY2thZ2VzUGx1Z2luQ29uZmlnUmVzdCxcbiAgICAgIGNoZWNrcyxcbiAgICAgIHBhY2thZ2VNYW5hZ2VyOiBwYWNrYWdlTWFuYWdlci5zbHVnLFxuICAgICAgZGVwZW5kZW5jeUdyb3VwczogZGVwR3JvdXBzLFxuICAgIH0pLFxuICB9O1xufVxuXG5mdW5jdGlvbiBjcmVhdGVHcm91cHMoXG4gIGlkOiBQYWNrYWdlTWFuYWdlcklkLFxuICBjaGVja3M6IFBhY2thZ2VDb21tYW5kW10sXG4gIGRlcEdyb3VwczogRGVwZW5kZW5jeUdyb3VwW10sXG4pOiBHcm91cFtdIHtcbiAgY29uc3QgcG0gPSBwYWNrYWdlTWFuYWdlcnNbaWRdO1xuICBjb25zdCBzdXBwb3J0ZWRBdWRpdERlcEdyb3VwcyA9XG4gICAgcG0uYXVkaXQuc3VwcG9ydGVkRGVwR3JvdXBzID8/IGRlcGVuZGVuY3lHcm91cHM7XG4gIGNvbnN0IGNvbXBhdGlibGVBdWRpdERlcEdyb3VwcyA9IGRlcEdyb3Vwcy5maWx0ZXIoZ3JvdXAgPT5cbiAgICBzdXBwb3J0ZWRBdWRpdERlcEdyb3Vwcy5pbmNsdWRlcyhncm91cCksXG4gICk7XG5cbiAgY29uc3QgZ3JvdXBzOiBSZWNvcmQ8UGFja2FnZUNvbW1hbmQsIEdyb3VwPiA9IHtcbiAgICBhdWRpdDoge1xuICAgICAgc2x1ZzogYCR7cG0uc2x1Z30tYXVkaXRgLFxuICAgICAgdGl0bGU6IGAke3BtLm5hbWV9IGF1ZGl0YCxcbiAgICAgIGRlc2NyaXB0aW9uOiBgR3JvdXAgY29udGFpbmluZyAke3BtLm5hbWV9IHZ1bG5lcmFiaWxpdGllcy5gLFxuICAgICAgZG9jc1VybDogcG0uZG9jcy5hdWRpdCxcbiAgICAgIHJlZnM6IGNvbXBhdGlibGVBdWRpdERlcEdyb3Vwcy5tYXAoZGVwR3JvdXAgPT4gKHtcbiAgICAgICAgc2x1ZzogYCR7cG0uc2x1Z30tYXVkaXQtJHtkZXBHcm91cH1gLFxuICAgICAgICB3ZWlnaHQ6IGRlcGVuZGVuY3lHcm91cFdlaWdodHNbZGVwR3JvdXBdLFxuICAgICAgfSkpLFxuICAgIH0sXG4gICAgb3V0ZGF0ZWQ6IHtcbiAgICAgIHNsdWc6IGAke3BtLnNsdWd9LW91dGRhdGVkYCxcbiAgICAgIHRpdGxlOiBgJHtwbS5uYW1lfSBvdXRkYXRlZCBkZXBlbmRlbmNpZXNgLFxuICAgICAgZGVzY3JpcHRpb246IGBHcm91cCBjb250YWluaW5nIG91dGRhdGVkICR7cG0ubmFtZX0gZGVwZW5kZW5jaWVzLmAsXG4gICAgICBkb2NzVXJsOiBwbS5kb2NzLm91dGRhdGVkLFxuICAgICAgcmVmczogZGVwR3JvdXBzLm1hcChkZXBHcm91cCA9PiAoe1xuICAgICAgICBzbHVnOiBgJHtwbS5zbHVnfS1vdXRkYXRlZC0ke2RlcEdyb3VwfWAsXG4gICAgICAgIHdlaWdodDogZGVwZW5kZW5jeUdyb3VwV2VpZ2h0c1tkZXBHcm91cF0sXG4gICAgICB9KSksXG4gICAgfSxcbiAgfTtcblxuICByZXR1cm4gY2hlY2tzLm1hcChjaGVjayA9PiBncm91cHNbY2hlY2tdKTtcbn1cblxuZnVuY3Rpb24gY3JlYXRlQXVkaXRzKFxuICBpZDogUGFja2FnZU1hbmFnZXJJZCxcbiAgY2hlY2tzOiBQYWNrYWdlQ29tbWFuZFtdLFxuICBkZXBHcm91cHM6IERlcGVuZGVuY3lHcm91cFtdLFxuKTogQXVkaXRbXSB7XG4gIGNvbnN0IHsgc2x1ZyB9ID0gcGFja2FnZU1hbmFnZXJzW2lkXTtcbiAgcmV0dXJuIGNoZWNrcy5mbGF0TWFwKGNoZWNrID0+IHtcbiAgICBjb25zdCBzdXBwb3J0ZWRBdWRpdERlcEdyb3VwcyA9XG4gICAgICBwYWNrYWdlTWFuYWdlcnNbaWRdLmF1ZGl0LnN1cHBvcnRlZERlcEdyb3VwcyA/PyBkZXBlbmRlbmN5R3JvdXBzO1xuXG4gICAgY29uc3QgY29tcGF0aWJsZURlcEdyb3VwcyA9XG4gICAgICBjaGVjayA9PT0gJ2F1ZGl0J1xuICAgICAgICA/IGRlcEdyb3Vwcy5maWx0ZXIoZ3JvdXAgPT4gc3VwcG9ydGVkQXVkaXREZXBHcm91cHMuaW5jbHVkZXMoZ3JvdXApKVxuICAgICAgICA6IGRlcEdyb3VwcztcblxuICAgIHJldHVybiBjb21wYXRpYmxlRGVwR3JvdXBzLm1hcChkZXBHcm91cCA9PiAoe1xuICAgICAgc2x1ZzogYCR7c2x1Z30tJHtjaGVja30tJHtkZXBHcm91cH1gLFxuICAgICAgdGl0bGU6IGdldEF1ZGl0VGl0bGUoc2x1ZywgY2hlY2ssIGRlcEdyb3VwKSxcbiAgICAgIGRlc2NyaXB0aW9uOiBnZXRBdWRpdERlc2NyaXB0aW9uKGNoZWNrLCBkZXBHcm91cCksXG4gICAgICBkb2NzVXJsOiBkZXBlbmRlbmN5RG9jc1tkZXBHcm91cF0sXG4gICAgfSkpO1xuICB9KTtcbn1cblxuZnVuY3Rpb24gZ2V0QXVkaXRUaXRsZShcbiAgaWQ6IFBhY2thZ2VNYW5hZ2VySWQsXG4gIGNoZWNrOiBQYWNrYWdlQ29tbWFuZCxcbiAgZGVwR3JvdXA6IERlcGVuZGVuY3lHcm91cCxcbikge1xuICBjb25zdCBwbSA9IHBhY2thZ2VNYW5hZ2Vyc1tpZF07XG4gIHJldHVybiBjaGVjayA9PT0gJ2F1ZGl0J1xuICAgID8gYFZ1bG5lcmFiaWxpdGllcyBmb3IgJHtwbS5uYW1lfSAke2RlcEdyb3VwfSBkZXBlbmRlbmNpZXMuYFxuICAgIDogYE91dGRhdGVkICR7cG0ubmFtZX0gJHtkZXBHcm91cH0gZGVwZW5kZW5jaWVzLmA7XG59XG5cbmZ1bmN0aW9uIGdldEF1ZGl0RGVzY3JpcHRpb24oY2hlY2s6IFBhY2thZ2VDb21tYW5kLCBkZXBHcm91cDogRGVwZW5kZW5jeUdyb3VwKSB7XG4gIHJldHVybiBjaGVjayA9PT0gJ2F1ZGl0J1xuICAgID8gYFJ1bnMgc2VjdXJpdHkgYXVkaXQgb24gJHtkZXBHcm91cH0gZGVwZW5kZW5jaWVzLmBcbiAgICA6IGBDaGVja3MgZm9yIG91dGRhdGVkICR7ZGVwR3JvdXB9IGRlcGVuZGVuY2llc2A7XG59XG4iLCAiY29uc3QgX19pbmplY3RlZF9maWxlbmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tanMtcGFja2FnZXMvc3JjL2xpYi9jb25maWcudHNcIjtjb25zdCBfX2luamVjdGVkX2Rpcm5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWpzLXBhY2thZ2VzL3NyYy9saWJcIjtjb25zdCBfX2luamVjdGVkX2ltcG9ydF9tZXRhX3VybF9fID0gXCJmaWxlOi8vL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWpzLXBhY2thZ2VzL3NyYy9saWIvY29uZmlnLnRzXCI7aW1wb3J0IHsgeiB9IGZyb20gJ3pvZCc7XG5pbXBvcnQgeyB0eXBlIElzc3VlU2V2ZXJpdHksIGlzc3VlU2V2ZXJpdHlTY2hlbWEgfSBmcm9tICdAY29kZS1wdXNodXAvbW9kZWxzJztcbmltcG9ydCB7IGRlZmF1bHRBdWRpdExldmVsTWFwcGluZyB9IGZyb20gJy4vY29uc3RhbnRzLmpzJztcblxuZXhwb3J0IGNvbnN0IGRlcGVuZGVuY3lHcm91cHMgPSBbJ3Byb2QnLCAnZGV2JywgJ29wdGlvbmFsJ10gYXMgY29uc3Q7XG5jb25zdCBkZXBlbmRlbmN5R3JvdXBTY2hlbWEgPSB6LmVudW0oZGVwZW5kZW5jeUdyb3Vwcyk7XG5leHBvcnQgdHlwZSBEZXBlbmRlbmN5R3JvdXAgPSAodHlwZW9mIGRlcGVuZGVuY3lHcm91cHMpW251bWJlcl07XG5cbmNvbnN0IHBhY2thZ2VDb21tYW5kU2NoZW1hID0gei5lbnVtKFsnYXVkaXQnLCAnb3V0ZGF0ZWQnXSk7XG5leHBvcnQgdHlwZSBQYWNrYWdlQ29tbWFuZCA9IHouaW5mZXI8dHlwZW9mIHBhY2thZ2VDb21tYW5kU2NoZW1hPjtcblxuY29uc3QgcGFja2FnZU1hbmFnZXJJZFNjaGVtYSA9IHouZW51bShbXG4gICducG0nLFxuICAneWFybi1jbGFzc2ljJyxcbiAgJ3lhcm4tbW9kZXJuJyxcbiAgJ3BucG0nLFxuXSk7XG5leHBvcnQgdHlwZSBQYWNrYWdlTWFuYWdlcklkID0gei5pbmZlcjx0eXBlb2YgcGFja2FnZU1hbmFnZXJJZFNjaGVtYT47XG5cbmNvbnN0IHBhY2thZ2VKc29uUGF0aFNjaGVtYSA9IHpcbiAgLnVuaW9uKFtcbiAgICB6LmFycmF5KHouc3RyaW5nKCkpLm1pbigxKSxcbiAgICB6Lm9iamVjdCh7IGF1dG9TZWFyY2g6IHoubGl0ZXJhbCh0cnVlKSB9KSxcbiAgXSlcbiAgLmRlc2NyaWJlKFxuICAgICdGaWxlIHBhdGhzIHRvIHBhY2thZ2UuanNvbi4gTG9va3Mgb25seSBhdCByb290IHBhY2thZ2UuanNvbiBieSBkZWZhdWx0JyxcbiAgKVxuICAuZGVmYXVsdChbJ3BhY2thZ2UuanNvbiddKTtcblxuZXhwb3J0IHR5cGUgUGFja2FnZUpzb25QYXRocyA9IHouaW5mZXI8dHlwZW9mIHBhY2thZ2VKc29uUGF0aFNjaGVtYT47XG5cbmV4cG9ydCBjb25zdCBwYWNrYWdlQXVkaXRMZXZlbHMgPSBbXG4gICdjcml0aWNhbCcsXG4gICdoaWdoJyxcbiAgJ21vZGVyYXRlJyxcbiAgJ2xvdycsXG4gICdpbmZvJyxcbl0gYXMgY29uc3Q7XG5jb25zdCBwYWNrYWdlQXVkaXRMZXZlbFNjaGVtYSA9IHouZW51bShwYWNrYWdlQXVkaXRMZXZlbHMpO1xuZXhwb3J0IHR5cGUgUGFja2FnZUF1ZGl0TGV2ZWwgPSB6LmluZmVyPHR5cGVvZiBwYWNrYWdlQXVkaXRMZXZlbFNjaGVtYT47XG5cbmV4cG9ydCB0eXBlIEF1ZGl0U2V2ZXJpdHkgPSBSZWNvcmQ8UGFja2FnZUF1ZGl0TGV2ZWwsIElzc3VlU2V2ZXJpdHk+O1xuXG5leHBvcnQgZnVuY3Rpb24gZmlsbEF1ZGl0TGV2ZWxNYXBwaW5nKFxuICBtYXBwaW5nOiBQYXJ0aWFsPEF1ZGl0U2V2ZXJpdHk+LFxuKTogQXVkaXRTZXZlcml0eSB7XG4gIHJldHVybiB7XG4gICAgY3JpdGljYWw6IG1hcHBpbmcuY3JpdGljYWwgPz8gZGVmYXVsdEF1ZGl0TGV2ZWxNYXBwaW5nLmNyaXRpY2FsLFxuICAgIGhpZ2g6IG1hcHBpbmcuaGlnaCA/PyBkZWZhdWx0QXVkaXRMZXZlbE1hcHBpbmcuaGlnaCxcbiAgICBtb2RlcmF0ZTogbWFwcGluZy5tb2RlcmF0ZSA/PyBkZWZhdWx0QXVkaXRMZXZlbE1hcHBpbmcubW9kZXJhdGUsXG4gICAgbG93OiBtYXBwaW5nLmxvdyA/PyBkZWZhdWx0QXVkaXRMZXZlbE1hcHBpbmcubG93LFxuICAgIGluZm86IG1hcHBpbmcuaW5mbyA/PyBkZWZhdWx0QXVkaXRMZXZlbE1hcHBpbmcuaW5mbyxcbiAgfTtcbn1cblxuZXhwb3J0IGNvbnN0IGpzUGFja2FnZXNQbHVnaW5Db25maWdTY2hlbWEgPSB6Lm9iamVjdCh7XG4gIGNoZWNrczogelxuICAgIC5hcnJheShwYWNrYWdlQ29tbWFuZFNjaGVtYSwge1xuICAgICAgZGVzY3JpcHRpb246XG4gICAgICAgICdQYWNrYWdlIG1hbmFnZXIgY29tbWFuZHMgdG8gYmUgcnVuLiBEZWZhdWx0cyB0byBib3RoIGF1ZGl0IGFuZCBvdXRkYXRlZC4nLFxuICAgIH0pXG4gICAgLm1pbigxKVxuICAgIC5kZWZhdWx0KFsnYXVkaXQnLCAnb3V0ZGF0ZWQnXSksXG4gIHBhY2thZ2VNYW5hZ2VyOiBwYWNrYWdlTWFuYWdlcklkU2NoZW1hXG4gICAgLmRlc2NyaWJlKCdQYWNrYWdlIG1hbmFnZXIgdG8gYmUgdXNlZC4nKVxuICAgIC5vcHRpb25hbCgpLFxuICBkZXBlbmRlbmN5R3JvdXBzOiB6XG4gICAgLmFycmF5KGRlcGVuZGVuY3lHcm91cFNjaGVtYSlcbiAgICAubWluKDEpXG4gICAgLmRlZmF1bHQoWydwcm9kJywgJ2RldiddKSxcbiAgYXVkaXRMZXZlbE1hcHBpbmc6IHpcbiAgICAucmVjb3JkKHBhY2thZ2VBdWRpdExldmVsU2NoZW1hLCBpc3N1ZVNldmVyaXR5U2NoZW1hLCB7XG4gICAgICBkZXNjcmlwdGlvbjpcbiAgICAgICAgJ01hcHBpbmcgb2YgYXVkaXQgbGV2ZWxzIHRvIGlzc3VlIHNldmVyaXR5LiBDdXN0b20gbWFwcGluZyBvciBvdmVycmlkZXMgbWF5IGJlIGVudGVyZWQgbWFudWFsbHksIG90aGVyd2lzZSBoYXMgYSBkZWZhdWx0IHByZXNldC4nLFxuICAgIH0pXG4gICAgLmRlZmF1bHQoZGVmYXVsdEF1ZGl0TGV2ZWxNYXBwaW5nKVxuICAgIC50cmFuc2Zvcm0oZmlsbEF1ZGl0TGV2ZWxNYXBwaW5nKSxcbiAgcGFja2FnZUpzb25QYXRoczogcGFja2FnZUpzb25QYXRoU2NoZW1hLFxufSk7XG5cbmV4cG9ydCB0eXBlIEpTUGFja2FnZXNQbHVnaW5Db25maWcgPSB6LmlucHV0PFxuICB0eXBlb2YganNQYWNrYWdlc1BsdWdpbkNvbmZpZ1NjaGVtYVxuPjtcblxuZXhwb3J0IHR5cGUgRmluYWxKU1BhY2thZ2VzUGx1Z2luQ29uZmlnID0gUmVxdWlyZWQ8XG4gIHouaW5mZXI8dHlwZW9mIGpzUGFja2FnZXNQbHVnaW5Db25maWdTY2hlbWE+XG4+O1xuIiwgImNvbnN0IF9faW5qZWN0ZWRfZmlsZW5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWpzLXBhY2thZ2VzL3NyYy9saWIvY29uc3RhbnRzLnRzXCI7Y29uc3QgX19pbmplY3RlZF9kaXJuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1qcy1wYWNrYWdlcy9zcmMvbGliXCI7Y29uc3QgX19pbmplY3RlZF9pbXBvcnRfbWV0YV91cmxfXyA9IFwiZmlsZTovLy9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1qcy1wYWNrYWdlcy9zcmMvbGliL2NvbnN0YW50cy50c1wiO2ltcG9ydCB0eXBlIHsgSXNzdWVTZXZlcml0eSB9IGZyb20gJ0Bjb2RlLXB1c2h1cC9tb2RlbHMnO1xuaW1wb3J0IHR5cGUgeyBEZXBlbmRlbmN5R3JvdXAsIFBhY2thZ2VBdWRpdExldmVsIH0gZnJvbSAnLi9jb25maWcuanMnO1xuaW1wb3J0IHR5cGUgeyBEZXBlbmRlbmN5R3JvdXBMb25nIH0gZnJvbSAnLi9ydW5uZXIvb3V0ZGF0ZWQvdHlwZXMuanMnO1xuXG5leHBvcnQgY29uc3QgZGVmYXVsdEF1ZGl0TGV2ZWxNYXBwaW5nOiBSZWNvcmQ8XG4gIFBhY2thZ2VBdWRpdExldmVsLFxuICBJc3N1ZVNldmVyaXR5XG4+ID0ge1xuICBjcml0aWNhbDogJ2Vycm9yJyxcbiAgaGlnaDogJ2Vycm9yJyxcbiAgbW9kZXJhdGU6ICd3YXJuaW5nJyxcbiAgbG93OiAnd2FybmluZycsXG4gIGluZm86ICdpbmZvJyxcbn07XG5cbmV4cG9ydCBjb25zdCBkZXBlbmRlbmN5R3JvdXBUb0xvbmc6IFJlY29yZDxcbiAgRGVwZW5kZW5jeUdyb3VwLFxuICBEZXBlbmRlbmN5R3JvdXBMb25nXG4+ID0ge1xuICBwcm9kOiAnZGVwZW5kZW5jaWVzJyxcbiAgZGV2OiAnZGV2RGVwZW5kZW5jaWVzJyxcbiAgb3B0aW9uYWw6ICdvcHRpb25hbERlcGVuZGVuY2llcycsXG59O1xuXG5leHBvcnQgY29uc3QgZGVwZW5kZW5jeUdyb3VwV2VpZ2h0czogUmVjb3JkPERlcGVuZGVuY3lHcm91cCwgbnVtYmVyPiA9IHtcbiAgLyogZXNsaW50LWRpc2FibGUgQHR5cGVzY3JpcHQtZXNsaW50L25vLW1hZ2ljLW51bWJlcnMgKi9cbiAgcHJvZDogODAsXG4gIGRldjogMTUsXG4gIG9wdGlvbmFsOiA1LFxuICAvKiBlc2xpbnQtZW5hYmxlIEB0eXBlc2NyaXB0LWVzbGludC9uby1tYWdpYy1udW1iZXJzICovXG59O1xuXG5leHBvcnQgY29uc3QgZGVwZW5kZW5jeURvY3M6IFJlY29yZDxEZXBlbmRlbmN5R3JvdXAsIHN0cmluZz4gPSB7XG4gIHByb2Q6ICdodHRwczovL2NsYXNzaWMueWFybnBrZy5jb20vZG9jcy9kZXBlbmRlbmN5LXR5cGVzI3RvYy1kZXBlbmRlbmNpZXMnLFxuICBkZXY6ICdodHRwczovL2NsYXNzaWMueWFybnBrZy5jb20vZG9jcy9kZXBlbmRlbmN5LXR5cGVzI3RvYy1kZXZkZXBlbmRlbmNpZXMnLFxuICBvcHRpb25hbDpcbiAgICAnaHR0cHM6Ly9jbGFzc2ljLnlhcm5wa2cuY29tL2RvY3MvZGVwZW5kZW5jeS10eXBlcyN0b2Mtb3B0aW9uYWxkZXBlbmRlbmNpZXMnLFxufTtcbiIsICJjb25zdCBfX2luamVjdGVkX2ZpbGVuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1qcy1wYWNrYWdlcy9zcmMvbGliL3BhY2thZ2UtbWFuYWdlcnMvbnBtL25wbS50c1wiO2NvbnN0IF9faW5qZWN0ZWRfZGlybmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tanMtcGFja2FnZXMvc3JjL2xpYi9wYWNrYWdlLW1hbmFnZXJzL25wbVwiO2NvbnN0IF9faW5qZWN0ZWRfaW1wb3J0X21ldGFfdXJsX18gPSBcImZpbGU6Ly8vaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tanMtcGFja2FnZXMvc3JjL2xpYi9wYWNrYWdlLW1hbmFnZXJzL25wbS9ucG0udHNcIjtpbXBvcnQgeyBvYmplY3RUb0tleXMgfSBmcm9tICdAY29kZS1wdXNodXAvdXRpbHMnO1xuaW1wb3J0IHR5cGUgeyBEZXBlbmRlbmN5R3JvdXAgfSBmcm9tICcuLi8uLi9jb25maWcuanMnO1xuaW1wb3J0IHsgZmlsdGVyQXVkaXRSZXN1bHQgfSBmcm9tICcuLi8uLi9ydW5uZXIvdXRpbHMuanMnO1xuaW1wb3J0IHsgQ09NTU9OX0FVRElUX0FSR1MsIENPTU1PTl9PVVREQVRFRF9BUkdTIH0gZnJvbSAnLi4vY29uc3RhbnRzLmpzJztcbmltcG9ydCB0eXBlIHsgQXVkaXRSZXN1bHRzLCBQYWNrYWdlTWFuYWdlciB9IGZyb20gJy4uL3R5cGVzLmpzJztcbmltcG9ydCB7IG5wbVRvQXVkaXRSZXN1bHQgfSBmcm9tICcuL2F1ZGl0LXJlc3VsdC5qcyc7XG5pbXBvcnQgeyBucG1Ub091dGRhdGVkUmVzdWx0IH0gZnJvbSAnLi9vdXRkYXRlZC1yZXN1bHQuanMnO1xuXG5jb25zdCBucG1EZXBlbmRlbmN5T3B0aW9uczogUmVjb3JkPERlcGVuZGVuY3lHcm91cCwgc3RyaW5nW10+ID0ge1xuICBwcm9kOiBbJy0tb21pdD1kZXYnLCAnLS1vbWl0PW9wdGlvbmFsJ10sXG4gIGRldjogWyctLWluY2x1ZGU9ZGV2JywgJy0tb21pdD1vcHRpb25hbCddLFxuICBvcHRpb25hbDogWyctLWluY2x1ZGU9b3B0aW9uYWwnLCAnLS1vbWl0PWRldiddLFxufTtcblxuZXhwb3J0IGNvbnN0IG5wbVBhY2thZ2VNYW5hZ2VyOiBQYWNrYWdlTWFuYWdlciA9IHtcbiAgc2x1ZzogJ25wbScsXG4gIG5hbWU6ICdOUE0nLFxuICBjb21tYW5kOiAnbnBtJyxcbiAgaWNvbjogJ25wbScsXG4gIGRvY3M6IHtcbiAgICBob21lcGFnZTogJ2h0dHBzOi8vZG9jcy5ucG1qcy5jb20vJyxcbiAgICBhdWRpdDogJ2h0dHBzOi8vZG9jcy5ucG1qcy5jb20vY2xpL2NvbW1hbmRzL25wbS1hdWRpdCcsXG4gICAgb3V0ZGF0ZWQ6ICdodHRwczovL2RvY3MubnBtanMuY29tL2NsaS9jb21tYW5kcy9ucG0tb3V0ZGF0ZWQnLFxuICB9LFxuICBhdWRpdDoge1xuICAgIGdldENvbW1hbmRBcmdzOiBncm91cERlcCA9PiBbXG4gICAgICAuLi5DT01NT05fQVVESVRfQVJHUyxcbiAgICAgIC4uLm5wbURlcGVuZGVuY3lPcHRpb25zW2dyb3VwRGVwXSxcbiAgICAgICctLWF1ZGl0LWxldmVsPW5vbmUnLFxuICAgIF0sXG4gICAgdW5pZnlSZXN1bHQ6IG5wbVRvQXVkaXRSZXN1bHQsXG4gICAgLy8gcHJvZCBkZXBlbmRlbmNpZXMgbmVlZCB0byBiZSBmaWx0ZXJlZCBvdXQgbWFudWFsbHkgc2luY2UgdjEwXG4gICAgcG9zdFByb2Nlc3NSZXN1bHQ6IChyZXN1bHRzOiBBdWRpdFJlc3VsdHMpID0+IHtcbiAgICAgIGNvbnN0IGRlcEdyb3VwcyA9IG9iamVjdFRvS2V5cyhyZXN1bHRzKTtcbiAgICAgIGNvbnN0IGRldkZpbHRlciA9XG4gICAgICAgIHJlc3VsdHMuZGV2ICYmIHJlc3VsdHMucHJvZFxuICAgICAgICAgID8gZmlsdGVyQXVkaXRSZXN1bHQocmVzdWx0cy5kZXYsICduYW1lJywgcmVzdWx0cy5wcm9kKVxuICAgICAgICAgIDogcmVzdWx0cy5kZXY7XG4gICAgICBjb25zdCBvcHRpb25hbEZpbHRlciA9XG4gICAgICAgIHJlc3VsdHMub3B0aW9uYWwgJiYgcmVzdWx0cy5wcm9kXG4gICAgICAgICAgPyBmaWx0ZXJBdWRpdFJlc3VsdChyZXN1bHRzLm9wdGlvbmFsLCAnbmFtZScsIHJlc3VsdHMucHJvZClcbiAgICAgICAgICA6IHJlc3VsdHMub3B0aW9uYWw7XG5cbiAgICAgIHJldHVybiB7XG4gICAgICAgIC4uLihkZXBHcm91cHMuaW5jbHVkZXMoJ3Byb2QnKSAmJiB7IHByb2Q6IHJlc3VsdHMucHJvZCB9KSxcbiAgICAgICAgLi4uKGRlcEdyb3Vwcy5pbmNsdWRlcygnZGV2JykgJiYgeyBkZXY6IGRldkZpbHRlciB9KSxcbiAgICAgICAgLi4uKGRlcEdyb3Vwcy5pbmNsdWRlcygnb3B0aW9uYWwnKSAmJiB7IG9wdGlvbmFsOiBvcHRpb25hbEZpbHRlciB9KSxcbiAgICAgIH07XG4gICAgfSxcbiAgfSxcbiAgb3V0ZGF0ZWQ6IHtcbiAgICBjb21tYW5kQXJnczogWy4uLkNPTU1PTl9PVVREQVRFRF9BUkdTLCAnLS1sb25nJ10sXG4gICAgdW5pZnlSZXN1bHQ6IG5wbVRvT3V0ZGF0ZWRSZXN1bHQsXG4gIH0sXG59O1xuIiwgImNvbnN0IF9faW5qZWN0ZWRfZmlsZW5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWpzLXBhY2thZ2VzL3NyYy9saWIvcnVubmVyL3V0aWxzLnRzXCI7Y29uc3QgX19pbmplY3RlZF9kaXJuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1qcy1wYWNrYWdlcy9zcmMvbGliL3J1bm5lclwiO2NvbnN0IF9faW5qZWN0ZWRfaW1wb3J0X21ldGFfdXJsX18gPSBcImZpbGU6Ly8vaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tanMtcGFja2FnZXMvc3JjL2xpYi9ydW5uZXIvdXRpbHMudHNcIjtpbXBvcnQgcGF0aCBmcm9tICdub2RlOnBhdGgnO1xuaW1wb3J0IHtcbiAgY3Jhd2xGaWxlU3lzdGVtLFxuICBvYmplY3RGcm9tRW50cmllcyxcbiAgb2JqZWN0VG9LZXlzLFxuICByZWFkSnNvbkZpbGUsXG59IGZyb20gJ0Bjb2RlLXB1c2h1cC91dGlscyc7XG5pbXBvcnQgdHlwZSB7IEF1ZGl0UmVzdWx0LCBWdWxuZXJhYmlsaXR5IH0gZnJvbSAnLi9hdWRpdC90eXBlcy5qcyc7XG5pbXBvcnQge1xuICB0eXBlIERlcGVuZGVuY3lHcm91cExvbmcsXG4gIHR5cGUgRGVwZW5kZW5jeVRvdGFscyxcbiAgdHlwZSBQYWNrYWdlSnNvbixcbiAgZGVwZW5kZW5jeUdyb3VwTG9uZyxcbn0gZnJvbSAnLi9vdXRkYXRlZC90eXBlcy5qcyc7XG5cbmV4cG9ydCBmdW5jdGlvbiBmaWx0ZXJBdWRpdFJlc3VsdChcbiAgcmVzdWx0OiBBdWRpdFJlc3VsdCxcbiAga2V5OiBrZXlvZiBWdWxuZXJhYmlsaXR5LFxuICByZWZlcmVuY2VSZXN1bHQ/OiBBdWRpdFJlc3VsdCxcbik6IEF1ZGl0UmVzdWx0IHtcbiAgaWYgKHJlc3VsdC52dWxuZXJhYmlsaXRpZXMubGVuZ3RoID09PSAwKSB7XG4gICAgcmV0dXJuIHJlc3VsdDtcbiAgfVxuXG4gIGNvbnN0IHVuaXF1ZVJlc3VsdCA9IHJlc3VsdC52dWxuZXJhYmlsaXRpZXMucmVkdWNlPEF1ZGl0UmVzdWx0PihcbiAgICAoYWNjLCByZWYpID0+IHtcbiAgICAgIGNvbnN0IG1hdGNoUmVmZXJlbmNlID0gcmVmZXJlbmNlUmVzdWx0ID8/IGFjYztcbiAgICAgIGNvbnN0IGlzTWF0Y2ggPSBtYXRjaFJlZmVyZW5jZS52dWxuZXJhYmlsaXRpZXNcbiAgICAgICAgLm1hcCh2dWxuZXJhYmlsaXR5ID0+IHZ1bG5lcmFiaWxpdHlba2V5XSlcbiAgICAgICAgLmluY2x1ZGVzKHJlZltrZXldKTtcblxuICAgICAgaWYgKGlzTWF0Y2gpIHtcbiAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICB2dWxuZXJhYmlsaXRpZXM6IGFjYy52dWxuZXJhYmlsaXRpZXMsXG4gICAgICAgICAgc3VtbWFyeToge1xuICAgICAgICAgICAgLi4uYWNjLnN1bW1hcnksXG4gICAgICAgICAgICBbcmVmLnNldmVyaXR5XTogYWNjLnN1bW1hcnlbcmVmLnNldmVyaXR5XSAtIDEsXG4gICAgICAgICAgICB0b3RhbDogYWNjLnN1bW1hcnkudG90YWwgLSAxLFxuICAgICAgICAgIH0sXG4gICAgICAgIH07XG4gICAgICB9XG5cbiAgICAgIHJldHVybiB7XG4gICAgICAgIHZ1bG5lcmFiaWxpdGllczogWy4uLmFjYy52dWxuZXJhYmlsaXRpZXMsIHJlZl0sXG4gICAgICAgIHN1bW1hcnk6IGFjYy5zdW1tYXJ5LFxuICAgICAgfTtcbiAgICB9LFxuICAgIHsgdnVsbmVyYWJpbGl0aWVzOiBbXSwgc3VtbWFyeTogcmVzdWx0LnN1bW1hcnkgfSxcbiAgKTtcblxuICByZXR1cm4ge1xuICAgIHZ1bG5lcmFiaWxpdGllczogdW5pcXVlUmVzdWx0LnZ1bG5lcmFiaWxpdGllcyxcbiAgICBzdW1tYXJ5OiB1bmlxdWVSZXN1bHQuc3VtbWFyeSxcbiAgfTtcbn1cblxuLy8gVE9ETzogdXNlIC5naXRpZ25vcmVcbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBmaW5kQWxsUGFja2FnZUpzb24oKTogUHJvbWlzZTxzdHJpbmdbXT4ge1xuICByZXR1cm4gKFxuICAgIGF3YWl0IGNyYXdsRmlsZVN5c3RlbSh7XG4gICAgICBkaXJlY3Rvcnk6ICcuJyxcbiAgICAgIHBhdHRlcm46IC8oXnxbXFxcXC9dKXBhY2thZ2VcXC5qc29uJC8sXG4gICAgfSlcbiAgKS5maWx0ZXIoXG4gICAgZmlsZVBhdGggPT5cbiAgICAgICFmaWxlUGF0aC5zdGFydHNXaXRoKGBub2RlX21vZHVsZXMke3BhdGguc2VwfWApICYmXG4gICAgICAhZmlsZVBhdGguaW5jbHVkZXMoYCR7cGF0aC5zZXB9bm9kZV9tb2R1bGVzJHtwYXRoLnNlcH1gKSAmJlxuICAgICAgIWZpbGVQYXRoLnN0YXJ0c1dpdGgoYC5ueCR7cGF0aC5zZXB9YCksXG4gICk7XG59XG5cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBnZXRUb3RhbERlcGVuZGVuY2llcyhcbiAgcGFja2FnZUpzb25QYXRoczogc3RyaW5nW10sXG4pOiBQcm9taXNlPERlcGVuZGVuY3lUb3RhbHM+IHtcbiAgY29uc3QgcGFyc2VkRGVwcyA9IGF3YWl0IFByb21pc2UuYWxsKFxuICAgIHBhY2thZ2VKc29uUGF0aHMubWFwKHJlYWRKc29uRmlsZTxQYWNrYWdlSnNvbj4pLFxuICApO1xuXG4gIGNvbnN0IG1lcmdlZERlcHMgPSBwYXJzZWREZXBzLnJlZHVjZTxSZWNvcmQ8RGVwZW5kZW5jeUdyb3VwTG9uZywgc3RyaW5nW10+PihcbiAgICAoYWNjLCBkZXBNYXBwZXIpID0+XG4gICAgICBvYmplY3RGcm9tRW50cmllcyhcbiAgICAgICAgZGVwZW5kZW5jeUdyb3VwTG9uZy5tYXAoZ3JvdXAgPT4ge1xuICAgICAgICAgIGNvbnN0IGRlcHMgPSBkZXBNYXBwZXJbZ3JvdXBdO1xuICAgICAgICAgIHJldHVybiBbXG4gICAgICAgICAgICBncm91cCxcbiAgICAgICAgICAgIFsuLi5hY2NbZ3JvdXBdLCAuLi4oZGVwcyA9PSBudWxsID8gW10gOiBvYmplY3RUb0tleXMoZGVwcykpXSxcbiAgICAgICAgICBdO1xuICAgICAgICB9KSxcbiAgICAgICksXG4gICAgeyBkZXBlbmRlbmNpZXM6IFtdLCBkZXZEZXBlbmRlbmNpZXM6IFtdLCBvcHRpb25hbERlcGVuZGVuY2llczogW10gfSxcbiAgKTtcbiAgcmV0dXJuIG9iamVjdEZyb21FbnRyaWVzKFxuICAgIG9iamVjdFRvS2V5cyhtZXJnZWREZXBzKS5tYXAoZGVwcyA9PiBbXG4gICAgICBkZXBzLFxuICAgICAgbmV3IFNldChtZXJnZWREZXBzW2RlcHNdKS5zaXplLFxuICAgIF0pLFxuICApO1xufVxuIiwgImNvbnN0IF9faW5qZWN0ZWRfZmlsZW5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWpzLXBhY2thZ2VzL3NyYy9saWIvcGFja2FnZS1tYW5hZ2Vycy9jb25zdGFudHMudHNcIjtjb25zdCBfX2luamVjdGVkX2Rpcm5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWpzLXBhY2thZ2VzL3NyYy9saWIvcGFja2FnZS1tYW5hZ2Vyc1wiO2NvbnN0IF9faW5qZWN0ZWRfaW1wb3J0X21ldGFfdXJsX18gPSBcImZpbGU6Ly8vaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tanMtcGFja2FnZXMvc3JjL2xpYi9wYWNrYWdlLW1hbmFnZXJzL2NvbnN0YW50cy50c1wiO2V4cG9ydCBjb25zdCBDT01NT05fQVVESVRfQVJHUyA9IFsnYXVkaXQnLCAnLS1qc29uJ107XG5leHBvcnQgY29uc3QgQ09NTU9OX09VVERBVEVEX0FSR1MgPSBbJ291dGRhdGVkJywgJy0tanNvbiddO1xuIiwgImNvbnN0IF9faW5qZWN0ZWRfZmlsZW5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWpzLXBhY2thZ2VzL3NyYy9saWIvcGFja2FnZS1tYW5hZ2Vycy9ucG0vYXVkaXQtcmVzdWx0LnRzXCI7Y29uc3QgX19pbmplY3RlZF9kaXJuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1qcy1wYWNrYWdlcy9zcmMvbGliL3BhY2thZ2UtbWFuYWdlcnMvbnBtXCI7Y29uc3QgX19pbmplY3RlZF9pbXBvcnRfbWV0YV91cmxfXyA9IFwiZmlsZTovLy9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1qcy1wYWNrYWdlcy9zcmMvbGliL3BhY2thZ2UtbWFuYWdlcnMvbnBtL2F1ZGl0LXJlc3VsdC50c1wiO2ltcG9ydCB7IG9iamVjdFRvRW50cmllcyB9IGZyb20gJ0Bjb2RlLXB1c2h1cC91dGlscyc7XG5pbXBvcnQgdHlwZSB7IEF1ZGl0UmVzdWx0LCBWdWxuZXJhYmlsaXR5IH0gZnJvbSAnLi4vLi4vcnVubmVyL2F1ZGl0L3R5cGVzLmpzJztcbmltcG9ydCB0eXBlIHtcbiAgTnBtQWR2aXNvcnksXG4gIE5wbUF1ZGl0UmVzdWx0SnNvbixcbiAgTnBtRml4SW5mb3JtYXRpb24sXG4gIE5wbVZ1bG5lcmFiaWxpdGllcyxcbn0gZnJvbSAnLi90eXBlcy5qcyc7XG5cbmV4cG9ydCBmdW5jdGlvbiBucG1Ub0F1ZGl0UmVzdWx0KG91dHB1dDogc3RyaW5nKTogQXVkaXRSZXN1bHQge1xuICBjb25zdCBucG1BdWRpdCA9IEpTT04ucGFyc2Uob3V0cHV0KSBhcyBOcG1BdWRpdFJlc3VsdEpzb247XG5cbiAgY29uc3QgdnVsbmVyYWJpbGl0aWVzID0gb2JqZWN0VG9FbnRyaWVzKG5wbUF1ZGl0LnZ1bG5lcmFiaWxpdGllcykubWFwKFxuICAgIChbbmFtZSwgZGV0YWlsXSk6IFZ1bG5lcmFiaWxpdHkgPT4ge1xuICAgICAgY29uc3QgYWR2aXNvcnkgPSBucG1Ub0Fkdmlzb3J5KG5hbWUsIG5wbUF1ZGl0LnZ1bG5lcmFiaWxpdGllcyk7XG4gICAgICByZXR1cm4ge1xuICAgICAgICBuYW1lOiBuYW1lLnRvU3RyaW5nKCksXG4gICAgICAgIHNldmVyaXR5OiBkZXRhaWwuc2V2ZXJpdHksXG4gICAgICAgIHZlcnNpb25SYW5nZTogZGV0YWlsLnJhbmdlLFxuICAgICAgICBkaXJlY3REZXBlbmRlbmN5OiBkZXRhaWwuaXNEaXJlY3QgPyB0cnVlIDogKGRldGFpbC5lZmZlY3RzWzBdID8/ICcnKSxcbiAgICAgICAgZml4SW5mb3JtYXRpb246IG5wbVRvRml4SW5mb3JtYXRpb24oZGV0YWlsLmZpeEF2YWlsYWJsZSksXG4gICAgICAgIC4uLihhZHZpc29yeSAhPSBudWxsICYmIHtcbiAgICAgICAgICB0aXRsZTogYWR2aXNvcnkudGl0bGUsXG4gICAgICAgICAgdXJsOiBhZHZpc29yeS51cmwsXG4gICAgICAgIH0pLFxuICAgICAgfTtcbiAgICB9LFxuICApO1xuXG4gIHJldHVybiB7XG4gICAgdnVsbmVyYWJpbGl0aWVzLFxuICAgIHN1bW1hcnk6IG5wbUF1ZGl0Lm1ldGFkYXRhLnZ1bG5lcmFiaWxpdGllcyxcbiAgfTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIG5wbVRvRml4SW5mb3JtYXRpb24oXG4gIGZpeEF2YWlsYWJsZTogYm9vbGVhbiB8IE5wbUZpeEluZm9ybWF0aW9uLFxuKTogc3RyaW5nIHtcbiAgaWYgKHR5cGVvZiBmaXhBdmFpbGFibGUgPT09ICdib29sZWFuJykge1xuICAgIHJldHVybiBmaXhBdmFpbGFibGUgPyAnRml4IGlzIGF2YWlsYWJsZS4nIDogJyc7XG4gIH1cblxuICByZXR1cm4gYEZpeCBhdmFpbGFibGU6IFVwZGF0ZSBcXGAke2ZpeEF2YWlsYWJsZS5uYW1lfVxcYCB0byB2ZXJzaW9uICoqJHtcbiAgICBmaXhBdmFpbGFibGUudmVyc2lvblxuICB9Kioke2ZpeEF2YWlsYWJsZS5pc1NlbVZlck1ham9yID8gJyAoYnJlYWtpbmcgY2hhbmdlKS4nIDogJy4nfWA7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBucG1Ub0Fkdmlzb3J5KFxuICBuYW1lOiBzdHJpbmcsXG4gIHZ1bG5lcmFiaWxpdGllczogTnBtVnVsbmVyYWJpbGl0aWVzLFxuICBwcmV2Tm9kZXMgPSBuZXcgU2V0PHN0cmluZz4oKSxcbik6IE5wbUFkdmlzb3J5IHwgbnVsbCB7XG4gIGNvbnN0IGFkdmlzb3J5ID0gdnVsbmVyYWJpbGl0aWVzW25hbWVdPy52aWE7XG5cbiAgaWYgKFxuICAgIEFycmF5LmlzQXJyYXkoYWR2aXNvcnkpICYmXG4gICAgYWR2aXNvcnkubGVuZ3RoID4gMCAmJlxuICAgIHR5cGVvZiBhZHZpc29yeVswXSA9PT0gJ29iamVjdCdcbiAgKSB7XG4gICAgcmV0dXJuIHsgdGl0bGU6IGFkdmlzb3J5WzBdLnRpdGxlLCB1cmw6IGFkdmlzb3J5WzBdLnVybCB9O1xuICB9XG5cbiAgLy8gQ3Jvc3MtcmVmZXJlbmNlcyBhbm90aGVyIHZ1bG5lcmFiaWxpdHlcbiAgaWYgKFxuICAgIEFycmF5LmlzQXJyYXkoYWR2aXNvcnkpICYmXG4gICAgYWR2aXNvcnkubGVuZ3RoID4gMCAmJlxuICAgIGFkdmlzb3J5LmV2ZXJ5KCh2YWx1ZSk6IHZhbHVlIGlzIHN0cmluZyA9PiB0eXBlb2YgdmFsdWUgPT09ICdzdHJpbmcnKVxuICApIHtcbiAgICAvKiBlc2xpbnQtZGlzYWJsZSBmdW5jdGlvbmFsL25vLWxldCwgZnVuY3Rpb25hbC9pbW11dGFibGUtZGF0YSwgZnVuY3Rpb25hbC9uby1sb29wLXN0YXRlbWVudHMsIHByZWZlci1jb25zdCAqL1xuICAgIGxldCBhZHZpc29yeUluZm86IE5wbUFkdmlzb3J5IHwgbnVsbCA9IG51bGw7XG4gICAgbGV0IG5ld1JlZmVyZW5jZXM6IHN0cmluZ1tdID0gW107XG4gICAgbGV0IGFkdmlzb3J5SW5mb0ZvdW5kID0gZmFsc2U7XG4gICAgLyogZXNsaW50LWVuYWJsZSBmdW5jdGlvbmFsL25vLWxldCwgcHJlZmVyLWNvbnN0ICovXG5cbiAgICBmb3IgKGNvbnN0IHZpYSBvZiBhZHZpc29yeSkge1xuICAgICAgaWYgKCFwcmV2Tm9kZXMuaGFzKHZpYSkpIHtcbiAgICAgICAgbmV3UmVmZXJlbmNlcy5wdXNoKHZpYSk7XG4gICAgICB9XG4gICAgfVxuXG4gICAgd2hpbGUgKG5ld1JlZmVyZW5jZXMubGVuZ3RoID4gMCAmJiAhYWR2aXNvcnlJbmZvRm91bmQpIHtcbiAgICAgIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBAdHlwZXNjcmlwdC1lc2xpbnQvbm8tbm9uLW51bGwtYXNzZXJ0aW9uXG4gICAgICBjb25zdCByZWYgPSBuZXdSZWZlcmVuY2VzLnBvcCgpITtcbiAgICAgIHByZXZOb2Rlcy5hZGQocmVmKTtcbiAgICAgIGNvbnN0IHJlc3VsdCA9IG5wbVRvQWR2aXNvcnkocmVmLCB2dWxuZXJhYmlsaXRpZXMsIHByZXZOb2Rlcyk7XG5cbiAgICAgIGlmIChyZXN1bHQgIT0gbnVsbCkge1xuICAgICAgICBhZHZpc29yeUluZm8gPSB7IHRpdGxlOiByZXN1bHQudGl0bGUsIHVybDogcmVzdWx0LnVybCB9O1xuICAgICAgICBhZHZpc29yeUluZm9Gb3VuZCA9IHRydWU7XG4gICAgICB9XG4gICAgfVxuICAgIC8qIGVzbGludC1lbmFibGUgZnVuY3Rpb25hbC9pbW11dGFibGUtZGF0YSwgZnVuY3Rpb25hbC9uby1sb29wLXN0YXRlbWVudHMgKi9cblxuICAgIHJldHVybiBhZHZpc29yeUluZm87XG4gIH1cblxuICByZXR1cm4gbnVsbDtcbn1cbiIsICJjb25zdCBfX2luamVjdGVkX2ZpbGVuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1qcy1wYWNrYWdlcy9zcmMvbGliL3BhY2thZ2UtbWFuYWdlcnMvbnBtL291dGRhdGVkLXJlc3VsdC50c1wiO2NvbnN0IF9faW5qZWN0ZWRfZGlybmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tanMtcGFja2FnZXMvc3JjL2xpYi9wYWNrYWdlLW1hbmFnZXJzL25wbVwiO2NvbnN0IF9faW5qZWN0ZWRfaW1wb3J0X21ldGFfdXJsX18gPSBcImZpbGU6Ly8vaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tanMtcGFja2FnZXMvc3JjL2xpYi9wYWNrYWdlLW1hbmFnZXJzL25wbS9vdXRkYXRlZC1yZXN1bHQudHNcIjtpbXBvcnQgeyBvYmplY3RUb0VudHJpZXMgfSBmcm9tICdAY29kZS1wdXNodXAvdXRpbHMnO1xuaW1wb3J0IHR5cGUgeyBPdXRkYXRlZFJlc3VsdCB9IGZyb20gJy4uLy4uL3J1bm5lci9vdXRkYXRlZC90eXBlcy5qcyc7XG5pbXBvcnQgdHlwZSB7IE5wbU5vcm1hbGl6ZWRPdmVydmlldywgTnBtT3V0ZGF0ZWRSZXN1bHRKc29uIH0gZnJvbSAnLi90eXBlcy5qcyc7XG5cbmV4cG9ydCBmdW5jdGlvbiBucG1Ub091dGRhdGVkUmVzdWx0KG91dHB1dDogc3RyaW5nKTogT3V0ZGF0ZWRSZXN1bHQge1xuICBjb25zdCBucG1PdXRkYXRlZCA9IEpTT04ucGFyc2Uob3V0cHV0KSBhcyBOcG1PdXRkYXRlZFJlc3VsdEpzb247XG4gIC8vIGN1cnJlbnQgbWlnaHQgYmUgbWlzc2luZyBpbiBzb21lIGNhc2VzXG4gIC8vIGh0dHBzOi8vc3RhY2tvdmVyZmxvdy5jb20vcXVlc3Rpb25zLzQyMjY3MTAxL25wbS1vdXRkYXRlZC1jb21tYW5kLXNob3dzLW1pc3NpbmctaW4tY3VycmVudC12ZXJzaW9uXG4gIHJldHVybiBvYmplY3RUb0VudHJpZXMobnBtT3V0ZGF0ZWQpXG4gICAgLmZpbHRlcihcbiAgICAgIChlbnRyeSk6IGVudHJ5IGlzIFtzdHJpbmcsIE5wbU5vcm1hbGl6ZWRPdmVydmlld10gPT5cbiAgICAgICAgZW50cnlbMV0uY3VycmVudCAhPSBudWxsLFxuICAgIClcbiAgICAubWFwKChbbmFtZSwgb3ZlcnZpZXddKSA9PiAoe1xuICAgICAgbmFtZSxcbiAgICAgIGN1cnJlbnQ6IG92ZXJ2aWV3LmN1cnJlbnQsXG4gICAgICBsYXRlc3Q6IG92ZXJ2aWV3LmxhdGVzdCxcbiAgICAgIHR5cGU6IG92ZXJ2aWV3LnR5cGUsXG4gICAgICAuLi4ob3ZlcnZpZXcuaG9tZXBhZ2UgIT0gbnVsbCAmJiB7IHVybDogb3ZlcnZpZXcuaG9tZXBhZ2UgfSksXG4gICAgfSkpO1xufVxuIiwgImNvbnN0IF9faW5qZWN0ZWRfZmlsZW5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWpzLXBhY2thZ2VzL3NyYy9saWIvcGFja2FnZS1tYW5hZ2Vycy9wbnBtL3BucG0udHNcIjtjb25zdCBfX2luamVjdGVkX2Rpcm5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWpzLXBhY2thZ2VzL3NyYy9saWIvcGFja2FnZS1tYW5hZ2Vycy9wbnBtXCI7Y29uc3QgX19pbmplY3RlZF9pbXBvcnRfbWV0YV91cmxfXyA9IFwiZmlsZTovLy9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1qcy1wYWNrYWdlcy9zcmMvbGliL3BhY2thZ2UtbWFuYWdlcnMvcG5wbS9wbnBtLnRzXCI7aW1wb3J0IHsgb2JqZWN0VG9LZXlzIH0gZnJvbSAnQGNvZGUtcHVzaHVwL3V0aWxzJztcbmltcG9ydCB0eXBlIHsgRGVwZW5kZW5jeUdyb3VwIH0gZnJvbSAnLi4vLi4vY29uZmlnLmpzJztcbmltcG9ydCB7IGZpbHRlckF1ZGl0UmVzdWx0IH0gZnJvbSAnLi4vLi4vcnVubmVyL3V0aWxzLmpzJztcbmltcG9ydCB7IENPTU1PTl9BVURJVF9BUkdTLCBDT01NT05fT1VUREFURURfQVJHUyB9IGZyb20gJy4uL2NvbnN0YW50cy5qcyc7XG5pbXBvcnQgdHlwZSB7IEF1ZGl0UmVzdWx0cywgUGFja2FnZU1hbmFnZXIgfSBmcm9tICcuLi90eXBlcy5qcyc7XG5pbXBvcnQgeyBwbnBtVG9BdWRpdFJlc3VsdCB9IGZyb20gJy4vYXVkaXQtcmVzdWx0LmpzJztcbmltcG9ydCB7IHBucG1Ub091dGRhdGVkUmVzdWx0IH0gZnJvbSAnLi9vdXRkYXRlZC1yZXN1bHQuanMnO1xuXG5jb25zdCBwbnBtRGVwZW5kZW5jeU9wdGlvbnM6IFJlY29yZDxEZXBlbmRlbmN5R3JvdXAsIHN0cmluZ1tdPiA9IHtcbiAgcHJvZDogWyctLXByb2QnLCAnLS1uby1vcHRpb25hbCddLFxuICBkZXY6IFsnLS1kZXYnLCAnLS1uby1vcHRpb25hbCddLFxuICBvcHRpb25hbDogW10sXG59O1xuXG5leHBvcnQgY29uc3QgcG5wbVBhY2thZ2VNYW5hZ2VyOiBQYWNrYWdlTWFuYWdlciA9IHtcbiAgc2x1ZzogJ3BucG0nLFxuICBuYW1lOiAncG5wbScsXG4gIGNvbW1hbmQ6ICdwbnBtJyxcbiAgaWNvbjogJ3BucG0nLFxuICBkb2NzOiB7XG4gICAgaG9tZXBhZ2U6ICdodHRwczovL3BucG0uaW8vcG5wbS1jbGknLFxuICAgIGF1ZGl0OiAnaHR0cHM6Ly9wbnBtLmlvL2NsaS9hdWRpdC8nLFxuICAgIG91dGRhdGVkOiAnaHR0cHM6Ly9wbnBtLmlvL2NsaS9vdXRkYXRlZCcsXG4gIH0sXG4gIGF1ZGl0OiB7XG4gICAgZ2V0Q29tbWFuZEFyZ3M6IGdyb3VwRGVwID0+IFtcbiAgICAgIC4uLkNPTU1PTl9BVURJVF9BUkdTLFxuICAgICAgLi4ucG5wbURlcGVuZGVuY3lPcHRpb25zW2dyb3VwRGVwXSxcbiAgICBdLFxuICAgIGlnbm9yZUV4aXRDb2RlOiB0cnVlLFxuICAgIHVuaWZ5UmVzdWx0OiBwbnBtVG9BdWRpdFJlc3VsdCxcbiAgICAvLyBvcHRpb25hbCBkZXBlbmRlbmNpZXMgZG9uJ3QgaGF2ZSBhbiBleGNsdXNpdmUgb3B0aW9uIHNvIHRoZXkgbmVlZCBkdXBsaWNhdGVzIGZpbHRlcmVkIG91dFxuICAgIHBvc3RQcm9jZXNzUmVzdWx0OiAocmVzdWx0czogQXVkaXRSZXN1bHRzKSA9PiB7XG4gICAgICBjb25zdCBkZXBHcm91cHMgPSBvYmplY3RUb0tleXMocmVzdWx0cyk7XG4gICAgICBjb25zdCBwcm9kRmlsdGVyID1cbiAgICAgICAgcmVzdWx0cy5vcHRpb25hbCAmJiByZXN1bHRzLnByb2RcbiAgICAgICAgICA/IGZpbHRlckF1ZGl0UmVzdWx0KHJlc3VsdHMub3B0aW9uYWwsICdpZCcsIHJlc3VsdHMucHJvZClcbiAgICAgICAgICA6IHJlc3VsdHMub3B0aW9uYWw7XG4gICAgICBjb25zdCBkZXZGaWx0ZXIgPVxuICAgICAgICBwcm9kRmlsdGVyICYmIHJlc3VsdHMuZGV2XG4gICAgICAgICAgPyBmaWx0ZXJBdWRpdFJlc3VsdChwcm9kRmlsdGVyLCAnaWQnLCByZXN1bHRzLmRldilcbiAgICAgICAgICA6IHJlc3VsdHMub3B0aW9uYWw7XG5cbiAgICAgIHJldHVybiB7XG4gICAgICAgIC4uLihkZXBHcm91cHMuaW5jbHVkZXMoJ3Byb2QnKSAmJiB7IHByb2Q6IHJlc3VsdHMucHJvZCB9KSxcbiAgICAgICAgLi4uKGRlcEdyb3Vwcy5pbmNsdWRlcygnZGV2JykgJiYgeyBkZXY6IHJlc3VsdHMuZGV2IH0pLFxuICAgICAgICAuLi4ocmVzdWx0cy5vcHRpb25hbCAmJiB7IG9wdGlvbmFsOiBkZXZGaWx0ZXIgfSksXG4gICAgICB9O1xuICAgIH0sXG4gIH0sXG4gIG91dGRhdGVkOiB7XG4gICAgY29tbWFuZEFyZ3M6IENPTU1PTl9PVVREQVRFRF9BUkdTLFxuICAgIHVuaWZ5UmVzdWx0OiBwbnBtVG9PdXRkYXRlZFJlc3VsdCxcbiAgfSxcbn07XG4iLCAiY29uc3QgX19pbmplY3RlZF9maWxlbmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tanMtcGFja2FnZXMvc3JjL2xpYi9wYWNrYWdlLW1hbmFnZXJzL3BucG0vb3V0ZGF0ZWQtcmVzdWx0LnRzXCI7Y29uc3QgX19pbmplY3RlZF9kaXJuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1qcy1wYWNrYWdlcy9zcmMvbGliL3BhY2thZ2UtbWFuYWdlcnMvcG5wbVwiO2NvbnN0IF9faW5qZWN0ZWRfaW1wb3J0X21ldGFfdXJsX18gPSBcImZpbGU6Ly8vaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tanMtcGFja2FnZXMvc3JjL2xpYi9wYWNrYWdlLW1hbmFnZXJzL3BucG0vb3V0ZGF0ZWQtcmVzdWx0LnRzXCI7aW1wb3J0IHsgb2JqZWN0VG9FbnRyaWVzIH0gZnJvbSAnQGNvZGUtcHVzaHVwL3V0aWxzJztcbmltcG9ydCB0eXBlIHsgT3V0ZGF0ZWRSZXN1bHQgfSBmcm9tICcuLi8uLi9ydW5uZXIvb3V0ZGF0ZWQvdHlwZXMuanMnO1xuaW1wb3J0IHR5cGUgeyBQbnBtT3V0ZGF0ZWRSZXN1bHRKc29uIH0gZnJvbSAnLi90eXBlcy5qcyc7XG5pbXBvcnQgeyBmaWx0ZXJPdXRXYXJuaW5ncyB9IGZyb20gJy4vdXRpbHMuanMnO1xuXG5leHBvcnQgZnVuY3Rpb24gcG5wbVRvT3V0ZGF0ZWRSZXN1bHQob3V0cHV0OiBzdHJpbmcpOiBPdXRkYXRlZFJlc3VsdCB7XG4gIGNvbnN0IHBucG1PdXRkYXRlZCA9IEpTT04ucGFyc2UoXG4gICAgZmlsdGVyT3V0V2FybmluZ3Mob3V0cHV0KSxcbiAgKSBhcyBQbnBtT3V0ZGF0ZWRSZXN1bHRKc29uO1xuXG4gIHJldHVybiBvYmplY3RUb0VudHJpZXMocG5wbU91dGRhdGVkKS5tYXAoXG4gICAgKFtuYW1lLCB7IGN1cnJlbnQsIGxhdGVzdCwgZGVwZW5kZW5jeVR5cGU6IHR5cGUgfV0pID0+ICh7XG4gICAgICBuYW1lLFxuICAgICAgY3VycmVudCxcbiAgICAgIGxhdGVzdCxcbiAgICAgIHR5cGUsXG4gICAgfSksXG4gICk7XG59XG4iLCAiY29uc3QgX19pbmplY3RlZF9maWxlbmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tanMtcGFja2FnZXMvc3JjL2xpYi9wYWNrYWdlLW1hbmFnZXJzL3lhcm4tY2xhc3NpYy9hdWRpdC1yZXN1bHQudHNcIjtjb25zdCBfX2luamVjdGVkX2Rpcm5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWpzLXBhY2thZ2VzL3NyYy9saWIvcGFja2FnZS1tYW5hZ2Vycy95YXJuLWNsYXNzaWNcIjtjb25zdCBfX2luamVjdGVkX2ltcG9ydF9tZXRhX3VybF9fID0gXCJmaWxlOi8vL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWpzLXBhY2thZ2VzL3NyYy9saWIvcGFja2FnZS1tYW5hZ2Vycy95YXJuLWNsYXNzaWMvYXVkaXQtcmVzdWx0LnRzXCI7aW1wb3J0IHsgZnJvbUpzb25MaW5lcyB9IGZyb20gJ0Bjb2RlLXB1c2h1cC91dGlscyc7XG5pbXBvcnQgdHlwZSB7IEF1ZGl0UmVzdWx0LCBWdWxuZXJhYmlsaXR5IH0gZnJvbSAnLi4vLi4vcnVubmVyL2F1ZGl0L3R5cGVzLmpzJztcbmltcG9ydCB7IGZpbHRlckF1ZGl0UmVzdWx0IH0gZnJvbSAnLi4vLi4vcnVubmVyL3V0aWxzLmpzJztcbmltcG9ydCB0eXBlIHtcbiAgWWFybnYxQXVkaXRBZHZpc29yeSxcbiAgWWFybnYxQXVkaXRSZXN1bHRKc29uLFxuICBZYXJudjFBdWRpdFN1bW1hcnksXG59IGZyb20gJy4vdHlwZXMuanMnO1xuXG5leHBvcnQgZnVuY3Rpb24geWFybnYxVG9BdWRpdFJlc3VsdChvdXRwdXQ6IHN0cmluZyk6IEF1ZGl0UmVzdWx0IHtcbiAgY29uc3QgeWFybnYxUmVzdWx0ID0gZnJvbUpzb25MaW5lczxZYXJudjFBdWRpdFJlc3VsdEpzb24+KG91dHB1dCk7XG4gIGNvbnN0IFt5YXJudjFBZHZpc29yeSwgeWFybnYxU3VtbWFyeV0gPSB2YWxpZGF0ZVlhcm52MVJlc3VsdCh5YXJudjFSZXN1bHQpO1xuXG4gIGNvbnN0IHZ1bG5lcmFiaWxpdGllcyA9IHlhcm52MUFkdmlzb3J5Lm1hcChcbiAgICAoeyBkYXRhOiB7IHJlc29sdXRpb24sIGFkdmlzb3J5IH0gfSk6IFZ1bG5lcmFiaWxpdHkgPT4ge1xuICAgICAgY29uc3QgeyBpZCwgcGF0aCB9ID0gcmVzb2x1dGlvbjtcbiAgICAgIGNvbnN0IGRpcmVjdERlcGVuZGVuY3kgPSBwYXRoLnNsaWNlKDAsIHBhdGguaW5kZXhPZignPicpKTtcblxuICAgICAgY29uc3Qge1xuICAgICAgICBtb2R1bGVfbmFtZTogbmFtZSxcbiAgICAgICAgdGl0bGUsXG4gICAgICAgIHVybCxcbiAgICAgICAgc2V2ZXJpdHksXG4gICAgICAgIHZ1bG5lcmFibGVfdmVyc2lvbnM6IHZlcnNpb25SYW5nZSxcbiAgICAgICAgcmVjb21tZW5kYXRpb246IGZpeEluZm9ybWF0aW9uLFxuICAgICAgfSA9IGFkdmlzb3J5O1xuXG4gICAgICByZXR1cm4ge1xuICAgICAgICBuYW1lLFxuICAgICAgICB0aXRsZSxcbiAgICAgICAgaWQsXG4gICAgICAgIHVybCxcbiAgICAgICAgc2V2ZXJpdHksXG4gICAgICAgIHZlcnNpb25SYW5nZSxcbiAgICAgICAgZGlyZWN0RGVwZW5kZW5jeTogbmFtZSA9PT0gZGlyZWN0RGVwZW5kZW5jeSA/IHRydWUgOiBkaXJlY3REZXBlbmRlbmN5LFxuICAgICAgICBmaXhJbmZvcm1hdGlvbixcbiAgICAgIH07XG4gICAgfSxcbiAgKTtcblxuICBjb25zdCBzdW1tYXJ5ID0ge1xuICAgIC4uLnlhcm52MVN1bW1hcnkuZGF0YS52dWxuZXJhYmlsaXRpZXMsXG4gICAgdG90YWw6IE9iamVjdC52YWx1ZXMoeWFybnYxU3VtbWFyeS5kYXRhLnZ1bG5lcmFiaWxpdGllcykucmVkdWNlKFxuICAgICAgKGFjYywgYW1vdW50KSA9PiBhY2MgKyBhbW91bnQsXG4gICAgICAwLFxuICAgICksXG4gIH07XG5cbiAgLy8gZHVwbGljYXRlcyBhcmUgZmlsdGVyZWQgb3V0IGJhc2VkIG9uIHRoZWlyIElEXG4gIHJldHVybiBmaWx0ZXJBdWRpdFJlc3VsdCh7IHZ1bG5lcmFiaWxpdGllcywgc3VtbWFyeSB9LCAnaWQnKTtcbn1cblxuZnVuY3Rpb24gdmFsaWRhdGVZYXJudjFSZXN1bHQoXG4gIHJlc3VsdDogWWFybnYxQXVkaXRSZXN1bHRKc29uLFxuKTogW1lhcm52MUF1ZGl0QWR2aXNvcnlbXSwgWWFybnYxQXVkaXRTdW1tYXJ5XSB7XG4gIGNvbnN0IHN1bW1hcnkgPSByZXN1bHQuYXQoLTEpO1xuICBpZiAoc3VtbWFyeT8udHlwZSAhPT0gJ2F1ZGl0U3VtbWFyeScpIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoJ0ludmFsaWQgWWFybiB2MSBhdWRpdCByZXN1bHQgLSBubyBzdW1tYXJ5IGZvdW5kLicpO1xuICB9XG5cbiAgY29uc3QgdnVsbmVyYWJpbGl0aWVzID0gcmVzdWx0LmZpbHRlcihcbiAgICAoaXRlbSk6IGl0ZW0gaXMgWWFybnYxQXVkaXRBZHZpc29yeSA9PiBpdGVtLnR5cGUgPT09ICdhdWRpdEFkdmlzb3J5JyxcbiAgKTtcblxuICByZXR1cm4gW3Z1bG5lcmFiaWxpdGllcywgc3VtbWFyeV07XG59XG4iLCAiY29uc3QgX19pbmplY3RlZF9maWxlbmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tanMtcGFja2FnZXMvc3JjL2xpYi9wYWNrYWdlLW1hbmFnZXJzL3lhcm4tY2xhc3NpYy9vdXRkYXRlZC1yZXN1bHQudHNcIjtjb25zdCBfX2luamVjdGVkX2Rpcm5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWpzLXBhY2thZ2VzL3NyYy9saWIvcGFja2FnZS1tYW5hZ2Vycy95YXJuLWNsYXNzaWNcIjtjb25zdCBfX2luamVjdGVkX2ltcG9ydF9tZXRhX3VybF9fID0gXCJmaWxlOi8vL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWpzLXBhY2thZ2VzL3NyYy9saWIvcGFja2FnZS1tYW5hZ2Vycy95YXJuLWNsYXNzaWMvb3V0ZGF0ZWQtcmVzdWx0LnRzXCI7aW1wb3J0IHtcbiAgZnJvbUpzb25MaW5lcyxcbiAgb2JqZWN0RnJvbUVudHJpZXMsXG4gIG9iamVjdFRvRW50cmllcyxcbiAgb2JqZWN0VG9LZXlzLFxufSBmcm9tICdAY29kZS1wdXNodXAvdXRpbHMnO1xuaW1wb3J0IHR5cGUge1xuICBPdXRkYXRlZERlcGVuZGVuY3ksXG4gIE91dGRhdGVkUmVzdWx0LFxufSBmcm9tICcuLi8uLi9ydW5uZXIvb3V0ZGF0ZWQvdHlwZXMuanMnO1xuaW1wb3J0IHtcbiAgUkVRVUlSRURfT1VUREFURURfRklFTERTLFxuICBvdXRkYXRlZHRvRmllbGRNYXBwZXIsXG59IGZyb20gJy4vY29uc3RhbnRzLmpzJztcbmltcG9ydCB7XG4gIHR5cGUgWWFybnYxRmllbGROYW1lLFxuICB0eXBlIFlhcm52MU91dGRhdGVkUmVzdWx0SnNvbixcbiAgeWFybnYxRmllbGROYW1lcyxcbn0gZnJvbSAnLi90eXBlcy5qcyc7XG5cbmV4cG9ydCBmdW5jdGlvbiB5YXJudjFUb091dGRhdGVkUmVzdWx0KG91dHB1dDogc3RyaW5nKTogT3V0ZGF0ZWRSZXN1bHQge1xuICBjb25zdCB5YXJudjFPdXRkYXRlZCA9IGZyb21Kc29uTGluZXM8WWFybnYxT3V0ZGF0ZWRSZXN1bHRKc29uPihvdXRwdXQpO1xuICBjb25zdCBmaWVsZHMgPSB5YXJudjFPdXRkYXRlZFsxXS5kYXRhLmhlYWQ7XG4gIGNvbnN0IGRlcGVuZGVuY2llcyA9IHlhcm52MU91dGRhdGVkWzFdLmRhdGEuYm9keTtcblxuICAvLyBubyBvdXRkYXRlZCBkZXBlbmRlbmNpZXNcbiAgaWYgKGRlcGVuZGVuY2llcy5sZW5ndGggPT09IDApIHtcbiAgICByZXR1cm4gW107XG4gIH1cblxuICAvLyBtYXAgZHluYW1pYyBmaWVsZHNcbiAgdmFsaWRhdGVPdXRkYXRlZEZpZWxkcyhmaWVsZHMpO1xuICBjb25zdCBpbmRleE1hcHBpbmcgPSBnZXRPdXRkYXRlZEZpZWxkSW5kZXhlcyhmaWVsZHMpO1xuXG4gIHJldHVybiBkZXBlbmRlbmNpZXMubWFwKFxuICAgIGRlcCA9PlxuICAgICAgb2JqZWN0RnJvbUVudHJpZXMoXG4gICAgICAgIG9iamVjdFRvS2V5cyhpbmRleE1hcHBpbmcpXG4gICAgICAgICAgLm1hcChmaWVsZCA9PiBbZmllbGQsIGRlcFtpbmRleE1hcHBpbmdbZmllbGRdXV0gYXMgY29uc3QpXG4gICAgICAgICAgLmZpbHRlcihcbiAgICAgICAgICAgIChlbnRyeSk6IGVudHJ5IGlzIFtrZXlvZiBPdXRkYXRlZERlcGVuZGVuY3ksIHN0cmluZ10gPT5cbiAgICAgICAgICAgICAgZW50cnlbMV0gIT0gbnVsbCxcbiAgICAgICAgICApLFxuICAgICAgKSBhcyBPdXRkYXRlZERlcGVuZGVuY3ksXG4gICk7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiB2YWxpZGF0ZU91dGRhdGVkRmllbGRzKGhlYWQ6IHN0cmluZ1tdKSB7XG4gIGNvbnN0IHJlbGV2YW50RmllbGRzID0gaGVhZC5maWx0ZXIoaXNZYXJudjFGaWVsZE5hbWUpO1xuICBpZiAoaGFzQWxsUmVxdWlyZWRGaWVsZHMocmVsZXZhbnRGaWVsZHMpKSB7XG4gICAgcmV0dXJuIHRydWU7XG4gIH1cblxuICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgYFlhcm4gdjEgb3V0ZGF0ZWQ6IFRlbXBsYXRlIFske2hlYWQuam9pbihcbiAgICAgICcsICcsXG4gICAgKX1dIGRvZXMgbm90IGNvbnRhaW4gYWxsIHJlcXVpcmVkIGZpZWxkcyBbJHt5YXJudjFGaWVsZE5hbWVzLmpvaW4oJywgJyl9XWAsXG4gICk7XG59XG5cbmZ1bmN0aW9uIGlzWWFybnYxRmllbGROYW1lKHZhbHVlOiBzdHJpbmcpOiB2YWx1ZSBpcyBZYXJudjFGaWVsZE5hbWUge1xuICBjb25zdCBuYW1lczogcmVhZG9ubHkgc3RyaW5nW10gPSB5YXJudjFGaWVsZE5hbWVzO1xuICByZXR1cm4gbmFtZXMuaW5jbHVkZXModmFsdWUpO1xufVxuXG5mdW5jdGlvbiBoYXNBbGxSZXF1aXJlZEZpZWxkcyhoZWFkOiBZYXJudjFGaWVsZE5hbWVbXSkge1xuICByZXR1cm4gUkVRVUlSRURfT1VUREFURURfRklFTERTLmV2ZXJ5KGZpZWxkID0+IGhlYWQuaW5jbHVkZXMoZmllbGQpKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGdldE91dGRhdGVkRmllbGRJbmRleGVzKGFsbDogc3RyaW5nW10pIHtcbiAgcmV0dXJuIG9iamVjdEZyb21FbnRyaWVzKFxuICAgIG9iamVjdFRvRW50cmllcyhvdXRkYXRlZHRvRmllbGRNYXBwZXIpLm1hcCgoW291dGRhdGVkRmllbGQsIHlhcm5GaWVsZF0pID0+IFtcbiAgICAgIG91dGRhdGVkRmllbGQsXG4gICAgICBhbGwuaW5kZXhPZih5YXJuRmllbGQpLFxuICAgIF0pLFxuICApO1xufVxuIiwgImNvbnN0IF9faW5qZWN0ZWRfZmlsZW5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWpzLXBhY2thZ2VzL3NyYy9saWIvcnVubmVyL2luZGV4LnRzXCI7Y29uc3QgX19pbmplY3RlZF9kaXJuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1qcy1wYWNrYWdlcy9zcmMvbGliL3J1bm5lclwiO2NvbnN0IF9faW5qZWN0ZWRfaW1wb3J0X21ldGFfdXJsX18gPSBcImZpbGU6Ly8vaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tanMtcGFja2FnZXMvc3JjL2xpYi9ydW5uZXIvaW5kZXgudHNcIjtpbXBvcnQgeyB3cml0ZUZpbGUgfSBmcm9tICdub2RlOmZzL3Byb21pc2VzJztcbmltcG9ydCBwYXRoIGZyb20gJ25vZGU6cGF0aCc7XG5pbXBvcnQgdHlwZSB7IFJ1bm5lckNvbmZpZyB9IGZyb20gJ0Bjb2RlLXB1c2h1cC9tb2RlbHMnO1xuaW1wb3J0IHtcbiAgZW5zdXJlRGlyZWN0b3J5RXhpc3RzLFxuICBleGVjdXRlUHJvY2VzcyxcbiAgZmlsZVBhdGhUb0NsaUFyZyxcbiAgaXNQcm9taXNlRnVsZmlsbGVkUmVzdWx0LFxuICBpc1Byb21pc2VSZWplY3RlZFJlc3VsdCxcbiAgb2JqZWN0RnJvbUVudHJpZXMsXG4gIHJlYWRKc29uRmlsZSxcbn0gZnJvbSAnQGNvZGUtcHVzaHVwL3V0aWxzJztcbmltcG9ydCB7XG4gIHR5cGUgQXVkaXRTZXZlcml0eSxcbiAgdHlwZSBEZXBlbmRlbmN5R3JvdXAsXG4gIHR5cGUgRmluYWxKU1BhY2thZ2VzUGx1Z2luQ29uZmlnLFxuICB0eXBlIFBhY2thZ2VKc29uUGF0aHMsXG4gIHR5cGUgUGFja2FnZU1hbmFnZXJJZCxcbiAgZGVwZW5kZW5jeUdyb3Vwcyxcbn0gZnJvbSAnLi4vY29uZmlnLmpzJztcbmltcG9ydCB7IGRlcGVuZGVuY3lHcm91cFRvTG9uZyB9IGZyb20gJy4uL2NvbnN0YW50cy5qcyc7XG5pbXBvcnQgeyBwYWNrYWdlTWFuYWdlcnMgfSBmcm9tICcuLi9wYWNrYWdlLW1hbmFnZXJzL3BhY2thZ2UtbWFuYWdlcnMuanMnO1xuaW1wb3J0IHsgYXVkaXRSZXN1bHRUb0F1ZGl0T3V0cHV0IH0gZnJvbSAnLi9hdWRpdC90cmFuc2Zvcm0uanMnO1xuaW1wb3J0IHR5cGUgeyBBdWRpdFJlc3VsdCB9IGZyb20gJy4vYXVkaXQvdHlwZXMuanMnO1xuaW1wb3J0IHsgUExVR0lOX0NPTkZJR19QQVRILCBSVU5ORVJfT1VUUFVUX1BBVEggfSBmcm9tICcuL2NvbnN0YW50cy5qcyc7XG5pbXBvcnQgeyBvdXRkYXRlZFJlc3VsdFRvQXVkaXRPdXRwdXQgfSBmcm9tICcuL291dGRhdGVkL3RyYW5zZm9ybS5qcyc7XG5pbXBvcnQgeyBmaW5kQWxsUGFja2FnZUpzb24sIGdldFRvdGFsRGVwZW5kZW5jaWVzIH0gZnJvbSAnLi91dGlscy5qcyc7XG5cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBjcmVhdGVSdW5uZXJDb25maWcoXG4gIHNjcmlwdFBhdGg6IHN0cmluZyxcbiAgY29uZmlnOiBGaW5hbEpTUGFja2FnZXNQbHVnaW5Db25maWcsXG4pOiBQcm9taXNlPFJ1bm5lckNvbmZpZz4ge1xuICBhd2FpdCBlbnN1cmVEaXJlY3RvcnlFeGlzdHMocGF0aC5kaXJuYW1lKFBMVUdJTl9DT05GSUdfUEFUSCkpO1xuICBhd2FpdCB3cml0ZUZpbGUoUExVR0lOX0NPTkZJR19QQVRILCBKU09OLnN0cmluZ2lmeShjb25maWcpKTtcblxuICByZXR1cm4ge1xuICAgIGNvbW1hbmQ6ICdub2RlJyxcbiAgICBhcmdzOiBbZmlsZVBhdGhUb0NsaUFyZyhzY3JpcHRQYXRoKV0sXG4gICAgb3V0cHV0RmlsZTogUlVOTkVSX09VVFBVVF9QQVRILFxuICB9O1xufVxuXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gZXhlY3V0ZVJ1bm5lcigpOiBQcm9taXNlPHZvaWQ+IHtcbiAgY29uc3Qge1xuICAgIHBhY2thZ2VNYW5hZ2VyLFxuICAgIGNoZWNrcyxcbiAgICBhdWRpdExldmVsTWFwcGluZyxcbiAgICBwYWNrYWdlSnNvblBhdGhzLFxuICAgIGRlcGVuZGVuY3lHcm91cHM6IGRlcEdyb3VwcyxcbiAgfSA9IGF3YWl0IHJlYWRKc29uRmlsZTxGaW5hbEpTUGFja2FnZXNQbHVnaW5Db25maWc+KFBMVUdJTl9DT05GSUdfUEFUSCk7XG5cbiAgY29uc3QgYXVkaXRSZXN1bHRzID0gY2hlY2tzLmluY2x1ZGVzKCdhdWRpdCcpXG4gICAgPyBhd2FpdCBwcm9jZXNzQXVkaXQocGFja2FnZU1hbmFnZXIsIGRlcEdyb3VwcywgYXVkaXRMZXZlbE1hcHBpbmcpXG4gICAgOiBbXTtcblxuICBjb25zdCBvdXRkYXRlZFJlc3VsdHMgPSBjaGVja3MuaW5jbHVkZXMoJ291dGRhdGVkJylcbiAgICA/IGF3YWl0IHByb2Nlc3NPdXRkYXRlZChwYWNrYWdlTWFuYWdlciwgZGVwR3JvdXBzLCBwYWNrYWdlSnNvblBhdGhzKVxuICAgIDogW107XG4gIGNvbnN0IGNoZWNrUmVzdWx0cyA9IFsuLi5hdWRpdFJlc3VsdHMsIC4uLm91dGRhdGVkUmVzdWx0c107XG5cbiAgYXdhaXQgZW5zdXJlRGlyZWN0b3J5RXhpc3RzKHBhdGguZGlybmFtZShSVU5ORVJfT1VUUFVUX1BBVEgpKTtcbiAgYXdhaXQgd3JpdGVGaWxlKFJVTk5FUl9PVVRQVVRfUEFUSCwgSlNPTi5zdHJpbmdpZnkoY2hlY2tSZXN1bHRzKSk7XG59XG5cbmFzeW5jIGZ1bmN0aW9uIHByb2Nlc3NPdXRkYXRlZChcbiAgaWQ6IFBhY2thZ2VNYW5hZ2VySWQsXG4gIGRlcEdyb3VwczogRGVwZW5kZW5jeUdyb3VwW10sXG4gIHBhY2thZ2VKc29uUGF0aHM6IFBhY2thZ2VKc29uUGF0aHMsXG4pIHtcbiAgY29uc3QgcG0gPSBwYWNrYWdlTWFuYWdlcnNbaWRdO1xuICBjb25zdCB7IHN0ZG91dCwgc3RkZXJyIH0gPSBhd2FpdCBleGVjdXRlUHJvY2Vzcyh7XG4gICAgY29tbWFuZDogcG0uY29tbWFuZCxcbiAgICBhcmdzOiBwbS5vdXRkYXRlZC5jb21tYW5kQXJncyxcbiAgICBjd2Q6IHByb2Nlc3MuY3dkKCksXG4gICAgaWdub3JlRXhpdENvZGU6IHRydWUsIC8vIG91dGRhdGVkIHJldHVybnMgZXhpdCBjb2RlIDEgd2hlbiBvdXRkYXRlZCBkZXBlbmRlbmNpZXMgYXJlIGZvdW5kXG4gIH0pO1xuXG4gIC8vIFN1Y2Nlc3NmdWwgb3V0ZGF0ZWQgY2hlY2sgaGFzIGVtcHR5IHN0ZGVyclxuICBpZiAoc3RkZXJyKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKGBKUyBwYWNrYWdlcyBwbHVnaW46IG91dGRhdGVkIGVycm9yOiAke3N0ZGVycn1gKTtcbiAgfVxuXG4gIC8vIExvY2F0ZSBhbGwgcGFja2FnZS5qc29uIGZpbGVzIGluIHRoZSByZXBvc2l0b3J5IGlmIG5vdCBwcm92aWRlZFxuICBjb25zdCBmaW5hbFBhdGhzID0gQXJyYXkuaXNBcnJheShwYWNrYWdlSnNvblBhdGhzKVxuICAgID8gcGFja2FnZUpzb25QYXRoc1xuICAgIDogYXdhaXQgZmluZEFsbFBhY2thZ2VKc29uKCk7XG4gIGNvbnN0IGRlcFRvdGFscyA9IGF3YWl0IGdldFRvdGFsRGVwZW5kZW5jaWVzKGZpbmFsUGF0aHMpO1xuXG4gIGNvbnN0IG5vcm1hbGl6ZWRSZXN1bHQgPSBwbS5vdXRkYXRlZC51bmlmeVJlc3VsdChzdGRvdXQpO1xuICByZXR1cm4gZGVwR3JvdXBzLm1hcChkZXBHcm91cCA9PlxuICAgIG91dGRhdGVkUmVzdWx0VG9BdWRpdE91dHB1dChcbiAgICAgIG5vcm1hbGl6ZWRSZXN1bHQsXG4gICAgICBpZCxcbiAgICAgIGRlcEdyb3VwLFxuICAgICAgZGVwVG90YWxzW2RlcGVuZGVuY3lHcm91cFRvTG9uZ1tkZXBHcm91cF1dLFxuICAgICksXG4gICk7XG59XG5cbmFzeW5jIGZ1bmN0aW9uIHByb2Nlc3NBdWRpdChcbiAgaWQ6IFBhY2thZ2VNYW5hZ2VySWQsXG4gIGRlcEdyb3VwczogRGVwZW5kZW5jeUdyb3VwW10sXG4gIGF1ZGl0TGV2ZWxNYXBwaW5nOiBBdWRpdFNldmVyaXR5LFxuKSB7XG4gIGNvbnN0IHBtID0gcGFja2FnZU1hbmFnZXJzW2lkXTtcbiAgY29uc3Qgc3VwcG9ydGVkQXVkaXREZXBHcm91cHMgPVxuICAgIHBtLmF1ZGl0LnN1cHBvcnRlZERlcEdyb3VwcyA/PyBkZXBlbmRlbmN5R3JvdXBzO1xuICBjb25zdCBjb21wYXRpYmxlQXVkaXREZXBHcm91cHMgPSBkZXBHcm91cHMuZmlsdGVyKGdyb3VwID0+XG4gICAgc3VwcG9ydGVkQXVkaXREZXBHcm91cHMuaW5jbHVkZXMoZ3JvdXApLFxuICApO1xuXG4gIGNvbnN0IGF1ZGl0UmVzdWx0cyA9IGF3YWl0IFByb21pc2UuYWxsU2V0dGxlZChcbiAgICBjb21wYXRpYmxlQXVkaXREZXBHcm91cHMubWFwKFxuICAgICAgYXN5bmMgKGRlcEdyb3VwKTogUHJvbWlzZTxbRGVwZW5kZW5jeUdyb3VwLCBBdWRpdFJlc3VsdF0+ID0+IHtcbiAgICAgICAgY29uc3QgeyBzdGRvdXQsIHN0ZGVyciB9ID0gYXdhaXQgZXhlY3V0ZVByb2Nlc3Moe1xuICAgICAgICAgIGNvbW1hbmQ6IHBtLmNvbW1hbmQsXG4gICAgICAgICAgYXJnczogcG0uYXVkaXQuZ2V0Q29tbWFuZEFyZ3MoZGVwR3JvdXApLFxuICAgICAgICAgIGN3ZDogcHJvY2Vzcy5jd2QoKSxcbiAgICAgICAgICBpZ25vcmVFeGl0Q29kZTogcG0uYXVkaXQuaWdub3JlRXhpdENvZGUsXG4gICAgICAgIH0pO1xuICAgICAgICAvLyBTdWNjZXNzZnVsIGF1ZGl0IGNoZWNrIGhhcyBlbXB0eSBzdGRlcnJcbiAgICAgICAgaWYgKHN0ZGVycikge1xuICAgICAgICAgIHRocm93IG5ldyBFcnJvcihgSlMgcGFja2FnZXMgcGx1Z2luOiBhdWRpdCBlcnJvcjogJHtzdGRlcnJ9YCk7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIFtkZXBHcm91cCwgcG0uYXVkaXQudW5pZnlSZXN1bHQoc3Rkb3V0KV07XG4gICAgICB9LFxuICAgICksXG4gICk7XG5cbiAgY29uc3QgcmVqZWN0ZWQgPSBhdWRpdFJlc3VsdHMuZmlsdGVyKGlzUHJvbWlzZVJlamVjdGVkUmVzdWx0KTtcbiAgaWYgKHJlamVjdGVkLmxlbmd0aCA+IDApIHtcbiAgICByZWplY3RlZC5mb3JFYWNoKHJlc3VsdCA9PiB7XG4gICAgICBjb25zb2xlLmVycm9yKHJlc3VsdC5yZWFzb24pO1xuICAgIH0pO1xuXG4gICAgdGhyb3cgbmV3IEVycm9yKGBKUyBQYWNrYWdlcyBwbHVnaW46IFJ1bm5pbmcgJHtwbS5uYW1lfSBhdWRpdCBmYWlsZWQuYCk7XG4gIH1cblxuICBjb25zdCBmdWxmaWxsZWQgPSBvYmplY3RGcm9tRW50cmllcyhcbiAgICBhdWRpdFJlc3VsdHMuZmlsdGVyKGlzUHJvbWlzZUZ1bGZpbGxlZFJlc3VsdCkubWFwKHggPT4geC52YWx1ZSksXG4gICk7XG5cbiAgY29uc3QgdW5pcXVlUmVzdWx0cyA9IHBtLmF1ZGl0LnBvc3RQcm9jZXNzUmVzdWx0Py4oZnVsZmlsbGVkKSA/PyBmdWxmaWxsZWQ7XG5cbiAgcmV0dXJuIGNvbXBhdGlibGVBdWRpdERlcEdyb3Vwcy5tYXAoZGVwR3JvdXAgPT5cbiAgICBhdWRpdFJlc3VsdFRvQXVkaXRPdXRwdXQoXG4gICAgICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgQHR5cGVzY3JpcHQtZXNsaW50L25vLW5vbi1udWxsLWFzc2VydGlvblxuICAgICAgdW5pcXVlUmVzdWx0c1tkZXBHcm91cF0hLFxuICAgICAgaWQsXG4gICAgICBkZXBHcm91cCxcbiAgICAgIGF1ZGl0TGV2ZWxNYXBwaW5nLFxuICAgICksXG4gICk7XG59XG4iLCAiY29uc3QgX19pbmplY3RlZF9maWxlbmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tanMtcGFja2FnZXMvc3JjL2xpYi9ydW5uZXIvYXVkaXQvdHJhbnNmb3JtLnRzXCI7Y29uc3QgX19pbmplY3RlZF9kaXJuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1qcy1wYWNrYWdlcy9zcmMvbGliL3J1bm5lci9hdWRpdFwiO2NvbnN0IF9faW5qZWN0ZWRfaW1wb3J0X21ldGFfdXJsX18gPSBcImZpbGU6Ly8vaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tanMtcGFja2FnZXMvc3JjL2xpYi9ydW5uZXIvYXVkaXQvdHJhbnNmb3JtLnRzXCI7aW1wb3J0IHsgbWQgfSBmcm9tICdidWlsZC1tZCc7XG5pbXBvcnQgdHlwZSB7IEF1ZGl0T3V0cHV0LCBJc3N1ZSB9IGZyb20gJ0Bjb2RlLXB1c2h1cC9tb2RlbHMnO1xuaW1wb3J0IHsgb2JqZWN0VG9FbnRyaWVzIH0gZnJvbSAnQGNvZGUtcHVzaHVwL3V0aWxzJztcbmltcG9ydCB7XG4gIHR5cGUgQXVkaXRTZXZlcml0eSxcbiAgdHlwZSBEZXBlbmRlbmN5R3JvdXAsXG4gIHR5cGUgUGFja2FnZU1hbmFnZXJJZCxcbiAgcGFja2FnZUF1ZGl0TGV2ZWxzLFxufSBmcm9tICcuLi8uLi9jb25maWcuanMnO1xuaW1wb3J0IHsgYXVkaXRTY29yZU1vZGlmaWVycyB9IGZyb20gJy4vY29uc3RhbnRzLmpzJztcbmltcG9ydCB0eXBlIHsgQXVkaXRSZXN1bHQsIEF1ZGl0U3VtbWFyeSwgVnVsbmVyYWJpbGl0eSB9IGZyb20gJy4vdHlwZXMuanMnO1xuXG5leHBvcnQgZnVuY3Rpb24gYXVkaXRSZXN1bHRUb0F1ZGl0T3V0cHV0KFxuICByZXN1bHQ6IEF1ZGl0UmVzdWx0LFxuICBpZDogUGFja2FnZU1hbmFnZXJJZCxcbiAgZGVwR3JvdXA6IERlcGVuZGVuY3lHcm91cCxcbiAgYXVkaXRMZXZlbE1hcHBpbmc6IEF1ZGl0U2V2ZXJpdHksXG4pOiBBdWRpdE91dHB1dCB7XG4gIGNvbnN0IGlzc3VlcyA9IHZ1bG5lcmFiaWxpdGllc1RvSXNzdWVzKFxuICAgIHJlc3VsdC52dWxuZXJhYmlsaXRpZXMsXG4gICAgYXVkaXRMZXZlbE1hcHBpbmcsXG4gICk7XG5cbiAgcmV0dXJuIHtcbiAgICBzbHVnOiBgJHtpZH0tYXVkaXQtJHtkZXBHcm91cH1gLFxuICAgIHNjb3JlOiBjYWxjdWxhdGVBdWRpdFNjb3JlKHJlc3VsdC5zdW1tYXJ5KSxcbiAgICB2YWx1ZTogcmVzdWx0LnN1bW1hcnkudG90YWwsXG4gICAgZGlzcGxheVZhbHVlOiBzdW1tYXJ5VG9EaXNwbGF5VmFsdWUocmVzdWx0LnN1bW1hcnkpLFxuICAgIGRldGFpbHM6IHsgaXNzdWVzIH0sXG4gIH07XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBjYWxjdWxhdGVBdWRpdFNjb3JlKHN0YXRzOiBBdWRpdFN1bW1hcnkpIHtcbiAgaWYgKHN0YXRzLnRvdGFsID09PSAwKSB7XG4gICAgcmV0dXJuIDE7XG4gIH1cblxuICByZXR1cm4gb2JqZWN0VG9FbnRyaWVzKHN0YXRzKS5yZWR1Y2U8bnVtYmVyPihcbiAgICAoc2NvcmUsIFtsZXZlbCwgdnVsbmVyYWJpbGl0aWVzXSkgPT4ge1xuICAgICAgaWYgKGxldmVsID09PSAndG90YWwnKSB7XG4gICAgICAgIHJldHVybiBzY29yZTtcbiAgICAgIH1cblxuICAgICAgY29uc3QgcmVkdWNlZFNjb3JlID0gc2NvcmUgLSBhdWRpdFNjb3JlTW9kaWZpZXJzW2xldmVsXSAqIHZ1bG5lcmFiaWxpdGllcztcbiAgICAgIHJldHVybiBNYXRoLm1heChyZWR1Y2VkU2NvcmUsIDApO1xuICAgIH0sXG4gICAgMSxcbiAgKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHN1bW1hcnlUb0Rpc3BsYXlWYWx1ZShzdW1tYXJ5OiBBdWRpdFN1bW1hcnkpOiBzdHJpbmcge1xuICBpZiAoc3VtbWFyeS50b3RhbCA9PT0gMCkge1xuICAgIHJldHVybiAnMCB2dWxuZXJhYmlsaXRpZXMnO1xuICB9XG5cbiAgY29uc3QgdnVsbmVyYWJpbGl0eVN0YXRzID0gcGFja2FnZUF1ZGl0TGV2ZWxzXG4gICAgLm1hcChsZXZlbCA9PiAoc3VtbWFyeVtsZXZlbF0gPiAwID8gYCR7c3VtbWFyeVtsZXZlbF19ICR7bGV2ZWx9YCA6ICcnKSlcbiAgICAuZmlsdGVyKHRleHQgPT4gdGV4dCAhPT0gJycpXG4gICAgLmpvaW4oJywgJyk7XG4gIHJldHVybiBgJHtzdW1tYXJ5LnRvdGFsfSAke1xuICAgIHN1bW1hcnkudG90YWwgPT09IDEgPyAndnVsbmVyYWJpbGl0eScgOiAndnVsbmVyYWJpbGl0aWVzJ1xuICB9ICgke3Z1bG5lcmFiaWxpdHlTdGF0c30pYDtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHZ1bG5lcmFiaWxpdGllc1RvSXNzdWVzKFxuICB2dWxuZXJhYmlsaXRpZXM6IFZ1bG5lcmFiaWxpdHlbXSxcbiAgYXVkaXRMZXZlbE1hcHBpbmc6IEF1ZGl0U2V2ZXJpdHksXG4pOiBJc3N1ZVtdIHtcbiAgaWYgKHZ1bG5lcmFiaWxpdGllcy5sZW5ndGggPT09IDApIHtcbiAgICByZXR1cm4gW107XG4gIH1cblxuICByZXR1cm4gdnVsbmVyYWJpbGl0aWVzLm1hcCgoZGV0YWlsKTogSXNzdWUgPT4ge1xuICAgIGNvbnN0IHZlcnNpb25SYW5nZSA9XG4gICAgICBkZXRhaWwudmVyc2lvblJhbmdlID09PSAnKidcbiAgICAgICAgPyBtZGAke21kLmJvbGQoJ2FsbCcpfSB2ZXJzaW9uc2BcbiAgICAgICAgOiBtZGB2ZXJzaW9ucyAke21kLmJvbGQoZGV0YWlsLnZlcnNpb25SYW5nZSl9YDtcbiAgICBjb25zdCBkaXJlY3REZXBlbmRlbmN5ID1cbiAgICAgIHR5cGVvZiBkZXRhaWwuZGlyZWN0RGVwZW5kZW5jeSA9PT0gJ3N0cmluZycgJiZcbiAgICAgIGRldGFpbC5kaXJlY3REZXBlbmRlbmN5ICE9PSAnJ1xuICAgICAgICA/IG1kLmNvZGUoZGV0YWlsLmRpcmVjdERlcGVuZGVuY3kpXG4gICAgICAgIDogJyc7XG4gICAgY29uc3QgZGVwSGllcmFyY2h5ID0gZGlyZWN0RGVwZW5kZW5jeVxuICAgICAgPyBtZGAke2RpcmVjdERlcGVuZGVuY3l9J3MgZGVwZW5kZW5jeSAke21kLmNvZGUoZGV0YWlsLm5hbWUpfWBcbiAgICAgIDogbWRgJHttZC5jb2RlKGRldGFpbC5uYW1lKX0gZGVwZW5kZW5jeWA7XG5cbiAgICBjb25zdCB2dWxuZXJhYmlsaXR5U3VtbWFyeSA9IG1kYGhhcyBhICR7bWQuYm9sZChcbiAgICAgIGRldGFpbC5zZXZlcml0eSxcbiAgICApfSB2dWxuZXJhYmlsaXR5IGluICR7dmVyc2lvblJhbmdlfS5gO1xuICAgIGNvbnN0IGZpeEluZm8gPSBkZXRhaWwuZml4SW5mb3JtYXRpb24gPyBgICR7ZGV0YWlsLmZpeEluZm9ybWF0aW9ufWAgOiAnJztcbiAgICBjb25zdCBhZGRpdGlvbmFsSW5mbyA9XG4gICAgICBkZXRhaWwudGl0bGUgIT0gbnVsbCAmJiBkZXRhaWwudXJsICE9IG51bGxcbiAgICAgICAgPyBtZGAgTW9yZSBpbmZvcm1hdGlvbjogJHttZC5saW5rKGRldGFpbC51cmwsIGRldGFpbC50aXRsZSl9YFxuICAgICAgICA6ICcnO1xuXG4gICAgcmV0dXJuIHtcbiAgICAgIG1lc3NhZ2U6XG4gICAgICAgIG1kYCR7ZGVwSGllcmFyY2h5fSAke3Z1bG5lcmFiaWxpdHlTdW1tYXJ5fSR7Zml4SW5mb30ke2FkZGl0aW9uYWxJbmZvfWAudG9TdHJpbmcoKSxcbiAgICAgIHNldmVyaXR5OiBhdWRpdExldmVsTWFwcGluZ1tkZXRhaWwuc2V2ZXJpdHldLFxuICAgIH07XG4gIH0pO1xufVxuIiwgImNvbnN0IF9faW5qZWN0ZWRfZmlsZW5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWpzLXBhY2thZ2VzL3NyYy9saWIvcnVubmVyL2NvbnN0YW50cy50c1wiO2NvbnN0IF9faW5qZWN0ZWRfZGlybmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tanMtcGFja2FnZXMvc3JjL2xpYi9ydW5uZXJcIjtjb25zdCBfX2luamVjdGVkX2ltcG9ydF9tZXRhX3VybF9fID0gXCJmaWxlOi8vL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWpzLXBhY2thZ2VzL3NyYy9saWIvcnVubmVyL2NvbnN0YW50cy50c1wiO2ltcG9ydCBwYXRoIGZyb20gJ25vZGU6cGF0aCc7XG5pbXBvcnQgeyBwbHVnaW5Xb3JrRGlyIH0gZnJvbSAnQGNvZGUtcHVzaHVwL3V0aWxzJztcblxuZXhwb3J0IGNvbnN0IFdPUktESVIgPSBwbHVnaW5Xb3JrRGlyKCdqcy1wYWNrYWdlcycpO1xuZXhwb3J0IGNvbnN0IFJVTk5FUl9PVVRQVVRfUEFUSCA9IHBhdGguam9pbihXT1JLRElSLCAncnVubmVyLW91dHB1dC5qc29uJyk7XG5leHBvcnQgY29uc3QgUExVR0lOX0NPTkZJR19QQVRIID0gcGF0aC5qb2luKFxuICBwcm9jZXNzLmN3ZCgpLFxuICBXT1JLRElSLFxuICAncGx1Z2luLWNvbmZpZy5qc29uJyxcbik7XG4iLCAiY29uc3QgX19pbmplY3RlZF9maWxlbmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tanMtcGFja2FnZXMvc3JjL2xpYi9ydW5uZXIvb3V0ZGF0ZWQvdHJhbnNmb3JtLnRzXCI7Y29uc3QgX19pbmplY3RlZF9kaXJuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1qcy1wYWNrYWdlcy9zcmMvbGliL3J1bm5lci9vdXRkYXRlZFwiO2NvbnN0IF9faW5qZWN0ZWRfaW1wb3J0X21ldGFfdXJsX18gPSBcImZpbGU6Ly8vaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tanMtcGFja2FnZXMvc3JjL2xpYi9ydW5uZXIvb3V0ZGF0ZWQvdHJhbnNmb3JtLnRzXCI7aW1wb3J0IHsgbWQgfSBmcm9tICdidWlsZC1tZCc7XG5pbXBvcnQgeyBjbGVhbiwgZGlmZiwgbmVxIH0gZnJvbSAnc2VtdmVyJztcbmltcG9ydCB0eXBlIHsgQXVkaXRPdXRwdXQsIElzc3VlIH0gZnJvbSAnQGNvZGUtcHVzaHVwL21vZGVscyc7XG5pbXBvcnQgeyBvYmplY3RGcm9tRW50cmllcywgcGx1cmFsaXplIH0gZnJvbSAnQGNvZGUtcHVzaHVwL3V0aWxzJztcbmltcG9ydCB0eXBlIHsgRGVwZW5kZW5jeUdyb3VwLCBQYWNrYWdlTWFuYWdlcklkIH0gZnJvbSAnLi4vLi4vY29uZmlnLmpzJztcbmltcG9ydCB7IGRlcGVuZGVuY3lHcm91cFRvTG9uZyB9IGZyb20gJy4uLy4uL2NvbnN0YW50cy5qcyc7XG5pbXBvcnQgeyBSRUxFQVNFX1RZUEVTLCBvdXRkYXRlZFNldmVyaXR5IH0gZnJvbSAnLi9jb25zdGFudHMuanMnO1xuaW1wb3J0IHR5cGUgeyBPdXRkYXRlZFJlc3VsdCwgUGFja2FnZVZlcnNpb24gfSBmcm9tICcuL3R5cGVzLmpzJztcblxuZXhwb3J0IGZ1bmN0aW9uIG91dGRhdGVkUmVzdWx0VG9BdWRpdE91dHB1dChcbiAgcmVzdWx0OiBPdXRkYXRlZFJlc3VsdCxcbiAgcGFja2FnZU1hbmFnZXI6IFBhY2thZ2VNYW5hZ2VySWQsXG4gIGRlcEdyb3VwOiBEZXBlbmRlbmN5R3JvdXAsXG4gIHRvdGFsRGVwczogbnVtYmVyLFxuKTogQXVkaXRPdXRwdXQge1xuICBjb25zdCByZWxldmFudERlcGVuZGVuY2llczogT3V0ZGF0ZWRSZXN1bHQgPSByZXN1bHQuZmlsdGVyKFxuICAgIGRlcCA9PiBkZXAudHlwZSA9PT0gZGVwZW5kZW5jeUdyb3VwVG9Mb25nW2RlcEdyb3VwXSxcbiAgKTtcblxuICBjb25zdCB2YWxpZERlcGVuZGVuY2llcyA9IHJlbGV2YW50RGVwZW5kZW5jaWVzXG4gICAgLm1hcChkZXAgPT4gKHtcbiAgICAgIC4uLmRlcCxcbiAgICAgIGN1cnJlbnQ6IGNsZWFuKGRlcC5jdXJyZW50KSxcbiAgICAgIGxhdGVzdDogY2xlYW4oZGVwLmxhdGVzdCksXG4gICAgfSkpXG4gICAgLmZpbHRlcihcbiAgICAgIChkZXApOiBkZXAgaXMgT3V0ZGF0ZWRSZXN1bHRbbnVtYmVyXSA9PlxuICAgICAgICBkZXAuY3VycmVudCAhPSBudWxsICYmIGRlcC5sYXRlc3QgIT0gbnVsbCxcbiAgICApO1xuXG4gIGNvbnN0IG91dGRhdGVkRGVwZW5kZW5jaWVzID0gdmFsaWREZXBlbmRlbmNpZXMuZmlsdGVyKGRlcCA9PlxuICAgIG5lcShkZXAuY3VycmVudCwgZGVwLmxhdGVzdCksXG4gICk7XG5cbiAgY29uc3Qgb3V0ZGF0ZWRTdGF0cyA9IG91dGRhdGVkRGVwZW5kZW5jaWVzLnJlZHVjZShcbiAgICAoYWNjLCBkZXApID0+IHtcbiAgICAgIGNvbnN0IG91dGRhdGVkTGV2ZWwgPSBkaWZmKGRlcC5jdXJyZW50LCBkZXAubGF0ZXN0KTtcbiAgICAgIGlmIChvdXRkYXRlZExldmVsID09IG51bGwpIHtcbiAgICAgICAgcmV0dXJuIGFjYztcbiAgICAgIH1cbiAgICAgIHJldHVybiB7IC4uLmFjYywgW291dGRhdGVkTGV2ZWxdOiBhY2Nbb3V0ZGF0ZWRMZXZlbF0gKyAxIH07XG4gICAgfSxcbiAgICBvYmplY3RGcm9tRW50cmllcyhSRUxFQVNFX1RZUEVTLm1hcCh2ZXJzaW9uVHlwZSA9PiBbdmVyc2lvblR5cGUsIDBdKSksXG4gICk7XG5cbiAgY29uc3QgaXNzdWVzID1cbiAgICBvdXRkYXRlZERlcGVuZGVuY2llcy5sZW5ndGggPT09IDBcbiAgICAgID8gW11cbiAgICAgIDogb3V0ZGF0ZWRUb0lzc3VlcyhvdXRkYXRlZERlcGVuZGVuY2llcyk7XG5cbiAgcmV0dXJuIHtcbiAgICBzbHVnOiBgJHtwYWNrYWdlTWFuYWdlcn0tb3V0ZGF0ZWQtJHtkZXBHcm91cH1gLFxuICAgIHNjb3JlOiBjYWxjdWxhdGVPdXRkYXRlZFNjb3JlKG91dGRhdGVkU3RhdHMubWFqb3IsIHRvdGFsRGVwcyksXG4gICAgdmFsdWU6IG91dGRhdGVkRGVwZW5kZW5jaWVzLmxlbmd0aCxcbiAgICBkaXNwbGF5VmFsdWU6IG91dGRhdGVkVG9EaXNwbGF5VmFsdWUob3V0ZGF0ZWRTdGF0cyksXG4gICAgZGV0YWlsczogeyBpc3N1ZXMgfSxcbiAgfTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGNhbGN1bGF0ZU91dGRhdGVkU2NvcmUoXG4gIG1ham9yT3V0ZGF0ZWQ6IG51bWJlcixcbiAgdG90YWxEZXBzOiBudW1iZXIsXG4pIHtcbiAgcmV0dXJuIHRvdGFsRGVwcyA+IDAgPyAodG90YWxEZXBzIC0gbWFqb3JPdXRkYXRlZCkgLyB0b3RhbERlcHMgOiAxO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gb3V0ZGF0ZWRUb0Rpc3BsYXlWYWx1ZShzdGF0czogUGFja2FnZVZlcnNpb24pIHtcbiAgY29uc3QgdG90YWwgPSBPYmplY3QudmFsdWVzKHN0YXRzKS5yZWR1Y2UoKGFjYywgdmFsdWUpID0+IGFjYyArIHZhbHVlLCAwKTtcblxuICBjb25zdCB2ZXJzaW9uQnJlYWtkb3duID0gUkVMRUFTRV9UWVBFUy5tYXAodmVyc2lvbiA9PlxuICAgIHN0YXRzW3ZlcnNpb25dID4gMCA/IGAke3N0YXRzW3ZlcnNpb25dfSAke3ZlcnNpb259YCA6ICcnLFxuICApLmZpbHRlcih0ZXh0ID0+IHRleHQgIT09ICcnKTtcblxuICBpZiAodmVyc2lvbkJyZWFrZG93bi5sZW5ndGggPT09IDApIHtcbiAgICByZXR1cm4gJ2FsbCBkZXBlbmRlbmNpZXMgYXJlIHVwIHRvIGRhdGUnO1xuICB9XG5cbiAgaWYgKHZlcnNpb25CcmVha2Rvd24ubGVuZ3RoID4gMSkge1xuICAgIHJldHVybiBgJHt0b3RhbH0gb3V0ZGF0ZWQgcGFja2FnZSB2ZXJzaW9ucyAoJHt2ZXJzaW9uQnJlYWtkb3duLmpvaW4oXG4gICAgICAnLCAnLFxuICAgICl9KWA7XG4gIH1cblxuICByZXR1cm4gYCR7dmVyc2lvbkJyZWFrZG93blswXX0gb3V0ZGF0ZWQgcGFja2FnZSAke3BsdXJhbGl6ZShcbiAgICAndmVyc2lvbicsXG4gICAgdG90YWwsXG4gICl9YDtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIG91dGRhdGVkVG9Jc3N1ZXMoZGVwZW5kZW5jaWVzOiBPdXRkYXRlZFJlc3VsdCk6IElzc3VlW10ge1xuICByZXR1cm4gZGVwZW5kZW5jaWVzLm1hcDxJc3N1ZT4oZGVwID0+IHtcbiAgICBjb25zdCB7IG5hbWUsIGN1cnJlbnQsIGxhdGVzdCwgdXJsIH0gPSBkZXA7XG4gICAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIEB0eXBlc2NyaXB0LWVzbGludC9uby1ub24tbnVsbC1hc3NlcnRpb25cbiAgICBjb25zdCBvdXRkYXRlZExldmVsID0gZGlmZihjdXJyZW50LCBsYXRlc3QpITtcbiAgICBjb25zdCBwYWNrYWdlUmVmZXJlbmNlID1cbiAgICAgIHVybCA9PSBudWxsID8gbWQuY29kZShuYW1lKSA6IG1kLmxpbmsodXJsLCBtZC5jb2RlKG5hbWUpKTtcblxuICAgIHJldHVybiB7XG4gICAgICBtZXNzYWdlOiBtZGBQYWNrYWdlICR7cGFja2FnZVJlZmVyZW5jZX0gcmVxdWlyZXMgYSAke21kLmJvbGQoXG4gICAgICAgIG91dGRhdGVkTGV2ZWwsXG4gICAgICApfSB1cGRhdGUgZnJvbSAke21kLmJvbGQoY3VycmVudCl9IHRvICR7bWQuYm9sZChsYXRlc3QpfS5gLnRvU3RyaW5nKCksXG4gICAgICBzZXZlcml0eTogb3V0ZGF0ZWRTZXZlcml0eVtvdXRkYXRlZExldmVsXSxcbiAgICB9O1xuICB9KTtcbn1cbiIsICJjb25zdCBfX2luamVjdGVkX2ZpbGVuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1qcy1wYWNrYWdlcy9zcmMvbGliL3J1bm5lci9vdXRkYXRlZC9jb25zdGFudHMudHNcIjtjb25zdCBfX2luamVjdGVkX2Rpcm5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWpzLXBhY2thZ2VzL3NyYy9saWIvcnVubmVyL291dGRhdGVkXCI7Y29uc3QgX19pbmplY3RlZF9pbXBvcnRfbWV0YV91cmxfXyA9IFwiZmlsZTovLy9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1qcy1wYWNrYWdlcy9zcmMvbGliL3J1bm5lci9vdXRkYXRlZC9jb25zdGFudHMudHNcIjtpbXBvcnQgdHlwZSB7IFJlbGVhc2VUeXBlIH0gZnJvbSAnc2VtdmVyJztcbmltcG9ydCB0eXBlIHsgSXNzdWVTZXZlcml0eSB9IGZyb20gJ0Bjb2RlLXB1c2h1cC9tb2RlbHMnO1xuaW1wb3J0IHsgb2JqZWN0VG9LZXlzIH0gZnJvbSAnQGNvZGUtcHVzaHVwL3V0aWxzJztcblxuZXhwb3J0IGNvbnN0IG91dGRhdGVkU2V2ZXJpdHk6IFJlY29yZDxSZWxlYXNlVHlwZSwgSXNzdWVTZXZlcml0eT4gPSB7XG4gIG1ham9yOiAnZXJyb3InLFxuICBwcmVtYWpvcjogJ2luZm8nLFxuICBtaW5vcjogJ3dhcm5pbmcnLFxuICBwcmVtaW5vcjogJ2luZm8nLFxuICBwYXRjaDogJ2luZm8nLFxuICBwcmVwYXRjaDogJ2luZm8nLFxuICBwcmVyZWxlYXNlOiAnaW5mbycsXG59O1xuXG4vLyBSRUxFQVNFX1RZUEVTIGRpcmVjdGx5IGV4cG9ydGVkIGZyb20gc2VtdmVyIGRvbid0IHdvcmsgb3V0IG9mIHRoZSBib3hcbmV4cG9ydCBjb25zdCBSRUxFQVNFX1RZUEVTID0gb2JqZWN0VG9LZXlzKG91dGRhdGVkU2V2ZXJpdHkpO1xuIiwgImNvbnN0IF9faW5qZWN0ZWRfZmlsZW5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWpzLXBhY2thZ2VzL3NyYy9saWIvcGFja2FnZS1tYW5hZ2Vycy9kZXJpdmUtcGFja2FnZS1tYW5hZ2VyLnRzXCI7Y29uc3QgX19pbmplY3RlZF9kaXJuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1qcy1wYWNrYWdlcy9zcmMvbGliL3BhY2thZ2UtbWFuYWdlcnNcIjtjb25zdCBfX2luamVjdGVkX2ltcG9ydF9tZXRhX3VybF9fID0gXCJmaWxlOi8vL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWpzLXBhY2thZ2VzL3NyYy9saWIvcGFja2FnZS1tYW5hZ2Vycy9kZXJpdmUtcGFja2FnZS1tYW5hZ2VyLnRzXCI7aW1wb3J0IHsgcmVhZEZpbGUgfSBmcm9tICdub2RlOmZzL3Byb21pc2VzJztcbmltcG9ydCBwYXRoIGZyb20gJ25vZGU6cGF0aCc7XG5pbXBvcnQgeyBmaWxlRXhpc3RzIH0gZnJvbSAnQGNvZGUtcHVzaHVwL3V0aWxzJztcbmltcG9ydCB0eXBlIHsgUGFja2FnZU1hbmFnZXJJZCB9IGZyb20gJy4uL2NvbmZpZy5qcyc7XG5pbXBvcnQgeyBkZXJpdmVZYXJuVmVyc2lvbiB9IGZyb20gJy4vZGVyaXZlLXlhcm4uanMnO1xuXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gZGVyaXZlUGFja2FnZU1hbmFnZXJJblBhY2thZ2VKc29uKFxuICBjdXJyZW50RGlyID0gcHJvY2Vzcy5jd2QoKSxcbikge1xuICBpZiAoYXdhaXQgZmlsZUV4aXN0cyhwYXRoLmpvaW4oY3VycmVudERpciwgJ3BhY2thZ2UuanNvbicpKSkge1xuICAgIGNvbnN0IGNvbnRlbnQgPSBKU09OLnBhcnNlKFxuICAgICAgKGF3YWl0IHJlYWRGaWxlKHBhdGguam9pbigncGFja2FnZS5qc29uJykpKS50b1N0cmluZygpLFxuICAgICkgYXMgeyBwYWNrYWdlTWFuYWdlcj86IHN0cmluZyB9O1xuICAgIGNvbnN0IHsgcGFja2FnZU1hbmFnZXI6IHBhY2thZ2VNYW5hZ2VyRGF0YSA9ICcnIH0gPSBjb250ZW50O1xuXG4gICAgY29uc3QgW21hbmFnZXIgPSAnJywgdmVyc2lvbiA9ICcnXSA9IHBhY2thZ2VNYW5hZ2VyRGF0YS5zcGxpdCgnQCcpO1xuXG4gICAgaWYgKG1hbmFnZXIgPT09ICducG0nKSB7XG4gICAgICByZXR1cm4gbWFuYWdlcjtcbiAgICB9XG4gICAgaWYgKG1hbmFnZXIgPT09ICdwbnBtJykge1xuICAgICAgcmV0dXJuIG1hbmFnZXI7XG4gICAgfVxuICAgIGlmIChtYW5hZ2VyID09PSAneWFybicpIHtcbiAgICAgIGNvbnN0IG1ham9yVmVyc2lvbiA9IE51bWJlcih2ZXJzaW9uLnNwbGl0KCcuJylbMF0pO1xuICAgICAgcmV0dXJuIG1ham9yVmVyc2lvbiA+IDEgPyAneWFybi1tb2Rlcm4nIDogJ3lhcm4tY2xhc3NpYyc7XG4gICAgfVxuICB9XG4gIHJldHVybiBmYWxzZTtcbn1cblxuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGRlcml2ZVBhY2thZ2VNYW5hZ2VyKFxuICBjdXJyZW50RGlyID0gcHJvY2Vzcy5jd2QoKSxcbik6IFByb21pc2U8UGFja2FnZU1hbmFnZXJJZD4ge1xuICBjb25zdCBwa2dNYW5hZ2VyRnJvbVBhY2thZ2VKc29uID1cbiAgICBhd2FpdCBkZXJpdmVQYWNrYWdlTWFuYWdlckluUGFja2FnZUpzb24oY3VycmVudERpcik7XG4gIGlmIChwa2dNYW5hZ2VyRnJvbVBhY2thZ2VKc29uKSB7XG4gICAgcmV0dXJuIHBrZ01hbmFnZXJGcm9tUGFja2FnZUpzb247XG4gIH1cblxuICAvLyBDaGVjayBmb3IgbG9jayBmaWxlc1xuICBpZiAoYXdhaXQgZmlsZUV4aXN0cyhwYXRoLmpvaW4oY3VycmVudERpciwgJ3BhY2thZ2UtbG9jay5qc29uJykpKSB7XG4gICAgcmV0dXJuICducG0nO1xuICB9IGVsc2UgaWYgKGF3YWl0IGZpbGVFeGlzdHMocGF0aC5qb2luKGN1cnJlbnREaXIsICdwbnBtLWxvY2sueWFtbCcpKSkge1xuICAgIHJldHVybiAncG5wbSc7XG4gIH0gZWxzZSBpZiAoYXdhaXQgZmlsZUV4aXN0cyhwYXRoLmpvaW4oY3VycmVudERpciwgJ3lhcm4ubG9jaycpKSkge1xuICAgIGNvbnN0IHlhcm5WZXJzaW9uID0gYXdhaXQgZGVyaXZlWWFyblZlcnNpb24oKTtcbiAgICBpZiAoeWFyblZlcnNpb24pIHtcbiAgICAgIHJldHVybiB5YXJuVmVyc2lvbjtcbiAgICB9XG4gIH1cblxuICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgJ0NvdWxkIG5vdCBkZXRlY3QgcGFja2FnZSBtYW5hZ2VyLiBQbGVhc2UgcHJvdmlkZSBpdCBpbiB0aGUganMtcGFja2FnZXMgcGx1Z2luIGNvbmZpZy4nLFxuICApO1xufVxuIiwgImNvbnN0IF9faW5qZWN0ZWRfZmlsZW5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWpzLXBhY2thZ2VzL3NyYy9saWIvcGFja2FnZS1tYW5hZ2Vycy9kZXJpdmUteWFybi50c1wiO2NvbnN0IF9faW5qZWN0ZWRfZGlybmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tanMtcGFja2FnZXMvc3JjL2xpYi9wYWNrYWdlLW1hbmFnZXJzXCI7Y29uc3QgX19pbmplY3RlZF9pbXBvcnRfbWV0YV91cmxfXyA9IFwiZmlsZTovLy9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1qcy1wYWNrYWdlcy9zcmMvbGliL3BhY2thZ2UtbWFuYWdlcnMvZGVyaXZlLXlhcm4udHNcIjtpbXBvcnQgeyBleGVjdXRlUHJvY2VzcyB9IGZyb20gJ0Bjb2RlLXB1c2h1cC91dGlscyc7XG5cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBkZXJpdmVZYXJuVmVyc2lvbigpIHtcbiAgY29uc3QgeyBzdGRvdXQgfSA9IGF3YWl0IGV4ZWN1dGVQcm9jZXNzKHtcbiAgICBjb21tYW5kOiAneWFybicsXG4gICAgYXJnczogWyctdiddLFxuICB9KTtcblxuICBjb25zdCB5YXJuVmVyc2lvbiA9IE51bWJlci5wYXJzZUludChzdGRvdXQudG9TdHJpbmcoKS50cmltKCkuYXQoMCkgPz8gJycsIDEwKTtcbiAgaWYgKHlhcm5WZXJzaW9uID49IDIpIHtcbiAgICByZXR1cm4gJ3lhcm4tbW9kZXJuJztcbiAgfSBlbHNlIGlmICh5YXJuVmVyc2lvbiA9PT0gMSkge1xuICAgIHJldHVybiAneWFybi1jbGFzc2ljJztcbiAgfVxuICByZXR1cm4gZmFsc2U7XG59XG4iLCAiY29uc3QgX19pbmplY3RlZF9maWxlbmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tbGlnaHRob3VzZS9zcmMvbGliL2xpZ2h0aG91c2UtcGx1Z2luLnRzXCI7Y29uc3QgX19pbmplY3RlZF9kaXJuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1saWdodGhvdXNlL3NyYy9saWJcIjtjb25zdCBfX2luamVjdGVkX2ltcG9ydF9tZXRhX3VybF9fID0gXCJmaWxlOi8vL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWxpZ2h0aG91c2Uvc3JjL2xpYi9saWdodGhvdXNlLXBsdWdpbi50c1wiO2ltcG9ydCB7IGNyZWF0ZVJlcXVpcmUgfSBmcm9tICdub2RlOm1vZHVsZSc7XG5pbXBvcnQgdHlwZSB7IFBsdWdpbkNvbmZpZyB9IGZyb20gJ0Bjb2RlLXB1c2h1cC9tb2RlbHMnO1xuaW1wb3J0IHsgTElHSFRIT1VTRV9QTFVHSU5fU0xVRyB9IGZyb20gJy4vY29uc3RhbnRzLmpzJztcbmltcG9ydCB7IG5vcm1hbGl6ZUZsYWdzIH0gZnJvbSAnLi9ub3JtYWxpemUtZmxhZ3MuanMnO1xuaW1wb3J0IHtcbiAgTElHSFRIT1VTRV9HUk9VUFMsXG4gIExJR0hUSE9VU0VfTkFWSUdBVElPTl9BVURJVFMsXG59IGZyb20gJy4vcnVubmVyL2NvbnN0YW50cy5qcyc7XG5pbXBvcnQgeyBjcmVhdGVSdW5uZXJGdW5jdGlvbiB9IGZyb20gJy4vcnVubmVyL3J1bm5lci5qcyc7XG5pbXBvcnQgdHlwZSB7IExpZ2h0aG91c2VPcHRpb25zIH0gZnJvbSAnLi90eXBlcy5qcyc7XG5pbXBvcnQgeyBmaWx0ZXJBdWRpdHNBbmRHcm91cHNCeU9ubHlPcHRpb25zIH0gZnJvbSAnLi91dGlscy5qcyc7XG5cbmV4cG9ydCBmdW5jdGlvbiBsaWdodGhvdXNlUGx1Z2luKFxuICB1cmw6IHN0cmluZyxcbiAgZmxhZ3M/OiBMaWdodGhvdXNlT3B0aW9ucyxcbik6IFBsdWdpbkNvbmZpZyB7XG4gIGNvbnN0IHsgc2tpcEF1ZGl0cywgb25seUF1ZGl0cywgb25seUNhdGVnb3JpZXMsIC4uLnVucGFyc2VkRmxhZ3MgfSA9XG4gICAgbm9ybWFsaXplRmxhZ3MoZmxhZ3MgPz8ge30pO1xuXG4gIGNvbnN0IHsgYXVkaXRzLCBncm91cHMgfSA9IGZpbHRlckF1ZGl0c0FuZEdyb3Vwc0J5T25seU9wdGlvbnMoXG4gICAgTElHSFRIT1VTRV9OQVZJR0FUSU9OX0FVRElUUyxcbiAgICBMSUdIVEhPVVNFX0dST1VQUyxcbiAgICB7IHNraXBBdWRpdHMsIG9ubHlBdWRpdHMsIG9ubHlDYXRlZ29yaWVzIH0sXG4gICk7XG5cbiAgY29uc3QgcGFja2FnZUpzb24gPSBjcmVhdGVSZXF1aXJlKGltcG9ydC5tZXRhLnVybCkoXG4gICAgJy4uLy4uL3BhY2thZ2UuanNvbicsXG4gICkgYXMgdHlwZW9mIGltcG9ydCgnLi4vLi4vcGFja2FnZS5qc29uJyk7XG5cbiAgcmV0dXJuIHtcbiAgICBzbHVnOiBMSUdIVEhPVVNFX1BMVUdJTl9TTFVHLFxuICAgIHBhY2thZ2VOYW1lOiBwYWNrYWdlSnNvbi5uYW1lLFxuICAgIHZlcnNpb246IHBhY2thZ2VKc29uLnZlcnNpb24sXG4gICAgdGl0bGU6ICdMaWdodGhvdXNlJyxcbiAgICBpY29uOiAnbGlnaHRob3VzZScsXG4gICAgYXVkaXRzLFxuICAgIGdyb3VwcyxcbiAgICBydW5uZXI6IGNyZWF0ZVJ1bm5lckZ1bmN0aW9uKHVybCwge1xuICAgICAgc2tpcEF1ZGl0cyxcbiAgICAgIG9ubHlBdWRpdHMsXG4gICAgICBvbmx5Q2F0ZWdvcmllcyxcbiAgICAgIC4uLnVucGFyc2VkRmxhZ3MsXG4gICAgfSksXG4gIH07XG59XG4iLCAiY29uc3QgX19pbmplY3RlZF9maWxlbmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tbGlnaHRob3VzZS9zcmMvbGliL2NvbnN0YW50cy50c1wiO2NvbnN0IF9faW5qZWN0ZWRfZGlybmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tbGlnaHRob3VzZS9zcmMvbGliXCI7Y29uc3QgX19pbmplY3RlZF9pbXBvcnRfbWV0YV91cmxfXyA9IFwiZmlsZTovLy9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1saWdodGhvdXNlL3NyYy9saWIvY29uc3RhbnRzLnRzXCI7aW1wb3J0IHsgREVGQVVMVF9GTEFHUyB9IGZyb20gJ2Nocm9tZS1sYXVuY2hlci9kaXN0L2ZsYWdzLmpzJztcbmltcG9ydCBwYXRoIGZyb20gJ25vZGU6cGF0aCc7XG5pbXBvcnQgeyBERUZBVUxUX1BFUlNJU1RfT1VUUFVUX0RJUiB9IGZyb20gJ0Bjb2RlLXB1c2h1cC9tb2RlbHMnO1xuXG4vLyBoZWFkbGVzcyBpcyBuZWVkZWQgdG8gcGFzcyBDSSBvbiBMaW51eCBhbmQgV2luZG93cyAobG9jYWxseSBpdCB3b3JrcyB3aXRob3V0IGhlYWRsZXNzIHRvbylcbmV4cG9ydCBjb25zdCBERUZBVUxUX0NIUk9NRV9GTEFHUyA9IFsuLi5ERUZBVUxUX0ZMQUdTLCAnLS1oZWFkbGVzcyddO1xuXG5leHBvcnQgY29uc3QgTElHSFRIT1VTRV9QTFVHSU5fU0xVRyA9ICdsaWdodGhvdXNlJztcbmV4cG9ydCBjb25zdCBMSUdIVEhPVVNFX09VVFBVVF9QQVRIID0gcGF0aC5qb2luKFxuICBERUZBVUxUX1BFUlNJU1RfT1VUUFVUX0RJUixcbiAgTElHSFRIT1VTRV9QTFVHSU5fU0xVRyxcbik7XG4iLCAiY29uc3QgX19pbmplY3RlZF9maWxlbmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tbGlnaHRob3VzZS9zcmMvbGliL25vcm1hbGl6ZS1mbGFncy50c1wiO2NvbnN0IF9faW5qZWN0ZWRfZGlybmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tbGlnaHRob3VzZS9zcmMvbGliXCI7Y29uc3QgX19pbmplY3RlZF9pbXBvcnRfbWV0YV91cmxfXyA9IFwiZmlsZTovLy9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1saWdodGhvdXNlL3NyYy9saWIvbm9ybWFsaXplLWZsYWdzLnRzXCI7aW1wb3J0IHsgYm9sZCwgeWVsbG93IH0gZnJvbSAnYW5zaXMnO1xuaW1wb3J0IHsgdWkgfSBmcm9tICdAY29kZS1wdXNodXAvdXRpbHMnO1xuaW1wb3J0IHsgTElHSFRIT1VTRV9QTFVHSU5fU0xVRyB9IGZyb20gJy4vY29uc3RhbnRzLmpzJztcbmltcG9ydCB7IERFRkFVTFRfQ0xJX0ZMQUdTIH0gZnJvbSAnLi9ydW5uZXIvY29uc3RhbnRzLmpzJztcbmltcG9ydCB0eXBlIHsgTGlnaHRob3VzZUNsaUZsYWdzIH0gZnJvbSAnLi9ydW5uZXIvdHlwZXMuanMnO1xuaW1wb3J0IHR5cGUgeyBMaWdodGhvdXNlT3B0aW9ucyB9IGZyb20gJy4vdHlwZXMuanMnO1xuXG5jb25zdCB7IG9ubHlDYXRlZ29yaWVzLCAuLi5vcmlnaW5hbERlZmF1bHRDbGlGbGFncyB9ID0gREVGQVVMVF9DTElfRkxBR1M7XG5leHBvcnQgY29uc3QgREVGQVVMVF9MSUdIVEhPVVNFX09QVElPTlMgPSB7XG4gIC4uLm9yaWdpbmFsRGVmYXVsdENsaUZsYWdzLFxuICBvbmx5R3JvdXBzOiBvbmx5Q2F0ZWdvcmllcyxcbn0gc2F0aXNmaWVzIExpZ2h0aG91c2VPcHRpb25zO1xuXG4vLyBOT1RFOlxuLy8gVGhpcyBpcyBhbiBpbnRlcm1lZGlhdGUgdmFyaWFibGUgdG8gZ2V0IGBVbnN1cHBvcnRlZENsaUZsYWdzYC4gRm9yIHVua25vd24gcmVhc29ucyBgdHlwZXNjcmlwdEA1LjMuM2AgZG9lc24ndCB3b3JrIG90aGVyd2lzZS5cbmNvbnN0IGxpZ2h0aG91c2VVbnN1cHBvcnRlZENsaUZsYWdzID0gW1xuICAncHJlY29tcHV0ZWRMYW50ZXJuRGF0YVBhdGgnLCAvLyBQYXRoIHRvIHRoZSBmaWxlIHdoZXJlIHByZWNvbXB1dGVkIGxhbnRlcm4gZGF0YSBzaG91bGQgYmUgcmVhZCBmcm9tLlxuICAnY2hyb21lSWdub3JlRGVmYXVsdEZsYWdzJywgLy8gaWdub3JlIGRlZmF1bHQgZmxhZ3MgZnJvbSBMaWdodGhvdXNlIENMSVxuICAvLyBObyBlcnJvciByZXBvcnRpbmcgaW1wbGVtZW50ZWQgYXMgaW4gdGhlIHNvdXJjZSBTZW50cnkgd2FzIGludm9sdmVkXG4gIC8vIFNlZTogaHR0cHM6Ly9naXRodWIuY29tL0dvb2dsZUNocm9tZS9saWdodGhvdXNlL2Jsb2IvZDhjY2Y3MDY5MjIxNmI3ZmEwNDdhNGVhYTJkMTI3N2IwYjdmZTk0Ny9jbGkvYmluLmpzI0wxMjRcbiAgJ2VuYWJsZUVycm9yUmVwb3J0aW5nJywgLy8gZW5hYmxlIGVycm9yIHJlcG9ydGluZ1xuICAvLyBsaWdodGhvdXNlIENMSSBzcGVjaWZpYyBkZWJ1ZyBsb2dzXG4gICdsaXN0LWFsbC1hdWRpdHMnLCAvLyBQcmludHMgYSBsaXN0IG9mIGFsbCBhdmFpbGFibGUgYXVkaXRzIGFuZCBleGl0cy5cbiAgJ2xpc3QtbG9jYWxlcycsIC8vIFByaW50cyBhIGxpc3Qgb2YgYWxsIHN1cHBvcnRlZCBsb2NhbGVzIGFuZCBleGl0cy5cbiAgJ2xpc3QtdHJhY2UtY2F0ZWdvcmllcycsIC8vIFByaW50cyBhIGxpc3Qgb2YgYWxsIHJlcXVpcmVkIHRyYWNlIGNhdGVnb3JpZXMgYW5kIGV4aXRzLlxuXSBhcyBjb25zdDtcbnR5cGUgVW5zdXBwb3J0ZWRDbGlGbGFncyA9ICh0eXBlb2YgbGlnaHRob3VzZVVuc3VwcG9ydGVkQ2xpRmxhZ3MpW251bWJlcl07XG5cbmNvbnN0IExJR0hUSE9VU0VfVU5TVVBQT1JURURfQ0xJX0ZMQUdTID0gbmV3IFNldChsaWdodGhvdXNlVW5zdXBwb3J0ZWRDbGlGbGFncyk7XG5cbmNvbnN0IFJFRklORURfU1RSSU5HX09SX1NUUklOR19BUlJBWSA9IG5ldyBTZXQoW1xuICAnb25seUF1ZGl0cycsXG4gICdvbmx5Q2F0ZWdvcmllcycsXG4gICdza2lwQXVkaXRzJyxcbiAgJ2J1ZGdldHMnLFxuICAnY2hyb21lRmxhZ3MnLFxuXSk7XG5cbmV4cG9ydCBmdW5jdGlvbiBub3JtYWxpemVGbGFncyhmbGFncz86IExpZ2h0aG91c2VPcHRpb25zKTogTGlnaHRob3VzZUNsaUZsYWdzIHtcbiAgY29uc3QgcHJlZmlsbGVkRmxhZ3MgPSB7IC4uLkRFRkFVTFRfTElHSFRIT1VTRV9PUFRJT05TLCAuLi5mbGFncyB9O1xuXG4gIGxvZ1Vuc3VwcG9ydGVkRmxhZ3NJblVzZShwcmVmaWxsZWRGbGFncyk7XG5cbiAgcmV0dXJuIE9iamVjdC5mcm9tRW50cmllcyhcbiAgICBPYmplY3QuZW50cmllcyhwcmVmaWxsZWRGbGFncylcbiAgICAgIC5maWx0ZXIoXG4gICAgICAgIChbZmxhZ05hbWVdKSA9PlxuICAgICAgICAgICFMSUdIVEhPVVNFX1VOU1VQUE9SVEVEX0NMSV9GTEFHUy5oYXMoXG4gICAgICAgICAgICBmbGFnTmFtZSBhcyBVbnN1cHBvcnRlZENsaUZsYWdzLFxuICAgICAgICAgICksXG4gICAgICApXG4gICAgICAvLyBpbiBjb2RlLXB1c2h1cCBsaWdodGhvdXNlIGNhdGVnb3JpZXMgYXJlIG1hcHBlZCBhcyBncm91cHMsIHRoZXJlZm9yIHdlIGhhZCB0byByZW5hbWUgXCJvbmx5Q2F0ZWdvcmllc1wiIHRvIFwib25seUdyb3Vwc1wiIGZvciB0aGUgdXNlciBvZiB0aGUgcGx1Z2luIGFzIGl0IHdhcyBjb25mdXNpbmdcbiAgICAgIC5tYXAoKFtrZXksIHZdKSA9PiBba2V5ID09PSAnb25seUdyb3VwcycgPyAnb25seUNhdGVnb3JpZXMnIDoga2V5LCB2XSlcbiAgICAgIC8vIG9ubHlBdWRpdHMgYW5kIG9ubHlDYXRlZ29yaWVzIGNhbm5vdCBiZSBlbXB0eSBhcnJheXMsIG90aGVyd2lzZSBza2lwQXVkaXRzIGlzIGlnbm9yZWQgYnkgbGlnaHRob3VzZVxuICAgICAgLmZpbHRlcigoW18sIHZdKSA9PiAhKEFycmF5LmlzQXJyYXkodikgJiYgdi5sZW5ndGggPT09IDApKVxuICAgICAgLy8gdW5kZWZpbmVkIHwgc3RyaW5nIHwgc3RyaW5nW10gPT4gc3RyaW5nW10gKGVtcHR5IGZvciB1bmRlZmluZWQpXG4gICAgICAubWFwKChba2V5LCB2XSkgPT4ge1xuICAgICAgICBpZiAoIVJFRklORURfU1RSSU5HX09SX1NUUklOR19BUlJBWS5oYXMoa2V5IGFzIG5ldmVyKSkge1xuICAgICAgICAgIHJldHVybiBba2V5LCB2XTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gW2tleSwgQXJyYXkuaXNBcnJheSh2KSA/IHYgOiB2ID09IG51bGwgPyBbXSA6IFt2XV07XG4gICAgICB9KSxcbiAgKSBhcyBMaWdodGhvdXNlQ2xpRmxhZ3M7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBsb2dVbnN1cHBvcnRlZEZsYWdzSW5Vc2UoXG4gIGZsYWdzOiBMaWdodGhvdXNlT3B0aW9ucyxcbiAgZGlzcGxheUNvdW50ID0gMyxcbikge1xuICBjb25zdCB1bnN1cHBvcnRlZEZsYWdzSW5Vc2UgPSBPYmplY3Qua2V5cyhmbGFncykuZmlsdGVyKGZsYWcgPT5cbiAgICBMSUdIVEhPVVNFX1VOU1VQUE9SVEVEX0NMSV9GTEFHUy5oYXMoZmxhZyBhcyBVbnN1cHBvcnRlZENsaUZsYWdzKSxcbiAgKTtcbiAgaWYgKHVuc3VwcG9ydGVkRmxhZ3NJblVzZS5sZW5ndGggPiAwKSB7XG4gICAgY29uc3QgcG9zdEZpeCA9IChjb3VudDogbnVtYmVyKSA9PlxuICAgICAgY291bnQgPiBkaXNwbGF5Q291bnQgPyBgIGFuZCAke2NvdW50IC0gZGlzcGxheUNvdW50fSBtb3JlLmAgOiAnJztcbiAgICB1aSgpLmxvZ2dlci5kZWJ1ZyhcbiAgICAgIGAke3llbGxvdygnXHUyNkEwJyl9IFBsdWdpbiAke2JvbGQoXG4gICAgICAgIExJR0hUSE9VU0VfUExVR0lOX1NMVUcsXG4gICAgICApfSB1c2VkIHVuc3VwcG9ydGVkIGZsYWdzOiAke2JvbGQoXG4gICAgICAgIHVuc3VwcG9ydGVkRmxhZ3NJblVzZS5zbGljZSgwLCBkaXNwbGF5Q291bnQpLmpvaW4oJywgJyksXG4gICAgICApfSR7cG9zdEZpeCh1bnN1cHBvcnRlZEZsYWdzSW5Vc2UubGVuZ3RoKX1gLFxuICAgICk7XG4gIH1cbn1cbiIsICJjb25zdCBfX2luamVjdGVkX2ZpbGVuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1saWdodGhvdXNlL3NyYy9saWIvcnVubmVyL2NvbnN0YW50cy50c1wiO2NvbnN0IF9faW5qZWN0ZWRfZGlybmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tbGlnaHRob3VzZS9zcmMvbGliL3J1bm5lclwiO2NvbnN0IF9faW5qZWN0ZWRfaW1wb3J0X21ldGFfdXJsX18gPSBcImZpbGU6Ly8vaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tbGlnaHRob3VzZS9zcmMvbGliL3J1bm5lci9jb25zdGFudHMudHNcIjtpbXBvcnQge1xuICB0eXBlIENsaUZsYWdzLFxuICB0eXBlIENvbmZpZyxcbiAgdHlwZSBJY3VNZXNzYWdlLFxuICB0eXBlIEF1ZGl0IGFzIExIQXVkaXQsXG4gIGRlZmF1bHRDb25maWcsXG59IGZyb20gJ2xpZ2h0aG91c2UnO1xuaW1wb3J0IHBhdGggZnJvbSAnbm9kZTpwYXRoJztcbmltcG9ydCB0eXBlIHsgQXVkaXQsIEdyb3VwIH0gZnJvbSAnQGNvZGUtcHVzaHVwL21vZGVscyc7XG5pbXBvcnQgeyBERUZBVUxUX0NIUk9NRV9GTEFHUywgTElHSFRIT1VTRV9PVVRQVVRfUEFUSCB9IGZyb20gJy4uL2NvbnN0YW50cy5qcyc7XG5cbmNvbnN0IHsgYXVkaXRzLCBjYXRlZ29yaWVzIH0gPSBkZWZhdWx0Q29uZmlnO1xuXG4vLyBpbnRlcm5hbCBpbnRlcm1lZGlhdGUgdmFyaWFibGUgdG8gZGVyaXZlIHRoZSByZWxldmFudCBhdWRpdHNcbmNvbnN0IGFsbFJhd0xpZ2h0aG91c2VBdWRpdHMgPSBhd2FpdCBQcm9taXNlLmFsbChcbiAgKGF1ZGl0cyA/PyBbXSkubWFwKGxvYWRMaWdodGhvdXNlQXVkaXQpLFxuKTtcblxuZXhwb3J0IGNvbnN0IFBMVUdJTl9TTFVHID0gJ2xpZ2h0aG91c2UnO1xuZXhwb3J0IGNvbnN0IExJR0hUSE9VU0VfTkFWSUdBVElPTl9BVURJVFM6IEF1ZGl0W10gPSBhbGxSYXdMaWdodGhvdXNlQXVkaXRzXG4gIC8vIFRoaXMgcGx1Z2luIG9ubHkgc3VwcG9ydHMgdGhlIFwibmF2aWdhdGlvblwiIG1vZGUgb2YgTGlnaHRob3VzZSBpbiB0aGUgY3VycmVudCBpbXBsZW1lbnRhdGlvblxuICAvLyBJZiB3ZSBkb24ndCBleGNsdWRlIG90aGVyIGF1ZGl0cyB3ZSB0aHJvdyBpbiB0aGUgcGx1Z2luIG91dHB1dCB2YWxpZGF0aW9uIGFzIHNvbWUgb2YgdGhlIHByb3ZpZGVkIGF1ZGl0cyBhcmUgbm90IGluY2x1ZGVkIGluIGBsaWdodGhvdXNlLXJlcG9ydC5qc29uYFxuICAuZmlsdGVyKFxuICAgIGF1ZGl0ID0+XG4gICAgICBhdWRpdC5tZXRhLnN1cHBvcnRlZE1vZGVzID09IG51bGwgfHxcbiAgICAgIChBcnJheS5pc0FycmF5KGF1ZGl0Lm1ldGEuc3VwcG9ydGVkTW9kZXMpICYmXG4gICAgICAgIGF1ZGl0Lm1ldGEuc3VwcG9ydGVkTW9kZXMuaW5jbHVkZXMoJ25hdmlnYXRpb24nKSksXG4gIClcbiAgLm1hcChhdWRpdCA9PiAoe1xuICAgIHNsdWc6IGF1ZGl0Lm1ldGEuaWQsXG4gICAgdGl0bGU6IGdldE1ldGFTdHJpbmcoYXVkaXQubWV0YS50aXRsZSksXG4gICAgZGVzY3JpcHRpb246IGdldE1ldGFTdHJpbmcoYXVkaXQubWV0YS5kZXNjcmlwdGlvbiksXG4gIH0pKTtcblxuY29uc3QgbmF2aWdhdGlvbkF1ZGl0U2x1Z3MgPSBuZXcgU2V0KFxuICBMSUdIVEhPVVNFX05BVklHQVRJT05fQVVESVRTLm1hcCgoeyBzbHVnIH0pID0+IHNsdWcpLFxuKTtcbmV4cG9ydCBjb25zdCBMSUdIVEhPVVNFX0dST1VQUzogR3JvdXBbXSA9IE9iamVjdC5lbnRyaWVzKGNhdGVnb3JpZXMgPz8ge30pLm1hcChcbiAgKFtpZCwgY2F0ZWdvcnldKSA9PiAoe1xuICAgIHNsdWc6IGlkLFxuICAgIHRpdGxlOiBnZXRNZXRhU3RyaW5nKGNhdGVnb3J5LnRpdGxlKSxcbiAgICAuLi4oY2F0ZWdvcnkuZGVzY3JpcHRpb24gJiYge1xuICAgICAgZGVzY3JpcHRpb246IGdldE1ldGFTdHJpbmcoY2F0ZWdvcnkuZGVzY3JpcHRpb24pLFxuICAgIH0pLFxuICAgIHJlZnM6IGNhdGVnb3J5LmF1ZGl0UmVmc1xuICAgICAgLmZpbHRlcigoeyBpZDogYXVkaXRTbHVnIH0pID0+IG5hdmlnYXRpb25BdWRpdFNsdWdzLmhhcyhhdWRpdFNsdWcpKVxuICAgICAgLm1hcChyZWYgPT4gKHtcbiAgICAgICAgc2x1ZzogcmVmLmlkLFxuICAgICAgICB3ZWlnaHQ6IHJlZi53ZWlnaHQsXG4gICAgICB9KSksXG4gIH0pLFxuKTtcblxuZnVuY3Rpb24gZ2V0TWV0YVN0cmluZyh2YWx1ZTogc3RyaW5nIHwgSWN1TWVzc2FnZSk6IHN0cmluZyB7XG4gIGlmICh0eXBlb2YgdmFsdWUgPT09ICdzdHJpbmcnKSB7XG4gICAgcmV0dXJuIHZhbHVlO1xuICB9XG4gIHJldHVybiB2YWx1ZS5mb3JtYXR0ZWREZWZhdWx0O1xufVxuXG5hc3luYyBmdW5jdGlvbiBsb2FkTGlnaHRob3VzZUF1ZGl0KFxuICB2YWx1ZTogQ29uZmlnLkF1ZGl0SnNvbixcbik6IFByb21pc2U8dHlwZW9mIExIQXVkaXQ+IHtcbiAgLy8gdGhlIHBhc3NlZCB2YWx1ZSBkaXJlY3RseSBpbmNsdWRlcyB0aGUgaW1wbGVtZW50YXRpb24gYXMgSlMgb2JqZWN0XG4gIC8vICAgc2hhcGU6IHsgaW1wbGVtZW50YXRpb246IHR5cGVvZiBMSEF1ZGl0OyBvcHRpb25zPzoge307IH1cbiAgaWYgKHR5cGVvZiB2YWx1ZSA9PT0gJ29iamVjdCcgJiYgJ2ltcGxlbWVudGF0aW9uJyBpbiB2YWx1ZSkge1xuICAgIHJldHVybiB2YWx1ZS5pbXBsZW1lbnRhdGlvbjtcbiAgfVxuICAvLyB0aGUgcGFzc2VkIHZhbHVlIGlzIGEgYExILkF1ZGl0YCBjbGFzcyBpbnN0YW5jZVxuICAvLyAgIHNoYXBlOiBMSEF1ZGl0XG4gIGlmICh0eXBlb2YgdmFsdWUgPT09ICdmdW5jdGlvbicpIHtcbiAgICByZXR1cm4gdmFsdWU7XG4gIH1cbiAgLy8gdGhlIHBhc3NlZCB2YWx1ZSBpcyB0aGUgcGF0aCBkaXJlY3RseVxuICAvLyAgIHNoYXBlOiBzdHJpbmdcbiAgLy8gb3RoZXJ3aXNlIGl0IGlzIGEgSlMgb2JqZWN0IG1haW50YWluaW5nIGEgYHBhdGhgIHByb3BlcnR5XG4gIC8vICAgc2hhcGU6IHsgcGF0aDogc3RyaW5nLCBvcHRpb25zPzoge307IH1cbiAgY29uc3QgZmlsZSA9IHR5cGVvZiB2YWx1ZSA9PT0gJ3N0cmluZycgPyB2YWx1ZSA6IHZhbHVlLnBhdGg7XG4gIGNvbnN0IG1vZHVsZSA9IChhd2FpdCBpbXBvcnQoYGxpZ2h0aG91c2UvY29yZS9hdWRpdHMvJHtmaWxlfS5qc2ApKSBhcyB7XG4gICAgZGVmYXVsdDogdHlwZW9mIExIQXVkaXQ7XG4gIH07XG4gIHJldHVybiBtb2R1bGUuZGVmYXVsdDtcbn1cblxuZXhwb3J0IGNvbnN0IExJR0hUSE9VU0VfUkVQT1JUX05BTUUgPSAnbGlnaHRob3VzZS1yZXBvcnQuanNvbic7XG5cbmV4cG9ydCBjb25zdCBERUZBVUxUX0NMSV9GTEFHUyA9IHtcbiAgLy8gZGVmYXVsdCB2YWx1ZXMgZXh0cmFjdGVkIGZyb21cbiAgLy8gaHR0cHM6Ly9naXRodWIuY29tL0dvb2dsZUNocm9tZS9saWdodGhvdXNlL2Jsb2IvN2Q4MDE3OGMzN2ExYjYwMGVhOGYwOTJmYzBiMDk4MDI5Nzk5YTY1OS9jbGkvY2xpLWZsYWdzLmpzI0w4MFxuICB2ZXJib3NlOiBmYWxzZSxcbiAgc2F2ZUFzc2V0czogZmFsc2UsXG4gIGNocm9tZUZsYWdzOiBERUZBVUxUX0NIUk9NRV9GTEFHUyxcbiAgcG9ydDogMCxcbiAgaG9zdG5hbWU6ICcxMjcuMC4wLjEnLFxuICB2aWV3OiBmYWxzZSxcbiAgY2hhbm5lbDogJ2NsaScsXG4gIC8vIGN1c3RvbSBvdmVyd3JpdGVzIGluIGZhdm91ciBvZiB0aGUgcGx1Z2luXG4gIC8vIGhpZGUgbG9ncyBieSBkZWZhdWx0XG4gIHF1aWV0OiB0cnVlLFxuICBvbmx5QXVkaXRzOiBbXSxcbiAgc2tpcEF1ZGl0czogW10sXG4gIG9ubHlDYXRlZ29yaWVzOiBbXSxcbiAgb3V0cHV0OiBbJ2pzb24nXSxcbiAgb3V0cHV0UGF0aDogcGF0aC5qb2luKExJR0hUSE9VU0VfT1VUUFVUX1BBVEgsIExJR0hUSE9VU0VfUkVQT1JUX05BTUUpLFxufSBzYXRpc2ZpZXMgUGFydGlhbDxDbGlGbGFncz47XG4iLCAiY29uc3QgX19pbmplY3RlZF9maWxlbmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tbGlnaHRob3VzZS9zcmMvbGliL3J1bm5lci9ydW5uZXIudHNcIjtjb25zdCBfX2luamVjdGVkX2Rpcm5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWxpZ2h0aG91c2Uvc3JjL2xpYi9ydW5uZXJcIjtjb25zdCBfX2luamVjdGVkX2ltcG9ydF9tZXRhX3VybF9fID0gXCJmaWxlOi8vL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWxpZ2h0aG91c2Uvc3JjL2xpYi9ydW5uZXIvcnVubmVyLnRzXCI7aW1wb3J0IHR5cGUgeyBSdW5uZXJSZXN1bHQgfSBmcm9tICdsaWdodGhvdXNlJztcbmltcG9ydCB7IHJ1bkxpZ2h0aG91c2UgfSBmcm9tICdsaWdodGhvdXNlL2NsaS9ydW4uanMnO1xuaW1wb3J0IHBhdGggZnJvbSAnbm9kZTpwYXRoJztcbmltcG9ydCB0eXBlIHsgQXVkaXRPdXRwdXRzLCBSdW5uZXJGdW5jdGlvbiB9IGZyb20gJ0Bjb2RlLXB1c2h1cC9tb2RlbHMnO1xuaW1wb3J0IHsgZW5zdXJlRGlyZWN0b3J5RXhpc3RzIH0gZnJvbSAnQGNvZGUtcHVzaHVwL3V0aWxzJztcbmltcG9ydCB7IERFRkFVTFRfQ0xJX0ZMQUdTIH0gZnJvbSAnLi9jb25zdGFudHMuanMnO1xuaW1wb3J0IHR5cGUgeyBMaWdodGhvdXNlQ2xpRmxhZ3MgfSBmcm9tICcuL3R5cGVzLmpzJztcbmltcG9ydCB7XG4gIGRldGVybWluZUFuZFNldExvZ0xldmVsLFxuICBnZXRDb25maWcsXG4gIG5vcm1hbGl6ZUF1ZGl0T3V0cHV0cyxcbiAgdG9BdWRpdE91dHB1dHMsXG59IGZyb20gJy4vdXRpbHMuanMnO1xuXG5leHBvcnQgZnVuY3Rpb24gY3JlYXRlUnVubmVyRnVuY3Rpb24oXG4gIHVybFVuZGVyVGVzdDogc3RyaW5nLFxuICBmbGFnczogTGlnaHRob3VzZUNsaUZsYWdzID0gREVGQVVMVF9DTElfRkxBR1MsXG4pOiBSdW5uZXJGdW5jdGlvbiB7XG4gIHJldHVybiBhc3luYyAoKTogUHJvbWlzZTxBdWRpdE91dHB1dHM+ID0+IHtcbiAgICBjb25zdCB7XG4gICAgICBjb25maWdQYXRoLFxuICAgICAgcHJlc2V0LFxuICAgICAgb3V0cHV0UGF0aCxcbiAgICAgIC4uLnBhcnNlZEZsYWdzXG4gICAgfTogUGFydGlhbDxMaWdodGhvdXNlQ2xpRmxhZ3M+ID0gZmxhZ3M7XG5cbiAgICBjb25zdCBsb2dMZXZlbCA9IGRldGVybWluZUFuZFNldExvZ0xldmVsKHBhcnNlZEZsYWdzKTtcblxuICAgIGNvbnN0IGNvbmZpZyA9IGF3YWl0IGdldENvbmZpZyh7IGNvbmZpZ1BhdGgsIHByZXNldCB9KTtcbiAgICBpZiAob3V0cHV0UGF0aCkge1xuICAgICAgYXdhaXQgZW5zdXJlRGlyZWN0b3J5RXhpc3RzKHBhdGguZGlybmFtZShvdXRwdXRQYXRoKSk7XG4gICAgfVxuXG4gICAgY29uc3QgZW5yaWNoZWRGbGFncyA9IHtcbiAgICAgIC4uLnBhcnNlZEZsYWdzLFxuICAgICAgbG9nTGV2ZWwsXG4gICAgICBvdXRwdXRQYXRoLFxuICAgIH07XG5cbiAgICBjb25zdCBydW5uZXJSZXN1bHQ6IHVua25vd24gPSBhd2FpdCBydW5MaWdodGhvdXNlKFxuICAgICAgdXJsVW5kZXJUZXN0LFxuICAgICAgZW5yaWNoZWRGbGFncyxcbiAgICAgIGNvbmZpZyxcbiAgICApO1xuXG4gICAgaWYgKHJ1bm5lclJlc3VsdCA9PSBudWxsKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoJ0xpZ2h0aG91c2UgZGlkIG5vdCBwcm9kdWNlIGEgcmVzdWx0LicpO1xuICAgIH1cblxuICAgIGNvbnN0IHsgbGhyIH0gPSBydW5uZXJSZXN1bHQgYXMgUnVubmVyUmVzdWx0O1xuICAgIGNvbnN0IGF1ZGl0T3V0cHV0cyA9IHRvQXVkaXRPdXRwdXRzKE9iamVjdC52YWx1ZXMobGhyLmF1ZGl0cyksIGZsYWdzKTtcblxuICAgIHJldHVybiBub3JtYWxpemVBdWRpdE91dHB1dHMoYXVkaXRPdXRwdXRzLCBlbnJpY2hlZEZsYWdzKTtcbiAgfTtcbn1cbiIsICJjb25zdCBfX2luamVjdGVkX2ZpbGVuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1saWdodGhvdXNlL3NyYy9saWIvcnVubmVyL3V0aWxzLnRzXCI7Y29uc3QgX19pbmplY3RlZF9kaXJuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1saWdodGhvdXNlL3NyYy9saWIvcnVubmVyXCI7Y29uc3QgX19pbmplY3RlZF9pbXBvcnRfbWV0YV91cmxfXyA9IFwiZmlsZTovLy9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1saWdodGhvdXNlL3NyYy9saWIvcnVubmVyL3V0aWxzLnRzXCI7aW1wb3J0IHsgYm9sZCB9IGZyb20gJ2Fuc2lzJztcbmltcG9ydCB0eXBlIHsgQ29uZmlnLCBGb3JtYXR0ZWRJY3UgfSBmcm9tICdsaWdodGhvdXNlJztcbmltcG9ydCBsb2cgZnJvbSAnbGlnaHRob3VzZS1sb2dnZXInO1xuaW1wb3J0IGRlc2t0b3BDb25maWcgZnJvbSAnbGlnaHRob3VzZS9jb3JlL2NvbmZpZy9kZXNrdG9wLWNvbmZpZy5qcyc7XG5pbXBvcnQgZXhwZXJpbWVudGFsQ29uZmlnIGZyb20gJ2xpZ2h0aG91c2UvY29yZS9jb25maWcvZXhwZXJpbWVudGFsLWNvbmZpZy5qcyc7XG5pbXBvcnQgcGVyZkNvbmZpZyBmcm9tICdsaWdodGhvdXNlL2NvcmUvY29uZmlnL3BlcmYtY29uZmlnLmpzJztcbmltcG9ydCB0eXBlIERldGFpbHMgZnJvbSAnbGlnaHRob3VzZS90eXBlcy9saHIvYXVkaXQtZGV0YWlscyc7XG5pbXBvcnQgdHlwZSB7IFJlc3VsdCB9IGZyb20gJ2xpZ2h0aG91c2UvdHlwZXMvbGhyL2F1ZGl0LXJlc3VsdCc7XG5pbXBvcnQgdHlwZSB7IEF1ZGl0T3V0cHV0LCBBdWRpdE91dHB1dHMgfSBmcm9tICdAY29kZS1wdXNodXAvbW9kZWxzJztcbmltcG9ydCB7XG4gIGZvcm1hdFJlcG9ydFNjb3JlLFxuICBpbXBvcnRNb2R1bGUsXG4gIHJlYWRKc29uRmlsZSxcbiAgdWksXG59IGZyb20gJ0Bjb2RlLXB1c2h1cC91dGlscyc7XG5pbXBvcnQgdHlwZSB7IExpZ2h0aG91c2VPcHRpb25zIH0gZnJvbSAnLi4vdHlwZXMuanMnO1xuaW1wb3J0IHsgbG9nVW5zdXBwb3J0ZWREZXRhaWxzLCB0b0F1ZGl0RGV0YWlscyB9IGZyb20gJy4vZGV0YWlscy9kZXRhaWxzLmpzJztcbmltcG9ydCB0eXBlIHsgTGlnaHRob3VzZUNsaUZsYWdzIH0gZnJvbSAnLi90eXBlcy5qcyc7XG5cbmV4cG9ydCBmdW5jdGlvbiBub3JtYWxpemVBdWRpdE91dHB1dHMoXG4gIGF1ZGl0T3V0cHV0czogQXVkaXRPdXRwdXRzLFxuICBmbGFnczogTGlnaHRob3VzZU9wdGlvbnMgPSB7IHNraXBBdWRpdHM6IFtdIH0sXG4pOiBBdWRpdE91dHB1dHMge1xuICBjb25zdCB0b1NraXAgPSBuZXcgU2V0KGZsYWdzLnNraXBBdWRpdHMgPz8gW10pO1xuICByZXR1cm4gYXVkaXRPdXRwdXRzLmZpbHRlcigoeyBzbHVnIH0pID0+ICF0b1NraXAuaGFzKHNsdWcpKTtcbn1cblxuZXhwb3J0IGNsYXNzIExpZ2h0aG91c2VBdWRpdFBhcnNpbmdFcnJvciBleHRlbmRzIEVycm9yIHtcbiAgY29uc3RydWN0b3Ioc2x1Zzogc3RyaW5nLCBlcnJvcjogRXJyb3IpIHtcbiAgICBzdXBlcihgXFxuQXVkaXQgJHtib2xkKHNsdWcpfSBmYWlsZWQgcGFyc2luZyBkZXRhaWxzOiBcXG4ke2Vycm9yLm1lc3NhZ2V9YCk7XG4gIH1cbn1cblxuZnVuY3Rpb24gZm9ybWF0QmFzZUF1ZGl0T3V0cHV0KGxockF1ZGl0OiBSZXN1bHQpOiBBdWRpdE91dHB1dCB7XG4gIGNvbnN0IHtcbiAgICBpZDogc2x1ZyxcbiAgICBzY29yZSxcbiAgICBudW1lcmljVmFsdWUsXG4gICAgZGlzcGxheVZhbHVlLFxuICAgIHNjb3JlRGlzcGxheU1vZGUsXG4gIH0gPSBsaHJBdWRpdDtcbiAgcmV0dXJuIHtcbiAgICBzbHVnLFxuICAgIHNjb3JlOiBzY29yZSA/PyAxLFxuICAgIHZhbHVlOiBudW1lcmljVmFsdWUgPz8gc2NvcmUgPz8gMCxcbiAgICBkaXNwbGF5VmFsdWU6XG4gICAgICBkaXNwbGF5VmFsdWUgPz9cbiAgICAgIChzY29yZURpc3BsYXlNb2RlID09PSAnYmluYXJ5J1xuICAgICAgICA/IHNjb3JlID09PSAxXG4gICAgICAgICAgPyAncGFzc2VkJ1xuICAgICAgICAgIDogJ2ZhaWxlZCdcbiAgICAgICAgOiBzY29yZVxuICAgICAgICAgID8gYCR7Zm9ybWF0UmVwb3J0U2NvcmUoc2NvcmUpfSVgXG4gICAgICAgICAgOiB1bmRlZmluZWQpLFxuICB9O1xufVxuXG5mdW5jdGlvbiBwcm9jZXNzQXVkaXREZXRhaWxzKFxuICBhdWRpdE91dHB1dDogQXVkaXRPdXRwdXQsXG4gIGRldGFpbHM6IEZvcm1hdHRlZEljdTxEZXRhaWxzPixcbik6IEF1ZGl0T3V0cHV0IHtcbiAgdHJ5IHtcbiAgICBjb25zdCBwYXJzZWREZXRhaWxzID0gdG9BdWRpdERldGFpbHMoZGV0YWlscyk7XG4gICAgcmV0dXJuIE9iamVjdC5rZXlzKHBhcnNlZERldGFpbHMpLmxlbmd0aCA+IDBcbiAgICAgID8geyAuLi5hdWRpdE91dHB1dCwgZGV0YWlsczogcGFyc2VkRGV0YWlscyB9XG4gICAgICA6IGF1ZGl0T3V0cHV0O1xuICB9IGNhdGNoIChlcnJvcikge1xuICAgIHRocm93IG5ldyBMaWdodGhvdXNlQXVkaXRQYXJzaW5nRXJyb3IoYXVkaXRPdXRwdXQuc2x1ZywgZXJyb3IgYXMgRXJyb3IpO1xuICB9XG59XG5cbmV4cG9ydCBmdW5jdGlvbiB0b0F1ZGl0T3V0cHV0cyhcbiAgbGhyQXVkaXRzOiBSZXN1bHRbXSxcbiAgeyB2ZXJib3NlID0gZmFsc2UgfTogeyB2ZXJib3NlPzogYm9vbGVhbiB9ID0ge30sXG4pOiBBdWRpdE91dHB1dHMge1xuICBpZiAodmVyYm9zZSkge1xuICAgIGxvZ1Vuc3VwcG9ydGVkRGV0YWlscyhsaHJBdWRpdHMpO1xuICB9XG4gIHJldHVybiBsaHJBdWRpdHMubWFwKGF1ZGl0ID0+IHtcbiAgICBjb25zdCBhdWRpdE91dHB1dCA9IGZvcm1hdEJhc2VBdWRpdE91dHB1dChhdWRpdCk7XG5cbiAgICByZXR1cm4gYXVkaXQuZGV0YWlscyA9PSBudWxsXG4gICAgICA/IGF1ZGl0T3V0cHV0XG4gICAgICA6IHByb2Nlc3NBdWRpdERldGFpbHMoYXVkaXRPdXRwdXQsIGF1ZGl0LmRldGFpbHMpO1xuICB9KTtcbn1cblxuZXhwb3J0IHR5cGUgTGlnaHRob3VzZUxvZ0xldmVsID1cbiAgfCAndmVyYm9zZSdcbiAgfCAnZXJyb3InXG4gIHwgJ2luZm8nXG4gIHwgJ3NpbGVudCdcbiAgfCAnd2FybidcbiAgfCB1bmRlZmluZWQ7XG5leHBvcnQgZnVuY3Rpb24gZGV0ZXJtaW5lQW5kU2V0TG9nTGV2ZWwoe1xuICB2ZXJib3NlLFxuICBxdWlldCxcbn06IHtcbiAgdmVyYm9zZT86IGJvb2xlYW47XG4gIHF1aWV0PzogYm9vbGVhbjtcbn0gPSB7fSk6IExpZ2h0aG91c2VMb2dMZXZlbCB7XG4gIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBmdW5jdGlvbmFsL25vLWxldFxuICBsZXQgbG9nTGV2ZWw6IExpZ2h0aG91c2VMb2dMZXZlbCA9ICdpbmZvJztcbiAgLy8gc2V0IGxvZ2dpbmcgcHJlZmVyZW5jZXNcbiAgaWYgKHZlcmJvc2UpIHtcbiAgICBsb2dMZXZlbCA9ICd2ZXJib3NlJztcbiAgfSBlbHNlIGlmIChxdWlldCkge1xuICAgIGxvZ0xldmVsID0gJ3NpbGVudCc7XG4gIH1cblxuICBsb2cuc2V0TGV2ZWwobG9nTGV2ZWwpO1xuXG4gIHJldHVybiBsb2dMZXZlbDtcbn1cblxuZXhwb3J0IHR5cGUgQ29uZmlnT3B0aW9ucyA9IFBhcnRpYWw8XG4gIFBpY2s8TGlnaHRob3VzZUNsaUZsYWdzLCAnY29uZmlnUGF0aCcgfCAncHJlc2V0Jz5cbj47XG5cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBnZXRDb25maWcoXG4gIG9wdGlvbnM6IENvbmZpZ09wdGlvbnMgPSB7fSxcbik6IFByb21pc2U8Q29uZmlnIHwgdW5kZWZpbmVkPiB7XG4gIGNvbnN0IHsgY29uZmlnUGF0aDogZmlsZXBhdGgsIHByZXNldCB9ID0gb3B0aW9ucztcblxuICBpZiAoZmlsZXBhdGggIT0gbnVsbCkge1xuICAgIGlmIChmaWxlcGF0aC5lbmRzV2l0aCgnLmpzb24nKSkge1xuICAgICAgLy8gUmVzb2x2ZSB0aGUgY29uZmlnIGZpbGUgcGF0aCByZWxhdGl2ZSB0byB3aGVyZSBjbGkgd2FzIGNhbGxlZC5cbiAgICAgIHJldHVybiByZWFkSnNvbkZpbGU8Q29uZmlnPihmaWxlcGF0aCk7XG4gICAgfSBlbHNlIGlmICgvXFwuKHRzfGpzfG1qcykkLy50ZXN0KGZpbGVwYXRoKSkge1xuICAgICAgcmV0dXJuIGltcG9ydE1vZHVsZTxDb25maWc+KHsgZmlsZXBhdGgsIGZvcm1hdDogJ2VzbScgfSk7XG4gICAgfSBlbHNlIHtcbiAgICAgIHVpKCkubG9nZ2VyLmluZm8oYEZvcm1hdCBvZiBmaWxlICR7ZmlsZXBhdGh9IG5vdCBzdXBwb3J0ZWRgKTtcbiAgICB9XG4gIH0gZWxzZSBpZiAocHJlc2V0ICE9IG51bGwpIHtcbiAgICBzd2l0Y2ggKHByZXNldCkge1xuICAgICAgY2FzZSAnZGVza3RvcCc6XG4gICAgICAgIHJldHVybiBkZXNrdG9wQ29uZmlnO1xuICAgICAgY2FzZSAncGVyZic6XG4gICAgICAgIHJldHVybiBwZXJmQ29uZmlnIGFzIENvbmZpZztcbiAgICAgIGNhc2UgJ2V4cGVyaW1lbnRhbCc6XG4gICAgICAgIHJldHVybiBleHBlcmltZW50YWxDb25maWcgYXMgQ29uZmlnO1xuICAgICAgZGVmYXVsdDpcbiAgICAgICAgLy8gYXMgcHJlc2V0IGlzIGEgc3RyaW5nIGxpdGVyYWwgdGhlIGRlZmF1bHQgY2FzZSBoZXJlIGlzIG5vcm1hbGx5IGNhdWdodCBieSBUUyBhbmQgbm90IHBvc3NpYmxlIHRvIGhhcHBlbi4gTm93IGluIHJlYWxpdHkgaXQgY2FuIGhhcHBlbiBhbmQgcHJlc2V0IGNvdWxkIGJlIGEgc3RyaW5nIG5vdCBpbmNsdWRlZCBpbiB0aGUgbGl0ZXJhbC5cbiAgICAgICAgLy8gVGhlcmVmb3JlLCB3ZSBoYXZlIHRvIHVzZSBgYXMgc3RyaW5nYC4gT3RoZXJ3aXNlLCBpdCB3aWxsIGNvbnNpZGVyIHByZXNldCBhcyB0eXBlIG5ldmVyXG4gICAgICAgIHVpKCkubG9nZ2VyLmluZm8oYFByZXNldCBcIiR7cHJlc2V0IGFzIHN0cmluZ31cIiBpcyBub3Qgc3VwcG9ydGVkYCk7XG4gICAgfVxuICB9XG4gIHJldHVybiB1bmRlZmluZWQ7XG59XG4iLCAiY29uc3QgX19pbmplY3RlZF9maWxlbmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tbGlnaHRob3VzZS9zcmMvbGliL3J1bm5lci9kZXRhaWxzL2RldGFpbHMudHNcIjtjb25zdCBfX2luamVjdGVkX2Rpcm5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWxpZ2h0aG91c2Uvc3JjL2xpYi9ydW5uZXIvZGV0YWlsc1wiO2NvbnN0IF9faW5qZWN0ZWRfaW1wb3J0X21ldGFfdXJsX18gPSBcImZpbGU6Ly8vaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tbGlnaHRob3VzZS9zcmMvbGliL3J1bm5lci9kZXRhaWxzL2RldGFpbHMudHNcIjtpbXBvcnQgeyBib2xkLCB5ZWxsb3cgfSBmcm9tICdhbnNpcyc7XG5pbXBvcnQgdHlwZSB7IEZvcm1hdHRlZEljdSB9IGZyb20gJ2xpZ2h0aG91c2UnO1xuaW1wb3J0IHR5cGUgRGV0YWlscyBmcm9tICdsaWdodGhvdXNlL3R5cGVzL2xoci9hdWRpdC1kZXRhaWxzJztcbmltcG9ydCB0eXBlIHsgUmVzdWx0IH0gZnJvbSAnbGlnaHRob3VzZS90eXBlcy9saHIvYXVkaXQtcmVzdWx0JztcbmltcG9ydCB0eXBlIHsgQXVkaXREZXRhaWxzLCBUYWJsZSB9IGZyb20gJ0Bjb2RlLXB1c2h1cC9tb2RlbHMnO1xuaW1wb3J0IHsgdWkgfSBmcm9tICdAY29kZS1wdXNodXAvdXRpbHMnO1xuaW1wb3J0IHsgUExVR0lOX1NMVUcgfSBmcm9tICcuLi9jb25zdGFudHMuanMnO1xuaW1wb3J0IHsgcGFyc2VPcHBvcnR1bml0eVRvQXVkaXREZXRhaWxzVGFibGUgfSBmcm9tICcuL29wcG9ydHVuaXR5LnR5cGUuanMnO1xuaW1wb3J0IHsgcGFyc2VUYWJsZVRvQXVkaXREZXRhaWxzVGFibGUgfSBmcm9tICcuL3RhYmxlLnR5cGUuanMnO1xuXG5leHBvcnQgZnVuY3Rpb24gdG9BdWRpdERldGFpbHM8VCBleHRlbmRzIEZvcm1hdHRlZEljdTxEZXRhaWxzPj4oXG4gIGRldGFpbHM6IFQgfCB1bmRlZmluZWQsXG4pOiBBdWRpdERldGFpbHMge1xuICBpZiAoZGV0YWlscyA9PSBudWxsKSB7XG4gICAgcmV0dXJuIHt9O1xuICB9XG5cbiAgY29uc3QgeyB0eXBlIH0gPSBkZXRhaWxzO1xuXG4gIHN3aXRjaCAodHlwZSkge1xuICAgIGNhc2UgJ3RhYmxlJzpcbiAgICAgIGNvbnN0IHRhYmxlOiBUYWJsZSB8IHVuZGVmaW5lZCA9IHBhcnNlVGFibGVUb0F1ZGl0RGV0YWlsc1RhYmxlKGRldGFpbHMpO1xuICAgICAgcmV0dXJuIHRhYmxlID8geyB0YWJsZSB9IDoge307XG4gICAgY2FzZSAnb3Bwb3J0dW5pdHknOlxuICAgICAgY29uc3Qgb3Bwb3J0dW5pdHk6IFRhYmxlIHwgdW5kZWZpbmVkID1cbiAgICAgICAgcGFyc2VPcHBvcnR1bml0eVRvQXVkaXREZXRhaWxzVGFibGUoZGV0YWlscyk7XG4gICAgICByZXR1cm4gb3Bwb3J0dW5pdHkgPyB7IHRhYmxlOiBvcHBvcnR1bml0eSB9IDoge307XG4gIH1cbiAgcmV0dXJuIHt9O1xufVxuXG4vLyBAVE9ETyBpbXBsZW1lbnQgYWxsIGRldGFpbHNcbmV4cG9ydCBjb25zdCB1bnN1cHBvcnRlZERldGFpbFR5cGVzID0gbmV3IFNldChbXG4gICdkZWJ1Z2RhdGEnLFxuICAndHJlZW1hcC1kYXRhJyxcbiAgJ3NjcmVlbnNob3QnLFxuICAnZmlsbXN0cmlwJyxcbiAgJ2NyaXRpY2FscmVxdWVzdGNoYWluJyxcbl0pO1xuXG5leHBvcnQgZnVuY3Rpb24gbG9nVW5zdXBwb3J0ZWREZXRhaWxzKFxuICBsaHJBdWRpdHM6IFJlc3VsdFtdLFxuICB7IGRpc3BsYXlDb3VudCA9IDMgfTogeyBkaXNwbGF5Q291bnQ/OiBudW1iZXIgfSA9IHt9LFxuKSB7XG4gIGNvbnN0IHNsdWdzV2l0aERldGFpbFBhcnNpbmdFcnJvcnMgPSBbXG4gICAgLi4ubmV3IFNldChcbiAgICAgIGxockF1ZGl0c1xuICAgICAgICAuZmlsdGVyKCh7IGRldGFpbHMgfSkgPT5cbiAgICAgICAgICB1bnN1cHBvcnRlZERldGFpbFR5cGVzLmhhcyhkZXRhaWxzPy50eXBlIGFzIHN0cmluZyksXG4gICAgICAgIClcbiAgICAgICAgLm1hcCgoeyBkZXRhaWxzIH0pID0+IGRldGFpbHM/LnR5cGUpLFxuICAgICksXG4gIF07XG4gIGlmIChzbHVnc1dpdGhEZXRhaWxQYXJzaW5nRXJyb3JzLmxlbmd0aCA+IDApIHtcbiAgICBjb25zdCBwb3N0Rml4ID0gKGNvdW50OiBudW1iZXIpID0+XG4gICAgICBjb3VudCA+IGRpc3BsYXlDb3VudCA/IGAgYW5kICR7Y291bnQgLSBkaXNwbGF5Q291bnR9IG1vcmUuYCA6ICcnO1xuICAgIHVpKCkubG9nZ2VyLmRlYnVnKFxuICAgICAgYCR7eWVsbG93KCdcdTI2QTAnKX0gUGx1Z2luICR7Ym9sZChcbiAgICAgICAgUExVR0lOX1NMVUcsXG4gICAgICApfSBza2lwcGVkIHBhcnNpbmcgb2YgdW5zdXBwb3J0ZWQgYXVkaXQgZGV0YWlsczogJHtib2xkKFxuICAgICAgICBzbHVnc1dpdGhEZXRhaWxQYXJzaW5nRXJyb3JzLnNsaWNlKDAsIGRpc3BsYXlDb3VudCkuam9pbignLCAnKSxcbiAgICAgICl9JHtwb3N0Rml4KHNsdWdzV2l0aERldGFpbFBhcnNpbmdFcnJvcnMubGVuZ3RoKX1gLFxuICAgICk7XG4gIH1cbn1cbiIsICJjb25zdCBfX2luamVjdGVkX2ZpbGVuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1saWdodGhvdXNlL3NyYy9saWIvcnVubmVyL2RldGFpbHMvb3Bwb3J0dW5pdHkudHlwZS50c1wiO2NvbnN0IF9faW5qZWN0ZWRfZGlybmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tbGlnaHRob3VzZS9zcmMvbGliL3J1bm5lci9kZXRhaWxzXCI7Y29uc3QgX19pbmplY3RlZF9pbXBvcnRfbWV0YV91cmxfXyA9IFwiZmlsZTovLy9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1saWdodGhvdXNlL3NyYy9saWIvcnVubmVyL2RldGFpbHMvb3Bwb3J0dW5pdHkudHlwZS50c1wiO2ltcG9ydCB0eXBlIERldGFpbHMgZnJvbSAnbGlnaHRob3VzZS90eXBlcy9saHIvYXVkaXQtZGV0YWlscyc7XG5pbXBvcnQge1xuICB0eXBlIFRhYmxlLFxuICB0eXBlIFRhYmxlUm93T2JqZWN0LFxuICB0YWJsZVNjaGVtYSxcbn0gZnJvbSAnQGNvZGUtcHVzaHVwL21vZGVscyc7XG5pbXBvcnQgeyBmb3JtYXRCeXRlcywgZm9ybWF0RHVyYXRpb24sIGh0bWwgfSBmcm9tICdAY29kZS1wdXNodXAvdXRpbHMnO1xuaW1wb3J0IHsgcGFyc2VUYWJsZUNvbHVtbnMsIHBhcnNlVGFibGVFbnRyeSB9IGZyb20gJy4vdGFibGUudHlwZS5qcyc7XG5pbXBvcnQgeyBMaWdodGhvdXNlQXVkaXREZXRhaWxzUGFyc2luZ0Vycm9yIH0gZnJvbSAnLi91dGlscy5qcyc7XG5cbmV4cG9ydCBmdW5jdGlvbiBwYXJzZU9wcG9ydHVuaXR5VG9BdWRpdERldGFpbHNUYWJsZShcbiAgZGV0YWlsczogRGV0YWlscy5PcHBvcnR1bml0eSxcbik6IFRhYmxlIHwgdW5kZWZpbmVkIHtcbiAgY29uc3QgeyBoZWFkaW5nczogcmF3SGVhZGluZ3MsIGl0ZW1zIH0gPSBkZXRhaWxzO1xuXG4gIGlmIChpdGVtcy5sZW5ndGggPT09IDApIHtcbiAgICByZXR1cm4gdW5kZWZpbmVkO1xuICB9XG5cbiAgdHJ5IHtcbiAgICByZXR1cm4gdGFibGVTY2hlbWEoKS5wYXJzZSh7XG4gICAgICB0aXRsZTogJ09wcG9ydHVuaXR5JyxcbiAgICAgIGNvbHVtbnM6IHBhcnNlVGFibGVDb2x1bW5zKHJhd0hlYWRpbmdzKSxcbiAgICAgIHJvd3M6IGl0ZW1zLm1hcChyb3cgPT4gcGFyc2VPcHBvcnR1bml0eUl0ZW1Ub1RhYmxlUm93KHJvdywgcmF3SGVhZGluZ3MpKSxcbiAgICB9KTtcbiAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICB0aHJvdyBuZXcgTGlnaHRob3VzZUF1ZGl0RGV0YWlsc1BhcnNpbmdFcnJvcihcbiAgICAgICdvcHBvcnR1bml0eScsXG4gICAgICB7IGl0ZW1zLCBoZWFkaW5nczogcmF3SGVhZGluZ3MgfSxcbiAgICAgIChlcnJvciBhcyBFcnJvcikubWVzc2FnZS50b1N0cmluZygpLFxuICAgICk7XG4gIH1cbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHBhcnNlT3Bwb3J0dW5pdHlJdGVtVG9UYWJsZVJvdyhcbiAgb3Bwb3J0dW5pdHlJdGVtOiBEZXRhaWxzLk9wcG9ydHVuaXR5SXRlbSxcbiAgaGVhZGluZ3M6IERldGFpbHMuVGFibGVDb2x1bW5IZWFkaW5nW10sXG4pOiBUYWJsZVJvd09iamVjdCB7XG4gIGNvbnN0IGtleXMgPSBuZXcgU2V0KGhlYWRpbmdzLm1hcCgoeyBrZXkgfSkgPT4ga2V5KSk7XG4gIGNvbnN0IHZhbHVlVHlwZXNCeUtleSA9IG5ldyBNYXAoXG4gICAgaGVhZGluZ3MubWFwKCh7IGtleSwgdmFsdWVUeXBlIH0pID0+IFtrZXksIHZhbHVlVHlwZV0pLFxuICApO1xuXG4gIHJldHVybiB7XG4gICAgLi4uKE9iamVjdC5mcm9tRW50cmllcyhcbiAgICAgIE9iamVjdC5lbnRyaWVzKG9wcG9ydHVuaXR5SXRlbSlcbiAgICAgICAgLy8gZm9yd2FyZCBvbmx5IHByb3BlcnRpZXMgd2l0aCBhIGdpdmVuIHZhbHVlXG4gICAgICAgIC5maWx0ZXIoKFtrZXldKSA9PiBrZXlzLmhhcyhrZXkpKVxuICAgICAgICAubWFwKChba2V5LCB2YWx1ZV0pID0+IHtcbiAgICAgICAgICBjb25zdCB2YWx1ZVR5cGUgPSB2YWx1ZVR5cGVzQnlLZXkuZ2V0KGtleSkgYXMgRGV0YWlscy5JdGVtVmFsdWVUeXBlO1xuICAgICAgICAgIHJldHVybiBwYXJzZU9wcG9ydHVuaXR5RW50cnkoW2tleSwgdmFsdWVdLCB2YWx1ZVR5cGUpO1xuICAgICAgICB9KSxcbiAgICApIGFzIFRhYmxlUm93T2JqZWN0KSxcbiAgfTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHBhcnNlT3Bwb3J0dW5pdHlFbnRyeShcbiAgW2tleSwgdmFsdWVdOiBbXG4gICAga2V5b2YgRGV0YWlscy5PcHBvcnR1bml0eUl0ZW0sXG4gICAgRGV0YWlscy5PcHBvcnR1bml0eUl0ZW1bc3RyaW5nXSxcbiAgXSxcbiAgdmFsdWVUeXBlOiBEZXRhaWxzLkl0ZW1WYWx1ZVR5cGUsXG4pIHtcbiAgc3dpdGNoIChrZXkpIHtcbiAgICBjYXNlICd1cmwnOlxuICAgICAgcmV0dXJuIFtrZXksIGh0bWwubGluayhTdHJpbmcodmFsdWUpKV07XG4gICAgY2FzZSAnd2FzdGVkUGVyY2VudCc6XG4gICAgICByZXR1cm4gW2tleSwgYCR7TnVtYmVyKHZhbHVlKS50b0ZpeGVkKDIpfSAlYF07XG4gICAgY2FzZSAndG90YWxCeXRlcyc6XG4gICAgY2FzZSAnd2FzdGVkQnl0ZXMnOlxuICAgICAgcmV0dXJuIFtrZXksIGZvcm1hdEJ5dGVzKE51bWJlcih2YWx1ZSkpXTtcbiAgICBjYXNlICd3YXN0ZWRNcyc6XG4gICAgICByZXR1cm4gW2tleSwgZm9ybWF0RHVyYXRpb24oTnVtYmVyKHZhbHVlKSldO1xuICAgIGRlZmF1bHQ6XG4gICAgICByZXR1cm4gcGFyc2VUYWJsZUVudHJ5KFtrZXksIHZhbHVlXSwgdmFsdWVUeXBlKTtcbiAgfVxufVxuIiwgImNvbnN0IF9faW5qZWN0ZWRfZmlsZW5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWxpZ2h0aG91c2Uvc3JjL2xpYi9ydW5uZXIvZGV0YWlscy90YWJsZS50eXBlLnRzXCI7Y29uc3QgX19pbmplY3RlZF9kaXJuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1saWdodGhvdXNlL3NyYy9saWIvcnVubmVyL2RldGFpbHNcIjtjb25zdCBfX2luamVjdGVkX2ltcG9ydF9tZXRhX3VybF9fID0gXCJmaWxlOi8vL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWxpZ2h0aG91c2Uvc3JjL2xpYi9ydW5uZXIvZGV0YWlscy90YWJsZS50eXBlLnRzXCI7aW1wb3J0IHR5cGUgRGV0YWlscyBmcm9tICdsaWdodGhvdXNlL3R5cGVzL2xoci9hdWRpdC1kZXRhaWxzJztcbmltcG9ydCB7XG4gIHR5cGUgVGFibGUsXG4gIHR5cGUgVGFibGVDb2x1bW5PYmplY3QsXG4gIHR5cGUgVGFibGVSb3dPYmplY3QsXG4gIHRhYmxlU2NoZW1hLFxufSBmcm9tICdAY29kZS1wdXNodXAvbW9kZWxzJztcbmltcG9ydCB7IGZvcm1hdFRhYmxlSXRlbVByb3BlcnR5VmFsdWUgfSBmcm9tICcuL2l0ZW0tdmFsdWUuanMnO1xuaW1wb3J0IHsgTGlnaHRob3VzZUF1ZGl0RGV0YWlsc1BhcnNpbmdFcnJvciB9IGZyb20gJy4vdXRpbHMuanMnO1xuXG5leHBvcnQgZnVuY3Rpb24gcGFyc2VUYWJsZVRvQXVkaXREZXRhaWxzVGFibGUoXG4gIGRldGFpbHM6IERldGFpbHMuVGFibGUsXG4pOiBUYWJsZSB8IHVuZGVmaW5lZCB7XG4gIGNvbnN0IHsgaGVhZGluZ3M6IHJhd0hlYWRpbmdzLCBpdGVtcyB9ID0gZGV0YWlscztcblxuICBpZiAoaXRlbXMubGVuZ3RoID09PSAwKSB7XG4gICAgcmV0dXJuIHVuZGVmaW5lZDtcbiAgfVxuXG4gIHRyeSB7XG4gICAgcmV0dXJuIHRhYmxlU2NoZW1hKCkucGFyc2Uoe1xuICAgICAgY29sdW1uczogcGFyc2VUYWJsZUNvbHVtbnMocmF3SGVhZGluZ3MpLFxuICAgICAgcm93czogaXRlbXMubWFwKHJvdyA9PiBwYXJzZVRhYmxlUm93KHJvdywgcmF3SGVhZGluZ3MpKSxcbiAgICB9KTtcbiAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICB0aHJvdyBuZXcgTGlnaHRob3VzZUF1ZGl0RGV0YWlsc1BhcnNpbmdFcnJvcihcbiAgICAgICd0YWJsZScsXG4gICAgICB7IGl0ZW1zLCBoZWFkaW5nczogcmF3SGVhZGluZ3MgfSxcbiAgICAgIChlcnJvciBhcyBFcnJvcikubWVzc2FnZS50b1N0cmluZygpLFxuICAgICk7XG4gIH1cbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHBhcnNlVGFibGVDb2x1bW5zKFxuICByYXdIZWFkaW5nczogRGV0YWlscy5UYWJsZUNvbHVtbkhlYWRpbmdbXSxcbik6IFRhYmxlQ29sdW1uT2JqZWN0W10ge1xuICByZXR1cm4gcmF3SGVhZGluZ3MubWFwKCh7IGtleSwgbGFiZWwgfSkgPT4gKHtcbiAgICBrZXk6IGtleSA/PyAnJyxcbiAgICAuLi4odHlwZW9mIGxhYmVsID09PSAnc3RyaW5nJyAmJiBsYWJlbC5sZW5ndGggPiAwID8geyBsYWJlbCB9IDoge30pLFxuICAgIGFsaWduOiAnbGVmdCcsXG4gIH0pKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHBhcnNlVGFibGVSb3coXG4gIHRhYmxlSXRlbTogRGV0YWlscy5UYWJsZUl0ZW0sXG4gIGhlYWRpbmdzOiBEZXRhaWxzLlRhYmxlQ29sdW1uSGVhZGluZ1tdLFxuKTogVGFibGVSb3dPYmplY3Qge1xuICBjb25zdCBrZXlzID0gbmV3IFNldChoZWFkaW5ncy5tYXAoKHsga2V5IH0pID0+IGtleSkpO1xuICBjb25zdCB2YWx1ZVR5cGVzQnlLZXkgPSBuZXcgTWFwKFxuICAgIGhlYWRpbmdzLm1hcCgoeyBrZXksIHZhbHVlVHlwZSB9KSA9PiBba2V5LCB2YWx1ZVR5cGVdKSxcbiAgKTtcblxuICByZXR1cm4gT2JqZWN0LmZyb21FbnRyaWVzKFxuICAgIE9iamVjdC5lbnRyaWVzKHRhYmxlSXRlbSlcbiAgICAgIC5maWx0ZXIoKFtrZXldKSA9PiBrZXlzLmhhcyhrZXkpKVxuICAgICAgLm1hcCgoW2tleSwgdmFsdWVdKSA9PiB7XG4gICAgICAgIGNvbnN0IHZhbHVlVHlwZSA9IHZhbHVlVHlwZXNCeUtleS5nZXQoa2V5KTtcbiAgICAgICAgcmV0dXJuIHBhcnNlVGFibGVFbnRyeShba2V5LCB2YWx1ZV0sIHZhbHVlVHlwZSk7XG4gICAgICB9KSxcbiAgKSBhcyBUYWJsZVJvd09iamVjdDtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHBhcnNlVGFibGVFbnRyeTxUIGV4dGVuZHMgRGV0YWlscy5UYWJsZUl0ZW0+KFxuICBba2V5LCB2YWx1ZV06IFtrZXlvZiBULCBUW2tleW9mIFRdXSxcbiAgdmFsdWVUeXBlPzogRGV0YWlscy5JdGVtVmFsdWVUeXBlLFxuKTogW2tleW9mIFQsIERldGFpbHMuSXRlbVZhbHVlIHwgdW5kZWZpbmVkXSB7XG4gIGlmICh2YWx1ZSA9PSBudWxsKSB7XG4gICAgcmV0dXJuIFtrZXksIHZhbHVlXTtcbiAgfVxuXG4gIHJldHVybiBba2V5LCBmb3JtYXRUYWJsZUl0ZW1Qcm9wZXJ0eVZhbHVlKHZhbHVlLCB2YWx1ZVR5cGUpXTtcbn1cbiIsICJjb25zdCBfX2luamVjdGVkX2ZpbGVuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1saWdodGhvdXNlL3NyYy9saWIvcnVubmVyL2RldGFpbHMvaXRlbS12YWx1ZS50c1wiO2NvbnN0IF9faW5qZWN0ZWRfZGlybmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tbGlnaHRob3VzZS9zcmMvbGliL3J1bm5lci9kZXRhaWxzXCI7Y29uc3QgX19pbmplY3RlZF9pbXBvcnRfbWV0YV91cmxfXyA9IFwiZmlsZTovLy9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1saWdodGhvdXNlL3NyYy9saWIvcnVubmVyL2RldGFpbHMvaXRlbS12YWx1ZS50c1wiO2ltcG9ydCB7IGJvbGQgfSBmcm9tICdhbnNpcyc7XG5pbXBvcnQgdHlwZSB7IEljdU1lc3NhZ2UgfSBmcm9tICdsaWdodGhvdXNlJztcbmltcG9ydCB0eXBlIERldGFpbHMgZnJvbSAnbGlnaHRob3VzZS90eXBlcy9saHIvYXVkaXQtZGV0YWlscyc7XG5pbXBvcnQge1xuICBmb3JtYXRCeXRlcyxcbiAgZm9ybWF0RHVyYXRpb24sXG4gIGh0bWwsXG4gIHRydW5jYXRlVGV4dCxcbiAgdWksXG59IGZyb20gJ0Bjb2RlLXB1c2h1cC91dGlscyc7XG5cbmV4cG9ydCB0eXBlIFByaW1pdGl2ZUl0ZW1WYWx1ZSA9IHN0cmluZyB8IG51bWJlciB8IGJvb2xlYW47XG5leHBvcnQgdHlwZSBPYmplY3RJdGVtVmFsdWUgPSBFeGNsdWRlPFxuICBEZXRhaWxzLkl0ZW1WYWx1ZSxcbiAgUHJpbWl0aXZlSXRlbVZhbHVlIHwgSWN1TWVzc2FnZVxuPjtcbmV4cG9ydCB0eXBlIFNpbXBsZUl0ZW1WYWx1ZSA9XG4gIHwgRXh0cmFjdDxcbiAgICAgIE9iamVjdEl0ZW1WYWx1ZSxcbiAgICAgIERldGFpbHMuTnVtZXJpY1ZhbHVlIHwgRGV0YWlscy5Db2RlVmFsdWUgfCBEZXRhaWxzLlVybFZhbHVlXG4gICAgPlxuICB8IFByaW1pdGl2ZUl0ZW1WYWx1ZTtcblxuZXhwb3J0IGZ1bmN0aW9uIHRyaW1TbGljZShpdGVtPzogUHJpbWl0aXZlSXRlbVZhbHVlLCBtYXhMZW5ndGggPSAwKSB7XG4gIGNvbnN0IHN0ciA9IFN0cmluZyhpdGVtKS50cmltKCk7XG4gIHJldHVybiBtYXhMZW5ndGggPiAwID8gc3RyLnNsaWNlKDAsIG1heExlbmd0aCkgOiBzdHI7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBwYXJzZU5vZGVWYWx1ZShub2RlPzogRGV0YWlscy5Ob2RlVmFsdWUpOiBzdHJpbmcge1xuICBjb25zdCB7IHNlbGVjdG9yID0gJycgfSA9IG5vZGUgPz8ge307XG4gIHJldHVybiBzZWxlY3Rvcjtcbn1cblxuLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIG1heC1saW5lcy1wZXItZnVuY3Rpb25cbmV4cG9ydCBmdW5jdGlvbiBmb3JtYXRUYWJsZUl0ZW1Qcm9wZXJ0eVZhbHVlKFxuICBpdGVtVmFsdWU/OiBEZXRhaWxzLkl0ZW1WYWx1ZSxcbiAgaXRlbVZhbHVlRm9ybWF0PzogRGV0YWlscy5JdGVtVmFsdWVUeXBlLFxuKSB7XG4gIC8vIG51bGxcbiAgaWYgKGl0ZW1WYWx1ZSA9PSBudWxsKSB7XG4gICAgcmV0dXJuICcnO1xuICB9XG5cbiAgLy8gUHJpbWl0aXZlIFZhbHVlc1xuICBpZiAoaXRlbVZhbHVlRm9ybWF0ID09IG51bGwpIHtcbiAgICBpZiAodHlwZW9mIGl0ZW1WYWx1ZSA9PT0gJ3N0cmluZycpIHtcbiAgICAgIHJldHVybiB0cmltU2xpY2UoaXRlbVZhbHVlKTtcbiAgICB9XG5cbiAgICBpZiAodHlwZW9mIGl0ZW1WYWx1ZSA9PT0gJ251bWJlcicpIHtcbiAgICAgIHJldHVybiBOdW1iZXIoaXRlbVZhbHVlKTtcbiAgICB9XG5cbiAgICBpZiAodHlwZW9mIGl0ZW1WYWx1ZSA9PT0gJ2Jvb2xlYW4nKSB7XG4gICAgICByZXR1cm4gaXRlbVZhbHVlO1xuICAgIH1cbiAgfVxuXG4gIGNvbnN0IHBhcnNlZEl0ZW1WYWx1ZSA9IHBhcnNlVGFibGVJdGVtUHJvcGVydHlWYWx1ZShpdGVtVmFsdWUpO1xuXG4gIC8qIGVzbGludC1kaXNhYmxlIEB0eXBlc2NyaXB0LWVzbGludC9uby1tYWdpYy1udW1iZXJzICovXG4gIHN3aXRjaCAoaXRlbVZhbHVlRm9ybWF0KSB7XG4gICAgY2FzZSAnYnl0ZXMnOlxuICAgICAgcmV0dXJuIGZvcm1hdEJ5dGVzKE51bWJlcihwYXJzZWRJdGVtVmFsdWUpKTtcbiAgICBjYXNlICdjb2RlJzpcbiAgICAgIHJldHVybiBodG1sLmNvZGUodHJpbVNsaWNlKHBhcnNlZEl0ZW1WYWx1ZSBhcyBzdHJpbmcpKTtcbiAgICBjYXNlICdsaW5rJzpcbiAgICAgIGNvbnN0IGxpbmsgPSBwYXJzZWRJdGVtVmFsdWUgYXMgRGV0YWlscy5MaW5rVmFsdWU7XG4gICAgICByZXR1cm4gaHRtbC5saW5rKGxpbmsudXJsLCBsaW5rLnRleHQpO1xuICAgIGNhc2UgJ3VybCc6XG4gICAgICBjb25zdCB1cmwgPSBwYXJzZWRJdGVtVmFsdWUgYXMgc3RyaW5nO1xuICAgICAgcmV0dXJuIGh0bWwubGluayh1cmwpO1xuICAgIGNhc2UgJ3RpbWVzcGFuTXMnOlxuICAgIGNhc2UgJ21zJzpcbiAgICAgIHJldHVybiBmb3JtYXREdXJhdGlvbihOdW1iZXIocGFyc2VkSXRlbVZhbHVlKSk7XG4gICAgY2FzZSAnbm9kZSc6XG4gICAgICByZXR1cm4gcGFyc2VOb2RlVmFsdWUoaXRlbVZhbHVlIGFzIERldGFpbHMuTm9kZVZhbHVlKTtcbiAgICBjYXNlICdzb3VyY2UtbG9jYXRpb24nOlxuICAgICAgcmV0dXJuIHRydW5jYXRlVGV4dChTdHJpbmcocGFyc2VkSXRlbVZhbHVlKSwgMjAwKTtcbiAgICBjYXNlICdudW1lcmljJzpcbiAgICAgIGNvbnN0IG51bSA9IE51bWJlcihwYXJzZWRJdGVtVmFsdWUpO1xuICAgICAgaWYgKG51bS50b0ZpeGVkKDMpLnRvU3RyaW5nKCkuZW5kc1dpdGgoJy4wMDAnKSkge1xuICAgICAgICByZXR1cm4gU3RyaW5nKG51bSk7XG4gICAgICB9XG4gICAgICByZXR1cm4gU3RyaW5nKG51bS50b0ZpeGVkKDMpKTtcbiAgICBjYXNlICd0ZXh0JzpcbiAgICAgIHJldHVybiB0cnVuY2F0ZVRleHQoU3RyaW5nKHBhcnNlZEl0ZW1WYWx1ZSksIDUwMCk7XG4gICAgY2FzZSAnbXVsdGknOiAvLyBAVE9ET1xuICAgICAgLy8gQFRPRE8gbG9nIHZlcmJvc2UgZmlyc3QsIHRoZW4gaW1wbGVtZW50IGRhdGEgdHlwZVxuICAgICAgdWkoKS5sb2dnZXIuaW5mbyhgRm9ybWF0IHR5cGUgJHtib2xkKCdtdWx0aScpfSBpcyBub3QgaW1wbGVtZW50ZWRgKTtcbiAgICAgIHJldHVybiAnJztcbiAgICBjYXNlICd0aHVtYm5haWwnOiAvLyBAVE9ET1xuICAgICAgLy8gQFRPRE8gbG9nIHZlcmJvc2UgZmlyc3QsIHRoZW4gaW1wbGVtZW50IGRhdGEgdHlwZVxuICAgICAgdWkoKS5sb2dnZXIuaW5mbyhgRm9ybWF0IHR5cGUgJHtib2xkKCd0aHVtYm5haWwnKX0gaXMgbm90IGltcGxlbWVudGVkYCk7XG4gICAgICByZXR1cm4gJyc7XG4gIH1cbiAgLyogZXNsaW50LWVuYWJsZSBAdHlwZXNjcmlwdC1lc2xpbnQvbm8tbWFnaWMtbnVtYmVycyAqL1xuXG4gIHJldHVybiBpdGVtVmFsdWU7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBwYXJzZVNpbXBsZUl0ZW1WYWx1ZShcbiAgaXRlbTogU2ltcGxlSXRlbVZhbHVlLFxuKTogUHJpbWl0aXZlSXRlbVZhbHVlIHtcbiAgaWYgKHR5cGVvZiBpdGVtID09PSAnb2JqZWN0Jykge1xuICAgIGNvbnN0IHZhbHVlID0gaXRlbS52YWx1ZTtcbiAgICBpZiAodHlwZW9mIHZhbHVlID09PSAnb2JqZWN0Jykge1xuICAgICAgcmV0dXJuIHZhbHVlLmZvcm1hdHRlZERlZmF1bHQ7XG4gICAgfVxuICAgIHJldHVybiB2YWx1ZTtcbiAgfVxuICByZXR1cm4gaXRlbTtcbn1cblxuLy8gQFRPRE8gZXh0cmFjdCBMaW5rIHR5cGUgZnJvbSBsb2dpY1xuZXhwb3J0IGZ1bmN0aW9uIHBhcnNlVGFibGVJdGVtUHJvcGVydHlWYWx1ZShcbiAgaXRlbVZhbHVlPzogRGV0YWlscy5JdGVtVmFsdWUsXG4pOiBQcmltaXRpdmVJdGVtVmFsdWUgfCBEZXRhaWxzLkxpbmtWYWx1ZSB7XG4gIGlmIChpdGVtVmFsdWUgPT0gbnVsbCkge1xuICAgIHJldHVybiAnJztcbiAgfVxuXG4gIC8vIFByaW1pdGl2ZSBWYWx1ZXNcbiAgaWYgKFxuICAgIHR5cGVvZiBpdGVtVmFsdWUgPT09ICdzdHJpbmcnIHx8XG4gICAgdHlwZW9mIGl0ZW1WYWx1ZSA9PT0gJ251bWJlcicgfHxcbiAgICB0eXBlb2YgaXRlbVZhbHVlID09PSAnYm9vbGVhbidcbiAgKSB7XG4gICAgcmV0dXJuIHBhcnNlU2ltcGxlSXRlbVZhbHVlKGl0ZW1WYWx1ZSk7XG4gIH1cblxuICAvLyBPYmplY3QgVmFsdWVzXG4gIGNvbnN0IG9iamVjdFZhbHVlID0gaXRlbVZhbHVlIGFzIE9iamVjdEl0ZW1WYWx1ZTtcbiAgY29uc3QgeyB0eXBlIH0gPSBvYmplY3RWYWx1ZTtcbiAgc3dpdGNoICh0eXBlKSB7XG4gICAgY2FzZSAnY29kZSc6XG4gICAgY2FzZSAndXJsJzpcbiAgICAgIHJldHVybiBTdHJpbmcocGFyc2VTaW1wbGVJdGVtVmFsdWUob2JqZWN0VmFsdWUpKTtcbiAgICBjYXNlICdub2RlJzpcbiAgICAgIHJldHVybiBwYXJzZU5vZGVWYWx1ZShvYmplY3RWYWx1ZSk7XG4gICAgY2FzZSAnbGluayc6XG4gICAgICByZXR1cm4gb2JqZWN0VmFsdWU7XG4gICAgY2FzZSAnbnVtZXJpYyc6XG4gICAgICByZXR1cm4gTnVtYmVyKHBhcnNlU2ltcGxlSXRlbVZhbHVlKG9iamVjdFZhbHVlKSk7XG4gICAgY2FzZSAnc291cmNlLWxvY2F0aW9uJzpcbiAgICAgIGNvbnN0IHsgdXJsIH0gPSBvYmplY3RWYWx1ZTtcbiAgICAgIHJldHVybiBTdHJpbmcodXJsKTtcbiAgICBjYXNlICdzdWJpdGVtcyc6XG4gICAgICAvLyBAVE9ETyBsb2cgdmVyYm9zZSBmaXJzdCwgdGhlbiBpbXBsZW1lbnQgZGF0YSB0eXBlXG4gICAgICB1aSgpLmxvZ2dlci5pbmZvKGBWYWx1ZSB0eXBlICR7Ym9sZCgnc3ViaXRlbXMnKX0gaXMgbm90IGltcGxlbWVudGVkYCk7XG4gICAgICByZXR1cm4gJyc7XG4gICAgY2FzZSAnZGVidWdkYXRhJzpcbiAgICAgIC8vIEBUT0RPIGxvZyB2ZXJib3NlIGZpcnN0LCB0aGVuIGltcGxlbWVudCBkYXRhIHR5cGVcbiAgICAgIHVpKCkubG9nZ2VyLmluZm8oYFZhbHVlIHR5cGUgJHtib2xkKCdkZWJ1Z2RhdGEnKX0gaXMgbm90IGltcGxlbWVudGVkYCwge1xuICAgICAgICBzaWxlbnQ6IHRydWUsXG4gICAgICB9KTtcbiAgICAgIHJldHVybiAnJztcbiAgfVxuICAvLyBJY3VNZXNzYWdlXG4gIHJldHVybiBwYXJzZVNpbXBsZUl0ZW1WYWx1ZShvYmplY3RWYWx1ZSBhcyBTaW1wbGVJdGVtVmFsdWUpO1xufVxuIiwgImNvbnN0IF9faW5qZWN0ZWRfZmlsZW5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWxpZ2h0aG91c2Uvc3JjL2xpYi9ydW5uZXIvZGV0YWlscy91dGlscy50c1wiO2NvbnN0IF9faW5qZWN0ZWRfZGlybmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tbGlnaHRob3VzZS9zcmMvbGliL3J1bm5lci9kZXRhaWxzXCI7Y29uc3QgX19pbmplY3RlZF9pbXBvcnRfbWV0YV91cmxfXyA9IFwiZmlsZTovLy9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1saWdodGhvdXNlL3NyYy9saWIvcnVubmVyL2RldGFpbHMvdXRpbHMudHNcIjtpbXBvcnQgeyBib2xkIH0gZnJvbSAnYW5zaXMnO1xuaW1wb3J0IHR5cGUgRGV0YWlscyBmcm9tICdsaWdodGhvdXNlL3R5cGVzL2xoci9hdWRpdC1kZXRhaWxzJztcblxuZXhwb3J0IGNsYXNzIExpZ2h0aG91c2VBdWRpdERldGFpbHNQYXJzaW5nRXJyb3IgZXh0ZW5kcyBFcnJvciB7XG4gIGNvbnN0cnVjdG9yKFxuICAgIHR5cGU6IERldGFpbHNbJ3R5cGUnXSxcbiAgICByYXdUYWJsZTogUmVjb3JkPHN0cmluZywgdW5rbm93bj4sXG4gICAgZXJyb3I6IHN0cmluZyxcbiAgKSB7XG4gICAgc3VwZXIoXG4gICAgICBgUGFyc2luZyBsaWdodGhvdXNlIHJlcG9ydCBkZXRhaWxzICR7Ym9sZChcbiAgICAgICAgdHlwZSxcbiAgICAgICl9IGZhaWxlZDogXFxuUmF3IGRhdGE6XFxuICR7SlNPTi5zdHJpbmdpZnkocmF3VGFibGUsIG51bGwsIDIpfVxcbiR7ZXJyb3J9YCxcbiAgICApO1xuICB9XG59XG4iLCAiY29uc3QgX19pbmplY3RlZF9maWxlbmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tbGlnaHRob3VzZS9zcmMvbGliL3V0aWxzLnRzXCI7Y29uc3QgX19pbmplY3RlZF9kaXJuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1saWdodGhvdXNlL3NyYy9saWJcIjtjb25zdCBfX2luamVjdGVkX2ltcG9ydF9tZXRhX3VybF9fID0gXCJmaWxlOi8vL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWxpZ2h0aG91c2Uvc3JjL2xpYi91dGlscy50c1wiO2ltcG9ydCB0eXBlIHsgQXVkaXQsIENhdGVnb3J5UmVmLCBHcm91cCB9IGZyb20gJ0Bjb2RlLXB1c2h1cC9tb2RlbHMnO1xuaW1wb3J0IHsgZmlsdGVySXRlbVJlZnNCeSwgdG9BcnJheSB9IGZyb20gJ0Bjb2RlLXB1c2h1cC91dGlscyc7XG5pbXBvcnQgeyBMSUdIVEhPVVNFX1BMVUdJTl9TTFVHIH0gZnJvbSAnLi9jb25zdGFudHMuanMnO1xuaW1wb3J0IHR5cGUgeyBMaWdodGhvdXNlQ2xpRmxhZ3MgfSBmcm9tICcuL3J1bm5lci90eXBlcy5qcyc7XG5cbmV4cG9ydCB0eXBlIExpZ2h0aG91c2VHcm91cFNsdWdzID1cbiAgfCAncGVyZm9ybWFuY2UnXG4gIHwgJ2FjY2Vzc2liaWxpdHknXG4gIHwgJ2Jlc3QtcHJhY3RpY2VzJ1xuICB8ICdzZW8nXG4gIHwgJ3B3YSc7XG5cbmV4cG9ydCBmdW5jdGlvbiBsaWdodGhvdXNlR3JvdXBSZWYoXG4gIGdyb3VwU2x1ZzogTGlnaHRob3VzZUdyb3VwU2x1Z3MsXG4gIHdlaWdodCA9IDEsXG4pOiBDYXRlZ29yeVJlZiB7XG4gIHJldHVybiB7XG4gICAgcGx1Z2luOiBMSUdIVEhPVVNFX1BMVUdJTl9TTFVHLFxuICAgIHNsdWc6IGdyb3VwU2x1ZyxcbiAgICB0eXBlOiAnZ3JvdXAnLFxuICAgIHdlaWdodCxcbiAgfTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGxpZ2h0aG91c2VBdWRpdFJlZihhdWRpdFNsdWc6IHN0cmluZywgd2VpZ2h0ID0gMSk6IENhdGVnb3J5UmVmIHtcbiAgcmV0dXJuIHtcbiAgICBwbHVnaW46IExJR0hUSE9VU0VfUExVR0lOX1NMVUcsXG4gICAgc2x1ZzogYXVkaXRTbHVnLFxuICAgIHR5cGU6ICdhdWRpdCcsXG4gICAgd2VpZ2h0LFxuICB9O1xufVxuXG5leHBvcnQgY2xhc3MgQXVkaXRzTm90SW1wbGVtZW50ZWRFcnJvciBleHRlbmRzIEVycm9yIHtcbiAgY29uc3RydWN0b3IoYXVkaXRTbHVnczogc3RyaW5nW10pIHtcbiAgICBzdXBlcihgYXVkaXRzOiBcIiR7YXVkaXRTbHVncy5qb2luKCcsICcpfVwiIG5vdCBpbXBsZW1lbnRlZGApO1xuICB9XG59XG5cbmV4cG9ydCBmdW5jdGlvbiB2YWxpZGF0ZUF1ZGl0cyhhdWRpdHM6IEF1ZGl0W10sIG9ubHlBdWRpdHM6IHN0cmluZ1tdKTogYm9vbGVhbiB7XG4gIGNvbnN0IG1pc3NpbmdBdWR0aXMgPSB0b0FycmF5KG9ubHlBdWRpdHMpLmZpbHRlcihcbiAgICBzbHVnID0+ICFhdWRpdHMuc29tZShhdWRpdCA9PiBhdWRpdC5zbHVnID09PSBzbHVnKSxcbiAgKTtcbiAgaWYgKG1pc3NpbmdBdWR0aXMubGVuZ3RoID4gMCkge1xuICAgIHRocm93IG5ldyBBdWRpdHNOb3RJbXBsZW1lbnRlZEVycm9yKG1pc3NpbmdBdWR0aXMpO1xuICB9XG4gIHJldHVybiB0cnVlO1xufVxuXG5leHBvcnQgY2xhc3MgQ2F0ZWdvcmllc05vdEltcGxlbWVudGVkRXJyb3IgZXh0ZW5kcyBFcnJvciB7XG4gIGNvbnN0cnVjdG9yKGNhdGVnb3J5U2x1Z3M6IHN0cmluZ1tdKSB7XG4gICAgc3VwZXIoYGNhdGVnb3JpZXM6IFwiJHtjYXRlZ29yeVNsdWdzLmpvaW4oJywgJyl9XCIgbm90IGltcGxlbWVudGVkYCk7XG4gIH1cbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHZhbGlkYXRlT25seUNhdGVnb3JpZXMoXG4gIGdyb3VwczogR3JvdXBbXSxcbiAgb25seUNhdGVnb3JpZXM6IHN0cmluZyB8IHN0cmluZ1tdLFxuKTogYm9vbGVhbiB7XG4gIGNvbnN0IG1pc3NpbmdDYXRlZ29yaWVzID0gdG9BcnJheShvbmx5Q2F0ZWdvcmllcykuZmlsdGVyKHNsdWcgPT5cbiAgICBncm91cHMuZXZlcnkoZ3JvdXAgPT4gZ3JvdXAuc2x1ZyAhPT0gc2x1ZyksXG4gICk7XG4gIGlmIChtaXNzaW5nQ2F0ZWdvcmllcy5sZW5ndGggPiAwKSB7XG4gICAgdGhyb3cgbmV3IENhdGVnb3JpZXNOb3RJbXBsZW1lbnRlZEVycm9yKG1pc3NpbmdDYXRlZ29yaWVzKTtcbiAgfVxuICByZXR1cm4gdHJ1ZTtcbn1cblxuZXhwb3J0IHR5cGUgRmlsdGVyT3B0aW9ucyA9IFBhcnRpYWw8XG4gIFBpY2s8TGlnaHRob3VzZUNsaUZsYWdzLCAnb25seUF1ZGl0cycgfCAnb25seUNhdGVnb3JpZXMnIHwgJ3NraXBBdWRpdHMnPlxuPjtcblxuZXhwb3J0IGZ1bmN0aW9uIGZpbHRlckF1ZGl0c0FuZEdyb3Vwc0J5T25seU9wdGlvbnMoXG4gIGF1ZGl0czogQXVkaXRbXSxcbiAgZ3JvdXBzOiBHcm91cFtdLFxuICBvcHRpb25zPzogRmlsdGVyT3B0aW9ucyxcbik6IHtcbiAgYXVkaXRzOiBBdWRpdFtdO1xuICBncm91cHM6IEdyb3VwW107XG59IHtcbiAgY29uc3Qge1xuICAgIG9ubHlBdWRpdHMgPSBbXSxcbiAgICBza2lwQXVkaXRzID0gW10sXG4gICAgb25seUNhdGVnb3JpZXMgPSBbXSxcbiAgfSA9IG9wdGlvbnMgPz8ge307XG5cbiAgLy8gY2F0ZWdvcnkgd2lucyBvdmVyIGF1ZGl0c1xuICBpZiAob25seUNhdGVnb3JpZXMubGVuZ3RoID4gMCkge1xuICAgIHZhbGlkYXRlT25seUNhdGVnb3JpZXMoZ3JvdXBzLCBvbmx5Q2F0ZWdvcmllcyk7XG5cbiAgICBjb25zdCBjYXRlZ29yeVNsdWdzID0gbmV3IFNldChvbmx5Q2F0ZWdvcmllcyk7XG4gICAgY29uc3QgZmlsdGVyZWRHcm91cHM6IEdyb3VwW10gPSBncm91cHMuZmlsdGVyKCh7IHNsdWcgfSkgPT5cbiAgICAgIGNhdGVnb3J5U2x1Z3MuaGFzKHNsdWcpLFxuICAgICk7XG4gICAgY29uc3QgYXVkaXRTbHVnc0Zyb21SZW1haW5pbmdHcm91cHMgPSBuZXcgU2V0KFxuICAgICAgZmlsdGVyZWRHcm91cHMuZmxhdE1hcCgoeyByZWZzIH0pID0+IHJlZnMubWFwKCh7IHNsdWcgfSkgPT4gc2x1ZykpLFxuICAgICk7XG4gICAgcmV0dXJuIHtcbiAgICAgIGF1ZGl0czogYXVkaXRzLmZpbHRlcigoeyBzbHVnIH0pID0+XG4gICAgICAgIGF1ZGl0U2x1Z3NGcm9tUmVtYWluaW5nR3JvdXBzLmhhcyhzbHVnKSxcbiAgICAgICksXG4gICAgICBncm91cHM6IGZpbHRlcmVkR3JvdXBzLFxuICAgIH07XG4gIH0gZWxzZSBpZiAob25seUF1ZGl0cy5sZW5ndGggPiAwIHx8IHNraXBBdWRpdHMubGVuZ3RoID4gMCkge1xuICAgIHZhbGlkYXRlQXVkaXRzKGF1ZGl0cywgb25seUF1ZGl0cyk7XG4gICAgdmFsaWRhdGVBdWRpdHMoYXVkaXRzLCBza2lwQXVkaXRzKTtcbiAgICBjb25zdCBvbmx5QXVkaXRTbHVncyA9IG5ldyBTZXQob25seUF1ZGl0cyk7XG4gICAgY29uc3Qgc2tpcEF1ZGl0U2x1Z3MgPSBuZXcgU2V0KHNraXBBdWRpdHMpO1xuICAgIGNvbnN0IGZpbHRlckF1ZGl0cyA9ICh7IHNsdWcgfTogUGljazxBdWRpdCwgJ3NsdWcnPikgPT5cbiAgICAgICEoXG4gICAgICAgIC8vIGF1ZGl0IGlzIE5PVCBpbiBnaXZlbiBvbmx5QXVkaXRTbHVnc1xuICAgICAgICAoXG4gICAgICAgICAgKG9ubHlBdWRpdHMubGVuZ3RoID4gMCAmJiAhb25seUF1ZGl0U2x1Z3MuaGFzKHNsdWcpKSB8fFxuICAgICAgICAgIC8vIGF1ZGl0IElTIGluIGdpdmVuIHNraXBBdWRpdFNsdWdzXG4gICAgICAgICAgKHNraXBBdWRpdHMubGVuZ3RoID4gMCAmJiBza2lwQXVkaXRTbHVncy5oYXMoc2x1ZykpXG4gICAgICAgIClcbiAgICAgICk7XG4gICAgcmV0dXJuIHtcbiAgICAgIGF1ZGl0czogYXVkaXRzLmZpbHRlcihmaWx0ZXJBdWRpdHMpLFxuICAgICAgZ3JvdXBzOiBmaWx0ZXJJdGVtUmVmc0J5KGdyb3VwcywgZmlsdGVyQXVkaXRzKSxcbiAgICB9O1xuICB9XG4gIC8vIHJldHVybiB1bmNoYW5nZWRcbiAgcmV0dXJuIHtcbiAgICBhdWRpdHMsXG4gICAgZ3JvdXBzLFxuICB9O1xufVxuIiwgImNvbnN0IF9faW5qZWN0ZWRfZmlsZW5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvY29kZS1wdXNodXAucHJlc2V0LnRzXCI7Y29uc3QgX19pbmplY3RlZF9kaXJuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpXCI7Y29uc3QgX19pbmplY3RlZF9pbXBvcnRfbWV0YV91cmxfXyA9IFwiZmlsZTovLy9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL2NvZGUtcHVzaHVwLnByZXNldC50c1wiO2ltcG9ydCB0eXBlIHtcbiAgQ2F0ZWdvcnlDb25maWcsXG4gIENvcmVDb25maWcsXG59IGZyb20gJy4vcGFja2FnZXMvbW9kZWxzL3NyYy9pbmRleC5qcyc7XG5pbXBvcnQgY292ZXJhZ2VQbHVnaW4sIHtcbiAgZ2V0TnhDb3ZlcmFnZVBhdGhzLFxufSBmcm9tICcuL3BhY2thZ2VzL3BsdWdpbi1jb3ZlcmFnZS9zcmMvaW5kZXguanMnO1xuaW1wb3J0IGRvY0NvdmVyYWdlUGx1Z2luLCB7IERvY0NvdmVyYWdlUGx1Z2luQ29uZmlnIH0gZnJvbSAnLi9wYWNrYWdlcy9wbHVnaW4tZG9jLWNvdmVyYWdlL3NyYy9pbmRleC5qcyc7XG5pbXBvcnQgeyBncm91cHMsIFBMVUdJTl9TTFVHIH0gZnJvbSAnLi9wYWNrYWdlcy9wbHVnaW4tZG9jLWNvdmVyYWdlL3NyYy9saWIvY29uc3RhbnRzLmpzJztcbmltcG9ydCB7IGZpbHRlckdyb3Vwc0J5T25seUF1ZGl0cyB9IGZyb20gJy4vcGFja2FnZXMvcGx1Z2luLWRvYy1jb3ZlcmFnZS9zcmMvbGliL3V0aWxzLmpzJztcbmltcG9ydCBlc2xpbnRQbHVnaW4sIHtcbiAgZXNsaW50Q29uZmlnRnJvbUFsbE54UHJvamVjdHMsXG4gIGVzbGludENvbmZpZ0Zyb21OeFByb2plY3QsXG59IGZyb20gJy4vcGFja2FnZXMvcGx1Z2luLWVzbGludC9zcmMvaW5kZXguanMnO1xuaW1wb3J0IGpzUGFja2FnZXNQbHVnaW4gZnJvbSAnLi9wYWNrYWdlcy9wbHVnaW4tanMtcGFja2FnZXMvc3JjL2luZGV4LmpzJztcbmltcG9ydCBsaWdodGhvdXNlUGx1Z2luLCB7XG4gIGxpZ2h0aG91c2VHcm91cFJlZixcbn0gZnJvbSAnLi9wYWNrYWdlcy9wbHVnaW4tbGlnaHRob3VzZS9zcmMvaW5kZXguanMnO1xuXG5leHBvcnQgY29uc3QganNQYWNrYWdlc0NhdGVnb3JpZXM6IENhdGVnb3J5Q29uZmlnW10gPSBbXG4gIHtcbiAgICBzbHVnOiAnc2VjdXJpdHknLFxuICAgIHRpdGxlOiAnU2VjdXJpdHknLFxuICAgIGRlc2NyaXB0aW9uOiAnRmluZHMga25vd24gKip2dWxuZXJhYmlsaXRpZXMqKiBpbiAzcmQtcGFydHkgcGFja2FnZXMuJyxcbiAgICByZWZzOiBbXG4gICAgICB7XG4gICAgICAgIHR5cGU6ICdncm91cCcsXG4gICAgICAgIHBsdWdpbjogJ2pzLXBhY2thZ2VzJyxcbiAgICAgICAgc2x1ZzogJ25wbS1hdWRpdCcsXG4gICAgICAgIHdlaWdodDogMSxcbiAgICAgIH0sXG4gICAgXSxcbiAgfSxcbiAge1xuICAgIHNsdWc6ICd1cGRhdGVzJyxcbiAgICB0aXRsZTogJ1VwZGF0ZXMnLFxuICAgIGRlc2NyaXB0aW9uOiAnRmluZHMgKipvdXRkYXRlZCoqIDNyZC1wYXJ0eSBwYWNrYWdlcy4nLFxuICAgIHJlZnM6IFtcbiAgICAgIHtcbiAgICAgICAgdHlwZTogJ2dyb3VwJyxcbiAgICAgICAgcGx1Z2luOiAnanMtcGFja2FnZXMnLFxuICAgICAgICBzbHVnOiAnbnBtLW91dGRhdGVkJyxcbiAgICAgICAgd2VpZ2h0OiAxLFxuICAgICAgfSxcbiAgICBdLFxuICB9LFxuXTtcblxuZXhwb3J0IGNvbnN0IGxpZ2h0aG91c2VDYXRlZ29yaWVzOiBDYXRlZ29yeUNvbmZpZ1tdID0gW1xuICB7XG4gICAgc2x1ZzogJ3BlcmZvcm1hbmNlJyxcbiAgICB0aXRsZTogJ1BlcmZvcm1hbmNlJyxcbiAgICByZWZzOiBbbGlnaHRob3VzZUdyb3VwUmVmKCdwZXJmb3JtYW5jZScpXSxcbiAgfSxcbiAge1xuICAgIHNsdWc6ICdhMTF5JyxcbiAgICB0aXRsZTogJ0FjY2Vzc2liaWxpdHknLFxuICAgIHJlZnM6IFtsaWdodGhvdXNlR3JvdXBSZWYoJ2FjY2Vzc2liaWxpdHknKV0sXG4gIH0sXG4gIHtcbiAgICBzbHVnOiAnYmVzdC1wcmFjdGljZXMnLFxuICAgIHRpdGxlOiAnQmVzdCBQcmFjdGljZXMnLFxuICAgIHJlZnM6IFtsaWdodGhvdXNlR3JvdXBSZWYoJ2Jlc3QtcHJhY3RpY2VzJyldLFxuICB9LFxuICB7XG4gICAgc2x1ZzogJ3NlbycsXG4gICAgdGl0bGU6ICdTRU8nLFxuICAgIHJlZnM6IFtsaWdodGhvdXNlR3JvdXBSZWYoJ3NlbycpXSxcbiAgfSxcbl07XG5cbmV4cG9ydCBjb25zdCBlc2xpbnRDYXRlZ29yaWVzOiBDYXRlZ29yeUNvbmZpZ1tdID0gW1xuICB7XG4gICAgc2x1ZzogJ2J1Zy1wcmV2ZW50aW9uJyxcbiAgICB0aXRsZTogJ0J1ZyBwcmV2ZW50aW9uJyxcbiAgICBkZXNjcmlwdGlvbjogJ0xpbnQgcnVsZXMgdGhhdCBmaW5kICoqcG90ZW50aWFsIGJ1Z3MqKiBpbiB5b3VyIGNvZGUuJyxcbiAgICByZWZzOiBbeyB0eXBlOiAnZ3JvdXAnLCBwbHVnaW46ICdlc2xpbnQnLCBzbHVnOiAncHJvYmxlbXMnLCB3ZWlnaHQ6IDEgfV0sXG4gIH0sXG4gIHtcbiAgICBzbHVnOiAnY29kZS1zdHlsZScsXG4gICAgdGl0bGU6ICdDb2RlIHN0eWxlJyxcbiAgICBkZXNjcmlwdGlvbjpcbiAgICAgICdMaW50IHJ1bGVzIHRoYXQgcHJvbW90ZSAqKmdvb2QgcHJhY3RpY2VzKiogYW5kIGNvbnNpc3RlbmN5IGluIHlvdXIgY29kZS4nLFxuICAgIHJlZnM6IFt7IHR5cGU6ICdncm91cCcsIHBsdWdpbjogJ2VzbGludCcsIHNsdWc6ICdzdWdnZXN0aW9ucycsIHdlaWdodDogMSB9XSxcbiAgfSxcbl07XG5cbmV4cG9ydCBmdW5jdGlvbiBnZXREb2NDb3ZlcmFnZUNhdGVnb3JpZXMoY29uZmlnOiBEb2NDb3ZlcmFnZVBsdWdpbkNvbmZpZyk6IENhdGVnb3J5Q29uZmlnW10ge1xuICByZXR1cm4gW3tcbiAgICBzbHVnOiAnZG9jLWNvdmVyYWdlLWNhdCcsXG4gICAgdGl0bGU6ICdEb2N1bWVudGF0aW9uIGNvdmVyYWdlJyxcbiAgICBkZXNjcmlwdGlvbjogJ01lYXN1cmVzIGhvdyBtdWNoIG9mIHlvdXIgY29kZSBpcyAqKmRvY3VtZW50ZWQqKi4nLFxuICAgIHJlZnM6IGZpbHRlckdyb3Vwc0J5T25seUF1ZGl0cyhncm91cHMsIGNvbmZpZykubWFwKGdyb3VwID0+ICh7XG4gICAgICB3ZWlnaHQ6IDEsXG4gICAgICB0eXBlOiAnZ3JvdXAnLFxuICAgICAgcGx1Z2luOiBQTFVHSU5fU0xVRyxcbiAgICAgIHNsdWc6IGdyb3VwLnNsdWcsXG4gICAgfSkpLFxuICB9XTtcbn1cblxuXG5cbmV4cG9ydCBjb25zdCBjb3ZlcmFnZUNhdGVnb3JpZXM6IENhdGVnb3J5Q29uZmlnW10gPSBbXG4gIHtcbiAgICBzbHVnOiAnY29kZS1jb3ZlcmFnZScsXG4gICAgdGl0bGU6ICdDb2RlIGNvdmVyYWdlJyxcbiAgICBkZXNjcmlwdGlvbjogJ01lYXN1cmVzIGhvdyBtdWNoIG9mIHlvdXIgY29kZSBpcyAqKmNvdmVyZWQgYnkgdGVzdHMqKi4nLFxuICAgIHJlZnM6IFtcbiAgICAgIHtcbiAgICAgICAgdHlwZTogJ2dyb3VwJyxcbiAgICAgICAgcGx1Z2luOiAnY292ZXJhZ2UnLFxuICAgICAgICBzbHVnOiAnY292ZXJhZ2UnLFxuICAgICAgICB3ZWlnaHQ6IDEsXG4gICAgICB9LFxuICAgIF0sXG4gIH0sXG5dO1xuXG5leHBvcnQgY29uc3QganNQYWNrYWdlc0NvcmVDb25maWcgPSBhc3luYyAoKTogUHJvbWlzZTxDb3JlQ29uZmlnPiA9PiB7XG4gIHJldHVybiB7XG4gICAgcGx1Z2luczogW2F3YWl0IGpzUGFja2FnZXNQbHVnaW4oKV0sXG4gICAgY2F0ZWdvcmllczoganNQYWNrYWdlc0NhdGVnb3JpZXMsXG4gIH07XG59O1xuXG5leHBvcnQgY29uc3QgbGlnaHRob3VzZUNvcmVDb25maWcgPSBhc3luYyAoXG4gIHVybDogc3RyaW5nLFxuKTogUHJvbWlzZTxDb3JlQ29uZmlnPiA9PiB7XG4gIHJldHVybiB7XG4gICAgcGx1Z2luczogW2F3YWl0IGxpZ2h0aG91c2VQbHVnaW4odXJsKV0sXG4gICAgY2F0ZWdvcmllczogbGlnaHRob3VzZUNhdGVnb3JpZXMsXG4gIH07XG59O1xuXG5leHBvcnQgY29uc3QgZG9jQ292ZXJhZ2VDb3JlQ29uZmlnID0gYXN5bmMgKGNvbmZpZzogRG9jQ292ZXJhZ2VQbHVnaW5Db25maWcpOiBQcm9taXNlPENvcmVDb25maWc+ID0+IHtcbiAgcmV0dXJuIHtcbiAgICBwbHVnaW5zOiBbYXdhaXQgZG9jQ292ZXJhZ2VQbHVnaW4oY29uZmlnKV0sXG4gICAgY2F0ZWdvcmllczogZ2V0RG9jQ292ZXJhZ2VDYXRlZ29yaWVzKGNvbmZpZyksXG4gIH07XG59O1xuXG5leHBvcnQgY29uc3QgZXNsaW50Q29yZUNvbmZpZ054ID0gYXN5bmMgKFxuICBwcm9qZWN0TmFtZT86IHN0cmluZyxcbik6IFByb21pc2U8Q29yZUNvbmZpZz4gPT4ge1xuICByZXR1cm4ge1xuICAgIHBsdWdpbnM6IFtcbiAgICAgIGF3YWl0IGVzbGludFBsdWdpbihcbiAgICAgICAgYXdhaXQgKHByb2plY3ROYW1lXG4gICAgICAgICAgPyBlc2xpbnRDb25maWdGcm9tTnhQcm9qZWN0KHByb2plY3ROYW1lKVxuICAgICAgICAgIDogZXNsaW50Q29uZmlnRnJvbUFsbE54UHJvamVjdHMoKSksXG4gICAgICApLFxuICAgIF0sXG4gICAgY2F0ZWdvcmllczogZXNsaW50Q2F0ZWdvcmllcyxcbiAgfTtcbn07XG5cbmV4cG9ydCBjb25zdCBjb3ZlcmFnZUNvcmVDb25maWdOeCA9IGFzeW5jIChcbiAgcHJvamVjdE5hbWU/OiBzdHJpbmcsXG4pOiBQcm9taXNlPENvcmVDb25maWc+ID0+IHtcbiAgaWYgKHByb2plY3ROYW1lKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKCdjb3ZlcmFnZUNvcmVDb25maWdOeCBmb3Igc2luZ2xlIHByb2plY3RzIG5vdCBpbXBsZW1lbnRlZCcpO1xuICB9XG4gIGNvbnN0IHRhcmdldE5hbWVzID0gWyd1bml0LXRlc3QnLCAnaW50ZWdyYXRpb24tdGVzdCddO1xuICBjb25zdCB0YXJnZXRBcmdzID0gW1xuICAgICctdCcsXG4gICAgJ3VuaXQtdGVzdCcsXG4gICAgJ2ludGVncmF0aW9uLXRlc3QnLFxuICAgICctLWNvdmVyYWdlLmVuYWJsZWQnLFxuICAgICctLXNraXBOeENhY2hlJyxcbiAgXTtcbiAgcmV0dXJuIHtcbiAgICBwbHVnaW5zOiBbXG4gICAgICBhd2FpdCBjb3ZlcmFnZVBsdWdpbih7XG4gICAgICAgIGNvdmVyYWdlVG9vbENvbW1hbmQ6IHtcbiAgICAgICAgICBjb21tYW5kOiAnbnB4JyxcbiAgICAgICAgICBhcmdzOiBbXG4gICAgICAgICAgICAnbngnLFxuICAgICAgICAgICAgcHJvamVjdE5hbWUgPyBgcnVuIC0tcHJvamVjdCAke3Byb2plY3ROYW1lfWAgOiAncnVuLW1hbnknLFxuICAgICAgICAgICAgLi4udGFyZ2V0QXJncyxcbiAgICAgICAgICBdLFxuICAgICAgICB9LFxuICAgICAgICByZXBvcnRzOiBhd2FpdCBnZXROeENvdmVyYWdlUGF0aHModGFyZ2V0TmFtZXMpLFxuICAgICAgfSksXG4gICAgXSxcbiAgICBjYXRlZ29yaWVzOiBjb3ZlcmFnZUNhdGVnb3JpZXMsXG4gIH07XG59O1xuIiwgImNvbnN0IF9faW5qZWN0ZWRfZmlsZW5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvdXRpbHMvc3JjL2luZGV4LnRzXCI7Y29uc3QgX19pbmplY3RlZF9kaXJuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3V0aWxzL3NyY1wiO2NvbnN0IF9faW5qZWN0ZWRfaW1wb3J0X21ldGFfdXJsX18gPSBcImZpbGU6Ly8vaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy91dGlscy9zcmMvaW5kZXgudHNcIjtleHBvcnQgeyBleGlzdHMgfSBmcm9tICdAY29kZS1wdXNodXAvbW9kZWxzJztcbmV4cG9ydCB7IGNvbXBhcmVQYWlycywgbWF0Y2hBcnJheUl0ZW1zQnlLZXksIHR5cGUgRGlmZiB9IGZyb20gJy4vbGliL2RpZmYuanMnO1xuZXhwb3J0IHsgc3RyaW5naWZ5RXJyb3IgfSBmcm9tICcuL2xpYi9lcnJvcnMuanMnO1xuZXhwb3J0IHtcbiAgUHJvY2Vzc0Vycm9yLFxuICBleGVjdXRlUHJvY2VzcyxcbiAgdHlwZSBQcm9jZXNzQ29uZmlnLFxuICB0eXBlIFByb2Nlc3NPYnNlcnZlcixcbiAgdHlwZSBQcm9jZXNzUmVzdWx0LFxufSBmcm9tICcuL2xpYi9leGVjdXRlLXByb2Nlc3MuanMnO1xuZXhwb3J0IHtcbiAgY3Jhd2xGaWxlU3lzdGVtLFxuICBkaXJlY3RvcnlFeGlzdHMsXG4gIGVuc3VyZURpcmVjdG9yeUV4aXN0cyxcbiAgZmlsZUV4aXN0cyxcbiAgZmlsZVBhdGhUb0NsaUFyZyxcbiAgZmluZExpbmVOdW1iZXJJblRleHQsXG4gIGZpbmROZWFyZXN0RmlsZSxcbiAgaW1wb3J0TW9kdWxlLFxuICBsb2dNdWx0aXBsZUZpbGVSZXN1bHRzLFxuICBwbHVnaW5Xb3JrRGlyLFxuICBwcm9qZWN0VG9GaWxlbmFtZSxcbiAgcmVhZEpzb25GaWxlLFxuICByZWFkVGV4dEZpbGUsXG4gIHJlbW92ZURpcmVjdG9yeUlmRXhpc3RzLFxuICB0eXBlIENyYXdsRmlsZVN5c3RlbU9wdGlvbnMsXG4gIHR5cGUgRmlsZVJlc3VsdCxcbiAgdHlwZSBNdWx0aXBsZUZpbGVSZXN1bHRzLFxufSBmcm9tICcuL2xpYi9maWxlLXN5c3RlbS5qcyc7XG5leHBvcnQgeyBmaWx0ZXJJdGVtUmVmc0J5IH0gZnJvbSAnLi9saWIvZmlsdGVyLmpzJztcbmV4cG9ydCB7XG4gIGZvcm1hdEJ5dGVzLFxuICBmb3JtYXREdXJhdGlvbixcbiAgcGx1cmFsaXplLFxuICBwbHVyYWxpemVUb2tlbixcbiAgc2x1Z2lmeSxcbiAgdHJ1bmNhdGVEZXNjcmlwdGlvbixcbiAgdHJ1bmNhdGVJc3N1ZU1lc3NhZ2UsXG4gIHRydW5jYXRlVGV4dCxcbiAgdHJ1bmNhdGVUaXRsZSxcbn0gZnJvbSAnLi9saWIvZm9ybWF0dGluZy5qcyc7XG5leHBvcnQge1xuICBnZXRDdXJyZW50QnJhbmNoT3JUYWcsXG4gIGdldEhhc2hGcm9tVGFnLFxuICBnZXRIYXNoZXMsXG4gIGdldExhdGVzdENvbW1pdCxcbiAgZ2V0U2VtdmVyVGFncyxcbiAgdHlwZSBMb2dSZXN1bHQsXG59IGZyb20gJy4vbGliL2dpdC9naXQuY29tbWl0cy1hbmQtdGFncy5qcyc7XG5leHBvcnQge1xuICBmb3JtYXRHaXRQYXRoLFxuICBnZXRHaXRSb290LFxuICBndWFyZEFnYWluc3RMb2NhbENoYW5nZXMsXG4gIHNhZmVDaGVja291dCxcbiAgdG9HaXRQYXRoLFxufSBmcm9tICcuL2xpYi9naXQvZ2l0LmpzJztcbmV4cG9ydCB7IGdyb3VwQnlTdGF0dXMgfSBmcm9tICcuL2xpYi9ncm91cC1ieS1zdGF0dXMuanMnO1xuZXhwb3J0IHtcbiAgaXNQcm9taXNlRnVsZmlsbGVkUmVzdWx0LFxuICBpc1Byb21pc2VSZWplY3RlZFJlc3VsdCxcbiAgaGFzTm9OdWxsYWJsZVByb3BzLFxufSBmcm9tICcuL2xpYi9ndWFyZHMuanMnO1xuZXhwb3J0IHsgbG9nTXVsdGlwbGVSZXN1bHRzIH0gZnJvbSAnLi9saWIvbG9nLXJlc3VsdHMuanMnO1xuZXhwb3J0IHsgbGluaywgdWksIHR5cGUgQ2xpVWksIHR5cGUgQ29sdW1uIH0gZnJvbSAnLi9saWIvbG9nZ2luZy5qcyc7XG5leHBvcnQgeyBtZXJnZUNvbmZpZ3MgfSBmcm9tICcuL2xpYi9tZXJnZS1jb25maWdzLmpzJztcbmV4cG9ydCB7IGdldFByb2dyZXNzQmFyLCB0eXBlIFByb2dyZXNzQmFyIH0gZnJvbSAnLi9saWIvcHJvZ3Jlc3MuanMnO1xuZXhwb3J0IHtcbiAgQ09ERV9QVVNIVVBfRE9NQUlOLFxuICBDT0RFX1BVU0hVUF9VTklDT0RFX0xPR08sXG4gIEZPT1RFUl9QUkVGSVgsXG4gIFJFQURNRV9MSU5LLFxuICBURVJNSU5BTF9XSURUSCxcbn0gZnJvbSAnLi9saWIvcmVwb3J0cy9jb25zdGFudHMuanMnO1xuZXhwb3J0IHtcbiAgbGlzdEF1ZGl0c0Zyb21BbGxQbHVnaW5zLFxuICBsaXN0R3JvdXBzRnJvbUFsbFBsdWdpbnMsXG59IGZyb20gJy4vbGliL3JlcG9ydHMvZmxhdHRlbi1wbHVnaW5zLmpzJztcbmV4cG9ydCB7IGdlbmVyYXRlTWRSZXBvcnQgfSBmcm9tICcuL2xpYi9yZXBvcnRzL2dlbmVyYXRlLW1kLXJlcG9ydC5qcyc7XG5leHBvcnQge1xuICBnZW5lcmF0ZU1kUmVwb3J0c0RpZmYsXG4gIGdlbmVyYXRlTWRSZXBvcnRzRGlmZkZvck1vbm9yZXBvLFxufSBmcm9tICcuL2xpYi9yZXBvcnRzL2dlbmVyYXRlLW1kLXJlcG9ydHMtZGlmZi5qcyc7XG5leHBvcnQgeyBsb2FkUmVwb3J0IH0gZnJvbSAnLi9saWIvcmVwb3J0cy9sb2FkLXJlcG9ydC5qcyc7XG5leHBvcnQgeyBsb2dTdGRvdXRTdW1tYXJ5IH0gZnJvbSAnLi9saWIvcmVwb3J0cy9sb2ctc3Rkb3V0LXN1bW1hcnkuanMnO1xuZXhwb3J0IHsgc2NvcmVSZXBvcnQgfSBmcm9tICcuL2xpYi9yZXBvcnRzL3Njb3JpbmcuanMnO1xuZXhwb3J0IHsgc29ydFJlcG9ydCB9IGZyb20gJy4vbGliL3JlcG9ydHMvc29ydGluZy5qcyc7XG5leHBvcnQgdHlwZSB7XG4gIFNjb3JlZENhdGVnb3J5Q29uZmlnLFxuICBTY29yZWRHcm91cCxcbiAgU2NvcmVkUmVwb3J0LFxufSBmcm9tICcuL2xpYi9yZXBvcnRzL3R5cGVzLmpzJztcbmV4cG9ydCB7XG4gIGNhbGNEdXJhdGlvbixcbiAgY29tcGFyZUlzc3VlU2V2ZXJpdHksXG4gIGZvcm1hdFJlcG9ydFNjb3JlLFxufSBmcm9tICcuL2xpYi9yZXBvcnRzL3V0aWxzLmpzJztcbmV4cG9ydCB7IGlzU2VtdmVyLCBub3JtYWxpemVTZW12ZXIsIHNvcnRTZW12ZXJzIH0gZnJvbSAnLi9saWIvc2VtdmVyLmpzJztcbmV4cG9ydCAqIGZyb20gJy4vbGliL3RleHQtZm9ybWF0cy9pbmRleC5qcyc7XG5leHBvcnQge1xuICBjYXBpdGFsaXplLFxuICBjb3VudE9jY3VycmVuY2VzLFxuICBkaXN0aW5jdCxcbiAgZmFjdG9yT2YsXG4gIGZyb21Kc29uTGluZXMsXG4gIG9iamVjdEZyb21FbnRyaWVzLFxuICBvYmplY3RUb0NsaUFyZ3MsXG4gIG9iamVjdFRvRW50cmllcyxcbiAgb2JqZWN0VG9LZXlzLFxuICB0b0FycmF5LFxuICB0b0pzb25MaW5lcyxcbiAgdG9OdW1iZXJQcmVjaXNpb24sXG4gIHRvT3JkaW5hbCxcbiAgdG9Vbml4TmV3bGluZXMsXG4gIHRvVW5peFBhdGgsXG4gIHR5cGUgQ2xpQXJnc09iamVjdCxcbn0gZnJvbSAnLi9saWIvdHJhbnNmb3JtLmpzJztcbmV4cG9ydCB0eXBlIHtcbiAgRXhjbHVkZU51bGxhYmxlUHJvcHMsXG4gIEV4dHJhY3RBcnJheSxcbiAgRXh0cmFjdEFycmF5cyxcbiAgSXRlbU9yQXJyYXksXG4gIFByZXR0aWZ5LFxuICBXaXRoUmVxdWlyZWQsXG59IGZyb20gJy4vbGliL3R5cGVzLmpzJztcbmV4cG9ydCB7IHZlcmJvc2VVdGlscyB9IGZyb20gJy4vbGliL3ZlcmJvc2UtdXRpbHMuanMnO1xuZXhwb3J0IHsgem9kRXJyb3JNZXNzYWdlQnVpbGRlciB9IGZyb20gJy4vbGliL3pvZC12YWxpZGF0aW9uLmpzJztcbiIsICJjb25zdCBfX2luamVjdGVkX2ZpbGVuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3V0aWxzL3NyYy9saWIvZXhlY3V0ZS1wcm9jZXNzLnRzXCI7Y29uc3QgX19pbmplY3RlZF9kaXJuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3V0aWxzL3NyYy9saWJcIjtjb25zdCBfX2luamVjdGVkX2ltcG9ydF9tZXRhX3VybF9fID0gXCJmaWxlOi8vL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvdXRpbHMvc3JjL2xpYi9leGVjdXRlLXByb2Nlc3MudHNcIjtpbXBvcnQge1xuICB0eXBlIENoaWxkUHJvY2VzcyxcbiAgdHlwZSBDaGlsZFByb2Nlc3NCeVN0ZGlvLFxuICB0eXBlIFNwYXduT3B0aW9uc1dpdGhTdGRpb1R1cGxlLFxuICB0eXBlIFN0ZGlvUGlwZSxcbiAgc3Bhd24sXG59IGZyb20gJ25vZGU6Y2hpbGRfcHJvY2Vzcyc7XG5pbXBvcnQgdHlwZSB7IFJlYWRhYmxlLCBXcml0YWJsZSB9IGZyb20gJ25vZGU6c3RyZWFtJztcbmltcG9ydCB7IGNhbGNEdXJhdGlvbiB9IGZyb20gJy4vcmVwb3J0cy91dGlscy5qcyc7XG5cbi8qKlxuICogUmVwcmVzZW50cyB0aGUgcHJvY2VzcyByZXN1bHQuXG4gKiBAY2F0ZWdvcnkgVHlwZXNcbiAqIEBwdWJsaWNcbiAqIEBwcm9wZXJ0eSB7c3RyaW5nfSBzdGRvdXQgLSBUaGUgc3Rkb3V0IG9mIHRoZSBwcm9jZXNzLlxuICogQHByb3BlcnR5IHtzdHJpbmd9IHN0ZGVyciAtIFRoZSBzdGRlcnIgb2YgdGhlIHByb2Nlc3MuXG4gKiBAcHJvcGVydHkge251bWJlciB8IG51bGx9IGNvZGUgLSBUaGUgZXhpdCBjb2RlIG9mIHRoZSBwcm9jZXNzLlxuICovXG5leHBvcnQgdHlwZSBQcm9jZXNzUmVzdWx0ID0ge1xuICBzdGRvdXQ6IHN0cmluZztcbiAgc3RkZXJyOiBzdHJpbmc7XG4gIGNvZGU6IG51bWJlciB8IG51bGw7XG4gIGRhdGU6IHN0cmluZztcbiAgZHVyYXRpb246IG51bWJlcjtcbn07XG5cbi8qKlxuICogRXJyb3IgY2xhc3MgZm9yIHByb2Nlc3MgZXJyb3JzLlxuICogQ29udGFpbnMgYWRkaXRpb25hbCBpbmZvcm1hdGlvbiBhYm91dCB0aGUgcHJvY2VzcyByZXN1bHQuXG4gKiBAY2F0ZWdvcnkgRXJyb3JcbiAqIEBwdWJsaWNcbiAqIEBjbGFzc1xuICogQGV4dGVuZHMgRXJyb3JcbiAqIEBleGFtcGxlXG4gKiBjb25zdCByZXN1bHQgPSBhd2FpdCBleGVjdXRlUHJvY2Vzcyh7fSlcbiAqIC5jYXRjaCgoZXJyb3IpID0+IHtcbiAqICAgaWYgKGVycm9yIGluc3RhbmNlb2YgUHJvY2Vzc0Vycm9yKSB7XG4gKiAgIGNvbnNvbGUuZXJyb3IoZXJyb3IuY29kZSk7XG4gKiAgIGNvbnNvbGUuZXJyb3IoZXJyb3Iuc3RkZXJyKTtcbiAqICAgY29uc29sZS5lcnJvcihlcnJvci5zdGRvdXQpO1xuICogICB9XG4gKiB9KTtcbiAqXG4gKi9cbmV4cG9ydCBjbGFzcyBQcm9jZXNzRXJyb3IgZXh0ZW5kcyBFcnJvciB7XG4gIGNvZGU6IG51bWJlciB8IG51bGw7XG4gIHN0ZGVycjogc3RyaW5nO1xuICBzdGRvdXQ6IHN0cmluZztcblxuICBjb25zdHJ1Y3RvcihyZXN1bHQ6IFByb2Nlc3NSZXN1bHQpIHtcbiAgICBzdXBlcihyZXN1bHQuc3RkZXJyKTtcbiAgICB0aGlzLmNvZGUgPSByZXN1bHQuY29kZTtcbiAgICB0aGlzLnN0ZGVyciA9IHJlc3VsdC5zdGRlcnI7XG4gICAgdGhpcy5zdGRvdXQgPSByZXN1bHQuc3Rkb3V0O1xuICB9XG59XG5cbi8qKlxuICogUHJvY2VzcyBjb25maWcgb2JqZWN0LiBDb250YWlucyB0aGUgY29tbWFuZCwgYXJncyBhbmQgb2JzZXJ2ZXIuXG4gKiBAcGFyYW0gY2ZnIC0gcHJvY2VzcyBjb25maWcgb2JqZWN0IHdpdGggY29tbWFuZCwgYXJncyBhbmQgb2JzZXJ2ZXIgKG9wdGlvbmFsKVxuICogQGNhdGVnb3J5IFR5cGVzXG4gKiBAcHVibGljXG4gKiBAcHJvcGVydHkge3N0cmluZ30gY29tbWFuZCAtIFRoZSBjb21tYW5kIHRvIGV4ZWN1dGUuXG4gKiBAcHJvcGVydHkge3N0cmluZ1tdfSBhcmdzIC0gVGhlIGFyZ3VtZW50cyBmb3IgdGhlIGNvbW1hbmQuXG4gKiBAcHJvcGVydHkge1Byb2Nlc3NPYnNlcnZlcn0gb2JzZXJ2ZXIgLSBUaGUgb2JzZXJ2ZXIgZm9yIHRoZSBwcm9jZXNzLlxuICpcbiAqIEBleGFtcGxlXG4gKlxuICogLy8gYmFzaCBjb21tYW5kXG4gKiBjb25zdCBjZmcgPSB7XG4gKiAgIGNvbW1hbmQ6ICdiYXNoJyxcbiAqICAgYXJnczogWyctYycsICdlY2hvIFwiaGVsbG8gd29ybGRcIiddXG4gKiB9O1xuICpcbiAqIC8vIG5vZGUgY29tbWFuZFxuICogY29uc3QgY2ZnID0ge1xuICogY29tbWFuZDogJ25vZGUnLFxuICogYXJnczogWyctLXZlcnNpb24nXVxuICogfTtcbiAqXG4gKiAvLyBucHggY29tbWFuZFxuICogY29uc3QgY2ZnID0ge1xuICogY29tbWFuZDogJ25weCcsXG4gKiBhcmdzOiBbJy0tdmVyc2lvbiddXG4gKlxuICovXG5leHBvcnQgdHlwZSBQcm9jZXNzQ29uZmlnID0gT21pdDxcbiAgU3Bhd25PcHRpb25zV2l0aFN0ZGlvVHVwbGU8U3RkaW9QaXBlLCBTdGRpb1BpcGUsIFN0ZGlvUGlwZT4sXG4gICdzdGRpbydcbj4gJiB7XG4gIGNvbW1hbmQ6IHN0cmluZztcbiAgYXJncz86IHN0cmluZ1tdO1xuICBvYnNlcnZlcj86IFByb2Nlc3NPYnNlcnZlcjtcbiAgaWdub3JlRXhpdENvZGU/OiBib29sZWFuO1xufTtcblxuLyoqXG4gKiBQcm9jZXNzIG9ic2VydmVyIG9iamVjdC4gQ29udGFpbnMgdGhlIG9uU3Rkb3V0LCBlcnJvciBhbmQgY29tcGxldGUgZnVuY3Rpb24uXG4gKiBAY2F0ZWdvcnkgVHlwZXNcbiAqIEBwdWJsaWNcbiAqIEBwcm9wZXJ0eSB7ZnVuY3Rpb259IG9uU3Rkb3V0IC0gVGhlIG9uU3Rkb3V0IGZ1bmN0aW9uIG9mIHRoZSBvYnNlcnZlciAob3B0aW9uYWwpLlxuICogQHByb3BlcnR5IHtmdW5jdGlvbn0gb25FcnJvciAtIFRoZSBlcnJvciBmdW5jdGlvbiBvZiB0aGUgb2JzZXJ2ZXIgKG9wdGlvbmFsKS5cbiAqIEBwcm9wZXJ0eSB7ZnVuY3Rpb259IG9uQ29tcGxldGUgLSBUaGUgY29tcGxldGUgZnVuY3Rpb24gb2YgdGhlIG9ic2VydmVyIChvcHRpb25hbCkuXG4gKlxuICogQGV4YW1wbGVcbiAqIGNvbnN0IG9ic2VydmVyID0ge1xuICogIG9uU3Rkb3V0OiAoc3Rkb3V0KSA9PiBjb25zb2xlLmluZm8oc3Rkb3V0KVxuICogIH1cbiAqL1xuZXhwb3J0IHR5cGUgUHJvY2Vzc09ic2VydmVyID0ge1xuICBvblN0ZG91dD86IChzdGRvdXQ6IHN0cmluZywgc291cmNlUHJvY2Vzcz86IENoaWxkUHJvY2VzcykgPT4gdm9pZDtcbiAgb25TdGRlcnI/OiAoc3RkZXJyOiBzdHJpbmcsIHNvdXJjZVByb2Nlc3M/OiBDaGlsZFByb2Nlc3MpID0+IHZvaWQ7XG4gIG9uRXJyb3I/OiAoZXJyb3I6IFByb2Nlc3NFcnJvcikgPT4gdm9pZDtcbiAgb25Db21wbGV0ZT86ICgpID0+IHZvaWQ7XG59O1xuXG4vKipcbiAqIEV4ZWN1dGVzIGEgcHJvY2VzcyBhbmQgcmV0dXJucyBhIHByb21pc2Ugd2l0aCB0aGUgcmVzdWx0IGFzIGBQcm9jZXNzUmVzdWx0YC5cbiAqXG4gKiBAZXhhbXBsZVxuICpcbiAqIC8vIHN5bmMgcHJvY2VzcyBleGVjdXRpb25cbiAqIGNvbnN0IHJlc3VsdCA9IGF3YWl0IGV4ZWN1dGVQcm9jZXNzKHtcbiAqICBjb21tYW5kOiAnbm9kZScsXG4gKiAgYXJnczogWyctLXZlcnNpb24nXVxuICogfSk7XG4gKlxuICogY29uc29sZS5pbmZvKHJlc3VsdCk7XG4gKlxuICogLy8gYXN5bmMgcHJvY2VzcyBleGVjdXRpb25cbiAqIGNvbnN0IHJlc3VsdCA9IGF3YWl0IGV4ZWN1dGVQcm9jZXNzKHtcbiAqICAgIGNvbW1hbmQ6ICdub2RlJyxcbiAqICAgIGFyZ3M6IFsnZG93bmxvYWQtZGF0YS5qcyddLFxuICogICAgb2JzZXJ2ZXI6IHtcbiAqICAgICAgb25TdGRvdXQ6IHVwZGF0ZVByb2dyZXNzLFxuICogICAgICBlcnJvcjogaGFuZGxlRXJyb3IsXG4gKiAgICAgIGNvbXBsZXRlOiBjbGVhbkxvZ3MsXG4gKiAgICB9XG4gKiB9KTtcbiAqXG4gKiBjb25zb2xlLmluZm8ocmVzdWx0KTtcbiAqXG4gKiBAcGFyYW0gY2ZnIC0gc2VlIHtAbGluayBQcm9jZXNzQ29uZmlnfVxuICovXG5leHBvcnQgZnVuY3Rpb24gZXhlY3V0ZVByb2Nlc3MoY2ZnOiBQcm9jZXNzQ29uZmlnKTogUHJvbWlzZTxQcm9jZXNzUmVzdWx0PiB7XG4gIGNvbnN0IHsgY29tbWFuZCwgYXJncywgb2JzZXJ2ZXIsIGlnbm9yZUV4aXRDb2RlID0gZmFsc2UsIC4uLm9wdGlvbnMgfSA9IGNmZztcbiAgY29uc3QgeyBvblN0ZG91dCwgb25TdGRlcnIsIG9uRXJyb3IsIG9uQ29tcGxldGUgfSA9IG9ic2VydmVyID8/IHt9O1xuICBjb25zdCBkYXRlID0gbmV3IERhdGUoKS50b0lTT1N0cmluZygpO1xuICBjb25zdCBzdGFydCA9IHBlcmZvcm1hbmNlLm5vdygpO1xuXG4gIHJldHVybiBuZXcgUHJvbWlzZSgocmVzb2x2ZSwgcmVqZWN0KSA9PiB7XG4gICAgLy8gc2hlbGw6dHJ1ZSB0ZWxscyBXaW5kb3dzIHRvIHVzZSBzaGVsbCBjb21tYW5kIGZvciBzcGF3bmluZyBhIGNoaWxkIHByb2Nlc3NcbiAgICBjb25zdCBzcGF3bmVkUHJvY2VzcyA9IHNwYXduKGNvbW1hbmQsIGFyZ3MgPz8gW10sIHtcbiAgICAgIHNoZWxsOiB0cnVlLFxuICAgICAgd2luZG93c0hpZGU6IHRydWUsXG4gICAgICAuLi5vcHRpb25zLFxuICAgIH0pIGFzIENoaWxkUHJvY2Vzc0J5U3RkaW88V3JpdGFibGUsIFJlYWRhYmxlLCBSZWFkYWJsZT47XG5cbiAgICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgZnVuY3Rpb25hbC9uby1sZXRcbiAgICBsZXQgc3Rkb3V0ID0gJyc7XG4gICAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIGZ1bmN0aW9uYWwvbm8tbGV0XG4gICAgbGV0IHN0ZGVyciA9ICcnO1xuXG4gICAgc3Bhd25lZFByb2Nlc3Muc3Rkb3V0Lm9uKCdkYXRhJywgZGF0YSA9PiB7XG4gICAgICBzdGRvdXQgKz0gU3RyaW5nKGRhdGEpO1xuICAgICAgb25TdGRvdXQ/LihTdHJpbmcoZGF0YSksIHNwYXduZWRQcm9jZXNzKTtcbiAgICB9KTtcblxuICAgIHNwYXduZWRQcm9jZXNzLnN0ZGVyci5vbignZGF0YScsIGRhdGEgPT4ge1xuICAgICAgc3RkZXJyICs9IFN0cmluZyhkYXRhKTtcbiAgICAgIG9uU3RkZXJyPy4oU3RyaW5nKGRhdGEpLCBzcGF3bmVkUHJvY2Vzcyk7XG4gICAgfSk7XG5cbiAgICBzcGF3bmVkUHJvY2Vzcy5vbignZXJyb3InLCBlcnIgPT4ge1xuICAgICAgc3RkZXJyICs9IGVyci50b1N0cmluZygpO1xuICAgIH0pO1xuXG4gICAgc3Bhd25lZFByb2Nlc3Mub24oJ2Nsb3NlJywgY29kZSA9PiB7XG4gICAgICBjb25zdCB0aW1pbmdzID0geyBkYXRlLCBkdXJhdGlvbjogY2FsY0R1cmF0aW9uKHN0YXJ0KSB9O1xuICAgICAgaWYgKGNvZGUgPT09IDAgfHwgaWdub3JlRXhpdENvZGUpIHtcbiAgICAgICAgb25Db21wbGV0ZT8uKCk7XG4gICAgICAgIHJlc29sdmUoeyBjb2RlLCBzdGRvdXQsIHN0ZGVyciwgLi4udGltaW5ncyB9KTtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIGNvbnN0IGVycm9yTXNnID0gbmV3IFByb2Nlc3NFcnJvcih7IGNvZGUsIHN0ZG91dCwgc3RkZXJyLCAuLi50aW1pbmdzIH0pO1xuICAgICAgICBvbkVycm9yPy4oZXJyb3JNc2cpO1xuICAgICAgICByZWplY3QoZXJyb3JNc2cpO1xuICAgICAgfVxuICAgIH0pO1xuICB9KTtcbn1cbiIsICJjb25zdCBfX2luamVjdGVkX2ZpbGVuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3V0aWxzL3NyYy9saWIvcmVwb3J0cy91dGlscy50c1wiO2NvbnN0IF9faW5qZWN0ZWRfZGlybmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy91dGlscy9zcmMvbGliL3JlcG9ydHNcIjtjb25zdCBfX2luamVjdGVkX2ltcG9ydF9tZXRhX3VybF9fID0gXCJmaWxlOi8vL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvdXRpbHMvc3JjL2xpYi9yZXBvcnRzL3V0aWxzLnRzXCI7aW1wb3J0IGFuc2lzLCB7IHR5cGUgQW5zaXMgfSBmcm9tICdhbnNpcyc7XG5pbXBvcnQgeyB0eXBlIElubGluZVRleHQsIG1kIH0gZnJvbSAnYnVpbGQtbWQnO1xuaW1wb3J0IHR5cGUge1xuICBBdWRpdERpZmYsXG4gIEF1ZGl0UmVwb3J0LFxuICBDYXRlZ29yeVJlZixcbiAgSXNzdWVTZXZlcml0eSBhcyBDbGlJc3N1ZVNldmVyaXR5LFxuICBHcm91cCxcbiAgSXNzdWUsXG59IGZyb20gJ0Bjb2RlLXB1c2h1cC9tb2RlbHMnO1xuaW1wb3J0IHsgU0NPUkVfQ09MT1JfUkFOR0UgfSBmcm9tICcuL2NvbnN0YW50cy5qcyc7XG5pbXBvcnQgdHlwZSB7XG4gIFNjb3JlZFJlcG9ydCxcbiAgU29ydGFibGVBdWRpdFJlcG9ydCxcbiAgU29ydGFibGVHcm91cCxcbn0gZnJvbSAnLi90eXBlcy5qcyc7XG5cbmV4cG9ydCBmdW5jdGlvbiBmb3JtYXRSZXBvcnRTY29yZShzY29yZTogbnVtYmVyKTogc3RyaW5nIHtcbiAgY29uc3Qgc2NhbGVkU2NvcmUgPSBzY29yZSAqIDEwMDtcbiAgY29uc3Qgcm91bmRlZFNjb3JlID0gTWF0aC5yb3VuZChzY2FsZWRTY29yZSk7XG5cbiAgcmV0dXJuIHJvdW5kZWRTY29yZSA9PT0gMTAwICYmIHNjb3JlICE9PSAxXG4gICAgPyBNYXRoLmZsb29yKHNjYWxlZFNjb3JlKS50b1N0cmluZygpXG4gICAgOiByb3VuZGVkU2NvcmUudG9TdHJpbmcoKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGZvcm1hdFNjb3JlV2l0aENvbG9yKFxuICBzY29yZTogbnVtYmVyLFxuICBvcHRpb25zPzogeyBza2lwQm9sZD86IGJvb2xlYW4gfSxcbik6IElubGluZVRleHQge1xuICBjb25zdCBzdHlsZWROdW1iZXIgPSBvcHRpb25zPy5za2lwQm9sZFxuICAgID8gZm9ybWF0UmVwb3J0U2NvcmUoc2NvcmUpXG4gICAgOiBtZC5ib2xkKGZvcm1hdFJlcG9ydFNjb3JlKHNjb3JlKSk7XG4gIHJldHVybiBtZGAke3Njb3JlTWFya2VyKHNjb3JlKX0gJHtzdHlsZWROdW1iZXJ9YDtcbn1cblxuZXhwb3J0IHR5cGUgTWFya2VyU2hhcGUgPSAnY2lyY2xlJyB8ICdzcXVhcmUnO1xuZXhwb3J0IHR5cGUgU2NvcmVDb2xvcnMgPSAncmVkJyB8ICd5ZWxsb3cnIHwgJ2dyZWVuJztcbmV4cG9ydCBjb25zdCBNQVJLRVJTOiBSZWNvcmQ8TWFya2VyU2hhcGUsIFJlY29yZDxTY29yZUNvbG9ycywgc3RyaW5nPj4gPSB7XG4gIGNpcmNsZToge1xuICAgIHJlZDogJ1x1RDgzRFx1REQzNCcsXG4gICAgeWVsbG93OiAnXHVEODNEXHVERkUxJyxcbiAgICBncmVlbjogJ1x1RDgzRFx1REZFMicsXG4gIH0sXG4gIHNxdWFyZToge1xuICAgIHJlZDogJ1x1RDgzRFx1REZFNScsXG4gICAgeWVsbG93OiAnXHVEODNEXHVERkU4JyxcbiAgICBncmVlbjogJ1x1RDgzRFx1REZFOScsXG4gIH0sXG59O1xuXG5leHBvcnQgZnVuY3Rpb24gc2NvcmVNYXJrZXIoXG4gIHNjb3JlOiBudW1iZXIsXG4gIG1hcmtlclR5cGU6IE1hcmtlclNoYXBlID0gJ2NpcmNsZScsXG4pOiBzdHJpbmcge1xuICBpZiAoc2NvcmUgPj0gU0NPUkVfQ09MT1JfUkFOR0UuR1JFRU5fTUlOKSB7XG4gICAgcmV0dXJuIE1BUktFUlNbbWFya2VyVHlwZV0uZ3JlZW47XG4gIH1cbiAgaWYgKHNjb3JlID49IFNDT1JFX0NPTE9SX1JBTkdFLllFTExPV19NSU4pIHtcbiAgICByZXR1cm4gTUFSS0VSU1ttYXJrZXJUeXBlXS55ZWxsb3c7XG4gIH1cbiAgcmV0dXJuIE1BUktFUlNbbWFya2VyVHlwZV0ucmVkO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gZ2V0RGlmZk1hcmtlcihkaWZmOiBudW1iZXIpOiBzdHJpbmcge1xuICBpZiAoZGlmZiA+IDApIHtcbiAgICByZXR1cm4gJ1x1MjE5MSc7XG4gIH1cbiAgaWYgKGRpZmYgPCAwKSB7XG4gICAgcmV0dXJuICdcdTIxOTMnO1xuICB9XG4gIHJldHVybiAnJztcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGNvbG9yQnlTY29yZURpZmYodGV4dDogc3RyaW5nLCBkaWZmOiBudW1iZXIpOiBJbmxpbmVUZXh0IHtcbiAgY29uc3QgY29sb3IgPSBkaWZmID4gMCA/ICdncmVlbicgOiBkaWZmIDwgMCA/ICdyZWQnIDogJ2dyYXknO1xuICByZXR1cm4gc2hpZWxkc0JhZGdlKHRleHQsIGNvbG9yKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHNoaWVsZHNCYWRnZSh0ZXh0OiBzdHJpbmcsIGNvbG9yOiBzdHJpbmcpOiBJbmxpbmVUZXh0IHtcbiAgcmV0dXJuIG1kLmltYWdlKFxuICAgIGBodHRwczovL2ltZy5zaGllbGRzLmlvL2JhZGdlLyR7ZW5jb2RlVVJJQ29tcG9uZW50KHRleHQpfS0ke2NvbG9yfWAsXG4gICAgdGV4dCxcbiAgKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGZvcm1hdERpZmZOdW1iZXIoZGlmZjogbnVtYmVyKTogc3RyaW5nIHtcbiAgY29uc3QgbnVtYmVyID1cbiAgICBNYXRoLmFicyhkaWZmKSA9PT0gTnVtYmVyLlBPU0lUSVZFX0lORklOSVRZID8gJ1x1MjIxRScgOiBgJHtNYXRoLmFicyhkaWZmKX1gO1xuICBjb25zdCBzaWduID0gZGlmZiA8IDAgPyAnXHUyMjEyJyA6ICcrJztcbiAgcmV0dXJuIGAke3NpZ259JHtudW1iZXJ9YDtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHNldmVyaXR5TWFya2VyKHNldmVyaXR5OiAnaW5mbycgfCAnd2FybmluZycgfCAnZXJyb3InKTogc3RyaW5nIHtcbiAgaWYgKHNldmVyaXR5ID09PSAnZXJyb3InKSB7XG4gICAgcmV0dXJuICdcdUQ4M0RcdURFQTgnO1xuICB9XG4gIGlmIChzZXZlcml0eSA9PT0gJ3dhcm5pbmcnKSB7XG4gICAgcmV0dXJuICdcdTI2QTBcdUZFMEYnO1xuICB9XG4gIHJldHVybiAnXHUyMTM5XHVGRTBGJztcbn1cblxuY29uc3QgTUlOX05PTl9aRVJPX1JFU1VMVCA9IDAuMTtcblxuZXhwb3J0IGZ1bmN0aW9uIHJvdW5kVmFsdWUodmFsdWU6IG51bWJlcik6IG51bWJlciB7XG4gIGNvbnN0IHJvdW5kZWRWYWx1ZSA9IE1hdGgucm91bmQodmFsdWUgKiAxMCkgLyAxMDsgLy8gcm91bmQgd2l0aCBtYXggMSBkZWNpbWFsXG4gIGlmIChyb3VuZGVkVmFsdWUgPT09IDAgJiYgdmFsdWUgIT09IDApIHtcbiAgICByZXR1cm4gTUlOX05PTl9aRVJPX1JFU1VMVCAqIE1hdGguc2lnbih2YWx1ZSk7XG4gIH1cbiAgcmV0dXJuIHJvdW5kZWRWYWx1ZTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGZvcm1hdFNjb3JlQ2hhbmdlKGRpZmY6IG51bWJlcik6IElubGluZVRleHQge1xuICBjb25zdCBtYXJrZXIgPSBnZXREaWZmTWFya2VyKGRpZmYpO1xuICBjb25zdCB0ZXh0ID0gZm9ybWF0RGlmZk51bWJlcihyb3VuZFZhbHVlKGRpZmYgKiAxMDApKTtcbiAgcmV0dXJuIGNvbG9yQnlTY29yZURpZmYoYCR7bWFya2VyfSAke3RleHR9YCwgZGlmZik7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBmb3JtYXRWYWx1ZUNoYW5nZSh7XG4gIHZhbHVlcyxcbiAgc2NvcmVzLFxufTogUGljazxBdWRpdERpZmYsICd2YWx1ZXMnIHwgJ3Njb3Jlcyc+KTogSW5saW5lVGV4dCB7XG4gIGNvbnN0IG1hcmtlciA9IGdldERpZmZNYXJrZXIodmFsdWVzLmRpZmYpO1xuICBjb25zdCBwZXJjZW50YWdlID1cbiAgICB2YWx1ZXMuYmVmb3JlID09PSAwXG4gICAgICA/IHZhbHVlcy5kaWZmID4gMFxuICAgICAgICA/IE51bWJlci5QT1NJVElWRV9JTkZJTklUWVxuICAgICAgICA6IE51bWJlci5ORUdBVElWRV9JTkZJTklUWVxuICAgICAgOiByb3VuZFZhbHVlKCh2YWx1ZXMuZGlmZiAvIHZhbHVlcy5iZWZvcmUpICogMTAwKTtcbiAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIG5vLWlycmVndWxhci13aGl0ZXNwYWNlXG4gIGNvbnN0IHRleHQgPSBgJHtmb3JtYXREaWZmTnVtYmVyKHBlcmNlbnRhZ2UpfVx1MjAwOSVgO1xuICByZXR1cm4gY29sb3JCeVNjb3JlRGlmZihgJHttYXJrZXJ9ICR7dGV4dH1gLCBzY29yZXMuZGlmZik7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBjYWxjRHVyYXRpb24oc3RhcnQ6IG51bWJlciwgc3RvcD86IG51bWJlcik6IG51bWJlciB7XG4gIHJldHVybiBNYXRoLnJvdW5kKChzdG9wID8/IHBlcmZvcm1hbmNlLm5vdygpKSAtIHN0YXJ0KTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGNvdW50V2VpZ2h0ZWRSZWZzKHJlZnM6IENhdGVnb3J5UmVmW10pIHtcbiAgcmV0dXJuIHJlZnNcbiAgICAuZmlsdGVyKCh7IHdlaWdodCB9KSA9PiB3ZWlnaHQgPiAwKVxuICAgIC5yZWR1Y2UoKHN1bSwgeyB3ZWlnaHQgfSkgPT4gc3VtICsgd2VpZ2h0LCAwKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGNvdW50Q2F0ZWdvcnlBdWRpdHMoXG4gIHJlZnM6IENhdGVnb3J5UmVmW10sXG4gIHBsdWdpbnM6IFNjb3JlZFJlcG9ydFsncGx1Z2lucyddLFxuKTogbnVtYmVyIHtcbiAgLy8gQ3JlYXRlIGxvb2t1cCBvYmplY3QgZm9yIGdyb3VwcyB3aXRoaW4gZWFjaCBwbHVnaW5cbiAgY29uc3QgZ3JvdXBMb29rdXAgPSBwbHVnaW5zLnJlZHVjZTxSZWNvcmQ8c3RyaW5nLCBSZWNvcmQ8c3RyaW5nLCBHcm91cD4+PihcbiAgICAobG9va3VwLCBwbHVnaW4pID0+IHtcbiAgICAgIGlmIChwbHVnaW4uZ3JvdXBzID09IG51bGwgfHwgcGx1Z2luLmdyb3Vwcy5sZW5ndGggPT09IDApIHtcbiAgICAgICAgcmV0dXJuIGxvb2t1cDtcbiAgICAgIH1cblxuICAgICAgcmV0dXJuIHtcbiAgICAgICAgLi4ubG9va3VwLFxuICAgICAgICBbcGx1Z2luLnNsdWddOiBPYmplY3QuZnJvbUVudHJpZXMoXG4gICAgICAgICAgcGx1Z2luLmdyb3Vwcy5tYXAoZ3JvdXAgPT4gW2dyb3VwLnNsdWcsIGdyb3VwXSksXG4gICAgICAgICksXG4gICAgICB9O1xuICAgIH0sXG4gICAge30sXG4gICk7XG5cbiAgLy8gQ291bnQgYXVkaXRzXG4gIHJldHVybiByZWZzLnJlZHVjZSgoYWNjLCByZWYpID0+IHtcbiAgICBpZiAocmVmLnR5cGUgPT09ICdncm91cCcpIHtcbiAgICAgIGNvbnN0IGdyb3VwUmVmcyA9IGdyb3VwTG9va3VwW3JlZi5wbHVnaW5dPy5bcmVmLnNsdWddPy5yZWZzO1xuICAgICAgcmV0dXJuIGFjYyArIChncm91cFJlZnM/Lmxlbmd0aCA/PyAwKTtcbiAgICB9XG4gICAgcmV0dXJuIGFjYyArIDE7XG4gIH0sIDApO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gY29tcGFyZUNhdGVnb3J5QXVkaXRzQW5kR3JvdXBzKFxuICBhOiBTb3J0YWJsZUF1ZGl0UmVwb3J0IHwgU29ydGFibGVHcm91cCxcbiAgYjogU29ydGFibGVBdWRpdFJlcG9ydCB8IFNvcnRhYmxlR3JvdXAsXG4pOiBudW1iZXIge1xuICBpZiAoYS5zY29yZSAhPT0gYi5zY29yZSkge1xuICAgIHJldHVybiBhLnNjb3JlIC0gYi5zY29yZTtcbiAgfVxuXG4gIGlmIChhLndlaWdodCAhPT0gYi53ZWlnaHQpIHtcbiAgICByZXR1cm4gYi53ZWlnaHQgLSBhLndlaWdodDtcbiAgfVxuXG4gIGlmICgndmFsdWUnIGluIGEgJiYgJ3ZhbHVlJyBpbiBiICYmIGEudmFsdWUgIT09IGIudmFsdWUpIHtcbiAgICByZXR1cm4gYi52YWx1ZSAtIGEudmFsdWU7XG4gIH1cblxuICByZXR1cm4gYS50aXRsZS5sb2NhbGVDb21wYXJlKGIudGl0bGUpO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gY29tcGFyZUF1ZGl0cyhhOiBBdWRpdFJlcG9ydCwgYjogQXVkaXRSZXBvcnQpOiBudW1iZXIge1xuICBpZiAoYS5zY29yZSAhPT0gYi5zY29yZSkge1xuICAgIHJldHVybiBhLnNjb3JlIC0gYi5zY29yZTtcbiAgfVxuXG4gIGlmIChhLnZhbHVlICE9PSBiLnZhbHVlKSB7XG4gICAgcmV0dXJuIGIudmFsdWUgLSBhLnZhbHVlO1xuICB9XG5cbiAgcmV0dXJuIGEudGl0bGUubG9jYWxlQ29tcGFyZShiLnRpdGxlKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGNvbXBhcmVJc3N1ZVNldmVyaXR5KFxuICBzZXZlcml0eTE6IENsaUlzc3VlU2V2ZXJpdHksXG4gIHNldmVyaXR5MjogQ2xpSXNzdWVTZXZlcml0eSxcbik6IG51bWJlciB7XG4gIGNvbnN0IGxldmVsczogUmVjb3JkPENsaUlzc3VlU2V2ZXJpdHksIG51bWJlcj4gPSB7XG4gICAgaW5mbzogMCxcbiAgICB3YXJuaW5nOiAxLFxuICAgIGVycm9yOiAyLFxuICB9O1xuICByZXR1cm4gbGV2ZWxzW3NldmVyaXR5MV0gLSBsZXZlbHNbc2V2ZXJpdHkyXTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHRocm93SXNOb3RQcmVzZW50RXJyb3IoXG4gIGl0ZW1OYW1lOiBzdHJpbmcsXG4gIHByZXNlbnRQbGFjZTogc3RyaW5nLFxuKTogbmV2ZXIge1xuICB0aHJvdyBuZXcgRXJyb3IoYCR7aXRlbU5hbWV9IGlzIG5vdCBwcmVzZW50IGluICR7cHJlc2VudFBsYWNlfWApO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gZ2V0UGx1Z2luTmFtZUZyb21TbHVnKFxuICBzbHVnOiBzdHJpbmcsXG4gIHBsdWdpbnM6IFNjb3JlZFJlcG9ydFsncGx1Z2lucyddLFxuKTogc3RyaW5nIHtcbiAgcmV0dXJuIChcbiAgICBwbHVnaW5zLmZpbmQoKHsgc2x1ZzogcGx1Z2luU2x1ZyB9KSA9PiBwbHVnaW5TbHVnID09PSBzbHVnKT8udGl0bGUgfHwgc2x1Z1xuICApO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gY29tcGFyZUlzc3VlcyhhOiBJc3N1ZSwgYjogSXNzdWUpOiBudW1iZXIge1xuICBpZiAoYS5zZXZlcml0eSAhPT0gYi5zZXZlcml0eSkge1xuICAgIHJldHVybiAtY29tcGFyZUlzc3VlU2V2ZXJpdHkoYS5zZXZlcml0eSwgYi5zZXZlcml0eSk7XG4gIH1cbiAgaWYgKCFhLnNvdXJjZSAmJiBiLnNvdXJjZSkge1xuICAgIHJldHVybiAtMTtcbiAgfVxuICBpZiAoYS5zb3VyY2UgJiYgIWIuc291cmNlKSB7XG4gICAgcmV0dXJuIDE7XG4gIH1cbiAgaWYgKGEuc291cmNlPy5maWxlICE9PSBiLnNvdXJjZT8uZmlsZSkge1xuICAgIHJldHVybiBhLnNvdXJjZT8uZmlsZS5sb2NhbGVDb21wYXJlKGIuc291cmNlPy5maWxlIHx8ICcnKSA/PyAwO1xuICB9XG4gIHJldHVybiBjb21wYXJlU291cmNlRmlsZVBvc2l0aW9uKGEuc291cmNlPy5wb3NpdGlvbiwgYi5zb3VyY2U/LnBvc2l0aW9uKTtcbn1cblxuZnVuY3Rpb24gY29tcGFyZVNvdXJjZUZpbGVQb3NpdGlvbihcbiAgYTogTm9uTnVsbGFibGU8SXNzdWVbJ3NvdXJjZSddPlsncG9zaXRpb24nXSxcbiAgYjogTm9uTnVsbGFibGU8SXNzdWVbJ3NvdXJjZSddPlsncG9zaXRpb24nXSxcbik6IG51bWJlciB7XG4gIGlmICghYSAmJiBiKSB7XG4gICAgcmV0dXJuIC0xO1xuICB9XG4gIGlmIChhICYmICFiKSB7XG4gICAgcmV0dXJuIDE7XG4gIH1cbiAgaWYgKGE/LnN0YXJ0TGluZSAhPT0gYj8uc3RhcnRMaW5lKSB7XG4gICAgcmV0dXJuIChhPy5zdGFydExpbmUgPz8gMCkgLSAoYj8uc3RhcnRMaW5lID8/IDApO1xuICB9XG4gIHJldHVybiAwO1xufVxuXG4vLyBAVE9ETyByZXRoaW5rIGltcGxlbWVudGF0aW9uXG5leHBvcnQgZnVuY3Rpb24gYXBwbHlTY29yZUNvbG9yKFxuICB7IHNjb3JlLCB0ZXh0IH06IHsgc2NvcmU6IG51bWJlcjsgdGV4dD86IHN0cmluZyB9LFxuICBzdHlsZTogQW5zaXMgPSBhbnNpcyxcbikge1xuICBjb25zdCBmb3JtYXR0ZWRTY29yZSA9IHRleHQgPz8gZm9ybWF0UmVwb3J0U2NvcmUoc2NvcmUpO1xuXG4gIGlmIChzY29yZSA+PSBTQ09SRV9DT0xPUl9SQU5HRS5HUkVFTl9NSU4pIHtcbiAgICByZXR1cm4gdGV4dFxuICAgICAgPyBzdHlsZS5ncmVlbihmb3JtYXR0ZWRTY29yZSlcbiAgICAgIDogc3R5bGUuYm9sZChzdHlsZS5ncmVlbihmb3JtYXR0ZWRTY29yZSkpO1xuICB9XG5cbiAgaWYgKHNjb3JlID49IFNDT1JFX0NPTE9SX1JBTkdFLllFTExPV19NSU4pIHtcbiAgICByZXR1cm4gdGV4dFxuICAgICAgPyBzdHlsZS55ZWxsb3coZm9ybWF0dGVkU2NvcmUpXG4gICAgICA6IHN0eWxlLmJvbGQoc3R5bGUueWVsbG93KGZvcm1hdHRlZFNjb3JlKSk7XG4gIH1cblxuICByZXR1cm4gdGV4dFxuICAgID8gc3R5bGUucmVkKGZvcm1hdHRlZFNjb3JlKVxuICAgIDogc3R5bGUuYm9sZChzdHlsZS5yZWQoZm9ybWF0dGVkU2NvcmUpKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHRhcmdldFNjb3JlSWNvbihcbiAgc2NvcmU6IG51bWJlcixcbiAgdGFyZ2V0U2NvcmU/OiBudW1iZXIsXG4gIG9wdGlvbnM6IHtcbiAgICBwYXNzSWNvbj86IHN0cmluZztcbiAgICBmYWlsSWNvbj86IHN0cmluZztcbiAgICBwcmVmaXg/OiBzdHJpbmc7XG4gICAgcG9zdGZpeD86IHN0cmluZztcbiAgfSA9IHt9LFxuKTogc3RyaW5nIHtcbiAgaWYgKHRhcmdldFNjb3JlICE9IG51bGwpIHtcbiAgICBjb25zdCB7XG4gICAgICBwYXNzSWNvbiA9ICdcdTI3MDUnLFxuICAgICAgZmFpbEljb24gPSAnXHUyNzRDJyxcbiAgICAgIHByZWZpeCA9ICcnLFxuICAgICAgcG9zdGZpeCA9ICcnLFxuICAgIH0gPSBvcHRpb25zO1xuICAgIGlmIChzY29yZSA+PSB0YXJnZXRTY29yZSkge1xuICAgICAgcmV0dXJuIGAke3ByZWZpeH0ke3Bhc3NJY29ufSR7cG9zdGZpeH1gO1xuICAgIH1cbiAgICByZXR1cm4gYCR7cHJlZml4fSR7ZmFpbEljb259JHtwb3N0Zml4fWA7XG4gIH1cbiAgcmV0dXJuICcnO1xufVxuIiwgImNvbnN0IF9faW5qZWN0ZWRfZmlsZW5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvdXRpbHMvc3JjL2xpYi9maWxlLXN5c3RlbS50c1wiO2NvbnN0IF9faW5qZWN0ZWRfZGlybmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy91dGlscy9zcmMvbGliXCI7Y29uc3QgX19pbmplY3RlZF9pbXBvcnRfbWV0YV91cmxfXyA9IFwiZmlsZTovLy9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3V0aWxzL3NyYy9saWIvZmlsZS1zeXN0ZW0udHNcIjtpbXBvcnQgeyBib2xkLCBncmF5IH0gZnJvbSAnYW5zaXMnO1xuaW1wb3J0IHsgdHlwZSBPcHRpb25zLCBidW5kbGVSZXF1aXJlIH0gZnJvbSAnYnVuZGxlLXJlcXVpcmUnO1xuaW1wb3J0IHsgbWtkaXIsIHJlYWRGaWxlLCByZWFkZGlyLCBybSwgc3RhdCB9IGZyb20gJ25vZGU6ZnMvcHJvbWlzZXMnO1xuaW1wb3J0IHBhdGggZnJvbSAnbm9kZTpwYXRoJztcbmltcG9ydCB7IGZvcm1hdEJ5dGVzIH0gZnJvbSAnLi9mb3JtYXR0aW5nLmpzJztcbmltcG9ydCB7IGxvZ011bHRpcGxlUmVzdWx0cyB9IGZyb20gJy4vbG9nLXJlc3VsdHMuanMnO1xuaW1wb3J0IHsgdWkgfSBmcm9tICcuL2xvZ2dpbmcuanMnO1xuXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gcmVhZFRleHRGaWxlKGZpbGVQYXRoOiBzdHJpbmcpOiBQcm9taXNlPHN0cmluZz4ge1xuICBjb25zdCBidWZmZXIgPSBhd2FpdCByZWFkRmlsZShmaWxlUGF0aCk7XG4gIHJldHVybiBidWZmZXIudG9TdHJpbmcoKTtcbn1cblxuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIHJlYWRKc29uRmlsZTxUID0gdW5rbm93bj4oZmlsZVBhdGg6IHN0cmluZyk6IFByb21pc2U8VD4ge1xuICBjb25zdCB0ZXh0ID0gYXdhaXQgcmVhZFRleHRGaWxlKGZpbGVQYXRoKTtcbiAgcmV0dXJuIEpTT04ucGFyc2UodGV4dCkgYXMgVDtcbn1cblxuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGZpbGVFeGlzdHMoZmlsZVBhdGg6IHN0cmluZyk6IFByb21pc2U8Ym9vbGVhbj4ge1xuICB0cnkge1xuICAgIGNvbnN0IHN0YXRzID0gYXdhaXQgc3RhdChmaWxlUGF0aCk7XG4gICAgcmV0dXJuIHN0YXRzLmlzRmlsZSgpO1xuICB9IGNhdGNoIHtcbiAgICByZXR1cm4gZmFsc2U7XG4gIH1cbn1cblxuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGRpcmVjdG9yeUV4aXN0cyhmaWxlUGF0aDogc3RyaW5nKTogUHJvbWlzZTxib29sZWFuPiB7XG4gIHRyeSB7XG4gICAgY29uc3Qgc3RhdHMgPSBhd2FpdCBzdGF0KGZpbGVQYXRoKTtcbiAgICByZXR1cm4gc3RhdHMuaXNEaXJlY3RvcnkoKTtcbiAgfSBjYXRjaCB7XG4gICAgcmV0dXJuIGZhbHNlO1xuICB9XG59XG5cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBlbnN1cmVEaXJlY3RvcnlFeGlzdHMoYmFzZURpcjogc3RyaW5nKSB7XG4gIHRyeSB7XG4gICAgYXdhaXQgbWtkaXIoYmFzZURpciwgeyByZWN1cnNpdmU6IHRydWUgfSk7XG4gICAgcmV0dXJuO1xuICB9IGNhdGNoIChlcnJvcikge1xuICAgIHVpKCkubG9nZ2VyLmluZm8oKGVycm9yIGFzIHsgY29kZTogc3RyaW5nOyBtZXNzYWdlOiBzdHJpbmcgfSkubWVzc2FnZSk7XG4gICAgaWYgKChlcnJvciBhcyB7IGNvZGU6IHN0cmluZyB9KS5jb2RlICE9PSAnRUVYSVNUJykge1xuICAgICAgdGhyb3cgZXJyb3I7XG4gICAgfVxuICB9XG59XG5cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiByZW1vdmVEaXJlY3RvcnlJZkV4aXN0cyhkaXI6IHN0cmluZykge1xuICBpZiAoYXdhaXQgZGlyZWN0b3J5RXhpc3RzKGRpcikpIHtcbiAgICBhd2FpdCBybShkaXIsIHsgcmVjdXJzaXZlOiB0cnVlLCBmb3JjZTogdHJ1ZSB9KTtcbiAgfVxufVxuXG5leHBvcnQgdHlwZSBGaWxlUmVzdWx0ID0gcmVhZG9ubHkgW3N0cmluZ10gfCByZWFkb25seSBbc3RyaW5nLCBudW1iZXJdO1xuZXhwb3J0IHR5cGUgTXVsdGlwbGVGaWxlUmVzdWx0cyA9IFByb21pc2VTZXR0bGVkUmVzdWx0PEZpbGVSZXN1bHQ+W107XG5cbmV4cG9ydCBmdW5jdGlvbiBsb2dNdWx0aXBsZUZpbGVSZXN1bHRzKFxuICBmaWxlUmVzdWx0czogTXVsdGlwbGVGaWxlUmVzdWx0cyxcbiAgbWVzc2FnZVByZWZpeDogc3RyaW5nLFxuKTogdm9pZCB7XG4gIGNvbnN0IHN1Y2NlZWRlZFRyYW5zZm9ybSA9IChyZXN1bHQ6IFByb21pc2VGdWxmaWxsZWRSZXN1bHQ8RmlsZVJlc3VsdD4pID0+IHtcbiAgICBjb25zdCBbZmlsZU5hbWUsIHNpemVdID0gcmVzdWx0LnZhbHVlO1xuICAgIGNvbnN0IGZvcm1hdHRlZFNpemUgPSBzaXplID8gYCAoJHtncmF5KGZvcm1hdEJ5dGVzKHNpemUpKX0pYCA6ICcnO1xuICAgIHJldHVybiBgLSAke2JvbGQoZmlsZU5hbWUpfSR7Zm9ybWF0dGVkU2l6ZX1gO1xuICB9O1xuICBjb25zdCBmYWlsZWRUcmFuc2Zvcm0gPSAocmVzdWx0OiBQcm9taXNlUmVqZWN0ZWRSZXN1bHQpID0+XG4gICAgYC0gJHtib2xkKHJlc3VsdC5yZWFzb24gYXMgc3RyaW5nKX1gO1xuXG4gIGxvZ011bHRpcGxlUmVzdWx0czxGaWxlUmVzdWx0PihcbiAgICBmaWxlUmVzdWx0cyxcbiAgICBtZXNzYWdlUHJlZml4LFxuICAgIHN1Y2NlZWRlZFRyYW5zZm9ybSxcbiAgICBmYWlsZWRUcmFuc2Zvcm0sXG4gICk7XG59XG5cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBpbXBvcnRNb2R1bGU8VCA9IHVua25vd24+KG9wdGlvbnM6IE9wdGlvbnMpOiBQcm9taXNlPFQ+IHtcbiAgY29uc3QgeyBtb2QgfSA9IGF3YWl0IGJ1bmRsZVJlcXVpcmU8b2JqZWN0PihvcHRpb25zKTtcblxuICBpZiAodHlwZW9mIG1vZCA9PT0gJ29iamVjdCcgJiYgJ2RlZmF1bHQnIGluIG1vZCkge1xuICAgIHJldHVybiBtb2QuZGVmYXVsdCBhcyBUO1xuICB9XG4gIHJldHVybiBtb2QgYXMgVDtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHBsdWdpbldvcmtEaXIoc2x1Zzogc3RyaW5nKTogc3RyaW5nIHtcbiAgcmV0dXJuIHBhdGguam9pbignbm9kZV9tb2R1bGVzJywgJy5jb2RlLXB1c2h1cCcsIHNsdWcpO1xufVxuXG5leHBvcnQgdHlwZSBDcmF3bEZpbGVTeXN0ZW1PcHRpb25zPFQ+ID0ge1xuICBkaXJlY3Rvcnk6IHN0cmluZztcbiAgcGF0dGVybj86IHN0cmluZyB8IFJlZ0V4cDtcbiAgZmlsZVRyYW5zZm9ybT86IChmaWxlUGF0aDogc3RyaW5nKSA9PiBQcm9taXNlPFQ+IHwgVDtcbn07XG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gY3Jhd2xGaWxlU3lzdGVtPFQgPSBzdHJpbmc+KFxuICBvcHRpb25zOiBDcmF3bEZpbGVTeXN0ZW1PcHRpb25zPFQ+LFxuKTogUHJvbWlzZTxUW10+IHtcbiAgY29uc3Qge1xuICAgIGRpcmVjdG9yeSxcbiAgICBwYXR0ZXJuLFxuICAgIGZpbGVUcmFuc2Zvcm0gPSAoZmlsZVBhdGg6IHN0cmluZykgPT4gZmlsZVBhdGggYXMgVCxcbiAgfSA9IG9wdGlvbnM7XG5cbiAgY29uc3QgZmlsZXMgPSBhd2FpdCByZWFkZGlyKGRpcmVjdG9yeSk7XG4gIGNvbnN0IHByb21pc2VzID0gZmlsZXMubWFwKGFzeW5jIChmaWxlKTogUHJvbWlzZTxUIHwgVFtdPiA9PiB7XG4gICAgY29uc3QgZmlsZVBhdGggPSBwYXRoLmpvaW4oZGlyZWN0b3J5LCBmaWxlKTtcbiAgICBjb25zdCBzdGF0cyA9IGF3YWl0IHN0YXQoZmlsZVBhdGgpO1xuXG4gICAgaWYgKHN0YXRzLmlzRGlyZWN0b3J5KCkpIHtcbiAgICAgIHJldHVybiBjcmF3bEZpbGVTeXN0ZW0oeyBkaXJlY3Rvcnk6IGZpbGVQYXRoLCBwYXR0ZXJuLCBmaWxlVHJhbnNmb3JtIH0pO1xuICAgIH1cbiAgICBpZiAoc3RhdHMuaXNGaWxlKCkgJiYgKCFwYXR0ZXJuIHx8IG5ldyBSZWdFeHAocGF0dGVybikudGVzdChmaWxlKSkpIHtcbiAgICAgIHJldHVybiBmaWxlVHJhbnNmb3JtKGZpbGVQYXRoKTtcbiAgICB9XG4gICAgcmV0dXJuIFtdO1xuICB9KTtcblxuICBjb25zdCByZXN1bHRzTmVzdGVkQXJyYXkgPSBhd2FpdCBQcm9taXNlLmFsbChwcm9taXNlcyk7XG4gIHJldHVybiByZXN1bHRzTmVzdGVkQXJyYXkuZmxhdCgpIGFzIFRbXTtcbn1cblxuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGZpbmROZWFyZXN0RmlsZShcbiAgZmlsZU5hbWVzOiBzdHJpbmdbXSxcbiAgY3dkID0gcHJvY2Vzcy5jd2QoKSxcbik6IFByb21pc2U8c3RyaW5nIHwgdW5kZWZpbmVkPiB7XG4gIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBmdW5jdGlvbmFsL25vLWxvb3Atc3RhdGVtZW50c1xuICBmb3IgKFxuICAgIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBmdW5jdGlvbmFsL25vLWxldFxuICAgIGxldCBkaXJlY3RvcnkgPSBjd2Q7XG4gICAgZGlyZWN0b3J5ICE9PSBwYXRoLmRpcm5hbWUoZGlyZWN0b3J5KTtcbiAgICBkaXJlY3RvcnkgPSBwYXRoLmRpcm5hbWUoZGlyZWN0b3J5KVxuICApIHtcbiAgICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgZnVuY3Rpb25hbC9uby1sb29wLXN0YXRlbWVudHNcbiAgICBmb3IgKGNvbnN0IGZpbGUgb2YgZmlsZU5hbWVzKSB7XG4gICAgICBpZiAoYXdhaXQgZmlsZUV4aXN0cyhwYXRoLmpvaW4oZGlyZWN0b3J5LCBmaWxlKSkpIHtcbiAgICAgICAgcmV0dXJuIHBhdGguam9pbihkaXJlY3RvcnksIGZpbGUpO1xuICAgICAgfVxuICAgIH1cbiAgfVxuICByZXR1cm4gdW5kZWZpbmVkO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gZmluZExpbmVOdW1iZXJJblRleHQoXG4gIGNvbnRlbnQ6IHN0cmluZyxcbiAgcGF0dGVybjogc3RyaW5nLFxuKTogbnVtYmVyIHwgbnVsbCB7XG4gIGNvbnN0IGxpbmVzID0gY29udGVudC5zcGxpdCgvXFxyP1xcbi8pOyAvLyBTcGxpdCBsaW5lcywgaGFuZGxlIGJvdGggV2luZG93cyBhbmQgVU5JWCBsaW5lIGVuZGluZ3NcblxuICBjb25zdCBsaW5lTnVtYmVyID0gbGluZXMuZmluZEluZGV4KGxpbmUgPT4gbGluZS5pbmNsdWRlcyhwYXR0ZXJuKSkgKyAxOyAvLyArMSBiZWNhdXNlIGxpbmUgbnVtYmVycyBhcmUgMS1iYXNlZFxuICByZXR1cm4gbGluZU51bWJlciA9PT0gMCA/IG51bGwgOiBsaW5lTnVtYmVyOyAvLyBJZiB0aGUgcGFja2FnZSBpc24ndCBmb3VuZCwgcmV0dXJuIG51bGxcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGZpbGVQYXRoVG9DbGlBcmcoZmlsZVBhdGg6IHN0cmluZyk6IHN0cmluZyB7XG4gIC8vIG5lZWRzIHRvIGJlIGVzY2FwZWQgaWYgc3BhY2VzIGluY2x1ZGVkXG4gIHJldHVybiBgXCIke2ZpbGVQYXRofVwiYDtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHByb2plY3RUb0ZpbGVuYW1lKHByb2plY3Q6IHN0cmluZyk6IHN0cmluZyB7XG4gIHJldHVybiBwcm9qZWN0LnJlcGxhY2UoL1svXFxcXFxcc10rL2csICctJykucmVwbGFjZSgvQC9nLCAnJyk7XG59XG4iLCAiY29uc3QgX19pbmplY3RlZF9maWxlbmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy91dGlscy9zcmMvbGliL2Zvcm1hdHRpbmcudHNcIjtjb25zdCBfX2luamVjdGVkX2Rpcm5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvdXRpbHMvc3JjL2xpYlwiO2NvbnN0IF9faW5qZWN0ZWRfaW1wb3J0X21ldGFfdXJsX18gPSBcImZpbGU6Ly8vaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy91dGlscy9zcmMvbGliL2Zvcm1hdHRpbmcudHNcIjtpbXBvcnQge1xuICBNQVhfREVTQ1JJUFRJT05fTEVOR1RILFxuICBNQVhfSVNTVUVfTUVTU0FHRV9MRU5HVEgsXG4gIE1BWF9USVRMRV9MRU5HVEgsXG59IGZyb20gJ0Bjb2RlLXB1c2h1cC9tb2RlbHMnO1xuXG5leHBvcnQgZnVuY3Rpb24gc2x1Z2lmeSh0ZXh0OiBzdHJpbmcpOiBzdHJpbmcge1xuICByZXR1cm4gdGV4dFxuICAgIC50cmltKClcbiAgICAudG9Mb3dlckNhc2UoKVxuICAgIC5yZXBsYWNlKC9cXHMrfFxcLy9nLCAnLScpXG4gICAgLnJlcGxhY2UoL1teYS16XFxkLV0vZywgJycpO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gcGx1cmFsaXplKHRleHQ6IHN0cmluZywgYW1vdW50PzogbnVtYmVyKTogc3RyaW5nIHtcbiAgaWYgKGFtb3VudCAhPSBudWxsICYmIE1hdGguYWJzKGFtb3VudCkgPT09IDEpIHtcbiAgICByZXR1cm4gdGV4dDtcbiAgfVxuXG4gIGlmICh0ZXh0LmVuZHNXaXRoKCd5JykpIHtcbiAgICByZXR1cm4gYCR7dGV4dC5zbGljZSgwLCAtMSl9aWVzYDtcbiAgfVxuICBpZiAodGV4dC5lbmRzV2l0aCgncycpKSB7XG4gICAgcmV0dXJuIGAke3RleHR9ZXNgO1xuICB9XG4gIHJldHVybiBgJHt0ZXh0fXNgO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gZm9ybWF0Qnl0ZXMoYnl0ZXM6IG51bWJlciwgZGVjaW1hbHMgPSAyKSB7XG4gIGNvbnN0IHBvc2l0aXZlQnl0ZXMgPSBNYXRoLm1heChieXRlcywgMCk7XG5cbiAgLy8gZWFybHkgZXhpdFxuICBpZiAocG9zaXRpdmVCeXRlcyA9PT0gMCkge1xuICAgIHJldHVybiAnMCBCJztcbiAgfVxuXG4gIGNvbnN0IGsgPSAxMDI0O1xuICBjb25zdCBkbSA9IE1hdGgubWF4KGRlY2ltYWxzLCAwKTtcbiAgY29uc3Qgc2l6ZXMgPSBbJ0InLCAna0InLCAnTUInLCAnR0InLCAnVEInLCAnUEInLCAnRUInLCAnWkInLCAnWUInXTtcblxuICBjb25zdCBpID0gTWF0aC5mbG9vcihNYXRoLmxvZyhwb3NpdGl2ZUJ5dGVzKSAvIE1hdGgubG9nKGspKTtcblxuICByZXR1cm4gYCR7TnVtYmVyLnBhcnNlRmxvYXQoKHBvc2l0aXZlQnl0ZXMgLyBNYXRoLnBvdyhrLCBpKSkudG9GaXhlZChkbSkpfSAke1xuICAgIHNpemVzW2ldXG4gIH1gO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gcGx1cmFsaXplVG9rZW4odG9rZW46IHN0cmluZywgdGltZXM6IG51bWJlcik6IHN0cmluZyB7XG4gIHJldHVybiBgJHt0aW1lc30gJHtNYXRoLmFicyh0aW1lcykgPT09IDEgPyB0b2tlbiA6IHBsdXJhbGl6ZSh0b2tlbil9YDtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGZvcm1hdER1cmF0aW9uKGR1cmF0aW9uOiBudW1iZXIsIGdyYW51bGFyaXR5ID0gMCk6IHN0cmluZyB7XG4gIGlmIChkdXJhdGlvbiA8IDEwMDApIHtcbiAgICByZXR1cm4gYCR7Z3JhbnVsYXJpdHkgPyBkdXJhdGlvbi50b0ZpeGVkKGdyYW51bGFyaXR5KSA6IGR1cmF0aW9ufSBtc2A7XG4gIH1cbiAgcmV0dXJuIGAkeyhkdXJhdGlvbiAvIDEwMDApLnRvRml4ZWQoMil9IHNgO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gZm9ybWF0RGF0ZShkYXRlOiBEYXRlKTogc3RyaW5nIHtcbiAgY29uc3QgbG9jYWxlID0gJ2VuLVVTJzsgLy8gZml4ZWQgbG9jYWxlIHRvIGVuc3VyZSBjb25zaXN0ZW5jeSBhY3Jvc3MgbG9jYWwgZGVmYXVsdHMgZXhlY3V0aW9uXG4gIHJldHVybiBkYXRlXG4gICAgLnRvTG9jYWxlU3RyaW5nKGxvY2FsZSwge1xuICAgICAgd2Vla2RheTogJ3Nob3J0JyxcbiAgICAgIG1vbnRoOiAnc2hvcnQnLFxuICAgICAgZGF5OiAnbnVtZXJpYycsXG4gICAgICB5ZWFyOiAnbnVtZXJpYycsXG4gICAgICBob3VyOiAnbnVtZXJpYycsXG4gICAgICBtaW51dGU6ICcyLWRpZ2l0JyxcbiAgICAgIHRpbWVab25lTmFtZTogJ3Nob3J0JyxcbiAgICB9KVxuICAgIC5yZXBsYWNlKC9cXHUyMDJGL2csICcgJyk7IC8vIHNlZSBodHRwczovL2dpdGh1Yi5jb20vbm9kZWpzL25vZGUvaXNzdWVzLzQ1MTcxXG59XG5cbmV4cG9ydCBmdW5jdGlvbiB0cnVuY2F0ZVRleHQoXG4gIHRleHQ6IHN0cmluZyxcbiAgb3B0aW9uczpcbiAgICB8IG51bWJlclxuICAgIHwge1xuICAgICAgICBtYXhDaGFyczogbnVtYmVyO1xuICAgICAgICBwb3NpdGlvbj86ICdzdGFydCcgfCAnbWlkZGxlJyB8ICdlbmQnO1xuICAgICAgICBlbGxpcHNpcz86IHN0cmluZztcbiAgICAgIH0sXG4pOiBzdHJpbmcge1xuICBjb25zdCB7XG4gICAgbWF4Q2hhcnMsXG4gICAgcG9zaXRpb24gPSAnZW5kJyxcbiAgICBlbGxpcHNpcyA9ICcuLi4nLFxuICB9ID0gdHlwZW9mIG9wdGlvbnMgPT09ICdudW1iZXInID8geyBtYXhDaGFyczogb3B0aW9ucyB9IDogb3B0aW9ucztcbiAgaWYgKHRleHQubGVuZ3RoIDw9IG1heENoYXJzKSB7XG4gICAgcmV0dXJuIHRleHQ7XG4gIH1cblxuICBjb25zdCBtYXhMZW5ndGggPSBtYXhDaGFycyAtIGVsbGlwc2lzLmxlbmd0aDtcbiAgc3dpdGNoIChwb3NpdGlvbikge1xuICAgIGNhc2UgJ3N0YXJ0JzpcbiAgICAgIHJldHVybiBlbGxpcHNpcyArIHRleHQuc2xpY2UoLW1heExlbmd0aCkudHJpbSgpO1xuICAgIGNhc2UgJ21pZGRsZSc6XG4gICAgICBjb25zdCBoYWxmTWF4Q2hhcnMgPSBNYXRoLmZsb29yKG1heExlbmd0aCAvIDIpO1xuICAgICAgcmV0dXJuIChcbiAgICAgICAgdGV4dC5zbGljZSgwLCBoYWxmTWF4Q2hhcnMpLnRyaW0oKSArXG4gICAgICAgIGVsbGlwc2lzICtcbiAgICAgICAgdGV4dC5zbGljZSgtaGFsZk1heENoYXJzKS50cmltKClcbiAgICAgICk7XG4gICAgY2FzZSAnZW5kJzpcbiAgICAgIHJldHVybiB0ZXh0LnNsaWNlKDAsIG1heExlbmd0aCkudHJpbSgpICsgZWxsaXBzaXM7XG4gIH1cbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHRydW5jYXRlVGl0bGUodGV4dDogc3RyaW5nKTogc3RyaW5nIHtcbiAgcmV0dXJuIHRydW5jYXRlVGV4dCh0ZXh0LCBNQVhfVElUTEVfTEVOR1RIKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHRydW5jYXRlRGVzY3JpcHRpb24odGV4dDogc3RyaW5nKTogc3RyaW5nIHtcbiAgcmV0dXJuIHRydW5jYXRlVGV4dCh0ZXh0LCBNQVhfREVTQ1JJUFRJT05fTEVOR1RIKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHRydW5jYXRlSXNzdWVNZXNzYWdlKHRleHQ6IHN0cmluZyk6IHN0cmluZyB7XG4gIHJldHVybiB0cnVuY2F0ZVRleHQodGV4dCwgTUFYX0lTU1VFX01FU1NBR0VfTEVOR1RIKTtcbn1cbiIsICJjb25zdCBfX2luamVjdGVkX2ZpbGVuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3V0aWxzL3NyYy9saWIvbG9nZ2luZy50c1wiO2NvbnN0IF9faW5qZWN0ZWRfZGlybmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy91dGlscy9zcmMvbGliXCI7Y29uc3QgX19pbmplY3RlZF9pbXBvcnRfbWV0YV91cmxfXyA9IFwiZmlsZTovLy9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3V0aWxzL3NyYy9saWIvbG9nZ2luZy50c1wiO2ltcG9ydCBpc2FhY3NfY2xpdWkgZnJvbSAnQGlzYWFjcy9jbGl1aSc7XG5pbXBvcnQgeyBjbGl1aSB9IGZyb20gJ0Bwb3BwaW5zcy9jbGl1aSc7XG5pbXBvcnQgeyB1bmRlcmxpbmUgfSBmcm9tICdhbnNpcyc7XG5pbXBvcnQgeyBURVJNSU5BTF9XSURUSCB9IGZyb20gJy4vcmVwb3J0cy9jb25zdGFudHMuanMnO1xuXG4vLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgIEB0eXBlc2NyaXB0LWVzbGludC9uby1leHBsaWNpdC1hbnlcbnR5cGUgQXJndW1lbnRzVHlwZTxUPiA9IFQgZXh0ZW5kcyAoLi4uYXJnczogaW5mZXIgVSkgPT4gYW55ID8gVSA6IG5ldmVyO1xuZXhwb3J0IHR5cGUgQ2xpVWlCYXNlID0gUmV0dXJuVHlwZTx0eXBlb2YgY2xpdWk+O1xudHlwZSBVSSA9IFJldHVyblR5cGU8dHlwZW9mIGlzYWFjc19jbGl1aT47XG50eXBlIENsaUV4dGVuc2lvbiA9IHtcbiAgcm93OiAocjogQXJndW1lbnRzVHlwZTxVSVsnZGl2J10+KSA9PiB2b2lkO1xufTtcbmV4cG9ydCB0eXBlIENvbHVtbiA9IHtcbiAgdGV4dDogc3RyaW5nO1xuICB3aWR0aD86IG51bWJlcjtcbiAgYWxpZ24/OiAncmlnaHQnIHwgJ2xlZnQnIHwgJ2NlbnRlcic7XG4gIHBhZGRpbmc6IG51bWJlcltdO1xuICBib3JkZXI/OiBib29sZWFuO1xufTtcbmV4cG9ydCB0eXBlIENsaVVpID0gQ2xpVWlCYXNlICYgQ2xpRXh0ZW5zaW9uO1xuXG4vLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgaW1wb3J0L25vLW11dGFibGUtZXhwb3J0cyxmdW5jdGlvbmFsL25vLWxldFxuZXhwb3J0IGxldCBzaW5nbGV0b25VaUluc3RhbmNlOiBDbGlVaUJhc2UgfCB1bmRlZmluZWQ7XG5cbmV4cG9ydCBmdW5jdGlvbiB1aSgpOiBDbGlVaSB7XG4gIGlmIChzaW5nbGV0b25VaUluc3RhbmNlID09PSB1bmRlZmluZWQpIHtcbiAgICBzaW5nbGV0b25VaUluc3RhbmNlID0gY2xpdWkoKTtcbiAgfVxuICByZXR1cm4ge1xuICAgIC4uLnNpbmdsZXRvblVpSW5zdGFuY2UsXG4gICAgcm93OiBhcmdzID0+IHtcbiAgICAgIGxvZ0xpc3RJdGVtKGFyZ3MpO1xuICAgIH0sXG4gIH07XG59XG5cbi8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBmdW5jdGlvbmFsL25vLWxldFxubGV0IHNpbmdsZXRvbmlzYWFjVWk6IFVJIHwgdW5kZWZpbmVkO1xuZXhwb3J0IGZ1bmN0aW9uIGxvZ0xpc3RJdGVtKGFyZ3M6IEFyZ3VtZW50c1R5cGU8VUlbJ2RpdiddPikge1xuICBpZiAoc2luZ2xldG9uaXNhYWNVaSA9PT0gdW5kZWZpbmVkKSB7XG4gICAgc2luZ2xldG9uaXNhYWNVaSA9IGlzYWFjc19jbGl1aSh7IHdpZHRoOiBURVJNSU5BTF9XSURUSCB9KTtcbiAgfVxuICBzaW5nbGV0b25pc2FhY1VpLmRpdiguLi5hcmdzKTtcbiAgY29uc3QgY29udGVudCA9IHNpbmdsZXRvbmlzYWFjVWkudG9TdHJpbmcoKTtcbiAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIGZ1bmN0aW9uYWwvaW1tdXRhYmxlLWRhdGFcbiAgc2luZ2xldG9uaXNhYWNVaS5yb3dzID0gW107XG4gIHNpbmdsZXRvblVpSW5zdGFuY2U/LmxvZ2dlci5sb2coY29udGVudCk7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBsaW5rKHRleHQ6IHN0cmluZykge1xuICByZXR1cm4gdW5kZXJsaW5lLmJsdWVCcmlnaHQodGV4dCk7XG59XG4iLCAiY29uc3QgX19pbmplY3RlZF9maWxlbmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy91dGlscy9zcmMvbGliL2dpdC9naXQuY29tbWl0cy1hbmQtdGFncy50c1wiO2NvbnN0IF9faW5qZWN0ZWRfZGlybmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy91dGlscy9zcmMvbGliL2dpdFwiO2NvbnN0IF9faW5qZWN0ZWRfaW1wb3J0X21ldGFfdXJsX18gPSBcImZpbGU6Ly8vaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy91dGlscy9zcmMvbGliL2dpdC9naXQuY29tbWl0cy1hbmQtdGFncy50c1wiO2ltcG9ydCB7IHR5cGUgTG9nT3B0aW9ucyBhcyBTaW1wbGVHaXRMb2dPcHRpb25zLCBzaW1wbGVHaXQgfSBmcm9tICdzaW1wbGUtZ2l0JztcbmltcG9ydCB7IHR5cGUgQ29tbWl0LCBjb21taXRTY2hlbWEgfSBmcm9tICdAY29kZS1wdXNodXAvbW9kZWxzJztcbmltcG9ydCB7IGlzU2VtdmVyIH0gZnJvbSAnLi4vc2VtdmVyLmpzJztcblxuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGdldExhdGVzdENvbW1pdChcbiAgZ2l0ID0gc2ltcGxlR2l0KCksXG4pOiBQcm9taXNlPENvbW1pdCB8IG51bGw+IHtcbiAgY29uc3QgbG9nID0gYXdhaXQgZ2l0LmxvZyh7XG4gICAgbWF4Q291bnQ6IDEsXG4gICAgLy8gZ2l0IGxvZyAtMSAtLXByZXR0eT1mb3JtYXQ6XCIlSCAlcyAlYW4gJWFJXCIgLSBTZWU6IGh0dHBzOi8vZ2l0LXNjbS5jb20vZG9jcy9wcmV0dHktZm9ybWF0c1xuICAgIGZvcm1hdDogeyBoYXNoOiAnJUgnLCBtZXNzYWdlOiAnJXMnLCBhdXRob3I6ICclYW4nLCBkYXRlOiAnJWFJJyB9LFxuICB9KTtcbiAgcmV0dXJuIGNvbW1pdFNjaGVtYS5wYXJzZShsb2cubGF0ZXN0KTtcbn1cblxuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGdldEN1cnJlbnRCcmFuY2hPclRhZyhcbiAgZ2l0ID0gc2ltcGxlR2l0KCksXG4pOiBQcm9taXNlPHN0cmluZz4ge1xuICByZXR1cm4gKFxuICAgIChhd2FpdCBnaXQuYnJhbmNoKCkudGhlbihyID0+IHIuY3VycmVudCkpIHx8XG4gICAgLy8gSWYgbm8gY3VycmVudCBicmFuY2gsIHRyeSB0byBnZXQgdGhlIHRhZ1xuICAgIC8vIEBUT0RPIHVzZSBzaW1wbGUgZ2l0XG4gICAgKGF3YWl0IGdpdFxuICAgICAgLnJhdyhbJ2Rlc2NyaWJlJywgJy0tdGFncycsICctLWV4YWN0LW1hdGNoJ10pXG4gICAgICAudGhlbihvdXQgPT4gb3V0LnRyaW0oKSkpXG4gICk7XG59XG5cbmV4cG9ydCB0eXBlIExvZ1Jlc3VsdCA9IHsgaGFzaDogc3RyaW5nOyBtZXNzYWdlOiBzdHJpbmcgfTtcblxuZnVuY3Rpb24gdmFsaWRhdGVGaWx0ZXIoeyBmcm9tLCB0byB9OiBMb2dPcHRpb25zKSB7XG4gIGlmICh0byAmJiAhZnJvbSkge1xuICAgIC8vIHRocm93IG1vcmUgdXNlci1mcmllbmRseSBlcnJvciBpbnN0ZWFkIG9mOlxuICAgIC8vIGZhdGFsOiBhbWJpZ3VvdXMgYXJndW1lbnQgJy4uLmEnOiB1bmtub3duIHJldmlzaW9uIG9yIHBhdGggbm90IGluIHRoZSB3b3JraW5nIHRyZWUuXG4gICAgLy8gVXNlICctLScgdG8gc2VwYXJhdGUgcGF0aHMgZnJvbSByZXZpc2lvbnMsIGxpa2UgdGhpczpcbiAgICAvLyAnZ2l0IDxjb21tYW5kPiBbPHJldmlzaW9uPi4uLl0gLS0gWzxmaWxlPi4uLl0nXG4gICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgYGZpbHRlciBuZWVkcyB0aGUgXCJmcm9tXCIgb3B0aW9uIGRlZmluZWQgdG8gYWNjZXB0IHRoZSBcInRvXCIgb3B0aW9uLlxcbmAsXG4gICAgKTtcbiAgfVxufVxuXG5leHBvcnQgZnVuY3Rpb24gZmlsdGVyTG9ncyhcbiAgYWxsVGFnczogc3RyaW5nW10sXG4gIG9wdD86IFBpY2s8TG9nT3B0aW9ucywgJ2Zyb20nIHwgJ3RvJyB8ICdtYXhDb3VudCc+LFxuKSB7XG4gIGlmICghb3B0KSB7XG4gICAgcmV0dXJuIGFsbFRhZ3M7XG4gIH1cbiAgdmFsaWRhdGVGaWx0ZXIob3B0KTtcbiAgY29uc3QgeyBmcm9tLCB0bywgbWF4Q291bnQgfSA9IG9wdDtcbiAgY29uc3QgZmluSW5kZXggPSA8VD4odGFnTmFtZT86IHN0cmluZywgZmFsbGJhY2s/OiBUKSA9PiB7XG4gICAgY29uc3QgaWR4ID0gYWxsVGFncy5pbmRleE9mKHRhZ05hbWUgPz8gJycpO1xuICAgIGlmIChpZHggIT09IC0xKSB7XG4gICAgICByZXR1cm4gaWR4O1xuICAgIH1cbiAgICByZXR1cm4gZmFsbGJhY2s7XG4gIH07XG4gIGNvbnN0IGZyb21JbmRleCA9IGZpbkluZGV4KGZyb20sIDApO1xuICBjb25zdCB0b0luZGV4ID0gZmluSW5kZXgodG8sIHVuZGVmaW5lZCk7XG4gIHJldHVybiBhbGxUYWdzXG4gICAgLnNsaWNlKGZyb21JbmRleCwgdG9JbmRleCA/IHRvSW5kZXggKyAxIDogdG9JbmRleClcbiAgICAuc2xpY2UoMCwgbWF4Q291bnQgPz8gdW5kZWZpbmVkKTtcbn1cblxuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGdldEhhc2hGcm9tVGFnKFxuICB0YWc6IHN0cmluZyxcbiAgZ2l0ID0gc2ltcGxlR2l0KCksXG4pOiBQcm9taXNlPExvZ1Jlc3VsdD4ge1xuICBjb25zdCB0YWdEZXRhaWxzID0gYXdhaXQgZ2l0LnNob3coWyctLW5vLXBhdGNoJywgJy0tZm9ybWF0PSVIJywgdGFnXSk7XG4gIGNvbnN0IGhhc2ggPSB0YWdEZXRhaWxzLnRyaW0oKTsgLy8gUmVtb3ZlIHF1b3RlcyBhbmQgdHJpbSB3aGl0ZXNwYWNlXG4gIHJldHVybiB7XG4gICAgaGFzaDogaGFzaC5zcGxpdCgnXFxuJykuYXQoLTEpID8/ICcnLFxuICAgIG1lc3NhZ2U6IHRhZyxcbiAgfTtcbn1cblxuZXhwb3J0IHR5cGUgTG9nT3B0aW9ucyA9IHtcbiAgdGFyZ2V0QnJhbmNoPzogc3RyaW5nO1xuICBmcm9tPzogc3RyaW5nO1xuICB0bz86IHN0cmluZztcbiAgbWF4Q291bnQ/OiBudW1iZXI7XG59O1xuXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gZ2V0U2VtdmVyVGFncyhcbiAgb3B0OiBMb2dPcHRpb25zID0ge30sXG4gIGdpdCA9IHNpbXBsZUdpdCgpLFxuKTogUHJvbWlzZTxMb2dSZXN1bHRbXT4ge1xuICB2YWxpZGF0ZUZpbHRlcihvcHQpO1xuICBjb25zdCB7IHRhcmdldEJyYW5jaCwgLi4ub3B0aW9ucyB9ID0gb3B0O1xuICAvLyBtYWtlIHN1cmUgd2UgaGF2ZSBhIHRhcmdldCBicmFuY2hcbiAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIGZ1bmN0aW9uYWwvbm8tbGV0XG4gIGxldCBjdXJyZW50QnJhbmNoO1xuICBpZiAodGFyZ2V0QnJhbmNoKSB7XG4gICAgY3VycmVudEJyYW5jaCA9IGF3YWl0IGdldEN1cnJlbnRCcmFuY2hPclRhZyhnaXQpO1xuICAgIGF3YWl0IGdpdC5jaGVja291dCh0YXJnZXRCcmFuY2gpO1xuICB9XG5cbiAgLy8gRmV0Y2ggYWxsIHRhZ3MgbWVyZ2VkIGludG8gdGhlIHRhcmdldCBicmFuY2hcbiAgY29uc3QgdGFnc1JhdyA9IGF3YWl0IGdpdC50YWcoW1xuICAgICctLW1lcmdlZCcsXG4gICAgdGFyZ2V0QnJhbmNoID8/IChhd2FpdCBnZXRDdXJyZW50QnJhbmNoT3JUYWcoZ2l0KSksXG4gIF0pO1xuXG4gIGNvbnN0IGFsbFRhZ3MgPSB0YWdzUmF3XG4gICAgLnNwbGl0KC9cXG4vKVxuICAgIC5tYXAodGFnID0+IHRhZy50cmltKCkpXG4gICAgLmZpbHRlcihCb29sZWFuKVxuICAgIC5maWx0ZXIoaXNTZW12ZXIpO1xuXG4gIGNvbnN0IHJlbGV2YW50VGFncyA9IGZpbHRlckxvZ3MoYWxsVGFncywgb3B0aW9ucyk7XG5cbiAgY29uc3QgdGFnc1dpdGhIYXNoZXM6IExvZ1Jlc3VsdFtdID0gYXdhaXQgUHJvbWlzZS5hbGwoXG4gICAgcmVsZXZhbnRUYWdzLm1hcCh0YWcgPT4gZ2V0SGFzaEZyb21UYWcodGFnLCBnaXQpKSxcbiAgKTtcblxuICBpZiAoY3VycmVudEJyYW5jaCkge1xuICAgIGF3YWl0IGdpdC5jaGVja291dChjdXJyZW50QnJhbmNoKTtcbiAgfVxuXG4gIHJldHVybiB0YWdzV2l0aEhhc2hlcztcbn1cblxuLyoqXG4gKiBgZ2V0SGFzaGVzYCByZXR1cm5zIGEgbGlzdCBvZiBjb21taXQgaGFzaGVzLiBJbnRlcm5hbGx5IGl0IHVzZXMgYGdpdC5sb2coKWAgdG8gZGV0ZXJtaW5lIHRoZSBjb21taXRzIHdpdGhpbiBhIHJhbmdlLlxuICogVGhlIGFtb3VudCBjYW4gYmUgbGltaXRlZCB0byBhIG1heGltdW0gbnVtYmVyIG9mIGNvbW1pdHMgc3BlY2lmaWVkIGJ5IGBtYXhDb3VudGAuXG4gKiBXaXRoIGBmcm9tYCBhbmQgYHRvYCwgeW91IGNhbiBzcGVjaWZ5IGEgcmFuZ2Ugb2YgY29tbWl0cy5cbiAqXG4gKiAqKk5PVEU6KipcbiAqIEluIEdpdCwgc3BlY2lmeWluZyBhIHJhbmdlIHdpdGggdHdvIGRvdHMgKGBmcm9tLi50b2ApIHNlbGVjdHMgY29tbWl0cyB0aGF0IGFyZSByZWFjaGFibGUgZnJvbSBgdG9gIGJ1dCBub3QgZnJvbSBgZnJvbWAuXG4gKiBFc3NlbnRpYWxseSwgaXQgc2hvd3MgdGhlIGNvbW1pdHMgdGhhdCBhcmUgaW4gYHRvYCBidXQgbm90IGluIGBmcm9tYCwgZXhjbHVkaW5nIHRoZSBjb21taXRzIHVuaXF1ZSB0byBgZnJvbWAuXG4gKlxuICogRXhhbXBsZTpcbiAqXG4gKiBMZXQncyBjb25zaWRlciB0aGUgZm9sbG93aW5nIGNvbW1pdCBoaXN0b3J5OlxuICpcbiAqICAgQS0tLUItLS1DLS0tRC0tLUUgKG1haW4pXG4gKlxuICogVXNpbmcgYGdpdCBsb2cgQi4uRGAsIHlvdSB3b3VsZCBnZXQgdGhlIGNvbW1pdHMgQyBhbmQgRDpcbiAqXG4gKiAgIEMtLS1EXG4gKlxuICogVGhpcyBpcyBiZWNhdXNlIHRoZXNlIGNvbW1pdHMgYXJlIHJlYWNoYWJsZSBmcm9tIEQgYnV0IG5vdCBmcm9tIEIuXG4gKlxuICogQVNDSUkgUmVwcmVzZW50YXRpb246XG4gKlxuICogICBNYWluIEJyYW5jaDogICAgQS0tLUItLS1DLS0tRC0tLUVcbiAqICAgICAgICAgICAgICAgICAgICAgICBcXCAgICAgICBcXFxuICogICAgICAgICAgICAgICAgICAgICAgICBcXCAgICAgICArLS0tIENvbW1pdHMgaW5jbHVkZWQgaW4gYGdpdCBsb2cgQi4uRGBcbiAqICAgICAgICAgICAgICAgICAgICAgICAgIFxcXG4gKiAgICAgICAgICAgICAgICAgICAgICAgICAgKy0tLSBFeGNsdWRlZCBieSB0aGUgYGZyb21gIHBhcmFtZXRlclxuICpcbiAqIFdpdGggYHNpbXBsZS1naXRgLCB3aGVuIHlvdSBzcGVjaWZ5IGEgYGZyb21gIGFuZCBgdG9gIHJhbmdlIGxpa2UgdGhpczpcbiAqXG4gKiAgIGdpdC5sb2coeyBmcm9tOiAnQicsIHRvOiAnRCcgfSk7XG4gKlxuICogSXQgaW50ZXJwcmV0cyBpdCBzaW1pbGFybHksIHNlbGVjdGluZyBjb21taXRzIGJldHdlZW4gQiBhbmQgRCwgaW5jbHVzaXZlIG9mIEQgYnV0IGV4Y2x1c2l2ZSBvZiBCLlxuICogRm9yIGBnaXQubG9nKHsgZnJvbTogJ0InLCB0bzogJ0QnIH0pYCBvciBgZ2l0IGxvZyBCLi5EYCwgY29tbWl0cyBDIGFuZCBEIGFyZSBzZWxlY3RlZC5cbiAqXG4gKiBAcGFyYW0gb3B0aW9ucyBPYmplY3QgY29udGFpbmluZyBgZnJvbWAsIGB0b2AsIGFuZCBvcHRpb25hbGx5IGBtYXhDb3VudGAgdG8gc3BlY2lmeSB0aGUgY29tbWl0IHJhbmdlIGFuZCBsaW1pdC5cbiAqIEBwYXJhbSBnaXQgVGhlIGBzaW1wbGUtZ2l0YCBpbnN0YW5jZSB1c2VkIHRvIGV4ZWN1dGUgR2l0IGNvbW1hbmRzLlxuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gZ2V0SGFzaGVzKFxuICBvcHRpb25zOiBTaW1wbGVHaXRMb2dPcHRpb25zICYgUGljazxMb2dPcHRpb25zLCAndGFyZ2V0QnJhbmNoJz4gPSB7fSxcbiAgZ2l0ID0gc2ltcGxlR2l0KCksXG4pOiBQcm9taXNlPExvZ1Jlc3VsdFtdPiB7XG4gIGNvbnN0IHsgdGFyZ2V0QnJhbmNoLCBmcm9tLCB0bywgbWF4Q291bnQsIC4uLm9wdCB9ID0gb3B0aW9ucztcblxuICB2YWxpZGF0ZUZpbHRlcih7IGZyb20sIHRvIH0pO1xuXG4gIC8vIEVuc3VyZSB5b3UgYXJlIG9uIHRoZSBjb3JyZWN0IGJyYW5jaFxuICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgZnVuY3Rpb25hbC9uby1sZXRcbiAgbGV0IGN1cnJlbnRCcmFuY2g7XG4gIGlmICh0YXJnZXRCcmFuY2gpIHtcbiAgICBjdXJyZW50QnJhbmNoID0gYXdhaXQgZ2V0Q3VycmVudEJyYW5jaE9yVGFnKGdpdCk7XG4gICAgYXdhaXQgZ2l0LmNoZWNrb3V0KHRhcmdldEJyYW5jaCk7XG4gIH1cblxuICBjb25zdCBsb2dzID0gYXdhaXQgZ2l0LmxvZyh7XG4gICAgLi4ub3B0LFxuICAgIGZvcm1hdDoge1xuICAgICAgaGFzaDogJyVIJyxcbiAgICAgIG1lc3NhZ2U6ICclcycsXG4gICAgfSxcbiAgICBmcm9tLFxuICAgIHRvLFxuICAgIG1heENvdW50LFxuICB9KTtcblxuICAvLyBFbnN1cmUgeW91IGFyZSBiYWNrIHRvIHRoZSBpbml0aWFsIGJyYW5jaFxuICBpZiAodGFyZ2V0QnJhbmNoKSB7XG4gICAgYXdhaXQgZ2l0LmNoZWNrb3V0KGN1cnJlbnRCcmFuY2ggYXMgc3RyaW5nKTtcbiAgfVxuXG4gIHJldHVybiBbLi4ubG9ncy5hbGxdO1xufVxuIiwgImNvbnN0IF9faW5qZWN0ZWRfZmlsZW5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvdXRpbHMvc3JjL2xpYi9zZW12ZXIudHNcIjtjb25zdCBfX2luamVjdGVkX2Rpcm5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvdXRpbHMvc3JjL2xpYlwiO2NvbnN0IF9faW5qZWN0ZWRfaW1wb3J0X21ldGFfdXJsX18gPSBcImZpbGU6Ly8vaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy91dGlscy9zcmMvbGliL3NlbXZlci50c1wiO2ltcG9ydCB7IHJjb21wYXJlLCB2YWxpZCB9IGZyb20gJ3NlbXZlcic7XG5cbmV4cG9ydCBmdW5jdGlvbiBub3JtYWxpemVTZW12ZXIoc2VtdmVyU3RyaW5nOiBzdHJpbmcpOiBzdHJpbmcge1xuICBpZiAoc2VtdmVyU3RyaW5nLnN0YXJ0c1dpdGgoJ3YnKSB8fCBzZW12ZXJTdHJpbmcuc3RhcnRzV2l0aCgnVicpKSB7XG4gICAgcmV0dXJuIHNlbXZlclN0cmluZy5zbGljZSgxKTtcbiAgfVxuXG4gIGlmIChzZW12ZXJTdHJpbmcuaW5jbHVkZXMoJ0AnKSkge1xuICAgIHJldHVybiBzZW12ZXJTdHJpbmcuc3BsaXQoJ0AnKS5hdCgtMSkgPz8gJyc7XG4gIH1cblxuICByZXR1cm4gc2VtdmVyU3RyaW5nO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gaXNTZW12ZXIoc2VtdmVyU3RyaW5nID0gJycpOiBib29sZWFuIHtcbiAgcmV0dXJuIHZhbGlkKG5vcm1hbGl6ZVNlbXZlcihzZW12ZXJTdHJpbmcpKSAhPSBudWxsO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gc29ydFNlbXZlcnMoc2VtdmVyU3RyaW5nczogc3RyaW5nW10pOiBzdHJpbmdbXSB7XG4gIHJldHVybiBzZW12ZXJTdHJpbmdzLm1hcChub3JtYWxpemVTZW12ZXIpLmZpbHRlcihpc1NlbXZlcikuc29ydChyY29tcGFyZSk7XG59XG4iLCAiY29uc3QgX19pbmplY3RlZF9maWxlbmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy91dGlscy9zcmMvbGliL2dpdC9naXQudHNcIjtjb25zdCBfX2luamVjdGVkX2Rpcm5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvdXRpbHMvc3JjL2xpYi9naXRcIjtjb25zdCBfX2luamVjdGVkX2ltcG9ydF9tZXRhX3VybF9fID0gXCJmaWxlOi8vL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvdXRpbHMvc3JjL2xpYi9naXQvZ2l0LnRzXCI7aW1wb3J0IHBhdGggZnJvbSAnbm9kZTpwYXRoJztcbmltcG9ydCB7IHR5cGUgU3RhdHVzUmVzdWx0LCBzaW1wbGVHaXQgfSBmcm9tICdzaW1wbGUtZ2l0JztcbmltcG9ydCB7IHVpIH0gZnJvbSAnLi4vbG9nZ2luZy5qcyc7XG5pbXBvcnQgeyB0b1VuaXhQYXRoIH0gZnJvbSAnLi4vdHJhbnNmb3JtLmpzJztcblxuZXhwb3J0IGZ1bmN0aW9uIGdldEdpdFJvb3QoZ2l0ID0gc2ltcGxlR2l0KCkpOiBQcm9taXNlPHN0cmluZz4ge1xuICByZXR1cm4gZ2l0LnJldnBhcnNlKCctLXNob3ctdG9wbGV2ZWwnKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGZvcm1hdEdpdFBhdGgoZmlsZVBhdGg6IHN0cmluZywgZ2l0Um9vdDogc3RyaW5nKTogc3RyaW5nIHtcbiAgY29uc3QgYWJzb2x1dGVQYXRoID0gcGF0aC5pc0Fic29sdXRlKGZpbGVQYXRoKVxuICAgID8gZmlsZVBhdGhcbiAgICA6IHBhdGguam9pbihwcm9jZXNzLmN3ZCgpLCBmaWxlUGF0aCk7XG4gIGNvbnN0IHJlbGF0aXZlUGF0aCA9IHBhdGgucmVsYXRpdmUoZ2l0Um9vdCwgYWJzb2x1dGVQYXRoKTtcbiAgcmV0dXJuIHRvVW5peFBhdGgocmVsYXRpdmVQYXRoKTtcbn1cblxuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIHRvR2l0UGF0aChcbiAgZmlsZVBhdGg6IHN0cmluZyxcbiAgZ2l0ID0gc2ltcGxlR2l0KCksXG4pOiBQcm9taXNlPHN0cmluZz4ge1xuICBjb25zdCBnaXRSb290ID0gYXdhaXQgZ2V0R2l0Um9vdChnaXQpO1xuICByZXR1cm4gZm9ybWF0R2l0UGF0aChmaWxlUGF0aCwgZ2l0Um9vdCk7XG59XG5cbmV4cG9ydCBjbGFzcyBHaXRTdGF0dXNFcnJvciBleHRlbmRzIEVycm9yIHtcbiAgc3RhdGljIGlnbm9yZWRQcm9wcyA9IG5ldyBTZXQoWydjdXJyZW50JywgJ3RyYWNraW5nJ10pO1xuXG4gIHN0YXRpYyBnZXRSZWR1Y2VkU3RhdHVzKHN0YXR1czogU3RhdHVzUmVzdWx0KSB7XG4gICAgcmV0dXJuIE9iamVjdC5mcm9tRW50cmllcyhcbiAgICAgIE9iamVjdC5lbnRyaWVzKHN0YXR1cylcbiAgICAgICAgLmZpbHRlcigoW2tleV0pID0+ICF0aGlzLmlnbm9yZWRQcm9wcy5oYXMoa2V5KSlcbiAgICAgICAgLmZpbHRlcihcbiAgICAgICAgICAoXG4gICAgICAgICAgICBlbnRyeTogW1xuICAgICAgICAgICAgICBzdHJpbmcsXG4gICAgICAgICAgICAgIG51bWJlciB8IHN0cmluZyB8IGJvb2xlYW4gfCBudWxsIHwgdW5kZWZpbmVkIHwgdW5rbm93bltdLFxuICAgICAgICAgICAgXSxcbiAgICAgICAgICApID0+IHtcbiAgICAgICAgICAgIGNvbnN0IHZhbHVlID0gZW50cnlbMV07XG4gICAgICAgICAgICBpZiAodmFsdWUgPT0gbnVsbCkge1xuICAgICAgICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBpZiAoQXJyYXkuaXNBcnJheSh2YWx1ZSkgJiYgdmFsdWUubGVuZ3RoID09PSAwKSB7XG4gICAgICAgICAgICAgIHJldHVybiBmYWxzZTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGlmICh0eXBlb2YgdmFsdWUgPT09ICdudW1iZXInICYmIHZhbHVlID09PSAwKSB7XG4gICAgICAgICAgICAgIHJldHVybiBmYWxzZTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIHJldHVybiAhKHR5cGVvZiB2YWx1ZSA9PT0gJ2Jvb2xlYW4nICYmICF2YWx1ZSk7XG4gICAgICAgICAgfSxcbiAgICAgICAgKSxcbiAgICApO1xuICB9XG5cbiAgY29uc3RydWN0b3Ioc3RhdHVzOiBTdGF0dXNSZXN1bHQpIHtcbiAgICBzdXBlcihcbiAgICAgIGBXb3JraW5nIGRpcmVjdG9yeSBuZWVkcyB0byBiZSBjbGVhbiBiZWZvcmUgd2UgeW91IGNhbiBwcm9jZWVkLiBDb21taXQgeW91ciBsb2NhbCBjaGFuZ2VzIG9yIHN0YXNoIHRoZW06IFxcbiAke0pTT04uc3RyaW5naWZ5KFxuICAgICAgICBHaXRTdGF0dXNFcnJvci5nZXRSZWR1Y2VkU3RhdHVzKHN0YXR1cyksXG4gICAgICAgIG51bGwsXG4gICAgICAgIDIsXG4gICAgICApfWAsXG4gICAgKTtcbiAgfVxufVxuXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gZ3VhcmRBZ2FpbnN0TG9jYWxDaGFuZ2VzKFxuICBnaXQgPSBzaW1wbGVHaXQoKSxcbik6IFByb21pc2U8dm9pZD4ge1xuICBjb25zdCBzdGF0dXMgPSBhd2FpdCBnaXQuc3RhdHVzKFsnLXMnXSk7XG4gIGlmIChzdGF0dXMuZmlsZXMubGVuZ3RoID4gMCkge1xuICAgIHRocm93IG5ldyBHaXRTdGF0dXNFcnJvcihzdGF0dXMpO1xuICB9XG59XG5cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBzYWZlQ2hlY2tvdXQoXG4gIGJyYW5jaE9ySGFzaDogc3RyaW5nLFxuICBmb3JjZUNsZWFuU3RhdHVzID0gZmFsc2UsXG4gIGdpdCA9IHNpbXBsZUdpdCgpLFxuKTogUHJvbWlzZTx2b2lkPiB7XG4gIC8vIGdpdCByZXF1aXJlcyBhIGNsZWFuIGhpc3RvcnkgdG8gY2hlY2sgb3V0IGEgYnJhbmNoXG4gIGlmIChmb3JjZUNsZWFuU3RhdHVzKSB7XG4gICAgYXdhaXQgZ2l0LnJhdyhbJ3Jlc2V0JywgJy0taGFyZCddKTtcbiAgICBhd2FpdCBnaXQuY2xlYW4oWydmJywgJ2QnXSk7XG4gICAgdWkoKS5sb2dnZXIuaW5mbyhgZ2l0IHN0YXR1cyBjbGVhbmVkYCk7XG4gIH1cbiAgYXdhaXQgZ3VhcmRBZ2FpbnN0TG9jYWxDaGFuZ2VzKGdpdCk7XG4gIGF3YWl0IGdpdC5jaGVja291dChicmFuY2hPckhhc2gpO1xufVxuIiwgImNvbnN0IF9faW5qZWN0ZWRfZmlsZW5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvdXRpbHMvc3JjL2xpYi90cmFuc2Zvcm0udHNcIjtjb25zdCBfX2luamVjdGVkX2Rpcm5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvdXRpbHMvc3JjL2xpYlwiO2NvbnN0IF9faW5qZWN0ZWRfaW1wb3J0X21ldGFfdXJsX18gPSBcImZpbGU6Ly8vaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy91dGlscy9zcmMvbGliL3RyYW5zZm9ybS50c1wiO2ltcG9ydCB7IHBsYXRmb3JtIH0gZnJvbSAnbm9kZTpvcyc7XG5cbmV4cG9ydCBmdW5jdGlvbiB0b0FycmF5PFQ+KHZhbDogVCB8IFRbXSk6IFRbXSB7XG4gIHJldHVybiBBcnJheS5pc0FycmF5KHZhbCkgPyB2YWwgOiBbdmFsXTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIG9iamVjdFRvS2V5czxUIGV4dGVuZHMgb2JqZWN0PihvYmo6IFQpIHtcbiAgcmV0dXJuIE9iamVjdC5rZXlzKG9iaikgYXMgKGtleW9mIFQpW107XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBvYmplY3RUb0VudHJpZXM8VCBleHRlbmRzIG9iamVjdD4ob2JqOiBUKSB7XG4gIHJldHVybiBPYmplY3QuZW50cmllcyhvYmopIGFzIFtrZXlvZiBULCBUW2tleW9mIFRdXVtdO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gb2JqZWN0RnJvbUVudHJpZXM8SyBleHRlbmRzIFByb3BlcnR5S2V5LCBWPihlbnRyaWVzOiBbSywgVl1bXSkge1xuICByZXR1cm4gT2JqZWN0LmZyb21FbnRyaWVzKGVudHJpZXMpIGFzIFJlY29yZDxLLCBWPjtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGNvdW50T2NjdXJyZW5jZXM8VCBleHRlbmRzIFByb3BlcnR5S2V5PihcbiAgdmFsdWVzOiBUW10sXG4pOiBQYXJ0aWFsPFJlY29yZDxULCBudW1iZXI+PiB7XG4gIHJldHVybiB2YWx1ZXMucmVkdWNlPFBhcnRpYWw8UmVjb3JkPFQsIG51bWJlcj4+PihcbiAgICAoYWNjLCB2YWx1ZSkgPT4gKHsgLi4uYWNjLCBbdmFsdWVdOiAoYWNjW3ZhbHVlXSA/PyAwKSArIDEgfSksXG4gICAge30sXG4gICk7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBkaXN0aW5jdDxUIGV4dGVuZHMgc3RyaW5nIHwgbnVtYmVyIHwgYm9vbGVhbj4oYXJyYXk6IFRbXSk6IFRbXSB7XG4gIHJldHVybiBbLi4ubmV3IFNldChhcnJheSldO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gZGVlcENsb25lPFQ+KG9iajogVCk6IFQge1xuICByZXR1cm4gb2JqID09IG51bGwgfHwgdHlwZW9mIG9iaiAhPT0gJ29iamVjdCcgPyBvYmogOiBzdHJ1Y3R1cmVkQ2xvbmUob2JqKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGZhY3Rvck9mPFQ+KGl0ZW1zOiBUW10sIGZpbHRlckZuOiAoaTogVCkgPT4gYm9vbGVhbik6IG51bWJlciB7XG4gIGNvbnN0IGl0ZW1Db3VudCA9IGl0ZW1zLmxlbmd0aDtcbiAgLy8gZWFybHkgZXhpdCBmb3IgZW1wdHkgcm93c1xuICBpZiAoIWl0ZW1Db3VudCkge1xuICAgIHJldHVybiAxO1xuICB9XG4gIGNvbnN0IGZpbHRlckNvdW50ID0gaXRlbXMuZmlsdGVyKGZpbHRlckZuKS5sZW5ndGg7XG4gIC8vIGlmIG5vIHJvd3MgcmVzdWx0IGZyb20gdGhlIGZpbHRlciBmbiB3ZSBmb3J3YXJkIHJldHVybiAxIGFzIGZhY3RvclxuICByZXR1cm4gZmlsdGVyQ291bnQgPT09IDAgPyAxIDogKGl0ZW1Db3VudCAtIGZpbHRlckNvdW50KSAvIGl0ZW1Db3VudDtcbn1cblxudHlwZSBBcmd1bWVudFZhbHVlID0gbnVtYmVyIHwgc3RyaW5nIHwgYm9vbGVhbiB8IHN0cmluZ1tdO1xuZXhwb3J0IHR5cGUgQ2xpQXJnc09iamVjdDxUIGV4dGVuZHMgb2JqZWN0ID0gUmVjb3JkPHN0cmluZywgQXJndW1lbnRWYWx1ZT4+ID1cbiAgVCBleHRlbmRzIG5ldmVyXG4gICAgPyBSZWNvcmQ8c3RyaW5nLCBBcmd1bWVudFZhbHVlIHwgdW5kZWZpbmVkPiB8IHsgXzogc3RyaW5nIH1cbiAgICA6IFQ7XG5cbi8qKlxuICogQ29udmVydHMgYW4gb2JqZWN0IHdpdGggZGlmZmVyZW50IHR5cGVzIG9mIHZhbHVlcyBpbnRvIGFuIGFycmF5IG9mIGNvbW1hbmQtbGluZSBhcmd1bWVudHMuXG4gKlxuICogQGV4YW1wbGVcbiAqIGNvbnN0IGFyZ3MgPSBvYmplY3RUb1Byb2Nlc3NBcmdzKHtcbiAqICAgXzogWydub2RlJywgJ2luZGV4LmpzJ10sIC8vIG5vZGUgaW5kZXguanNcbiAqICAgbmFtZTogJ0p1YW5pdGEnLCAvLyAtLW5hbWU9SnVhbml0YVxuICogICBmb3JtYXRzOiBbJ2pzb24nLCAnbWQnXSAvLyAtLWZvcm1hdD1qc29uIC0tZm9ybWF0PW1kXG4gKiB9KTtcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIG9iamVjdFRvQ2xpQXJnczxcbiAgVCBleHRlbmRzIG9iamVjdCA9IFJlY29yZDxzdHJpbmcsIEFyZ3VtZW50VmFsdWU+LFxuPihwYXJhbXM/OiBDbGlBcmdzT2JqZWN0PFQ+KTogc3RyaW5nW10ge1xuICBpZiAoIXBhcmFtcykge1xuICAgIHJldHVybiBbXTtcbiAgfVxuXG4gIHJldHVybiBPYmplY3QuZW50cmllcyhwYXJhbXMpLmZsYXRNYXAoKFtrZXksIHZhbHVlXSkgPT4ge1xuICAgIC8vIHByb2Nlc3MvZmlsZS9zY3JpcHRcbiAgICBpZiAoa2V5ID09PSAnXycpIHtcbiAgICAgIHJldHVybiBBcnJheS5pc0FycmF5KHZhbHVlKSA/IHZhbHVlIDogW2Ake3ZhbHVlfWBdO1xuICAgIH1cbiAgICBjb25zdCBwcmVmaXggPSBrZXkubGVuZ3RoID09PSAxID8gJy0nIDogJy0tJztcbiAgICAvLyBcIi0qXCIgYXJndW1lbnRzIChzaG9ydGhhbmRzKVxuICAgIGlmIChBcnJheS5pc0FycmF5KHZhbHVlKSkge1xuICAgICAgcmV0dXJuIHZhbHVlLm1hcCh2ID0+IGAke3ByZWZpeH0ke2tleX09XCIke3Z9XCJgKTtcbiAgICB9XG4gICAgLy8gXCItLSpcIiBhcmd1bWVudHMgPT09PT09PT09PVxuXG4gICAgaWYgKEFycmF5LmlzQXJyYXkodmFsdWUpKSB7XG4gICAgICByZXR1cm4gdmFsdWUubWFwKHYgPT4gYCR7cHJlZml4fSR7a2V5fT1cIiR7dn1cImApO1xuICAgIH1cblxuICAgIGlmICh0eXBlb2YgdmFsdWUgPT09ICdvYmplY3QnKSB7XG4gICAgICByZXR1cm4gT2JqZWN0LmVudHJpZXModmFsdWUgYXMgUmVjb3JkPHN0cmluZywgQXJndW1lbnRWYWx1ZT4pLmZsYXRNYXAoXG4gICAgICAgIC8vIHRyYW5zZm9ybSBuZXN0ZWQgb2JqZWN0cyB0byB0aGUgZG90IG5vdGF0aW9uIGBrZXkuc3Via2V5YFxuICAgICAgICAoW2ssIHZdKSA9PiBvYmplY3RUb0NsaUFyZ3MoeyBbYCR7a2V5fS4ke2t9YF06IHYgfSksXG4gICAgICApO1xuICAgIH1cblxuICAgIGlmICh0eXBlb2YgdmFsdWUgPT09ICdzdHJpbmcnKSB7XG4gICAgICByZXR1cm4gW2Ake3ByZWZpeH0ke2tleX09XCIke3ZhbHVlfVwiYF07XG4gICAgfVxuXG4gICAgaWYgKHR5cGVvZiB2YWx1ZSA9PT0gJ251bWJlcicpIHtcbiAgICAgIHJldHVybiBbYCR7cHJlZml4fSR7a2V5fT0ke3ZhbHVlfWBdO1xuICAgIH1cblxuICAgIGlmICh0eXBlb2YgdmFsdWUgPT09ICdib29sZWFuJykge1xuICAgICAgcmV0dXJuIFtgJHtwcmVmaXh9JHt2YWx1ZSA/ICcnIDogJ25vLSd9JHtrZXl9YF07XG4gICAgfVxuXG4gICAgdGhyb3cgbmV3IEVycm9yKGBVbnN1cHBvcnRlZCB0eXBlICR7dHlwZW9mIHZhbHVlfSBmb3Iga2V5ICR7a2V5fWApO1xuICB9KTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHRvVW5peFBhdGgocGF0aDogc3RyaW5nKTogc3RyaW5nIHtcbiAgcmV0dXJuIHBhdGgucmVwbGFjZSgvXFxcXC9nLCAnLycpO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gdG9Vbml4TmV3bGluZXModGV4dDogc3RyaW5nKTogc3RyaW5nIHtcbiAgcmV0dXJuIHBsYXRmb3JtKCkgPT09ICd3aW4zMicgPyB0ZXh0LnJlcGxhY2UoL1xcclxcbi9nLCAnXFxuJykgOiB0ZXh0O1xufVxuXG5leHBvcnQgZnVuY3Rpb24gZnJvbUpzb25MaW5lczxUID0gdW5rbm93bj4oanNvbkxpbmVzOiBzdHJpbmcpIHtcbiAgY29uc3QgdW5pZmllZE5ld0xpbmVzID0gdG9Vbml4TmV3bGluZXMoanNvbkxpbmVzKS50cmltKCk7XG4gIHJldHVybiBKU09OLnBhcnNlKGBbJHt1bmlmaWVkTmV3TGluZXMuc3BsaXQoJ1xcbicpLmpvaW4oJywnKX1dYCkgYXMgVDtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHRvSnNvbkxpbmVzPFQ+KGpzb246IFRbXSkge1xuICByZXR1cm4ganNvbi5tYXAoaXRlbSA9PiBKU09OLnN0cmluZ2lmeShpdGVtKSkuam9pbignXFxuJyk7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBjYXBpdGFsaXplPFQgZXh0ZW5kcyBzdHJpbmc+KHRleHQ6IFQpOiBDYXBpdGFsaXplPFQ+IHtcbiAgcmV0dXJuIGAke3RleHQuY2hhckF0KDApLnRvTG9jYWxlVXBwZXJDYXNlKCl9JHt0ZXh0LnNsaWNlKFxuICAgIDEsXG4gICl9YCBhcyBDYXBpdGFsaXplPFQ+O1xufVxuXG5leHBvcnQgZnVuY3Rpb24gdG9OdW1iZXJQcmVjaXNpb24oXG4gIHZhbHVlOiBudW1iZXIsXG4gIGRlY2ltYWxQbGFjZXM6IG51bWJlcixcbik6IG51bWJlciB7XG4gIHJldHVybiBOdW1iZXIoXG4gICAgYCR7TWF0aC5yb3VuZChcbiAgICAgIE51bWJlci5wYXJzZUZsb2F0KGAke3ZhbHVlfWUke2RlY2ltYWxQbGFjZXN9YCksXG4gICAgKX1lLSR7ZGVjaW1hbFBsYWNlc31gLFxuICApO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gdG9PcmRpbmFsKHZhbHVlOiBudW1iZXIpOiBzdHJpbmcge1xuICAvKiBlc2xpbnQtZGlzYWJsZSBAdHlwZXNjcmlwdC1lc2xpbnQvbm8tbWFnaWMtbnVtYmVycyAqL1xuICBpZiAodmFsdWUgJSAxMCA9PT0gMSAmJiB2YWx1ZSAlIDEwMCAhPT0gMTEpIHtcbiAgICByZXR1cm4gYCR7dmFsdWV9c3RgO1xuICB9XG5cbiAgaWYgKHZhbHVlICUgMTAgPT09IDIgJiYgdmFsdWUgJSAxMDAgIT09IDEyKSB7XG4gICAgcmV0dXJuIGAke3ZhbHVlfW5kYDtcbiAgfVxuXG4gIGlmICh2YWx1ZSAlIDEwID09PSAzICYmIHZhbHVlICUgMTAwICE9PSAxMykge1xuICAgIHJldHVybiBgJHt2YWx1ZX1yZGA7XG4gIH1cbiAgLyogZXNsaW50LWVuYWJsZSBAdHlwZXNjcmlwdC1lc2xpbnQvbm8tbWFnaWMtbnVtYmVycyAqL1xuXG4gIHJldHVybiBgJHt2YWx1ZX10aGA7XG59XG4iLCAiY29uc3QgX19pbmplY3RlZF9maWxlbmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy91dGlscy9zcmMvbGliL21lcmdlLWNvbmZpZ3MudHNcIjtjb25zdCBfX2luamVjdGVkX2Rpcm5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvdXRpbHMvc3JjL2xpYlwiO2NvbnN0IF9faW5qZWN0ZWRfaW1wb3J0X21ldGFfdXJsX18gPSBcImZpbGU6Ly8vaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy91dGlscy9zcmMvbGliL21lcmdlLWNvbmZpZ3MudHNcIjtpbXBvcnQgdHlwZSB7XG4gIENhdGVnb3J5Q29uZmlnLFxuICBDb3JlQ29uZmlnLFxuICBQZXJzaXN0Q29uZmlnLFxuICBQbHVnaW5Db25maWcsXG4gIFVwbG9hZENvbmZpZyxcbn0gZnJvbSAnQGNvZGUtcHVzaHVwL21vZGVscyc7XG5cbmV4cG9ydCBmdW5jdGlvbiBtZXJnZUNvbmZpZ3MoXG4gIGNvbmZpZzogQ29yZUNvbmZpZyxcbiAgLi4uY29uZmlnczogUGFydGlhbDxDb3JlQ29uZmlnPltdXG4pOiBQYXJ0aWFsPENvcmVDb25maWc+IHtcbiAgcmV0dXJuIGNvbmZpZ3MucmVkdWNlKFxuICAgIChhY2MsIG9iaikgPT4gKHtcbiAgICAgIC4uLmFjYyxcbiAgICAgIC4uLm1lcmdlQ2F0ZWdvcmllcyhhY2MuY2F0ZWdvcmllcywgb2JqLmNhdGVnb3JpZXMpLFxuICAgICAgLi4ubWVyZ2VQbHVnaW5zKGFjYy5wbHVnaW5zLCBvYmoucGx1Z2lucyksXG4gICAgICAuLi5tZXJnZVBlcnNpc3QoYWNjLnBlcnNpc3QsIG9iai5wZXJzaXN0KSxcbiAgICAgIC4uLm1lcmdlVXBsb2FkKGFjYy51cGxvYWQsIG9iai51cGxvYWQpLFxuICAgIH0pLFxuICAgIGNvbmZpZyxcbiAgKTtcbn1cblxuZnVuY3Rpb24gbWVyZ2VDYXRlZ29yaWVzKFxuICBhOiBDYXRlZ29yeUNvbmZpZ1tdIHwgdW5kZWZpbmVkLFxuICBiOiBDYXRlZ29yeUNvbmZpZ1tdIHwgdW5kZWZpbmVkLFxuKTogUGljazxDb3JlQ29uZmlnLCAnY2F0ZWdvcmllcyc+IHtcbiAgaWYgKCFhICYmICFiKSB7XG4gICAgcmV0dXJuIHt9O1xuICB9XG5cbiAgY29uc3QgbWVyZ2VkTWFwID0gbmV3IE1hcDxzdHJpbmcsIENhdGVnb3J5Q29uZmlnPigpO1xuXG4gIGNvbnN0IGFkZFRvTWFwID0gKGNhdGVnb3JpZXM6IENhdGVnb3J5Q29uZmlnW10pID0+IHtcbiAgICBjYXRlZ29yaWVzLmZvckVhY2gobmV3T2JqZWN0ID0+IHtcbiAgICAgIGlmIChtZXJnZWRNYXAuaGFzKG5ld09iamVjdC5zbHVnKSkge1xuICAgICAgICBjb25zdCBleGlzdGluZ09iamVjdDogQ2F0ZWdvcnlDb25maWcgfCB1bmRlZmluZWQgPSBtZXJnZWRNYXAuZ2V0KFxuICAgICAgICAgIG5ld09iamVjdC5zbHVnLFxuICAgICAgICApO1xuXG4gICAgICAgIG1lcmdlZE1hcC5zZXQobmV3T2JqZWN0LnNsdWcsIHtcbiAgICAgICAgICAuLi5leGlzdGluZ09iamVjdCxcbiAgICAgICAgICAuLi5uZXdPYmplY3QsXG5cbiAgICAgICAgICByZWZzOiBtZXJnZUJ5VW5pcXVlQ2F0ZWdvcnlSZWZDb21iaW5hdGlvbihcbiAgICAgICAgICAgIGV4aXN0aW5nT2JqZWN0Py5yZWZzLFxuICAgICAgICAgICAgbmV3T2JqZWN0LnJlZnMsXG4gICAgICAgICAgKSxcbiAgICAgICAgfSk7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICBtZXJnZWRNYXAuc2V0KG5ld09iamVjdC5zbHVnLCBuZXdPYmplY3QpO1xuICAgICAgfVxuICAgIH0pO1xuICB9O1xuXG4gIGlmIChhKSB7XG4gICAgYWRkVG9NYXAoYSk7XG4gIH1cbiAgaWYgKGIpIHtcbiAgICBhZGRUb01hcChiKTtcbiAgfVxuXG4gIC8vIENvbnZlcnQgdGhlIG1hcCBiYWNrIHRvIGFuIGFycmF5XG4gIHJldHVybiB7IGNhdGVnb3JpZXM6IFsuLi5tZXJnZWRNYXAudmFsdWVzKCldIH07XG59XG5cbmZ1bmN0aW9uIG1lcmdlUGx1Z2lucyhcbiAgYTogUGx1Z2luQ29uZmlnW10gfCB1bmRlZmluZWQsXG4gIGI6IFBsdWdpbkNvbmZpZ1tdIHwgdW5kZWZpbmVkLFxuKTogUGljazxDb3JlQ29uZmlnLCAncGx1Z2lucyc+IHtcbiAgaWYgKCFhICYmICFiKSB7XG4gICAgcmV0dXJuIHsgcGx1Z2luczogW10gfTtcbiAgfVxuXG4gIGNvbnN0IG1lcmdlZE1hcCA9IG5ldyBNYXA8c3RyaW5nLCBQbHVnaW5Db25maWc+KCk7XG5cbiAgY29uc3QgYWRkVG9NYXAgPSAocGx1Z2luczogUGx1Z2luQ29uZmlnW10pID0+IHtcbiAgICBwbHVnaW5zLmZvckVhY2gobmV3T2JqZWN0ID0+IHtcbiAgICAgIG1lcmdlZE1hcC5zZXQobmV3T2JqZWN0LnNsdWcsIG5ld09iamVjdCk7XG4gICAgfSk7XG4gIH07XG5cbiAgaWYgKGEpIHtcbiAgICBhZGRUb01hcChhKTtcbiAgfVxuICBpZiAoYikge1xuICAgIGFkZFRvTWFwKGIpO1xuICB9XG5cbiAgcmV0dXJuIHsgcGx1Z2luczogWy4uLm1lcmdlZE1hcC52YWx1ZXMoKV0gfTtcbn1cblxuZnVuY3Rpb24gbWVyZ2VQZXJzaXN0KFxuICBhOiBQZXJzaXN0Q29uZmlnIHwgdW5kZWZpbmVkLFxuICBiOiBQZXJzaXN0Q29uZmlnIHwgdW5kZWZpbmVkLFxuKTogUGljazxDb3JlQ29uZmlnLCAncGVyc2lzdCc+IHtcbiAgaWYgKCFhICYmICFiKSB7XG4gICAgcmV0dXJuIHt9O1xuICB9XG5cbiAgaWYgKGEpIHtcbiAgICByZXR1cm4gYiA/IHsgcGVyc2lzdDogeyAuLi5hLCAuLi5iIH0gfSA6IHt9O1xuICB9IGVsc2Uge1xuICAgIHJldHVybiB7IHBlcnNpc3Q6IGIgfTtcbiAgfVxufVxuXG5mdW5jdGlvbiBtZXJnZUJ5VW5pcXVlQ2F0ZWdvcnlSZWZDb21iaW5hdGlvbjxcbiAgVCBleHRlbmRzIHsgc2x1Zzogc3RyaW5nOyB0eXBlOiBzdHJpbmc7IHBsdWdpbjogc3RyaW5nIH0sXG4+KGE6IFRbXSB8IHVuZGVmaW5lZCwgYjogVFtdIHwgdW5kZWZpbmVkKSB7XG4gIGNvbnN0IG1hcCA9IG5ldyBNYXA8c3RyaW5nLCBUPigpO1xuXG4gIGNvbnN0IGFkZFRvTWFwID0gKHJlZnM6IFRbXSkgPT4ge1xuICAgIHJlZnMuZm9yRWFjaChyZWYgPT4ge1xuICAgICAgY29uc3QgdW5pcXVlSWRlbnRpZmljYXRpb24gPSBgJHtyZWYudHlwZX06JHtyZWYucGx1Z2lufToke3JlZi5zbHVnfWA7XG4gICAgICBpZiAobWFwLmhhcyh1bmlxdWVJZGVudGlmaWNhdGlvbikpIHtcbiAgICAgICAgbWFwLnNldCh1bmlxdWVJZGVudGlmaWNhdGlvbiwge1xuICAgICAgICAgIC4uLm1hcC5nZXQodW5pcXVlSWRlbnRpZmljYXRpb24pLFxuICAgICAgICAgIC4uLnJlZixcbiAgICAgICAgfSk7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICBtYXAuc2V0KHVuaXF1ZUlkZW50aWZpY2F0aW9uLCByZWYpO1xuICAgICAgfVxuICAgIH0pO1xuICB9O1xuXG4gIC8vIEFkZCBvYmplY3RzIGZyb20gYm90aCBhcnJheXMgdG8gdGhlIG1hcFxuICBpZiAoYSkge1xuICAgIGFkZFRvTWFwKGEpO1xuICB9XG4gIGlmIChiKSB7XG4gICAgYWRkVG9NYXAoYik7XG4gIH1cblxuICByZXR1cm4gWy4uLm1hcC52YWx1ZXMoKV07XG59XG5cbmZ1bmN0aW9uIG1lcmdlVXBsb2FkKFxuICBhOiBVcGxvYWRDb25maWcgfCB1bmRlZmluZWQsXG4gIGI6IFVwbG9hZENvbmZpZyB8IHVuZGVmaW5lZCxcbik6IFBpY2s8Q29yZUNvbmZpZywgJ3VwbG9hZCc+IHtcbiAgaWYgKCFhICYmICFiKSB7XG4gICAgcmV0dXJuIHt9O1xuICB9XG5cbiAgaWYgKGEpIHtcbiAgICByZXR1cm4gYiA/IHsgdXBsb2FkOiB7IC4uLmEsIC4uLmIgfSB9IDoge307XG4gIH0gZWxzZSB7XG4gICAgcmV0dXJuIHsgdXBsb2FkOiBiIH07XG4gIH1cbn1cbiIsICJjb25zdCBfX2luamVjdGVkX2ZpbGVuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3V0aWxzL3NyYy9saWIvcHJvZ3Jlc3MudHNcIjtjb25zdCBfX2luamVjdGVkX2Rpcm5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvdXRpbHMvc3JjL2xpYlwiO2NvbnN0IF9faW5qZWN0ZWRfaW1wb3J0X21ldGFfdXJsX18gPSBcImZpbGU6Ly8vaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy91dGlscy9zcmMvbGliL3Byb2dyZXNzLnRzXCI7aW1wb3J0IHsgYmxhY2ssIGJvbGQsIGdyYXksIGdyZWVuIH0gZnJvbSAnYW5zaXMnO1xuaW1wb3J0IHsgdHlwZSBDdG9yT3B0aW9ucywgTXVsdGlQcm9ncmVzc0JhcnMgfSBmcm9tICdtdWx0aS1wcm9ncmVzcy1iYXJzJztcbmltcG9ydCB7IFRFUk1JTkFMX1dJRFRIIH0gZnJvbSAnLi9yZXBvcnRzL2NvbnN0YW50cy5qcyc7XG5cbnR5cGUgQmFyU3R5bGVzID0gJ2FjdGl2ZScgfCAnZG9uZScgfCAnaWRsZSc7XG50eXBlIFN0YXR1c1N0eWxlcyA9IFJlY29yZDxCYXJTdHlsZXMsIChzOiBzdHJpbmcpID0+IHN0cmluZz47XG5leHBvcnQgY29uc3QgYmFyU3R5bGVzOiBTdGF0dXNTdHlsZXMgPSB7XG4gIGFjdGl2ZTogKHM6IHN0cmluZykgPT4gZ3JlZW4ocyksXG4gIGRvbmU6IChzOiBzdHJpbmcpID0+IGdyYXkocyksXG4gIGlkbGU6IChzOiBzdHJpbmcpID0+IGdyYXkocyksXG59O1xuXG5leHBvcnQgY29uc3QgbWVzc2FnZVN0eWxlczogU3RhdHVzU3R5bGVzID0ge1xuICBhY3RpdmU6IChzOiBzdHJpbmcpID0+IGJsYWNrKHMpLFxuICBkb25lOiAoczogc3RyaW5nKSA9PiBib2xkLmdyZWVuKHMpLFxuICBpZGxlOiAoczogc3RyaW5nKSA9PiBncmF5KHMpLFxufTtcblxuZXhwb3J0IHR5cGUgUHJvZ3Jlc3NCYXIgPSB7XG4gIC8vIEBUT0RPIGZpbmQgYmV0dGVyIG5hbWluZ1xuICBpbmNyZW1lbnRJblN0ZXBzOiAobnVtU3RlcHM6IG51bWJlcikgPT4gdm9pZDtcbiAgdXBkYXRlVGl0bGU6ICh0aXRsZTogc3RyaW5nKSA9PiB2b2lkO1xuICBlbmRQcm9ncmVzczogKG1lc3NhZ2U/OiBzdHJpbmcpID0+IHZvaWQ7XG59O1xuXG4vLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgZnVuY3Rpb25hbC9uby1sZXRcbmxldCBtcGI6IE11bHRpUHJvZ3Jlc3NCYXJzO1xuXG5leHBvcnQgZnVuY3Rpb24gZ2V0U2luZ2xldG9uUHJvZ3Jlc3NCYXJzKFxuICBvcHRpb25zPzogUGFydGlhbDxDdG9yT3B0aW9ucz4sXG4pOiBNdWx0aVByb2dyZXNzQmFycyB7XG4gIGlmICghbXBiKSB7XG4gICAgbXBiID0gbmV3IE11bHRpUHJvZ3Jlc3NCYXJzKHtcbiAgICAgIHByb2dyZXNzV2lkdGg6IFRFUk1JTkFMX1dJRFRILFxuICAgICAgaW5pdE1lc3NhZ2U6ICcnLFxuICAgICAgYm9yZGVyOiB0cnVlLFxuICAgICAgLi4ub3B0aW9ucyxcbiAgICB9KTtcbiAgfVxuICByZXR1cm4gbXBiO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gZ2V0UHJvZ3Jlc3NCYXIodGFza05hbWU6IHN0cmluZyk6IFByb2dyZXNzQmFyIHtcbiAgY29uc3QgdGFza3MgPSBnZXRTaW5nbGV0b25Qcm9ncmVzc0JhcnMoKTtcblxuICAvLyBJbml0aWFsaXplIHByb2dyZXNzIGJhciBpZiBub3Qgc2V0XG4gIHRhc2tzLmFkZFRhc2sodGFza05hbWUsIHtcbiAgICB0eXBlOiAncGVyY2VudGFnZScsXG4gICAgcGVyY2VudGFnZTogMCxcbiAgICBtZXNzYWdlOiAnJyxcbiAgICBiYXJUcmFuc2Zvcm1GbjogYmFyU3R5bGVzLmlkbGUsXG4gIH0pO1xuXG4gIHJldHVybiB7XG4gICAgaW5jcmVtZW50SW5TdGVwczogKG51bVBsdWdpbnM6IG51bWJlcikgPT4ge1xuICAgICAgdGFza3MuaW5jcmVtZW50VGFzayh0YXNrTmFtZSwge1xuICAgICAgICBwZXJjZW50YWdlOiAxIC8gbnVtUGx1Z2lucyxcbiAgICAgICAgYmFyVHJhbnNmb3JtRm46IGJhclN0eWxlcy5hY3RpdmUsXG4gICAgICB9KTtcbiAgICB9LFxuICAgIHVwZGF0ZVRpdGxlOiAodGl0bGU6IHN0cmluZykgPT4ge1xuICAgICAgdGFza3MudXBkYXRlVGFzayh0YXNrTmFtZSwge1xuICAgICAgICBtZXNzYWdlOiB0aXRsZSxcbiAgICAgICAgYmFyVHJhbnNmb3JtRm46IGJhclN0eWxlcy5hY3RpdmUsXG4gICAgICB9KTtcbiAgICB9LFxuICAgIGVuZFByb2dyZXNzOiAobWVzc2FnZSA9ICcnKSA9PiB7XG4gICAgICB0YXNrcy5kb25lKHRhc2tOYW1lLCB7XG4gICAgICAgIG1lc3NhZ2U6IG1lc3NhZ2VTdHlsZXMuZG9uZShtZXNzYWdlKSxcbiAgICAgICAgYmFyVHJhbnNmb3JtRm46IGJhclN0eWxlcy5kb25lLFxuICAgICAgfSk7XG4gICAgfSxcbiAgfTtcbn1cbiIsICJjb25zdCBfX2luamVjdGVkX2ZpbGVuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3V0aWxzL3NyYy9saWIvcmVwb3J0cy9nZW5lcmF0ZS1tZC1yZXBvcnQudHNcIjtjb25zdCBfX2luamVjdGVkX2Rpcm5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvdXRpbHMvc3JjL2xpYi9yZXBvcnRzXCI7Y29uc3QgX19pbmplY3RlZF9pbXBvcnRfbWV0YV91cmxfXyA9IFwiZmlsZTovLy9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3V0aWxzL3NyYy9saWIvcmVwb3J0cy9nZW5lcmF0ZS1tZC1yZXBvcnQudHNcIjtpbXBvcnQgeyB0eXBlIElubGluZVRleHQsIE1hcmtkb3duRG9jdW1lbnQsIG1kIH0gZnJvbSAnYnVpbGQtbWQnO1xuaW1wb3J0IHR5cGUgeyBBdWRpdFJlcG9ydCwgSXNzdWUsIFJlcG9ydCB9IGZyb20gJ0Bjb2RlLXB1c2h1cC9tb2RlbHMnO1xuaW1wb3J0IHsgZm9ybWF0RGF0ZSwgZm9ybWF0RHVyYXRpb24gfSBmcm9tICcuLi9mb3JtYXR0aW5nLmpzJztcbmltcG9ydCB7IEhJRVJBUkNIWSB9IGZyb20gJy4uL3RleHQtZm9ybWF0cy9pbmRleC5qcyc7XG5pbXBvcnQge1xuICBGT09URVJfUFJFRklYLFxuICBSRUFETUVfTElOSyxcbiAgUkVQT1JUX0hFQURMSU5FX1RFWFQsXG59IGZyb20gJy4vY29uc3RhbnRzLmpzJztcbmltcG9ydCB7XG4gIGZvcm1hdFNvdXJjZUxpbmUsXG4gIGxpbmtUb0xvY2FsU291cmNlRm9ySWRlLFxuICBtZXRhRGVzY3JpcHRpb24sXG4gIHRhYmxlU2VjdGlvbixcbn0gZnJvbSAnLi9mb3JtYXR0aW5nLmpzJztcbmltcG9ydCB7XG4gIGNhdGVnb3JpZXNEZXRhaWxzU2VjdGlvbixcbiAgY2F0ZWdvcmllc092ZXJ2aWV3U2VjdGlvbixcbn0gZnJvbSAnLi9nZW5lcmF0ZS1tZC1yZXBvcnQtY2F0ZWdveS1zZWN0aW9uLmpzJztcbmltcG9ydCB0eXBlIHsgTWRSZXBvcnRPcHRpb25zLCBTY29yZWRSZXBvcnQgfSBmcm9tICcuL3R5cGVzLmpzJztcbmltcG9ydCB7IGZvcm1hdFJlcG9ydFNjb3JlLCBzY29yZU1hcmtlciwgc2V2ZXJpdHlNYXJrZXIgfSBmcm9tICcuL3V0aWxzLmpzJztcblxuZXhwb3J0IGZ1bmN0aW9uIGF1ZGl0RGV0YWlsc0F1ZGl0VmFsdWUoe1xuICBzY29yZSxcbiAgdmFsdWUsXG4gIGRpc3BsYXlWYWx1ZSxcbn06IEF1ZGl0UmVwb3J0KTogSW5saW5lVGV4dCB7XG4gIHJldHVybiBtZGAke3Njb3JlTWFya2VyKHNjb3JlLCAnc3F1YXJlJyl9ICR7bWQuYm9sZChcbiAgICBTdHJpbmcoZGlzcGxheVZhbHVlID8/IHZhbHVlKSxcbiAgKX0gKHNjb3JlOiAke2Zvcm1hdFJlcG9ydFNjb3JlKHNjb3JlKX0pYDtcbn1cblxuZnVuY3Rpb24gaGFzQ2F0ZWdvcmllcyhcbiAgcmVwb3J0OiBTY29yZWRSZXBvcnQsXG4pOiByZXBvcnQgaXMgU2NvcmVkUmVwb3J0ICYgUmVxdWlyZWQ8UGljazxTY29yZWRSZXBvcnQsICdjYXRlZ29yaWVzJz4+IHtcbiAgcmV0dXJuICEhcmVwb3J0LmNhdGVnb3JpZXMgJiYgcmVwb3J0LmNhdGVnb3JpZXMubGVuZ3RoID4gMDtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGdlbmVyYXRlTWRSZXBvcnQoXG4gIHJlcG9ydDogU2NvcmVkUmVwb3J0LFxuICBvcHRpb25zPzogTWRSZXBvcnRPcHRpb25zLFxuKTogc3RyaW5nIHtcbiAgcmV0dXJuIG5ldyBNYXJrZG93bkRvY3VtZW50KClcbiAgICAuaGVhZGluZyhISUVSQVJDSFkubGV2ZWxfMSwgUkVQT1JUX0hFQURMSU5FX1RFWFQpXG4gICAgLiRjb25jYXQoXG4gICAgICAuLi4oaGFzQ2F0ZWdvcmllcyhyZXBvcnQpXG4gICAgICAgID8gW2NhdGVnb3JpZXNPdmVydmlld1NlY3Rpb24ocmVwb3J0KSwgY2F0ZWdvcmllc0RldGFpbHNTZWN0aW9uKHJlcG9ydCldXG4gICAgICAgIDogW10pLFxuICAgICAgYXVkaXRzU2VjdGlvbihyZXBvcnQsIG9wdGlvbnMpLFxuICAgICAgYWJvdXRTZWN0aW9uKHJlcG9ydCksXG4gICAgKVxuICAgIC5ydWxlKClcbiAgICAucGFyYWdyYXBoKG1kYCR7Rk9PVEVSX1BSRUZJWH0gJHttZC5saW5rKFJFQURNRV9MSU5LLCAnQ29kZSBQdXNoVXAnKX1gKVxuICAgIC50b1N0cmluZygpO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gYXVkaXREZXRhaWxzSXNzdWVzKFxuICBpc3N1ZXM6IElzc3VlW10gPSBbXSxcbiAgb3B0aW9ucz86IE1kUmVwb3J0T3B0aW9ucyxcbik6IE1hcmtkb3duRG9jdW1lbnQgfCBudWxsIHtcbiAgaWYgKGlzc3Vlcy5sZW5ndGggPT09IDApIHtcbiAgICByZXR1cm4gbnVsbDtcbiAgfVxuICByZXR1cm4gbmV3IE1hcmtkb3duRG9jdW1lbnQoKS5oZWFkaW5nKEhJRVJBUkNIWS5sZXZlbF80LCAnSXNzdWVzJykudGFibGUoXG4gICAgW1xuICAgICAgeyBoZWFkaW5nOiAnU2V2ZXJpdHknLCBhbGlnbm1lbnQ6ICdjZW50ZXInIH0sXG4gICAgICB7IGhlYWRpbmc6ICdNZXNzYWdlJywgYWxpZ25tZW50OiAnbGVmdCcgfSxcbiAgICAgIHsgaGVhZGluZzogJ1NvdXJjZSBmaWxlJywgYWxpZ25tZW50OiAnbGVmdCcgfSxcbiAgICAgIHsgaGVhZGluZzogJ0xpbmUocyknLCBhbGlnbm1lbnQ6ICdjZW50ZXInIH0sXG4gICAgXSxcbiAgICBpc3N1ZXMubWFwKCh7IHNldmVyaXR5OiBsZXZlbCwgbWVzc2FnZSwgc291cmNlIH06IElzc3VlKSA9PiB7XG4gICAgICBjb25zdCBzZXZlcml0eSA9IG1kYCR7c2V2ZXJpdHlNYXJrZXIobGV2ZWwpfSAke21kLml0YWxpYyhsZXZlbCl9YDtcblxuICAgICAgaWYgKCFzb3VyY2UpIHtcbiAgICAgICAgcmV0dXJuIFtzZXZlcml0eSwgbWVzc2FnZV07XG4gICAgICB9XG4gICAgICBjb25zdCBmaWxlID0gbGlua1RvTG9jYWxTb3VyY2VGb3JJZGUoc291cmNlLCBvcHRpb25zKTtcbiAgICAgIGlmICghc291cmNlLnBvc2l0aW9uKSB7XG4gICAgICAgIHJldHVybiBbc2V2ZXJpdHksIG1lc3NhZ2UsIGZpbGVdO1xuICAgICAgfVxuICAgICAgY29uc3QgbGluZSA9IGZvcm1hdFNvdXJjZUxpbmUoc291cmNlLnBvc2l0aW9uKTtcbiAgICAgIHJldHVybiBbc2V2ZXJpdHksIG1lc3NhZ2UsIGZpbGUsIGxpbmVdO1xuICAgIH0pLFxuICApO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gYXVkaXREZXRhaWxzKFxuICBhdWRpdDogQXVkaXRSZXBvcnQsXG4gIG9wdGlvbnM/OiBNZFJlcG9ydE9wdGlvbnMsXG4pOiBNYXJrZG93bkRvY3VtZW50IHtcbiAgY29uc3QgeyB0YWJsZSwgaXNzdWVzID0gW10gfSA9IGF1ZGl0LmRldGFpbHMgPz8ge307XG4gIGNvbnN0IGRldGFpbHNWYWx1ZSA9IGF1ZGl0RGV0YWlsc0F1ZGl0VmFsdWUoYXVkaXQpO1xuXG4gIC8vIHVuZGVmaW5lZCBkZXRhaWxzIE9SIGVtcHR5IGRldGFpbHMgKHVuZGVmaW5lZCBpc3N1ZXMgT1IgZW1wdHkgaXNzdWVzIEFORCBlbXB0eSB0YWJsZSlcbiAgaWYgKGlzc3Vlcy5sZW5ndGggPT09IDAgJiYgIXRhYmxlPy5yb3dzLmxlbmd0aCkge1xuICAgIHJldHVybiBuZXcgTWFya2Rvd25Eb2N1bWVudCgpLnBhcmFncmFwaChkZXRhaWxzVmFsdWUpO1xuICB9XG5cbiAgY29uc3QgdGFibGVTZWN0aW9uQ29udGVudCA9IHRhYmxlICYmIHRhYmxlU2VjdGlvbih0YWJsZSk7XG4gIGNvbnN0IGlzc3Vlc1NlY3Rpb25Db250ZW50ID1cbiAgICBpc3N1ZXMubGVuZ3RoID4gMCAmJiBhdWRpdERldGFpbHNJc3N1ZXMoaXNzdWVzLCBvcHRpb25zKTtcblxuICByZXR1cm4gbmV3IE1hcmtkb3duRG9jdW1lbnQoKS5kZXRhaWxzKFxuICAgIGRldGFpbHNWYWx1ZSxcbiAgICBuZXcgTWFya2Rvd25Eb2N1bWVudCgpLiRjb25jYXQodGFibGVTZWN0aW9uQ29udGVudCwgaXNzdWVzU2VjdGlvbkNvbnRlbnQpLFxuICApO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gYXVkaXRzU2VjdGlvbihcbiAgeyBwbHVnaW5zIH06IFBpY2s8U2NvcmVkUmVwb3J0LCAncGx1Z2lucyc+LFxuICBvcHRpb25zPzogTWRSZXBvcnRPcHRpb25zLFxuKTogTWFya2Rvd25Eb2N1bWVudCB7XG4gIHJldHVybiBuZXcgTWFya2Rvd25Eb2N1bWVudCgpXG4gICAgLmhlYWRpbmcoSElFUkFSQ0hZLmxldmVsXzIsICdcdUQ4M0RcdURFRTFcdUZFMEYgQXVkaXRzJylcbiAgICAuJGZvcmVhY2goXG4gICAgICBwbHVnaW5zLmZsYXRNYXAocGx1Z2luID0+XG4gICAgICAgIHBsdWdpbi5hdWRpdHMubWFwKGF1ZGl0ID0+ICh7IC4uLmF1ZGl0LCBwbHVnaW4gfSkpLFxuICAgICAgKSxcbiAgICAgIChkb2MsIHsgcGx1Z2luLCAuLi5hdWRpdCB9KSA9PiB7XG4gICAgICAgIGNvbnN0IGF1ZGl0VGl0bGUgPSBgJHthdWRpdC50aXRsZX0gKCR7cGx1Z2luLnRpdGxlfSlgO1xuICAgICAgICBjb25zdCBkZXRhaWxzQ29udGVudCA9IGF1ZGl0RGV0YWlscyhhdWRpdCwgb3B0aW9ucyk7XG4gICAgICAgIGNvbnN0IGRlc2NyaXB0aW9uQ29udGVudCA9IG1ldGFEZXNjcmlwdGlvbihhdWRpdCk7XG5cbiAgICAgICAgcmV0dXJuIGRvY1xuICAgICAgICAgIC5oZWFkaW5nKEhJRVJBUkNIWS5sZXZlbF8zLCBhdWRpdFRpdGxlKVxuICAgICAgICAgIC4kY29uY2F0KGRldGFpbHNDb250ZW50KVxuICAgICAgICAgIC5wYXJhZ3JhcGgoZGVzY3JpcHRpb25Db250ZW50KTtcbiAgICAgIH0sXG4gICAgKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGFib3V0U2VjdGlvbihcbiAgcmVwb3J0OiBPbWl0PFNjb3JlZFJlcG9ydCwgJ3BhY2thZ2VOYW1lJz4sXG4pOiBNYXJrZG93bkRvY3VtZW50IHtcbiAgY29uc3QgeyBkYXRlLCBwbHVnaW5zIH0gPSByZXBvcnQ7XG4gIHJldHVybiBuZXcgTWFya2Rvd25Eb2N1bWVudCgpXG4gICAgLmhlYWRpbmcoSElFUkFSQ0hZLmxldmVsXzIsICdBYm91dCcpXG4gICAgLnBhcmFncmFwaChcbiAgICAgIG1kYFJlcG9ydCB3YXMgY3JlYXRlZCBieSAke21kLmxpbmsoXG4gICAgICAgIFJFQURNRV9MSU5LLFxuICAgICAgICAnQ29kZSBQdXNoVXAnLFxuICAgICAgKX0gb24gJHtmb3JtYXREYXRlKG5ldyBEYXRlKGRhdGUpKX0uYCxcbiAgICApXG4gICAgLnRhYmxlKC4uLnBsdWdpbk1ldGFUYWJsZSh7IHBsdWdpbnMgfSkpXG4gICAgLnRhYmxlKC4uLnJlcG9ydE1ldGFUYWJsZShyZXBvcnQpKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHBsdWdpbk1ldGFUYWJsZSh7XG4gIHBsdWdpbnMsXG59OiBQaWNrPFJlcG9ydCwgJ3BsdWdpbnMnPik6IFBhcmFtZXRlcnM8TWFya2Rvd25Eb2N1bWVudFsndGFibGUnXT4ge1xuICByZXR1cm4gW1xuICAgIFtcbiAgICAgIHsgaGVhZGluZzogJ1BsdWdpbicsIGFsaWdubWVudDogJ2xlZnQnIH0sXG4gICAgICB7IGhlYWRpbmc6ICdBdWRpdHMnLCBhbGlnbm1lbnQ6ICdjZW50ZXInIH0sXG4gICAgICB7IGhlYWRpbmc6ICdWZXJzaW9uJywgYWxpZ25tZW50OiAnY2VudGVyJyB9LFxuICAgICAgeyBoZWFkaW5nOiAnRHVyYXRpb24nLCBhbGlnbm1lbnQ6ICdyaWdodCcgfSxcbiAgICBdLFxuICAgIHBsdWdpbnMubWFwKCh7IHRpdGxlLCBhdWRpdHMsIHZlcnNpb24gPSAnJywgZHVyYXRpb24gfSkgPT4gW1xuICAgICAgdGl0bGUsXG4gICAgICBhdWRpdHMubGVuZ3RoLnRvU3RyaW5nKCksXG4gICAgICB2ZXJzaW9uICYmIG1kLmNvZGUodmVyc2lvbiksXG4gICAgICBmb3JtYXREdXJhdGlvbihkdXJhdGlvbiksXG4gICAgXSksXG4gIF07XG59XG5cbmV4cG9ydCBmdW5jdGlvbiByZXBvcnRNZXRhVGFibGUoe1xuICBjb21taXQsXG4gIHZlcnNpb24sXG4gIGR1cmF0aW9uLFxuICBwbHVnaW5zLFxuICBjYXRlZ29yaWVzLFxufTogUGljazxcbiAgU2NvcmVkUmVwb3J0LFxuICAnZGF0ZScgfCAnZHVyYXRpb24nIHwgJ3ZlcnNpb24nIHwgJ2NvbW1pdCcgfCAncGx1Z2lucycgfCAnY2F0ZWdvcmllcydcbj4pOiBQYXJhbWV0ZXJzPE1hcmtkb3duRG9jdW1lbnRbJ3RhYmxlJ10+IHtcbiAgcmV0dXJuIFtcbiAgICBbXG4gICAgICB7IGhlYWRpbmc6ICdDb21taXQnLCBhbGlnbm1lbnQ6ICdsZWZ0JyB9LFxuICAgICAgeyBoZWFkaW5nOiAnVmVyc2lvbicsIGFsaWdubWVudDogJ2NlbnRlcicgfSxcbiAgICAgIHsgaGVhZGluZzogJ0R1cmF0aW9uJywgYWxpZ25tZW50OiAncmlnaHQnIH0sXG4gICAgICB7IGhlYWRpbmc6ICdQbHVnaW5zJywgYWxpZ25tZW50OiAnY2VudGVyJyB9LFxuICAgICAgeyBoZWFkaW5nOiAnQ2F0ZWdvcmllcycsIGFsaWdubWVudDogJ2NlbnRlcicgfSxcbiAgICAgIHsgaGVhZGluZzogJ0F1ZGl0cycsIGFsaWdubWVudDogJ2NlbnRlcicgfSxcbiAgICBdLFxuICAgIFtcbiAgICAgIFtcbiAgICAgICAgY29tbWl0ID8gYCR7Y29tbWl0Lm1lc3NhZ2V9ICgke2NvbW1pdC5oYXNofSlgIDogJ04vQScsXG4gICAgICAgIG1kLmNvZGUodmVyc2lvbiksXG4gICAgICAgIGZvcm1hdER1cmF0aW9uKGR1cmF0aW9uKSxcbiAgICAgICAgcGx1Z2lucy5sZW5ndGgudG9TdHJpbmcoKSxcbiAgICAgICAgKGNhdGVnb3JpZXM/Lmxlbmd0aCA/PyAwKS50b1N0cmluZygpLFxuICAgICAgICBwbHVnaW5zLnJlZHVjZSgoYWNjLCB7IGF1ZGl0cyB9KSA9PiBhY2MgKyBhdWRpdHMubGVuZ3RoLCAwKS50b1N0cmluZygpLFxuICAgICAgXSxcbiAgICBdLFxuICBdO1xufVxuIiwgImNvbnN0IF9faW5qZWN0ZWRfZmlsZW5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvdXRpbHMvc3JjL2xpYi9yZXBvcnRzL2Zvcm1hdHRpbmcudHNcIjtjb25zdCBfX2luamVjdGVkX2Rpcm5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvdXRpbHMvc3JjL2xpYi9yZXBvcnRzXCI7Y29uc3QgX19pbmplY3RlZF9pbXBvcnRfbWV0YV91cmxfXyA9IFwiZmlsZTovLy9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3V0aWxzL3NyYy9saWIvcmVwb3J0cy9mb3JtYXR0aW5nLnRzXCI7aW1wb3J0IHtcbiAgdHlwZSBIZWFkaW5nTGV2ZWwsXG4gIHR5cGUgSW5saW5lVGV4dCxcbiAgTWFya2Rvd25Eb2N1bWVudCxcbiAgbWQsXG59IGZyb20gJ2J1aWxkLW1kJztcbmltcG9ydCBwYXRoIGZyb20gJ25vZGU6cGF0aCc7XG5pbXBvcnQgdHlwZSB7XG4gIEF1ZGl0UmVwb3J0LFxuICBTb3VyY2VGaWxlTG9jYXRpb24sXG4gIFRhYmxlLFxufSBmcm9tICdAY29kZS1wdXNodXAvbW9kZWxzJztcbmltcG9ydCB7IEhJRVJBUkNIWSB9IGZyb20gJy4uL3RleHQtZm9ybWF0cy9pbmRleC5qcyc7XG5pbXBvcnQge1xuICBjb2x1bW5zVG9TdHJpbmdBcnJheSxcbiAgZ2V0Q29sdW1uQWxpZ25tZW50cyxcbiAgcm93VG9TdHJpbmdBcnJheSxcbn0gZnJvbSAnLi4vdGV4dC1mb3JtYXRzL3RhYmxlLmpzJztcbmltcG9ydCB7XG4gIGdldEVudmlyb25tZW50VHlwZSxcbiAgZ2V0R2l0SHViQmFzZVVybCxcbiAgZ2V0R2l0TGFiQmFzZVVybCxcbn0gZnJvbSAnLi9lbnZpcm9ubWVudC10eXBlLmpzJztcbmltcG9ydCB0eXBlIHsgTWRSZXBvcnRPcHRpb25zIH0gZnJvbSAnLi90eXBlcy5qcyc7XG5cbmV4cG9ydCBmdW5jdGlvbiB0YWJsZVNlY3Rpb24oXG4gIHRhYmxlRGF0YTogVGFibGUsXG4gIG9wdGlvbnM/OiB7XG4gICAgbGV2ZWw/OiBIZWFkaW5nTGV2ZWw7XG4gIH0sXG4pOiBNYXJrZG93bkRvY3VtZW50IHwgbnVsbCB7XG4gIGlmICh0YWJsZURhdGEucm93cy5sZW5ndGggPT09IDApIHtcbiAgICByZXR1cm4gbnVsbDtcbiAgfVxuICBjb25zdCB7IGxldmVsID0gSElFUkFSQ0hZLmxldmVsXzQgfSA9IG9wdGlvbnMgPz8ge307XG4gIGNvbnN0IGNvbHVtbnMgPSBjb2x1bW5zVG9TdHJpbmdBcnJheSh0YWJsZURhdGEpO1xuICBjb25zdCBhbGlnbm1lbnRzID0gZ2V0Q29sdW1uQWxpZ25tZW50cyh0YWJsZURhdGEpO1xuICBjb25zdCByb3dzID0gcm93VG9TdHJpbmdBcnJheSh0YWJsZURhdGEpO1xuICByZXR1cm4gbmV3IE1hcmtkb3duRG9jdW1lbnQoKS5oZWFkaW5nKGxldmVsLCB0YWJsZURhdGEudGl0bGUpLnRhYmxlKFxuICAgIGNvbHVtbnMubWFwKChoZWFkaW5nLCBpKSA9PiB7XG4gICAgICBjb25zdCBhbGlnbm1lbnQgPSBhbGlnbm1lbnRzW2ldO1xuICAgICAgaWYgKGFsaWdubWVudCkge1xuICAgICAgICByZXR1cm4geyBoZWFkaW5nLCBhbGlnbm1lbnQgfTtcbiAgICAgIH1cbiAgICAgIHJldHVybiBoZWFkaW5nO1xuICAgIH0pLFxuICAgIHJvd3MsXG4gICk7XG59XG5cbi8vIEBUT0RPIGV4dHJhY3QgYFBpY2s8QXVkaXRSZXBvcnQsICdkb2NzVXJsJyB8ICdkZXNjcmlwdGlvbic+YCB0byBhIHJldXNhYmxlIHNjaGVtYSBhbmQgdHlwZVxuZXhwb3J0IGZ1bmN0aW9uIG1ldGFEZXNjcmlwdGlvbihcbiAgYXVkaXQ6IFBpY2s8QXVkaXRSZXBvcnQsICdkb2NzVXJsJyB8ICdkZXNjcmlwdGlvbic+LFxuKTogSW5saW5lVGV4dCB7XG4gIGNvbnN0IGRvY3NVcmwgPSBhdWRpdC5kb2NzVXJsO1xuICBjb25zdCBkZXNjcmlwdGlvbiA9IGF1ZGl0LmRlc2NyaXB0aW9uPy50cmltKCk7XG4gIGlmIChkb2NzVXJsKSB7XG4gICAgY29uc3QgZG9jc0xpbmsgPSBtZC5saW5rKGRvY3NVcmwsICdcdUQ4M0RcdURDRDYgRG9jcycpO1xuICAgIGlmICghZGVzY3JpcHRpb24pIHtcbiAgICAgIHJldHVybiBkb2NzTGluaztcbiAgICB9XG4gICAgY29uc3QgcGFyc2VkRGVzY3JpcHRpb24gPSBkZXNjcmlwdGlvbi5lbmRzV2l0aCgnYGBgJylcbiAgICAgID8gYCR7ZGVzY3JpcHRpb259XFxuXFxuYFxuICAgICAgOiBgJHtkZXNjcmlwdGlvbn0gYDtcbiAgICByZXR1cm4gbWRgJHtwYXJzZWREZXNjcmlwdGlvbn0ke2RvY3NMaW5rfWA7XG4gIH1cbiAgaWYgKGRlc2NyaXB0aW9uICYmIGRlc2NyaXB0aW9uLnRyaW0oKS5sZW5ndGggPiAwKSB7XG4gICAgcmV0dXJuIGRlc2NyaXB0aW9uO1xuICB9XG4gIHJldHVybiAnJztcbn1cblxuLyoqXG4gKiBMaW5rIHRvIGxvY2FsIHNvdXJjZSBmb3IgSURFXG4gKiBAcGFyYW0gc291cmNlXG4gKiBAcGFyYW0gcmVwb3J0TG9jYXRpb25cbiAqXG4gKiBAZXhhbXBsZVxuICogbGlua1RvTG9jYWxTb3VyY2VJbklkZSh7IGZpbGU6ICdzcmMvaW5kZXgudHMnIH0sIHsgb3V0cHV0RGlyOiAnLmNvZGUtcHVzaHVwJyB9KSAvLyBbYHNyYy9pbmRleC50c2BdKC4uL3NyYy9pbmRleC50cylcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGxpbmtUb0xvY2FsU291cmNlRm9ySWRlKFxuICBzb3VyY2U6IFNvdXJjZUZpbGVMb2NhdGlvbixcbiAgb3B0aW9ucz86IFBpY2s8TWRSZXBvcnRPcHRpb25zLCAnb3V0cHV0RGlyJz4sXG4pOiBJbmxpbmVUZXh0IHtcbiAgY29uc3QgeyBmaWxlLCBwb3NpdGlvbiB9ID0gc291cmNlO1xuICBjb25zdCB7IG91dHB1dERpciB9ID0gb3B0aW9ucyA/PyB7fTtcblxuICAvLyBOT1QgbGlua2FibGVcbiAgaWYgKCFvdXRwdXREaXIpIHtcbiAgICByZXR1cm4gbWQuY29kZShmaWxlKTtcbiAgfVxuXG4gIHJldHVybiBtZC5saW5rKGZvcm1hdEZpbGVMaW5rKGZpbGUsIHBvc2l0aW9uLCBvdXRwdXREaXIpLCBtZC5jb2RlKGZpbGUpKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGZvcm1hdFNvdXJjZUxpbmUoXG4gIHBvc2l0aW9uOiBTb3VyY2VGaWxlTG9jYXRpb25bJ3Bvc2l0aW9uJ10sXG4pOiBzdHJpbmcge1xuICBpZiAoIXBvc2l0aW9uKSB7XG4gICAgcmV0dXJuICcnO1xuICB9XG4gIGNvbnN0IHsgc3RhcnRMaW5lLCBlbmRMaW5lIH0gPSBwb3NpdGlvbjtcbiAgcmV0dXJuIGVuZExpbmUgJiYgc3RhcnRMaW5lICE9PSBlbmRMaW5lXG4gICAgPyBgJHtzdGFydExpbmV9LSR7ZW5kTGluZX1gXG4gICAgOiBgJHtzdGFydExpbmV9YDtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGZvcm1hdEdpdEh1YkxpbmsoXG4gIGZpbGU6IHN0cmluZyxcbiAgcG9zaXRpb246IFNvdXJjZUZpbGVMb2NhdGlvblsncG9zaXRpb24nXSxcbik6IHN0cmluZyB7XG4gIGNvbnN0IGJhc2VVcmwgPSBnZXRHaXRIdWJCYXNlVXJsKCk7XG4gIGlmICghcG9zaXRpb24pIHtcbiAgICByZXR1cm4gYCR7YmFzZVVybH0vJHtmaWxlfWA7XG4gIH1cbiAgY29uc3QgeyBzdGFydExpbmUsIGVuZExpbmUsIHN0YXJ0Q29sdW1uLCBlbmRDb2x1bW4gfSA9IHBvc2l0aW9uO1xuICBjb25zdCBzdGFydCA9IHN0YXJ0Q29sdW1uID8gYEwke3N0YXJ0TGluZX1DJHtzdGFydENvbHVtbn1gIDogYEwke3N0YXJ0TGluZX1gO1xuICBjb25zdCBlbmQgPSBlbmRMaW5lXG4gICAgPyBlbmRDb2x1bW5cbiAgICAgID8gYEwke2VuZExpbmV9QyR7ZW5kQ29sdW1ufWBcbiAgICAgIDogYEwke2VuZExpbmV9YFxuICAgIDogJyc7XG4gIGNvbnN0IGxpbmVSYW5nZSA9IGVuZCAmJiBzdGFydCAhPT0gZW5kID8gYCR7c3RhcnR9LSR7ZW5kfWAgOiBzdGFydDtcbiAgcmV0dXJuIGAke2Jhc2VVcmx9LyR7ZmlsZX0jJHtsaW5lUmFuZ2V9YDtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGZvcm1hdEdpdExhYkxpbmsoXG4gIGZpbGU6IHN0cmluZyxcbiAgcG9zaXRpb246IFNvdXJjZUZpbGVMb2NhdGlvblsncG9zaXRpb24nXSxcbik6IHN0cmluZyB7XG4gIGNvbnN0IGJhc2VVcmwgPSBnZXRHaXRMYWJCYXNlVXJsKCk7XG4gIGlmICghcG9zaXRpb24pIHtcbiAgICByZXR1cm4gYCR7YmFzZVVybH0vJHtmaWxlfWA7XG4gIH1cbiAgY29uc3QgeyBzdGFydExpbmUsIGVuZExpbmUgfSA9IHBvc2l0aW9uO1xuICBjb25zdCBsaW5lUmFuZ2UgPVxuICAgIGVuZExpbmUgJiYgc3RhcnRMaW5lICE9PSBlbmRMaW5lID8gYCR7c3RhcnRMaW5lfS0ke2VuZExpbmV9YCA6IHN0YXJ0TGluZTtcbiAgcmV0dXJuIGAke2Jhc2VVcmx9LyR7ZmlsZX0jTCR7bGluZVJhbmdlfWA7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBmb3JtYXRGaWxlTGluayhcbiAgZmlsZTogc3RyaW5nLFxuICBwb3NpdGlvbjogU291cmNlRmlsZUxvY2F0aW9uWydwb3NpdGlvbiddLFxuICBvdXRwdXREaXI6IHN0cmluZyxcbik6IHN0cmluZyB7XG4gIGNvbnN0IHJlbGF0aXZlUGF0aCA9IHBhdGgucG9zaXgucmVsYXRpdmUob3V0cHV0RGlyLCBmaWxlKTtcbiAgY29uc3QgZW52ID0gZ2V0RW52aXJvbm1lbnRUeXBlKCk7XG5cbiAgc3dpdGNoIChlbnYpIHtcbiAgICBjYXNlICd2c2NvZGUnOlxuICAgICAgcmV0dXJuIHBvc2l0aW9uID8gYCR7cmVsYXRpdmVQYXRofSNMJHtwb3NpdGlvbi5zdGFydExpbmV9YCA6IHJlbGF0aXZlUGF0aDtcbiAgICBjYXNlICdnaXRodWInOlxuICAgICAgcmV0dXJuIGZvcm1hdEdpdEh1YkxpbmsoZmlsZSwgcG9zaXRpb24pO1xuICAgIGNhc2UgJ2dpdGxhYic6XG4gICAgICByZXR1cm4gZm9ybWF0R2l0TGFiTGluayhmaWxlLCBwb3NpdGlvbik7XG4gICAgZGVmYXVsdDpcbiAgICAgIHJldHVybiByZWxhdGl2ZVBhdGg7XG4gIH1cbn1cbiIsICJjb25zdCBfX2luamVjdGVkX2ZpbGVuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3V0aWxzL3NyYy9saWIvcmVwb3J0cy9nZW5lcmF0ZS1tZC1yZXBvcnQtY2F0ZWdveS1zZWN0aW9uLnRzXCI7Y29uc3QgX19pbmplY3RlZF9kaXJuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3V0aWxzL3NyYy9saWIvcmVwb3J0c1wiO2NvbnN0IF9faW5qZWN0ZWRfaW1wb3J0X21ldGFfdXJsX18gPSBcImZpbGU6Ly8vaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy91dGlscy9zcmMvbGliL3JlcG9ydHMvZ2VuZXJhdGUtbWQtcmVwb3J0LWNhdGVnb3ktc2VjdGlvbi50c1wiO2ltcG9ydCB7IHR5cGUgSW5saW5lVGV4dCwgTWFya2Rvd25Eb2N1bWVudCwgbWQgfSBmcm9tICdidWlsZC1tZCc7XG5pbXBvcnQgdHlwZSB7IEF1ZGl0UmVwb3J0IH0gZnJvbSAnQGNvZGUtcHVzaHVwL21vZGVscyc7XG5pbXBvcnQgeyBzbHVnaWZ5IH0gZnJvbSAnLi4vZm9ybWF0dGluZy5qcyc7XG5pbXBvcnQgeyBISUVSQVJDSFkgfSBmcm9tICcuLi90ZXh0LWZvcm1hdHMvaW5kZXguanMnO1xuaW1wb3J0IHsgbWV0YURlc2NyaXB0aW9uIH0gZnJvbSAnLi9mb3JtYXR0aW5nLmpzJztcbmltcG9ydCB7IGdldFNvcnRhYmxlQXVkaXRCeVJlZiwgZ2V0U29ydGFibGVHcm91cEJ5UmVmIH0gZnJvbSAnLi9zb3J0aW5nLmpzJztcbmltcG9ydCB0eXBlIHsgU2NvcmVkR3JvdXAsIFNjb3JlZFJlcG9ydCB9IGZyb20gJy4vdHlwZXMuanMnO1xuaW1wb3J0IHtcbiAgY291bnRDYXRlZ29yeUF1ZGl0cyxcbiAgZm9ybWF0UmVwb3J0U2NvcmUsXG4gIGdldFBsdWdpbk5hbWVGcm9tU2x1ZyxcbiAgc2NvcmVNYXJrZXIsXG4gIHRhcmdldFNjb3JlSWNvbixcbn0gZnJvbSAnLi91dGlscy5qcyc7XG5cbmV4cG9ydCBmdW5jdGlvbiBjYXRlZ29yaWVzT3ZlcnZpZXdTZWN0aW9uKFxuICByZXBvcnQ6IFJlcXVpcmVkPFBpY2s8U2NvcmVkUmVwb3J0LCAncGx1Z2lucycgfCAnY2F0ZWdvcmllcyc+Pixcbik6IE1hcmtkb3duRG9jdW1lbnQge1xuICBjb25zdCB7IGNhdGVnb3JpZXMsIHBsdWdpbnMgfSA9IHJlcG9ydDtcbiAgcmV0dXJuIG5ldyBNYXJrZG93bkRvY3VtZW50KCkudGFibGUoXG4gICAgW1xuICAgICAgeyBoZWFkaW5nOiAnXHVEODNDXHVERkY3IENhdGVnb3J5JywgYWxpZ25tZW50OiAnbGVmdCcgfSxcbiAgICAgIHsgaGVhZGluZzogJ1x1MkI1MCBTY29yZScsIGFsaWdubWVudDogJ2NlbnRlcicgfSxcbiAgICAgIHsgaGVhZGluZzogJ1x1RDgzRFx1REVFMSBBdWRpdHMnLCBhbGlnbm1lbnQ6ICdjZW50ZXInIH0sXG4gICAgXSxcbiAgICBjYXRlZ29yaWVzLm1hcCgoeyB0aXRsZSwgcmVmcywgc2NvcmUsIGlzQmluYXJ5IH0pID0+IFtcbiAgICAgIC8vIEBUT0RPIHJlZmFjdG9yIGBpc0JpbmFyeTogYm9vbGVhbmAgdG8gYHRhcmdldFNjb3JlOiBudW1iZXJgICM3MTNcbiAgICAgIC8vIFRoZSBoZWFkaW5nIFwiSURcIiBpcyBpbmZlcnJlZCBmcm9tIHRoZSBoZWFkaW5nIHRleHQgaW4gTWFya2Rvd24uXG4gICAgICBtZC5saW5rKGAjJHtzbHVnaWZ5KHRpdGxlKX1gLCB0aXRsZSksXG4gICAgICBtZGAke3Njb3JlTWFya2VyKHNjb3JlKX0gJHttZC5ib2xkKFxuICAgICAgICBmb3JtYXRSZXBvcnRTY29yZShzY29yZSksXG4gICAgICApfSR7YmluYXJ5SWNvblN1ZmZpeChzY29yZSwgaXNCaW5hcnkpfWAsXG4gICAgICBjb3VudENhdGVnb3J5QXVkaXRzKHJlZnMsIHBsdWdpbnMpLnRvU3RyaW5nKCksXG4gICAgXSksXG4gICk7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBjYXRlZ29yaWVzRGV0YWlsc1NlY3Rpb24oXG4gIHJlcG9ydDogUmVxdWlyZWQ8UGljazxTY29yZWRSZXBvcnQsICdwbHVnaW5zJyB8ICdjYXRlZ29yaWVzJz4+LFxuKTogTWFya2Rvd25Eb2N1bWVudCB7XG4gIGNvbnN0IHsgY2F0ZWdvcmllcywgcGx1Z2lucyB9ID0gcmVwb3J0O1xuXG4gIHJldHVybiBuZXcgTWFya2Rvd25Eb2N1bWVudCgpXG4gICAgLmhlYWRpbmcoSElFUkFSQ0hZLmxldmVsXzIsICdcdUQ4M0NcdURGRjcgQ2F0ZWdvcmllcycpXG4gICAgLiRmb3JlYWNoKGNhdGVnb3JpZXMsIChkb2MsIGNhdGVnb3J5KSA9PlxuICAgICAgZG9jXG4gICAgICAgIC5oZWFkaW5nKEhJRVJBUkNIWS5sZXZlbF8zLCBjYXRlZ29yeS50aXRsZSlcbiAgICAgICAgLnBhcmFncmFwaChtZXRhRGVzY3JpcHRpb24oY2F0ZWdvcnkpKVxuICAgICAgICAucGFyYWdyYXBoKFxuICAgICAgICAgIG1kYCR7c2NvcmVNYXJrZXIoY2F0ZWdvcnkuc2NvcmUpfSBTY29yZTogJHttZC5ib2xkKFxuICAgICAgICAgICAgZm9ybWF0UmVwb3J0U2NvcmUoY2F0ZWdvcnkuc2NvcmUpLFxuICAgICAgICAgICl9JHtiaW5hcnlJY29uU3VmZml4KGNhdGVnb3J5LnNjb3JlLCBjYXRlZ29yeS5pc0JpbmFyeSl9YCxcbiAgICAgICAgKVxuICAgICAgICAubGlzdChcbiAgICAgICAgICBjYXRlZ29yeS5yZWZzLm1hcChyZWYgPT4ge1xuICAgICAgICAgICAgLy8gQWRkIGdyb3VwIGRldGFpbHNcbiAgICAgICAgICAgIGlmIChyZWYudHlwZSA9PT0gJ2dyb3VwJykge1xuICAgICAgICAgICAgICBjb25zdCBncm91cCA9IGdldFNvcnRhYmxlR3JvdXBCeVJlZihyZWYsIHBsdWdpbnMpO1xuICAgICAgICAgICAgICBjb25zdCBncm91cEF1ZGl0cyA9IGdyb3VwLnJlZnMubWFwKGdyb3VwUmVmID0+XG4gICAgICAgICAgICAgICAgZ2V0U29ydGFibGVBdWRpdEJ5UmVmKFxuICAgICAgICAgICAgICAgICAgeyAuLi5ncm91cFJlZiwgcGx1Z2luOiBncm91cC5wbHVnaW4sIHR5cGU6ICdhdWRpdCcgfSxcbiAgICAgICAgICAgICAgICAgIHBsdWdpbnMsXG4gICAgICAgICAgICAgICAgKSxcbiAgICAgICAgICAgICAgKTtcbiAgICAgICAgICAgICAgY29uc3QgcGx1Z2luVGl0bGUgPSBnZXRQbHVnaW5OYW1lRnJvbVNsdWcocmVmLnBsdWdpbiwgcGx1Z2lucyk7XG4gICAgICAgICAgICAgIHJldHVybiBjYXRlZ29yeUdyb3VwSXRlbShncm91cCwgZ3JvdXBBdWRpdHMsIHBsdWdpblRpdGxlKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIC8vIEFkZCBhdWRpdCBkZXRhaWxzXG4gICAgICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgICAgY29uc3QgYXVkaXQgPSBnZXRTb3J0YWJsZUF1ZGl0QnlSZWYocmVmLCBwbHVnaW5zKTtcbiAgICAgICAgICAgICAgY29uc3QgcGx1Z2luVGl0bGUgPSBnZXRQbHVnaW5OYW1lRnJvbVNsdWcocmVmLnBsdWdpbiwgcGx1Z2lucyk7XG4gICAgICAgICAgICAgIHJldHVybiBjYXRlZ29yeVJlZihhdWRpdCwgcGx1Z2luVGl0bGUpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgIH0pLFxuICAgICAgICApLFxuICAgICk7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBjYXRlZ29yeVJlZihcbiAgeyB0aXRsZSwgc2NvcmUsIHZhbHVlLCBkaXNwbGF5VmFsdWUgfTogQXVkaXRSZXBvcnQsXG4gIHBsdWdpblRpdGxlOiBzdHJpbmcsXG4pOiBJbmxpbmVUZXh0IHtcbiAgY29uc3QgYXVkaXRUaXRsZUFzTGluayA9IG1kLmxpbmsoXG4gICAgYCMke3NsdWdpZnkodGl0bGUpfS0ke3NsdWdpZnkocGx1Z2luVGl0bGUpfWAsXG4gICAgdGl0bGUsXG4gICk7XG4gIGNvbnN0IG1hcmtlciA9IHNjb3JlTWFya2VyKHNjb3JlLCAnc3F1YXJlJyk7XG4gIHJldHVybiBtZGAke21hcmtlcn0gJHthdWRpdFRpdGxlQXNMaW5rfSAoJHttZC5pdGFsaWMoXG4gICAgcGx1Z2luVGl0bGUsXG4gICl9KSAtICR7bWQuYm9sZCgoZGlzcGxheVZhbHVlIHx8IHZhbHVlKS50b1N0cmluZygpKX1gO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gY2F0ZWdvcnlHcm91cEl0ZW0oXG4gIHsgc2NvcmUgPSAwLCB0aXRsZSB9OiBTY29yZWRHcm91cCxcbiAgZ3JvdXBBdWRpdHM6IEF1ZGl0UmVwb3J0W10sXG4gIHBsdWdpblRpdGxlOiBzdHJpbmcsXG4pOiBJbmxpbmVUZXh0IHtcbiAgY29uc3QgZ3JvdXBUaXRsZSA9IG1kYCR7c2NvcmVNYXJrZXIoc2NvcmUpfSAke3RpdGxlfSAoJHttZC5pdGFsaWMoXG4gICAgcGx1Z2luVGl0bGUsXG4gICl9KWA7XG5cbiAgY29uc3QgYXVkaXRzTGlzdCA9IG1kLmxpc3QoXG4gICAgZ3JvdXBBdWRpdHMubWFwKFxuICAgICAgKHsgdGl0bGU6IGF1ZGl0VGl0bGUsIHNjb3JlOiBhdWRpdFNjb3JlLCB2YWx1ZSwgZGlzcGxheVZhbHVlIH0pID0+IHtcbiAgICAgICAgY29uc3QgYXVkaXRUaXRsZUxpbmsgPSBtZC5saW5rKFxuICAgICAgICAgIGAjJHtzbHVnaWZ5KGF1ZGl0VGl0bGUpfS0ke3NsdWdpZnkocGx1Z2luVGl0bGUpfWAsXG4gICAgICAgICAgYXVkaXRUaXRsZSxcbiAgICAgICAgKTtcbiAgICAgICAgY29uc3QgbWFya2VyID0gc2NvcmVNYXJrZXIoYXVkaXRTY29yZSwgJ3NxdWFyZScpO1xuICAgICAgICByZXR1cm4gbWRgJHttYXJrZXJ9ICR7YXVkaXRUaXRsZUxpbmt9IC0gJHttZC5ib2xkKFxuICAgICAgICAgIFN0cmluZyhkaXNwbGF5VmFsdWUgPz8gdmFsdWUpLFxuICAgICAgICApfWA7XG4gICAgICB9LFxuICAgICksXG4gICk7XG5cbiAgcmV0dXJuIG1kYCR7Z3JvdXBUaXRsZX0ke2F1ZGl0c0xpc3R9YDtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGJpbmFyeUljb25TdWZmaXgoXG4gIHNjb3JlOiBudW1iZXIsXG4gIGlzQmluYXJ5OiBib29sZWFuIHwgdW5kZWZpbmVkLFxuKTogc3RyaW5nIHtcbiAgLy8gQFRPRE8gcmVmYWN0b3IgYGlzQmluYXJ5OiBib29sZWFuYCB0byBgdGFyZ2V0U2NvcmU6IG51bWJlcmAgIzcxM1xuICByZXR1cm4gdGFyZ2V0U2NvcmVJY29uKHNjb3JlLCBpc0JpbmFyeSA/IDEgOiB1bmRlZmluZWQsIHsgcHJlZml4OiAnICcgfSk7XG59XG4iLCAiY29uc3QgX19pbmplY3RlZF9maWxlbmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy91dGlscy9zcmMvbGliL3JlcG9ydHMvZ2VuZXJhdGUtbWQtcmVwb3J0cy1kaWZmLnRzXCI7Y29uc3QgX19pbmplY3RlZF9kaXJuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3V0aWxzL3NyYy9saWIvcmVwb3J0c1wiO2NvbnN0IF9faW5qZWN0ZWRfaW1wb3J0X21ldGFfdXJsX18gPSBcImZpbGU6Ly8vaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy91dGlscy9zcmMvbGliL3JlcG9ydHMvZ2VuZXJhdGUtbWQtcmVwb3J0cy1kaWZmLnRzXCI7aW1wb3J0IHtcbiAgdHlwZSBIZWFkaW5nTGV2ZWwsXG4gIE1hcmtkb3duRG9jdW1lbnQsXG4gIHR5cGUgVGFibGVDb2x1bW5PYmplY3QsXG4gIHR5cGUgVGFibGVSb3csXG4gIG1kLFxufSBmcm9tICdidWlsZC1tZCc7XG5pbXBvcnQgdHlwZSB7IFJlcG9ydHNEaWZmIH0gZnJvbSAnQGNvZGUtcHVzaHVwL21vZGVscyc7XG5pbXBvcnQgeyBISUVSQVJDSFkgfSBmcm9tICcuLi90ZXh0LWZvcm1hdHMvaW5kZXguanMnO1xuaW1wb3J0IHsgdG9BcnJheSB9IGZyb20gJy4uL3RyYW5zZm9ybS5qcyc7XG5pbXBvcnQgdHlwZSB7IFdpdGhSZXF1aXJlZCB9IGZyb20gJy4uL3R5cGVzLmpzJztcbmltcG9ydCB7XG4gIGNoYW5nZXNUb0RpZmZPdXRjb21lcyxcbiAgY29tcGFyZURpZmZzQnksXG4gIGNyZWF0ZUdyb3Vwc09yQXVkaXRzRGV0YWlscyxcbiAgZm9ybWF0UG9ydGFsTGluayxcbiAgZm9ybWF0UmVwb3J0T3V0Y29tZSxcbiAgZm9ybWF0VGl0bGUsXG4gIGdldERpZmZDaGFuZ2VzLFxuICBtZXJnZURpZmZPdXRjb21lcyxcbiAgc29ydENoYW5nZXMsXG4gIHN1bW1hcml6ZURpZmZPdXRjb21lcyxcbiAgc3VtbWFyaXplVW5jaGFuZ2VkLFxufSBmcm9tICcuL2dlbmVyYXRlLW1kLXJlcG9ydHMtZGlmZi11dGlscy5qcyc7XG5pbXBvcnQgdHlwZSB7IERpZmZPdXRjb21lIH0gZnJvbSAnLi90eXBlcy5qcyc7XG5pbXBvcnQge1xuICBmb3JtYXRTY29yZUNoYW5nZSxcbiAgZm9ybWF0U2NvcmVXaXRoQ29sb3IsXG4gIGZvcm1hdFZhbHVlQ2hhbmdlLFxuICBzY29yZU1hcmtlcixcbn0gZnJvbSAnLi91dGlscy5qcyc7XG5cbmV4cG9ydCBmdW5jdGlvbiBnZW5lcmF0ZU1kUmVwb3J0c0RpZmYoZGlmZjogUmVwb3J0c0RpZmYpOiBzdHJpbmcge1xuICByZXR1cm4gbmV3IE1hcmtkb3duRG9jdW1lbnQoKVxuICAgIC4kY29uY2F0KFxuICAgICAgY3JlYXRlRGlmZkhlYWRlclNlY3Rpb24oZGlmZiksXG4gICAgICBjcmVhdGVEaWZmQ2F0ZWdvcmllc1NlY3Rpb24oZGlmZiksXG4gICAgICBjcmVhdGVEaWZmRGV0YWlsc1NlY3Rpb24oZGlmZiksXG4gICAgKVxuICAgIC50b1N0cmluZygpO1xufVxuXG5leHBvcnQgdHlwZSBMYWJlbGVkRGlmZiA9IFdpdGhSZXF1aXJlZDxSZXBvcnRzRGlmZiwgJ2xhYmVsJz47XG5cbmV4cG9ydCBmdW5jdGlvbiBnZW5lcmF0ZU1kUmVwb3J0c0RpZmZGb3JNb25vcmVwbyhkaWZmczogTGFiZWxlZERpZmZbXSk6IHN0cmluZyB7XG4gIGNvbnN0IGRpZmZzV2l0aE91dGNvbWVzID0gZGlmZnNcbiAgICAubWFwKGRpZmYgPT4gKHtcbiAgICAgIC4uLmRpZmYsXG4gICAgICBvdXRjb21lOiBtZXJnZURpZmZPdXRjb21lcyhjaGFuZ2VzVG9EaWZmT3V0Y29tZXMoZ2V0RGlmZkNoYW5nZXMoZGlmZikpKSxcbiAgICB9KSlcbiAgICAuc29ydChcbiAgICAgIChhLCBiKSA9PlxuICAgICAgICBjb21wYXJlRGlmZnNCeSgnY2F0ZWdvcmllcycsIGEsIGIpIHx8XG4gICAgICAgIGNvbXBhcmVEaWZmc0J5KCdncm91cHMnLCBhLCBiKSB8fFxuICAgICAgICBjb21wYXJlRGlmZnNCeSgnYXVkaXRzJywgYSwgYikgfHxcbiAgICAgICAgYS5sYWJlbC5sb2NhbGVDb21wYXJlKGIubGFiZWwpLFxuICAgICk7XG4gIGNvbnN0IHVuY2hhbmdlZCA9IGRpZmZzV2l0aE91dGNvbWVzLmZpbHRlcihcbiAgICAoeyBvdXRjb21lIH0pID0+IG91dGNvbWUgPT09ICd1bmNoYW5nZWQnLFxuICApO1xuICBjb25zdCBjaGFuZ2VkID0gZGlmZnNXaXRoT3V0Y29tZXMuZmlsdGVyKGRpZmYgPT4gIXVuY2hhbmdlZC5pbmNsdWRlcyhkaWZmKSk7XG5cbiAgcmV0dXJuIG5ldyBNYXJrZG93bkRvY3VtZW50KClcbiAgICAuJGNvbmNhdChcbiAgICAgIGNyZWF0ZURpZmZIZWFkZXJTZWN0aW9uKGRpZmZzKSxcbiAgICAgIC4uLmNoYW5nZWQubWFwKGNyZWF0ZURpZmZQcm9qZWN0U2VjdGlvbiksXG4gICAgKVxuICAgIC4kaWYodW5jaGFuZ2VkLmxlbmd0aCA+IDAsIGRvYyA9PlxuICAgICAgZG9jXG4gICAgICAgIC5ydWxlKClcbiAgICAgICAgLnBhcmFncmFwaChzdW1tYXJpemVVbmNoYW5nZWQoJ3Byb2plY3QnLCB7IHVuY2hhbmdlZCwgY2hhbmdlZCB9KSksXG4gICAgKVxuICAgIC50b1N0cmluZygpO1xufVxuXG5mdW5jdGlvbiBjcmVhdGVEaWZmSGVhZGVyU2VjdGlvbihcbiAgZGlmZjogUmVwb3J0c0RpZmYgfCBSZXBvcnRzRGlmZltdLFxuKTogTWFya2Rvd25Eb2N1bWVudCB7XG4gIGNvbnN0IG91dGNvbWUgPSBtZXJnZURpZmZPdXRjb21lcyhcbiAgICBjaGFuZ2VzVG9EaWZmT3V0Y29tZXModG9BcnJheShkaWZmKS5mbGF0TWFwKGdldERpZmZDaGFuZ2VzKSksXG4gICk7XG4gIC8vIFRPRE86IHdoYXQgaWYgYXJyYXkgY29udGFpbnMgZGlmZmVyZW50IGNvbW1pdCBwYWlycz9cbiAgY29uc3QgY29tbWl0cyA9IEFycmF5LmlzQXJyYXkoZGlmZikgPyBkaWZmWzBdPy5jb21taXRzIDogZGlmZi5jb21taXRzO1xuICBjb25zdCBwb3J0YWxVcmwgPSBBcnJheS5pc0FycmF5KGRpZmYpID8gdW5kZWZpbmVkIDogZGlmZi5wb3J0YWxVcmw7XG5cbiAgcmV0dXJuIG5ldyBNYXJrZG93bkRvY3VtZW50KClcbiAgICAuaGVhZGluZyhISUVSQVJDSFkubGV2ZWxfMSwgJ0NvZGUgUHVzaFVwJylcbiAgICAucGFyYWdyYXBoKGZvcm1hdFJlcG9ydE91dGNvbWUob3V0Y29tZSwgY29tbWl0cykpXG4gICAgLnBhcmFncmFwaChmb3JtYXRQb3J0YWxMaW5rKHBvcnRhbFVybCkpO1xufVxuXG5mdW5jdGlvbiBjcmVhdGVEaWZmUHJvamVjdFNlY3Rpb24oXG4gIGRpZmY6IExhYmVsZWREaWZmICYgeyBvdXRjb21lOiBEaWZmT3V0Y29tZSB9LFxuKTogTWFya2Rvd25Eb2N1bWVudCB7XG4gIHJldHVybiBuZXcgTWFya2Rvd25Eb2N1bWVudCgpXG4gICAgLmhlYWRpbmcoSElFUkFSQ0hZLmxldmVsXzIsIG1kYFx1RDgzRFx1RENCQyBQcm9qZWN0ICR7bWQuY29kZShkaWZmLmxhYmVsKX1gKVxuICAgIC5wYXJhZ3JhcGgoZm9ybWF0UmVwb3J0T3V0Y29tZShkaWZmLm91dGNvbWUpKVxuICAgIC5wYXJhZ3JhcGgoZm9ybWF0UG9ydGFsTGluayhkaWZmLnBvcnRhbFVybCkpXG4gICAgLiRjb25jYXQoXG4gICAgICBjcmVhdGVEaWZmQ2F0ZWdvcmllc1NlY3Rpb24oZGlmZiwge1xuICAgICAgICBza2lwSGVhZGluZzogdHJ1ZSxcbiAgICAgICAgc2tpcFVuY2hhbmdlZDogdHJ1ZSxcbiAgICAgIH0pLFxuICAgICAgY3JlYXRlRGlmZkRldGFpbHNTZWN0aW9uKGRpZmYsIEhJRVJBUkNIWS5sZXZlbF8zKSxcbiAgICApO1xufVxuXG5mdW5jdGlvbiBjcmVhdGVEaWZmQ2F0ZWdvcmllc1NlY3Rpb24oXG4gIGRpZmY6IFJlcG9ydHNEaWZmLFxuICBvcHRpb25zPzogeyBza2lwSGVhZGluZz86IGJvb2xlYW47IHNraXBVbmNoYW5nZWQ/OiBib29sZWFuIH0sXG4pOiBNYXJrZG93bkRvY3VtZW50IHwgbnVsbCB7XG4gIGNvbnN0IHsgY2hhbmdlZCwgdW5jaGFuZ2VkLCBhZGRlZCB9ID0gZGlmZi5jYXRlZ29yaWVzO1xuICBjb25zdCB7IHNraXBIZWFkaW5nLCBza2lwVW5jaGFuZ2VkIH0gPSBvcHRpb25zID8/IHt9O1xuXG4gIGNvbnN0IGNhdGVnb3JpZXNDb3VudCA9IGNoYW5nZWQubGVuZ3RoICsgdW5jaGFuZ2VkLmxlbmd0aCArIGFkZGVkLmxlbmd0aDtcbiAgY29uc3QgaGFzQ2hhbmdlcyA9IHVuY2hhbmdlZC5sZW5ndGggPCBjYXRlZ29yaWVzQ291bnQ7XG5cbiAgaWYgKGNhdGVnb3JpZXNDb3VudCA9PT0gMCkge1xuICAgIHJldHVybiBudWxsO1xuICB9XG5cbiAgY29uc3QgW2NvbHVtbnMsIHJvd3NdID0gY3JlYXRlQ2F0ZWdvcmllc1RhYmxlKGRpZmYsIHtcbiAgICBoYXNDaGFuZ2VzLFxuICAgIHNraXBVbmNoYW5nZWQsXG4gIH0pO1xuXG4gIHJldHVybiBuZXcgTWFya2Rvd25Eb2N1bWVudCgpXG4gICAgLmhlYWRpbmcoSElFUkFSQ0hZLmxldmVsXzIsICFza2lwSGVhZGluZyAmJiAnXHVEODNDXHVERkY3XHVGRTBGIENhdGVnb3JpZXMnKVxuICAgIC50YWJsZShjb2x1bW5zLCByb3dzKVxuICAgIC5wYXJhZ3JhcGgoYWRkZWQubGVuZ3RoID4gMCAmJiBtZC5pdGFsaWMoU3RyaW5nLnJhd2AoXFwqKSBOZXcgY2F0ZWdvcnkuYCkpXG4gICAgLnBhcmFncmFwaChcbiAgICAgIHNraXBVbmNoYW5nZWQgJiZcbiAgICAgICAgdW5jaGFuZ2VkLmxlbmd0aCA+IDAgJiZcbiAgICAgICAgc3VtbWFyaXplVW5jaGFuZ2VkKCdjYXRlZ29yeScsIHsgY2hhbmdlZCwgdW5jaGFuZ2VkIH0pLFxuICAgICk7XG59XG5cbmZ1bmN0aW9uIGNyZWF0ZUNhdGVnb3JpZXNUYWJsZShcbiAgZGlmZjogUmVwb3J0c0RpZmYsXG4gIG9wdGlvbnM6IHsgaGFzQ2hhbmdlczogYm9vbGVhbjsgc2tpcFVuY2hhbmdlZD86IGJvb2xlYW4gfSxcbik6IFBhcmFtZXRlcnM8TWFya2Rvd25Eb2N1bWVudFsndGFibGUnXT4ge1xuICBjb25zdCB7IGNoYW5nZWQsIHVuY2hhbmdlZCwgYWRkZWQgfSA9IGRpZmYuY2F0ZWdvcmllcztcbiAgY29uc3QgeyBoYXNDaGFuZ2VzLCBza2lwVW5jaGFuZ2VkIH0gPSBvcHRpb25zO1xuXG4gIGNvbnN0IHJvd3M6IFRhYmxlUm93W10gPSBbXG4gICAgLi4uc29ydENoYW5nZXMoY2hhbmdlZCkubWFwKGNhdGVnb3J5ID0+IFtcbiAgICAgIGZvcm1hdFRpdGxlKGNhdGVnb3J5KSxcbiAgICAgIGZvcm1hdFNjb3JlV2l0aENvbG9yKGNhdGVnb3J5LnNjb3Jlcy5iZWZvcmUsIHtcbiAgICAgICAgc2tpcEJvbGQ6IHRydWUsXG4gICAgICB9KSxcbiAgICAgIGZvcm1hdFNjb3JlV2l0aENvbG9yKGNhdGVnb3J5LnNjb3Jlcy5hZnRlciksXG4gICAgICBmb3JtYXRTY29yZUNoYW5nZShjYXRlZ29yeS5zY29yZXMuZGlmZiksXG4gICAgXSksXG4gICAgLi4uYWRkZWQubWFwKGNhdGVnb3J5ID0+IFtcbiAgICAgIGZvcm1hdFRpdGxlKGNhdGVnb3J5KSxcbiAgICAgIG1kLml0YWxpYyhTdHJpbmcucmF3YG4vYSAoXFwqKWApLFxuICAgICAgZm9ybWF0U2NvcmVXaXRoQ29sb3IoY2F0ZWdvcnkuc2NvcmUpLFxuICAgICAgbWQuaXRhbGljKFN0cmluZy5yYXdgbi9hIChcXCopYCksXG4gICAgXSksXG4gICAgLi4uKHNraXBVbmNoYW5nZWRcbiAgICAgID8gW11cbiAgICAgIDogdW5jaGFuZ2VkLm1hcChjYXRlZ29yeSA9PiBbXG4gICAgICAgICAgZm9ybWF0VGl0bGUoY2F0ZWdvcnkpLFxuICAgICAgICAgIGZvcm1hdFNjb3JlV2l0aENvbG9yKGNhdGVnb3J5LnNjb3JlLCB7IHNraXBCb2xkOiB0cnVlIH0pLFxuICAgICAgICAgIGZvcm1hdFNjb3JlV2l0aENvbG9yKGNhdGVnb3J5LnNjb3JlKSxcbiAgICAgICAgICAnXHUyMDEzJyxcbiAgICAgICAgXSkpLFxuICBdO1xuXG4gIGlmIChyb3dzLmxlbmd0aCA9PT0gMCkge1xuICAgIHJldHVybiBbW10sIFtdXTtcbiAgfVxuXG4gIGNvbnN0IGNvbHVtbnM6IFRhYmxlQ29sdW1uT2JqZWN0W10gPSBbXG4gICAgeyBoZWFkaW5nOiAnXHVEODNDXHVERkY3XHVGRTBGIENhdGVnb3J5JywgYWxpZ25tZW50OiAnbGVmdCcgfSxcbiAgICB7XG4gICAgICBoZWFkaW5nOiBoYXNDaGFuZ2VzID8gJ1x1MkI1MCBQcmV2aW91cyBzY29yZScgOiAnXHUyQjUwIFNjb3JlJyxcbiAgICAgIGFsaWdubWVudDogJ2NlbnRlcicsXG4gICAgfSxcbiAgICB7IGhlYWRpbmc6ICdcdTJCNTAgQ3VycmVudCBzY29yZScsIGFsaWdubWVudDogJ2NlbnRlcicgfSxcbiAgICB7IGhlYWRpbmc6ICdcdUQ4M0RcdUREMDQgU2NvcmUgY2hhbmdlJywgYWxpZ25tZW50OiAnY2VudGVyJyB9LFxuICBdO1xuXG4gIHJldHVybiBbXG4gICAgaGFzQ2hhbmdlcyA/IGNvbHVtbnMgOiBjb2x1bW5zLnNsaWNlKDAsIDIpLFxuICAgIHJvd3MubWFwKHJvdyA9PiAoaGFzQ2hhbmdlcyA/IHJvdyA6IHJvdy5zbGljZSgwLCAyKSkpLFxuICBdO1xufVxuXG5mdW5jdGlvbiBjcmVhdGVEaWZmRGV0YWlsc1NlY3Rpb24oXG4gIGRpZmY6IFJlcG9ydHNEaWZmLFxuICBsZXZlbDogSGVhZGluZ0xldmVsID0gSElFUkFSQ0hZLmxldmVsXzIsXG4pOiBNYXJrZG93bkRvY3VtZW50IHwgbnVsbCB7XG4gIGlmIChkaWZmLmdyb3Vwcy5jaGFuZ2VkLmxlbmd0aCArIGRpZmYuYXVkaXRzLmNoYW5nZWQubGVuZ3RoID09PSAwKSB7XG4gICAgcmV0dXJuIG51bGw7XG4gIH1cbiAgY29uc3Qgc3VtbWFyeSA9IChbJ2dyb3VwJywgJ2F1ZGl0J10gYXMgY29uc3QpXG4gICAgLm1hcCh0b2tlbiA9PlxuICAgICAgc3VtbWFyaXplRGlmZk91dGNvbWVzKFxuICAgICAgICBjaGFuZ2VzVG9EaWZmT3V0Y29tZXMoZGlmZltgJHt0b2tlbn1zYF0uY2hhbmdlZCksXG4gICAgICAgIHRva2VuLFxuICAgICAgKSxcbiAgICApXG4gICAgLmZpbHRlcihCb29sZWFuKVxuICAgIC5qb2luKCcsICcpO1xuICBjb25zdCBkZXRhaWxzID0gbmV3IE1hcmtkb3duRG9jdW1lbnQoKS4kY29uY2F0KFxuICAgIGNyZWF0ZURpZmZHcm91cHNTZWN0aW9uKGRpZmYsIGxldmVsKSxcbiAgICBjcmVhdGVEaWZmQXVkaXRzU2VjdGlvbihkaWZmLCBsZXZlbCksXG4gICk7XG4gIHJldHVybiBuZXcgTWFya2Rvd25Eb2N1bWVudCgpLmRldGFpbHMoc3VtbWFyeSwgZGV0YWlscyk7XG59XG5cbmZ1bmN0aW9uIGNyZWF0ZURpZmZHcm91cHNTZWN0aW9uKFxuICBkaWZmOiBSZXBvcnRzRGlmZixcbiAgbGV2ZWw6IEhlYWRpbmdMZXZlbCxcbik6IE1hcmtkb3duRG9jdW1lbnQgfCBudWxsIHtcbiAgaWYgKGRpZmYuZ3JvdXBzLmNoYW5nZWQubGVuZ3RoICsgZGlmZi5ncm91cHMudW5jaGFuZ2VkLmxlbmd0aCA9PT0gMCkge1xuICAgIHJldHVybiBudWxsO1xuICB9XG4gIHJldHVybiBuZXcgTWFya2Rvd25Eb2N1bWVudCgpLmhlYWRpbmcobGV2ZWwsICdcdUQ4M0RcdUREQzNcdUZFMEYgR3JvdXBzJykuJGNvbmNhdChcbiAgICBjcmVhdGVHcm91cHNPckF1ZGl0c0RldGFpbHMoXG4gICAgICAnZ3JvdXAnLFxuICAgICAgZGlmZi5ncm91cHMsXG4gICAgICBbXG4gICAgICAgIHsgaGVhZGluZzogJ1x1RDgzRFx1REQwQyBQbHVnaW4nLCBhbGlnbm1lbnQ6ICdsZWZ0JyB9LFxuICAgICAgICB7IGhlYWRpbmc6ICdcdUQ4M0RcdUREQzNcdUZFMEYgR3JvdXAnLCBhbGlnbm1lbnQ6ICdsZWZ0JyB9LFxuICAgICAgICB7IGhlYWRpbmc6ICdcdTJCNTAgUHJldmlvdXMgc2NvcmUnLCBhbGlnbm1lbnQ6ICdjZW50ZXInIH0sXG4gICAgICAgIHsgaGVhZGluZzogJ1x1MkI1MCBDdXJyZW50IHNjb3JlJywgYWxpZ25tZW50OiAnY2VudGVyJyB9LFxuICAgICAgICB7IGhlYWRpbmc6ICdcdUQ4M0RcdUREMDQgU2NvcmUgY2hhbmdlJywgYWxpZ25tZW50OiAnY2VudGVyJyB9LFxuICAgICAgXSxcbiAgICAgIHNvcnRDaGFuZ2VzKGRpZmYuZ3JvdXBzLmNoYW5nZWQpLm1hcChncm91cCA9PiBbXG4gICAgICAgIGZvcm1hdFRpdGxlKGdyb3VwLnBsdWdpbiksXG4gICAgICAgIGZvcm1hdFRpdGxlKGdyb3VwKSxcbiAgICAgICAgZm9ybWF0U2NvcmVXaXRoQ29sb3IoZ3JvdXAuc2NvcmVzLmJlZm9yZSwgeyBza2lwQm9sZDogdHJ1ZSB9KSxcbiAgICAgICAgZm9ybWF0U2NvcmVXaXRoQ29sb3IoZ3JvdXAuc2NvcmVzLmFmdGVyKSxcbiAgICAgICAgZm9ybWF0U2NvcmVDaGFuZ2UoZ3JvdXAuc2NvcmVzLmRpZmYpLFxuICAgICAgXSksXG4gICAgKSxcbiAgKTtcbn1cblxuZnVuY3Rpb24gY3JlYXRlRGlmZkF1ZGl0c1NlY3Rpb24oXG4gIGRpZmY6IFJlcG9ydHNEaWZmLFxuICBsZXZlbDogSGVhZGluZ0xldmVsLFxuKTogTWFya2Rvd25Eb2N1bWVudCB7XG4gIHJldHVybiBuZXcgTWFya2Rvd25Eb2N1bWVudCgpLmhlYWRpbmcobGV2ZWwsICdcdUQ4M0RcdURFRTFcdUZFMEYgQXVkaXRzJykuJGNvbmNhdChcbiAgICBjcmVhdGVHcm91cHNPckF1ZGl0c0RldGFpbHMoXG4gICAgICAnYXVkaXQnLFxuICAgICAgZGlmZi5hdWRpdHMsXG4gICAgICBbXG4gICAgICAgIHsgaGVhZGluZzogJ1x1RDgzRFx1REQwQyBQbHVnaW4nLCBhbGlnbm1lbnQ6ICdsZWZ0JyB9LFxuICAgICAgICB7IGhlYWRpbmc6ICdcdUQ4M0RcdURFRTFcdUZFMEYgQXVkaXQnLCBhbGlnbm1lbnQ6ICdsZWZ0JyB9LFxuICAgICAgICB7IGhlYWRpbmc6ICdcdUQ4M0RcdURDQ0YgUHJldmlvdXMgdmFsdWUnLCBhbGlnbm1lbnQ6ICdjZW50ZXInIH0sXG4gICAgICAgIHsgaGVhZGluZzogJ1x1RDgzRFx1RENDRiBDdXJyZW50IHZhbHVlJywgYWxpZ25tZW50OiAnY2VudGVyJyB9LFxuICAgICAgICB7IGhlYWRpbmc6ICdcdUQ4M0RcdUREMDQgVmFsdWUgY2hhbmdlJywgYWxpZ25tZW50OiAnY2VudGVyJyB9LFxuICAgICAgXSxcbiAgICAgIHNvcnRDaGFuZ2VzKGRpZmYuYXVkaXRzLmNoYW5nZWQpLm1hcChhdWRpdCA9PiBbXG4gICAgICAgIGZvcm1hdFRpdGxlKGF1ZGl0LnBsdWdpbiksXG4gICAgICAgIGZvcm1hdFRpdGxlKGF1ZGl0KSxcbiAgICAgICAgYCR7c2NvcmVNYXJrZXIoYXVkaXQuc2NvcmVzLmJlZm9yZSwgJ3NxdWFyZScpfSAke1xuICAgICAgICAgIGF1ZGl0LmRpc3BsYXlWYWx1ZXMuYmVmb3JlIHx8IGF1ZGl0LnZhbHVlcy5iZWZvcmUudG9TdHJpbmcoKVxuICAgICAgICB9YCxcbiAgICAgICAgbWRgJHtzY29yZU1hcmtlcihhdWRpdC5zY29yZXMuYWZ0ZXIsICdzcXVhcmUnKX0gJHttZC5ib2xkKFxuICAgICAgICAgIGF1ZGl0LmRpc3BsYXlWYWx1ZXMuYWZ0ZXIgfHwgYXVkaXQudmFsdWVzLmFmdGVyLnRvU3RyaW5nKCksXG4gICAgICAgICl9YCxcbiAgICAgICAgZm9ybWF0VmFsdWVDaGFuZ2UoYXVkaXQpLFxuICAgICAgXSksXG4gICAgKSxcbiAgKTtcbn1cbiIsICJjb25zdCBfX2luamVjdGVkX2ZpbGVuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3V0aWxzL3NyYy9saWIvcmVwb3J0cy9nZW5lcmF0ZS1tZC1yZXBvcnRzLWRpZmYtdXRpbHMudHNcIjtjb25zdCBfX2luamVjdGVkX2Rpcm5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvdXRpbHMvc3JjL2xpYi9yZXBvcnRzXCI7Y29uc3QgX19pbmplY3RlZF9pbXBvcnRfbWV0YV91cmxfXyA9IFwiZmlsZTovLy9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3V0aWxzL3NyYy9saWIvcmVwb3J0cy9nZW5lcmF0ZS1tZC1yZXBvcnRzLWRpZmYtdXRpbHMudHNcIjtpbXBvcnQgeyB0eXBlIElubGluZVRleHQsIE1hcmtkb3duRG9jdW1lbnQsIG1kIH0gZnJvbSAnYnVpbGQtbWQnO1xuaW1wb3J0IHR5cGUgeyBSZXBvcnRzRGlmZiB9IGZyb20gJ0Bjb2RlLXB1c2h1cC9tb2RlbHMnO1xuaW1wb3J0IHsgcGx1cmFsaXplLCBwbHVyYWxpemVUb2tlbiB9IGZyb20gJy4uL2Zvcm1hdHRpbmcuanMnO1xuaW1wb3J0IHsgb2JqZWN0VG9FbnRyaWVzIH0gZnJvbSAnLi4vdHJhbnNmb3JtLmpzJztcbmltcG9ydCB0eXBlIHsgRGlmZk91dGNvbWUgfSBmcm9tICcuL3R5cGVzLmpzJztcblxuLy8gdG8gcHJldmVudCBleGNlZWRpbmcgTWFya2Rvd24gY29tbWVudCBjaGFyYWN0ZXIgbGltaXRcbmNvbnN0IE1BWF9ST1dTID0gMTAwO1xuXG5leHBvcnQgZnVuY3Rpb24gc3VtbWFyaXplVW5jaGFuZ2VkKFxuICB0b2tlbjogc3RyaW5nLFxuICB7IGNoYW5nZWQsIHVuY2hhbmdlZCB9OiB7IGNoYW5nZWQ6IHVua25vd25bXTsgdW5jaGFuZ2VkOiB1bmtub3duW10gfSxcbik6IHN0cmluZyB7XG4gIGNvbnN0IHBsdXJhbGl6ZWRDb3VudCA9XG4gICAgY2hhbmdlZC5sZW5ndGggPiAwXG4gICAgICA/IHBsdXJhbGl6ZVRva2VuKGBvdGhlciAke3Rva2VufWAsIHVuY2hhbmdlZC5sZW5ndGgpXG4gICAgICA6IGBBbGwgb2YgJHtwbHVyYWxpemVUb2tlbih0b2tlbiwgdW5jaGFuZ2VkLmxlbmd0aCl9YDtcbiAgY29uc3QgcGx1cmFsaXplZFZlcmIgPSB1bmNoYW5nZWQubGVuZ3RoID09PSAxID8gJ2lzJyA6ICdhcmUnO1xuICByZXR1cm4gYCR7cGx1cmFsaXplZENvdW50fSAke3BsdXJhbGl6ZWRWZXJifSB1bmNoYW5nZWQuYDtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHN1bW1hcml6ZURpZmZPdXRjb21lcyhcbiAgb3V0Y29tZXM6IERpZmZPdXRjb21lW10sXG4gIHRva2VuOiBzdHJpbmcsXG4pOiBzdHJpbmcge1xuICByZXR1cm4gb2JqZWN0VG9FbnRyaWVzKGNvdW50RGlmZk91dGNvbWVzKG91dGNvbWVzKSlcbiAgICAuZmlsdGVyKFxuICAgICAgKGVudHJ5KTogZW50cnkgaXMgW0V4Y2x1ZGU8RGlmZk91dGNvbWUsICd1bmNoYW5nZWQnPiwgbnVtYmVyXSA9PlxuICAgICAgICBlbnRyeVswXSAhPT0gJ3VuY2hhbmdlZCcgJiYgZW50cnlbMV0gPiAwLFxuICAgIClcbiAgICAubWFwKChbb3V0Y29tZSwgY291bnRdKTogc3RyaW5nID0+IHtcbiAgICAgIGNvbnN0IGZvcm1hdHRlZENvdW50ID0gYDxzdHJvbmc+JHtjb3VudH08L3N0cm9uZz4gJHtwbHVyYWxpemUoXG4gICAgICAgIHRva2VuLFxuICAgICAgICBjb3VudCxcbiAgICAgICl9YDtcbiAgICAgIHN3aXRjaCAob3V0Y29tZSkge1xuICAgICAgICBjYXNlICdwb3NpdGl2ZSc6XG4gICAgICAgICAgcmV0dXJuIGBcdUQ4M0RcdURDNEQgJHtmb3JtYXR0ZWRDb3VudH0gaW1wcm92ZWRgO1xuICAgICAgICBjYXNlICduZWdhdGl2ZSc6XG4gICAgICAgICAgcmV0dXJuIGBcdUQ4M0RcdURDNEUgJHtmb3JtYXR0ZWRDb3VudH0gcmVncmVzc2VkYDtcbiAgICAgICAgY2FzZSAnbWl4ZWQnOlxuICAgICAgICAgIHJldHVybiBgJHtmb3JtYXR0ZWRDb3VudH0gY2hhbmdlZCB3aXRob3V0IGltcGFjdGluZyBzY29yZWA7XG4gICAgICB9XG4gICAgfSlcbiAgICAuam9pbignLCAnKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGNyZWF0ZUdyb3Vwc09yQXVkaXRzRGV0YWlsczxUIGV4dGVuZHMgJ2dyb3VwJyB8ICdhdWRpdCc+KFxuICB0b2tlbjogVCxcbiAgeyBjaGFuZ2VkLCB1bmNoYW5nZWQgfTogUmVwb3J0c0RpZmZbYCR7VH1zYF0sXG4gIC4uLltjb2x1bW5zLCByb3dzXTogUGFyYW1ldGVyczxNYXJrZG93bkRvY3VtZW50Wyd0YWJsZSddPlxuKTogTWFya2Rvd25Eb2N1bWVudCB7XG4gIGlmIChjaGFuZ2VkLmxlbmd0aCA9PT0gMCkge1xuICAgIHJldHVybiBuZXcgTWFya2Rvd25Eb2N1bWVudCgpLnBhcmFncmFwaChcbiAgICAgIHN1bW1hcml6ZVVuY2hhbmdlZCh0b2tlbiwgeyBjaGFuZ2VkLCB1bmNoYW5nZWQgfSksXG4gICAgKTtcbiAgfVxuICByZXR1cm4gbmV3IE1hcmtkb3duRG9jdW1lbnQoKVxuICAgIC50YWJsZShjb2x1bW5zLCByb3dzLnNsaWNlKDAsIE1BWF9ST1dTKSlcbiAgICAucGFyYWdyYXBoKFxuICAgICAgY2hhbmdlZC5sZW5ndGggPiBNQVhfUk9XUyAmJlxuICAgICAgICBtZC5pdGFsaWMoXG4gICAgICAgICAgYE9ubHkgdGhlICR7TUFYX1JPV1N9IG1vc3QgYWZmZWN0ZWQgJHtwbHVyYWxpemUoXG4gICAgICAgICAgICB0b2tlbixcbiAgICAgICAgICApfSBhcmUgbGlzdGVkIGFib3ZlIGZvciBicmV2aXR5LmAsXG4gICAgICAgICksXG4gICAgKVxuICAgIC5wYXJhZ3JhcGgoXG4gICAgICB1bmNoYW5nZWQubGVuZ3RoID4gMCAmJiBzdW1tYXJpemVVbmNoYW5nZWQodG9rZW4sIHsgY2hhbmdlZCwgdW5jaGFuZ2VkIH0pLFxuICAgICk7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBmb3JtYXRUaXRsZSh7XG4gIHRpdGxlLFxuICBkb2NzVXJsLFxufToge1xuICB0aXRsZTogc3RyaW5nO1xuICBkb2NzVXJsPzogc3RyaW5nO1xufSk6IElubGluZVRleHQge1xuICBpZiAoZG9jc1VybCkge1xuICAgIHJldHVybiBtZC5saW5rKGRvY3NVcmwsIHRpdGxlKTtcbiAgfVxuICByZXR1cm4gdGl0bGU7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBmb3JtYXRQb3J0YWxMaW5rKFxuICBwb3J0YWxVcmw6IHN0cmluZyB8IHVuZGVmaW5lZCxcbik6IElubGluZVRleHQgfCB1bmRlZmluZWQge1xuICByZXR1cm4gKFxuICAgIHBvcnRhbFVybCAmJlxuICAgIG1kLmxpbmsocG9ydGFsVXJsLCAnXHVEODNEXHVERDc1XHVGRTBGIFNlZSBmdWxsIGNvbXBhcmlzb24gaW4gQ29kZSBQdXNoVXAgcG9ydGFsIFx1RDgzRFx1REQwRCcpXG4gICk7XG59XG5cbnR5cGUgQ2hhbmdlID0ge1xuICBzY29yZXM6IHsgZGlmZjogbnVtYmVyIH07XG4gIHZhbHVlcz86IHsgZGlmZjogbnVtYmVyIH07XG59O1xuXG5leHBvcnQgZnVuY3Rpb24gc29ydENoYW5nZXM8VCBleHRlbmRzIENoYW5nZT4oY2hhbmdlczogVFtdKTogVFtdIHtcbiAgcmV0dXJuIGNoYW5nZXMudG9Tb3J0ZWQoXG4gICAgKGEsIGIpID0+XG4gICAgICBNYXRoLmFicyhiLnNjb3Jlcy5kaWZmKSAtIE1hdGguYWJzKGEuc2NvcmVzLmRpZmYpIHx8XG4gICAgICBNYXRoLmFicyhiLnZhbHVlcz8uZGlmZiA/PyAwKSAtIE1hdGguYWJzKGEudmFsdWVzPy5kaWZmID8/IDApLFxuICApO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gZ2V0RGlmZkNoYW5nZXMoZGlmZjogUmVwb3J0c0RpZmYpOiBDaGFuZ2VbXSB7XG4gIHJldHVybiBbXG4gICAgLi4uZGlmZi5jYXRlZ29yaWVzLmNoYW5nZWQsXG4gICAgLi4uZGlmZi5ncm91cHMuY2hhbmdlZCxcbiAgICAuLi5kaWZmLmF1ZGl0cy5jaGFuZ2VkLFxuICBdO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gY2hhbmdlc1RvRGlmZk91dGNvbWVzKGNoYW5nZXM6IENoYW5nZVtdKTogRGlmZk91dGNvbWVbXSB7XG4gIHJldHVybiBjaGFuZ2VzLm1hcCgoY2hhbmdlKTogRGlmZk91dGNvbWUgPT4ge1xuICAgIGlmIChjaGFuZ2Uuc2NvcmVzLmRpZmYgPiAwKSB7XG4gICAgICByZXR1cm4gJ3Bvc2l0aXZlJztcbiAgICB9XG4gICAgaWYgKGNoYW5nZS5zY29yZXMuZGlmZiA8IDApIHtcbiAgICAgIHJldHVybiAnbmVnYXRpdmUnO1xuICAgIH1cbiAgICBpZiAoY2hhbmdlLnZhbHVlcyAhPSBudWxsICYmIGNoYW5nZS52YWx1ZXMuZGlmZiAhPT0gMCkge1xuICAgICAgcmV0dXJuICdtaXhlZCc7XG4gICAgfVxuICAgIHJldHVybiAndW5jaGFuZ2VkJztcbiAgfSk7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBtZXJnZURpZmZPdXRjb21lcyhvdXRjb21lczogRGlmZk91dGNvbWVbXSk6IERpZmZPdXRjb21lIHtcbiAgaWYgKG91dGNvbWVzLmV2ZXJ5KG91dGNvbWUgPT4gb3V0Y29tZSA9PT0gJ3VuY2hhbmdlZCcpKSB7XG4gICAgcmV0dXJuICd1bmNoYW5nZWQnO1xuICB9XG4gIGlmIChvdXRjb21lcy5pbmNsdWRlcygncG9zaXRpdmUnKSAmJiAhb3V0Y29tZXMuaW5jbHVkZXMoJ25lZ2F0aXZlJykpIHtcbiAgICByZXR1cm4gJ3Bvc2l0aXZlJztcbiAgfVxuICBpZiAob3V0Y29tZXMuaW5jbHVkZXMoJ25lZ2F0aXZlJykgJiYgIW91dGNvbWVzLmluY2x1ZGVzKCdwb3NpdGl2ZScpKSB7XG4gICAgcmV0dXJuICduZWdhdGl2ZSc7XG4gIH1cbiAgcmV0dXJuICdtaXhlZCc7XG59XG5cbmZ1bmN0aW9uIGNvdW50RGlmZk91dGNvbWVzKFxuICBvdXRjb21lczogRGlmZk91dGNvbWVbXSxcbik6IFJlY29yZDxEaWZmT3V0Y29tZSwgbnVtYmVyPiB7XG4gIHJldHVybiB7XG4gICAgcG9zaXRpdmU6IG91dGNvbWVzLmZpbHRlcihvdXRjb21lID0+IG91dGNvbWUgPT09ICdwb3NpdGl2ZScpLmxlbmd0aCxcbiAgICBuZWdhdGl2ZTogb3V0Y29tZXMuZmlsdGVyKG91dGNvbWUgPT4gb3V0Y29tZSA9PT0gJ25lZ2F0aXZlJykubGVuZ3RoLFxuICAgIG1peGVkOiBvdXRjb21lcy5maWx0ZXIob3V0Y29tZSA9PiBvdXRjb21lID09PSAnbWl4ZWQnKS5sZW5ndGgsXG4gICAgdW5jaGFuZ2VkOiBvdXRjb21lcy5maWx0ZXIob3V0Y29tZSA9PiBvdXRjb21lID09PSAndW5jaGFuZ2VkJykubGVuZ3RoLFxuICB9O1xufVxuXG5leHBvcnQgZnVuY3Rpb24gZm9ybWF0UmVwb3J0T3V0Y29tZShcbiAgb3V0Y29tZTogRGlmZk91dGNvbWUsXG4gIGNvbW1pdHM/OiBSZXBvcnRzRGlmZlsnY29tbWl0cyddLFxuKTogSW5saW5lVGV4dCB7XG4gIGNvbnN0IG91dGNvbWVUZXh0cyA9IHtcbiAgICBwb3NpdGl2ZTogbWRgXHVEODNFXHVERDczIENvZGUgUHVzaFVwIHJlcG9ydCBoYXMgJHttZC5ib2xkKCdpbXByb3ZlZCcpfWAsXG4gICAgbmVnYXRpdmU6IG1kYFx1RDgzRFx1REUxRiBDb2RlIFB1c2hVcCByZXBvcnQgaGFzICR7bWQuYm9sZCgncmVncmVzc2VkJyl9YCxcbiAgICBtaXhlZDogbWRgXHVEODNFXHVERDI4IENvZGUgUHVzaFVwIHJlcG9ydCBoYXMgYm90aCAke21kLmJvbGQoXG4gICAgICAnaW1wcm92ZW1lbnRzIGFuZCByZWdyZXNzaW9ucycsXG4gICAgKX1gLFxuICAgIHVuY2hhbmdlZDogbWRgXHVEODNEXHVERTEwIENvZGUgUHVzaFVwIHJlcG9ydCBpcyAke21kLmJvbGQoJ3VuY2hhbmdlZCcpfWAsXG4gIH07XG5cbiAgaWYgKGNvbW1pdHMpIHtcbiAgICBjb25zdCBjb21taXRzVGV4dCA9IGBjb21wYXJlZCB0YXJnZXQgY29tbWl0ICR7Y29tbWl0cy5hZnRlci5oYXNofSB3aXRoIHNvdXJjZSBjb21taXQgJHtjb21taXRzLmJlZm9yZS5oYXNofWA7XG4gICAgcmV0dXJuIG1kYCR7b3V0Y29tZVRleHRzW291dGNvbWVdfSBcdTIwMTMgJHtjb21taXRzVGV4dH0uYDtcbiAgfVxuXG4gIHJldHVybiBtZGAke291dGNvbWVUZXh0c1tvdXRjb21lXX0uYDtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGNvbXBhcmVEaWZmc0J5PFQgZXh0ZW5kcyAnY2F0ZWdvcmllcycgfCAnZ3JvdXBzJyB8ICdhdWRpdHMnPihcbiAgdHlwZTogVCxcbiAgYTogUmVwb3J0c0RpZmYsXG4gIGI6IFJlcG9ydHNEaWZmLFxuKTogbnVtYmVyIHtcbiAgcmV0dXJuIChcbiAgICBzdW1TY29yZUNoYW5nZXMoYlt0eXBlXS5jaGFuZ2VkKSAtIHN1bVNjb3JlQ2hhbmdlcyhhW3R5cGVdLmNoYW5nZWQpIHx8XG4gICAgc3VtQ29uZmlnQ2hhbmdlcyhiW3R5cGVdKSAtIHN1bUNvbmZpZ0NoYW5nZXMoYVt0eXBlXSlcbiAgKTtcbn1cblxuZnVuY3Rpb24gc3VtU2NvcmVDaGFuZ2VzKGNoYW5nZXM6IENoYW5nZVtdKTogbnVtYmVyIHtcbiAgcmV0dXJuIGNoYW5nZXMucmVkdWNlPG51bWJlcj4oXG4gICAgKGFjYywgeyBzY29yZXMgfSkgPT4gYWNjICsgTWF0aC5hYnMoc2NvcmVzLmRpZmYpLFxuICAgIDAsXG4gICk7XG59XG5cbmZ1bmN0aW9uIHN1bUNvbmZpZ0NoYW5nZXMoe1xuICBhZGRlZCxcbiAgcmVtb3ZlZCxcbn06IHtcbiAgYWRkZWQ6IHVua25vd25bXTtcbiAgcmVtb3ZlZDogdW5rbm93bltdO1xufSk6IG51bWJlciB7XG4gIHJldHVybiBhZGRlZC5sZW5ndGggKyByZW1vdmVkLmxlbmd0aDtcbn1cbiIsICJjb25zdCBfX2luamVjdGVkX2ZpbGVuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3V0aWxzL3NyYy9saWIvcmVwb3J0cy9sb2FkLXJlcG9ydC50c1wiO2NvbnN0IF9faW5qZWN0ZWRfZGlybmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy91dGlscy9zcmMvbGliL3JlcG9ydHNcIjtjb25zdCBfX2luamVjdGVkX2ltcG9ydF9tZXRhX3VybF9fID0gXCJmaWxlOi8vL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvdXRpbHMvc3JjL2xpYi9yZXBvcnRzL2xvYWQtcmVwb3J0LnRzXCI7aW1wb3J0IHBhdGggZnJvbSAnbm9kZTpwYXRoJztcbmltcG9ydCB7XG4gIHR5cGUgRm9ybWF0LFxuICB0eXBlIFBlcnNpc3RDb25maWcsXG4gIHR5cGUgUmVwb3J0LFxuICByZXBvcnRTY2hlbWEsXG59IGZyb20gJ0Bjb2RlLXB1c2h1cC9tb2RlbHMnO1xuaW1wb3J0IHtcbiAgZW5zdXJlRGlyZWN0b3J5RXhpc3RzLFxuICByZWFkSnNvbkZpbGUsXG4gIHJlYWRUZXh0RmlsZSxcbn0gZnJvbSAnLi4vZmlsZS1zeXN0ZW0uanMnO1xuXG50eXBlIExvYWRlZFJlcG9ydEZvcm1hdDxUIGV4dGVuZHMgRm9ybWF0PiA9IFQgZXh0ZW5kcyAnanNvbicgPyBSZXBvcnQgOiBzdHJpbmc7XG5cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBsb2FkUmVwb3J0PFQgZXh0ZW5kcyBGb3JtYXQ+KFxuICBvcHRpb25zOiBSZXF1aXJlZDxPbWl0PFBlcnNpc3RDb25maWcsICdmb3JtYXQnPj4gJiB7XG4gICAgZm9ybWF0OiBUO1xuICB9LFxuKTogUHJvbWlzZTxMb2FkZWRSZXBvcnRGb3JtYXQ8VD4+IHtcbiAgY29uc3QgeyBvdXRwdXREaXIsIGZpbGVuYW1lLCBmb3JtYXQgfSA9IG9wdGlvbnM7XG4gIGF3YWl0IGVuc3VyZURpcmVjdG9yeUV4aXN0cyhvdXRwdXREaXIpO1xuICBjb25zdCBmaWxlUGF0aCA9IHBhdGguam9pbihvdXRwdXREaXIsIGAke2ZpbGVuYW1lfS4ke2Zvcm1hdH1gKTtcblxuICBpZiAoZm9ybWF0ID09PSAnanNvbicpIHtcbiAgICBjb25zdCBjb250ZW50ID0gYXdhaXQgcmVhZEpzb25GaWxlKGZpbGVQYXRoKTtcbiAgICByZXR1cm4gcmVwb3J0U2NoZW1hLnBhcnNlKGNvbnRlbnQpIGFzIExvYWRlZFJlcG9ydEZvcm1hdDxUPjtcbiAgfVxuXG4gIGNvbnN0IHRleHQgPSBhd2FpdCByZWFkVGV4dEZpbGUoZmlsZVBhdGgpO1xuICByZXR1cm4gdGV4dCBhcyBMb2FkZWRSZXBvcnRGb3JtYXQ8VD47XG59XG4iLCAiY29uc3QgX19pbmplY3RlZF9maWxlbmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy91dGlscy9zcmMvbGliL3JlcG9ydHMvbG9nLXN0ZG91dC1zdW1tYXJ5LnRzXCI7Y29uc3QgX19pbmplY3RlZF9kaXJuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3V0aWxzL3NyYy9saWIvcmVwb3J0c1wiO2NvbnN0IF9faW5qZWN0ZWRfaW1wb3J0X21ldGFfdXJsX18gPSBcImZpbGU6Ly8vaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy91dGlscy9zcmMvbGliL3JlcG9ydHMvbG9nLXN0ZG91dC1zdW1tYXJ5LnRzXCI7aW1wb3J0IHsgYm9sZCwgY3lhbiwgY3lhbkJyaWdodCwgZ3JlZW4sIHJlZCB9IGZyb20gJ2Fuc2lzJztcbmltcG9ydCB0eXBlIHsgQXVkaXRSZXBvcnQgfSBmcm9tICdAY29kZS1wdXNodXAvbW9kZWxzJztcbmltcG9ydCB7IHVpIH0gZnJvbSAnLi4vbG9nZ2luZy5qcyc7XG5pbXBvcnQge1xuICBDT0RFX1BVU0hVUF9ET01BSU4sXG4gIEZPT1RFUl9QUkVGSVgsXG4gIFJFUE9SVF9IRUFETElORV9URVhULFxuICBSRVBPUlRfUkFXX09WRVJWSUVXX1RBQkxFX0hFQURFUlMsXG4gIFRFUk1JTkFMX1dJRFRILFxufSBmcm9tICcuL2NvbnN0YW50cy5qcyc7XG5pbXBvcnQgdHlwZSB7IFNjb3JlZFJlcG9ydCB9IGZyb20gJy4vdHlwZXMuanMnO1xuaW1wb3J0IHtcbiAgYXBwbHlTY29yZUNvbG9yLFxuICBjb3VudENhdGVnb3J5QXVkaXRzLFxuICB0YXJnZXRTY29yZUljb24sXG59IGZyb20gJy4vdXRpbHMuanMnO1xuXG5mdW5jdGlvbiBsb2cobXNnID0gJycpOiB2b2lkIHtcbiAgdWkoKS5sb2dnZXIubG9nKG1zZyk7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBsb2dTdGRvdXRTdW1tYXJ5KHJlcG9ydDogU2NvcmVkUmVwb3J0LCB2ZXJib3NlID0gZmFsc2UpOiB2b2lkIHtcbiAgY29uc3QgeyBwbHVnaW5zLCBjYXRlZ29yaWVzLCBwYWNrYWdlTmFtZSwgdmVyc2lvbiB9ID0gcmVwb3J0O1xuICBsb2cocmVwb3J0VG9IZWFkZXJTZWN0aW9uKHsgcGFja2FnZU5hbWUsIHZlcnNpb24gfSkpO1xuICBsb2coKTtcbiAgbG9nUGx1Z2lucyhwbHVnaW5zLCB2ZXJib3NlKTtcbiAgaWYgKGNhdGVnb3JpZXMgJiYgY2F0ZWdvcmllcy5sZW5ndGggPiAwKSB7XG4gICAgbG9nQ2F0ZWdvcmllcyh7IHBsdWdpbnMsIGNhdGVnb3JpZXMgfSk7XG4gIH1cbiAgbG9nKGAke0ZPT1RFUl9QUkVGSVh9ICR7Q09ERV9QVVNIVVBfRE9NQUlOfWApO1xuICBsb2coKTtcbn1cblxuZnVuY3Rpb24gcmVwb3J0VG9IZWFkZXJTZWN0aW9uKHtcbiAgcGFja2FnZU5hbWUsXG4gIHZlcnNpb24sXG59OiBQaWNrPFNjb3JlZFJlcG9ydCwgJ3BhY2thZ2VOYW1lJyB8ICd2ZXJzaW9uJz4pOiBzdHJpbmcge1xuICByZXR1cm4gYCR7Ym9sZChSRVBPUlRfSEVBRExJTkVfVEVYVCl9IC0gJHtwYWNrYWdlTmFtZX1AJHt2ZXJzaW9ufWA7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBsb2dQbHVnaW5zKFxuICBwbHVnaW5zOiBTY29yZWRSZXBvcnRbJ3BsdWdpbnMnXSxcbiAgdmVyYm9zZTogYm9vbGVhbixcbik6IHZvaWQge1xuICBwbHVnaW5zLmZvckVhY2gocGx1Z2luID0+IHtcbiAgICBjb25zdCB7IHRpdGxlLCBhdWRpdHMgfSA9IHBsdWdpbjtcbiAgICBjb25zdCBmaWx0ZXJlZEF1ZGl0cyA9XG4gICAgICB2ZXJib3NlIHx8IGF1ZGl0cy5sZW5ndGggPT09IDFcbiAgICAgICAgPyBhdWRpdHNcbiAgICAgICAgOiBhdWRpdHMuZmlsdGVyKCh7IHNjb3JlIH0pID0+IHNjb3JlICE9PSAxKTtcbiAgICBjb25zdCBkaWZmID0gYXVkaXRzLmxlbmd0aCAtIGZpbHRlcmVkQXVkaXRzLmxlbmd0aDtcblxuICAgIGxvZ0F1ZGl0cyh0aXRsZSwgZmlsdGVyZWRBdWRpdHMpO1xuXG4gICAgaWYgKGRpZmYgPiAwKSB7XG4gICAgICBjb25zdCBub3RpY2UgPVxuICAgICAgICBmaWx0ZXJlZEF1ZGl0cy5sZW5ndGggPT09IDBcbiAgICAgICAgICA/IGAuLi4gQWxsICR7ZGlmZn0gYXVkaXRzIGhhdmUgcGVyZmVjdCBzY29yZXMgLi4uYFxuICAgICAgICAgIDogYC4uLiAke2RpZmZ9IGF1ZGl0cyB3aXRoIHBlcmZlY3Qgc2NvcmVzIG9taXR0ZWQgZm9yIGJyZXZpdHkgLi4uYDtcbiAgICAgIGxvZ1JvdygxLCBub3RpY2UpO1xuICAgIH1cbiAgICBsb2coKTtcbiAgfSk7XG59XG5cbmZ1bmN0aW9uIGxvZ0F1ZGl0cyhwbHVnaW5UaXRsZTogc3RyaW5nLCBhdWRpdHM6IEF1ZGl0UmVwb3J0W10pOiB2b2lkIHtcbiAgbG9nKCk7XG4gIGxvZyhib2xkLm1hZ2VudGFCcmlnaHQoYCR7cGx1Z2luVGl0bGV9IGF1ZGl0c2ApKTtcbiAgbG9nKCk7XG4gIGF1ZGl0cy5mb3JFYWNoKCh7IHNjb3JlLCB0aXRsZSwgZGlzcGxheVZhbHVlLCB2YWx1ZSB9KSA9PiB7XG4gICAgbG9nUm93KHNjb3JlLCB0aXRsZSwgZGlzcGxheVZhbHVlIHx8IGAke3ZhbHVlfWApO1xuICB9KTtcbn1cblxuZnVuY3Rpb24gbG9nUm93KHNjb3JlOiBudW1iZXIsIHRpdGxlOiBzdHJpbmcsIHZhbHVlPzogc3RyaW5nKTogdm9pZCB7XG4gIHVpKCkucm93KFtcbiAgICB7XG4gICAgICB0ZXh0OiBhcHBseVNjb3JlQ29sb3IoeyBzY29yZSwgdGV4dDogJ1x1MjVDRicgfSksXG4gICAgICB3aWR0aDogMixcbiAgICAgIHBhZGRpbmc6IFswLCAxLCAwLCAwXSxcbiAgICB9LFxuICAgIHtcbiAgICAgIHRleHQ6IHRpdGxlLFxuICAgICAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIEB0eXBlc2NyaXB0LWVzbGludC9uby1tYWdpYy1udW1iZXJzXG4gICAgICBwYWRkaW5nOiBbMCwgMywgMCwgMF0sXG4gICAgfSxcbiAgICAuLi4odmFsdWVcbiAgICAgID8gW1xuICAgICAgICAgIHtcbiAgICAgICAgICAgIHRleHQ6IGN5YW5CcmlnaHQodmFsdWUpLFxuICAgICAgICAgICAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIEB0eXBlc2NyaXB0LWVzbGludC9uby1tYWdpYy1udW1iZXJzXG4gICAgICAgICAgICB3aWR0aDogMjAsXG4gICAgICAgICAgICBwYWRkaW5nOiBbMCwgMCwgMCwgMF0sXG4gICAgICAgICAgfSxcbiAgICAgICAgXVxuICAgICAgOiBbXSksXG4gIF0pO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gbG9nQ2F0ZWdvcmllcyh7XG4gIHBsdWdpbnMsXG4gIGNhdGVnb3JpZXMsXG59OiBSZXF1aXJlZDxQaWNrPFNjb3JlZFJlcG9ydCwgJ3BsdWdpbnMnIHwgJ2NhdGVnb3JpZXMnPj4pOiB2b2lkIHtcbiAgY29uc3QgaEFsaWduID0gKGlkeDogbnVtYmVyKSA9PiAoaWR4ID09PSAwID8gJ2xlZnQnIDogJ3JpZ2h0Jyk7XG5cbiAgY29uc3Qgcm93cyA9IGNhdGVnb3JpZXMubWFwKCh7IHRpdGxlLCBzY29yZSwgcmVmcywgaXNCaW5hcnkgfSkgPT4gW1xuICAgIHRpdGxlLFxuICAgIGAke2JpbmFyeUljb25QcmVmaXgoc2NvcmUsIGlzQmluYXJ5KX0ke2FwcGx5U2NvcmVDb2xvcih7IHNjb3JlIH0pfWAsXG4gICAgY291bnRDYXRlZ29yeUF1ZGl0cyhyZWZzLCBwbHVnaW5zKSxcbiAgXSk7XG4gIGNvbnN0IHRhYmxlID0gdWkoKS50YWJsZSgpO1xuICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgQHR5cGVzY3JpcHQtZXNsaW50L25vLW1hZ2ljLW51bWJlcnNcbiAgdGFibGUuY29sdW1uV2lkdGhzKFtURVJNSU5BTF9XSURUSCAtIDkgLSAxMCAtIDQsIDksIDEwXSk7XG4gIHRhYmxlLmhlYWQoXG4gICAgUkVQT1JUX1JBV19PVkVSVklFV19UQUJMRV9IRUFERVJTLm1hcCgoaGVhZGluZywgaWR4KSA9PiAoe1xuICAgICAgY29udGVudDogY3lhbihoZWFkaW5nKSxcbiAgICAgIGhBbGlnbjogaEFsaWduKGlkeCksXG4gICAgfSkpLFxuICApO1xuICByb3dzLmZvckVhY2gocm93ID0+XG4gICAgdGFibGUucm93KFxuICAgICAgcm93Lm1hcCgoY29udGVudCwgaWR4KSA9PiAoe1xuICAgICAgICBjb250ZW50OiBjb250ZW50LnRvU3RyaW5nKCksXG4gICAgICAgIGhBbGlnbjogaEFsaWduKGlkeCksXG4gICAgICB9KSksXG4gICAgKSxcbiAgKTtcblxuICBsb2coYm9sZC5tYWdlbnRhQnJpZ2h0KCdDYXRlZ29yaWVzJykpO1xuICBsb2coKTtcbiAgdGFibGUucmVuZGVyKCk7XG4gIGxvZygpO1xufVxuXG4vLyBAVE9ETyByZWZhY3RvciBgaXNCaW5hcnk6IGJvb2xlYW5gIHRvIGB0YXJnZXRTY29yZTogbnVtYmVyYCAjNzEzXG5leHBvcnQgZnVuY3Rpb24gYmluYXJ5SWNvblByZWZpeChcbiAgc2NvcmU6IG51bWJlcixcbiAgaXNCaW5hcnk6IGJvb2xlYW4gfCB1bmRlZmluZWQsXG4pOiBzdHJpbmcge1xuICByZXR1cm4gdGFyZ2V0U2NvcmVJY29uKHNjb3JlLCBpc0JpbmFyeSA/IDEgOiB1bmRlZmluZWQsIHtcbiAgICBwYXNzSWNvbjogYm9sZChncmVlbignXHUyNzEzJykpLFxuICAgIGZhaWxJY29uOiBib2xkKHJlZCgnXHUyNzE3JykpLFxuICAgIHBvc3RmaXg6ICcgJyxcbiAgfSk7XG59XG4iLCAiY29uc3QgX19pbmplY3RlZF9maWxlbmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy91dGlscy9zcmMvbGliL3pvZC12YWxpZGF0aW9uLnRzXCI7Y29uc3QgX19pbmplY3RlZF9kaXJuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3V0aWxzL3NyYy9saWJcIjtjb25zdCBfX2luamVjdGVkX2ltcG9ydF9tZXRhX3VybF9fID0gXCJmaWxlOi8vL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvdXRpbHMvc3JjL2xpYi96b2QtdmFsaWRhdGlvbi50c1wiO2ltcG9ydCB7IGJvbGQsIHJlZCB9IGZyb20gJ2Fuc2lzJztcbmltcG9ydCB0eXBlIHsgTWVzc2FnZUJ1aWxkZXIgfSBmcm9tICd6b2QtdmFsaWRhdGlvbi1lcnJvcic7XG5cbmV4cG9ydCBmdW5jdGlvbiBmb3JtYXRFcnJvclBhdGgoZXJyb3JQYXRoOiAoc3RyaW5nIHwgbnVtYmVyKVtdKTogc3RyaW5nIHtcbiAgcmV0dXJuIGVycm9yUGF0aFxuICAgIC5tYXAoKGtleSwgaW5kZXgpID0+IHtcbiAgICAgIGlmICh0eXBlb2Yga2V5ID09PSAnbnVtYmVyJykge1xuICAgICAgICByZXR1cm4gYFske2tleX1dYDtcbiAgICAgIH1cbiAgICAgIHJldHVybiBpbmRleCA+IDAgPyBgLiR7a2V5fWAgOiBrZXk7XG4gICAgfSlcbiAgICAuam9pbignJyk7XG59XG5cbmV4cG9ydCBjb25zdCB6b2RFcnJvck1lc3NhZ2VCdWlsZGVyOiBNZXNzYWdlQnVpbGRlciA9IGlzc3VlcyA9PlxuICBpc3N1ZXNcbiAgICAubWFwKGlzc3VlID0+IHtcbiAgICAgIGNvbnN0IGZvcm1hdHRlZE1lc3NhZ2UgPSByZWQoYCR7Ym9sZChpc3N1ZS5jb2RlKX06ICR7aXNzdWUubWVzc2FnZX1gKTtcbiAgICAgIGNvbnN0IGZvcm1hdHRlZFBhdGggPSBmb3JtYXRFcnJvclBhdGgoaXNzdWUucGF0aCk7XG4gICAgICBpZiAoZm9ybWF0dGVkUGF0aCkge1xuICAgICAgICByZXR1cm4gYFZhbGlkYXRpb24gZXJyb3IgYXQgJHtib2xkKGZvcm1hdHRlZFBhdGgpfVxcbiR7Zm9ybWF0dGVkTWVzc2FnZX1cXG5gO1xuICAgICAgfVxuICAgICAgcmV0dXJuIGAke2Zvcm1hdHRlZE1lc3NhZ2V9XFxuYDtcbiAgICB9KVxuICAgIC5qb2luKCdcXG4nKTtcbiJdLAogICJtYXBwaW5ncyI6ICI7QUFBcVEsT0FBTztBQUM1USxTQUFTLEtBQUFB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O0FBQUEsRUFDdkQsQ0FBQztBQUNIOyIsCiAgIm5hbWVzIjogWyJ6IiwgInBhdGgiLCAicGF0aCIsICJ1aSIsICJwYXRoIiwgImJvbGQiLCAicGF0aCIsICJ1aSIsICJ6IiwgInoiLCAiY29uZmlnIiwgImdyb3VwcyIsICJhdWRpdHMiLCAiY2FsY3VsYXRlQ292ZXJhZ2UiLCAiY29uZmlnIiwgImNhbGN1bGF0ZUNvdmVyYWdlIiwgImNvbmZpZyIsICJjcmVhdGVSZXF1aXJlIiwgInBhdGgiLCAiZmlsZVVSTFRvUGF0aCIsICJ6IiwgInoiLCAic2x1Z2lmeSIsICJ0b0FycmF5IiwgInBhdGgiLCAiZXhpc3RzIiwgInRvQXJyYXkiLCAidWkiLCAiZXhpc3RzIiwgInRvQXJyYXkiLCAidWkiLCAiRVNMaW50IiwgIndyaXRlRmlsZSIsICJwYXRoIiwgImVuc3VyZURpcmVjdG9yeUV4aXN0cyIsICJmaWxlUGF0aFRvQ2xpQXJnIiwgInBsdWdpbldvcmtEaXIiLCAicmVhZEpzb25GaWxlIiwgImRpc3RpbmN0IiwgImV4ZWN1dGVQcm9jZXNzIiwgImZpbGVQYXRoVG9DbGlBcmciLCAidG9BcnJheSIsICJ1aSIsICJXT1JLRElSIiwgInBsdWdpbldvcmtEaXIiLCAiUlVOTkVSX09VVFBVVF9QQVRIIiwgInBhdGgiLCAiUExVR0lOX0NPTkZJR19QQVRIIiwgInBhdGgiLCAiZmlsZUV4aXN0cyIsICJ0b0FycmF5IiwgImNyZWF0ZVJlcXVpcmUiLCAicGF0aCIsICJmaWxlVVJMVG9QYXRoIiwgInoiLCAieiIsICJvYmplY3RUb0tleXMiLCAicGF0aCIsICJvYmplY3RUb0tleXMiLCAicmVhZEpzb25GaWxlIiwgIm9iamVjdFRvRW50cmllcyIsICJvYmplY3RUb0VudHJpZXMiLCAib2JqZWN0VG9FbnRyaWVzIiwgIm9iamVjdFRvRW50cmllcyIsICJvYmplY3RUb0tleXMiLCAib2JqZWN0VG9LZXlzIiwgIm9iamVjdFRvRW50cmllcyIsICJmcm9tSnNvbkxpbmVzIiwgIm9iamVjdEZyb21FbnRyaWVzIiwgIm9iamVjdFRvRW50cmllcyIsICJvYmplY3RUb0tleXMiLCAid3JpdGVGaWxlIiwgInBhdGgiLCAiZW5zdXJlRGlyZWN0b3J5RXhpc3RzIiwgImV4ZWN1dGVQcm9jZXNzIiwgImZpbGVQYXRoVG9DbGlBcmciLCAib2JqZWN0RnJvbUVudHJpZXMiLCAicmVhZEpzb25GaWxlIiwgIm9iamVjdFRvRW50cmllcyIsICJwYXRoIiwgInBsdWdpbldvcmtEaXIiLCAiV09SS0RJUiIsICJwbHVnaW5Xb3JrRGlyIiwgIlJVTk5FUl9PVVRQVVRfUEFUSCIsICJwYXRoIiwgIlBMVUdJTl9DT05GSUdfUEFUSCIsICJtZCIsICJvYmplY3RGcm9tRW50cmllcyIsICJvYmplY3RUb0tleXMiLCAib2JqZWN0VG9LZXlzIiwgInBhdGgiLCAiZmlsZUV4aXN0cyIsICJleGVjdXRlUHJvY2VzcyIsICJjcmVhdGVSZXF1aXJlIiwgInBhdGgiLCAicGF0aCIsICJib2xkIiwgInVpIiwgInBhdGgiLCAicGF0aCIsICJwYXRoIiwgImVuc3VyZURpcmVjdG9yeUV4aXN0cyIsICJib2xkIiwgImltcG9ydE1vZHVsZSIsICJyZWFkSnNvbkZpbGUiLCAidWkiLCAiYm9sZCIsICJ5ZWxsb3ciLCAidWkiLCAidGFibGVTY2hlbWEiLCAiZm9ybWF0Qnl0ZXMiLCAiZm9ybWF0RHVyYXRpb24iLCAiaHRtbCIsICJib2xkIiwgInVpIiwgImJvbGQiLCAidG9BcnJheSIsICJjb25maWciLCAiY29uZmlnIiwgImV4aXN0cyIsICJtZCIsICJib2xkIiwgInJlYWRGaWxlIiwgInBhdGgiLCAicGF0aCIsICJzaW1wbGVHaXQiLCAicGxhdGZvcm0iLCAiY29uZmlnIiwgImNhdGVnb3JpZXMiLCAiYm9sZCIsICJncmF5IiwgIk1hcmtkb3duRG9jdW1lbnQiLCAibWQiLCAibWQiLCAicGF0aCIsICJNYXJrZG93bkRvY3VtZW50IiwgIm1kIiwgIk1hcmtkb3duRG9jdW1lbnQiLCAibWQiLCAiTWFya2Rvd25Eb2N1bWVudCIsICJtZCIsICJwYXRoIiwgImJvbGQiLCAiZ3JlZW4iLCAiYm9sZCIsICJyZWQiLCAieiJdCn0K diff --git a/code-pushup.config.ts b/code-pushup.config.ts index d1b674a1d..ddfcd59f6 100644 --- a/code-pushup.config.ts +++ b/code-pushup.config.ts @@ -40,5 +40,22 @@ export default mergeConfigs( 'https://github.com/code-pushup/cli?tab=readme-ov-file#code-pushup-cli/', ), await eslintCoreConfigNx(), - await docCoverageCoreConfig(), + await docCoverageCoreConfig({ + sourceGlob: [ + 'packages/**/src/**/*.ts', + '!**/*.spec.ts', + '!**/*.test.ts', + '!**/implementation/**', + '!**/internal/**', + ], + onlyAudits: [ + 'methods-coverage', + 'functions-coverage', + 'types-coverage', + 'classes-coverage', + 'interfaces-coverage', + 'enums-coverage', + 'type-aliases-coverage', + ], + }), ); diff --git a/code-pushup.preset.ts b/code-pushup.preset.ts index 979610544..1060dd1e9 100644 --- a/code-pushup.preset.ts +++ b/code-pushup.preset.ts @@ -5,8 +5,14 @@ import type { import coveragePlugin, { getNxCoveragePaths, } from './packages/plugin-coverage/src/index.js'; -import docCoveragePlugin from './packages/plugin-doc-coverage/src/index.js'; -import { docCoverageAudits } from './packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.js'; +import docCoveragePlugin, { + DocCoveragePluginConfig, +} from './packages/plugin-doc-coverage/src/index.js'; +import { + PLUGIN_SLUG, + groups, +} from './packages/plugin-doc-coverage/src/lib/constants.js'; +import { filterGroupsByOnlyAudits } from './packages/plugin-doc-coverage/src/lib/utils.js'; import eslintPlugin, { eslintConfigFromAllNxProjects, eslintConfigFromNxProject, @@ -84,19 +90,23 @@ export const eslintCategories: CategoryConfig[] = [ }, ]; -export const docCoverageCategories: CategoryConfig[] = [ - { - slug: 'doc-coverage', - title: 'Documentation coverage', - description: 'Measures how much of your code is **documented**.', - refs: docCoverageAudits.map(audit => ({ - weight: 1, - type: 'audit', - plugin: 'doc-coverage', - slug: audit.slug, - })), - }, -]; +export function getDocCoverageCategories( + config: DocCoveragePluginConfig, +): CategoryConfig[] { + return [ + { + slug: 'doc-coverage-cat', + title: 'Documentation coverage', + description: 'Measures how much of your code is **documented**.', + refs: filterGroupsByOnlyAudits(groups, config).map(group => ({ + weight: 1, + type: 'group', + plugin: PLUGIN_SLUG, + slug: group.slug, + })), + }, + ]; +} export const coverageCategories: CategoryConfig[] = [ { @@ -130,15 +140,12 @@ export const lighthouseCoreConfig = async ( }; }; -export const docCoverageCoreConfig = async (): Promise => { +export const docCoverageCoreConfig = async ( + config: DocCoveragePluginConfig, +): Promise => { return { - plugins: [ - await docCoveragePlugin({ - language: 'typescript', - sourceGlob: 'packages/**/*.ts', - }), - ], - categories: docCoverageCategories, + plugins: [await docCoveragePlugin(config)], + categories: getDocCoverageCategories(config), }; }; diff --git a/packages/plugin-doc-coverage/mocks/component-mock.spec.ts b/packages/plugin-doc-coverage/mocks/component-mock.spec.ts new file mode 100644 index 000000000..62998c2a9 --- /dev/null +++ b/packages/plugin-doc-coverage/mocks/component-mock.spec.ts @@ -0,0 +1,16 @@ +import { describe, expect, it } from 'vitest'; +import { DUMMY_FUNCTION, DUMMY_FUNCTION_2 } from './component-mock'; + +export function shouldnotBeHere() { + return 'Hello World'; +} + +describe('component-mock', () => { + it('should return Hello World', () => { + expect(DUMMY_FUNCTION()).toBe('Hello World'); + }); + + it('should return Hello World 2', () => { + expect(DUMMY_FUNCTION_2()).toBe('Hello World 2'); + }); +}); diff --git a/packages/plugin-doc-coverage/mocks/component-mock.ts b/packages/plugin-doc-coverage/mocks/component-mock.ts index acd80f441..5b2da7c27 100644 --- a/packages/plugin-doc-coverage/mocks/component-mock.ts +++ b/packages/plugin-doc-coverage/mocks/component-mock.ts @@ -10,40 +10,40 @@ export function DUMMY_FUNCTION_2() { return 'Hello World 2'; } -class DummyClass { - /** - * Dummy property that returns 'Hello World 3'. - * @returns {string} - The string 'Hello World 3'. - */ - dummyProperty = 'Hello World 3'; - - /** - * Dummy method that returns 'Hello World 4'. - * @returns {string} - The string 'Hello World 4'. - */ - dummyMethod() { - return 'Hello World 4'; - } - - constructor() { - this.dummyProperty = 'Hello World 3'; - } -} - -export default DummyClass; - -export const variableDummy = 'Hello World 5'; - -export const variableDummy2 = 'Hello World 6'; - -/** Dummy variable that returns 'Hello World 7'. */ -export const variableDummy3 = 'Hello World 7'; - -/** Dummy interface that returns 'Hello World 8'. */ -export interface DummyInterface { - dummyProperty: string; - dummyMethod(): string; -} - -/** Dummy type that returns 'Hello World 9'. */ -export type DummyType = string; +// class DummyClass { +// /** +// * Dummy property that returns 'Hello World 3'. +// * @returns {string} - The string 'Hello World 3'. +// */ +// dummyProperty = 'Hello World 3'; + +// /** +// * Dummy method that returns 'Hello World 4'. +// * @returns {string} - The string 'Hello World 4'. +// */ +// dummyMethod() { +// return 'Hello World 4'; +// } + +// constructor() { +// this.dummyProperty = 'Hello World 3'; +// } +// } + +// export default DummyClass; + +// export const variableDummy = 'Hello World 5'; + +// export const variableDummy2 = 'Hello World 6'; + +// /** Dummy variable that returns 'Hello World 7'. */ +// export const variableDummy3 = 'Hello World 7'; + +// /** Dummy interface that returns 'Hello World 8'. */ +// export interface DummyInterface { +// dummyProperty: string; +// dummyMethod(): string; +// } + +// /** Dummy type that returns 'Hello World 9'. */ +// export type DummyType = string; diff --git a/packages/plugin-doc-coverage/mocks/fixtures/angular/app.component.css b/packages/plugin-doc-coverage/mocks/fixtures/angular/app.component.css new file mode 100644 index 000000000..f3958f2b4 --- /dev/null +++ b/packages/plugin-doc-coverage/mocks/fixtures/angular/app.component.css @@ -0,0 +1,4 @@ +h1 { + color: #336699; + text-align: center; +} diff --git a/packages/plugin-doc-coverage/mocks/fixtures/angular/app.component.html b/packages/plugin-doc-coverage/mocks/fixtures/angular/app.component.html new file mode 100644 index 000000000..b6515528b --- /dev/null +++ b/packages/plugin-doc-coverage/mocks/fixtures/angular/app.component.html @@ -0,0 +1 @@ +

{{ title }}

diff --git a/packages/plugin-doc-coverage/mocks/fixtures/angular/app.component.spec.ts b/packages/plugin-doc-coverage/mocks/fixtures/angular/app.component.spec.ts new file mode 100644 index 000000000..c89f47dd8 --- /dev/null +++ b/packages/plugin-doc-coverage/mocks/fixtures/angular/app.component.spec.ts @@ -0,0 +1,3 @@ +function notRealisticFunction() { + return 'notRealisticFunction'; +} diff --git a/packages/plugin-doc-coverage/mocks/fixtures/angular/app.component.ts b/packages/plugin-doc-coverage/mocks/fixtures/angular/app.component.ts new file mode 100644 index 000000000..2fc2b165f --- /dev/null +++ b/packages/plugin-doc-coverage/mocks/fixtures/angular/app.component.ts @@ -0,0 +1,18 @@ +/** + * Basic Angular component that displays a welcome message + */ +export class AppComponent { + protected readonly title = 'My Angular App'; + + /** + * Dummy method that returns a welcome message + * @returns {string} - The welcome message + */ + getWelcomeMessage() { + return 'Welcome to My Angular App!'; + } + + sendEvent() { + return 'Event sent'; + } +} diff --git a/packages/plugin-doc-coverage/mocks/fixtures/angular/map-event.function.ts b/packages/plugin-doc-coverage/mocks/fixtures/angular/map-event.function.ts new file mode 100644 index 000000000..9cd32ce8a --- /dev/null +++ b/packages/plugin-doc-coverage/mocks/fixtures/angular/map-event.function.ts @@ -0,0 +1,8 @@ +export function mapEventToCustomEvent(event: string) { + return event; +} + +/** Commented */ +export function mapCustomEventToEvent(event: string) { + return event; +} diff --git a/packages/plugin-doc-coverage/mocks/source-files.mock.ts b/packages/plugin-doc-coverage/mocks/source-files.mock.ts new file mode 100644 index 000000000..bc0ad113c --- /dev/null +++ b/packages/plugin-doc-coverage/mocks/source-files.mock.ts @@ -0,0 +1,114 @@ +import { + ClassDeclaration, + EnumDeclaration, + FunctionDeclaration, + InterfaceDeclaration, + SourceFile, + SyntaxKind, + TypeAliasDeclaration, +} from 'ts-morph'; +import type { CoverageType } from '../src/lib/models'; + +export function sourceFileMock( + file: string, + nodes: Partial>>, +): SourceFile { + return { + getFilePath: () => file as any, + getClasses: () => + nodes.classes + ? (Object.entries(nodes.classes).map(([line, isCommented]) => + nodeMock({ + coverageType: 'classes', + line: Number(line), + file, + isCommented, + }), + ) as unknown as ClassDeclaration[]) + : [], + getFunctions: () => + nodes.functions + ? (Object.entries(nodes.functions).map(([line, isCommented]) => + nodeMock({ + coverageType: 'functions', + line: Number(line), + file, + isCommented, + }), + ) as unknown as FunctionDeclaration[]) + : [], + getEnums: () => + nodes.enums + ? (Object.entries(nodes.enums).map(([line, isCommented]) => + nodeMock({ + coverageType: 'enums', + line: Number(line), + file, + isCommented, + }), + ) as unknown as EnumDeclaration[]) + : [], + getTypeAliases: () => + nodes.types + ? (Object.entries(nodes.types).map(([line, isCommented]) => + nodeMock({ + coverageType: 'types', + line: Number(line), + file, + isCommented, + }), + ) as unknown as TypeAliasDeclaration[]) + : [], + getInterfaces: () => + nodes.interfaces + ? (Object.entries(nodes.interfaces).map(([line, isCommented]) => + nodeMock({ + coverageType: 'interfaces', + line: Number(line), + file, + isCommented, + }), + ) as unknown as InterfaceDeclaration[]) + : [], + } as SourceFile; +} + +export function nodeMock(options: { + coverageType: CoverageType; + line: number; + file: string; + isCommented: boolean; +}) { + return { + getKind: () => getKindFromCoverageType(options.coverageType), + getJsDocs: () => (options.isCommented ? ['Comment'] : []), + getName: () => 'test', + getStartLineNumber: () => options.line, + // Only for classes + getMethods: () => [], + getProperties: () => [], + }; +} + +function getKindFromCoverageType(coverageType: CoverageType): SyntaxKind { + switch (coverageType) { + case 'classes': + return SyntaxKind.ClassDeclaration; + case 'methods': + return SyntaxKind.MethodDeclaration; + case 'functions': + return SyntaxKind.FunctionDeclaration; + case 'interfaces': + return SyntaxKind.InterfaceDeclaration; + case 'enums': + return SyntaxKind.EnumDeclaration; + case 'variables': + return SyntaxKind.VariableDeclaration; + case 'properties': + return SyntaxKind.PropertyDeclaration; + case 'types': + return SyntaxKind.TypeAliasDeclaration; + default: + throw new Error(`Unsupported syntax kind: ${coverageType}`); + } +} diff --git a/packages/plugin-doc-coverage/src/lib/config.ts b/packages/plugin-doc-coverage/src/lib/config.ts index 853099796..52ccb5cb8 100644 --- a/packages/plugin-doc-coverage/src/lib/config.ts +++ b/packages/plugin-doc-coverage/src/lib/config.ts @@ -1,13 +1,10 @@ import { z } from 'zod'; -export type DocType = 'percentage-coverage'; - export const docCoveragePluginConfigSchema = z.object({ + onlyAudits: z.array(z.string()).optional(), sourceGlob: z - .string({ - description: 'Glob pattern to find source files', - }) - .default('src/**/*.{ts,tsx}'), + .array(z.string()) + .default(['src/**/*.{ts,tsx}', '!**/*.spec.ts', '!**/*.test.ts']), }); export type DocCoveragePluginConfig = z.infer< diff --git a/packages/plugin-doc-coverage/src/lib/config.unit.test.ts b/packages/plugin-doc-coverage/src/lib/config.unit.test.ts index 4cb85ed8a..63d0007b9 100644 --- a/packages/plugin-doc-coverage/src/lib/config.unit.test.ts +++ b/packages/plugin-doc-coverage/src/lib/config.unit.test.ts @@ -8,7 +8,7 @@ describe('docCoveragePluginConfigSchema', () => { it('accepts a valid source glob pattern', () => { expect(() => docCoveragePluginConfigSchema.parse({ - sourceGlob: 'src/**/*.{ts,tsx}', + sourceGlob: ['src/**/*.{ts,tsx}', '!**/*.spec.ts', '!**/*.test.ts'], } satisfies DocCoveragePluginConfig), ).not.toThrow(); }); diff --git a/packages/plugin-doc-coverage/src/lib/constants.ts b/packages/plugin-doc-coverage/src/lib/constants.ts new file mode 100644 index 000000000..458409066 --- /dev/null +++ b/packages/plugin-doc-coverage/src/lib/constants.ts @@ -0,0 +1,68 @@ +import type { Audit, Group } from '@code-pushup/models'; +import type { AuditSlug } from './models'; + +export const PLUGIN_SLUG = 'doc-coverage'; + +export const AUDITS_MAP: Record = { + 'classes-coverage': { + slug: 'classes-coverage', + title: 'Classes coverage', + description: 'Coverage of classes', + }, + 'methods-coverage': { + slug: 'methods-coverage', + title: 'Methods coverage', + description: 'Coverage of methods', + }, + 'functions-coverage': { + slug: 'functions-coverage', + title: 'Functions coverage', + description: 'Coverage of functions', + }, + 'interfaces-coverage': { + slug: 'interfaces-coverage', + title: 'Interfaces coverage', + description: 'Coverage of interfaces', + }, + 'variables-coverage': { + slug: 'variables-coverage', + title: 'Variables coverage', + description: 'Coverage of variables', + }, + 'properties-coverage': { + slug: 'properties-coverage', + title: 'Properties coverage', + description: 'Coverage of properties', + }, + 'types-coverage': { + slug: 'types-coverage', + title: 'Types coverage', + description: 'Coverage of types', + }, + 'enums-coverage': { + slug: 'enums-coverage', + title: 'Enums coverage', + description: 'Coverage of enums', + }, +} as const; + +export const groups: Group[] = [ + { + slug: 'documentation-coverage', + title: 'Documentation coverage', + description: 'Documentation coverage', + refs: Object.keys(AUDITS_MAP).map(slug => { + switch (slug as AuditSlug) { + case 'classes-coverage': + case 'functions-coverage': + case 'methods-coverage': + return { slug, weight: 2 }; + case 'interfaces-coverage': + case 'properties-coverage': + case 'types-coverage': + default: + return { slug, weight: 1 }; + } + }), + }, +]; diff --git a/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.ts b/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.ts index 37d54858e..371409e1b 100644 --- a/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.ts +++ b/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.ts @@ -1,12 +1,22 @@ -import { createRequire } from 'node:module'; -import path from 'node:path'; -import { fileURLToPath } from 'node:url'; import type { PluginConfig } from '@code-pushup/models'; import { type DocCoveragePluginConfig, docCoveragePluginConfigSchema, } from './config.js'; -import { createRunnerConfig } from './runner/index.js'; +import { PLUGIN_SLUG, groups } from './constants.js'; +import { createRunnerFunction } from './runner/runner.js'; +import { + filterAuditsByPluginConfig, + filterGroupsByOnlyAudits, +} from './utils.js'; + +const PLUGIN_TITLE = 'Documentation coverage'; + +const PLUGIN_DESCRIPTION = + 'Official Code PushUp documentation coverage plugin.'; + +const PLUGIN_DOCS_URL = + 'https://www.npmjs.com/package/@code-pushup/doc-coverage-plugin/'; /** * Instantiates Code PushUp documentation coverage plugin for core config. @@ -26,40 +36,19 @@ import { createRunnerConfig } from './runner/index.js'; * * @returns Plugin configuration. */ - -export const docCoverageAudits = [ - { - slug: 'percentage-coverage', - title: 'Percentage of codebase with documentation', - description: 'Measures how many % of the codebase have documentation.', - }, -]; - export async function docCoveragePlugin( config: DocCoveragePluginConfig, ): Promise { const docCoverageConfig = docCoveragePluginConfigSchema.parse(config); - const runnerScriptPath = path.join( - fileURLToPath(path.dirname(import.meta.url)), - '..', - 'bin.js', - ); - - const packageJson = createRequire(import.meta.url)( - '../../package.json', - ) as typeof import('../../package.json'); - return { - slug: 'doc-coverage', - title: 'Documentation coverage', + slug: PLUGIN_SLUG, + title: PLUGIN_TITLE, icon: 'folder-src', - description: 'Official Code PushUp documentation coverage plugin.', - docsUrl: 'https://www.npmjs.com/package/@code-pushup/doc-coverage-plugin/', - packageName: packageJson.name, - version: packageJson.version, - audits: docCoverageAudits, - // groups: [group], - runner: await createRunnerConfig(runnerScriptPath, docCoverageConfig), + description: PLUGIN_DESCRIPTION, + docsUrl: PLUGIN_DOCS_URL, + groups: filterGroupsByOnlyAudits(groups, docCoverageConfig), + audits: filterAuditsByPluginConfig(docCoverageConfig), + runner: createRunnerFunction(docCoverageConfig), }; } diff --git a/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.unit.test.ts b/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.unit.test.ts index 992f6de6c..d3d178a91 100644 --- a/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.unit.test.ts +++ b/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.unit.test.ts @@ -12,7 +12,9 @@ vi.mock('./runner/index.ts', () => ({ describe('docCoveragePlugin', () => { it('should initialise a Documentation coverage plugin', async () => { await expect( - docCoveragePlugin({ sourceGlob: 'src/**/*.ts' }), + docCoveragePlugin({ + sourceGlob: ['src/**/*.ts', '!**/*.spec.ts', '!**/*.test.ts'], + }), ).resolves.toStrictEqual( expect.objectContaining({ slug: 'doc-coverage', @@ -25,7 +27,9 @@ describe('docCoveragePlugin', () => { it('should generate percentage coverage audit', async () => { await expect( - docCoveragePlugin({ sourceGlob: 'src/**/*.ts' }), + docCoveragePlugin({ + sourceGlob: ['src/**/*.ts', '!**/*.spec.ts', '!**/*.test.ts'], + }), ).resolves.toStrictEqual( expect.objectContaining({ audits: [ @@ -43,7 +47,9 @@ describe('docCoveragePlugin', () => { it('should include package metadata', async () => { await expect( - docCoveragePlugin({ sourceGlob: 'src/**/*.ts' }), + docCoveragePlugin({ + sourceGlob: ['src/**/*.ts', '!**/*.spec.ts', '!**/*.test.ts'], + }), ).resolves.toStrictEqual( expect.objectContaining({ icon: 'folder-src', diff --git a/packages/plugin-doc-coverage/src/lib/models.ts b/packages/plugin-doc-coverage/src/lib/models.ts index dd61e1bdc..4500a13d1 100644 --- a/packages/plugin-doc-coverage/src/lib/models.ts +++ b/packages/plugin-doc-coverage/src/lib/models.ts @@ -1,30 +1,3 @@ -export type UndocumentedItem = { - file: string; - type: string; - name: string; - line: number; - class?: string; -}; +import type { CoverageType } from './runner/models'; -export type CoverageByType = { - functions: number; - variables: number; - classes: number; - methods: number; - properties: number; - interfaces: number; - types: number; -}; - -export type CoverageKey = keyof CoverageByType; - -export type DocumentationStats = { - documented: number; - total: number; -}; - -export type CoverageResult = { - undocumentedItems: UndocumentedItem[]; - currentCoverage: number; - coverageByType: CoverageByType; -}; +export type AuditSlug = `${CoverageType}-coverage`; diff --git a/packages/plugin-doc-coverage/src/lib/runner/__snapshots__/doc-processer.integration.test.ts.snap b/packages/plugin-doc-coverage/src/lib/runner/__snapshots__/doc-processer.integration.test.ts.snap new file mode 100644 index 000000000..33a38bd67 --- /dev/null +++ b/packages/plugin-doc-coverage/src/lib/runner/__snapshots__/doc-processer.integration.test.ts.snap @@ -0,0 +1,139 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`processDocCoverage > should succesfully get the right number of ts files 1`] = ` +{ + "classes": { + "coverage": 100, + "issues": [], + "nodesCount": 1, + }, + "enums": { + "coverage": 100, + "issues": [], + "nodesCount": 0, + }, + "functions": { + "coverage": 33.333333333333336, + "issues": [ + { + "file": "/home/alejandro/dev/code-pushup-cli/packages/plugin-doc-coverage/mocks/fixtures/angular/app.component.spec.ts", + "line": 1, + "name": "notRealisticFunction", + "type": "functions", + }, + { + "file": "/home/alejandro/dev/code-pushup-cli/packages/plugin-doc-coverage/mocks/fixtures/angular/map-event.function.ts", + "line": 1, + "name": "mapEventToCustomEvent", + "type": "functions", + }, + ], + "nodesCount": 3, + }, + "interfaces": { + "coverage": 100, + "issues": [], + "nodesCount": 0, + }, + "methods": { + "coverage": 50, + "issues": [ + { + "file": "/home/alejandro/dev/code-pushup-cli/packages/plugin-doc-coverage/mocks/fixtures/angular/app.component.ts", + "line": 15, + "name": "sendEvent", + "type": "methods", + }, + ], + "nodesCount": 2, + }, + "properties": { + "coverage": 0, + "issues": [ + { + "file": "/home/alejandro/dev/code-pushup-cli/packages/plugin-doc-coverage/mocks/fixtures/angular/app.component.ts", + "line": 5, + "name": "title", + "type": "properties", + }, + ], + "nodesCount": 1, + }, + "types": { + "coverage": 100, + "issues": [], + "nodesCount": 0, + }, + "variables": { + "coverage": 100, + "issues": [], + "nodesCount": 0, + }, +} +`; + +exports[`processDocCoverage > should succesfully get the right number of ts files and not include spec files 1`] = ` +{ + "classes": { + "coverage": 100, + "issues": [], + "nodesCount": 1, + }, + "enums": { + "coverage": 100, + "issues": [], + "nodesCount": 0, + }, + "functions": { + "coverage": 50, + "issues": [ + { + "file": "/home/alejandro/dev/code-pushup-cli/packages/plugin-doc-coverage/mocks/fixtures/angular/map-event.function.ts", + "line": 1, + "name": "mapEventToCustomEvent", + "type": "functions", + }, + ], + "nodesCount": 2, + }, + "interfaces": { + "coverage": 100, + "issues": [], + "nodesCount": 0, + }, + "methods": { + "coverage": 50, + "issues": [ + { + "file": "/home/alejandro/dev/code-pushup-cli/packages/plugin-doc-coverage/mocks/fixtures/angular/app.component.ts", + "line": 15, + "name": "sendEvent", + "type": "methods", + }, + ], + "nodesCount": 2, + }, + "properties": { + "coverage": 0, + "issues": [ + { + "file": "/home/alejandro/dev/code-pushup-cli/packages/plugin-doc-coverage/mocks/fixtures/angular/app.component.ts", + "line": 5, + "name": "title", + "type": "properties", + }, + ], + "nodesCount": 1, + }, + "types": { + "coverage": 100, + "issues": [], + "nodesCount": 0, + }, + "variables": { + "coverage": 100, + "issues": [], + "nodesCount": 0, + }, +} +`; diff --git a/packages/plugin-doc-coverage/src/lib/runner/__snapshots__/doc-processer.unit.test.ts.snap b/packages/plugin-doc-coverage/src/lib/runner/__snapshots__/doc-processer.unit.test.ts.snap new file mode 100644 index 000000000..b72b0a151 --- /dev/null +++ b/packages/plugin-doc-coverage/src/lib/runner/__snapshots__/doc-processer.unit.test.ts.snap @@ -0,0 +1,92 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`getUnprocessedCoverageReport > should produce a full report 1`] = ` +{ + "classes": { + "coverage": 33.333333333333336, + "issues": [ + { + "file": "test.ts", + "line": 4, + "name": "test", + "type": "classes", + }, + { + "file": "test.ts", + "line": 5, + "name": "test", + "type": "classes", + }, + ], + "nodesCount": 3, + }, + "enums": { + "coverage": 33.333333333333336, + "issues": [ + { + "file": "test.ts", + "line": 8, + "name": "test", + "type": "enums", + }, + { + "file": "test.ts", + "line": 9, + "name": "test", + "type": "enums", + }, + ], + "nodesCount": 3, + }, + "functions": { + "coverage": 100, + "issues": [], + "nodesCount": 3, + }, + "interfaces": { + "coverage": 66.66666666666667, + "issues": [ + { + "file": "test.ts", + "line": 15, + "name": "test", + "type": "interfaces", + }, + ], + "nodesCount": 3, + }, + "methods": { + "coverage": 100, + "issues": [], + "nodesCount": 0, + }, + "properties": { + "coverage": 100, + "issues": [], + "nodesCount": 0, + }, + "types": { + "coverage": 50, + "issues": [ + { + "file": "test.ts", + "line": 10, + "name": "test", + "type": "types", + }, + { + "file": "test.ts", + "line": 11, + "name": "test", + "type": "types", + }, + ], + "nodesCount": 4, + }, + "variables": { + "coverage": 100, + "issues": [], + "nodesCount": 0, + }, +} +`; diff --git a/packages/plugin-doc-coverage/src/lib/runner/constants.ts b/packages/plugin-doc-coverage/src/lib/runner/constants.ts deleted file mode 100644 index 9cf24d169..000000000 --- a/packages/plugin-doc-coverage/src/lib/runner/constants.ts +++ /dev/null @@ -1,42 +0,0 @@ -import path from 'node:path'; -import { pluginWorkDir } from '@code-pushup/utils'; - -export const WORKDIR = pluginWorkDir('doc-coverage'); - -export const RUNNER_OUTPUT_PATH = path.join(WORKDIR, 'runner-output.json'); - -export const PLUGIN_CONFIG_PATH = path.join( - process.cwd(), - WORKDIR, - 'plugin-config.json', -); - -export const enum ProgrammingLanguage { - JavaScript = 'javascript', - TypeScript = 'typescript', -} - -export const DEFAULT_SOURCE_GLOB = { - [ProgrammingLanguage.JavaScript]: '"src/**/*.js"', - [ProgrammingLanguage.TypeScript]: '"src/**/*.ts"', -}; - -export const DEFAULT_OUTPUT_FOLDER_PATH = './documentation'; - -export const COMMANDS_FOR_LANGUAGES: Readonly< - Record -> = { - [ProgrammingLanguage.JavaScript]: { - command: 'npx', - args: 'typedoc $sourceGlob --entryPointStrategy expand --plugin typedoc-plugin-coverage --coverageOutputType json --skipErrorChecking --out $outputFolderPath', - }, - [ProgrammingLanguage.TypeScript]: { - command: 'npx', - args: 'typedoc $sourceGlob --entryPointStrategy expand --plugin typedoc-plugin-coverage --coverageOutputType json --skipErrorChecking --out $outputFolderPath', - }, -} as const; - -export type TypedocResult = { - percent: number; - notDocumented: string[]; -}; diff --git a/packages/plugin-doc-coverage/src/lib/runner/doc-processer.integration.test.ts b/packages/plugin-doc-coverage/src/lib/runner/doc-processer.integration.test.ts index 94bebac27..51e607eb9 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/doc-processer.integration.test.ts +++ b/packages/plugin-doc-coverage/src/lib/runner/doc-processer.integration.test.ts @@ -1,21 +1,23 @@ import { processDocCoverage } from './doc-processer'; -describe('docProcesser', () => { - it('should successfully get documentation coverage', () => { - const results = processDocCoverage( - 'packages/plugin-doc-coverage/mocks/**/*.ts', - ); - console.log(results); - expect(results).toBeDefined(); - expect(results.currentCoverage).toBe(60); - expect(results.coverageByType).toEqual({ - functions: 50, - variables: 33.33, - classes: 0, - methods: 100, - properties: 100, - interfaces: 100, - types: 100, +describe('processDocCoverage', () => { + it('should succesfully get the right number of ts files', () => { + const results = processDocCoverage({ + sourceGlob: [ + 'packages/plugin-doc-coverage/mocks/fixtures/angular/**/*.ts', + ], }); + expect(results).toMatchSnapshot(); + }); + + it('should succesfully get the right number of ts files and not include spec files', () => { + const results = processDocCoverage({ + sourceGlob: [ + 'packages/plugin-doc-coverage/mocks/fixtures/angular/**/*.ts', + '!**/*.spec.ts', + '!**/*.test.ts', + ], + }); + expect(results).toMatchSnapshot(); }); }); diff --git a/packages/plugin-doc-coverage/src/lib/runner/doc-processer.ts b/packages/plugin-doc-coverage/src/lib/runner/doc-processer.ts index 46a2cbacf..fbf333bfc 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/doc-processer.ts +++ b/packages/plugin-doc-coverage/src/lib/runner/doc-processer.ts @@ -1,289 +1,115 @@ -import { Project } from 'ts-morph'; +import { ClassDeclaration, Project, SourceFile } from 'ts-morph'; +import type { DocCoveragePluginConfig } from '../config.js'; import type { - CoverageByType, - CoverageKey, CoverageResult, - DocumentationStats, - UndocumentedItem, -} from '../models.js'; - -/* eslint-disable @typescript-eslint/no-explicit-any */ -/* eslint-disable functional/immutable-data */ -/* eslint-disable @typescript-eslint/max-params */ -/* eslint-disable functional/no-let */ + CoverageType, + UnprocessedCoverageResult, +} from './models.js'; +import { + calculateCoverage, + createEmptyUnprocessedCoverageReport, + getCoverageTypeFromKind, +} from './utils.js'; /** * Processes documentation coverage for TypeScript files in the specified path * @param toInclude - The file path pattern to include for documentation analysis * @returns {CoverageResult} Object containing coverage statistics and undocumented items */ -export function processDocCoverage(toInclude: string): CoverageResult { +export function processDocCoverage( + config: DocCoveragePluginConfig, +): CoverageResult { const project = new Project(); - project.addSourceFilesAtPaths(toInclude); - - const stats: Record = { - functions: { documented: 0, total: 0 }, - variables: { documented: 0, total: 0 }, - classes: { documented: 0, total: 0 }, - methods: { documented: 0, total: 0 }, - properties: { documented: 0, total: 0 }, - interfaces: { documented: 0, total: 0 }, - types: { documented: 0, total: 0 }, - }; - - const undocumentedItems: UndocumentedItem[] = []; - - project.getSourceFiles().forEach(sourceFile => { - if (isTestFile(sourceFile.getFilePath())) { - return; - } - - processClassDeclarations(sourceFile, undocumentedItems, stats); - processDeclarations(sourceFile, undocumentedItems, stats); - }); - - return { - undocumentedItems, - currentCoverage: calculateOverallCoverage(stats), - coverageByType: calculateCoverageByType(stats), - }; -} - -/** - * Checks if a file is a test file based on its path - * @param filePath - The path of the file to check - * @returns {boolean} True if the file is a test file, false otherwise - */ -function isTestFile(filePath: string): boolean { - return filePath.includes('.spec.') || filePath.includes('.test.'); + project.addSourceFilesAtPaths(config.sourceGlob); + return getUnprocessedCoverageReport(project.getSourceFiles()); } /** - * Creates an undocumented item entry - * @param file - The file path where the item was found - * @param type - The type of the undocumented item - * @param name - The name of the undocumented item - * @param line - The line number where the item appears - * @returns {UndocumentedItem} The undocumented item entry + * Gets the unprocessed coverage report from the source files + * @param sourceFiles - The source files to process + * @returns {UnprocessedCoverageResult} The unprocessed coverage report */ -function addUndocumentedItem( - file: string, - type: CoverageKey, - name: string, - line: number, -): UndocumentedItem { - return { file, type, name, line }; -} - -/** - * Processes class declarations in a source file and updates documentation statistics - * @param sourceFile - The source file to process - * @param undocumentedItems - Array to store undocumented items found - * @param stats - Object to track documentation statistics - */ -function processClassDeclarations( - sourceFile: any, - undocumentedItems: UndocumentedItem[], - stats: Record, -): void { - sourceFile.getClasses().forEach((classDeclaration: any) => { - const className = classDeclaration.getName() || 'Anonymous Class'; - const filePath = sourceFile.getFilePath(); - stats.classes.total++; - - if (classDeclaration.getJsDocs().length === 0) { - undocumentedItems.push( - addUndocumentedItem( - filePath, - 'classes', - className, - classDeclaration.getStartLineNumber(), - ), +export function getUnprocessedCoverageReport(sourceFiles: SourceFile[]) { + const unprocessedCoverageReport = sourceFiles.reduce( + (coverageReportOfAllFiles, sourceFile) => { + // Info of the file + const filePath = sourceFile.getFilePath(); + const classes = sourceFile.getClasses(); + + // All nodes of the file + const allNodesFromFile = [ + ...sourceFile.getFunctions(), + ...classes, + ...getClassNodes(classes), + ...sourceFile.getTypeAliases(), + ...sourceFile.getEnums(), + ...sourceFile.getInterfaces(), + // ...sourceFile.getVariableStatements().flatMap(statement => statement.getDeclarations()) + ]; + + const coverageReportOfCurrentFile = allNodesFromFile.reduce( + (acc, node) => { + const nodeType = getCoverageTypeFromKind(node.getKind()); + acc[nodeType].nodesCount++; + if (node.getJsDocs().length === 0) { + acc[nodeType].issues.push({ + file: filePath, + type: nodeType, + name: node.getName() || '', + line: node.getStartLineNumber(), + }); + } + return acc; + }, + createEmptyUnprocessedCoverageReport(), ); - } else { - stats.classes.documented++; - } - // Process properties - classDeclaration.getProperties().forEach((property: any) => { - stats.properties.total++; - if (property.getJsDocs().length === 0) { - undocumentedItems.push( - addUndocumentedItem( - filePath, - 'properties', - property.getName(), - property.getStartLineNumber(), - ), - ); - } else { - stats.properties.documented++; - } - }); - - // Process methods - classDeclaration.getMethods().forEach((method: any) => { - stats.methods.total++; - if (method.getJsDocs().length === 0) { - undocumentedItems.push( - addUndocumentedItem( - filePath, - 'methods', - method.getName(), - method.getStartLineNumber(), - ), - ); - } else { - stats.methods.documented++; - } - }); - }); -} - -/** - * Processes declarations (functions, variables, interfaces, and types) in a source file - * @param sourceFile - The source file to process - * @param undocumentedItems - Array to store undocumented items found - * @param stats - Object to track documentation statistics - */ -function processDeclarations( - sourceFile: any, - undocumentedItems: UndocumentedItem[], - stats: Record, -): void { - const filePath = sourceFile.getFilePath(); - - // Process functions - processItems( - sourceFile.getFunctions(), - 'functions', - item => item.getName() || 'Anonymous Function', - filePath, - undocumentedItems, - stats, + return mergeCoverageResults( + coverageReportOfAllFiles, + coverageReportOfCurrentFile, + ); + }, + createEmptyUnprocessedCoverageReport(), ); - // Process variables - sourceFile.getVariableStatements().forEach((statement: any) => { - statement.getDeclarations().forEach((declaration: any) => { - stats.variables.total++; - if (statement.getJsDocs().length === 0) { - undocumentedItems.push( - addUndocumentedItem( - filePath, - 'variables', - declaration.getName(), - declaration.getStartLineNumber(), - ), - ); - } else { - stats.variables.documented++; - } - }); - }); - - // Process interfaces and types - processItems( - sourceFile.getInterfaces(), - 'interfaces', - item => item.getName(), - filePath, - undocumentedItems, - stats, - ); - processItems( - sourceFile.getTypeAliases(), - 'types', - item => item.getName(), - filePath, - undocumentedItems, - stats, - ); + return calculateCoverage(unprocessedCoverageReport); } /** - * Generic function to process a collection of items and update documentation statistics - * @param items - Array of items to process - * @param type - The type of items being processed - * @param getName - Function to extract the name from an item - * @param filePath - The path of the file being processed - * @param undocumentedItems - Array to store undocumented items found - * @param stats - Object to track documentation statistics + * Merges two coverage results + * @param results - The first empty coverage result + * @param current - The second coverage result + * @returns {UnprocessedCoverageResult} The merged coverage result */ -function processItems( - items: any[], - type: CoverageKey, - getName: (item: any) => string, - filePath: string, - undocumentedItems: UndocumentedItem[], - stats: Record, -): void { - items.forEach(item => { - stats[type].total++; - if (item.getJsDocs().length === 0) { - undocumentedItems.push( - addUndocumentedItem( - filePath, +export function mergeCoverageResults( + results: UnprocessedCoverageResult, + current: Partial, +) { + return { + ...Object.fromEntries( + Object.entries(results).map(([key, value]) => { + const node = value as CoverageResult[CoverageType]; + const type = key as CoverageType; + return [ type, - getName(item), - item.getStartLineNumber(), - ), - ); - } else { - stats[type].documented++; - } - }); -} - -/** - * Calculates the overall documentation coverage percentage - * @param stats - Object containing documentation statistics - * @returns {number} The overall coverage percentage (0-100) - */ -function calculateOverallCoverage( - stats: Record, -): number { - let totalDocumented = 0; - let totalItems = 0; - - Object.values(stats).forEach(({ documented, total }) => { - totalDocumented += documented; - totalItems += total; - }); - - return totalItems === 0 ? 0 : (totalDocumented / totalItems) * 100; + { + nodesCount: node.nodesCount + (current[type]?.nodesCount ?? 0), + issues: [...node.issues, ...(current[type]?.issues ?? [])], + }, + ]; + }), + ), + } as UnprocessedCoverageResult; } /** - * Calculates documentation coverage percentage for each type - * @param stats - Object containing documentation statistics - * @returns {CoverageByType} Object containing coverage percentages for each type + * Gets the nodes from a class + * @param classNodes - The class nodes to process + * @returns {Node[]} The nodes from the class */ -function calculateCoverageByType( - stats: Record, -): CoverageByType { - const calculatePercentage = (documented: number, total: number) => - total === 0 ? 0 : Number(((documented / total) * 100).toFixed(2)); - - return { - functions: calculatePercentage( - stats.functions.documented, - stats.functions.total, - ), - variables: calculatePercentage( - stats.variables.documented, - stats.variables.total, - ), - classes: calculatePercentage(stats.classes.documented, stats.classes.total), - methods: calculatePercentage(stats.methods.documented, stats.methods.total), - properties: calculatePercentage( - stats.properties.documented, - stats.properties.total, - ), - interfaces: calculatePercentage( - stats.interfaces.documented, - stats.interfaces.total, - ), - types: calculatePercentage(stats.types.documented, stats.types.total), - }; +export function getClassNodes(classNodes: ClassDeclaration[]) { + return classNodes.flatMap(classNode => [ + ...classNode.getMethods(), + ...classNode.getProperties(), + ]); } diff --git a/packages/plugin-doc-coverage/src/lib/runner/doc-processer.unit.test.ts b/packages/plugin-doc-coverage/src/lib/runner/doc-processer.unit.test.ts new file mode 100644 index 000000000..21c829349 --- /dev/null +++ b/packages/plugin-doc-coverage/src/lib/runner/doc-processer.unit.test.ts @@ -0,0 +1,198 @@ +import type { ClassDeclaration } from 'ts-morph'; +import { nodeMock, sourceFileMock } from './../../../mocks/source-files.mock'; +import { + getClassNodes, + getUnprocessedCoverageReport, + mergeCoverageResults, +} from './doc-processer'; +import type { UnprocessedCoverageResult } from './models'; + +describe('getUnprocessedCoverageReport', () => { + it('should produce a full report', () => { + const results = getUnprocessedCoverageReport([ + sourceFileMock('test.ts', { + functions: { 1: true, 2: true, 3: true }, + classes: { 4: false, 5: false, 6: true }, + enums: { 7: true, 8: false, 9: false }, + types: { 10: false, 11: false, 12: true, 40: true }, + interfaces: { 13: true, 14: true, 15: false }, + properties: { 16: false, 17: false, 18: false }, + variables: { 22: true, 23: true, 24: true }, + }), + ]); + expect(results).toMatchSnapshot(); + }); + + it('should accept array of source files', () => { + const results = getUnprocessedCoverageReport([ + sourceFileMock('test.ts', { functions: { 1: true, 2: true, 3: false } }), + ]); + expect(results).toBeDefined(); + }); + + it('should count nodes correctly', () => { + const results = getUnprocessedCoverageReport([ + sourceFileMock('test.ts', { functions: { 1: true, 2: true, 3: false } }), + ]); + + expect(results.functions.nodesCount).toBe(3); + }); + + it('should collect uncommented nodes issues', () => { + const results = getUnprocessedCoverageReport([ + sourceFileMock('test.ts', { functions: { 1: true, 2: false, 3: false } }), + ]); + + expect(results.functions.issues.length).toBe(2); + }); + + it('should collect valid issues', () => { + const results = getUnprocessedCoverageReport([ + sourceFileMock('test.ts', { functions: { 1: false } }), + ]); + + expect(results.functions.issues).toStrictEqual([ + { + line: 1, + file: 'test.ts', + type: 'functions', + name: 'test', + }, + ]); + }); + + it('should calculate coverage correctly', () => { + const results = getUnprocessedCoverageReport([ + sourceFileMock('test.ts', { functions: { 1: true, 2: false } }), + ]); + + expect(results.functions.coverage).toBe(50); + }); +}); + +describe('mergeCoverageResults', () => { + const emptyResult: UnprocessedCoverageResult = { + enums: { nodesCount: 0, issues: [] }, + interfaces: { nodesCount: 0, issues: [] }, + types: { nodesCount: 0, issues: [] }, + functions: { nodesCount: 0, issues: [] }, + variables: { nodesCount: 0, issues: [] }, + classes: { nodesCount: 0, issues: [] }, + methods: { nodesCount: 0, issues: [] }, + properties: { nodesCount: 0, issues: [] }, + }; + + it.each([ + 'enums', + 'interfaces', + 'types', + 'functions', + 'variables', + 'classes', + 'methods', + 'properties', + ])('should merge results on top-level property: %s', type => { + const secondResult = { + [type]: { + nodesCount: 1, + issues: [{ file: 'test2.ts', line: 1, name: 'test2', type }], + }, + }; + + const results = mergeCoverageResults( + emptyResult, + secondResult as Partial, + ); + expect(results).toStrictEqual( + expect.objectContaining({ + [type]: { + nodesCount: 1, + issues: [{ file: 'test2.ts', line: 1, name: 'test2', type }], + }, + }), + ); + }); + + it('should merge empty results', () => { + const results = mergeCoverageResults(emptyResult, emptyResult); + expect(results).toStrictEqual(emptyResult); + }); + + it('should merge second level property nodesCount', () => { + const results = mergeCoverageResults( + { + ...emptyResult, + enums: { nodesCount: 1, issues: [] }, + }, + { + enums: { nodesCount: 1, issues: [] }, + }, + ); + expect(results.enums.nodesCount).toBe(2); + }); + + it('should merge second level property issues', () => { + const results = mergeCoverageResults( + { + ...emptyResult, + enums: { + nodesCount: 0, + issues: [ + { + file: 'file.enum-first.ts', + line: 6, + name: 'file.enum-first', + type: 'enums', + }, + ], + }, + }, + { + enums: { + nodesCount: 0, + issues: [ + { + file: 'file.enum-second.ts', + line: 5, + name: 'file.enum-second', + type: 'enums', + }, + ], + }, + }, + ); + expect(results.enums.issues).toStrictEqual([ + { + file: 'file.enum-first.ts', + line: 6, + name: 'file.enum-first', + type: 'enums', + }, + { + file: 'file.enum-second.ts', + line: 5, + name: 'file.enum-second', + type: 'enums', + }, + ]); + }); +}); + +describe('getClassNodes', () => { + it('should return all nodes from a class', () => { + const nodeMock1 = nodeMock({ + coverageType: 'classes', + line: 1, + file: 'test.ts', + isCommented: false, + }); + + const classNodeSpy = vi.spyOn(nodeMock1, 'getMethods'); + const propertyNodeSpy = vi.spyOn(nodeMock1, 'getProperties'); + + getClassNodes([nodeMock1] as unknown as ClassDeclaration[]); + + expect(classNodeSpy).toHaveBeenCalledTimes(1); + expect(propertyNodeSpy).toHaveBeenCalledTimes(1); + }); +}); diff --git a/packages/plugin-doc-coverage/src/lib/runner/doc-processer.unit.test.ts.snap b/packages/plugin-doc-coverage/src/lib/runner/doc-processer.unit.test.ts.snap new file mode 100644 index 000000000..220467a98 --- /dev/null +++ b/packages/plugin-doc-coverage/src/lib/runner/doc-processer.unit.test.ts.snap @@ -0,0 +1,125 @@ +{ + "classes": { + "coverage": 0, + "issues": [ + { + "file": "test.ts", + "line": 4, + "name": "test", + "type": "classes", + }, + { + "file": "test.ts", + "line": 5, + "name": "test", + "type": "classes", + }, + { + "file": "test.ts", + "line": 6, + "name": "test", + "type": "classes", + }, + ], + "nodesCount": 3, + }, + "enums": { + "coverage": 0, + "issues": [ + { + "file": "test.ts", + "line": 7, + "name": "test", + "type": "enums", + }, + { + "file": "test.ts", + "line": 8, + "name": "test", + "type": "enums", + }, + { + "file": "test.ts", + "line": 9, + "name": "test", + "type": "enums", + }, + ], + "nodesCount": 3, + }, + "functions": { + "coverage": 66.66666666666667, + "issues": [ + { + "file": "test.ts", + "line": 3, + "name": "test", + "type": "functions", + }, + ], + "nodesCount": 3, + }, + "interfaces": { + "coverage": 0, + "issues": [ + { + "file": "test.ts", + "line": 13, + "name": "test", + "type": "interfaces", + }, + { + "file": "test.ts", + "line": 14, + "name": "test", + "type": "interfaces", + }, + { + "file": "test.ts", + "line": 15, + "name": "test", + "type": "interfaces", + }, + ], + "nodesCount": 3, + }, + "methods": { + "coverage": 100, + "issues": [], + "nodesCount": 0, + }, + "properties": { + "coverage": 100, + "issues": [], + "nodesCount": 0, + }, + "types": { + "coverage": 0, + "issues": [ + { + "file": "test.ts", + "line": 10, + "name": "test", + "type": "types", + }, + { + "file": "test.ts", + "line": 11, + "name": "test", + "type": "types", + }, + { + "file": "test.ts", + "line": 12, + "name": "test", + "type": "types", + }, + ], + "nodesCount": 3, + }, + "variables": { + "coverage": 100, + "issues": [], + "nodesCount": 0, + }, +} \ No newline at end of file diff --git a/packages/plugin-doc-coverage/src/lib/runner/index.ts b/packages/plugin-doc-coverage/src/lib/runner/index.ts deleted file mode 100644 index c56713ada..000000000 --- a/packages/plugin-doc-coverage/src/lib/runner/index.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { bold } from 'ansis'; -import { writeFile } from 'node:fs/promises'; -import path from 'node:path'; -import type { AuditOutput, RunnerConfig } from '@code-pushup/models'; -import { - ProcessError, - ensureDirectoryExists, - filePathToCliArg, - readJsonFile, - ui, -} from '@code-pushup/utils'; -import type { DocCoveragePluginConfig } from '../config.js'; -import type { CoverageResult } from '../models.js'; -import { PLUGIN_CONFIG_PATH, RUNNER_OUTPUT_PATH } from './constants.js'; -import { processDocCoverage } from './doc-processer.js'; - -export { PLUGIN_CONFIG_PATH, RUNNER_OUTPUT_PATH } from './constants.js'; - -export async function executeRunner(): Promise { - try { - const config = - await readJsonFile(PLUGIN_CONFIG_PATH); - console.log(config.sourceGlob, 'dadawdawd'); - const processResult = processDocCoverage(config.sourceGlob); - await _createFinalReport(processResult); - } catch (error) { - if (error instanceof ProcessError) { - ui().logger.error(bold('stdout from failed Typedoc process:')); - ui().logger.error(error.stdout); - ui().logger.error(bold('stderr from failed Typedoc process:')); - ui().logger.error(error.stderr); - } - throw new Error( - 'Doc Coverage plugin: Running Typedoc failed. Please check the error above.', - ); - } -} - -export async function createRunnerConfig( - scriptPath: string, - config: DocCoveragePluginConfig, -): Promise { - await ensureDirectoryExists(path.dirname(PLUGIN_CONFIG_PATH)); - await writeFile(PLUGIN_CONFIG_PATH, JSON.stringify(config)); - - return { - command: 'node', - args: [filePathToCliArg(scriptPath)], - outputFile: RUNNER_OUTPUT_PATH, - }; -} - -/** - * Create the final report. - * @param coverageResult - The coverage result. - */ -async function _createFinalReport( - coverageResult: CoverageResult, -): Promise { - const auditOutputs: AuditOutput[] = [ - { - slug: 'percentage-coverage', - value: coverageResult.currentCoverage, - score: coverageResult.currentCoverage / 100, - displayValue: `${coverageResult.currentCoverage} %`, - details: { - issues: coverageResult.undocumentedItems.map(item => ({ - message: `Missing documentation for a ${item.type}`, - source: { file: item.file, position: { startLine: item.line } }, - severity: 'warning', - })), - }, - }, - ]; - - await ensureDirectoryExists(path.dirname(RUNNER_OUTPUT_PATH)); - await writeFile(RUNNER_OUTPUT_PATH, JSON.stringify(auditOutputs)); -} diff --git a/packages/plugin-doc-coverage/src/lib/runner/models.ts b/packages/plugin-doc-coverage/src/lib/runner/models.ts new file mode 100644 index 000000000..81849c428 --- /dev/null +++ b/packages/plugin-doc-coverage/src/lib/runner/models.ts @@ -0,0 +1,37 @@ +import type { SyntaxKind } from 'ts-morph'; + +type SyntaxKindToStringLiteral = { + [SyntaxKind.ClassDeclaration]: 'classes'; + [SyntaxKind.MethodDeclaration]: 'methods'; + [SyntaxKind.FunctionDeclaration]: 'functions'; + [SyntaxKind.InterfaceDeclaration]: 'interfaces'; + [SyntaxKind.EnumDeclaration]: 'enums'; + [SyntaxKind.VariableDeclaration]: 'variables'; + [SyntaxKind.PropertyDeclaration]: 'properties'; + [SyntaxKind.TypeAliasDeclaration]: 'types'; +}; + +export type CoverageType = + SyntaxKindToStringLiteral[keyof SyntaxKindToStringLiteral]; + +export type UndocumentedNode = { + file: string; + type: CoverageType; + name: string; + line: number; + class?: string; +}; + +export type CoverageData = { + issues: UndocumentedNode[]; + nodesCount: number; +}; + +export type UnprocessedCoverageResult = Record; + +export type CoverageResult = Record< + CoverageType, + CoverageData & { + coverage: number; + } +>; diff --git a/packages/plugin-doc-coverage/src/lib/runner/runner.integration.test.ts b/packages/plugin-doc-coverage/src/lib/runner/runner.integration.test.ts deleted file mode 100644 index 8d8fe081a..000000000 --- a/packages/plugin-doc-coverage/src/lib/runner/runner.integration.test.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { writeFile } from 'node:fs/promises'; -import { describe, it } from 'vitest'; -import type { AuditOutputs, RunnerConfig } from '@code-pushup/models'; -import { readJsonFile, removeDirectoryIfExists } from '@code-pushup/utils'; -import type { DocCoveragePluginConfig } from '../config.js'; -import { - PLUGIN_CONFIG_PATH, - RUNNER_OUTPUT_PATH, - WORKDIR, -} from './constants.js'; -import { createRunnerConfig, executeRunner } from './index.js'; - -describe('createRunnerConfig', () => { - it('should create a valid runner config', async () => { - const runnerConfig = await createRunnerConfig('executeRunner.ts', { - sourceGlob: 'src/**/*.ts', - }); - expect(runnerConfig).toStrictEqual({ - command: 'node', - args: ['"executeRunner.ts"'], - outputFile: expect.stringContaining('runner-output.json'), - }); - }); - - it('should provide plugin config to runner in JSON file', async () => { - await removeDirectoryIfExists(WORKDIR); - - const pluginConfig: DocCoveragePluginConfig = { - sourceGlob: 'src/**/*.ts', - }; - - await createRunnerConfig('executeRunner.ts', pluginConfig); - - const config = - await readJsonFile(PLUGIN_CONFIG_PATH); - expect(config).toStrictEqual(pluginConfig); - }); -}); - -describe('executeRunner', () => { - it( - 'should successfully execute runner', - { - timeout: 60 * 1000, - }, - async () => { - const config: DocCoveragePluginConfig = { - sourceGlob: 'packages/plugin-doc-coverage/mocks/*.ts', - }; - - await writeFile(PLUGIN_CONFIG_PATH, JSON.stringify(config)); - await executeRunner(); - - const results = await readJsonFile(RUNNER_OUTPUT_PATH); - expect(results).toBeDefined(); - }, - ); -}); diff --git a/packages/plugin-doc-coverage/src/lib/runner/runner.ts b/packages/plugin-doc-coverage/src/lib/runner/runner.ts new file mode 100644 index 000000000..91e8282e1 --- /dev/null +++ b/packages/plugin-doc-coverage/src/lib/runner/runner.ts @@ -0,0 +1,49 @@ +import type { AuditOutputs, RunnerFunction } from '@code-pushup/models'; +import type { DocCoveragePluginConfig } from '../config'; +import { processDocCoverage } from './doc-processer'; +import type { CoverageResult, CoverageType } from './models'; + +export function createRunnerFunction( + config: DocCoveragePluginConfig, +): RunnerFunction { + return (): AuditOutputs => { + const coverageResult = processDocCoverage(config); + return trasformCoverageReportToAudits(coverageResult, config); + }; +} + +/** + * Transforms the coverage report into audit outputs. + * @param coverageResult - The coverage result containing undocumented items and coverage statistics + * @param options - Configuration options specifying which audits to include + * @returns Audit outputs with coverage scores and details about undocumented items + */ +export function trasformCoverageReportToAudits( + coverageResult: CoverageResult, + options: Pick, +): AuditOutputs { + return Object.entries(coverageResult) + .filter( + ([type]) => + !options.onlyAudits?.length || + options.onlyAudits.includes(`${type}-coverage`), + ) + .map(([type, items]) => { + const coverageType = type as CoverageType; + const coverage = items.coverage; + + return { + slug: `${coverageType}-coverage`, + value: coverage, + score: coverage / 100, + displayValue: `${coverage} %`, + details: { + issues: items.issues.map(({ file, line }) => ({ + message: 'Missing documentation', + source: { file, position: { startLine: line } }, + severity: 'warning', + })), + }, + }; + }); +} diff --git a/packages/plugin-doc-coverage/src/lib/runner/utils.ts b/packages/plugin-doc-coverage/src/lib/runner/utils.ts new file mode 100644 index 000000000..434187a00 --- /dev/null +++ b/packages/plugin-doc-coverage/src/lib/runner/utils.ts @@ -0,0 +1,61 @@ +import { SyntaxKind } from 'ts-morph'; +import type { + CoverageResult, + CoverageType, + UnprocessedCoverageResult, +} from './models'; + +export function createEmptyUnprocessedCoverageReport(): UnprocessedCoverageResult { + return { + enums: { nodesCount: 0, issues: [] }, + interfaces: { nodesCount: 0, issues: [] }, + types: { nodesCount: 0, issues: [] }, + functions: { nodesCount: 0, issues: [] }, + variables: { nodesCount: 0, issues: [] }, + classes: { nodesCount: 0, issues: [] }, + methods: { nodesCount: 0, issues: [] }, + properties: { nodesCount: 0, issues: [] }, + }; +} + +export function calculateCoverage(result: UnprocessedCoverageResult) { + return Object.fromEntries( + Object.entries(result).map(([key, value]) => { + const type = key as CoverageType; + return [ + type, + { + coverage: + value.nodesCount === 0 + ? 100 + : (1 - value.issues.length / value.nodesCount) * 100, + issues: value.issues, + nodesCount: value.nodesCount, + }, + ]; + }), + ) as CoverageResult; +} + +export function getCoverageTypeFromKind(kind: SyntaxKind): CoverageType { + switch (kind) { + case SyntaxKind.ClassDeclaration: + return 'classes'; + case SyntaxKind.MethodDeclaration: + return 'methods'; + case SyntaxKind.FunctionDeclaration: + return 'functions'; + case SyntaxKind.InterfaceDeclaration: + return 'interfaces'; + case SyntaxKind.EnumDeclaration: + return 'enums'; + case SyntaxKind.VariableDeclaration: + return 'variables'; + case SyntaxKind.PropertyDeclaration: + return 'properties'; + case SyntaxKind.TypeAliasDeclaration: + return 'types'; + default: + throw new Error(`Unsupported syntax kind: ${kind}`); + } +} diff --git a/packages/plugin-doc-coverage/src/lib/utils.ts b/packages/plugin-doc-coverage/src/lib/utils.ts new file mode 100644 index 000000000..b3e7584cb --- /dev/null +++ b/packages/plugin-doc-coverage/src/lib/utils.ts @@ -0,0 +1,46 @@ +import type { Audit, Group } from '@code-pushup/models'; +import type { DocCoveragePluginConfig } from './config'; +import { AUDITS_MAP } from './constants'; + +/** + * Get audits based on the configuration. + * If no audits are specified, return all audits. + * If audits are specified, return only the specified audits. + * @param config - The configuration object. + * @returns The audits. + */ +export function filterAuditsByPluginConfig( + config: Pick, +): Audit[] { + const { onlyAudits } = config; + + if (!onlyAudits || onlyAudits.length === 0) { + return Object.values(AUDITS_MAP); + } + + return Object.values(AUDITS_MAP).filter(audit => + onlyAudits.includes(audit.slug), + ); +} + +/** + * Filter groups by the audits that are specified in the configuration. + * The groups refs are filtered to only include the audits that are specified in the configuration. + * @param groups - The groups to filter. + * @param options - The configuration object. + * @returns The filtered groups. + */ +export function filterGroupsByOnlyAudits( + groups: Group[], + options: Pick, +): Group[] { + const audits = filterAuditsByPluginConfig(options); + return groups + .map(group => ({ + ...group, + refs: group.refs.filter(ref => + audits.some(audit => audit.slug === ref.slug), + ), + })) + .filter(group => group.refs.length > 0); +} diff --git a/packages/plugin-doc-coverage/tsconfig.test.json b/packages/plugin-doc-coverage/tsconfig.test.json index 05637ee6e..9f29d6bb0 100644 --- a/packages/plugin-doc-coverage/tsconfig.test.json +++ b/packages/plugin-doc-coverage/tsconfig.test.json @@ -8,7 +8,6 @@ "vite.config.unit.ts", "vite.config.integration.ts", "mocks/**/*.ts", - "src/**/*.test.ts", - "mocks/component-mock.ts" + "src/**/*.test.ts" ] } From 40c8daa41852741d53b5c66b6f2ed5ded0fbc5d3 Mon Sep 17 00:00:00 2001 From: Alejandro Date: Fri, 20 Dec 2024 18:43:20 +0100 Subject: [PATCH 06/66] test(plugin-doc-coverage): finish remaining tests, add format to coverage --- .../src/lib/config.unit.test.ts | 81 +++++++++++++++--- .../src/lib/doc-coverage-plugin.ts | 6 +- .../src/lib/doc-coverage-plugin.unit.test.ts | 54 +++--------- .../doc-processer.integration.test.ts.snap | 2 +- .../doc-processer.unit.test.ts.snap | 6 +- .../src/lib/runner/utils.ts | 6 +- .../src/lib/runner/utils.unit.test.ts | 85 +++++++++++++++++++ .../src/lib/utils.unit.test.ts | 79 +++++++++++++++++ 8 files changed, 259 insertions(+), 60 deletions(-) create mode 100644 packages/plugin-doc-coverage/src/lib/runner/utils.unit.test.ts create mode 100644 packages/plugin-doc-coverage/src/lib/utils.unit.test.ts diff --git a/packages/plugin-doc-coverage/src/lib/config.unit.test.ts b/packages/plugin-doc-coverage/src/lib/config.unit.test.ts index 63d0007b9..66f33ab52 100644 --- a/packages/plugin-doc-coverage/src/lib/config.unit.test.ts +++ b/packages/plugin-doc-coverage/src/lib/config.unit.test.ts @@ -5,23 +5,80 @@ import { } from './config.js'; describe('docCoveragePluginConfigSchema', () => { - it('accepts a valid source glob pattern', () => { - expect(() => - docCoveragePluginConfigSchema.parse({ - sourceGlob: ['src/**/*.{ts,tsx}', '!**/*.spec.ts', '!**/*.test.ts'], - } satisfies DocCoveragePluginConfig), - ).not.toThrow(); + describe('sourceGlob', () => { + it('accepts a valid source glob pattern', () => { + expect(() => + docCoveragePluginConfigSchema.parse({ + sourceGlob: ['src/**/*.{ts,tsx}', '!**/*.spec.ts', '!**/*.test.ts'], + } satisfies DocCoveragePluginConfig), + ).not.toThrow(); + }); + + it('uses default value for missing sourceGlob', () => { + const result = docCoveragePluginConfigSchema.parse({}); + expect(result.sourceGlob).toEqual([ + 'src/**/*.{ts,tsx}', + '!**/*.spec.ts', + '!**/*.test.ts', + ]); + }); + + it('throws for invalid sourceGlob type', () => { + expect(() => + docCoveragePluginConfigSchema.parse({ + sourceGlob: 123, + }), + ).toThrow('Expected array'); + }); }); - it('not throws for missing sourceGlob', () => { - expect(() => docCoveragePluginConfigSchema.parse({})).not.toThrow(); + describe('onlyAudits', () => { + it('accepts valid audit slugs array', () => { + expect(() => + docCoveragePluginConfigSchema.parse({ + onlyAudits: ['functions-coverage', 'classes-coverage'], + sourceGlob: ['src/**/*.ts'], + }), + ).not.toThrow(); + }); + + it('accepts empty array for onlyAudits', () => { + expect(() => + docCoveragePluginConfigSchema.parse({ + onlyAudits: [], + sourceGlob: ['src/**/*.ts'], + }), + ).not.toThrow(); + }); + + it('allows onlyAudits to be undefined', () => { + const result = docCoveragePluginConfigSchema.parse({}); + expect(result.onlyAudits).toBeUndefined(); + }); + + it('throws for invalid onlyAudits type', () => { + expect(() => + docCoveragePluginConfigSchema.parse({ + onlyAudits: 'functions-coverage', + }), + ).toThrow('Expected array'); + }); + + it('throws for array with non-string elements', () => { + expect(() => + docCoveragePluginConfigSchema.parse({ + onlyAudits: [123, true], + }), + ).toThrow('Expected string'); + }); }); - it('throws for invalid sourceGlob type', () => { + it('accepts a complete valid configuration', () => { expect(() => docCoveragePluginConfigSchema.parse({ - sourceGlob: 123, - }), - ).toThrow('Expected string'); + sourceGlob: ['src/**/*.ts'], + onlyAudits: ['functions-coverage'], + } satisfies DocCoveragePluginConfig), + ).not.toThrow(); }); }); diff --git a/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.ts b/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.ts index 371409e1b..9d08784b1 100644 --- a/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.ts +++ b/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.ts @@ -10,12 +10,12 @@ import { filterGroupsByOnlyAudits, } from './utils.js'; -const PLUGIN_TITLE = 'Documentation coverage'; +export const PLUGIN_TITLE = 'Documentation coverage'; -const PLUGIN_DESCRIPTION = +export const PLUGIN_DESCRIPTION = 'Official Code PushUp documentation coverage plugin.'; -const PLUGIN_DOCS_URL = +export const PLUGIN_DOCS_URL = 'https://www.npmjs.com/package/@code-pushup/doc-coverage-plugin/'; /** diff --git a/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.unit.test.ts b/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.unit.test.ts index d3d178a91..fea278277 100644 --- a/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.unit.test.ts +++ b/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.unit.test.ts @@ -1,6 +1,12 @@ import { describe, expect, it } from 'vitest'; import type { RunnerConfig } from '@code-pushup/models'; -import { docCoveragePlugin } from './doc-coverage-plugin.js'; +import { PLUGIN_SLUG } from './constants.js'; +import { + PLUGIN_DESCRIPTION, + PLUGIN_DOCS_URL, + PLUGIN_TITLE, + docCoveragePlugin, +} from './doc-coverage-plugin.js'; vi.mock('./runner/index.ts', () => ({ createRunnerConfig: vi.fn().mockReturnValue({ @@ -17,46 +23,14 @@ describe('docCoveragePlugin', () => { }), ).resolves.toStrictEqual( expect.objectContaining({ - slug: 'doc-coverage', - title: 'Documentation coverage', - audits: expect.any(Array), - runner: expect.any(Object), - }), - ); - }); - - it('should generate percentage coverage audit', async () => { - await expect( - docCoveragePlugin({ - sourceGlob: ['src/**/*.ts', '!**/*.spec.ts', '!**/*.test.ts'], - }), - ).resolves.toStrictEqual( - expect.objectContaining({ - audits: [ - { - slug: 'percentage-coverage', - title: 'Percentage of codebase with documentation', - description: expect.stringContaining( - 'how many % of the codebase have documentation', - ), - }, - ], - }), - ); - }); - - it('should include package metadata', async () => { - await expect( - docCoveragePlugin({ - sourceGlob: ['src/**/*.ts', '!**/*.spec.ts', '!**/*.test.ts'], - }), - ).resolves.toStrictEqual( - expect.objectContaining({ + slug: PLUGIN_SLUG, + title: PLUGIN_TITLE, icon: 'folder-src', - description: expect.stringContaining('documentation coverage plugin'), - docsUrl: expect.stringContaining('npmjs.com'), - packageName: expect.any(String), - version: expect.any(String), + description: PLUGIN_DESCRIPTION, + docsUrl: PLUGIN_DOCS_URL, + groups: expect.any(Array), + audits: expect.any(Array), + runner: expect.any(Function), }), ); }); diff --git a/packages/plugin-doc-coverage/src/lib/runner/__snapshots__/doc-processer.integration.test.ts.snap b/packages/plugin-doc-coverage/src/lib/runner/__snapshots__/doc-processer.integration.test.ts.snap index 33a38bd67..2507460f6 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/__snapshots__/doc-processer.integration.test.ts.snap +++ b/packages/plugin-doc-coverage/src/lib/runner/__snapshots__/doc-processer.integration.test.ts.snap @@ -13,7 +13,7 @@ exports[`processDocCoverage > should succesfully get the right number of ts file "nodesCount": 0, }, "functions": { - "coverage": 33.333333333333336, + "coverage": 33.33, "issues": [ { "file": "/home/alejandro/dev/code-pushup-cli/packages/plugin-doc-coverage/mocks/fixtures/angular/app.component.spec.ts", diff --git a/packages/plugin-doc-coverage/src/lib/runner/__snapshots__/doc-processer.unit.test.ts.snap b/packages/plugin-doc-coverage/src/lib/runner/__snapshots__/doc-processer.unit.test.ts.snap index b72b0a151..1090891fe 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/__snapshots__/doc-processer.unit.test.ts.snap +++ b/packages/plugin-doc-coverage/src/lib/runner/__snapshots__/doc-processer.unit.test.ts.snap @@ -3,7 +3,7 @@ exports[`getUnprocessedCoverageReport > should produce a full report 1`] = ` { "classes": { - "coverage": 33.333333333333336, + "coverage": 33.33, "issues": [ { "file": "test.ts", @@ -21,7 +21,7 @@ exports[`getUnprocessedCoverageReport > should produce a full report 1`] = ` "nodesCount": 3, }, "enums": { - "coverage": 33.333333333333336, + "coverage": 33.33, "issues": [ { "file": "test.ts", @@ -44,7 +44,7 @@ exports[`getUnprocessedCoverageReport > should produce a full report 1`] = ` "nodesCount": 3, }, "interfaces": { - "coverage": 66.66666666666667, + "coverage": 66.67, "issues": [ { "file": "test.ts", diff --git a/packages/plugin-doc-coverage/src/lib/runner/utils.ts b/packages/plugin-doc-coverage/src/lib/runner/utils.ts index 434187a00..e9628eac6 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/utils.ts +++ b/packages/plugin-doc-coverage/src/lib/runner/utils.ts @@ -28,7 +28,11 @@ export function calculateCoverage(result: UnprocessedCoverageResult) { coverage: value.nodesCount === 0 ? 100 - : (1 - value.issues.length / value.nodesCount) * 100, + : Number( + ((1 - value.issues.length / value.nodesCount) * 100).toFixed( + 2, + ), + ), issues: value.issues, nodesCount: value.nodesCount, }, diff --git a/packages/plugin-doc-coverage/src/lib/runner/utils.unit.test.ts b/packages/plugin-doc-coverage/src/lib/runner/utils.unit.test.ts new file mode 100644 index 000000000..403c7b3ad --- /dev/null +++ b/packages/plugin-doc-coverage/src/lib/runner/utils.unit.test.ts @@ -0,0 +1,85 @@ +import { SyntaxKind } from 'ts-morph'; +import type { UnprocessedCoverageResult } from './models'; +import { + calculateCoverage, + createEmptyUnprocessedCoverageReport, + getCoverageTypeFromKind, +} from './utils'; + +describe('createEmptyUnprocessedCoverageReport', () => { + it('should create an empty report with all categories initialized', () => { + const result = createEmptyUnprocessedCoverageReport(); + + expect(result).toStrictEqual({ + enums: { nodesCount: 0, issues: [] }, + interfaces: { nodesCount: 0, issues: [] }, + types: { nodesCount: 0, issues: [] }, + functions: { nodesCount: 0, issues: [] }, + variables: { nodesCount: 0, issues: [] }, + classes: { nodesCount: 0, issues: [] }, + methods: { nodesCount: 0, issues: [] }, + properties: { nodesCount: 0, issues: [] }, + }); + }); +}); + +describe('calculateCoverage', () => { + it('should calculate 100% coverage when there are no nodes', () => { + const input: UnprocessedCoverageResult = + createEmptyUnprocessedCoverageReport(); + const result = calculateCoverage(input); + + Object.values(result).forEach(category => { + expect(category.coverage).toBe(100); + expect(category.nodesCount).toBe(0); + expect(category.issues).toEqual([]); + }); + }); + + it('should calculate correct coverage percentage with issues', () => { + const input: UnprocessedCoverageResult = { + ...createEmptyUnprocessedCoverageReport(), + functions: { + nodesCount: 4, + issues: [ + { type: 'functions', line: 1, file: 'test.ts', name: 'fn1' }, + { type: 'functions', line: 2, file: 'test.ts', name: 'fn2' }, + ], + }, + classes: { + nodesCount: 4, + issues: [ + { type: 'classes', line: 1, file: 'test.ts', name: 'Class1' }, + { type: 'classes', line: 2, file: 'test.ts', name: 'Class2' }, + { type: 'classes', line: 3, file: 'test.ts', name: 'Class3' }, + ], + }, + }; + + const result = calculateCoverage(input); + + expect(result.functions.coverage).toBe(50); + expect(result.classes.coverage).toBe(25); + }); +}); + +describe('getCoverageTypeFromKind', () => { + it.each([ + [SyntaxKind.ClassDeclaration, 'classes'], + [SyntaxKind.MethodDeclaration, 'methods'], + [SyntaxKind.FunctionDeclaration, 'functions'], + [SyntaxKind.InterfaceDeclaration, 'interfaces'], + [SyntaxKind.EnumDeclaration, 'enums'], + [SyntaxKind.VariableDeclaration, 'variables'], + [SyntaxKind.PropertyDeclaration, 'properties'], + [SyntaxKind.TypeAliasDeclaration, 'types'], + ])('should return %s for SyntaxKind.%s', (kind, expectedType) => { + expect(getCoverageTypeFromKind(kind)).toBe(expectedType); + }); + + it('should throw error for unsupported syntax kind', () => { + expect(() => getCoverageTypeFromKind(SyntaxKind.Unknown)).toThrow( + 'Unsupported syntax kind', + ); + }); +}); diff --git a/packages/plugin-doc-coverage/src/lib/utils.unit.test.ts b/packages/plugin-doc-coverage/src/lib/utils.unit.test.ts new file mode 100644 index 000000000..ec5ec4008 --- /dev/null +++ b/packages/plugin-doc-coverage/src/lib/utils.unit.test.ts @@ -0,0 +1,79 @@ +import type { Group } from '@code-pushup/models'; +import { AUDITS_MAP } from './constants'; +import { filterAuditsByPluginConfig, filterGroupsByOnlyAudits } from './utils'; + +describe('filterAuditsByPluginConfig', () => { + it('should return all audits when onlyAudits is not provided', () => { + const result = filterAuditsByPluginConfig({}); + expect(result).toStrictEqual(Object.values(AUDITS_MAP)); + }); + + it('should return all audits when onlyAudits is empty array', () => { + const result = filterAuditsByPluginConfig({ onlyAudits: [] }); + expect(result).toStrictEqual(Object.values(AUDITS_MAP)); + }); + + it('should return only specified audits when onlyAudits is provided', () => { + const onlyAudits = ['functions-coverage', 'classes-coverage']; + const result = filterAuditsByPluginConfig({ onlyAudits }); + + expect(result).toStrictEqual( + Object.values(AUDITS_MAP).filter(audit => + onlyAudits.includes(audit.slug), + ), + ); + }); +}); + +describe('filterGroupsByOnlyAudits', () => { + const mockGroups: Group[] = [ + { + title: 'Group 1', + slug: 'group-1', + refs: [ + { slug: 'functions-coverage', weight: 1 }, + { slug: 'classes-coverage', weight: 1 }, + ], + }, + { + title: 'Group 2', + slug: 'group-2', + refs: [ + { slug: 'types-coverage', weight: 1 }, + { slug: 'interfaces-coverage', weight: 1 }, + ], + }, + ]; + + it('should return all groups when onlyAudits is not provided', () => { + const result = filterGroupsByOnlyAudits(mockGroups, {}); + expect(result).toStrictEqual(mockGroups); + }); + + it('should return all groups when onlyAudits is empty array', () => { + const result = filterGroupsByOnlyAudits(mockGroups, { onlyAudits: [] }); + expect(result).toStrictEqual(mockGroups); + }); + + it('should filter groups based on specified audits', () => { + const result = filterGroupsByOnlyAudits(mockGroups, { + onlyAudits: ['functions-coverage'], + }); + + expect(result).toStrictEqual([ + { + title: 'Group 1', + slug: 'group-1', + refs: [{ slug: 'functions-coverage', weight: 1 }], + }, + ]); + }); + + it('should remove groups with no matching refs', () => { + const result = filterGroupsByOnlyAudits(mockGroups, { + onlyAudits: ['enums-coverage'], + }); + + expect(result).toStrictEqual([]); + }); +}); From 47486c80c6262017d6bc5e902bac50d712cae9c4 Mon Sep 17 00:00:00 2001 From: Alejandro Date: Fri, 20 Dec 2024 19:32:30 +0100 Subject: [PATCH 07/66] feat(plugin-doc-coverage): add skipAudits options to the plugin, create unit test for runner file --- code-pushup.config.ts | 41 +++-- .../plugin-doc-coverage/src/lib/config.ts | 18 ++- .../src/lib/config.unit.test.ts | 42 ++++- .../src/lib/doc-coverage-plugin.ts | 2 +- .../__snapshots__/runner.unit.test.ts.snap | 147 ++++++++++++++++++ .../src/lib/runner/models.ts | 6 + .../src/lib/runner/runner.ts | 19 ++- .../src/lib/runner/runner.unit.test.ts | 88 +++++++++++ .../src/lib/runner/utils.ts | 14 ++ packages/plugin-doc-coverage/src/lib/utils.ts | 24 +-- .../src/lib/utils.unit.test.ts | 19 ++- 11 files changed, 364 insertions(+), 56 deletions(-) create mode 100644 packages/plugin-doc-coverage/src/lib/runner/__snapshots__/runner.unit.test.ts.snap create mode 100644 packages/plugin-doc-coverage/src/lib/runner/runner.unit.test.ts diff --git a/code-pushup.config.ts b/code-pushup.config.ts index ddfcd59f6..0b2d52680 100644 --- a/code-pushup.config.ts +++ b/code-pushup.config.ts @@ -1,12 +1,6 @@ import 'dotenv/config'; import { z } from 'zod'; -import { - coverageCoreConfigNx, - docCoverageCoreConfig, - eslintCoreConfigNx, - jsPackagesCoreConfig, - lighthouseCoreConfig, -} from './code-pushup.preset.js'; +import { docCoverageCoreConfig } from './code-pushup.preset.js'; import type { CoreConfig } from './packages/models/src/index.js'; import { mergeConfigs } from './packages/utils/src/index.js'; @@ -33,13 +27,13 @@ const config: CoreConfig = { }; export default mergeConfigs( - config, - await coverageCoreConfigNx(), - await jsPackagesCoreConfig(), - await lighthouseCoreConfig( - 'https://github.com/code-pushup/cli?tab=readme-ov-file#code-pushup-cli/', - ), - await eslintCoreConfigNx(), + // config, + // await coverageCoreConfigNx(), + // await jsPackagesCoreConfig(), + // await lighthouseCoreConfig( + // 'https://github.com/code-pushup/cli?tab=readme-ov-file#code-pushup-cli/', + // ), + // await eslintCoreConfigNx(), await docCoverageCoreConfig({ sourceGlob: [ 'packages/**/src/**/*.ts', @@ -48,14 +42,15 @@ export default mergeConfigs( '!**/implementation/**', '!**/internal/**', ], - onlyAudits: [ - 'methods-coverage', - 'functions-coverage', - 'types-coverage', - 'classes-coverage', - 'interfaces-coverage', - 'enums-coverage', - 'type-aliases-coverage', - ], + skipAudits: ['methods-coverage'], + // onlyAudits: [ + // 'methods-coverage', + // 'functions-coverage', + // 'types-coverage', + // 'classes-coverage', + // 'interfaces-coverage', + // 'enums-coverage', + // 'type-aliases-coverage', + // ], }), ); diff --git a/packages/plugin-doc-coverage/src/lib/config.ts b/packages/plugin-doc-coverage/src/lib/config.ts index 52ccb5cb8..648420a1d 100644 --- a/packages/plugin-doc-coverage/src/lib/config.ts +++ b/packages/plugin-doc-coverage/src/lib/config.ts @@ -1,11 +1,17 @@ import { z } from 'zod'; -export const docCoveragePluginConfigSchema = z.object({ - onlyAudits: z.array(z.string()).optional(), - sourceGlob: z - .array(z.string()) - .default(['src/**/*.{ts,tsx}', '!**/*.spec.ts', '!**/*.test.ts']), -}); +export const docCoveragePluginConfigSchema = z + .object({ + skipAudits: z.array(z.string()).optional(), + onlyAudits: z.array(z.string()).optional(), + sourceGlob: z + .array(z.string()) + .default(['src/**/*.{ts,tsx}', '!**/*.spec.ts', '!**/*.test.ts']), + }) + .refine(data => !(data.skipAudits && data.onlyAudits), { + message: "You can't define 'skipAudits' and 'onlyAudits' simultaneously", + path: ['skipAudits', 'onlyAudits'], + }); export type DocCoveragePluginConfig = z.infer< typeof docCoveragePluginConfigSchema diff --git a/packages/plugin-doc-coverage/src/lib/config.unit.test.ts b/packages/plugin-doc-coverage/src/lib/config.unit.test.ts index 66f33ab52..1d28d2a7d 100644 --- a/packages/plugin-doc-coverage/src/lib/config.unit.test.ts +++ b/packages/plugin-doc-coverage/src/lib/config.unit.test.ts @@ -5,6 +5,15 @@ import { } from './config.js'; describe('docCoveragePluginConfigSchema', () => { + it('throws when skipAudits and onlyAudits are defined', () => { + expect(() => + docCoveragePluginConfigSchema.parse({ + skipAudits: ['functions-coverage'], + onlyAudits: ['classes-coverage'], + }), + ).toThrow("You can't define 'skipAudits' and 'onlyAudits' simultaneously"); + }); + describe('sourceGlob', () => { it('accepts a valid source glob pattern', () => { expect(() => @@ -32,6 +41,15 @@ describe('docCoveragePluginConfigSchema', () => { }); }); + it('accepts a complete valid configuration', () => { + expect(() => + docCoveragePluginConfigSchema.parse({ + sourceGlob: ['src/**/*.ts'], + onlyAudits: ['functions-coverage'], + } satisfies DocCoveragePluginConfig), + ).not.toThrow(); + }); + describe('onlyAudits', () => { it('accepts valid audit slugs array', () => { expect(() => @@ -73,12 +91,22 @@ describe('docCoveragePluginConfigSchema', () => { }); }); - it('accepts a complete valid configuration', () => { - expect(() => - docCoveragePluginConfigSchema.parse({ - sourceGlob: ['src/**/*.ts'], - onlyAudits: ['functions-coverage'], - } satisfies DocCoveragePluginConfig), - ).not.toThrow(); + describe('skipAudits', () => { + it('accepts valid audit slugs array', () => { + expect(() => + docCoveragePluginConfigSchema.parse({ + skipAudits: ['functions-coverage', 'classes-coverage'], + sourceGlob: ['src/**/*.ts'], + }), + ).not.toThrow(); + }); + + it('throws for array with non-string elements', () => { + expect(() => + docCoveragePluginConfigSchema.parse({ + skipAudits: [123, true], + }), + ).toThrow('Expected string'); + }); }); }); diff --git a/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.ts b/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.ts index 9d08784b1..d452839d6 100644 --- a/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.ts +++ b/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.ts @@ -29,7 +29,7 @@ export const PLUGIN_DOCS_URL = * plugins: [ * // ... other plugins ... * await docCoveragePlugin({ - * sourceGlob: 'src/**/*.{ts,tsx}' + * sourceGlob: 'src/**/*.{ts,tsx}', * }) * ] * } diff --git a/packages/plugin-doc-coverage/src/lib/runner/__snapshots__/runner.unit.test.ts.snap b/packages/plugin-doc-coverage/src/lib/runner/__snapshots__/runner.unit.test.ts.snap new file mode 100644 index 000000000..9db5313d1 --- /dev/null +++ b/packages/plugin-doc-coverage/src/lib/runner/__snapshots__/runner.unit.test.ts.snap @@ -0,0 +1,147 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`trasformCoverageReportToAudits > should filter audits when onlyAudits is provided 1`] = ` +[ + { + "details": { + "issues": [ + { + "message": "Missing documentation", + "severity": "warning", + "source": { + "file": "test.ts", + "position": { + "startLine": 10, + }, + }, + }, + ], + }, + "displayValue": "75 %", + "score": 0.75, + "slug": "functions-coverage", + "value": 75, + }, +] +`; + +exports[`trasformCoverageReportToAudits > should filter audits when skipAudits is provided 1`] = ` +[ + { + "details": { + "issues": [ + { + "message": "Missing documentation", + "severity": "warning", + "source": { + "file": "test.ts", + "position": { + "startLine": 10, + }, + }, + }, + ], + }, + "displayValue": "75 %", + "score": 0.75, + "slug": "functions-coverage", + "value": 75, + }, +] +`; + +exports[`trasformCoverageReportToAudits > should handle coverage result with multiple issues 1`] = ` +[ + { + "details": { + "issues": [ + { + "message": "Missing documentation", + "severity": "warning", + "source": { + "file": "test1.ts", + "position": { + "startLine": 10, + }, + }, + }, + { + "message": "Missing documentation", + "severity": "warning", + "source": { + "file": "test2.ts", + "position": { + "startLine": 20, + }, + }, + }, + ], + }, + "displayValue": "50 %", + "score": 0.5, + "slug": "functions-coverage", + "value": 50, + }, +] +`; + +exports[`trasformCoverageReportToAudits > should handle empty coverage result 1`] = `[]`; + +exports[`trasformCoverageReportToAudits > should prioritize onlyAudits over skipAudits when both are provided 1`] = ` +[ + { + "details": { + "issues": [ + { + "message": "Missing documentation", + "severity": "warning", + "source": { + "file": "test.ts", + "position": { + "startLine": 10, + }, + }, + }, + ], + }, + "displayValue": "75 %", + "score": 0.75, + "slug": "functions-coverage", + "value": 75, + }, +] +`; + +exports[`trasformCoverageReportToAudits > should transform coverage report to audit outputs with no filters 1`] = ` +[ + { + "details": { + "issues": [ + { + "message": "Missing documentation", + "severity": "warning", + "source": { + "file": "test.ts", + "position": { + "startLine": 10, + }, + }, + }, + ], + }, + "displayValue": "75 %", + "score": 0.75, + "slug": "functions-coverage", + "value": 75, + }, + { + "details": { + "issues": [], + }, + "displayValue": "100 %", + "score": 1, + "slug": "classes-coverage", + "value": 100, + }, +] +`; diff --git a/packages/plugin-doc-coverage/src/lib/runner/models.ts b/packages/plugin-doc-coverage/src/lib/runner/models.ts index 81849c428..c8119b4db 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/models.ts +++ b/packages/plugin-doc-coverage/src/lib/runner/models.ts @@ -1,5 +1,6 @@ import type { SyntaxKind } from 'ts-morph'; +/** Maps the SyntaxKind from the library ts-morph to the coverage type. */ type SyntaxKindToStringLiteral = { [SyntaxKind.ClassDeclaration]: 'classes'; [SyntaxKind.MethodDeclaration]: 'methods'; @@ -11,9 +12,11 @@ type SyntaxKindToStringLiteral = { [SyntaxKind.TypeAliasDeclaration]: 'types'; }; +/**The coverage type is the same as the SyntaxKind from the library ts-morph but as a string. */ export type CoverageType = SyntaxKindToStringLiteral[keyof SyntaxKindToStringLiteral]; +/** The undocumented node is the node that is not documented and has the information for the report. */ export type UndocumentedNode = { file: string; type: CoverageType; @@ -22,13 +25,16 @@ export type UndocumentedNode = { class?: string; }; +/** The coverage data is the data that is used to create the coverage report. Without coverage stats yet */ export type CoverageData = { issues: UndocumentedNode[]; nodesCount: number; }; +/** The unprocessed coverage result CoverageData but for each coverage type. */ export type UnprocessedCoverageResult = Record; +/** The processed coverage result CoverageData but for each coverage type and with coverage stats. */ export type CoverageResult = Record< CoverageType, CoverageData & { diff --git a/packages/plugin-doc-coverage/src/lib/runner/runner.ts b/packages/plugin-doc-coverage/src/lib/runner/runner.ts index 91e8282e1..bb037e933 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/runner.ts +++ b/packages/plugin-doc-coverage/src/lib/runner/runner.ts @@ -15,19 +15,24 @@ export function createRunnerFunction( /** * Transforms the coverage report into audit outputs. * @param coverageResult - The coverage result containing undocumented items and coverage statistics - * @param options - Configuration options specifying which audits to include + * @param options - Configuration options specifying which audits to include and exclude * @returns Audit outputs with coverage scores and details about undocumented items */ export function trasformCoverageReportToAudits( coverageResult: CoverageResult, - options: Pick, + options: Pick, ): AuditOutputs { return Object.entries(coverageResult) - .filter( - ([type]) => - !options.onlyAudits?.length || - options.onlyAudits.includes(`${type}-coverage`), - ) + .filter(([type]) => { + const auditSlug = `${type}-coverage`; + if (options.onlyAudits?.length) { + return options.onlyAudits.includes(auditSlug); + } + if (options.skipAudits?.length) { + return !options.skipAudits.includes(auditSlug); + } + return true; + }) .map(([type, items]) => { const coverageType = type as CoverageType; const coverage = items.coverage; diff --git a/packages/plugin-doc-coverage/src/lib/runner/runner.unit.test.ts b/packages/plugin-doc-coverage/src/lib/runner/runner.unit.test.ts new file mode 100644 index 000000000..daa1321ac --- /dev/null +++ b/packages/plugin-doc-coverage/src/lib/runner/runner.unit.test.ts @@ -0,0 +1,88 @@ +import type { CoverageResult } from './models'; +import { trasformCoverageReportToAudits } from './runner'; + +describe('trasformCoverageReportToAudits', () => { + const mockCoverageResult = { + functions: { + coverage: 75, + nodesCount: 4, + issues: [ + { + file: 'test.ts', + line: 10, + name: 'testFunction', + type: 'functions', + }, + ], + }, + classes: { + coverage: 100, + nodesCount: 2, + issues: [], + }, + } as unknown as CoverageResult; + + it('should transform coverage report to audit outputs with no filters', () => { + const result = trasformCoverageReportToAudits(mockCoverageResult, {}); + expect(result).toMatchSnapshot(); + }); + + it('should filter audits when onlyAudits is provided', () => { + const result = trasformCoverageReportToAudits(mockCoverageResult, { + onlyAudits: ['functions-coverage'], + }); + expect(result).toMatchSnapshot(); + }); + + it('should filter audits when skipAudits is provided', () => { + const result = trasformCoverageReportToAudits(mockCoverageResult, { + skipAudits: ['classes-coverage'], + }); + expect(result).toMatchSnapshot(); + }); + + it('should handle empty coverage result', () => { + const result = trasformCoverageReportToAudits( + {} as unknown as CoverageResult, + {}, + ); + expect(result).toMatchSnapshot(); + }); + + it('should handle coverage result with multiple issues', () => { + const coverageWithMultipleIssues = { + functions: { + coverage: 50, + nodesCount: 4, + issues: [ + { + file: 'test1.ts', + line: 10, + name: 'function1', + type: 'functions', + }, + { + file: 'test2.ts', + line: 20, + name: 'function2', + type: 'functions', + }, + ], + }, + } as unknown as CoverageResult; + + const result = trasformCoverageReportToAudits( + coverageWithMultipleIssues, + {}, + ); + expect(result).toMatchSnapshot(); + }); + + it('should prioritize onlyAudits over skipAudits when both are provided', () => { + const result = trasformCoverageReportToAudits(mockCoverageResult, { + onlyAudits: ['functions-coverage'], + skipAudits: ['functions-coverage'], + }); + expect(result).toMatchSnapshot(); + }); +}); diff --git a/packages/plugin-doc-coverage/src/lib/runner/utils.ts b/packages/plugin-doc-coverage/src/lib/runner/utils.ts index e9628eac6..1cdede6dd 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/utils.ts +++ b/packages/plugin-doc-coverage/src/lib/runner/utils.ts @@ -5,6 +5,10 @@ import type { UnprocessedCoverageResult, } from './models'; +/** + * Creates an empty unprocessed coverage report. + * @returns The empty unprocessed coverage report. + */ export function createEmptyUnprocessedCoverageReport(): UnprocessedCoverageResult { return { enums: { nodesCount: 0, issues: [] }, @@ -18,6 +22,11 @@ export function createEmptyUnprocessedCoverageReport(): UnprocessedCoverageResul }; } +/** + * Calculates the coverage percentage for each coverage type. + * @param result - The unprocessed coverage result. + * @returns The processed coverage result. + */ export function calculateCoverage(result: UnprocessedCoverageResult) { return Object.fromEntries( Object.entries(result).map(([key, value]) => { @@ -41,6 +50,11 @@ export function calculateCoverage(result: UnprocessedCoverageResult) { ) as CoverageResult; } +/** + * Maps the SyntaxKind from the library ts-morph to the coverage type. + * @param kind - The SyntaxKind from the library ts-morph. + * @returns The coverage type. + */ export function getCoverageTypeFromKind(kind: SyntaxKind): CoverageType { switch (kind) { case SyntaxKind.ClassDeclaration: diff --git a/packages/plugin-doc-coverage/src/lib/utils.ts b/packages/plugin-doc-coverage/src/lib/utils.ts index b3e7584cb..45b664693 100644 --- a/packages/plugin-doc-coverage/src/lib/utils.ts +++ b/packages/plugin-doc-coverage/src/lib/utils.ts @@ -10,29 +10,35 @@ import { AUDITS_MAP } from './constants'; * @returns The audits. */ export function filterAuditsByPluginConfig( - config: Pick, + config: Pick, ): Audit[] { - const { onlyAudits } = config; + const { onlyAudits, skipAudits } = config; - if (!onlyAudits || onlyAudits.length === 0) { - return Object.values(AUDITS_MAP); + if (onlyAudits && onlyAudits.length > 0) { + return Object.values(AUDITS_MAP).filter(audit => + onlyAudits.includes(audit.slug), + ); } - return Object.values(AUDITS_MAP).filter(audit => - onlyAudits.includes(audit.slug), - ); + if (skipAudits && skipAudits.length > 0) { + return Object.values(AUDITS_MAP).filter( + audit => !skipAudits.includes(audit.slug), + ); + } + + return Object.values(AUDITS_MAP); } /** * Filter groups by the audits that are specified in the configuration. * The groups refs are filtered to only include the audits that are specified in the configuration. * @param groups - The groups to filter. - * @param options - The configuration object. + * @param options - The configuration object containing either onlyAudits or skipAudits. * @returns The filtered groups. */ export function filterGroupsByOnlyAudits( groups: Group[], - options: Pick, + options: Pick, ): Group[] { const audits = filterAuditsByPluginConfig(options); return groups diff --git a/packages/plugin-doc-coverage/src/lib/utils.unit.test.ts b/packages/plugin-doc-coverage/src/lib/utils.unit.test.ts index ec5ec4008..a7ea37d59 100644 --- a/packages/plugin-doc-coverage/src/lib/utils.unit.test.ts +++ b/packages/plugin-doc-coverage/src/lib/utils.unit.test.ts @@ -3,13 +3,16 @@ import { AUDITS_MAP } from './constants'; import { filterAuditsByPluginConfig, filterGroupsByOnlyAudits } from './utils'; describe('filterAuditsByPluginConfig', () => { - it('should return all audits when onlyAudits is not provided', () => { + it('should return all audits when onlyAudits and skipAudits are not provided', () => { const result = filterAuditsByPluginConfig({}); expect(result).toStrictEqual(Object.values(AUDITS_MAP)); }); - it('should return all audits when onlyAudits is empty array', () => { - const result = filterAuditsByPluginConfig({ onlyAudits: [] }); + it('should return all audits when onlyAudits is empty array and skipAudits is also empty array', () => { + const result = filterAuditsByPluginConfig({ + onlyAudits: [], + skipAudits: [], + }); expect(result).toStrictEqual(Object.values(AUDITS_MAP)); }); @@ -23,6 +26,16 @@ describe('filterAuditsByPluginConfig', () => { ), ); }); + + it('should return only specified audits when skipAudits is provided', () => { + const skipAudits = ['functions-coverage', 'classes-coverage']; + const result = filterAuditsByPluginConfig({ skipAudits }); + expect(result).toStrictEqual( + Object.values(AUDITS_MAP).filter( + audit => !skipAudits.includes(audit.slug), + ), + ); + }); }); describe('filterGroupsByOnlyAudits', () => { From f21fbe2866730129bb727affed0a045296263b59 Mon Sep 17 00:00:00 2001 From: Alejandro Date: Fri, 20 Dec 2024 19:45:03 +0100 Subject: [PATCH 08/66] chore(plugin-doc-coverage): fix linter problems --- ...ck.spec.ts => component-mock.unit.test.ts} | 2 +- packages/plugin-doc-coverage/package.json | 2 - packages/plugin-doc-coverage/src/bin.ts | 3 - .../plugin-doc-coverage/src/lib/constants.ts | 25 +++---- .../plugin-doc-coverage/src/lib/models.ts | 2 +- .../runner/doc-processer.integration.test.ts | 2 +- .../src/lib/runner/doc-processer.ts | 69 +++++++++++-------- .../src/lib/runner/doc-processer.unit.test.ts | 8 +-- .../src/lib/runner/runner.ts | 6 +- .../src/lib/runner/runner.unit.test.ts | 4 +- .../src/lib/runner/utils.ts | 2 +- .../src/lib/runner/utils.unit.test.ts | 4 +- packages/plugin-doc-coverage/src/lib/utils.ts | 4 +- .../src/lib/utils.unit.test.ts | 7 +- 14 files changed, 72 insertions(+), 68 deletions(-) rename packages/plugin-doc-coverage/mocks/{component-mock.spec.ts => component-mock.unit.test.ts} (98%) delete mode 100644 packages/plugin-doc-coverage/src/bin.ts diff --git a/packages/plugin-doc-coverage/mocks/component-mock.spec.ts b/packages/plugin-doc-coverage/mocks/component-mock.unit.test.ts similarity index 98% rename from packages/plugin-doc-coverage/mocks/component-mock.spec.ts rename to packages/plugin-doc-coverage/mocks/component-mock.unit.test.ts index 62998c2a9..648080fb0 100644 --- a/packages/plugin-doc-coverage/mocks/component-mock.spec.ts +++ b/packages/plugin-doc-coverage/mocks/component-mock.unit.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest'; -import { DUMMY_FUNCTION, DUMMY_FUNCTION_2 } from './component-mock'; +import { DUMMY_FUNCTION, DUMMY_FUNCTION_2 } from './component-mock.js'; export function shouldnotBeHere() { return 'Hello World'; diff --git a/packages/plugin-doc-coverage/package.json b/packages/plugin-doc-coverage/package.json index ec8b984a9..c06f92a38 100644 --- a/packages/plugin-doc-coverage/package.json +++ b/packages/plugin-doc-coverage/package.json @@ -35,8 +35,6 @@ "type": "module", "dependencies": { "@code-pushup/models": "0.57.0", - "@code-pushup/utils": "0.57.0", - "ansis": "^3.3.0", "zod": "^3.22.4", "ts-morph": "^24.0.0" }, diff --git a/packages/plugin-doc-coverage/src/bin.ts b/packages/plugin-doc-coverage/src/bin.ts deleted file mode 100644 index bf6572a76..000000000 --- a/packages/plugin-doc-coverage/src/bin.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { executeRunner } from './lib/runner/index.js'; - -await executeRunner(); diff --git a/packages/plugin-doc-coverage/src/lib/constants.ts b/packages/plugin-doc-coverage/src/lib/constants.ts index 458409066..d199b2b55 100644 --- a/packages/plugin-doc-coverage/src/lib/constants.ts +++ b/packages/plugin-doc-coverage/src/lib/constants.ts @@ -1,5 +1,5 @@ import type { Audit, Group } from '@code-pushup/models'; -import type { AuditSlug } from './models'; +import type { AuditSlug } from './models.js'; export const PLUGIN_SLUG = 'doc-coverage'; @@ -51,18 +51,15 @@ export const groups: Group[] = [ slug: 'documentation-coverage', title: 'Documentation coverage', description: 'Documentation coverage', - refs: Object.keys(AUDITS_MAP).map(slug => { - switch (slug as AuditSlug) { - case 'classes-coverage': - case 'functions-coverage': - case 'methods-coverage': - return { slug, weight: 2 }; - case 'interfaces-coverage': - case 'properties-coverage': - case 'types-coverage': - default: - return { slug, weight: 1 }; - } - }), + refs: Object.keys(AUDITS_MAP).map(slug => ({ + slug, + weight: [ + 'classes-coverage', + 'functions-coverage', + 'methods-coverage', + ].includes(slug) + ? 2 + : 1, + })), }, ]; diff --git a/packages/plugin-doc-coverage/src/lib/models.ts b/packages/plugin-doc-coverage/src/lib/models.ts index 4500a13d1..a407d2a74 100644 --- a/packages/plugin-doc-coverage/src/lib/models.ts +++ b/packages/plugin-doc-coverage/src/lib/models.ts @@ -1,3 +1,3 @@ -import type { CoverageType } from './runner/models'; +import type { CoverageType } from './runner/models.js'; export type AuditSlug = `${CoverageType}-coverage`; diff --git a/packages/plugin-doc-coverage/src/lib/runner/doc-processer.integration.test.ts b/packages/plugin-doc-coverage/src/lib/runner/doc-processer.integration.test.ts index 51e607eb9..00bdc83a7 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/doc-processer.integration.test.ts +++ b/packages/plugin-doc-coverage/src/lib/runner/doc-processer.integration.test.ts @@ -1,4 +1,4 @@ -import { processDocCoverage } from './doc-processer'; +import { processDocCoverage } from './doc-processer.js'; describe('processDocCoverage', () => { it('should succesfully get the right number of ts files', () => { diff --git a/packages/plugin-doc-coverage/src/lib/runner/doc-processer.ts b/packages/plugin-doc-coverage/src/lib/runner/doc-processer.ts index fbf333bfc..d510ae15f 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/doc-processer.ts +++ b/packages/plugin-doc-coverage/src/lib/runner/doc-processer.ts @@ -29,14 +29,14 @@ export function processDocCoverage( * @param sourceFiles - The source files to process * @returns {UnprocessedCoverageResult} The unprocessed coverage report */ -export function getUnprocessedCoverageReport(sourceFiles: SourceFile[]) { +export function getUnprocessedCoverageReport( + sourceFiles: SourceFile[], +): CoverageResult { const unprocessedCoverageReport = sourceFiles.reduce( (coverageReportOfAllFiles, sourceFile) => { - // Info of the file const filePath = sourceFile.getFilePath(); const classes = sourceFile.getClasses(); - // All nodes of the file const allNodesFromFile = [ ...sourceFile.getFunctions(), ...classes, @@ -44,22 +44,33 @@ export function getUnprocessedCoverageReport(sourceFiles: SourceFile[]) { ...sourceFile.getTypeAliases(), ...sourceFile.getEnums(), ...sourceFile.getInterfaces(), - // ...sourceFile.getVariableStatements().flatMap(statement => statement.getDeclarations()) ]; const coverageReportOfCurrentFile = allNodesFromFile.reduce( (acc, node) => { const nodeType = getCoverageTypeFromKind(node.getKind()); - acc[nodeType].nodesCount++; - if (node.getJsDocs().length === 0) { - acc[nodeType].issues.push({ - file: filePath, - type: nodeType, - name: node.getName() || '', - line: node.getStartLineNumber(), - }); - } - return acc; + const currentTypeReport = acc[nodeType]; + + const updatedIssues = + node.getJsDocs().length === 0 + ? [ + ...currentTypeReport.issues, + { + file: filePath, + type: nodeType, + name: node.getName() || '', + line: node.getStartLineNumber(), + }, + ] + : currentTypeReport.issues; + + return { + ...acc, + [nodeType]: { + nodesCount: currentTypeReport.nodesCount + 1, + issues: updatedIssues, + }, + }; }, createEmptyUnprocessedCoverageReport(), ); @@ -84,22 +95,20 @@ export function getUnprocessedCoverageReport(sourceFiles: SourceFile[]) { export function mergeCoverageResults( results: UnprocessedCoverageResult, current: Partial, -) { - return { - ...Object.fromEntries( - Object.entries(results).map(([key, value]) => { - const node = value as CoverageResult[CoverageType]; - const type = key as CoverageType; - return [ - type, - { - nodesCount: node.nodesCount + (current[type]?.nodesCount ?? 0), - issues: [...node.issues, ...(current[type]?.issues ?? [])], - }, - ]; - }), - ), - } as UnprocessedCoverageResult; +): UnprocessedCoverageResult { + return Object.fromEntries( + Object.entries(results).map(([key, value]) => { + const node = value as CoverageResult[CoverageType]; + const type = key as CoverageType; + return [ + type, + { + nodesCount: node.nodesCount + (current[type]?.nodesCount ?? 0), + issues: [...node.issues, ...(current[type]?.issues ?? [])], + }, + ]; + }), + ) as UnprocessedCoverageResult; } /** diff --git a/packages/plugin-doc-coverage/src/lib/runner/doc-processer.unit.test.ts b/packages/plugin-doc-coverage/src/lib/runner/doc-processer.unit.test.ts index 21c829349..233723f03 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/doc-processer.unit.test.ts +++ b/packages/plugin-doc-coverage/src/lib/runner/doc-processer.unit.test.ts @@ -1,11 +1,11 @@ import type { ClassDeclaration } from 'ts-morph'; -import { nodeMock, sourceFileMock } from './../../../mocks/source-files.mock'; +import { nodeMock, sourceFileMock } from '../../../mocks/source-files.mock'; import { getClassNodes, getUnprocessedCoverageReport, mergeCoverageResults, -} from './doc-processer'; -import type { UnprocessedCoverageResult } from './models'; +} from './doc-processer.js'; +import type { UnprocessedCoverageResult } from './models.js'; describe('getUnprocessedCoverageReport', () => { it('should produce a full report', () => { @@ -43,7 +43,7 @@ describe('getUnprocessedCoverageReport', () => { sourceFileMock('test.ts', { functions: { 1: true, 2: false, 3: false } }), ]); - expect(results.functions.issues.length).toBe(2); + expect(results.functions.issues).toHaveLength(2); }); it('should collect valid issues', () => { diff --git a/packages/plugin-doc-coverage/src/lib/runner/runner.ts b/packages/plugin-doc-coverage/src/lib/runner/runner.ts index bb037e933..8bbf54066 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/runner.ts +++ b/packages/plugin-doc-coverage/src/lib/runner/runner.ts @@ -1,7 +1,7 @@ import type { AuditOutputs, RunnerFunction } from '@code-pushup/models'; -import type { DocCoveragePluginConfig } from '../config'; -import { processDocCoverage } from './doc-processer'; -import type { CoverageResult, CoverageType } from './models'; +import type { DocCoveragePluginConfig } from '../config.js'; +import { processDocCoverage } from './doc-processer.js'; +import type { CoverageResult, CoverageType } from './models.js'; export function createRunnerFunction( config: DocCoveragePluginConfig, diff --git a/packages/plugin-doc-coverage/src/lib/runner/runner.unit.test.ts b/packages/plugin-doc-coverage/src/lib/runner/runner.unit.test.ts index daa1321ac..d81748ab3 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/runner.unit.test.ts +++ b/packages/plugin-doc-coverage/src/lib/runner/runner.unit.test.ts @@ -1,5 +1,5 @@ -import type { CoverageResult } from './models'; -import { trasformCoverageReportToAudits } from './runner'; +import type { CoverageResult } from './models.js'; +import { trasformCoverageReportToAudits } from './runner.js'; describe('trasformCoverageReportToAudits', () => { const mockCoverageResult = { diff --git a/packages/plugin-doc-coverage/src/lib/runner/utils.ts b/packages/plugin-doc-coverage/src/lib/runner/utils.ts index 1cdede6dd..31c312619 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/utils.ts +++ b/packages/plugin-doc-coverage/src/lib/runner/utils.ts @@ -3,7 +3,7 @@ import type { CoverageResult, CoverageType, UnprocessedCoverageResult, -} from './models'; +} from './models.js'; /** * Creates an empty unprocessed coverage report. diff --git a/packages/plugin-doc-coverage/src/lib/runner/utils.unit.test.ts b/packages/plugin-doc-coverage/src/lib/runner/utils.unit.test.ts index 403c7b3ad..731d5280c 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/utils.unit.test.ts +++ b/packages/plugin-doc-coverage/src/lib/runner/utils.unit.test.ts @@ -1,10 +1,10 @@ import { SyntaxKind } from 'ts-morph'; -import type { UnprocessedCoverageResult } from './models'; +import type { UnprocessedCoverageResult } from './models.js'; import { calculateCoverage, createEmptyUnprocessedCoverageReport, getCoverageTypeFromKind, -} from './utils'; +} from './utils.js'; describe('createEmptyUnprocessedCoverageReport', () => { it('should create an empty report with all categories initialized', () => { diff --git a/packages/plugin-doc-coverage/src/lib/utils.ts b/packages/plugin-doc-coverage/src/lib/utils.ts index 45b664693..73e04c0af 100644 --- a/packages/plugin-doc-coverage/src/lib/utils.ts +++ b/packages/plugin-doc-coverage/src/lib/utils.ts @@ -1,6 +1,6 @@ import type { Audit, Group } from '@code-pushup/models'; -import type { DocCoveragePluginConfig } from './config'; -import { AUDITS_MAP } from './constants'; +import type { DocCoveragePluginConfig } from './config.js'; +import { AUDITS_MAP } from './constants.js'; /** * Get audits based on the configuration. diff --git a/packages/plugin-doc-coverage/src/lib/utils.unit.test.ts b/packages/plugin-doc-coverage/src/lib/utils.unit.test.ts index a7ea37d59..9252b5fd5 100644 --- a/packages/plugin-doc-coverage/src/lib/utils.unit.test.ts +++ b/packages/plugin-doc-coverage/src/lib/utils.unit.test.ts @@ -1,6 +1,9 @@ import type { Group } from '@code-pushup/models'; -import { AUDITS_MAP } from './constants'; -import { filterAuditsByPluginConfig, filterGroupsByOnlyAudits } from './utils'; +import { AUDITS_MAP } from './constants.js'; +import { + filterAuditsByPluginConfig, + filterGroupsByOnlyAudits, +} from './utils.js'; describe('filterAuditsByPluginConfig', () => { it('should return all audits when onlyAudits and skipAudits are not provided', () => { From 4fb478ef46f6242a1c903a2b69edbe0db04c3c63 Mon Sep 17 00:00:00 2001 From: Alejandro Date: Fri, 20 Dec 2024 19:46:40 +0100 Subject: [PATCH 09/66] fix: return code-pushup config to original state --- code-pushup.config.ts | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/code-pushup.config.ts b/code-pushup.config.ts index 0b2d52680..a0a33cfee 100644 --- a/code-pushup.config.ts +++ b/code-pushup.config.ts @@ -1,6 +1,12 @@ import 'dotenv/config'; import { z } from 'zod'; -import { docCoverageCoreConfig } from './code-pushup.preset.js'; +import { + coverageCoreConfigNx, + docCoverageCoreConfig, + eslintCoreConfigNx, + jsPackagesCoreConfig, + lighthouseCoreConfig, +} from './code-pushup.preset.js'; import type { CoreConfig } from './packages/models/src/index.js'; import { mergeConfigs } from './packages/utils/src/index.js'; @@ -27,13 +33,13 @@ const config: CoreConfig = { }; export default mergeConfigs( - // config, - // await coverageCoreConfigNx(), - // await jsPackagesCoreConfig(), - // await lighthouseCoreConfig( - // 'https://github.com/code-pushup/cli?tab=readme-ov-file#code-pushup-cli/', - // ), - // await eslintCoreConfigNx(), + config, + await coverageCoreConfigNx(), + await jsPackagesCoreConfig(), + await lighthouseCoreConfig( + 'https://github.com/code-pushup/cli?tab=readme-ov-file#code-pushup-cli/', + ), + await eslintCoreConfigNx(), await docCoverageCoreConfig({ sourceGlob: [ 'packages/**/src/**/*.ts', @@ -43,14 +49,5 @@ export default mergeConfigs( '!**/internal/**', ], skipAudits: ['methods-coverage'], - // onlyAudits: [ - // 'methods-coverage', - // 'functions-coverage', - // 'types-coverage', - // 'classes-coverage', - // 'interfaces-coverage', - // 'enums-coverage', - // 'type-aliases-coverage', - // ], }), ); From feefa0ad771f97448be79a35334e379907fdc21b Mon Sep 17 00:00:00 2001 From: Alejandro Date: Sat, 21 Dec 2024 12:44:27 +0100 Subject: [PATCH 10/66] feat(plugin-doc-coverage): add support for variable statement to be documented, improve test --- code-pushup.config.bundled_oq2x2csd2at.mjs | 1210 ----------------- .../mocks/component-mock.ts | 38 - .../fixtures/angular/map-event.function.ts | 2 + .../mocks/source-files.mock.ts | 72 +- .../doc-processer.integration.test.ts.snap | 38 +- .../src/lib/runner/doc-processer.ts | 32 +- .../src/lib/runner/doc-processer.unit.test.ts | 79 +- .../src/lib/runner/utils.ts | 1 + 8 files changed, 158 insertions(+), 1314 deletions(-) delete mode 100644 code-pushup.config.bundled_oq2x2csd2at.mjs diff --git a/code-pushup.config.bundled_oq2x2csd2at.mjs b/code-pushup.config.bundled_oq2x2csd2at.mjs deleted file mode 100644 index 4c033e42d..000000000 --- a/code-pushup.config.bundled_oq2x2csd2at.mjs +++ /dev/null @@ -1,1210 +0,0 @@ -// code-pushup.config.ts -// packages/utils/src/lib/logging.ts -import isaacs_cliui from '@isaacs/cliui'; -import { cliui } from '@poppinss/cliui'; -// packages/plugin-coverage/src/lib/runner/index.ts -import { bold } from 'ansis'; -// packages/plugin-coverage/src/lib/nx/coverage-paths.ts -import { bold as bold2 } from 'ansis'; -// packages/plugin-lighthouse/src/lib/normalize-flags.ts -import { bold as bold3, yellow } from 'ansis'; -// packages/plugin-lighthouse/src/lib/runner/utils.ts -import { bold as bold7 } from 'ansis'; -// packages/plugin-lighthouse/src/lib/runner/details/details.ts -import { bold as bold6, yellow as yellow2 } from 'ansis'; -// packages/plugin-lighthouse/src/lib/runner/details/item-value.ts -import { bold as bold4 } from 'ansis'; -// packages/plugin-lighthouse/src/lib/runner/details/utils.ts -import { bold as bold5 } from 'ansis'; -// packages/utils/src/lib/reports/utils.ts -import ansis from 'ansis'; -// packages/utils/src/lib/file-system.ts -import { bold as bold8, gray } from 'ansis'; -import { underline } from 'ansis'; -// packages/utils/src/lib/progress.ts -import { black, bold as bold9, gray as gray2, green } from 'ansis'; -// packages/utils/src/lib/reports/log-stdout-summary.ts -import { bold as bold11, cyan, cyanBright, green as green2, red } from 'ansis'; -// packages/utils/src/lib/zod-validation.ts -import { bold as bold12, red as red2 } from 'ansis'; -// packages/plugin-js-packages/src/lib/runner/audit/transform.ts -import { md } from 'build-md'; -// packages/plugin-js-packages/src/lib/runner/outdated/transform.ts -import { md as md2 } from 'build-md'; -import { md as md3 } from 'build-md'; -// packages/utils/src/lib/reports/generate-md-report.ts -import { MarkdownDocument as MarkdownDocument3, md as md6 } from 'build-md'; -// packages/utils/src/lib/reports/formatting.ts -import { MarkdownDocument, md as md4 } from 'build-md'; -// packages/utils/src/lib/reports/generate-md-report-categoy-section.ts -import { MarkdownDocument as MarkdownDocument2, md as md5 } from 'build-md'; -// packages/utils/src/lib/reports/generate-md-reports-diff.ts -import { MarkdownDocument as MarkdownDocument5, md as md8 } from 'build-md'; -// packages/utils/src/lib/reports/generate-md-reports-diff-utils.ts -import { MarkdownDocument as MarkdownDocument4, md as md7 } from 'build-md'; -import { bundleRequire } from 'bundle-require'; -// packages/plugin-lighthouse/src/lib/constants.ts -import { DEFAULT_FLAGS } from 'chrome-launcher/dist/flags.js'; -import 'dotenv/config'; -// packages/plugin-eslint/src/lib/setup.ts -import { ESLint } from 'eslint'; -// packages/plugin-eslint/src/lib/meta/versions/detect.ts -import { ESLint as ESLint2 } from 'eslint'; -// packages/plugin-eslint/src/lib/meta/versions/flat.ts -import { builtinRules } from 'eslint/use-at-your-own-risk'; -// packages/plugin-lighthouse/src/lib/runner/constants.ts -import { defaultConfig } from 'lighthouse'; -import log from 'lighthouse-logger'; -// packages/plugin-lighthouse/src/lib/runner/runner.ts -import { runLighthouse } from 'lighthouse/cli/run.js'; -import desktopConfig from 'lighthouse/core/config/desktop-config.js'; -import experimentalConfig from 'lighthouse/core/config/experimental-config.js'; -import perfConfig from 'lighthouse/core/config/perf-config.js'; -import { MultiProgressBars } from 'multi-progress-bars'; -// packages/utils/src/lib/execute-process.ts -import { spawn } from 'node:child_process'; -// packages/plugin-eslint/src/lib/meta/hash.ts -import { createHash } from 'node:crypto'; -import { writeFile } from 'node:fs/promises'; -// packages/plugin-eslint/src/lib/runner/index.ts -import { writeFile as writeFile2 } from 'node:fs/promises'; -// packages/plugin-js-packages/src/lib/runner/index.ts -import { writeFile as writeFile3 } from 'node:fs/promises'; -// packages/plugin-js-packages/src/lib/package-managers/derive-package-manager.ts -import { readFile } from 'node:fs/promises'; -import { - mkdir, - readFile as readFile2, - readdir, - rm, - stat, -} from 'node:fs/promises'; -// packages/plugin-coverage/src/lib/coverage-plugin.ts -import { createRequire } from 'node:module'; -// packages/plugin-eslint/src/lib/eslint-plugin.ts -import { createRequire as createRequire2 } from 'node:module'; -// packages/plugin-js-packages/src/lib/js-packages-plugin.ts -import { createRequire as createRequire3 } from 'node:module'; -// packages/plugin-lighthouse/src/lib/lighthouse-plugin.ts -import { createRequire as createRequire4 } from 'node:module'; -// packages/plugin-eslint/src/lib/runner/lint.ts -import { platform } from 'node:os'; -// packages/utils/src/lib/transform.ts -import { platform as platform2 } from 'node:os'; -import path4 from 'node:path'; -import path3 from 'node:path'; -// packages/plugin-coverage/src/lib/runner/constants.ts -import path from 'node:path'; -// packages/plugin-coverage/src/lib/runner/lcov/lcov-runner.ts -import path2 from 'node:path'; -import path5 from 'node:path'; -import path8 from 'node:path'; -import path6 from 'node:path'; -import path7 from 'node:path'; -// packages/plugin-eslint/src/lib/nx/utils.ts -import path9 from 'node:path'; -import path14 from 'node:path'; -// packages/plugin-js-packages/src/lib/runner/utils.ts -import path10 from 'node:path'; -import path12 from 'node:path'; -// packages/plugin-js-packages/src/lib/runner/constants.ts -import path11 from 'node:path'; -import path13 from 'node:path'; -import path15 from 'node:path'; -import path16 from 'node:path'; -import path17 from 'node:path'; -import path18 from 'node:path'; -// packages/utils/src/lib/git/git.ts -import path19 from 'node:path'; -import path20 from 'node:path'; -// packages/utils/src/lib/reports/load-report.ts -import path21 from 'node:path'; -import { fileURLToPath } from 'node:url'; -import { fileURLToPath as fileURLToPath2 } from 'node:url'; -import { pathToFileURL } from 'node:url'; -import { fileURLToPath as fileURLToPath3 } from 'node:url'; -// packages/plugin-coverage/src/lib/runner/lcov/parse-lcov.ts -import parseLcovExport from 'parse-lcov'; -import { clean, diff, neq } from 'semver'; -// packages/utils/src/lib/semver.ts -import { rcompare, valid } from 'semver'; -// packages/utils/src/lib/git/git.commits-and-tags.ts -import { simpleGit } from 'simple-git'; -import { simpleGit as simpleGit2 } from 'simple-git'; -// packages/plugin-doc-coverage/src/lib/runner/doc-processer.ts -import { Project } from 'ts-morph'; -// packages/plugin-doc-coverage/src/lib/utils.ts -import { SyntaxKind } from 'typescript'; -import { z as z5 } from 'zod'; -// packages/plugin-coverage/src/lib/config.ts -import { z } from 'zod'; -// packages/plugin-doc-coverage/src/lib/config.ts -import { z as z2 } from 'zod'; -// packages/plugin-eslint/src/lib/config.ts -import { z as z3 } from 'zod'; -// packages/plugin-js-packages/src/lib/config.ts -import { z as z4 } from 'zod'; -import { issueSeveritySchema } from '@code-pushup/models'; -import { DEFAULT_PERSIST_OUTPUT_DIR } from '@code-pushup/models'; -// packages/plugin-lighthouse/src/lib/runner/details/opportunity.type.ts -import { tableSchema as tableSchema2 } from '@code-pushup/models'; -// packages/plugin-lighthouse/src/lib/runner/details/table.type.ts -import { tableSchema } from '@code-pushup/models'; -// packages/utils/src/index.ts -import { exists as exists4 } from '@code-pushup/models'; -// packages/utils/src/lib/formatting.ts -import { - MAX_DESCRIPTION_LENGTH, - MAX_ISSUE_MESSAGE_LENGTH, - MAX_TITLE_LENGTH, -} from '@code-pushup/models'; -import { commitSchema } from '@code-pushup/models'; -import { reportSchema } from '@code-pushup/models'; -import { capitalize } from '@code-pushup/utils'; -import { - ProcessError, - ensureDirectoryExists, - executeProcess, - filePathToCliArg, - readJsonFile, - ui as ui2, -} from '@code-pushup/utils'; -import { pluginWorkDir } from '@code-pushup/utils'; -import { exists, readTextFile, toUnixNewlines, ui } from '@code-pushup/utils'; -// packages/plugin-coverage/src/lib/runner/lcov/transform.ts -import { toNumberPrecision, toOrdinal } from '@code-pushup/utils'; -import { importModule, ui as ui3 } from '@code-pushup/utils'; -import { toArray } from '@code-pushup/utils'; -// packages/plugin-eslint/src/lib/meta/groups.ts -import { objectToKeys, slugify as slugify2 } from '@code-pushup/utils'; -import { slugify } from '@code-pushup/utils'; -// packages/plugin-eslint/src/lib/meta/parse.ts -import { toArray as toArray2 } from '@code-pushup/utils'; -import { - exists as exists2, - findNearestFile, - toArray as toArray3, - ui as ui4, -} from '@code-pushup/utils'; -// packages/plugin-eslint/src/lib/meta/versions/legacy.ts -import { - distinct, - exists as exists3, - toArray as toArray4, - ui as ui5, -} from '@code-pushup/utils'; -import { fileExists } from '@code-pushup/utils'; -// packages/plugin-eslint/src/lib/meta/transform.ts -import { truncateDescription, truncateTitle } from '@code-pushup/utils'; -import { - ensureDirectoryExists as ensureDirectoryExists2, - filePathToCliArg as filePathToCliArg3, - pluginWorkDir as pluginWorkDir2, - readJsonFile as readJsonFile2, -} from '@code-pushup/utils'; -import { - distinct as distinct2, - executeProcess as executeProcess2, - filePathToCliArg as filePathToCliArg2, - toArray as toArray5, -} from '@code-pushup/utils'; -// packages/plugin-eslint/src/lib/runner/transform.ts -import { - compareIssueSeverity, - countOccurrences, - objectToEntries, - pluralizeToken, - truncateIssueMessage, - ui as ui6, -} from '@code-pushup/utils'; -import { - fileExists as fileExists2, - toArray as toArray6, -} from '@code-pushup/utils'; -// packages/plugin-js-packages/src/lib/package-managers/npm/npm.ts -import { objectToKeys as objectToKeys3 } from '@code-pushup/utils'; -import { - crawlFileSystem, - objectFromEntries, - objectToKeys as objectToKeys2, - readJsonFile as readJsonFile3, -} from '@code-pushup/utils'; -// packages/plugin-js-packages/src/lib/package-managers/npm/audit-result.ts -import { objectToEntries as objectToEntries2 } from '@code-pushup/utils'; -// packages/plugin-js-packages/src/lib/package-managers/npm/outdated-result.ts -import { objectToEntries as objectToEntries3 } from '@code-pushup/utils'; -// packages/plugin-js-packages/src/lib/package-managers/pnpm/pnpm.ts -import { objectToKeys as objectToKeys4 } from '@code-pushup/utils'; -// packages/plugin-js-packages/src/lib/package-managers/pnpm/outdated-result.ts -import { objectToEntries as objectToEntries4 } from '@code-pushup/utils'; -// packages/plugin-js-packages/src/lib/package-managers/yarn-classic/audit-result.ts -import { fromJsonLines } from '@code-pushup/utils'; -// packages/plugin-js-packages/src/lib/package-managers/yarn-classic/outdated-result.ts -import { - fromJsonLines as fromJsonLines2, - objectFromEntries as objectFromEntries2, - objectToEntries as objectToEntries5, - objectToKeys as objectToKeys5, -} from '@code-pushup/utils'; -import { - ensureDirectoryExists as ensureDirectoryExists3, - executeProcess as executeProcess3, - filePathToCliArg as filePathToCliArg4, - isPromiseFulfilledResult, - isPromiseRejectedResult, - objectFromEntries as objectFromEntries4, - readJsonFile as readJsonFile4, -} from '@code-pushup/utils'; -import { objectToEntries as objectToEntries6 } from '@code-pushup/utils'; -import { pluginWorkDir as pluginWorkDir3 } from '@code-pushup/utils'; -import { - objectFromEntries as objectFromEntries3, - pluralize, -} from '@code-pushup/utils'; -// packages/plugin-js-packages/src/lib/runner/outdated/constants.ts -import { objectToKeys as objectToKeys6 } from '@code-pushup/utils'; -import { fileExists as fileExists3 } from '@code-pushup/utils'; -// packages/plugin-js-packages/src/lib/package-managers/derive-yarn.ts -import { executeProcess as executeProcess4 } from '@code-pushup/utils'; -import { ui as ui7 } from '@code-pushup/utils'; -import { ensureDirectoryExists as ensureDirectoryExists4 } from '@code-pushup/utils'; -import { - formatReportScore, - importModule as importModule2, - readJsonFile as readJsonFile5, - ui as ui10, -} from '@code-pushup/utils'; -import { ui as ui9 } from '@code-pushup/utils'; -import { - formatBytes as formatBytes2, - formatDuration as formatDuration2, - html as html2, -} from '@code-pushup/utils'; -import { - formatBytes, - formatDuration, - html, - truncateText, - ui as ui8, -} from '@code-pushup/utils'; -// packages/plugin-lighthouse/src/lib/utils.ts -import { filterItemRefsBy, toArray as toArray7 } from '@code-pushup/utils'; - -var coverageTypeSchema = z.enum(['function', 'branch', 'line']); -var coverageResultSchema = z.union([ - z.object({ - resultsPath: z - .string({ - description: 'Path to coverage results for Nx setup.', - }) - .includes('lcov'), - pathToProject: z - .string({ - description: - 'Path from workspace root to project root. Necessary for LCOV reports which provide a relative path.', - }) - .optional(), - }), - z - .string({ - description: 'Path to coverage results for a single project setup.', - }) - .includes('lcov'), -]); -var coveragePluginConfigSchema = z.object({ - coverageToolCommand: z - .object({ - command: z - .string({ description: 'Command to run coverage tool.' }) - .min(1), - args: z - .array(z.string(), { - description: 'Arguments to be passed to the coverage tool.', - }) - .optional(), - }) - .optional(), - coverageTypes: z - .array(coverageTypeSchema, { - description: 'Coverage types measured. Defaults to all available types.', - }) - .min(1) - .default(['function', 'branch', 'line']), - reports: z - .array(coverageResultSchema, { - description: - 'Path to all code coverage report files. Only LCOV format is supported for now.', - }) - .min(1), - perfectScoreThreshold: z - .number({ - description: - 'Score will be 1 (perfect) for this coverage and above. Score range is 0 - 1.', - }) - .gt(0) - .max(1) - .optional(), -}); - -var WORKDIR = pluginWorkDir('coverage'); -var RUNNER_OUTPUT_PATH = path.join(WORKDIR, 'runner-output.json'); -var PLUGIN_CONFIG_PATH = path.join( - process.cwd(), - WORKDIR, - 'plugin-config.json', -); - -var godKnows = parseLcovExport; -var parseLcov = 'default' in godKnows ? godKnows.default : godKnows; - -var docCoveragePluginConfigSchema = z2.object({ - onlyAudits: z2.array(z2.string()).optional(), - sourceGlob: z2 - .array(z2.string()) - .default(['src/**/*.{ts,tsx}', '!**/*.spec.ts', '!**/*.test.ts']), -}); - -// packages/plugin-doc-coverage/src/lib/constants.ts -var PLUGIN_SLUG = 'doc-coverage'; -var AUDITS_MAP = { - 'classes-coverage': { - slug: 'classes-coverage', - title: 'Classes coverage', - description: 'Coverage of classes', - }, - 'methods-coverage': { - slug: 'methods-coverage', - title: 'Methods coverage', - description: 'Coverage of methods', - }, - 'functions-coverage': { - slug: 'functions-coverage', - title: 'Functions coverage', - description: 'Coverage of functions', - }, - 'interfaces-coverage': { - slug: 'interfaces-coverage', - title: 'Interfaces coverage', - description: 'Coverage of interfaces', - }, - 'variables-coverage': { - slug: 'variables-coverage', - title: 'Variables coverage', - description: 'Coverage of variables', - }, - 'properties-coverage': { - slug: 'properties-coverage', - title: 'Properties coverage', - description: 'Coverage of properties', - }, - 'types-coverage': { - slug: 'types-coverage', - title: 'Types coverage', - description: 'Coverage of types', - }, - 'enums-coverage': { - slug: 'enums-coverage', - title: 'Enums coverage', - description: 'Coverage of enums', - }, -}; -var groups = [ - { - slug: 'documentation-coverage', - title: 'Documentation coverage', - description: 'Documentation coverage', - refs: Object.keys(AUDITS_MAP).map(slug => { - switch (slug) { - case 'classes-coverage': - case 'functions-coverage': - case 'methods-coverage': - return { slug, weight: 2 }; - case 'interfaces-coverage': - case 'properties-coverage': - case 'types-coverage': - default: - return { slug, weight: 1 }; - } - }), - }, -]; - -function filterAuditsByPluginConfig(config2) { - const { onlyAudits } = config2; - if (!onlyAudits || onlyAudits.length === 0) { - return Object.values(AUDITS_MAP); - } - return Object.values(AUDITS_MAP).filter(audit => - onlyAudits.includes(audit.slug), - ); -} -function filterGroupsByOnlyAudits(groups2, options) { - const audits2 = filterAuditsByPluginConfig(options); - return groups2 - .map(group => ({ - ...group, - refs: group.refs.filter(ref => - audits2.some(audit => audit.slug === ref.slug), - ), - })) - .filter(group => group.refs.length > 0); -} -function trasformCoverageReportToAudits(coverageResult, options) { - return Object.entries(coverageResult) - .filter( - ([type]) => - !options.onlyAudits?.length || - options.onlyAudits.includes(`${type}-coverage`), - ) - .map(([type, items]) => { - const coverageType = type; - const coverage = items.coverage; - return { - slug: `${coverageType}-coverage`, - value: coverage, - score: coverage / 100, - displayValue: `${coverage} %`, - details: { - issues: items.issues.map(({ file, line }) => ({ - message: 'Missing documentation', - source: { file, position: { startLine: line } }, - severity: 'warning', - })), - }, - }; - }); -} -function getCoverageTypeFromKind(kind) { - switch (kind) { - case SyntaxKind.ClassDeclaration: - return 'classes'; - case SyntaxKind.MethodDeclaration: - return 'methods'; - case SyntaxKind.FunctionDeclaration: - return 'functions'; - case SyntaxKind.InterfaceDeclaration: - return 'interfaces'; - case SyntaxKind.EnumDeclaration: - return 'enums'; - case SyntaxKind.VariableDeclaration: - return 'variables'; - case SyntaxKind.PropertyDeclaration: - return 'properties'; - case SyntaxKind.TypeAliasDeclaration: - return 'types'; - default: - throw new Error(`Unsupported syntax kind: ${kind}`); - } -} - -// packages/plugin-doc-coverage/src/lib/runner/utils.ts -function createEmptyUnprocessedCoverageReport() { - return { - enums: { nodesCount: 0, issues: [] }, - interfaces: { nodesCount: 0, issues: [] }, - types: { nodesCount: 0, issues: [] }, - functions: { nodesCount: 0, issues: [] }, - variables: { nodesCount: 0, issues: [] }, - classes: { nodesCount: 0, issues: [] }, - methods: { nodesCount: 0, issues: [] }, - properties: { nodesCount: 0, issues: [] }, - }; -} -function calculateCoverage2(result) { - return Object.fromEntries( - Object.entries(result).map(([key, value]) => { - const type = key; - return [ - type, - { - coverage: - value.nodesCount === 0 - ? 100 - : (1 - value.issues.length / value.nodesCount) * 100, - issues: value.issues, - nodesCount: value.nodesCount, - }, - ]; - }), - ); -} - -// packages/plugin-doc-coverage/src/lib/runner/doc-processer.ts -function processDocCoverage(config2) { - const project = new Project(); - project.addSourceFilesAtPaths(config2.sourceGlob); - return getUnprocessedCoverageReport(project.getSourceFiles()); -} -function getUnprocessedCoverageReport(sourceFiles) { - const unprocessedCoverageReport = sourceFiles.reduce( - (coverageReportOfAllFiles, sourceFile) => { - const filePath = sourceFile.getFilePath(); - const classes = sourceFile.getClasses(); - const allNodesFromFile = [ - ...sourceFile.getFunctions(), - ...classes, - ...getClassNodes(classes), - ...sourceFile.getTypeAliases(), - ...sourceFile.getEnums(), - ...sourceFile.getInterfaces(), - // ...sourceFile.getVariableStatements().flatMap(statement => statement.getDeclarations()) - ]; - const coverageReportOfCurrentFile = allNodesFromFile.reduce( - (acc, node) => { - const nodeType = getCoverageTypeFromKind(node.getKind()); - acc[nodeType].nodesCount++; - if (node.getJsDocs().length === 0) { - acc[nodeType].issues.push( - getUndocumentedNode( - filePath, - nodeType, - node.getName() || '', - node.getStartLineNumber(), - ), - ); - } - return acc; - }, - createEmptyUnprocessedCoverageReport(), - ); - return mergeCoverageResults( - coverageReportOfAllFiles, - coverageReportOfCurrentFile, - ); - }, - createEmptyUnprocessedCoverageReport(), - ); - return calculateCoverage2(unprocessedCoverageReport); -} -function mergeCoverageResults(results, current) { - return { - ...Object.fromEntries( - Object.entries(results).map(([key, value]) => { - const node = value; - const type = key; - return [ - type, - { - nodesCount: node.nodesCount + current[type].nodesCount, - issues: [...node.issues, ...current[type].issues], - }, - ]; - }), - ), - }; -} -function getClassNodes(classNodes) { - return classNodes.flatMap(classNode => [ - ...classNode.getMethods(), - ...classNode.getProperties(), - ]); -} -function getUndocumentedNode(file, type, name, line) { - return { file, type, name, line }; -} - -// packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.ts -var PLUGIN_TITLE = 'Documentation coverage'; -var PLUGIN_DESCRIPTION = 'Official Code PushUp documentation coverage plugin.'; -var PLUGIN_DOCS_URL = - 'https://www.npmjs.com/package/@code-pushup/doc-coverage-plugin/'; -async function docCoveragePlugin(config2) { - const docCoverageConfig = docCoveragePluginConfigSchema.parse(config2); - const groupsC = filterGroupsByOnlyAudits(groups, docCoverageConfig); - const auditsC = filterAuditsByPluginConfig(docCoverageConfig); - return { - slug: PLUGIN_SLUG, - title: PLUGIN_TITLE, - icon: 'folder-src', - description: PLUGIN_DESCRIPTION, - docsUrl: PLUGIN_DOCS_URL, - groups: filterGroupsByOnlyAudits(groups, docCoverageConfig), - audits: filterAuditsByPluginConfig(docCoverageConfig), - runner: createRunnerFunction(docCoverageConfig), - }; -} -function createRunnerFunction(config2) { - return () => { - const coverageResult = processDocCoverage(config2); - return trasformCoverageReportToAudits(coverageResult, config2); - }; -} - -// packages/plugin-doc-coverage/src/index.ts -var src_default = docCoveragePlugin; - -var patternsSchema = z3.union([z3.string(), z3.array(z3.string()).min(1)], { - description: - 'Lint target files. May contain file paths, directory paths or glob patterns', -}); -var eslintrcSchema = z3.string({ description: 'Path to ESLint config file' }); -var eslintTargetObjectSchema = z3.object({ - eslintrc: eslintrcSchema.optional(), - patterns: patternsSchema, -}); -var eslintTargetSchema = z3 - .union([patternsSchema, eslintTargetObjectSchema]) - .transform(target => - typeof target === 'string' || Array.isArray(target) - ? { patterns: target } - : target, - ); -var eslintPluginConfigSchema = z3 - .union([eslintTargetSchema, z3.array(eslintTargetSchema).min(1)]) - .transform(toArray); - -// packages/plugin-eslint/src/lib/runner/index.ts -var WORKDIR2 = pluginWorkDir2('eslint'); -var RUNNER_OUTPUT_PATH2 = path7.join(WORKDIR2, 'runner-output.json'); -var PLUGIN_CONFIG_PATH2 = path7.join( - process.cwd(), - WORKDIR2, - 'plugin-config.json', -); - -// packages/plugin-js-packages/src/lib/constants.ts -var defaultAuditLevelMapping = { - critical: 'error', - high: 'error', - moderate: 'warning', - low: 'warning', - info: 'info', -}; - -// packages/plugin-js-packages/src/lib/config.ts -var dependencyGroups = ['prod', 'dev', 'optional']; -var dependencyGroupSchema = z4.enum(dependencyGroups); -var packageCommandSchema = z4.enum(['audit', 'outdated']); -var packageManagerIdSchema = z4.enum([ - 'npm', - 'yarn-classic', - 'yarn-modern', - 'pnpm', -]); -var packageJsonPathSchema = z4 - .union([ - z4.array(z4.string()).min(1), - z4.object({ autoSearch: z4.literal(true) }), - ]) - .describe( - 'File paths to package.json. Looks only at root package.json by default', - ) - .default(['package.json']); -var packageAuditLevels = ['critical', 'high', 'moderate', 'low', 'info']; -var packageAuditLevelSchema = z4.enum(packageAuditLevels); -function fillAuditLevelMapping(mapping) { - return { - critical: mapping.critical ?? defaultAuditLevelMapping.critical, - high: mapping.high ?? defaultAuditLevelMapping.high, - moderate: mapping.moderate ?? defaultAuditLevelMapping.moderate, - low: mapping.low ?? defaultAuditLevelMapping.low, - info: mapping.info ?? defaultAuditLevelMapping.info, - }; -} -var jsPackagesPluginConfigSchema = z4.object({ - checks: z4 - .array(packageCommandSchema, { - description: - 'Package manager commands to be run. Defaults to both audit and outdated.', - }) - .min(1) - .default(['audit', 'outdated']), - packageManager: packageManagerIdSchema - .describe('Package manager to be used.') - .optional(), - dependencyGroups: z4 - .array(dependencyGroupSchema) - .min(1) - .default(['prod', 'dev']), - auditLevelMapping: z4 - .record(packageAuditLevelSchema, issueSeveritySchema, { - description: - 'Mapping of audit levels to issue severity. Custom mapping or overrides may be entered manually, otherwise has a default preset.', - }) - .default(defaultAuditLevelMapping) - .transform(fillAuditLevelMapping), - packageJsonPaths: packageJsonPathSchema, -}); - -function filterAuditResult(result, key, referenceResult) { - if (result.vulnerabilities.length === 0) { - return result; - } - const uniqueResult = result.vulnerabilities.reduce( - (acc, ref) => { - const matchReference = referenceResult ?? acc; - const isMatch = matchReference.vulnerabilities - .map(vulnerability => vulnerability[key]) - .includes(ref[key]); - if (isMatch) { - return { - vulnerabilities: acc.vulnerabilities, - summary: { - ...acc.summary, - [ref.severity]: acc.summary[ref.severity] - 1, - total: acc.summary.total - 1, - }, - }; - } - return { - vulnerabilities: [...acc.vulnerabilities, ref], - summary: acc.summary, - }; - }, - { vulnerabilities: [], summary: result.summary }, - ); - return { - vulnerabilities: uniqueResult.vulnerabilities, - summary: uniqueResult.summary, - }; -} - -// packages/plugin-js-packages/src/lib/package-managers/constants.ts -var COMMON_AUDIT_ARGS = ['audit', '--json']; -var COMMON_OUTDATED_ARGS = ['outdated', '--json']; - -function npmToAuditResult(output) { - const npmAudit = JSON.parse(output); - const vulnerabilities = objectToEntries2(npmAudit.vulnerabilities).map( - ([name, detail]) => { - const advisory = npmToAdvisory(name, npmAudit.vulnerabilities); - return { - name: name.toString(), - severity: detail.severity, - versionRange: detail.range, - directDependency: detail.isDirect ? true : (detail.effects[0] ?? ''), - fixInformation: npmToFixInformation(detail.fixAvailable), - ...(advisory != null && { - title: advisory.title, - url: advisory.url, - }), - }; - }, - ); - return { - vulnerabilities, - summary: npmAudit.metadata.vulnerabilities, - }; -} -function npmToFixInformation(fixAvailable) { - if (typeof fixAvailable === 'boolean') { - return fixAvailable ? 'Fix is available.' : ''; - } - return `Fix available: Update \`${fixAvailable.name}\` to version **${fixAvailable.version}**${fixAvailable.isSemVerMajor ? ' (breaking change).' : '.'}`; -} -function npmToAdvisory( - name, - vulnerabilities, - prevNodes = /* @__PURE__ */ new Set(), -) { - const advisory = vulnerabilities[name]?.via; - if ( - Array.isArray(advisory) && - advisory.length > 0 && - typeof advisory[0] === 'object' - ) { - return { title: advisory[0].title, url: advisory[0].url }; - } - if ( - Array.isArray(advisory) && - advisory.length > 0 && - advisory.every(value => typeof value === 'string') - ) { - let advisoryInfo = null; - let newReferences = []; - let advisoryInfoFound = false; - for (const via of advisory) { - if (!prevNodes.has(via)) { - newReferences.push(via); - } - } - while (newReferences.length > 0 && !advisoryInfoFound) { - const ref = newReferences.pop(); - prevNodes.add(ref); - const result = npmToAdvisory(ref, vulnerabilities, prevNodes); - if (result != null) { - advisoryInfo = { title: result.title, url: result.url }; - advisoryInfoFound = true; - } - } - return advisoryInfo; - } - return null; -} - -function npmToOutdatedResult(output) { - const npmOutdated = JSON.parse(output); - return objectToEntries3(npmOutdated) - .filter(entry => entry[1].current != null) - .map(([name, overview]) => ({ - name, - current: overview.current, - latest: overview.latest, - type: overview.type, - ...(overview.homepage != null && { url: overview.homepage }), - })); -} - -// packages/plugin-js-packages/src/lib/package-managers/npm/npm.ts -var npmDependencyOptions = { - prod: ['--omit=dev', '--omit=optional'], - dev: ['--include=dev', '--omit=optional'], - optional: ['--include=optional', '--omit=dev'], -}; -var npmPackageManager = { - slug: 'npm', - name: 'NPM', - command: 'npm', - icon: 'npm', - docs: { - homepage: 'https://docs.npmjs.com/', - audit: 'https://docs.npmjs.com/cli/commands/npm-audit', - outdated: 'https://docs.npmjs.com/cli/commands/npm-outdated', - }, - audit: { - getCommandArgs: groupDep => [ - ...COMMON_AUDIT_ARGS, - ...npmDependencyOptions[groupDep], - '--audit-level=none', - ], - unifyResult: npmToAuditResult, - // prod dependencies need to be filtered out manually since v10 - postProcessResult: results => { - const depGroups = objectToKeys3(results); - const devFilter = - results.dev && results.prod - ? filterAuditResult(results.dev, 'name', results.prod) - : results.dev; - const optionalFilter = - results.optional && results.prod - ? filterAuditResult(results.optional, 'name', results.prod) - : results.optional; - return { - ...(depGroups.includes('prod') && { prod: results.prod }), - ...(depGroups.includes('dev') && { dev: devFilter }), - ...(depGroups.includes('optional') && { optional: optionalFilter }), - }; - }, - }, - outdated: { - commandArgs: [...COMMON_OUTDATED_ARGS, '--long'], - unifyResult: npmToOutdatedResult, - }, -}; - -var WORKDIR3 = pluginWorkDir3('js-packages'); -var RUNNER_OUTPUT_PATH3 = path11.join(WORKDIR3, 'runner-output.json'); -var PLUGIN_CONFIG_PATH3 = path11.join( - process.cwd(), - WORKDIR3, - 'plugin-config.json', -); - -var outdatedSeverity = { - major: 'error', - premajor: 'info', - minor: 'warning', - preminor: 'info', - patch: 'info', - prepatch: 'info', - prerelease: 'info', -}; -var RELEASE_TYPES = objectToKeys6(outdatedSeverity); - -var DEFAULT_CHROME_FLAGS = [...DEFAULT_FLAGS, '--headless']; -var LIGHTHOUSE_PLUGIN_SLUG = 'lighthouse'; -var LIGHTHOUSE_OUTPUT_PATH = path15.join( - DEFAULT_PERSIST_OUTPUT_DIR, - LIGHTHOUSE_PLUGIN_SLUG, -); - -var { audits, categories } = defaultConfig; -var allRawLighthouseAudits = await Promise.all( - (audits ?? []).map(loadLighthouseAudit), -); -var LIGHTHOUSE_NAVIGATION_AUDITS = allRawLighthouseAudits - .filter( - audit => - audit.meta.supportedModes == null || - (Array.isArray(audit.meta.supportedModes) && - audit.meta.supportedModes.includes('navigation')), - ) - .map(audit => ({ - slug: audit.meta.id, - title: getMetaString(audit.meta.title), - description: getMetaString(audit.meta.description), - })); -var navigationAuditSlugs = new Set( - LIGHTHOUSE_NAVIGATION_AUDITS.map(({ slug }) => slug), -); -var LIGHTHOUSE_GROUPS = Object.entries(categories ?? {}).map( - ([id, category]) => ({ - slug: id, - title: getMetaString(category.title), - ...(category.description && { - description: getMetaString(category.description), - }), - refs: category.auditRefs - .filter(({ id: auditSlug }) => navigationAuditSlugs.has(auditSlug)) - .map(ref => ({ - slug: ref.id, - weight: ref.weight, - })), - }), -); -function getMetaString(value) { - if (typeof value === 'string') { - return value; - } - return value.formattedDefault; -} -async function loadLighthouseAudit(value) { - if (typeof value === 'object' && 'implementation' in value) { - return value.implementation; - } - if (typeof value === 'function') { - return value; - } - const file = typeof value === 'string' ? value : value.path; - const module = await import(`lighthouse/core/audits/${file}.js`); - return module.default; -} -var LIGHTHOUSE_REPORT_NAME = 'lighthouse-report.json'; -var DEFAULT_CLI_FLAGS = { - // default values extracted from - // https://github.com/GoogleChrome/lighthouse/blob/7d80178c37a1b600ea8f092fc0b098029799a659/cli/cli-flags.js#L80 - verbose: false, - saveAssets: false, - chromeFlags: DEFAULT_CHROME_FLAGS, - port: 0, - hostname: '127.0.0.1', - view: false, - channel: 'cli', - // custom overwrites in favour of the plugin - // hide logs by default - quiet: true, - onlyAudits: [], - skipAudits: [], - onlyCategories: [], - output: ['json'], - outputPath: path16.join(LIGHTHOUSE_OUTPUT_PATH, LIGHTHOUSE_REPORT_NAME), -}; - -// packages/plugin-lighthouse/src/lib/normalize-flags.ts -var { onlyCategories, ...originalDefaultCliFlags } = DEFAULT_CLI_FLAGS; -var DEFAULT_LIGHTHOUSE_OPTIONS = { - ...originalDefaultCliFlags, - onlyGroups: onlyCategories, -}; -var lighthouseUnsupportedCliFlags = [ - 'precomputedLanternDataPath', - // Path to the file where precomputed lantern data should be read from. - 'chromeIgnoreDefaultFlags', - // ignore default flags from Lighthouse CLI - // No error reporting implemented as in the source Sentry was involved - // See: https://github.com/GoogleChrome/lighthouse/blob/d8ccf70692216b7fa047a4eaa2d1277b0b7fe947/cli/bin.js#L124 - 'enableErrorReporting', - // enable error reporting - // lighthouse CLI specific debug logs - 'list-all-audits', - // Prints a list of all available audits and exits. - 'list-locales', - // Prints a list of all supported locales and exits. - 'list-trace-categories', - // Prints a list of all required trace categories and exits. -]; -var LIGHTHOUSE_UNSUPPORTED_CLI_FLAGS = new Set(lighthouseUnsupportedCliFlags); - -function lighthouseGroupRef(groupSlug, weight = 1) { - return { - plugin: LIGHTHOUSE_PLUGIN_SLUG, - slug: groupSlug, - type: 'group', - weight, - }; -} - -// code-pushup.preset.ts -var lighthouseCategories = [ - { - slug: 'performance', - title: 'Performance', - refs: [lighthouseGroupRef('performance')], - }, - { - slug: 'a11y', - title: 'Accessibility', - refs: [lighthouseGroupRef('accessibility')], - }, - { - slug: 'best-practices', - title: 'Best Practices', - refs: [lighthouseGroupRef('best-practices')], - }, - { - slug: 'seo', - title: 'SEO', - refs: [lighthouseGroupRef('seo')], - }, -]; -function getDocCoverageCategories(config2) { - return [ - { - slug: 'doc-coverage-cat', - title: 'Documentation coverage', - description: 'Measures how much of your code is **documented**.', - refs: filterGroupsByOnlyAudits(groups, config2).map(group => ({ - weight: 1, - type: 'group', - plugin: PLUGIN_SLUG, - slug: group.slug, - })), - }, - ]; -} -var docCoverageCoreConfig = async config2 => { - return { - plugins: [await src_default(config2)], - categories: getDocCoverageCategories(config2), - }; -}; - -// packages/utils/src/lib/merge-configs.ts -function mergeConfigs(config2, ...configs) { - return configs.reduce( - (acc, obj) => ({ - ...acc, - ...mergeCategories(acc.categories, obj.categories), - ...mergePlugins(acc.plugins, obj.plugins), - ...mergePersist(acc.persist, obj.persist), - ...mergeUpload(acc.upload, obj.upload), - }), - config2, - ); -} -function mergeCategories(a, b) { - if (!a && !b) { - return {}; - } - const mergedMap = /* @__PURE__ */ new Map(); - const addToMap = categories2 => { - categories2.forEach(newObject => { - if (mergedMap.has(newObject.slug)) { - const existingObject = mergedMap.get(newObject.slug); - mergedMap.set(newObject.slug, { - ...existingObject, - ...newObject, - refs: mergeByUniqueCategoryRefCombination( - existingObject?.refs, - newObject.refs, - ), - }); - } else { - mergedMap.set(newObject.slug, newObject); - } - }); - }; - if (a) { - addToMap(a); - } - if (b) { - addToMap(b); - } - return { categories: [...mergedMap.values()] }; -} -function mergePlugins(a, b) { - if (!a && !b) { - return { plugins: [] }; - } - const mergedMap = /* @__PURE__ */ new Map(); - const addToMap = plugins => { - plugins.forEach(newObject => { - mergedMap.set(newObject.slug, newObject); - }); - }; - if (a) { - addToMap(a); - } - if (b) { - addToMap(b); - } - return { plugins: [...mergedMap.values()] }; -} -function mergePersist(a, b) { - if (!a && !b) { - return {}; - } - if (a) { - return b ? { persist: { ...a, ...b } } : {}; - } else { - return { persist: b }; - } -} -function mergeByUniqueCategoryRefCombination(a, b) { - const map = /* @__PURE__ */ new Map(); - const addToMap = refs => { - refs.forEach(ref => { - const uniqueIdentification = `${ref.type}:${ref.plugin}:${ref.slug}`; - if (map.has(uniqueIdentification)) { - map.set(uniqueIdentification, { - ...map.get(uniqueIdentification), - ...ref, - }); - } else { - map.set(uniqueIdentification, ref); - } - }); - }; - if (a) { - addToMap(a); - } - if (b) { - addToMap(b); - } - return [...map.values()]; -} -function mergeUpload(a, b) { - if (!a && !b) { - return {}; - } - if (a) { - return b ? { upload: { ...a, ...b } } : {}; - } else { - return { upload: b }; - } -} - -// code-pushup.config.ts -var envSchema = z5.object({ - CP_SERVER: z5.string().url(), - CP_API_KEY: z5.string().min(1), - CP_ORGANIZATION: z5.string().min(1), - CP_PROJECT: z5.string().min(1), -}); -var { data: env } = await envSchema.safeParseAsync(process.env); -var config = { - ...(env && { - upload: { - server: env.CP_SERVER, - apiKey: env.CP_API_KEY, - organization: env.CP_ORGANIZATION, - project: env.CP_PROJECT, - }, - }), - plugins: [], -}; -var code_pushup_config_default = mergeConfigs( - config, - // await coverageCoreConfigNx(), - // await jsPackagesCoreConfig(), - // await lighthouseCoreConfig( - // 'https://github.com/code-pushup/cli?tab=readme-ov-file#code-pushup-cli/', - // ), - // await eslintCoreConfigNx(), - await docCoverageCoreConfig({ - sourceGlob: ['packages/**/*.ts', '!**/*.spec.ts', '!**/*.test.ts'], - onlyAudits: ['methods-coverage', 'functions-coverage'], - }), -); -export { code_pushup_config_default as default }; -//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiY29kZS1wdXNodXAuY29uZmlnLnRzIiwgInBhY2thZ2VzL3BsdWdpbi1jb3ZlcmFnZS9zcmMvbGliL2NvdmVyYWdlLXBsdWdpbi50cyIsICJwYWNrYWdlcy9wbHVnaW4tY292ZXJhZ2Uvc3JjL2xpYi9jb25maWcudHMiLCAicGFja2FnZXMvcGx1Z2luLWNvdmVyYWdlL3NyYy9saWIvcnVubmVyL2luZGV4LnRzIiwgInBhY2thZ2VzL3BsdWdpbi1jb3ZlcmFnZS9zcmMvbGliL3J1bm5lci9jb25zdGFudHMudHMiLCAicGFja2FnZXMvcGx1Z2luLWNvdmVyYWdlL3NyYy9saWIvcnVubmVyL2xjb3YvbGNvdi1ydW5uZXIudHMiLCAicGFja2FnZXMvcGx1Z2luLWNvdmVyYWdlL3NyYy9saWIvcnVubmVyL2xjb3YvcGFyc2UtbGNvdi50cyIsICJwYWNrYWdlcy9wbHVnaW4tY292ZXJhZ2Uvc3JjL2xpYi9ydW5uZXIvbGNvdi90cmFuc2Zvcm0udHMiLCAicGFja2FnZXMvcGx1Z2luLWNvdmVyYWdlL3NyYy9saWIvbngvY292ZXJhZ2UtcGF0aHMudHMiLCAicGFja2FnZXMvcGx1Z2luLWRvYy1jb3ZlcmFnZS9zcmMvbGliL2NvbmZpZy50cyIsICJwYWNrYWdlcy9wbHVnaW4tZG9jLWNvdmVyYWdlL3NyYy9saWIvY29uc3RhbnRzLnRzIiwgInBhY2thZ2VzL3BsdWdpbi1kb2MtY292ZXJhZ2Uvc3JjL2xpYi9ydW5uZXIvZG9jLXByb2Nlc3Nlci50cyIsICJwYWNrYWdlcy9wbHVnaW4tZG9jLWNvdmVyYWdlL3NyYy9saWIvdXRpbHMudHMiLCAicGFja2FnZXMvcGx1Z2luLWRvYy1jb3ZlcmFnZS9zcmMvbGliL3J1bm5lci91dGlscy50cyIsICJwYWNrYWdlcy9wbHVnaW4tZG9jLWNvdmVyYWdlL3NyYy9saWIvZG9jLWNvdmVyYWdlLXBsdWdpbi50cyIsICJwYWNrYWdlcy9wbHVnaW4tZG9jLWNvdmVyYWdlL3NyYy9pbmRleC50cyIsICJwYWNrYWdlcy9wbHVnaW4tZXNsaW50L3NyYy9saWIvZXNsaW50LXBsdWdpbi50cyIsICJwYWNrYWdlcy9wbHVnaW4tZXNsaW50L3NyYy9saWIvY29uZmlnLnRzIiwgInBhY2thZ2VzL3BsdWdpbi1lc2xpbnQvc3JjL2xpYi9tZXRhL2dyb3Vwcy50cyIsICJwYWNrYWdlcy9wbHVnaW4tZXNsaW50L3NyYy9saWIvbWV0YS9oYXNoLnRzIiwgInBhY2thZ2VzL3BsdWdpbi1lc2xpbnQvc3JjL2xpYi9tZXRhL3BhcnNlLnRzIiwgInBhY2thZ2VzL3BsdWdpbi1lc2xpbnQvc3JjL2xpYi9tZXRhL3ZlcnNpb25zL2ZsYXQudHMiLCAicGFja2FnZXMvcGx1Z2luLWVzbGludC9zcmMvbGliL21ldGEvdmVyc2lvbnMvbGVnYWN5LnRzIiwgInBhY2thZ2VzL3BsdWdpbi1lc2xpbnQvc3JjL2xpYi9zZXR1cC50cyIsICJwYWNrYWdlcy9wbHVnaW4tZXNsaW50L3NyYy9saWIvbWV0YS92ZXJzaW9ucy9kZXRlY3QudHMiLCAicGFja2FnZXMvcGx1Z2luLWVzbGludC9zcmMvbGliL21ldGEvdHJhbnNmb3JtLnRzIiwgInBhY2thZ2VzL3BsdWdpbi1lc2xpbnQvc3JjL2xpYi9ydW5uZXIvaW5kZXgudHMiLCAicGFja2FnZXMvcGx1Z2luLWVzbGludC9zcmMvbGliL3J1bm5lci9saW50LnRzIiwgInBhY2thZ2VzL3BsdWdpbi1lc2xpbnQvc3JjL2xpYi9ydW5uZXIvdHJhbnNmb3JtLnRzIiwgInBhY2thZ2VzL3BsdWdpbi1lc2xpbnQvc3JjL2xpYi9ueC91dGlscy50cyIsICJwYWNrYWdlcy9wbHVnaW4tanMtcGFja2FnZXMvc3JjL2xpYi9qcy1wYWNrYWdlcy1wbHVnaW4udHMiLCAicGFja2FnZXMvcGx1Z2luLWpzLXBhY2thZ2VzL3NyYy9saWIvY29uZmlnLnRzIiwgInBhY2thZ2VzL3BsdWdpbi1qcy1wYWNrYWdlcy9zcmMvbGliL2NvbnN0YW50cy50cyIsICJwYWNrYWdlcy9wbHVnaW4tanMtcGFja2FnZXMvc3JjL2xpYi9wYWNrYWdlLW1hbmFnZXJzL25wbS9ucG0udHMiLCAicGFja2FnZXMvcGx1Z2luLWpzLXBhY2thZ2VzL3NyYy9saWIvcnVubmVyL3V0aWxzLnRzIiwgInBhY2thZ2VzL3BsdWdpbi1qcy1wYWNrYWdlcy9zcmMvbGliL3BhY2thZ2UtbWFuYWdlcnMvY29uc3RhbnRzLnRzIiwgInBhY2thZ2VzL3BsdWdpbi1qcy1wYWNrYWdlcy9zcmMvbGliL3BhY2thZ2UtbWFuYWdlcnMvbnBtL2F1ZGl0LXJlc3VsdC50cyIsICJwYWNrYWdlcy9wbHVnaW4tanMtcGFja2FnZXMvc3JjL2xpYi9wYWNrYWdlLW1hbmFnZXJzL25wbS9vdXRkYXRlZC1yZXN1bHQudHMiLCAicGFja2FnZXMvcGx1Z2luLWpzLXBhY2thZ2VzL3NyYy9saWIvcGFja2FnZS1tYW5hZ2Vycy9wbnBtL3BucG0udHMiLCAicGFja2FnZXMvcGx1Z2luLWpzLXBhY2thZ2VzL3NyYy9saWIvcGFja2FnZS1tYW5hZ2Vycy9wbnBtL291dGRhdGVkLXJlc3VsdC50cyIsICJwYWNrYWdlcy9wbHVnaW4tanMtcGFja2FnZXMvc3JjL2xpYi9wYWNrYWdlLW1hbmFnZXJzL3lhcm4tY2xhc3NpYy9hdWRpdC1yZXN1bHQudHMiLCAicGFja2FnZXMvcGx1Z2luLWpzLXBhY2thZ2VzL3NyYy9saWIvcGFja2FnZS1tYW5hZ2Vycy95YXJuLWNsYXNzaWMvb3V0ZGF0ZWQtcmVzdWx0LnRzIiwgInBhY2thZ2VzL3BsdWdpbi1qcy1wYWNrYWdlcy9zcmMvbGliL3J1bm5lci9pbmRleC50cyIsICJwYWNrYWdlcy9wbHVnaW4tanMtcGFja2FnZXMvc3JjL2xpYi9ydW5uZXIvYXVkaXQvdHJhbnNmb3JtLnRzIiwgInBhY2thZ2VzL3BsdWdpbi1qcy1wYWNrYWdlcy9zcmMvbGliL3J1bm5lci9jb25zdGFudHMudHMiLCAicGFja2FnZXMvcGx1Z2luLWpzLXBhY2thZ2VzL3NyYy9saWIvcnVubmVyL291dGRhdGVkL3RyYW5zZm9ybS50cyIsICJwYWNrYWdlcy9wbHVnaW4tanMtcGFja2FnZXMvc3JjL2xpYi9ydW5uZXIvb3V0ZGF0ZWQvY29uc3RhbnRzLnRzIiwgInBhY2thZ2VzL3BsdWdpbi1qcy1wYWNrYWdlcy9zcmMvbGliL3BhY2thZ2UtbWFuYWdlcnMvZGVyaXZlLXBhY2thZ2UtbWFuYWdlci50cyIsICJwYWNrYWdlcy9wbHVnaW4tanMtcGFja2FnZXMvc3JjL2xpYi9wYWNrYWdlLW1hbmFnZXJzL2Rlcml2ZS15YXJuLnRzIiwgInBhY2thZ2VzL3BsdWdpbi1saWdodGhvdXNlL3NyYy9saWIvbGlnaHRob3VzZS1wbHVnaW4udHMiLCAicGFja2FnZXMvcGx1Z2luLWxpZ2h0aG91c2Uvc3JjL2xpYi9jb25zdGFudHMudHMiLCAicGFja2FnZXMvcGx1Z2luLWxpZ2h0aG91c2Uvc3JjL2xpYi9ub3JtYWxpemUtZmxhZ3MudHMiLCAicGFja2FnZXMvcGx1Z2luLWxpZ2h0aG91c2Uvc3JjL2xpYi9ydW5uZXIvY29uc3RhbnRzLnRzIiwgInBhY2thZ2VzL3BsdWdpbi1saWdodGhvdXNlL3NyYy9saWIvcnVubmVyL3J1bm5lci50cyIsICJwYWNrYWdlcy9wbHVnaW4tbGlnaHRob3VzZS9zcmMvbGliL3J1bm5lci91dGlscy50cyIsICJwYWNrYWdlcy9wbHVnaW4tbGlnaHRob3VzZS9zcmMvbGliL3J1bm5lci9kZXRhaWxzL2RldGFpbHMudHMiLCAicGFja2FnZXMvcGx1Z2luLWxpZ2h0aG91c2Uvc3JjL2xpYi9ydW5uZXIvZGV0YWlscy9vcHBvcnR1bml0eS50eXBlLnRzIiwgInBhY2thZ2VzL3BsdWdpbi1saWdodGhvdXNlL3NyYy9saWIvcnVubmVyL2RldGFpbHMvdGFibGUudHlwZS50cyIsICJwYWNrYWdlcy9wbHVnaW4tbGlnaHRob3VzZS9zcmMvbGliL3J1bm5lci9kZXRhaWxzL2l0ZW0tdmFsdWUudHMiLCAicGFja2FnZXMvcGx1Z2luLWxpZ2h0aG91c2Uvc3JjL2xpYi9ydW5uZXIvZGV0YWlscy91dGlscy50cyIsICJwYWNrYWdlcy9wbHVnaW4tbGlnaHRob3VzZS9zcmMvbGliL3V0aWxzLnRzIiwgImNvZGUtcHVzaHVwLnByZXNldC50cyIsICJwYWNrYWdlcy91dGlscy9zcmMvaW5kZXgudHMiLCAicGFja2FnZXMvdXRpbHMvc3JjL2xpYi9leGVjdXRlLXByb2Nlc3MudHMiLCAicGFja2FnZXMvdXRpbHMvc3JjL2xpYi9yZXBvcnRzL3V0aWxzLnRzIiwgInBhY2thZ2VzL3V0aWxzL3NyYy9saWIvZmlsZS1zeXN0ZW0udHMiLCAicGFja2FnZXMvdXRpbHMvc3JjL2xpYi9mb3JtYXR0aW5nLnRzIiwgInBhY2thZ2VzL3V0aWxzL3NyYy9saWIvbG9nZ2luZy50cyIsICJwYWNrYWdlcy91dGlscy9zcmMvbGliL2dpdC9naXQuY29tbWl0cy1hbmQtdGFncy50cyIsICJwYWNrYWdlcy91dGlscy9zcmMvbGliL3NlbXZlci50cyIsICJwYWNrYWdlcy91dGlscy9zcmMvbGliL2dpdC9naXQudHMiLCAicGFja2FnZXMvdXRpbHMvc3JjL2xpYi90cmFuc2Zvcm0udHMiLCAicGFja2FnZXMvdXRpbHMvc3JjL2xpYi9tZXJnZS1jb25maWdzLnRzIiwgInBhY2thZ2VzL3V0aWxzL3NyYy9saWIvcHJvZ3Jlc3MudHMiLCAicGFja2FnZXMvdXRpbHMvc3JjL2xpYi9yZXBvcnRzL2dlbmVyYXRlLW1kLXJlcG9ydC50cyIsICJwYWNrYWdlcy91dGlscy9zcmMvbGliL3JlcG9ydHMvZm9ybWF0dGluZy50cyIsICJwYWNrYWdlcy91dGlscy9zcmMvbGliL3JlcG9ydHMvZ2VuZXJhdGUtbWQtcmVwb3J0LWNhdGVnb3ktc2VjdGlvbi50cyIsICJwYWNrYWdlcy91dGlscy9zcmMvbGliL3JlcG9ydHMvZ2VuZXJhdGUtbWQtcmVwb3J0cy1kaWZmLnRzIiwgInBhY2thZ2VzL3V0aWxzL3NyYy9saWIvcmVwb3J0cy9nZW5lcmF0ZS1tZC1yZXBvcnRzLWRpZmYtdXRpbHMudHMiLCAicGFja2FnZXMvdXRpbHMvc3JjL2xpYi9yZXBvcnRzL2xvYWQtcmVwb3J0LnRzIiwgInBhY2thZ2VzL3V0aWxzL3NyYy9saWIvcmVwb3J0cy9sb2ctc3Rkb3V0LXN1bW1hcnkudHMiLCAicGFja2FnZXMvdXRpbHMvc3JjL2xpYi96b2QtdmFsaWRhdGlvbi50cyJdLAogICJzb3VyY2VzQ29udGVudCI6IFsiY29uc3QgX19pbmplY3RlZF9maWxlbmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9jb2RlLXB1c2h1cC5jb25maWcudHNcIjtjb25zdCBfX2luamVjdGVkX2Rpcm5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGlcIjtjb25zdCBfX2luamVjdGVkX2ltcG9ydF9tZXRhX3VybF9fID0gXCJmaWxlOi8vL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvY29kZS1wdXNodXAuY29uZmlnLnRzXCI7aW1wb3J0ICdkb3RlbnYvY29uZmlnJztcbmltcG9ydCB7IHogfSBmcm9tICd6b2QnO1xuaW1wb3J0IHsgZG9jQ292ZXJhZ2VDb3JlQ29uZmlnIH0gZnJvbSAnLi9jb2RlLXB1c2h1cC5wcmVzZXQuanMnO1xuaW1wb3J0IHR5cGUgeyBDb3JlQ29uZmlnIH0gZnJvbSAnLi9wYWNrYWdlcy9tb2RlbHMvc3JjL2luZGV4LmpzJztcbmltcG9ydCB7IG1lcmdlQ29uZmlncyB9IGZyb20gJy4vcGFja2FnZXMvdXRpbHMvc3JjL2luZGV4LmpzJztcbi8vIGxvYWQgdXBsb2FkIGNvbmZpZ3VyYXRpb24gZnJvbSBlbnZpcm9ubWVudFxuY29uc3QgZW52U2NoZW1hID0gei5vYmplY3Qoe1xuICBDUF9TRVJWRVI6IHouc3RyaW5nKCkudXJsKCksXG4gIENQX0FQSV9LRVk6IHouc3RyaW5nKCkubWluKDEpLFxuICBDUF9PUkdBTklaQVRJT046IHouc3RyaW5nKCkubWluKDEpLFxuICBDUF9QUk9KRUNUOiB6LnN0cmluZygpLm1pbigxKSxcbn0pO1xuY29uc3QgeyBkYXRhOiBlbnYgfSA9IGF3YWl0IGVudlNjaGVtYS5zYWZlUGFyc2VBc3luYyhwcm9jZXNzLmVudik7XG5cbmNvbnN0IGNvbmZpZzogQ29yZUNvbmZpZyA9IHtcbiAgLi4uKGVudiAmJiB7XG4gICAgdXBsb2FkOiB7XG4gICAgICBzZXJ2ZXI6IGVudi5DUF9TRVJWRVIsXG4gICAgICBhcGlLZXk6IGVudi5DUF9BUElfS0VZLFxuICAgICAgb3JnYW5pemF0aW9uOiBlbnYuQ1BfT1JHQU5JWkFUSU9OLFxuICAgICAgcHJvamVjdDogZW52LkNQX1BST0pFQ1QsXG4gICAgfSxcbiAgfSksXG5cbiAgcGx1Z2luczogW10sXG59O1xuXG5leHBvcnQgZGVmYXVsdCBtZXJnZUNvbmZpZ3MoXG4gIGNvbmZpZyxcbiAgLy8gYXdhaXQgY292ZXJhZ2VDb3JlQ29uZmlnTngoKSxcbiAgLy8gYXdhaXQganNQYWNrYWdlc0NvcmVDb25maWcoKSxcbiAgLy8gYXdhaXQgbGlnaHRob3VzZUNvcmVDb25maWcoXG4gIC8vICAgJ2h0dHBzOi8vZ2l0aHViLmNvbS9jb2RlLXB1c2h1cC9jbGk/dGFiPXJlYWRtZS1vdi1maWxlI2NvZGUtcHVzaHVwLWNsaS8nLFxuICAvLyApLFxuICAvLyBhd2FpdCBlc2xpbnRDb3JlQ29uZmlnTngoKSxcbiAgYXdhaXQgZG9jQ292ZXJhZ2VDb3JlQ29uZmlnKHtcbiAgICBzb3VyY2VHbG9iOiBbJ3BhY2thZ2VzLyoqLyoudHMnLCAnISoqLyouc3BlYy50cycsICchKiovKi50ZXN0LnRzJ10sXG4gICAgb25seUF1ZGl0czogWydtZXRob2RzLWNvdmVyYWdlJywgJ2Z1bmN0aW9ucy1jb3ZlcmFnZSddXG4gIH0pLFxuKTtcbiIsICJjb25zdCBfX2luamVjdGVkX2ZpbGVuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1jb3ZlcmFnZS9zcmMvbGliL2NvdmVyYWdlLXBsdWdpbi50c1wiO2NvbnN0IF9faW5qZWN0ZWRfZGlybmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tY292ZXJhZ2Uvc3JjL2xpYlwiO2NvbnN0IF9faW5qZWN0ZWRfaW1wb3J0X21ldGFfdXJsX18gPSBcImZpbGU6Ly8vaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tY292ZXJhZ2Uvc3JjL2xpYi9jb3ZlcmFnZS1wbHVnaW4udHNcIjtpbXBvcnQgeyBjcmVhdGVSZXF1aXJlIH0gZnJvbSAnbm9kZTptb2R1bGUnO1xuaW1wb3J0IHBhdGggZnJvbSAnbm9kZTpwYXRoJztcbmltcG9ydCB7IGZpbGVVUkxUb1BhdGggfSBmcm9tICdub2RlOnVybCc7XG5pbXBvcnQgdHlwZSB7IEF1ZGl0LCBHcm91cCwgUGx1Z2luQ29uZmlnIH0gZnJvbSAnQGNvZGUtcHVzaHVwL21vZGVscyc7XG5pbXBvcnQgeyBjYXBpdGFsaXplIH0gZnJvbSAnQGNvZGUtcHVzaHVwL3V0aWxzJztcbmltcG9ydCB7XG4gIHR5cGUgQ292ZXJhZ2VQbHVnaW5Db25maWcsXG4gIHR5cGUgQ292ZXJhZ2VUeXBlLFxuICBjb3ZlcmFnZVBsdWdpbkNvbmZpZ1NjaGVtYSxcbn0gZnJvbSAnLi9jb25maWcuanMnO1xuaW1wb3J0IHsgY3JlYXRlUnVubmVyQ29uZmlnIH0gZnJvbSAnLi9ydW5uZXIvaW5kZXguanMnO1xuaW1wb3J0IHsgY292ZXJhZ2VEZXNjcmlwdGlvbiwgY292ZXJhZ2VUeXBlV2VpZ2h0TWFwcGVyIH0gZnJvbSAnLi91dGlscy5qcyc7XG5cbi8qKlxuICogSW5zdGFudGlhdGVzIENvZGUgUHVzaFVwIGNvZGUgY292ZXJhZ2UgcGx1Z2luIGZvciBjb3JlIGNvbmZpZy5cbiAqXG4gKiBAZXhhbXBsZVxuICogaW1wb3J0IGNvdmVyYWdlUGx1Z2luIGZyb20gJ0Bjb2RlLXB1c2h1cC9jb3ZlcmFnZS1wbHVnaW4nXG4gKlxuICogZXhwb3J0IGRlZmF1bHQge1xuICogICAvLyAuLi4gY29yZSBjb25maWcgLi4uXG4gKiAgIHBsdWdpbnM6IFtcbiAqICAgICAvLyAuLi4gb3RoZXIgcGx1Z2lucyAuLi5cbiAqICAgICBhd2FpdCBjb3ZlcmFnZVBsdWdpbih7XG4gKiAgICAgICByZXBvcnRzOiBbeyByZXN1bHRzUGF0aDogJ2NvdmVyYWdlL2NsaS9sY292LmluZm8nLCBwYXRoVG9Qcm9qZWN0OiAncGFja2FnZXMvY2xpJyB9XVxuICogICAgIH0pXG4gKiAgIF1cbiAqIH1cbiAqXG4gKiBAcmV0dXJucyBQbHVnaW4gY29uZmlndXJhdGlvbi5cbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGNvdmVyYWdlUGx1Z2luKFxuICBjb25maWc6IENvdmVyYWdlUGx1Z2luQ29uZmlnLFxuKTogUHJvbWlzZTxQbHVnaW5Db25maWc+IHtcbiAgY29uc3QgY292ZXJhZ2VDb25maWcgPSBjb3ZlcmFnZVBsdWdpbkNvbmZpZ1NjaGVtYS5wYXJzZShjb25maWcpO1xuXG4gIGNvbnN0IGF1ZGl0cyA9IGNvdmVyYWdlQ29uZmlnLmNvdmVyYWdlVHlwZXMubWFwKFxuICAgICh0eXBlKTogQXVkaXQgPT4gKHtcbiAgICAgIHNsdWc6IGAke3R5cGV9LWNvdmVyYWdlYCxcbiAgICAgIHRpdGxlOiBgJHtjYXBpdGFsaXplKHR5cGUpfSBjb3ZlcmFnZWAsXG4gICAgICBkZXNjcmlwdGlvbjogY292ZXJhZ2VEZXNjcmlwdGlvblt0eXBlXSxcbiAgICB9KSxcbiAgKTtcblxuICBjb25zdCBncm91cDogR3JvdXAgPSB7XG4gICAgc2x1ZzogJ2NvdmVyYWdlJyxcbiAgICB0aXRsZTogJ0NvZGUgY292ZXJhZ2UgbWV0cmljcycsXG4gICAgZGVzY3JpcHRpb246ICdHcm91cCBjb250YWluaW5nIGFsbCBkZWZpbmVkIGNvdmVyYWdlIHR5cGVzIGFzIGF1ZGl0cy4nLFxuICAgIHJlZnM6IGF1ZGl0cy5tYXAoYXVkaXQgPT4gKHtcbiAgICAgIC4uLmF1ZGl0LFxuICAgICAgd2VpZ2h0OlxuICAgICAgICBjb3ZlcmFnZVR5cGVXZWlnaHRNYXBwZXJbXG4gICAgICAgICAgYXVkaXQuc2x1Zy5zbGljZSgwLCBhdWRpdC5zbHVnLmluZGV4T2YoJy0nKSkgYXMgQ292ZXJhZ2VUeXBlXG4gICAgICAgIF0sXG4gICAgfSkpLFxuICB9O1xuXG4gIGNvbnN0IHJ1bm5lclNjcmlwdFBhdGggPSBwYXRoLmpvaW4oXG4gICAgZmlsZVVSTFRvUGF0aChwYXRoLmRpcm5hbWUoaW1wb3J0Lm1ldGEudXJsKSksXG4gICAgJy4uJyxcbiAgICAnYmluLmpzJyxcbiAgKTtcblxuICBjb25zdCBwYWNrYWdlSnNvbiA9IGNyZWF0ZVJlcXVpcmUoaW1wb3J0Lm1ldGEudXJsKShcbiAgICAnLi4vLi4vcGFja2FnZS5qc29uJyxcbiAgKSBhcyB0eXBlb2YgaW1wb3J0KCcuLi8uLi9wYWNrYWdlLmpzb24nKTtcblxuICByZXR1cm4ge1xuICAgIHNsdWc6ICdjb3ZlcmFnZScsXG4gICAgdGl0bGU6ICdDb2RlIGNvdmVyYWdlJyxcbiAgICBpY29uOiAnZm9sZGVyLWNvdmVyYWdlLW9wZW4nLFxuICAgIGRlc2NyaXB0aW9uOiAnT2ZmaWNpYWwgQ29kZSBQdXNoVXAgY29kZSBjb3ZlcmFnZSBwbHVnaW4uJyxcbiAgICBkb2NzVXJsOiAnaHR0cHM6Ly93d3cubnBtanMuY29tL3BhY2thZ2UvQGNvZGUtcHVzaHVwL2NvdmVyYWdlLXBsdWdpbi8nLFxuICAgIHBhY2thZ2VOYW1lOiBwYWNrYWdlSnNvbi5uYW1lLFxuICAgIHZlcnNpb246IHBhY2thZ2VKc29uLnZlcnNpb24sXG4gICAgYXVkaXRzLFxuICAgIGdyb3VwczogW2dyb3VwXSxcbiAgICBydW5uZXI6IGF3YWl0IGNyZWF0ZVJ1bm5lckNvbmZpZyhydW5uZXJTY3JpcHRQYXRoLCBjb3ZlcmFnZUNvbmZpZyksXG4gIH07XG59XG4iLCAiY29uc3QgX19pbmplY3RlZF9maWxlbmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tY292ZXJhZ2Uvc3JjL2xpYi9jb25maWcudHNcIjtjb25zdCBfX2luamVjdGVkX2Rpcm5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWNvdmVyYWdlL3NyYy9saWJcIjtjb25zdCBfX2luamVjdGVkX2ltcG9ydF9tZXRhX3VybF9fID0gXCJmaWxlOi8vL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWNvdmVyYWdlL3NyYy9saWIvY29uZmlnLnRzXCI7aW1wb3J0IHsgeiB9IGZyb20gJ3pvZCc7XG5cbmV4cG9ydCBjb25zdCBjb3ZlcmFnZVR5cGVTY2hlbWEgPSB6LmVudW0oWydmdW5jdGlvbicsICdicmFuY2gnLCAnbGluZSddKTtcbmV4cG9ydCB0eXBlIENvdmVyYWdlVHlwZSA9IHouaW5mZXI8dHlwZW9mIGNvdmVyYWdlVHlwZVNjaGVtYT47XG5cbmV4cG9ydCBjb25zdCBjb3ZlcmFnZVJlc3VsdFNjaGVtYSA9IHoudW5pb24oW1xuICB6Lm9iamVjdCh7XG4gICAgcmVzdWx0c1BhdGg6IHpcbiAgICAgIC5zdHJpbmcoe1xuICAgICAgICBkZXNjcmlwdGlvbjogJ1BhdGggdG8gY292ZXJhZ2UgcmVzdWx0cyBmb3IgTnggc2V0dXAuJyxcbiAgICAgIH0pXG4gICAgICAuaW5jbHVkZXMoJ2xjb3YnKSxcbiAgICBwYXRoVG9Qcm9qZWN0OiB6XG4gICAgICAuc3RyaW5nKHtcbiAgICAgICAgZGVzY3JpcHRpb246XG4gICAgICAgICAgJ1BhdGggZnJvbSB3b3Jrc3BhY2Ugcm9vdCB0byBwcm9qZWN0IHJvb3QuIE5lY2Vzc2FyeSBmb3IgTENPViByZXBvcnRzIHdoaWNoIHByb3ZpZGUgYSByZWxhdGl2ZSBwYXRoLicsXG4gICAgICB9KVxuICAgICAgLm9wdGlvbmFsKCksXG4gIH0pLFxuICB6XG4gICAgLnN0cmluZyh7XG4gICAgICBkZXNjcmlwdGlvbjogJ1BhdGggdG8gY292ZXJhZ2UgcmVzdWx0cyBmb3IgYSBzaW5nbGUgcHJvamVjdCBzZXR1cC4nLFxuICAgIH0pXG4gICAgLmluY2x1ZGVzKCdsY292JyksXG5dKTtcbmV4cG9ydCB0eXBlIENvdmVyYWdlUmVzdWx0ID0gei5pbmZlcjx0eXBlb2YgY292ZXJhZ2VSZXN1bHRTY2hlbWE+O1xuXG5leHBvcnQgY29uc3QgY292ZXJhZ2VQbHVnaW5Db25maWdTY2hlbWEgPSB6Lm9iamVjdCh7XG4gIGNvdmVyYWdlVG9vbENvbW1hbmQ6IHpcbiAgICAub2JqZWN0KHtcbiAgICAgIGNvbW1hbmQ6IHpcbiAgICAgICAgLnN0cmluZyh7IGRlc2NyaXB0aW9uOiAnQ29tbWFuZCB0byBydW4gY292ZXJhZ2UgdG9vbC4nIH0pXG4gICAgICAgIC5taW4oMSksXG4gICAgICBhcmdzOiB6XG4gICAgICAgIC5hcnJheSh6LnN0cmluZygpLCB7XG4gICAgICAgICAgZGVzY3JpcHRpb246ICdBcmd1bWVudHMgdG8gYmUgcGFzc2VkIHRvIHRoZSBjb3ZlcmFnZSB0b29sLicsXG4gICAgICAgIH0pXG4gICAgICAgIC5vcHRpb25hbCgpLFxuICAgIH0pXG4gICAgLm9wdGlvbmFsKCksXG4gIGNvdmVyYWdlVHlwZXM6IHpcbiAgICAuYXJyYXkoY292ZXJhZ2VUeXBlU2NoZW1hLCB7XG4gICAgICBkZXNjcmlwdGlvbjogJ0NvdmVyYWdlIHR5cGVzIG1lYXN1cmVkLiBEZWZhdWx0cyB0byBhbGwgYXZhaWxhYmxlIHR5cGVzLicsXG4gICAgfSlcbiAgICAubWluKDEpXG4gICAgLmRlZmF1bHQoWydmdW5jdGlvbicsICdicmFuY2gnLCAnbGluZSddKSxcbiAgcmVwb3J0czogelxuICAgIC5hcnJheShjb3ZlcmFnZVJlc3VsdFNjaGVtYSwge1xuICAgICAgZGVzY3JpcHRpb246XG4gICAgICAgICdQYXRoIHRvIGFsbCBjb2RlIGNvdmVyYWdlIHJlcG9ydCBmaWxlcy4gT25seSBMQ09WIGZvcm1hdCBpcyBzdXBwb3J0ZWQgZm9yIG5vdy4nLFxuICAgIH0pXG4gICAgLm1pbigxKSxcbiAgcGVyZmVjdFNjb3JlVGhyZXNob2xkOiB6XG4gICAgLm51bWJlcih7XG4gICAgICBkZXNjcmlwdGlvbjpcbiAgICAgICAgJ1Njb3JlIHdpbGwgYmUgMSAocGVyZmVjdCkgZm9yIHRoaXMgY292ZXJhZ2UgYW5kIGFib3ZlLiBTY29yZSByYW5nZSBpcyAwIC0gMS4nLFxuICAgIH0pXG4gICAgLmd0KDApXG4gICAgLm1heCgxKVxuICAgIC5vcHRpb25hbCgpLFxufSk7XG5leHBvcnQgdHlwZSBDb3ZlcmFnZVBsdWdpbkNvbmZpZyA9IHouaW5wdXQ8dHlwZW9mIGNvdmVyYWdlUGx1Z2luQ29uZmlnU2NoZW1hPjtcbmV4cG9ydCB0eXBlIEZpbmFsQ292ZXJhZ2VQbHVnaW5Db25maWcgPSB6LmluZmVyPFxuICB0eXBlb2YgY292ZXJhZ2VQbHVnaW5Db25maWdTY2hlbWFcbj47XG4iLCAiY29uc3QgX19pbmplY3RlZF9maWxlbmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tY292ZXJhZ2Uvc3JjL2xpYi9ydW5uZXIvaW5kZXgudHNcIjtjb25zdCBfX2luamVjdGVkX2Rpcm5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWNvdmVyYWdlL3NyYy9saWIvcnVubmVyXCI7Y29uc3QgX19pbmplY3RlZF9pbXBvcnRfbWV0YV91cmxfXyA9IFwiZmlsZTovLy9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1jb3ZlcmFnZS9zcmMvbGliL3J1bm5lci9pbmRleC50c1wiO2ltcG9ydCB7IGJvbGQgfSBmcm9tICdhbnNpcyc7XG5pbXBvcnQgeyB3cml0ZUZpbGUgfSBmcm9tICdub2RlOmZzL3Byb21pc2VzJztcbmltcG9ydCBwYXRoIGZyb20gJ25vZGU6cGF0aCc7XG5pbXBvcnQgdHlwZSB7IEF1ZGl0T3V0cHV0cywgUnVubmVyQ29uZmlnIH0gZnJvbSAnQGNvZGUtcHVzaHVwL21vZGVscyc7XG5pbXBvcnQge1xuICBQcm9jZXNzRXJyb3IsXG4gIGVuc3VyZURpcmVjdG9yeUV4aXN0cyxcbiAgZXhlY3V0ZVByb2Nlc3MsXG4gIGZpbGVQYXRoVG9DbGlBcmcsXG4gIHJlYWRKc29uRmlsZSxcbiAgdWksXG59IGZyb20gJ0Bjb2RlLXB1c2h1cC91dGlscyc7XG5pbXBvcnQgdHlwZSB7IEZpbmFsQ292ZXJhZ2VQbHVnaW5Db25maWcgfSBmcm9tICcuLi9jb25maWcuanMnO1xuaW1wb3J0IHsgYXBwbHlNYXhTY29yZUFib3ZlVGhyZXNob2xkIH0gZnJvbSAnLi4vdXRpbHMuanMnO1xuaW1wb3J0IHsgUExVR0lOX0NPTkZJR19QQVRILCBSVU5ORVJfT1VUUFVUX1BBVEggfSBmcm9tICcuL2NvbnN0YW50cy5qcyc7XG5pbXBvcnQgeyBsY292UmVzdWx0c1RvQXVkaXRPdXRwdXRzIH0gZnJvbSAnLi9sY292L2xjb3YtcnVubmVyLmpzJztcblxuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGV4ZWN1dGVSdW5uZXIoKTogUHJvbWlzZTx2b2lkPiB7XG4gIGNvbnN0IHsgcmVwb3J0cywgY292ZXJhZ2VUb29sQ29tbWFuZCwgY292ZXJhZ2VUeXBlcyB9ID1cbiAgICBhd2FpdCByZWFkSnNvbkZpbGU8RmluYWxDb3ZlcmFnZVBsdWdpbkNvbmZpZz4oUExVR0lOX0NPTkZJR19QQVRIKTtcblxuICAvLyBSdW4gY292ZXJhZ2UgdG9vbCBpZiBwcm92aWRlZFxuICBpZiAoY292ZXJhZ2VUb29sQ29tbWFuZCAhPSBudWxsKSB7XG4gICAgY29uc3QgeyBjb21tYW5kLCBhcmdzIH0gPSBjb3ZlcmFnZVRvb2xDb21tYW5kO1xuICAgIHRyeSB7XG4gICAgICBhd2FpdCBleGVjdXRlUHJvY2Vzcyh7IGNvbW1hbmQsIGFyZ3MgfSk7XG4gICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgIGlmIChlcnJvciBpbnN0YW5jZW9mIFByb2Nlc3NFcnJvcikge1xuICAgICAgICB1aSgpLmxvZ2dlci5lcnJvcihib2xkKCdzdGRvdXQgZnJvbSBmYWlsZWQgY292ZXJhZ2UgdG9vbCBwcm9jZXNzOicpKTtcbiAgICAgICAgdWkoKS5sb2dnZXIuZXJyb3IoZXJyb3Iuc3Rkb3V0KTtcbiAgICAgICAgdWkoKS5sb2dnZXIuZXJyb3IoYm9sZCgnc3RkZXJyIGZyb20gZmFpbGVkIGNvdmVyYWdlIHRvb2wgcHJvY2VzczonKSk7XG4gICAgICAgIHVpKCkubG9nZ2VyLmVycm9yKGVycm9yLnN0ZGVycik7XG4gICAgICB9XG5cbiAgICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgICAgJ0NvdmVyYWdlIHBsdWdpbjogUnVubmluZyBjb3ZlcmFnZSB0b29sIGZhaWxlZC4gTWFrZSBzdXJlIGFsbCB5b3VyIHByb3ZpZGVkIHRlc3RzIGFyZSBwYXNzaW5nLicsXG4gICAgICApO1xuICAgIH1cbiAgfVxuXG4gIC8vIENhbGN1bGF0ZSBjb3ZlcmFnZSBmcm9tIExDT1YgcmVzdWx0c1xuICBjb25zdCBhdWRpdE91dHB1dHMgPSBhd2FpdCBsY292UmVzdWx0c1RvQXVkaXRPdXRwdXRzKHJlcG9ydHMsIGNvdmVyYWdlVHlwZXMpO1xuXG4gIGF3YWl0IGVuc3VyZURpcmVjdG9yeUV4aXN0cyhwYXRoLmRpcm5hbWUoUlVOTkVSX09VVFBVVF9QQVRIKSk7XG4gIGF3YWl0IHdyaXRlRmlsZShSVU5ORVJfT1VUUFVUX1BBVEgsIEpTT04uc3RyaW5naWZ5KGF1ZGl0T3V0cHV0cykpO1xufVxuXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gY3JlYXRlUnVubmVyQ29uZmlnKFxuICBzY3JpcHRQYXRoOiBzdHJpbmcsXG4gIGNvbmZpZzogRmluYWxDb3ZlcmFnZVBsdWdpbkNvbmZpZyxcbik6IFByb21pc2U8UnVubmVyQ29uZmlnPiB7XG4gIC8vIENyZWF0ZSBKU09OIGNvbmZpZyBmb3IgZXhlY3V0ZVJ1bm5lclxuICBhd2FpdCBlbnN1cmVEaXJlY3RvcnlFeGlzdHMocGF0aC5kaXJuYW1lKFBMVUdJTl9DT05GSUdfUEFUSCkpO1xuICBhd2FpdCB3cml0ZUZpbGUoUExVR0lOX0NPTkZJR19QQVRILCBKU09OLnN0cmluZ2lmeShjb25maWcpKTtcblxuICBjb25zdCB0aHJlc2hvbGQgPSBjb25maWcucGVyZmVjdFNjb3JlVGhyZXNob2xkO1xuXG4gIHJldHVybiB7XG4gICAgY29tbWFuZDogJ25vZGUnLFxuICAgIGFyZ3M6IFtmaWxlUGF0aFRvQ2xpQXJnKHNjcmlwdFBhdGgpXSxcbiAgICBvdXRwdXRGaWxlOiBSVU5ORVJfT1VUUFVUX1BBVEgsXG4gICAgLi4uKHRocmVzaG9sZCAhPSBudWxsICYmIHtcbiAgICAgIG91dHB1dFRyYW5zZm9ybTogb3V0cHV0cyA9PlxuICAgICAgICBhcHBseU1heFNjb3JlQWJvdmVUaHJlc2hvbGQob3V0cHV0cyBhcyBBdWRpdE91dHB1dHMsIHRocmVzaG9sZCksXG4gICAgfSksXG4gIH07XG59XG4iLCAiY29uc3QgX19pbmplY3RlZF9maWxlbmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tY292ZXJhZ2Uvc3JjL2xpYi9ydW5uZXIvY29uc3RhbnRzLnRzXCI7Y29uc3QgX19pbmplY3RlZF9kaXJuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1jb3ZlcmFnZS9zcmMvbGliL3J1bm5lclwiO2NvbnN0IF9faW5qZWN0ZWRfaW1wb3J0X21ldGFfdXJsX18gPSBcImZpbGU6Ly8vaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tY292ZXJhZ2Uvc3JjL2xpYi9ydW5uZXIvY29uc3RhbnRzLnRzXCI7aW1wb3J0IHBhdGggZnJvbSAnbm9kZTpwYXRoJztcbmltcG9ydCB7IHBsdWdpbldvcmtEaXIgfSBmcm9tICdAY29kZS1wdXNodXAvdXRpbHMnO1xuXG5leHBvcnQgY29uc3QgV09SS0RJUiA9IHBsdWdpbldvcmtEaXIoJ2NvdmVyYWdlJyk7XG5leHBvcnQgY29uc3QgUlVOTkVSX09VVFBVVF9QQVRIID0gcGF0aC5qb2luKFdPUktESVIsICdydW5uZXItb3V0cHV0Lmpzb24nKTtcbmV4cG9ydCBjb25zdCBQTFVHSU5fQ09ORklHX1BBVEggPSBwYXRoLmpvaW4oXG4gIHByb2Nlc3MuY3dkKCksXG4gIFdPUktESVIsXG4gICdwbHVnaW4tY29uZmlnLmpzb24nLFxuKTtcblxuZXhwb3J0IGNvbnN0IElOVkFMSURfRlVOQ1RJT05fTkFNRSA9ICcoZW1wdHktcmVwb3J0KSc7XG4iLCAiY29uc3QgX19pbmplY3RlZF9maWxlbmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tY292ZXJhZ2Uvc3JjL2xpYi9ydW5uZXIvbGNvdi9sY292LXJ1bm5lci50c1wiO2NvbnN0IF9faW5qZWN0ZWRfZGlybmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tY292ZXJhZ2Uvc3JjL2xpYi9ydW5uZXIvbGNvdlwiO2NvbnN0IF9faW5qZWN0ZWRfaW1wb3J0X21ldGFfdXJsX18gPSBcImZpbGU6Ly8vaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tY292ZXJhZ2Uvc3JjL2xpYi9ydW5uZXIvbGNvdi9sY292LXJ1bm5lci50c1wiO2ltcG9ydCBwYXRoIGZyb20gJ25vZGU6cGF0aCc7XG5pbXBvcnQgdHlwZSB7IExDT1ZSZWNvcmQgfSBmcm9tICdwYXJzZS1sY292JztcbmltcG9ydCB0eXBlIHsgQXVkaXRPdXRwdXRzIH0gZnJvbSAnQGNvZGUtcHVzaHVwL21vZGVscyc7XG5pbXBvcnQgeyBleGlzdHMsIHJlYWRUZXh0RmlsZSwgdG9Vbml4TmV3bGluZXMsIHVpIH0gZnJvbSAnQGNvZGUtcHVzaHVwL3V0aWxzJztcbmltcG9ydCB0eXBlIHsgQ292ZXJhZ2VSZXN1bHQsIENvdmVyYWdlVHlwZSB9IGZyb20gJy4uLy4uL2NvbmZpZy5qcyc7XG5pbXBvcnQgeyBtZXJnZUxjb3ZSZXN1bHRzIH0gZnJvbSAnLi9tZXJnZS1sY292LmpzJztcbmltcG9ydCB7IHBhcnNlTGNvdiB9IGZyb20gJy4vcGFyc2UtbGNvdi5qcyc7XG5pbXBvcnQge1xuICBsY292Q292ZXJhZ2VUb0F1ZGl0T3V0cHV0LFxuICByZWNvcmRUb1N0YXRGdW5jdGlvbk1hcHBlcixcbn0gZnJvbSAnLi90cmFuc2Zvcm0uanMnO1xuaW1wb3J0IHR5cGUgeyBMQ09WU3RhdCwgTENPVlN0YXRzIH0gZnJvbSAnLi90eXBlcy5qcyc7XG5cbi8vIE5vdGU6IGNvbmRpdGlvbiBvciBzdGF0ZW1lbnQgY292ZXJhZ2UgaXMgbm90IHN1cHBvcnRlZCBpbiBMQ09WXG4vLyBodHRwczovL3N0YWNrb3ZlcmZsb3cuY29tL3F1ZXN0aW9ucy80ODI2MDQzNC9pcy1pdC1wb3NzaWJsZS10by1jaGVjay1jb25kaXRpb24tY292ZXJhZ2Utd2l0aC1nY292XG5cbi8qKlxuICpcbiAqIEBwYXJhbSByZXN1bHRzIFBhdGhzIHRvIExDT1YgcmVzdWx0c1xuICogQHBhcmFtIGNvdmVyYWdlVHlwZXMgdHlwZXMgb2YgY292ZXJhZ2UgdG8gYmUgY29uc2lkZXJlZFxuICogQHJldHVybnMgQXVkaXQgb3V0cHV0cyB3aXRoIGNvbXBsZXRlIGNvdmVyYWdlIGRhdGEuXG4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBsY292UmVzdWx0c1RvQXVkaXRPdXRwdXRzKFxuICByZXN1bHRzOiBDb3ZlcmFnZVJlc3VsdFtdLFxuICBjb3ZlcmFnZVR5cGVzOiBDb3ZlcmFnZVR5cGVbXSxcbik6IFByb21pc2U8QXVkaXRPdXRwdXRzPiB7XG4gIC8vIFBhcnNlIGxjb3YgZmlsZXNcbiAgY29uc3QgbGNvdlJlc3VsdHMgPSBhd2FpdCBwYXJzZUxjb3ZGaWxlcyhyZXN1bHRzKTtcblxuICAvLyBNZXJnZSBtdWx0aXBsZSBjb3ZlcmFnZSByZXBvcnRzIGZvciB0aGUgc2FtZSBmaWxlXG4gIGNvbnN0IG1lcmdlZFJlc3VsdHMgPSBtZXJnZUxjb3ZSZXN1bHRzKGxjb3ZSZXN1bHRzKTtcblxuICAvLyBDYWxjdWxhdGUgY29kZSBjb3ZlcmFnZSBmcm9tIGFsbCBjb3ZlcmFnZSByZXN1bHRzXG4gIGNvbnN0IHRvdGFsQ292ZXJhZ2VTdGF0cyA9IGdldFRvdGFsQ292ZXJhZ2VGcm9tTGNvdlJlY29yZHMoXG4gICAgbWVyZ2VkUmVzdWx0cyxcbiAgICBjb3ZlcmFnZVR5cGVzLFxuICApO1xuXG4gIHJldHVybiBjb3ZlcmFnZVR5cGVzXG4gICAgLm1hcChjb3ZlcmFnZVR5cGUgPT4ge1xuICAgICAgY29uc3Qgc3RhdHMgPSB0b3RhbENvdmVyYWdlU3RhdHNbY292ZXJhZ2VUeXBlXTtcbiAgICAgIGlmICghc3RhdHMpIHtcbiAgICAgICAgcmV0dXJuIG51bGw7XG4gICAgICB9XG4gICAgICByZXR1cm4gbGNvdkNvdmVyYWdlVG9BdWRpdE91dHB1dChzdGF0cywgY292ZXJhZ2VUeXBlKTtcbiAgICB9KVxuICAgIC5maWx0ZXIoZXhpc3RzKTtcbn1cblxuLyoqXG4gKlxuICogQHBhcmFtIHJlc3VsdHMgUGF0aHMgdG8gTENPViByZXN1bHRzXG4gKiBAcmV0dXJucyBBcnJheSBvZiBwYXJzZWQgTENPVlJlY29yZHMuXG4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBwYXJzZUxjb3ZGaWxlcyhcbiAgcmVzdWx0czogQ292ZXJhZ2VSZXN1bHRbXSxcbik6IFByb21pc2U8TENPVlJlY29yZFtdPiB7XG4gIGNvbnN0IHBhcnNlZFJlc3VsdHMgPSBhd2FpdCBQcm9taXNlLmFsbChcbiAgICByZXN1bHRzLm1hcChhc3luYyByZXN1bHQgPT4ge1xuICAgICAgY29uc3QgcmVzdWx0c1BhdGggPVxuICAgICAgICB0eXBlb2YgcmVzdWx0ID09PSAnc3RyaW5nJyA/IHJlc3VsdCA6IHJlc3VsdC5yZXN1bHRzUGF0aDtcbiAgICAgIGNvbnN0IGxjb3ZGaWxlQ29udGVudCA9IGF3YWl0IHJlYWRUZXh0RmlsZShyZXN1bHRzUGF0aCk7XG4gICAgICBpZiAobGNvdkZpbGVDb250ZW50LnRyaW0oKSA9PT0gJycpIHtcbiAgICAgICAgdWkoKS5sb2dnZXIud2FybmluZyhcbiAgICAgICAgICBgQ292ZXJhZ2UgcGx1Z2luOiBFbXB0eSBsY292IHJlcG9ydCBmaWxlIGRldGVjdGVkIGF0ICR7cmVzdWx0c1BhdGh9LmAsXG4gICAgICAgICk7XG4gICAgICB9XG4gICAgICBjb25zdCBwYXJzZWRSZWNvcmRzID0gcGFyc2VMY292KHRvVW5peE5ld2xpbmVzKGxjb3ZGaWxlQ29udGVudCkpO1xuICAgICAgcmV0dXJuIHBhcnNlZFJlY29yZHMubWFwPExDT1ZSZWNvcmQ+KHJlY29yZCA9PiAoe1xuICAgICAgICAuLi5yZWNvcmQsXG4gICAgICAgIGZpbGU6XG4gICAgICAgICAgdHlwZW9mIHJlc3VsdCA9PT0gJ3N0cmluZycgfHwgcmVzdWx0LnBhdGhUb1Byb2plY3QgPT0gbnVsbFxuICAgICAgICAgICAgPyByZWNvcmQuZmlsZVxuICAgICAgICAgICAgOiBwYXRoLmpvaW4ocmVzdWx0LnBhdGhUb1Byb2plY3QsIHJlY29yZC5maWxlKSxcbiAgICAgIH0pKTtcbiAgICB9KSxcbiAgKTtcbiAgaWYgKHBhcnNlZFJlc3VsdHMubGVuZ3RoICE9PSByZXN1bHRzLmxlbmd0aCkge1xuICAgIHRocm93IG5ldyBFcnJvcignU29tZSBwcm92aWRlZCBMQ09WIHJlc3VsdHMgd2VyZSBub3QgdmFsaWQuJyk7XG4gIH1cblxuICBjb25zdCBmbGF0UmVzdWx0cyA9IHBhcnNlZFJlc3VsdHMuZmxhdCgpO1xuXG4gIGlmIChmbGF0UmVzdWx0cy5sZW5ndGggPT09IDApIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoJ0FsbCBwcm92aWRlZCByZXN1bHRzIGFyZSBlbXB0eS4nKTtcbiAgfVxuXG4gIHJldHVybiBmbGF0UmVzdWx0cztcbn1cblxuLyoqXG4gKlxuICogQHBhcmFtIHJlY29yZHMgVGhpcyBmdW5jdGlvbiBhZ2dyZWdhdGVzIGNvdmVyYWdlIHN0YXRzIGZyb20gYWxsIGNvdmVyYWdlIGZpbGVzXG4gKiBAcGFyYW0gY292ZXJhZ2VUeXBlcyBUeXBlcyBvZiBjb3ZlcmFnZSB0byBiZSBnYXRoZXJlZFxuICogQHJldHVybnMgQ29tcGxldGUgY292ZXJhZ2Ugc3RhdHMgZm9yIGFsbCBkZWZpbmVkIHR5cGVzIG9mIGNvdmVyYWdlLlxuICovXG5mdW5jdGlvbiBnZXRUb3RhbENvdmVyYWdlRnJvbUxjb3ZSZWNvcmRzKFxuICByZWNvcmRzOiBMQ09WUmVjb3JkW10sXG4gIGNvdmVyYWdlVHlwZXM6IENvdmVyYWdlVHlwZVtdLFxuKTogTENPVlN0YXRzIHtcbiAgcmV0dXJuIHJlY29yZHMucmVkdWNlPExDT1ZTdGF0cz4oXG4gICAgKGFjYywgcmVwb3J0KSA9PlxuICAgICAgT2JqZWN0LmZyb21FbnRyaWVzKFtcbiAgICAgICAgLi4uT2JqZWN0LmVudHJpZXMoYWNjKSxcbiAgICAgICAgLi4uKFxuICAgICAgICAgIE9iamVjdC5lbnRyaWVzKFxuICAgICAgICAgICAgZ2V0Q292ZXJhZ2VTdGF0c0Zyb21MY292UmVjb3JkKHJlcG9ydCwgY292ZXJhZ2VUeXBlcyksXG4gICAgICAgICAgKSBhcyBbQ292ZXJhZ2VUeXBlLCBMQ09WU3RhdF1bXVxuICAgICAgICApLm1hcCgoW3R5cGUsIHN0YXRzXSk6IFtDb3ZlcmFnZVR5cGUsIExDT1ZTdGF0XSA9PiBbXG4gICAgICAgICAgdHlwZSxcbiAgICAgICAgICB7XG4gICAgICAgICAgICB0b3RhbEZvdW5kOiAoYWNjW3R5cGVdPy50b3RhbEZvdW5kID8/IDApICsgc3RhdHMudG90YWxGb3VuZCxcbiAgICAgICAgICAgIHRvdGFsSGl0OiAoYWNjW3R5cGVdPy50b3RhbEhpdCA/PyAwKSArIHN0YXRzLnRvdGFsSGl0LFxuICAgICAgICAgICAgaXNzdWVzOiBbLi4uKGFjY1t0eXBlXT8uaXNzdWVzID8/IFtdKSwgLi4uc3RhdHMuaXNzdWVzXSxcbiAgICAgICAgICB9LFxuICAgICAgICBdKSxcbiAgICAgIF0pLFxuICAgIHt9LFxuICApO1xufVxuXG4vKipcbiAqIEBwYXJhbSByZWNvcmQgcmVjb3JkIGZpbGUgZGF0YVxuICogQHBhcmFtIGNvdmVyYWdlVHlwZXMgdHlwZXMgb2YgY292ZXJhZ2UgdG8gYmUgZ2F0aGVyZWRcbiAqIEByZXR1cm5zIFJlbGV2YW50IGNvdmVyYWdlIGRhdGEgZnJvbSBvbmUgbGNvdiByZWNvcmQgZmlsZS5cbiAqL1xuZnVuY3Rpb24gZ2V0Q292ZXJhZ2VTdGF0c0Zyb21MY292UmVjb3JkKFxuICByZWNvcmQ6IExDT1ZSZWNvcmQsXG4gIGNvdmVyYWdlVHlwZXM6IENvdmVyYWdlVHlwZVtdLFxuKTogTENPVlN0YXRzIHtcbiAgcmV0dXJuIE9iamVjdC5mcm9tRW50cmllcyhcbiAgICBjb3ZlcmFnZVR5cGVzLm1hcCgoY292ZXJhZ2VUeXBlKTogW0NvdmVyYWdlVHlwZSwgTENPVlN0YXRdID0+IFtcbiAgICAgIGNvdmVyYWdlVHlwZSxcbiAgICAgIHJlY29yZFRvU3RhdEZ1bmN0aW9uTWFwcGVyW2NvdmVyYWdlVHlwZV0ocmVjb3JkKSxcbiAgICBdKSxcbiAgKTtcbn1cbiIsICJjb25zdCBfX2luamVjdGVkX2ZpbGVuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1jb3ZlcmFnZS9zcmMvbGliL3J1bm5lci9sY292L3BhcnNlLWxjb3YudHNcIjtjb25zdCBfX2luamVjdGVkX2Rpcm5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWNvdmVyYWdlL3NyYy9saWIvcnVubmVyL2xjb3ZcIjtjb25zdCBfX2luamVjdGVkX2ltcG9ydF9tZXRhX3VybF9fID0gXCJmaWxlOi8vL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWNvdmVyYWdlL3NyYy9saWIvcnVubmVyL2xjb3YvcGFyc2UtbGNvdi50c1wiO2ltcG9ydCBwYXJzZUxjb3ZFeHBvcnQgZnJvbSAncGFyc2UtbGNvdic7XG5cbnR5cGUgUGFyc2VMY292Rm4gPSB0eXBlb2YgcGFyc2VMY292RXhwb3J0O1xuXG4vLyB0aGUgcGFyc2UtbGNvdiBleHBvcnQgaXMgaW5jb25zaXN0ZW50IChzb21ldGltZXMgaXQncyAuZGVmYXVsdCwgc29tZXRpbWVzIGl0J3MgLmRlZmF1bHQuZGVmYXVsdClcbmNvbnN0IGdvZEtub3dzID0gcGFyc2VMY292RXhwb3J0IGFzIHVua25vd24gYXNcbiAgfCBQYXJzZUxjb3ZGblxuICB8IHsgZGVmYXVsdDogUGFyc2VMY292Rm4gfTtcblxuZXhwb3J0IGNvbnN0IHBhcnNlTGNvdjogUGFyc2VMY292Rm4gPVxuICAnZGVmYXVsdCcgaW4gZ29kS25vd3MgPyBnb2RLbm93cy5kZWZhdWx0IDogZ29kS25vd3M7XG4iLCAiY29uc3QgX19pbmplY3RlZF9maWxlbmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tY292ZXJhZ2Uvc3JjL2xpYi9ydW5uZXIvbGNvdi90cmFuc2Zvcm0udHNcIjtjb25zdCBfX2luamVjdGVkX2Rpcm5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWNvdmVyYWdlL3NyYy9saWIvcnVubmVyL2xjb3ZcIjtjb25zdCBfX2luamVjdGVkX2ltcG9ydF9tZXRhX3VybF9fID0gXCJmaWxlOi8vL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWNvdmVyYWdlL3NyYy9saWIvcnVubmVyL2xjb3YvdHJhbnNmb3JtLnRzXCI7aW1wb3J0IHR5cGUgeyBMQ09WUmVjb3JkIH0gZnJvbSAncGFyc2UtbGNvdic7XG5pbXBvcnQgdHlwZSB7IEF1ZGl0T3V0cHV0LCBJc3N1ZSB9IGZyb20gJ0Bjb2RlLXB1c2h1cC9tb2RlbHMnO1xuaW1wb3J0IHsgdG9OdW1iZXJQcmVjaXNpb24sIHRvT3JkaW5hbCB9IGZyb20gJ0Bjb2RlLXB1c2h1cC91dGlscyc7XG5pbXBvcnQgdHlwZSB7IENvdmVyYWdlVHlwZSB9IGZyb20gJy4uLy4uL2NvbmZpZy5qcyc7XG5pbXBvcnQgeyBJTlZBTElEX0ZVTkNUSU9OX05BTUUgfSBmcm9tICcuLi9jb25zdGFudHMuanMnO1xuaW1wb3J0IHR5cGUgeyBMQ09WU3RhdCB9IGZyb20gJy4vdHlwZXMuanMnO1xuaW1wb3J0IHsgY2FsY3VsYXRlQ292ZXJhZ2UsIG1lcmdlQ29uc2VjdXRpdmVOdW1iZXJzIH0gZnJvbSAnLi91dGlscy5qcyc7XG5cbmV4cG9ydCBmdW5jdGlvbiBsY292UmVwb3J0VG9GdW5jdGlvblN0YXQocmVjb3JkOiBMQ09WUmVjb3JkKTogTENPVlN0YXQge1xuICBjb25zdCB2YWxpZFJlY29yZCA9IHJlbW92ZUVtcHR5UmVwb3J0KHJlY29yZCk7XG5cbiAgcmV0dXJuIHtcbiAgICB0b3RhbEZvdW5kOiB2YWxpZFJlY29yZC5mdW5jdGlvbnMuZm91bmQsXG4gICAgdG90YWxIaXQ6IHZhbGlkUmVjb3JkLmZ1bmN0aW9ucy5oaXQsXG4gICAgaXNzdWVzOlxuICAgICAgdmFsaWRSZWNvcmQuZnVuY3Rpb25zLmhpdCA8IHZhbGlkUmVjb3JkLmZ1bmN0aW9ucy5mb3VuZFxuICAgICAgICA/IHZhbGlkUmVjb3JkLmZ1bmN0aW9ucy5kZXRhaWxzXG4gICAgICAgICAgICAuZmlsdGVyKGRldGFpbCA9PiAhZGV0YWlsLmhpdClcbiAgICAgICAgICAgIC5tYXAoXG4gICAgICAgICAgICAgIChkZXRhaWwpOiBJc3N1ZSA9PiAoe1xuICAgICAgICAgICAgICAgIG1lc3NhZ2U6IGBGdW5jdGlvbiAke2RldGFpbC5uYW1lfSBpcyBub3QgY2FsbGVkIGluIGFueSB0ZXN0IGNhc2UuYCxcbiAgICAgICAgICAgICAgICBzZXZlcml0eTogJ2Vycm9yJyxcbiAgICAgICAgICAgICAgICBzb3VyY2U6IHtcbiAgICAgICAgICAgICAgICAgIGZpbGU6IHZhbGlkUmVjb3JkLmZpbGUsXG4gICAgICAgICAgICAgICAgICBwb3NpdGlvbjogeyBzdGFydExpbmU6IGRldGFpbC5saW5lIH0sXG4gICAgICAgICAgICAgICAgfSxcbiAgICAgICAgICAgICAgfSksXG4gICAgICAgICAgICApXG4gICAgICAgIDogW10sXG4gIH07XG59XG5cbmZ1bmN0aW9uIHJlbW92ZUVtcHR5UmVwb3J0KHJlY29yZDogTENPVlJlY29yZCk6IExDT1ZSZWNvcmQge1xuICBjb25zdCB2YWxpZEZ1bmN0aW9ucyA9IHJlY29yZC5mdW5jdGlvbnMuZGV0YWlscy5maWx0ZXIoXG4gICAgZGV0YWlsID0+IGRldGFpbC5uYW1lICE9PSBJTlZBTElEX0ZVTkNUSU9OX05BTUUsXG4gICk7XG5cbiAgaWYgKHZhbGlkRnVuY3Rpb25zLmxlbmd0aCA9PT0gcmVjb3JkLmZ1bmN0aW9ucy5mb3VuZCkge1xuICAgIHJldHVybiByZWNvcmQ7XG4gIH1cblxuICByZXR1cm4ge1xuICAgIC4uLnJlY29yZCxcbiAgICBmdW5jdGlvbnM6IHtcbiAgICAgIGRldGFpbHM6IHZhbGlkRnVuY3Rpb25zLFxuICAgICAgZm91bmQ6IHZhbGlkRnVuY3Rpb25zLmxlbmd0aCxcbiAgICAgIGhpdDogdmFsaWRGdW5jdGlvbnMucmVkdWNlKFxuICAgICAgICAoYWNjLCBmbikgPT4gYWNjICsgKGZuLmhpdCAhPSBudWxsICYmIGZuLmhpdCA+IDAgPyAxIDogMCksXG4gICAgICAgIDAsXG4gICAgICApLFxuICAgIH0sXG4gIH07XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBsY292UmVwb3J0VG9MaW5lU3RhdChyZWNvcmQ6IExDT1ZSZWNvcmQpOiBMQ09WU3RhdCB7XG4gIGNvbnN0IG1pc3NpbmdDb3ZlcmFnZSA9IHJlY29yZC5saW5lcy5oaXQgPCByZWNvcmQubGluZXMuZm91bmQ7XG4gIGNvbnN0IGxpbmVzID0gbWlzc2luZ0NvdmVyYWdlXG4gICAgPyByZWNvcmQubGluZXMuZGV0YWlsc1xuICAgICAgICAuZmlsdGVyKGRldGFpbCA9PiAhZGV0YWlsLmhpdClcbiAgICAgICAgLm1hcChkZXRhaWwgPT4gZGV0YWlsLmxpbmUpXG4gICAgOiBbXTtcblxuICBjb25zdCBsaW5lUG9zaXRpb25zID0gbWVyZ2VDb25zZWN1dGl2ZU51bWJlcnMobGluZXMpO1xuXG4gIHJldHVybiB7XG4gICAgdG90YWxGb3VuZDogcmVjb3JkLmxpbmVzLmZvdW5kLFxuICAgIHRvdGFsSGl0OiByZWNvcmQubGluZXMuaGl0LFxuICAgIGlzc3VlczogbWlzc2luZ0NvdmVyYWdlXG4gICAgICA/IGxpbmVQb3NpdGlvbnMubWFwKChsaW5lUG9zaXRpb24pOiBJc3N1ZSA9PiB7XG4gICAgICAgICAgY29uc3QgbGluZVJlZmVyZW5jZSA9XG4gICAgICAgICAgICBsaW5lUG9zaXRpb24uZW5kID09IG51bGxcbiAgICAgICAgICAgICAgPyBgTGluZSAke2xpbmVQb3NpdGlvbi5zdGFydH0gaXNgXG4gICAgICAgICAgICAgIDogYExpbmVzICR7bGluZVBvc2l0aW9uLnN0YXJ0fS0ke2xpbmVQb3NpdGlvbi5lbmR9IGFyZWA7XG5cbiAgICAgICAgICByZXR1cm4ge1xuICAgICAgICAgICAgbWVzc2FnZTogYCR7bGluZVJlZmVyZW5jZX0gbm90IGNvdmVyZWQgaW4gYW55IHRlc3QgY2FzZS5gLFxuICAgICAgICAgICAgc2V2ZXJpdHk6ICd3YXJuaW5nJyxcbiAgICAgICAgICAgIHNvdXJjZToge1xuICAgICAgICAgICAgICBmaWxlOiByZWNvcmQuZmlsZSxcbiAgICAgICAgICAgICAgcG9zaXRpb246IHtcbiAgICAgICAgICAgICAgICBzdGFydExpbmU6IGxpbmVQb3NpdGlvbi5zdGFydCxcbiAgICAgICAgICAgICAgICBlbmRMaW5lOiBsaW5lUG9zaXRpb24uZW5kLFxuICAgICAgICAgICAgICB9LFxuICAgICAgICAgICAgfSxcbiAgICAgICAgICB9O1xuICAgICAgICB9KVxuICAgICAgOiBbXSxcbiAgfTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGxjb3ZSZXBvcnRUb0JyYW5jaFN0YXQocmVjb3JkOiBMQ09WUmVjb3JkKTogTENPVlN0YXQge1xuICByZXR1cm4ge1xuICAgIHRvdGFsRm91bmQ6IHJlY29yZC5icmFuY2hlcy5mb3VuZCxcbiAgICB0b3RhbEhpdDogcmVjb3JkLmJyYW5jaGVzLmhpdCxcbiAgICBpc3N1ZXM6XG4gICAgICByZWNvcmQuYnJhbmNoZXMuaGl0IDwgcmVjb3JkLmJyYW5jaGVzLmZvdW5kXG4gICAgICAgID8gcmVjb3JkLmJyYW5jaGVzLmRldGFpbHNcbiAgICAgICAgICAgIC5maWx0ZXIoZGV0YWlsID0+ICFkZXRhaWwudGFrZW4pXG4gICAgICAgICAgICAubWFwKFxuICAgICAgICAgICAgICAoZGV0YWlsKTogSXNzdWUgPT4gKHtcbiAgICAgICAgICAgICAgICBtZXNzYWdlOiBgJHt0b09yZGluYWwoXG4gICAgICAgICAgICAgICAgICBkZXRhaWwuYnJhbmNoICsgMSxcbiAgICAgICAgICAgICAgICApfSBicmFuY2ggaXMgbm90IHRha2VuIGluIGFueSB0ZXN0IGNhc2UuYCxcbiAgICAgICAgICAgICAgICBzZXZlcml0eTogJ2Vycm9yJyxcbiAgICAgICAgICAgICAgICBzb3VyY2U6IHtcbiAgICAgICAgICAgICAgICAgIGZpbGU6IHJlY29yZC5maWxlLFxuICAgICAgICAgICAgICAgICAgcG9zaXRpb246IHsgc3RhcnRMaW5lOiBkZXRhaWwubGluZSB9LFxuICAgICAgICAgICAgICAgIH0sXG4gICAgICAgICAgICAgIH0pLFxuICAgICAgICAgICAgKVxuICAgICAgICA6IFtdLFxuICB9O1xufVxuXG5leHBvcnQgY29uc3QgcmVjb3JkVG9TdGF0RnVuY3Rpb25NYXBwZXIgPSB7XG4gIGJyYW5jaDogbGNvdlJlcG9ydFRvQnJhbmNoU3RhdCxcbiAgbGluZTogbGNvdlJlcG9ydFRvTGluZVN0YXQsXG4gIGZ1bmN0aW9uOiBsY292UmVwb3J0VG9GdW5jdGlvblN0YXQsXG59O1xuXG4vKipcbiAqXG4gKiBAcGFyYW0gc3RhdCBjb2RlIGNvdmVyYWdlIHJlc3VsdCBmb3IgYSBnaXZlbiB0eXBlXG4gKiBAcGFyYW0gY292ZXJhZ2VUeXBlIGNvZGUgY292ZXJhZ2UgdHlwZVxuICogQHJldHVybnMgUmVzdWx0IG9mIGNvbXBsZXRlIGNvZGUgY2NvdmVyYWdlIGRhdGEgY292ZXJ0ZWQgdG8gQXVkaXRPdXRwdXRcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGxjb3ZDb3ZlcmFnZVRvQXVkaXRPdXRwdXQoXG4gIHN0YXQ6IExDT1ZTdGF0LFxuICBjb3ZlcmFnZVR5cGU6IENvdmVyYWdlVHlwZSxcbik6IEF1ZGl0T3V0cHV0IHtcbiAgY29uc3QgY292ZXJhZ2UgPSBjYWxjdWxhdGVDb3ZlcmFnZShzdGF0LnRvdGFsSGl0LCBzdGF0LnRvdGFsRm91bmQpO1xuICBjb25zdCBNQVhfREVDSU1BTF9QTEFDRVMgPSA0O1xuICBjb25zdCBjb3ZlcmFnZVBlcmNlbnRhZ2UgPSBjb3ZlcmFnZSAqIDEwMDtcblxuICByZXR1cm4ge1xuICAgIHNsdWc6IGAke2NvdmVyYWdlVHlwZX0tY292ZXJhZ2VgLFxuICAgIHNjb3JlOiB0b051bWJlclByZWNpc2lvbihjb3ZlcmFnZSwgTUFYX0RFQ0lNQUxfUExBQ0VTKSxcbiAgICB2YWx1ZTogY292ZXJhZ2VQZXJjZW50YWdlLFxuICAgIGRpc3BsYXlWYWx1ZTogYCR7dG9OdW1iZXJQcmVjaXNpb24oY292ZXJhZ2VQZXJjZW50YWdlLCAxKX0gJWAsXG4gICAgZGV0YWlsczoge1xuICAgICAgaXNzdWVzOiBzdGF0Lmlzc3VlcyxcbiAgICB9LFxuICB9O1xufVxuIiwgImNvbnN0IF9faW5qZWN0ZWRfZmlsZW5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWNvdmVyYWdlL3NyYy9saWIvbngvY292ZXJhZ2UtcGF0aHMudHNcIjtjb25zdCBfX2luamVjdGVkX2Rpcm5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWNvdmVyYWdlL3NyYy9saWIvbnhcIjtjb25zdCBfX2luamVjdGVkX2ltcG9ydF9tZXRhX3VybF9fID0gXCJmaWxlOi8vL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWNvdmVyYWdlL3NyYy9saWIvbngvY292ZXJhZ2UtcGF0aHMudHNcIjsvLy8gPHJlZmVyZW5jZSB0eXBlcz1cInZpdGVzdFwiIC8+XG5pbXBvcnQgdHlwZSB7XG4gIFByb2plY3RDb25maWd1cmF0aW9uLFxuICBQcm9qZWN0R3JhcGhQcm9qZWN0Tm9kZSxcbiAgVHJlZSxcbn0gZnJvbSAnQG54L2RldmtpdCc7XG5pbXBvcnQgdHlwZSB7IEplc3RFeGVjdXRvck9wdGlvbnMgfSBmcm9tICdAbngvamVzdC9zcmMvZXhlY3V0b3JzL2plc3Qvc2NoZW1hJztcbmltcG9ydCB0eXBlIHsgVml0ZXN0RXhlY3V0b3JPcHRpb25zIH0gZnJvbSAnQG54L3ZpdGUvZXhlY3V0b3JzJztcbmltcG9ydCB7IGJvbGQgfSBmcm9tICdhbnNpcyc7XG5pbXBvcnQgcGF0aCBmcm9tICdub2RlOnBhdGgnO1xuaW1wb3J0IHsgaW1wb3J0TW9kdWxlLCB1aSB9IGZyb20gJ0Bjb2RlLXB1c2h1cC91dGlscyc7XG5pbXBvcnQgdHlwZSB7IENvdmVyYWdlUmVzdWx0IH0gZnJvbSAnLi4vY29uZmlnLmpzJztcblxuLyoqXG4gKiBAcGFyYW0gdGFyZ2V0cyBueCB0YXJnZXRzIHRvIGJlIHVzZWQgZm9yIG1lYXN1cmluZyBjb3ZlcmFnZSwgdGVzdCBieSBkZWZhdWx0XG4gKiBAcmV0dXJucyBBbiBhcnJheSBvZiBjb3ZlcmFnZSByZXN1bHQgaW5mb3JtYXRpb24gZm9yIHRoZSBjb3ZlcmFnZSBwbHVnaW4uXG4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBnZXROeENvdmVyYWdlUGF0aHMoXG4gIHRhcmdldHM6IHN0cmluZ1tdID0gWyd0ZXN0J10sXG4gIHZlcmJvc2U/OiBib29sZWFuLFxuKTogUHJvbWlzZTxDb3ZlcmFnZVJlc3VsdFtdPiB7XG4gIGlmICh2ZXJib3NlKSB7XG4gICAgdWkoKS5sb2dnZXIuaW5mbyhcbiAgICAgIGJvbGQoJ1x1RDgzRFx1RENBMSBHYXRoZXJpbmcgY292ZXJhZ2UgZnJvbSB0aGUgZm9sbG93aW5nIG54IHByb2plY3RzOicpLFxuICAgICk7XG4gIH1cblxuICBjb25zdCB7IGNyZWF0ZVByb2plY3RHcmFwaEFzeW5jIH0gPSBhd2FpdCBpbXBvcnQoJ0BueC9kZXZraXQnKTtcbiAgY29uc3QgeyBub2RlcyB9ID0gYXdhaXQgY3JlYXRlUHJvamVjdEdyYXBoQXN5bmMoeyBleGl0T25FcnJvcjogZmFsc2UgfSk7XG5cbiAgY29uc3QgY292ZXJhZ2VSZXN1bHRzID0gYXdhaXQgUHJvbWlzZS5hbGwoXG4gICAgdGFyZ2V0cy5tYXAoYXN5bmMgdGFyZ2V0ID0+IHtcbiAgICAgIGNvbnN0IHJlbGV2YW50Tm9kZXMgPSBPYmplY3QudmFsdWVzKG5vZGVzKS5maWx0ZXIoZ3JhcGggPT5cbiAgICAgICAgaGFzTnhUYXJnZXQoZ3JhcGgsIHRhcmdldCksXG4gICAgICApO1xuXG4gICAgICByZXR1cm4gYXdhaXQgUHJvbWlzZS5hbGwoXG4gICAgICAgIHJlbGV2YW50Tm9kZXMubWFwPFByb21pc2U8Q292ZXJhZ2VSZXN1bHQ+Pihhc3luYyAoeyBuYW1lLCBkYXRhIH0pID0+IHtcbiAgICAgICAgICBjb25zdCBjb3ZlcmFnZVBhdGhzID0gYXdhaXQgZ2V0Q292ZXJhZ2VQYXRoc0ZvclRhcmdldChkYXRhLCB0YXJnZXQpO1xuICAgICAgICAgIGlmICh2ZXJib3NlKSB7XG4gICAgICAgICAgICB1aSgpLmxvZ2dlci5pbmZvKGAtICR7bmFtZX06ICR7dGFyZ2V0fWApO1xuICAgICAgICAgIH1cbiAgICAgICAgICByZXR1cm4gY292ZXJhZ2VQYXRocztcbiAgICAgICAgfSksXG4gICAgICApO1xuICAgIH0pLFxuICApO1xuXG4gIGlmICh2ZXJib3NlKSB7XG4gICAgdWkoKS5sb2dnZXIuaW5mbygnXFxuJyk7XG4gIH1cblxuICByZXR1cm4gY292ZXJhZ2VSZXN1bHRzLmZsYXQoKTtcbn1cblxuZnVuY3Rpb24gaGFzTnhUYXJnZXQoXG4gIHByb2plY3Q6IFByb2plY3RHcmFwaFByb2plY3ROb2RlLFxuICB0YXJnZXQ6IHN0cmluZyxcbik6IGJvb2xlYW4ge1xuICByZXR1cm4gcHJvamVjdC5kYXRhLnRhcmdldHMgIT0gbnVsbCAmJiB0YXJnZXQgaW4gcHJvamVjdC5kYXRhLnRhcmdldHM7XG59XG5cbmV4cG9ydCB0eXBlIFZpdGVzdENvdmVyYWdlQ29uZmlnID0ge1xuICB0ZXN0OiB7XG4gICAgY292ZXJhZ2U/OiB7XG4gICAgICByZXBvcnRlcj86IHN0cmluZ1tdO1xuICAgICAgcmVwb3J0c0RpcmVjdG9yeT86IHN0cmluZztcbiAgICB9O1xuICB9O1xufTtcblxuZXhwb3J0IHR5cGUgSmVzdENvdmVyYWdlQ29uZmlnID0ge1xuICBjb3ZlcmFnZURpcmVjdG9yeT86IHN0cmluZztcbiAgY292ZXJhZ2VSZXBvcnRlcnM/OiBzdHJpbmdbXTtcbn07XG5cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBnZXRDb3ZlcmFnZVBhdGhzRm9yVGFyZ2V0KFxuICBwcm9qZWN0OiBQcm9qZWN0Q29uZmlndXJhdGlvbixcbiAgdGFyZ2V0OiBzdHJpbmcsXG4pOiBQcm9taXNlPENvdmVyYWdlUmVzdWx0PiB7XG4gIGNvbnN0IHRhcmdldENvbmZpZyA9IHByb2plY3QudGFyZ2V0cz8uW3RhcmdldF07XG5cbiAgaWYgKCF0YXJnZXRDb25maWcpIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICBgTm8gY29uZmlndXJhdGlvbiBmb3VuZCBmb3IgdGFyZ2V0ICR7dGFyZ2V0fSBpbiBwcm9qZWN0ICR7cHJvamVjdC5uYW1lfWAsXG4gICAgKTtcbiAgfVxuXG4gIGlmICh0YXJnZXRDb25maWcuZXhlY3V0b3I/LmluY2x1ZGVzKCdAbngvdml0ZScpKSB7XG4gICAgcmV0dXJuIGdldENvdmVyYWdlUGF0aEZvclZpdGVzdChcbiAgICAgIHRhcmdldENvbmZpZy5vcHRpb25zIGFzIFZpdGVzdEV4ZWN1dG9yT3B0aW9ucyxcbiAgICAgIHByb2plY3QsXG4gICAgICB0YXJnZXQsXG4gICAgKTtcbiAgfVxuXG4gIGlmICh0YXJnZXRDb25maWcuZXhlY3V0b3I/LmluY2x1ZGVzKCdAbngvamVzdCcpKSB7XG4gICAgcmV0dXJuIGdldENvdmVyYWdlUGF0aEZvckplc3QoXG4gICAgICB0YXJnZXRDb25maWcub3B0aW9ucyBhcyBKZXN0RXhlY3V0b3JPcHRpb25zLFxuICAgICAgcHJvamVjdCxcbiAgICAgIHRhcmdldCxcbiAgICApO1xuICB9XG5cbiAgdGhyb3cgbmV3IEVycm9yKFxuICAgIGBVbnN1cHBvcnRlZCBleGVjdXRvciAke3RhcmdldENvbmZpZy5leGVjdXRvcn0uIE9ubHkgQG54L3ZpdGUgYW5kIEBueC9qZXN0IGFyZSBjdXJyZW50bHkgc3VwcG9ydGVkLmAsXG4gICk7XG59XG5cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBnZXRDb3ZlcmFnZVBhdGhGb3JWaXRlc3QoXG4gIG9wdGlvbnM6IFZpdGVzdEV4ZWN1dG9yT3B0aW9ucyxcbiAgcHJvamVjdDogUHJvamVjdENvbmZpZ3VyYXRpb24sXG4gIHRhcmdldDogc3RyaW5nLFxuKSB7XG4gIGNvbnN0IHtcbiAgICBkZWZhdWx0OiB7IG5vcm1hbGl6ZVZpdGVDb25maWdGaWxlUGF0aFdpdGhUcmVlIH0sXG4gIH0gPSBhd2FpdCBpbXBvcnQoJ0BueC92aXRlJyk7XG4gIGNvbnN0IGNvbmZpZyA9IG5vcm1hbGl6ZVZpdGVDb25maWdGaWxlUGF0aFdpdGhUcmVlKFxuICAgIC8vIEhBQ0s6IG9ubHkgdHJlZS5leGlzdHMgaXMgY2FsbGVkLCBzbyBpbmplY3RpbmcgZXhpc3RTeW5jIGZyb20gbm9kZTpmcyBpbnN0ZWFkXG4gICAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIEB0eXBlc2NyaXB0LWVzbGludC9jb25zaXN0ZW50LXR5cGUtYXNzZXJ0aW9uc1xuICAgIHsgZXhpc3RzOiAoYXdhaXQgaW1wb3J0KCdub2RlOmZzJykpLmV4aXN0c1N5bmMgfSBhcyBUcmVlLFxuICAgIHByb2plY3Qucm9vdCxcbiAgICBvcHRpb25zLmNvbmZpZ0ZpbGUsXG4gICk7XG4gIGlmICghY29uZmlnKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgYENvdWxkIG5vdCBmaW5kIFZpdGVzdCBjb25maWcgZmlsZSBmb3IgdGFyZ2V0ICR7dGFyZ2V0fSBpbiBwcm9qZWN0ICR7cHJvamVjdC5uYW1lfWAsXG4gICAgKTtcbiAgfVxuXG4gIGNvbnN0IHZpdGVzdENvbmZpZyA9IGF3YWl0IGltcG9ydE1vZHVsZTxWaXRlc3RDb3ZlcmFnZUNvbmZpZz4oe1xuICAgIGZpbGVwYXRoOiBjb25maWcsXG4gICAgZm9ybWF0OiAnZXNtJyxcbiAgfSk7XG5cbiAgY29uc3QgcmVwb3J0c0RpcmVjdG9yeSA9XG4gICAgb3B0aW9ucy5yZXBvcnRzRGlyZWN0b3J5ID8/IHZpdGVzdENvbmZpZy50ZXN0LmNvdmVyYWdlPy5yZXBvcnRzRGlyZWN0b3J5O1xuICBjb25zdCByZXBvcnRlciA9IHZpdGVzdENvbmZpZy50ZXN0LmNvdmVyYWdlPy5yZXBvcnRlcjtcblxuICBpZiAocmVwb3J0c0RpcmVjdG9yeSA9PSBudWxsKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgYFZpdGVzdCBjb3ZlcmFnZSBjb25maWd1cmF0aW9uIGF0ICR7Y29uZmlnfSBkb2VzIG5vdCBpbmNsdWRlIGNvdmVyYWdlIHBhdGggZm9yIHRhcmdldCAke3RhcmdldH0gaW4gcHJvamVjdCAke3Byb2plY3QubmFtZX0uIEFkZCB0aGUgcGF0aCB1bmRlciBjb3ZlcmFnZSA+IHJlcG9ydHNEaXJlY3RvcnkuYCxcbiAgICApO1xuICB9XG5cbiAgaWYgKCFyZXBvcnRlcj8uaW5jbHVkZXMoJ2xjb3YnKSkge1xuICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgIGBWaXRlc3QgY292ZXJhZ2UgY29uZmlndXJhdGlvbiBhdCAke2NvbmZpZ30gZG9lcyBub3QgaW5jbHVkZSBMQ09WIHJlcG9ydCBmb3JtYXQgZm9yIHRhcmdldCAke3RhcmdldH0gaW4gcHJvamVjdCAke3Byb2plY3QubmFtZX0uIEFkZCAnbGNvdicgZm9ybWF0IHVuZGVyIGNvdmVyYWdlID4gcmVwb3J0ZXIuYCxcbiAgICApO1xuICB9XG5cbiAgaWYgKHBhdGguaXNBYnNvbHV0ZShyZXBvcnRzRGlyZWN0b3J5KSkge1xuICAgIHJldHVybiBwYXRoLmpvaW4ocmVwb3J0c0RpcmVjdG9yeSwgJ2xjb3YuaW5mbycpO1xuICB9XG4gIHJldHVybiB7XG4gICAgcGF0aFRvUHJvamVjdDogcHJvamVjdC5yb290LFxuICAgIHJlc3VsdHNQYXRoOiBwYXRoLmpvaW4ocHJvamVjdC5yb290LCByZXBvcnRzRGlyZWN0b3J5LCAnbGNvdi5pbmZvJyksXG4gIH07XG59XG5cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBnZXRDb3ZlcmFnZVBhdGhGb3JKZXN0KFxuICBvcHRpb25zOiBKZXN0RXhlY3V0b3JPcHRpb25zLFxuICBwcm9qZWN0OiBQcm9qZWN0Q29uZmlndXJhdGlvbixcbiAgdGFyZ2V0OiBzdHJpbmcsXG4pIHtcbiAgY29uc3QgeyBqZXN0Q29uZmlnIH0gPSBvcHRpb25zO1xuXG4gIGNvbnN0IHRlc3RDb25maWcgPSBhd2FpdCBpbXBvcnRNb2R1bGU8SmVzdENvdmVyYWdlQ29uZmlnPih7XG4gICAgZmlsZXBhdGg6IGplc3RDb25maWcsXG4gIH0pO1xuICBjb25zdCB7IGNvdmVyYWdlRGlyZWN0b3J5LCBjb3ZlcmFnZVJlcG9ydGVycyB9ID0ge1xuICAgIC4uLnRlc3RDb25maWcsXG4gICAgLi4ub3B0aW9ucyxcbiAgfTtcblxuICBpZiAoY292ZXJhZ2VEaXJlY3RvcnkgPT0gbnVsbCkge1xuICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgIGBKZXN0IGNvdmVyYWdlIGNvbmZpZ3VyYXRpb24gYXQgJHtqZXN0Q29uZmlnfSBkb2VzIG5vdCBpbmNsdWRlIGNvdmVyYWdlIHBhdGggZm9yIHRhcmdldCAke3RhcmdldH0gaW4gJHtwcm9qZWN0Lm5hbWV9LiBBZGQgdGhlIHBhdGggdW5kZXIgY292ZXJhZ2VEaXJlY3RvcnkuYCxcbiAgICApO1xuICB9XG5cbiAgaWYgKCFjb3ZlcmFnZVJlcG9ydGVycz8uaW5jbHVkZXMoJ2xjb3YnKSAmJiAhKCdwcmVzZXQnIGluIHRlc3RDb25maWcpKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgYEplc3QgY292ZXJhZ2UgY29uZmlndXJhdGlvbiBhdCAke2plc3RDb25maWd9IGRvZXMgbm90IGluY2x1ZGUgTENPViByZXBvcnQgZm9ybWF0IGZvciB0YXJnZXQgJHt0YXJnZXR9IGluICR7cHJvamVjdC5uYW1lfS4gQWRkICdsY292JyBmb3JtYXQgdW5kZXIgY292ZXJhZ2VSZXBvcnRlcnMuYCxcbiAgICApO1xuICB9XG5cbiAgaWYgKHBhdGguaXNBYnNvbHV0ZShjb3ZlcmFnZURpcmVjdG9yeSkpIHtcbiAgICByZXR1cm4gcGF0aC5qb2luKGNvdmVyYWdlRGlyZWN0b3J5LCAnbGNvdi5pbmZvJyk7XG4gIH1cbiAgcmV0dXJuIHBhdGguam9pbihwcm9qZWN0LnJvb3QsIGNvdmVyYWdlRGlyZWN0b3J5LCAnbGNvdi5pbmZvJyk7XG59XG4iLCAiY29uc3QgX19pbmplY3RlZF9maWxlbmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tZG9jLWNvdmVyYWdlL3NyYy9saWIvY29uZmlnLnRzXCI7Y29uc3QgX19pbmplY3RlZF9kaXJuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1kb2MtY292ZXJhZ2Uvc3JjL2xpYlwiO2NvbnN0IF9faW5qZWN0ZWRfaW1wb3J0X21ldGFfdXJsX18gPSBcImZpbGU6Ly8vaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tZG9jLWNvdmVyYWdlL3NyYy9saWIvY29uZmlnLnRzXCI7aW1wb3J0IHsgeiB9IGZyb20gJ3pvZCc7XG5cbmV4cG9ydCBjb25zdCBkb2NDb3ZlcmFnZVBsdWdpbkNvbmZpZ1NjaGVtYSA9IHoub2JqZWN0KHtcbiAgb25seUF1ZGl0czogei5hcnJheSh6LnN0cmluZygpKS5vcHRpb25hbCgpLFxuICBzb3VyY2VHbG9iOiB6XG4gICAgLmFycmF5KHouc3RyaW5nKCkpXG4gICAgLmRlZmF1bHQoWydzcmMvKiovKi57dHMsdHN4fScsICchKiovKi5zcGVjLnRzJywgJyEqKi8qLnRlc3QudHMnXSksXG59KTtcblxuZXhwb3J0IHR5cGUgRG9jQ292ZXJhZ2VQbHVnaW5Db25maWcgPSB6LmluZmVyPFxuICB0eXBlb2YgZG9jQ292ZXJhZ2VQbHVnaW5Db25maWdTY2hlbWFcbj47IiwgImNvbnN0IF9faW5qZWN0ZWRfZmlsZW5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWRvYy1jb3ZlcmFnZS9zcmMvbGliL2NvbnN0YW50cy50c1wiO2NvbnN0IF9faW5qZWN0ZWRfZGlybmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tZG9jLWNvdmVyYWdlL3NyYy9saWJcIjtjb25zdCBfX2luamVjdGVkX2ltcG9ydF9tZXRhX3VybF9fID0gXCJmaWxlOi8vL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWRvYy1jb3ZlcmFnZS9zcmMvbGliL2NvbnN0YW50cy50c1wiO2ltcG9ydCB0eXBlIHsgQXVkaXQsIEdyb3VwIH0gZnJvbSBcIkBjb2RlLXB1c2h1cC9tb2RlbHNcIjtcbmltcG9ydCB0eXBlIHsgQXVkaXRTbHVnIH0gZnJvbSBcIi4vbW9kZWxzXCI7XG5cbmV4cG9ydCBjb25zdCBQTFVHSU5fU0xVRyA9ICdkb2MtY292ZXJhZ2UnO1xuXG5leHBvcnQgY29uc3QgQVVESVRTX01BUDogUmVjb3JkPEF1ZGl0U2x1ZywgQXVkaXQ+ID0ge1xuICAgICdjbGFzc2VzLWNvdmVyYWdlJzoge1xuICAgICAgICBzbHVnOiAnY2xhc3Nlcy1jb3ZlcmFnZScsXG4gICAgICAgIHRpdGxlOiAnQ2xhc3NlcyBjb3ZlcmFnZScsXG4gICAgICAgIGRlc2NyaXB0aW9uOiAnQ292ZXJhZ2Ugb2YgY2xhc3NlcycsXG4gICAgfSxcbiAgICAnbWV0aG9kcy1jb3ZlcmFnZSc6IHtcbiAgICAgICAgc2x1ZzogJ21ldGhvZHMtY292ZXJhZ2UnLFxuICAgICAgICB0aXRsZTogJ01ldGhvZHMgY292ZXJhZ2UnLFxuICAgICAgICBkZXNjcmlwdGlvbjogJ0NvdmVyYWdlIG9mIG1ldGhvZHMnLFxuICAgIH0sXG4gICAgJ2Z1bmN0aW9ucy1jb3ZlcmFnZSc6IHtcbiAgICAgICAgc2x1ZzogJ2Z1bmN0aW9ucy1jb3ZlcmFnZScsXG4gICAgICAgIHRpdGxlOiAnRnVuY3Rpb25zIGNvdmVyYWdlJyxcbiAgICAgICAgZGVzY3JpcHRpb246ICdDb3ZlcmFnZSBvZiBmdW5jdGlvbnMnLFxuICAgIH0sXG4gICAgJ2ludGVyZmFjZXMtY292ZXJhZ2UnOiB7XG4gICAgICAgIHNsdWc6ICdpbnRlcmZhY2VzLWNvdmVyYWdlJyxcbiAgICAgICAgdGl0bGU6ICdJbnRlcmZhY2VzIGNvdmVyYWdlJyxcbiAgICAgICAgZGVzY3JpcHRpb246ICdDb3ZlcmFnZSBvZiBpbnRlcmZhY2VzJyxcbiAgICB9LFxuICAgICd2YXJpYWJsZXMtY292ZXJhZ2UnOiB7XG4gICAgICAgIHNsdWc6ICd2YXJpYWJsZXMtY292ZXJhZ2UnLFxuICAgICAgICB0aXRsZTogJ1ZhcmlhYmxlcyBjb3ZlcmFnZScsXG4gICAgICAgIGRlc2NyaXB0aW9uOiAnQ292ZXJhZ2Ugb2YgdmFyaWFibGVzJyxcbiAgICB9LFxuICAgICdwcm9wZXJ0aWVzLWNvdmVyYWdlJzoge1xuICAgICAgICBzbHVnOiAncHJvcGVydGllcy1jb3ZlcmFnZScsXG4gICAgICAgIHRpdGxlOiAnUHJvcGVydGllcyBjb3ZlcmFnZScsXG4gICAgICAgIGRlc2NyaXB0aW9uOiAnQ292ZXJhZ2Ugb2YgcHJvcGVydGllcycsXG4gICAgfSxcbiAgICAndHlwZXMtY292ZXJhZ2UnOiB7XG4gICAgICAgIHNsdWc6ICd0eXBlcy1jb3ZlcmFnZScsXG4gICAgICAgIHRpdGxlOiAnVHlwZXMgY292ZXJhZ2UnLFxuICAgICAgICBkZXNjcmlwdGlvbjogJ0NvdmVyYWdlIG9mIHR5cGVzJyxcbiAgICB9LFxuICAgICdlbnVtcy1jb3ZlcmFnZSc6IHtcbiAgICAgICAgc2x1ZzogJ2VudW1zLWNvdmVyYWdlJyxcbiAgICAgICAgdGl0bGU6ICdFbnVtcyBjb3ZlcmFnZScsXG4gICAgICAgIGRlc2NyaXB0aW9uOiAnQ292ZXJhZ2Ugb2YgZW51bXMnLFxuICAgIH0sXG59IGFzIGNvbnN0O1xuXG5leHBvcnQgY29uc3QgZ3JvdXBzOiBHcm91cFtdID0gW1xuICAgIHtcbiAgICAgICAgc2x1ZzogJ2RvY3VtZW50YXRpb24tY292ZXJhZ2UnLFxuICAgICAgICB0aXRsZTogJ0RvY3VtZW50YXRpb24gY292ZXJhZ2UnLFxuICAgICAgICBkZXNjcmlwdGlvbjogJ0RvY3VtZW50YXRpb24gY292ZXJhZ2UnLFxuICAgICAgICByZWZzOiBPYmplY3Qua2V5cyhBVURJVFNfTUFQKS5tYXAoc2x1ZyA9PiB7XG4gICAgICAgICAgICBzd2l0Y2ggKHNsdWcgYXMgQXVkaXRTbHVnKSB7XG4gICAgICAgICAgICAgICAgY2FzZSAnY2xhc3Nlcy1jb3ZlcmFnZSc6XG4gICAgICAgICAgICAgICAgY2FzZSAnZnVuY3Rpb25zLWNvdmVyYWdlJzpcbiAgICAgICAgICAgICAgICBjYXNlICdtZXRob2RzLWNvdmVyYWdlJzpcbiAgICAgICAgICAgICAgICAgICAgcmV0dXJuIHsgc2x1Zywgd2VpZ2h0OiAyIH1cbiAgICAgICAgICAgICAgICBjYXNlICdpbnRlcmZhY2VzLWNvdmVyYWdlJzpcbiAgICAgICAgICAgICAgICBjYXNlICdwcm9wZXJ0aWVzLWNvdmVyYWdlJzpcbiAgICAgICAgICAgICAgICBjYXNlICd0eXBlcy1jb3ZlcmFnZSc6XG4gICAgICAgICAgICAgICAgZGVmYXVsdDpcbiAgICAgICAgICAgICAgICAgICAgcmV0dXJuIHsgc2x1Zywgd2VpZ2h0OiAxIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgfSksXG4gICAgfV07IiwgImNvbnN0IF9faW5qZWN0ZWRfZmlsZW5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWRvYy1jb3ZlcmFnZS9zcmMvbGliL3J1bm5lci9kb2MtcHJvY2Vzc2VyLnRzXCI7Y29uc3QgX19pbmplY3RlZF9kaXJuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1kb2MtY292ZXJhZ2Uvc3JjL2xpYi9ydW5uZXJcIjtjb25zdCBfX2luamVjdGVkX2ltcG9ydF9tZXRhX3VybF9fID0gXCJmaWxlOi8vL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWRvYy1jb3ZlcmFnZS9zcmMvbGliL3J1bm5lci9kb2MtcHJvY2Vzc2VyLnRzXCI7aW1wb3J0IHsgQ2xhc3NEZWNsYXJhdGlvbiwgUHJvamVjdCwgU291cmNlRmlsZSB9IGZyb20gJ3RzLW1vcnBoJztcbmltcG9ydCB0eXBlIHsgRG9jQ292ZXJhZ2VQbHVnaW5Db25maWcgfSBmcm9tICcuLi9jb25maWcuanMnO1xuaW1wb3J0IHR5cGUge1xuICBDb3ZlcmFnZVJlc3VsdCxcbiAgQ292ZXJhZ2VUeXBlLFxuICBVbmRvY3VtZW50ZWROb2RlLFxuICBVbnByb2Nlc3NlZENvdmVyYWdlUmVzdWx0XG59IGZyb20gJy4uL21vZGVscy5qcyc7XG5pbXBvcnQgeyBnZXRDb3ZlcmFnZVR5cGVGcm9tS2luZCB9IGZyb20gJy4uL3V0aWxzLmpzJztcbmltcG9ydCB7IGNhbGN1bGF0ZUNvdmVyYWdlLCBjcmVhdGVFbXB0eVVucHJvY2Vzc2VkQ292ZXJhZ2VSZXBvcnQgfSBmcm9tICcuL3V0aWxzLmpzJztcblxuXG4vKiBlc2xpbnQtZGlzYWJsZSBAdHlwZXNjcmlwdC1lc2xpbnQvbm8tZXhwbGljaXQtYW55ICovXG4vKiBlc2xpbnQtZGlzYWJsZSBmdW5jdGlvbmFsL2ltbXV0YWJsZS1kYXRhICovXG4vKiBlc2xpbnQtZGlzYWJsZSBAdHlwZXNjcmlwdC1lc2xpbnQvbWF4LXBhcmFtcyAqL1xuLyogZXNsaW50LWRpc2FibGUgZnVuY3Rpb25hbC9uby1sZXQgKi9cblxuXG4vKipcbiAqIFByb2Nlc3NlcyBkb2N1bWVudGF0aW9uIGNvdmVyYWdlIGZvciBUeXBlU2NyaXB0IGZpbGVzIGluIHRoZSBzcGVjaWZpZWQgcGF0aFxuICogQHBhcmFtIHRvSW5jbHVkZSAtIFRoZSBmaWxlIHBhdGggcGF0dGVybiB0byBpbmNsdWRlIGZvciBkb2N1bWVudGF0aW9uIGFuYWx5c2lzXG4gKiBAcmV0dXJucyB7Q292ZXJhZ2VSZXN1bHR9IE9iamVjdCBjb250YWluaW5nIGNvdmVyYWdlIHN0YXRpc3RpY3MgYW5kIHVuZG9jdW1lbnRlZCBpdGVtc1xuICovXG5leHBvcnQgZnVuY3Rpb24gcHJvY2Vzc0RvY0NvdmVyYWdlKGNvbmZpZzogRG9jQ292ZXJhZ2VQbHVnaW5Db25maWcpOiBDb3ZlcmFnZVJlc3VsdCB7XG4gIGNvbnN0IHByb2plY3QgPSBuZXcgUHJvamVjdCgpO1xuICBwcm9qZWN0LmFkZFNvdXJjZUZpbGVzQXRQYXRocyhjb25maWcuc291cmNlR2xvYik7XG5cbiAgcmV0dXJuIGdldFVucHJvY2Vzc2VkQ292ZXJhZ2VSZXBvcnQocHJvamVjdC5nZXRTb3VyY2VGaWxlcygpKTtcblxufVxuXG5leHBvcnQgZnVuY3Rpb24gZ2V0VW5wcm9jZXNzZWRDb3ZlcmFnZVJlcG9ydChzb3VyY2VGaWxlczogU291cmNlRmlsZVtdKSB7XG4gIGNvbnN0IHVucHJvY2Vzc2VkQ292ZXJhZ2VSZXBvcnQgPSBzb3VyY2VGaWxlc1xuICAgIC5yZWR1Y2UoKGNvdmVyYWdlUmVwb3J0T2ZBbGxGaWxlcywgc291cmNlRmlsZSkgPT4ge1xuXG4gICAgICAvLyBJbmZvIG9mIHRoZSBmaWxlXG4gICAgICBjb25zdCBmaWxlUGF0aCA9IHNvdXJjZUZpbGUuZ2V0RmlsZVBhdGgoKTtcbiAgICAgIGNvbnN0IGNsYXNzZXMgPSBzb3VyY2VGaWxlLmdldENsYXNzZXMoKTtcblxuICAgICAgLy8gQWxsIG5vZGVzIG9mIHRoZSBmaWxlXG4gICAgICBjb25zdCBhbGxOb2Rlc0Zyb21GaWxlID0gW1xuICAgICAgICAuLi5zb3VyY2VGaWxlLmdldEZ1bmN0aW9ucygpLFxuICAgICAgICAuLi5jbGFzc2VzLFxuICAgICAgICAuLi5nZXRDbGFzc05vZGVzKGNsYXNzZXMpLFxuICAgICAgICAuLi5zb3VyY2VGaWxlLmdldFR5cGVBbGlhc2VzKCksXG4gICAgICAgIC4uLnNvdXJjZUZpbGUuZ2V0RW51bXMoKSxcbiAgICAgICAgLi4uc291cmNlRmlsZS5nZXRJbnRlcmZhY2VzKCksXG4gICAgICAgIC8vIC4uLnNvdXJjZUZpbGUuZ2V0VmFyaWFibGVTdGF0ZW1lbnRzKCkuZmxhdE1hcChzdGF0ZW1lbnQgPT4gc3RhdGVtZW50LmdldERlY2xhcmF0aW9ucygpKVxuICAgICAgXTtcblxuICAgICAgY29uc3QgY292ZXJhZ2VSZXBvcnRPZkN1cnJlbnRGaWxlID0gYWxsTm9kZXNGcm9tRmlsZS5yZWR1Y2UoKGFjYywgbm9kZSkgPT4ge1xuICAgICAgICBjb25zdCBub2RlVHlwZSA9IGdldENvdmVyYWdlVHlwZUZyb21LaW5kKG5vZGUuZ2V0S2luZCgpKTtcbiAgICAgICAgYWNjW25vZGVUeXBlXS5ub2Rlc0NvdW50Kys7XG4gICAgICAgIGlmIChub2RlLmdldEpzRG9jcygpLmxlbmd0aCA9PT0gMCkge1xuICAgICAgICAgIGFjY1tub2RlVHlwZV0uaXNzdWVzLnB1c2goXG4gICAgICAgICAgICBnZXRVbmRvY3VtZW50ZWROb2RlKGZpbGVQYXRoLCBub2RlVHlwZSwgbm9kZS5nZXROYW1lKCkgfHwgJycsIG5vZGUuZ2V0U3RhcnRMaW5lTnVtYmVyKCkpXG4gICAgICAgICAgKTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gYWNjO1xuICAgICAgfSwgY3JlYXRlRW1wdHlVbnByb2Nlc3NlZENvdmVyYWdlUmVwb3J0KCkpO1xuXG4gICAgICByZXR1cm4gbWVyZ2VDb3ZlcmFnZVJlc3VsdHMoY292ZXJhZ2VSZXBvcnRPZkFsbEZpbGVzLCBjb3ZlcmFnZVJlcG9ydE9mQ3VycmVudEZpbGUpO1xuICAgIH0sIGNyZWF0ZUVtcHR5VW5wcm9jZXNzZWRDb3ZlcmFnZVJlcG9ydCgpKTtcblxuICByZXR1cm4gY2FsY3VsYXRlQ292ZXJhZ2UodW5wcm9jZXNzZWRDb3ZlcmFnZVJlcG9ydCk7XG5cbn1cblxuZnVuY3Rpb24gbWVyZ2VDb3ZlcmFnZVJlc3VsdHMocmVzdWx0czogVW5wcm9jZXNzZWRDb3ZlcmFnZVJlc3VsdCwgY3VycmVudDogVW5wcm9jZXNzZWRDb3ZlcmFnZVJlc3VsdCkge1xuICByZXR1cm4ge1xuICAgIC4uLk9iamVjdC5mcm9tRW50cmllcyhPYmplY3QuZW50cmllcyhyZXN1bHRzKS5tYXAoKFtrZXksIHZhbHVlXSkgPT4ge1xuICAgICAgY29uc3Qgbm9kZSA9IHZhbHVlIGFzIENvdmVyYWdlUmVzdWx0W0NvdmVyYWdlVHlwZV07XG4gICAgICBjb25zdCB0eXBlID0ga2V5IGFzIENvdmVyYWdlVHlwZTtcbiAgICAgIHJldHVybiBbdHlwZSwge1xuICAgICAgICBub2Rlc0NvdW50OiBub2RlLm5vZGVzQ291bnQgKyBjdXJyZW50W3R5cGVdLm5vZGVzQ291bnQsXG4gICAgICAgIGlzc3VlczogWy4uLm5vZGUuaXNzdWVzLCAuLi5jdXJyZW50W3R5cGVdLmlzc3Vlc10sXG4gICAgICB9XVxuICAgIH0pKVxuICB9IGFzIFVucHJvY2Vzc2VkQ292ZXJhZ2VSZXN1bHQ7XG59XG5cbmZ1bmN0aW9uIGdldENsYXNzTm9kZXMoY2xhc3NOb2RlczogQ2xhc3NEZWNsYXJhdGlvbltdKSB7XG4gIHJldHVybiBjbGFzc05vZGVzLmZsYXRNYXAoY2xhc3NOb2RlID0+IFsuLi5jbGFzc05vZGUuZ2V0TWV0aG9kcygpLCAuLi5jbGFzc05vZGUuZ2V0UHJvcGVydGllcygpXSlcbn1cblxuLyoqXG4gKiBDcmVhdGVzIGFuIHVuZG9jdW1lbnRlZCBpdGVtIGVudHJ5XG4gKiBAcGFyYW0gZmlsZSAtIFRoZSBmaWxlIHBhdGggd2hlcmUgdGhlIGl0ZW0gd2FzIGZvdW5kXG4gKiBAcGFyYW0gdHlwZSAtIFRoZSB0eXBlIG9mIHRoZSB1bmRvY3VtZW50ZWQgaXRlbVxuICogQHBhcmFtIG5hbWUgLSBUaGUgbmFtZSBvZiB0aGUgdW5kb2N1bWVudGVkIGl0ZW1cbiAqIEBwYXJhbSBsaW5lIC0gVGhlIGxpbmUgbnVtYmVyIHdoZXJlIHRoZSBpdGVtIGFwcGVhcnNcbiAqIEByZXR1cm5zIHtVbmRvY3VtZW50ZWROb2RlfSBUaGUgdW5kb2N1bWVudGVkIGl0ZW0gZW50cnlcbiAqL1xuZnVuY3Rpb24gZ2V0VW5kb2N1bWVudGVkTm9kZShmaWxlOiBzdHJpbmcsIHR5cGU6IENvdmVyYWdlVHlwZSwgbmFtZTogc3RyaW5nLCBsaW5lOiBudW1iZXIpOiBVbmRvY3VtZW50ZWROb2RlIHtcbiAgcmV0dXJuIHsgZmlsZSwgdHlwZSwgbmFtZSwgbGluZSB9O1xufVxuXG5cblxuXG4iLCAiY29uc3QgX19pbmplY3RlZF9maWxlbmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tZG9jLWNvdmVyYWdlL3NyYy9saWIvdXRpbHMudHNcIjtjb25zdCBfX2luamVjdGVkX2Rpcm5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWRvYy1jb3ZlcmFnZS9zcmMvbGliXCI7Y29uc3QgX19pbmplY3RlZF9pbXBvcnRfbWV0YV91cmxfXyA9IFwiZmlsZTovLy9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1kb2MtY292ZXJhZ2Uvc3JjL2xpYi91dGlscy50c1wiO2ltcG9ydCB0eXBlIHsgQXVkaXQsIEF1ZGl0T3V0cHV0cywgR3JvdXAgfSBmcm9tIFwiQGNvZGUtcHVzaHVwL21vZGVsc1wiO1xuaW1wb3J0IHsgU3ludGF4S2luZCB9IGZyb20gXCJ0eXBlc2NyaXB0XCI7XG5pbXBvcnQgdHlwZSB7IERvY0NvdmVyYWdlUGx1Z2luQ29uZmlnIH0gZnJvbSBcIi4vY29uZmlnXCI7XG5pbXBvcnQgeyBBVURJVFNfTUFQIH0gZnJvbSBcIi4vY29uc3RhbnRzXCI7XG5pbXBvcnQgdHlwZSB7IENvdmVyYWdlUmVzdWx0LCBDb3ZlcmFnZVR5cGUgfSBmcm9tIFwiLi9tb2RlbHNcIjtcblxuLyoqXG4gKiBHZXQgYXVkaXRzIGJhc2VkIG9uIHRoZSBjb25maWd1cmF0aW9uLlxuICogSWYgbm8gYXVkaXRzIGFyZSBzcGVjaWZpZWQsIHJldHVybiBhbGwgYXVkaXRzLlxuICogSWYgYXVkaXRzIGFyZSBzcGVjaWZpZWQsIHJldHVybiBvbmx5IHRoZSBzcGVjaWZpZWQgYXVkaXRzLlxuICogQHBhcmFtIGNvbmZpZyAtIFRoZSBjb25maWd1cmF0aW9uIG9iamVjdC5cbiAqIEByZXR1cm5zIFRoZSBhdWRpdHMuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBmaWx0ZXJBdWRpdHNCeVBsdWdpbkNvbmZpZyhjb25maWc6IFBpY2s8RG9jQ292ZXJhZ2VQbHVnaW5Db25maWcsICdvbmx5QXVkaXRzJz4pOiBBdWRpdFtdIHtcbiAgICBjb25zdCB7IG9ubHlBdWRpdHMgfSA9IGNvbmZpZ1xuXG4gICAgaWYgKCFvbmx5QXVkaXRzIHx8IG9ubHlBdWRpdHMubGVuZ3RoID09PSAwKSB7XG4gICAgICAgIHJldHVybiBPYmplY3QudmFsdWVzKEFVRElUU19NQVApO1xuICAgIH1cblxuICAgIHJldHVybiBPYmplY3QudmFsdWVzKEFVRElUU19NQVApLmZpbHRlcihhdWRpdCA9PiBvbmx5QXVkaXRzLmluY2x1ZGVzKGF1ZGl0LnNsdWcpKTtcblxufVxuXG4vLyByZXR1cm4gZ3JvdXBzLmZpbHRlcihncm91cCA9PiBncm91cC5yZWZzLnNvbWUocmVmID0+IGF1ZGl0cy5zb21lKGF1ZGl0ID0+IGF1ZGl0LnNsdWcgPT09IHJlZi5zbHVnKSkpO1xuXG4vKipcbiAqIEZpbHRlciBncm91cHMgYnkgdGhlIGF1ZGl0cyB0aGF0IGFyZSBzcGVjaWZpZWQgaW4gdGhlIGNvbmZpZ3VyYXRpb24uXG4gKiBUaGUgZ3JvdXBzIHJlZnMgYXJlIGZpbHRlcmVkIHRvIG9ubHkgaW5jbHVkZSB0aGUgYXVkaXRzIHRoYXQgYXJlIHNwZWNpZmllZCBpbiB0aGUgY29uZmlndXJhdGlvbi5cbiAqIEBwYXJhbSBncm91cHMgLSBUaGUgZ3JvdXBzIHRvIGZpbHRlci5cbiAqIEBwYXJhbSBvcHRpb25zIC0gVGhlIGNvbmZpZ3VyYXRpb24gb2JqZWN0LlxuICogQHJldHVybnMgVGhlIGZpbHRlcmVkIGdyb3Vwcy5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGZpbHRlckdyb3Vwc0J5T25seUF1ZGl0cyhncm91cHM6IEdyb3VwW10sIG9wdGlvbnM6IFBpY2s8RG9jQ292ZXJhZ2VQbHVnaW5Db25maWcsICdvbmx5QXVkaXRzJz4pOiBHcm91cFtdIHtcbiAgICBjb25zdCBhdWRpdHMgPSBmaWx0ZXJBdWRpdHNCeVBsdWdpbkNvbmZpZyhvcHRpb25zKTtcbiAgICByZXR1cm4gZ3JvdXBzXG4gICAgICAgIC5tYXAoZ3JvdXAgPT4gKHtcbiAgICAgICAgICAgIC4uLmdyb3VwLFxuICAgICAgICAgICAgcmVmczogZ3JvdXAucmVmcy5maWx0ZXIocmVmID0+IGF1ZGl0cy5zb21lKGF1ZGl0ID0+IGF1ZGl0LnNsdWcgPT09IHJlZi5zbHVnKSlcbiAgICAgICAgfSkpXG4gICAgICAgIC5maWx0ZXIoZ3JvdXAgPT4gZ3JvdXAucmVmcy5sZW5ndGggPiAwKTs7XG59XG5cblxuLyoqXG4gKiBUcmFuc2Zvcm1zIHRoZSBjb3ZlcmFnZSByZXBvcnQgaW50byBhdWRpdCBvdXRwdXRzLlxuICogQHBhcmFtIGNvdmVyYWdlUmVzdWx0IC0gVGhlIGNvdmVyYWdlIHJlc3VsdCBjb250YWluaW5nIHVuZG9jdW1lbnRlZCBpdGVtcyBhbmQgY292ZXJhZ2Ugc3RhdGlzdGljc1xuICogQHBhcmFtIG9wdGlvbnMgLSBDb25maWd1cmF0aW9uIG9wdGlvbnMgc3BlY2lmeWluZyB3aGljaCBhdWRpdHMgdG8gaW5jbHVkZVxuICogQHJldHVybnMgQXVkaXQgb3V0cHV0cyB3aXRoIGNvdmVyYWdlIHNjb3JlcyBhbmQgZGV0YWlscyBhYm91dCB1bmRvY3VtZW50ZWQgaXRlbXNcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHRyYXNmb3JtQ292ZXJhZ2VSZXBvcnRUb0F1ZGl0cyhjb3ZlcmFnZVJlc3VsdDogQ292ZXJhZ2VSZXN1bHQsIG9wdGlvbnM6IFBpY2s8RG9jQ292ZXJhZ2VQbHVnaW5Db25maWcsICdvbmx5QXVkaXRzJz4pOiBBdWRpdE91dHB1dHMge1xuXG4gICAgcmV0dXJuIE9iamVjdC5lbnRyaWVzKGNvdmVyYWdlUmVzdWx0KVxuICAgICAgICAuZmlsdGVyKChbdHlwZV0pID0+ICFvcHRpb25zLm9ubHlBdWRpdHM/Lmxlbmd0aCB8fCBvcHRpb25zLm9ubHlBdWRpdHMuaW5jbHVkZXMoYCR7dHlwZX0tY292ZXJhZ2VgKSlcbiAgICAgICAgLm1hcCgoW3R5cGUsIGl0ZW1zXSkgPT4ge1xuICAgICAgICAgICAgY29uc3QgY292ZXJhZ2VUeXBlID0gdHlwZSBhcyBDb3ZlcmFnZVR5cGU7XG4gICAgICAgICAgICBjb25zdCBjb3ZlcmFnZSA9IGl0ZW1zLmNvdmVyYWdlO1xuXG4gICAgICAgICAgICByZXR1cm4ge1xuICAgICAgICAgICAgICAgIHNsdWc6IGAke2NvdmVyYWdlVHlwZX0tY292ZXJhZ2VgLFxuICAgICAgICAgICAgICAgIHZhbHVlOiBjb3ZlcmFnZSxcbiAgICAgICAgICAgICAgICBzY29yZTogY292ZXJhZ2UgLyAxMDAsXG4gICAgICAgICAgICAgICAgZGlzcGxheVZhbHVlOiBgJHtjb3ZlcmFnZX0gJWAsXG4gICAgICAgICAgICAgICAgZGV0YWlsczoge1xuICAgICAgICAgICAgICAgICAgICBpc3N1ZXM6IGl0ZW1zLmlzc3Vlcy5tYXAoKHsgZmlsZSwgbGluZSB9KSA9PiAoe1xuICAgICAgICAgICAgICAgICAgICAgICAgbWVzc2FnZTogJ01pc3NpbmcgZG9jdW1lbnRhdGlvbicsXG4gICAgICAgICAgICAgICAgICAgICAgICBzb3VyY2U6IHsgZmlsZSwgcG9zaXRpb246IHsgc3RhcnRMaW5lOiBsaW5lIH0gfSxcbiAgICAgICAgICAgICAgICAgICAgICAgIHNldmVyaXR5OiAnd2FybmluZycsXG4gICAgICAgICAgICAgICAgICAgIH0pKSxcbiAgICAgICAgICAgICAgICB9LFxuICAgICAgICAgICAgfTtcbiAgICAgICAgfSk7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBnZXRDb3ZlcmFnZVR5cGVGcm9tS2luZChraW5kOiBTeW50YXhLaW5kKTogQ292ZXJhZ2VUeXBlIHtcbiAgICBzd2l0Y2ggKGtpbmQpIHtcbiAgICAgICAgY2FzZSBTeW50YXhLaW5kLkNsYXNzRGVjbGFyYXRpb246XG4gICAgICAgICAgICByZXR1cm4gJ2NsYXNzZXMnO1xuICAgICAgICBjYXNlIFN5bnRheEtpbmQuTWV0aG9kRGVjbGFyYXRpb246XG4gICAgICAgICAgICByZXR1cm4gJ21ldGhvZHMnO1xuICAgICAgICBjYXNlIFN5bnRheEtpbmQuRnVuY3Rpb25EZWNsYXJhdGlvbjpcbiAgICAgICAgICAgIHJldHVybiAnZnVuY3Rpb25zJztcbiAgICAgICAgY2FzZSBTeW50YXhLaW5kLkludGVyZmFjZURlY2xhcmF0aW9uOlxuICAgICAgICAgICAgcmV0dXJuICdpbnRlcmZhY2VzJztcbiAgICAgICAgY2FzZSBTeW50YXhLaW5kLkVudW1EZWNsYXJhdGlvbjpcbiAgICAgICAgICAgIHJldHVybiAnZW51bXMnO1xuICAgICAgICBjYXNlIFN5bnRheEtpbmQuVmFyaWFibGVEZWNsYXJhdGlvbjpcbiAgICAgICAgICAgIHJldHVybiAndmFyaWFibGVzJztcbiAgICAgICAgY2FzZSBTeW50YXhLaW5kLlByb3BlcnR5RGVjbGFyYXRpb246XG4gICAgICAgICAgICByZXR1cm4gJ3Byb3BlcnRpZXMnO1xuICAgICAgICBjYXNlIFN5bnRheEtpbmQuVHlwZUFsaWFzRGVjbGFyYXRpb246XG4gICAgICAgICAgICByZXR1cm4gJ3R5cGVzJztcbiAgICAgICAgZGVmYXVsdDpcbiAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcihgVW5zdXBwb3J0ZWQgc3ludGF4IGtpbmQ6ICR7a2luZH1gKTtcbiAgICB9XG59XG4iLCAiY29uc3QgX19pbmplY3RlZF9maWxlbmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tZG9jLWNvdmVyYWdlL3NyYy9saWIvcnVubmVyL3V0aWxzLnRzXCI7Y29uc3QgX19pbmplY3RlZF9kaXJuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1kb2MtY292ZXJhZ2Uvc3JjL2xpYi9ydW5uZXJcIjtjb25zdCBfX2luamVjdGVkX2ltcG9ydF9tZXRhX3VybF9fID0gXCJmaWxlOi8vL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWRvYy1jb3ZlcmFnZS9zcmMvbGliL3J1bm5lci91dGlscy50c1wiO2ltcG9ydCB0eXBlIHsgQ292ZXJhZ2VSZXN1bHQsIENvdmVyYWdlVHlwZSwgVW5wcm9jZXNzZWRDb3ZlcmFnZVJlc3VsdCB9IGZyb20gXCIuLi9tb2RlbHNcIjtcblxuZXhwb3J0IGZ1bmN0aW9uIGNyZWF0ZUVtcHR5VW5wcm9jZXNzZWRDb3ZlcmFnZVJlcG9ydCgpOiBVbnByb2Nlc3NlZENvdmVyYWdlUmVzdWx0IHtcbiAgICByZXR1cm4ge1xuICAgICAgICBlbnVtczogeyBub2Rlc0NvdW50OiAwLCBpc3N1ZXM6IFtdIH0sXG4gICAgICAgIGludGVyZmFjZXM6IHsgbm9kZXNDb3VudDogMCwgaXNzdWVzOiBbXSB9LFxuICAgICAgICB0eXBlczogeyBub2Rlc0NvdW50OiAwLCBpc3N1ZXM6IFtdIH0sXG4gICAgICAgIGZ1bmN0aW9uczogeyBub2Rlc0NvdW50OiAwLCBpc3N1ZXM6IFtdIH0sXG4gICAgICAgIHZhcmlhYmxlczogeyBub2Rlc0NvdW50OiAwLCBpc3N1ZXM6IFtdIH0sXG4gICAgICAgIGNsYXNzZXM6IHsgbm9kZXNDb3VudDogMCwgaXNzdWVzOiBbXSB9LFxuICAgICAgICBtZXRob2RzOiB7IG5vZGVzQ291bnQ6IDAsIGlzc3VlczogW10gfSxcbiAgICAgICAgcHJvcGVydGllczogeyBub2Rlc0NvdW50OiAwLCBpc3N1ZXM6IFtdIH0sXG4gICAgfVxufVxuXG5leHBvcnQgZnVuY3Rpb24gY2FsY3VsYXRlQ292ZXJhZ2UocmVzdWx0OiBVbnByb2Nlc3NlZENvdmVyYWdlUmVzdWx0KSB7XG4gICAgcmV0dXJuIE9iamVjdC5mcm9tRW50cmllcyhPYmplY3QuZW50cmllcyhyZXN1bHQpLm1hcCgoW2tleSwgdmFsdWVdKSA9PiB7XG4gICAgICAgIGNvbnN0IHR5cGUgPSBrZXkgYXMgQ292ZXJhZ2VUeXBlO1xuICAgICAgICByZXR1cm4gW3R5cGUsIHtcbiAgICAgICAgICAgIGNvdmVyYWdlOiB2YWx1ZS5ub2Rlc0NvdW50ID09PSAwID8gMTAwIDogKDEgLSB2YWx1ZS5pc3N1ZXMubGVuZ3RoIC8gdmFsdWUubm9kZXNDb3VudCkgKiAxMDAsXG4gICAgICAgICAgICBpc3N1ZXM6IHZhbHVlLmlzc3VlcyxcbiAgICAgICAgICAgIG5vZGVzQ291bnQ6IHZhbHVlLm5vZGVzQ291bnRcbiAgICAgICAgfV1cbiAgICB9KSkgYXMgQ292ZXJhZ2VSZXN1bHQ7XG59XG4iLCAiY29uc3QgX19pbmplY3RlZF9maWxlbmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tZG9jLWNvdmVyYWdlL3NyYy9saWIvZG9jLWNvdmVyYWdlLXBsdWdpbi50c1wiO2NvbnN0IF9faW5qZWN0ZWRfZGlybmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tZG9jLWNvdmVyYWdlL3NyYy9saWJcIjtjb25zdCBfX2luamVjdGVkX2ltcG9ydF9tZXRhX3VybF9fID0gXCJmaWxlOi8vL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWRvYy1jb3ZlcmFnZS9zcmMvbGliL2RvYy1jb3ZlcmFnZS1wbHVnaW4udHNcIjtpbXBvcnQgdHlwZSB7IEF1ZGl0T3V0cHV0cywgUGx1Z2luQ29uZmlnLCBSdW5uZXJGdW5jdGlvbiB9IGZyb20gJ0Bjb2RlLXB1c2h1cC9tb2RlbHMnO1xuaW1wb3J0IHtcbiAgdHlwZSBEb2NDb3ZlcmFnZVBsdWdpbkNvbmZpZyxcbiAgZG9jQ292ZXJhZ2VQbHVnaW5Db25maWdTY2hlbWEsXG59IGZyb20gJy4vY29uZmlnLmpzJztcbmltcG9ydCB7IGdyb3VwcywgUExVR0lOX1NMVUcgfSBmcm9tICcuL2NvbnN0YW50cy5qcyc7XG5pbXBvcnQgeyBwcm9jZXNzRG9jQ292ZXJhZ2UgfSBmcm9tICcuL3J1bm5lci9kb2MtcHJvY2Vzc2VyLmpzJztcbmltcG9ydCB7IGZpbHRlckF1ZGl0c0J5UGx1Z2luQ29uZmlnLCBmaWx0ZXJHcm91cHNCeU9ubHlBdWRpdHMsIHRyYXNmb3JtQ292ZXJhZ2VSZXBvcnRUb0F1ZGl0cyB9IGZyb20gJy4vdXRpbHMuanMnO1xuXG5jb25zdCBQTFVHSU5fVElUTEUgPSAnRG9jdW1lbnRhdGlvbiBjb3ZlcmFnZSc7XG5cbmNvbnN0IFBMVUdJTl9ERVNDUklQVElPTiA9ICdPZmZpY2lhbCBDb2RlIFB1c2hVcCBkb2N1bWVudGF0aW9uIGNvdmVyYWdlIHBsdWdpbi4nO1xuXG5jb25zdCBQTFVHSU5fRE9DU19VUkwgPSAnaHR0cHM6Ly93d3cubnBtanMuY29tL3BhY2thZ2UvQGNvZGUtcHVzaHVwL2RvYy1jb3ZlcmFnZS1wbHVnaW4vJztcblxuLyoqXG4gKiBJbnN0YW50aWF0ZXMgQ29kZSBQdXNoVXAgZG9jdW1lbnRhdGlvbiBjb3ZlcmFnZSBwbHVnaW4gZm9yIGNvcmUgY29uZmlnLlxuICpcbiAqIEBleGFtcGxlXG4gKiBpbXBvcnQgZG9jQ292ZXJhZ2VQbHVnaW4gZnJvbSAnQGNvZGUtcHVzaHVwL2RvYy1jb3ZlcmFnZS1wbHVnaW4nXG4gKlxuICogZXhwb3J0IGRlZmF1bHQge1xuICogICAvLyAuLi4gY29yZSBjb25maWcgLi4uXG4gKiAgIHBsdWdpbnM6IFtcbiAqICAgICAvLyAuLi4gb3RoZXIgcGx1Z2lucyAuLi5cbiAqICAgICBhd2FpdCBkb2NDb3ZlcmFnZVBsdWdpbih7XG4gKiAgICAgICBzb3VyY2VHbG9iOiAnc3JjJiM0NzsqKiYjNDc7Ki57dHMsdHN4fSdcbiAqICAgICB9KVxuICogICBdXG4gKiB9XG4gKlxuICogQHJldHVybnMgUGx1Z2luIGNvbmZpZ3VyYXRpb24uXG4gKi9cblxuXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gZG9jQ292ZXJhZ2VQbHVnaW4oY29uZmlnOiBEb2NDb3ZlcmFnZVBsdWdpbkNvbmZpZyk6IFByb21pc2U8UGx1Z2luQ29uZmlnPiB7XG5cblxuICBjb25zdCBkb2NDb3ZlcmFnZUNvbmZpZyA9IGRvY0NvdmVyYWdlUGx1Z2luQ29uZmlnU2NoZW1hLnBhcnNlKGNvbmZpZyk7XG5cblxuICBjb25zdCBncm91cHNDID0gZmlsdGVyR3JvdXBzQnlPbmx5QXVkaXRzKGdyb3VwcywgZG9jQ292ZXJhZ2VDb25maWcpO1xuICBjb25zdCBhdWRpdHNDID0gZmlsdGVyQXVkaXRzQnlQbHVnaW5Db25maWcoZG9jQ292ZXJhZ2VDb25maWcpO1xuXG4gIHJldHVybiB7XG4gICAgc2x1ZzogUExVR0lOX1NMVUcsXG4gICAgdGl0bGU6IFBMVUdJTl9USVRMRSxcbiAgICBpY29uOiAnZm9sZGVyLXNyYycsXG4gICAgZGVzY3JpcHRpb246IFBMVUdJTl9ERVNDUklQVElPTixcbiAgICBkb2NzVXJsOiBQTFVHSU5fRE9DU19VUkwsXG4gICAgZ3JvdXBzOiBmaWx0ZXJHcm91cHNCeU9ubHlBdWRpdHMoZ3JvdXBzLCBkb2NDb3ZlcmFnZUNvbmZpZyksXG4gICAgYXVkaXRzOiBmaWx0ZXJBdWRpdHNCeVBsdWdpbkNvbmZpZyhkb2NDb3ZlcmFnZUNvbmZpZyksXG4gICAgcnVubmVyOiBjcmVhdGVSdW5uZXJGdW5jdGlvbihkb2NDb3ZlcmFnZUNvbmZpZyksXG4gIH07XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBjcmVhdGVSdW5uZXJGdW5jdGlvbihjb25maWc6IERvY0NvdmVyYWdlUGx1Z2luQ29uZmlnKTogUnVubmVyRnVuY3Rpb24ge1xuICByZXR1cm4gKCk6IEF1ZGl0T3V0cHV0cyA9PiB7XG4gICAgY29uc3QgY292ZXJhZ2VSZXN1bHQgPSBwcm9jZXNzRG9jQ292ZXJhZ2UoY29uZmlnKVxuICAgIHJldHVybiB0cmFzZm9ybUNvdmVyYWdlUmVwb3J0VG9BdWRpdHMoY292ZXJhZ2VSZXN1bHQsIGNvbmZpZyk7XG4gIH07XG59XG4iLCAiY29uc3QgX19pbmplY3RlZF9maWxlbmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tZG9jLWNvdmVyYWdlL3NyYy9pbmRleC50c1wiO2NvbnN0IF9faW5qZWN0ZWRfZGlybmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tZG9jLWNvdmVyYWdlL3NyY1wiO2NvbnN0IF9faW5qZWN0ZWRfaW1wb3J0X21ldGFfdXJsX18gPSBcImZpbGU6Ly8vaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tZG9jLWNvdmVyYWdlL3NyYy9pbmRleC50c1wiO2ltcG9ydCB7IGRvY0NvdmVyYWdlUGx1Z2luIH0gZnJvbSAnLi9saWIvZG9jLWNvdmVyYWdlLXBsdWdpbi5qcyc7XG5cbmV4cG9ydCBkZWZhdWx0IGRvY0NvdmVyYWdlUGx1Z2luO1xuZXhwb3J0IHR5cGUgeyBEb2NDb3ZlcmFnZVBsdWdpbkNvbmZpZyB9IGZyb20gJy4vbGliL2NvbmZpZy5qcyc7XG4iLCAiY29uc3QgX19pbmplY3RlZF9maWxlbmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tZXNsaW50L3NyYy9saWIvZXNsaW50LXBsdWdpbi50c1wiO2NvbnN0IF9faW5qZWN0ZWRfZGlybmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tZXNsaW50L3NyYy9saWJcIjtjb25zdCBfX2luamVjdGVkX2ltcG9ydF9tZXRhX3VybF9fID0gXCJmaWxlOi8vL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWVzbGludC9zcmMvbGliL2VzbGludC1wbHVnaW4udHNcIjtpbXBvcnQgeyBjcmVhdGVSZXF1aXJlIH0gZnJvbSAnbm9kZTptb2R1bGUnO1xuaW1wb3J0IHBhdGggZnJvbSAnbm9kZTpwYXRoJztcbmltcG9ydCB7IGZpbGVVUkxUb1BhdGggfSBmcm9tICdub2RlOnVybCc7XG5pbXBvcnQgdHlwZSB7IFBsdWdpbkNvbmZpZyB9IGZyb20gJ0Bjb2RlLXB1c2h1cC9tb2RlbHMnO1xuaW1wb3J0IHsgdHlwZSBFU0xpbnRQbHVnaW5Db25maWcsIGVzbGludFBsdWdpbkNvbmZpZ1NjaGVtYSB9IGZyb20gJy4vY29uZmlnLmpzJztcbmltcG9ydCB7IGxpc3RBdWRpdHNBbmRHcm91cHMgfSBmcm9tICcuL21ldGEvaW5kZXguanMnO1xuaW1wb3J0IHsgY3JlYXRlUnVubmVyQ29uZmlnIH0gZnJvbSAnLi9ydW5uZXIvaW5kZXguanMnO1xuXG4vKipcbiAqIEluc3RhbnRpYXRlcyBDb2RlIFB1c2hVcCBFU0xpbnQgcGx1Z2luIGZvciB1c2UgaW4gY29yZSBjb25maWcuXG4gKlxuICogQGV4YW1wbGVcbiAqIGltcG9ydCBlc2xpbnRQbHVnaW4gZnJvbSAnQGNvZGUtcHVzaHVwL2VzbGludC1wbHVnaW4nXG4gKlxuICogZXhwb3J0IGRlZmF1bHQge1xuICogICAvLyAuLi4gY29yZSBjb25maWcgLi4uXG4gKiAgIHBsdWdpbnM6IFtcbiAqICAgICAvLyAuLi4gb3RoZXIgcGx1Z2lucyAuLi5cbiAqICAgICBhd2FpdCBlc2xpbnRQbHVnaW4oe1xuICogICAgICAgZXNsaW50cmM6ICcuZXNsaW50cmMuanNvbicsXG4gKiAgICAgICBwYXR0ZXJuczogWydzcmMnLCAndGVzdC8qLnNwZWMuanMnXVxuICogICAgIH0pXG4gKiAgIF1cbiAqIH1cbiAqXG4gKiBAcGFyYW0gY29uZmlnIENvbmZpZ3VyYXRpb24gb3B0aW9ucy5cbiAqIEByZXR1cm5zIFBsdWdpbiBjb25maWd1cmF0aW9uIGFzIGEgcHJvbWlzZS5cbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGVzbGludFBsdWdpbihcbiAgY29uZmlnOiBFU0xpbnRQbHVnaW5Db25maWcsXG4pOiBQcm9taXNlPFBsdWdpbkNvbmZpZz4ge1xuICBjb25zdCB0YXJnZXRzID0gZXNsaW50UGx1Z2luQ29uZmlnU2NoZW1hLnBhcnNlKGNvbmZpZyk7XG5cbiAgY29uc3QgeyBhdWRpdHMsIGdyb3VwcyB9ID0gYXdhaXQgbGlzdEF1ZGl0c0FuZEdyb3Vwcyh0YXJnZXRzKTtcblxuICBjb25zdCBydW5uZXJTY3JpcHRQYXRoID0gcGF0aC5qb2luKFxuICAgIGZpbGVVUkxUb1BhdGgocGF0aC5kaXJuYW1lKGltcG9ydC5tZXRhLnVybCkpLFxuICAgICcuLicsXG4gICAgJ2Jpbi5qcycsXG4gICk7XG5cbiAgY29uc3QgcGFja2FnZUpzb24gPSBjcmVhdGVSZXF1aXJlKGltcG9ydC5tZXRhLnVybCkoXG4gICAgJy4uLy4uL3BhY2thZ2UuanNvbicsXG4gICkgYXMgdHlwZW9mIGltcG9ydCgnLi4vLi4vcGFja2FnZS5qc29uJyk7XG5cbiAgcmV0dXJuIHtcbiAgICBzbHVnOiAnZXNsaW50JyxcbiAgICB0aXRsZTogJ0VTTGludCcsXG4gICAgaWNvbjogJ2VzbGludCcsXG4gICAgZGVzY3JpcHRpb246ICdPZmZpY2lhbCBDb2RlIFB1c2hVcCBFU0xpbnQgcGx1Z2luJyxcbiAgICBkb2NzVXJsOiAnaHR0cHM6Ly93d3cubnBtanMuY29tL3BhY2thZ2UvQGNvZGUtcHVzaHVwL2VzbGludC1wbHVnaW4nLFxuICAgIHBhY2thZ2VOYW1lOiBwYWNrYWdlSnNvbi5uYW1lLFxuICAgIHZlcnNpb246IHBhY2thZ2VKc29uLnZlcnNpb24sXG5cbiAgICBhdWRpdHMsXG4gICAgZ3JvdXBzLFxuXG4gICAgcnVubmVyOiBhd2FpdCBjcmVhdGVSdW5uZXJDb25maWcocnVubmVyU2NyaXB0UGF0aCwgYXVkaXRzLCB0YXJnZXRzKSxcbiAgfTtcbn1cbiIsICJjb25zdCBfX2luamVjdGVkX2ZpbGVuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1lc2xpbnQvc3JjL2xpYi9jb25maWcudHNcIjtjb25zdCBfX2luamVjdGVkX2Rpcm5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWVzbGludC9zcmMvbGliXCI7Y29uc3QgX19pbmplY3RlZF9pbXBvcnRfbWV0YV91cmxfXyA9IFwiZmlsZTovLy9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1lc2xpbnQvc3JjL2xpYi9jb25maWcudHNcIjtpbXBvcnQgeyB6IH0gZnJvbSAnem9kJztcbmltcG9ydCB7IHRvQXJyYXkgfSBmcm9tICdAY29kZS1wdXNodXAvdXRpbHMnO1xuXG5jb25zdCBwYXR0ZXJuc1NjaGVtYSA9IHoudW5pb24oW3ouc3RyaW5nKCksIHouYXJyYXkoei5zdHJpbmcoKSkubWluKDEpXSwge1xuICBkZXNjcmlwdGlvbjpcbiAgICAnTGludCB0YXJnZXQgZmlsZXMuIE1heSBjb250YWluIGZpbGUgcGF0aHMsIGRpcmVjdG9yeSBwYXRocyBvciBnbG9iIHBhdHRlcm5zJyxcbn0pO1xuXG5jb25zdCBlc2xpbnRyY1NjaGVtYSA9IHouc3RyaW5nKHsgZGVzY3JpcHRpb246ICdQYXRoIHRvIEVTTGludCBjb25maWcgZmlsZScgfSk7XG5cbmNvbnN0IGVzbGludFRhcmdldE9iamVjdFNjaGVtYSA9IHoub2JqZWN0KHtcbiAgZXNsaW50cmM6IGVzbGludHJjU2NoZW1hLm9wdGlvbmFsKCksXG4gIHBhdHRlcm5zOiBwYXR0ZXJuc1NjaGVtYSxcbn0pO1xudHlwZSBFU0xpbnRUYXJnZXRPYmplY3QgPSB6LmluZmVyPHR5cGVvZiBlc2xpbnRUYXJnZXRPYmplY3RTY2hlbWE+O1xuXG5leHBvcnQgY29uc3QgZXNsaW50VGFyZ2V0U2NoZW1hID0gelxuICAudW5pb24oW3BhdHRlcm5zU2NoZW1hLCBlc2xpbnRUYXJnZXRPYmplY3RTY2hlbWFdKVxuICAudHJhbnNmb3JtKFxuICAgICh0YXJnZXQpOiBFU0xpbnRUYXJnZXRPYmplY3QgPT5cbiAgICAgIHR5cGVvZiB0YXJnZXQgPT09ICdzdHJpbmcnIHx8IEFycmF5LmlzQXJyYXkodGFyZ2V0KVxuICAgICAgICA/IHsgcGF0dGVybnM6IHRhcmdldCB9XG4gICAgICAgIDogdGFyZ2V0LFxuICApO1xuZXhwb3J0IHR5cGUgRVNMaW50VGFyZ2V0ID0gei5pbmZlcjx0eXBlb2YgZXNsaW50VGFyZ2V0U2NoZW1hPjtcblxuZXhwb3J0IGNvbnN0IGVzbGludFBsdWdpbkNvbmZpZ1NjaGVtYSA9IHpcbiAgLnVuaW9uKFtlc2xpbnRUYXJnZXRTY2hlbWEsIHouYXJyYXkoZXNsaW50VGFyZ2V0U2NoZW1hKS5taW4oMSldKVxuICAudHJhbnNmb3JtKHRvQXJyYXkpO1xuZXhwb3J0IHR5cGUgRVNMaW50UGx1Z2luQ29uZmlnID0gei5pbnB1dDx0eXBlb2YgZXNsaW50UGx1Z2luQ29uZmlnU2NoZW1hPjtcblxuZXhwb3J0IHR5cGUgRVNMaW50UGx1Z2luUnVubmVyQ29uZmlnID0ge1xuICB0YXJnZXRzOiBFU0xpbnRUYXJnZXRbXTtcbiAgc2x1Z3M6IHN0cmluZ1tdO1xufTtcbiIsICJjb25zdCBfX2luamVjdGVkX2ZpbGVuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1lc2xpbnQvc3JjL2xpYi9tZXRhL2dyb3Vwcy50c1wiO2NvbnN0IF9faW5qZWN0ZWRfZGlybmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tZXNsaW50L3NyYy9saWIvbWV0YVwiO2NvbnN0IF9faW5qZWN0ZWRfaW1wb3J0X21ldGFfdXJsX18gPSBcImZpbGU6Ly8vaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tZXNsaW50L3NyYy9saWIvbWV0YS9ncm91cHMudHNcIjtpbXBvcnQgdHlwZSB7IFJ1bGUgfSBmcm9tICdlc2xpbnQnO1xuaW1wb3J0IHR5cGUgeyBHcm91cCwgR3JvdXBSZWYgfSBmcm9tICdAY29kZS1wdXNodXAvbW9kZWxzJztcbmltcG9ydCB7IG9iamVjdFRvS2V5cywgc2x1Z2lmeSB9IGZyb20gJ0Bjb2RlLXB1c2h1cC91dGlscyc7XG5pbXBvcnQgeyBydWxlVG9TbHVnIH0gZnJvbSAnLi9oYXNoLmpzJztcbmltcG9ydCB7IHR5cGUgUnVsZURhdGEsIHBhcnNlUnVsZUlkIH0gZnJvbSAnLi9wYXJzZS5qcyc7XG5cbnR5cGUgUnVsZVR5cGUgPSBOb25OdWxsYWJsZTxSdWxlLlJ1bGVNZXRhRGF0YVsndHlwZSddPjtcblxuLy8gZG9jcyBvbiBtZXRhLnR5cGU6IGh0dHBzOi8vZXNsaW50Lm9yZy9kb2NzL2xhdGVzdC9leHRlbmQvY3VzdG9tLXJ1bGVzI3J1bGUtc3RydWN0dXJlXG5jb25zdCB0eXBlR3JvdXBzOiBSZWNvcmQ8UnVsZVR5cGUsIE9taXQ8R3JvdXAsICdyZWZzJz4+ID0ge1xuICBwcm9ibGVtOiB7XG4gICAgc2x1ZzogJ3Byb2JsZW1zJyxcbiAgICB0aXRsZTogJ1Byb2JsZW1zJyxcbiAgICBkZXNjcmlwdGlvbjpcbiAgICAgICdDb2RlIHRoYXQgZWl0aGVyIHdpbGwgY2F1c2UgYW4gZXJyb3Igb3IgbWF5IGNhdXNlIGNvbmZ1c2luZyBiZWhhdmlvci4gRGV2ZWxvcGVycyBzaG91bGQgY29uc2lkZXIgdGhpcyBhIGhpZ2ggcHJpb3JpdHkgdG8gcmVzb2x2ZS4nLFxuICB9LFxuICBzdWdnZXN0aW9uOiB7XG4gICAgc2x1ZzogJ3N1Z2dlc3Rpb25zJyxcbiAgICB0aXRsZTogJ1N1Z2dlc3Rpb25zJyxcbiAgICBkZXNjcmlwdGlvbjpcbiAgICAgIFwiU29tZXRoaW5nIHRoYXQgY291bGQgYmUgZG9uZSBpbiBhIGJldHRlciB3YXkgYnV0IG5vIGVycm9ycyB3aWxsIG9jY3VyIGlmIHRoZSBjb2RlIGlzbid0IGNoYW5nZWQuXCIsXG4gIH0sXG4gIGxheW91dDoge1xuICAgIHNsdWc6ICdmb3JtYXR0aW5nJyxcbiAgICB0aXRsZTogJ0Zvcm1hdHRpbmcnLFxuICAgIGRlc2NyaXB0aW9uOlxuICAgICAgJ1ByaW1hcmlseSBhYm91dCB3aGl0ZXNwYWNlLCBzZW1pY29sb25zLCBjb21tYXMsIGFuZCBwYXJlbnRoZXNlcywgYWxsIHRoZSBwYXJ0cyBvZiB0aGUgcHJvZ3JhbSB0aGF0IGRldGVybWluZSBob3cgdGhlIGNvZGUgbG9va3MgcmF0aGVyIHRoYW4gaG93IGl0IGV4ZWN1dGVzLicsXG4gIH0sXG59O1xuXG5leHBvcnQgZnVuY3Rpb24gZ3JvdXBzRnJvbVJ1bGVUeXBlcyhydWxlczogUnVsZURhdGFbXSk6IEdyb3VwW10ge1xuICBjb25zdCBhbGxUeXBlcyA9IG9iamVjdFRvS2V5cyh0eXBlR3JvdXBzKTtcblxuICBjb25zdCBhdWRpdFNsdWdzTWFwID0gcnVsZXMucmVkdWNlPFBhcnRpYWw8UmVjb3JkPFJ1bGVUeXBlLCBzdHJpbmdbXT4+PihcbiAgICAoYWNjLCBydWxlKSA9PlxuICAgICAgcnVsZS5tZXRhLnR5cGUgPT0gbnVsbFxuICAgICAgICA/IGFjY1xuICAgICAgICA6IHtcbiAgICAgICAgICAgIC4uLmFjYyxcbiAgICAgICAgICAgIFtydWxlLm1ldGEudHlwZV06IFtcbiAgICAgICAgICAgICAgLi4uKGFjY1tydWxlLm1ldGEudHlwZV0gPz8gW10pLFxuICAgICAgICAgICAgICBydWxlVG9TbHVnKHJ1bGUpLFxuICAgICAgICAgICAgXSxcbiAgICAgICAgICB9LFxuICAgIHt9LFxuICApO1xuXG4gIHJldHVybiBhbGxUeXBlc1xuICAgIC5tYXAodHlwZSA9PiAoe1xuICAgICAgLi4udHlwZUdyb3Vwc1t0eXBlXSxcbiAgICAgIHJlZnM6XG4gICAgICAgIGF1ZGl0U2x1Z3NNYXBbdHlwZV0/Lm1hcCgoc2x1Zyk6IEdyb3VwUmVmID0+ICh7IHNsdWcsIHdlaWdodDogMSB9KSkgPz9cbiAgICAgICAgW10sXG4gICAgfSkpXG4gICAgLmZpbHRlcihncm91cCA9PiBncm91cC5yZWZzLmxlbmd0aCk7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBncm91cHNGcm9tUnVsZUNhdGVnb3JpZXMocnVsZXM6IFJ1bGVEYXRhW10pOiBHcm91cFtdIHtcbiAgY29uc3QgY2F0ZWdvcmllc01hcCA9IHJ1bGVzLnJlZHVjZTxSZWNvcmQ8c3RyaW5nLCBSZWNvcmQ8c3RyaW5nLCBzdHJpbmdbXT4+PihcbiAgICAoYWNjLCBydWxlKSA9PiB7XG4gICAgICAvLyBtZXRhLmRvY3MuY2F0ZWdvcnkgc3RpbGwgdXNlZCBieSBzb21lIHBvcHVsYXIgcGx1Z2lucyAoZS5nLiBpbXBvcnQsIHJlYWN0LCBmdW5jdGlvbmFsKVxuICAgICAgY29uc3QgY2F0ZWdvcnkgPSBydWxlLm1ldGEuZG9jcz8uY2F0ZWdvcnk7XG4gICAgICBpZiAoIWNhdGVnb3J5KSB7XG4gICAgICAgIHJldHVybiBhY2M7XG4gICAgICB9XG4gICAgICBjb25zdCB7IHBsdWdpbiA9ICcnIH0gPSBwYXJzZVJ1bGVJZChydWxlLmlkKTtcbiAgICAgIHJldHVybiB7XG4gICAgICAgIC4uLmFjYyxcbiAgICAgICAgW3BsdWdpbl06IHtcbiAgICAgICAgICAuLi5hY2NbcGx1Z2luXSxcbiAgICAgICAgICBbY2F0ZWdvcnldOiBbLi4uKGFjY1twbHVnaW5dPy5bY2F0ZWdvcnldID8/IFtdKSwgcnVsZVRvU2x1ZyhydWxlKV0sXG4gICAgICAgIH0sXG4gICAgICB9O1xuICAgIH0sXG4gICAge30sXG4gICk7XG5cbiAgY29uc3QgZ3JvdXBzID0gT2JqZWN0LmVudHJpZXMoY2F0ZWdvcmllc01hcCkuZmxhdE1hcCgoW3BsdWdpbiwgY2F0ZWdvcmllc10pID0+XG4gICAgT2JqZWN0LmVudHJpZXMoY2F0ZWdvcmllcykubWFwKFxuICAgICAgKFtjYXRlZ29yeSwgc2x1Z3NdKTogR3JvdXAgPT4gKHtcbiAgICAgICAgc2x1ZzogYCR7c2x1Z2lmeShwbHVnaW4pfS0ke3NsdWdpZnkoY2F0ZWdvcnkpfWAsXG4gICAgICAgIHRpdGxlOiBgJHtjYXRlZ29yeX0gKCR7cGx1Z2lufSlgLFxuICAgICAgICByZWZzOiBzbHVncy5tYXAoc2x1ZyA9PiAoeyBzbHVnLCB3ZWlnaHQ6IDEgfSkpLFxuICAgICAgfSksXG4gICAgKSxcbiAgKTtcblxuICByZXR1cm4gZ3JvdXBzLnRvU29ydGVkKChhLCBiKSA9PiBhLnNsdWcubG9jYWxlQ29tcGFyZShiLnNsdWcpKTtcbn1cbiIsICJjb25zdCBfX2luamVjdGVkX2ZpbGVuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1lc2xpbnQvc3JjL2xpYi9tZXRhL2hhc2gudHNcIjtjb25zdCBfX2luamVjdGVkX2Rpcm5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWVzbGludC9zcmMvbGliL21ldGFcIjtjb25zdCBfX2luamVjdGVkX2ltcG9ydF9tZXRhX3VybF9fID0gXCJmaWxlOi8vL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWVzbGludC9zcmMvbGliL21ldGEvaGFzaC50c1wiO2ltcG9ydCB7IGNyZWF0ZUhhc2ggfSBmcm9tICdub2RlOmNyeXB0byc7XG5pbXBvcnQgeyBzbHVnaWZ5IH0gZnJvbSAnQGNvZGUtcHVzaHVwL3V0aWxzJztcbmltcG9ydCB7IHR5cGUgUnVsZURhdGEsIHJlc29sdmVSdWxlT3B0aW9ucyB9IGZyb20gJy4vcGFyc2UuanMnO1xuXG5leHBvcnQgZnVuY3Rpb24gcnVsZVRvU2x1ZyhydWxlOiBSdWxlRGF0YSk6IHN0cmluZyB7XG4gIHJldHVybiBydWxlSWRUb1NsdWcocnVsZS5pZCwgcmVzb2x2ZVJ1bGVPcHRpb25zKHJ1bGUpKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHJ1bGVJZFRvU2x1ZyhcbiAgcnVsZUlkOiBzdHJpbmcsXG4gIG9wdGlvbnM6IHVua25vd25bXSB8IHVuZGVmaW5lZCxcbik6IHN0cmluZyB7XG4gIGNvbnN0IHNsdWcgPSBzbHVnaWZ5KHJ1bGVJZCk7XG4gIGlmICghb3B0aW9ucz8ubGVuZ3RoKSB7XG4gICAgcmV0dXJuIHNsdWc7XG4gIH1cbiAgcmV0dXJuIGAke3NsdWd9LSR7anNvbkhhc2gob3B0aW9ucyl9YDtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGpzb25IYXNoKGRhdGE6IHVua25vd24sIGJ5dGVzID0gOCk6IHN0cmluZyB7XG4gIHJldHVybiBjcmVhdGVIYXNoKCdzaGFrZTI1NicsIHsgb3V0cHV0TGVuZ3RoOiBieXRlcyB9KVxuICAgIC51cGRhdGUoSlNPTi5zdHJpbmdpZnkoZGF0YSkgfHwgJ251bGwnKVxuICAgIC5kaWdlc3QoJ2hleCcpO1xufVxuIiwgImNvbnN0IF9faW5qZWN0ZWRfZmlsZW5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWVzbGludC9zcmMvbGliL21ldGEvcGFyc2UudHNcIjtjb25zdCBfX2luamVjdGVkX2Rpcm5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWVzbGludC9zcmMvbGliL21ldGFcIjtjb25zdCBfX2luamVjdGVkX2ltcG9ydF9tZXRhX3VybF9fID0gXCJmaWxlOi8vL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWVzbGludC9zcmMvbGliL21ldGEvcGFyc2UudHNcIjtpbXBvcnQgdHlwZSB7IExpbnRlciwgUnVsZSB9IGZyb20gJ2VzbGludCc7XG5pbXBvcnQgeyB0b0FycmF5IH0gZnJvbSAnQGNvZGUtcHVzaHVwL3V0aWxzJztcblxuZXhwb3J0IHR5cGUgUnVsZURhdGEgPSB7XG4gIGlkOiBzdHJpbmc7XG4gIG1ldGE6IFJ1bGUuUnVsZU1ldGFEYXRhO1xuICBvcHRpb25zOiB1bmtub3duW10gfCB1bmRlZmluZWQ7XG59O1xuXG5leHBvcnQgZnVuY3Rpb24gcGFyc2VSdWxlSWQocnVsZUlkOiBzdHJpbmcpOiB7IHBsdWdpbj86IHN0cmluZzsgbmFtZTogc3RyaW5nIH0ge1xuICBjb25zdCBpID0gcnVsZUlkLnN0YXJ0c1dpdGgoJ0AnKVxuICAgID8gcnVsZUlkLmxhc3RJbmRleE9mKCcvJylcbiAgICA6IHJ1bGVJZC5pbmRleE9mKCcvJyk7XG4gIGlmIChpID09PSAtMSkge1xuICAgIHJldHVybiB7IG5hbWU6IHJ1bGVJZCB9O1xuICB9XG4gIHJldHVybiB7XG4gICAgcGx1Z2luOiBydWxlSWQuc2xpY2UoMCwgaSksXG4gICAgbmFtZTogcnVsZUlkLnNsaWNlKGkgKyAxKSxcbiAgfTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGlzUnVsZU9mZihlbnRyeTogTGludGVyLlJ1bGVFbnRyeTx1bmtub3duW10+KTogYm9vbGVhbiB7XG4gIGNvbnN0IGxldmVsID0gQXJyYXkuaXNBcnJheShlbnRyeSkgPyBlbnRyeVswXSA6IGVudHJ5O1xuXG4gIHN3aXRjaCAobGV2ZWwpIHtcbiAgICBjYXNlIDA6XG4gICAgY2FzZSAnb2ZmJzpcbiAgICAgIHJldHVybiB0cnVlO1xuICAgIGNhc2UgMTpcbiAgICBjYXNlIDI6XG4gICAgY2FzZSAnd2Fybic6XG4gICAgY2FzZSAnZXJyb3InOlxuICAgICAgcmV0dXJuIGZhbHNlO1xuICB9XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBvcHRpb25zRnJvbVJ1bGVFbnRyeShcbiAgZW50cnk6IExpbnRlci5SdWxlRW50cnk8dW5rbm93bltdPixcbik6IHVua25vd25bXSB7XG4gIHJldHVybiB0b0FycmF5KGVudHJ5KS5zbGljZSgxKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHJlc29sdmVSdWxlT3B0aW9ucyhydWxlOiBSdWxlRGF0YSk6IHVua25vd25bXSB8IHVuZGVmaW5lZCB7XG4gIGlmIChydWxlLm9wdGlvbnM/Lmxlbmd0aCkge1xuICAgIHJldHVybiBydWxlLm9wdGlvbnM7XG4gIH1cbiAgcmV0dXJuIHJ1bGUubWV0YS5kZWZhdWx0T3B0aW9ucztcbn1cbiIsICJjb25zdCBfX2luamVjdGVkX2ZpbGVuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1lc2xpbnQvc3JjL2xpYi9tZXRhL3ZlcnNpb25zL2ZsYXQudHNcIjtjb25zdCBfX2luamVjdGVkX2Rpcm5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWVzbGludC9zcmMvbGliL21ldGEvdmVyc2lvbnNcIjtjb25zdCBfX2luamVjdGVkX2ltcG9ydF9tZXRhX3VybF9fID0gXCJmaWxlOi8vL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWVzbGludC9zcmMvbGliL21ldGEvdmVyc2lvbnMvZmxhdC50c1wiO2ltcG9ydCB0eXBlIHsgTGludGVyLCBSdWxlIH0gZnJvbSAnZXNsaW50JztcbmltcG9ydCB7IGJ1aWx0aW5SdWxlcyB9IGZyb20gJ2VzbGludC91c2UtYXQteW91ci1vd24tcmlzayc7XG5pbXBvcnQgcGF0aCBmcm9tICdub2RlOnBhdGgnO1xuaW1wb3J0IHsgcGF0aFRvRmlsZVVSTCB9IGZyb20gJ25vZGU6dXJsJztcbmltcG9ydCB7IGV4aXN0cywgZmluZE5lYXJlc3RGaWxlLCB0b0FycmF5LCB1aSB9IGZyb20gJ0Bjb2RlLXB1c2h1cC91dGlscyc7XG5pbXBvcnQgdHlwZSB7IEVTTGludFRhcmdldCB9IGZyb20gJy4uLy4uL2NvbmZpZy5qcyc7XG5pbXBvcnQgeyBqc29uSGFzaCB9IGZyb20gJy4uL2hhc2guanMnO1xuaW1wb3J0IHtcbiAgdHlwZSBSdWxlRGF0YSxcbiAgaXNSdWxlT2ZmLFxuICBvcHRpb25zRnJvbVJ1bGVFbnRyeSxcbiAgcGFyc2VSdWxlSWQsXG59IGZyb20gJy4uL3BhcnNlLmpzJztcblxuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGxvYWRSdWxlc0ZvckZsYXRDb25maWcoe1xuICBlc2xpbnRyYyxcbn06IFBpY2s8RVNMaW50VGFyZ2V0LCAnZXNsaW50cmMnPik6IFByb21pc2U8UnVsZURhdGFbXT4ge1xuICBjb25zdCBjb25maWcgPSBlc2xpbnRyY1xuICAgID8gYXdhaXQgbG9hZENvbmZpZ0J5UGF0aChlc2xpbnRyYylcbiAgICA6IGF3YWl0IGxvYWRDb25maWdCeURlZmF1bHRMb2NhdGlvbigpO1xuICBjb25zdCBjb25maWdzID0gdG9BcnJheShjb25maWcpO1xuXG4gIGNvbnN0IHJ1bGVzID0gZmluZEVuYWJsZWRSdWxlc1dpdGhPcHRpb25zKGNvbmZpZ3MpO1xuICByZXR1cm4gcnVsZXNcbiAgICAubWFwKHJ1bGUgPT4ge1xuICAgICAgY29uc3QgbWV0YSA9IGZpbmRSdWxlTWV0YShydWxlLmlkLCBjb25maWdzKTtcbiAgICAgIGlmICghbWV0YSkge1xuICAgICAgICB1aSgpLmxvZ2dlci53YXJuaW5nKGBDYW5ub3QgZmluZCBtZXRhZGF0YSBmb3IgcnVsZSAke3J1bGUuaWR9YCk7XG4gICAgICAgIHJldHVybiBudWxsO1xuICAgICAgfVxuICAgICAgcmV0dXJuIHsgLi4ucnVsZSwgbWV0YSB9O1xuICAgIH0pXG4gICAgLmZpbHRlcihleGlzdHMpO1xufVxuXG50eXBlIEZsYXRDb25maWcgPSBMaW50ZXIuQ29uZmlnIHwgTGludGVyLkNvbmZpZ1tdO1xuXG5hc3luYyBmdW5jdGlvbiBsb2FkQ29uZmlnQnlEZWZhdWx0TG9jYXRpb24oKTogUHJvbWlzZTxGbGF0Q29uZmlnPiB7XG4gIGNvbnN0IGZsYXRDb25maWdGaWxlTmFtZXMgPSBbXG4gICAgJ2VzbGludC5jb25maWcuanMnLFxuICAgICdlc2xpbnQuY29uZmlnLm1qcycsXG4gICAgJ2VzbGludC5jb25maWcuY2pzJyxcbiAgXTtcbiAgY29uc3QgY29uZmlnUGF0aCA9IGF3YWl0IGZpbmROZWFyZXN0RmlsZShmbGF0Q29uZmlnRmlsZU5hbWVzKTtcbiAgaWYgKGNvbmZpZ1BhdGgpIHtcbiAgICByZXR1cm4gbG9hZENvbmZpZ0J5UGF0aChjb25maWdQYXRoKTtcbiAgfVxuICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgW1xuICAgICAgYEVTTGludCBjb25maWcgZmlsZSBub3QgZm91bmQgLSBleHBlY3RlZCAke2ZsYXRDb25maWdGaWxlTmFtZXMuam9pbignLycpfSBpbiAke3Byb2Nlc3MuY3dkKCl9IG9yIHNvbWUgcGFyZW50IGRpcmVjdG9yeWAsXG4gICAgICAnSWYgeW91ciBFU0xpbnQgY29uZmlnIGlzIGluIGEgbm9uLXN0YW5kYXJkIGxvY2F0aW9uLCB1c2UgdGhlIGBlc2xpbnRyY2AgcGFyYW1ldGVyIHRvIHNwZWNpZnkgdGhlIHBhdGguJyxcbiAgICBdLmpvaW4oJ1xcbicpLFxuICApO1xufVxuXG5hc3luYyBmdW5jdGlvbiBsb2FkQ29uZmlnQnlQYXRoKGNvbmZpZ1BhdGg6IHN0cmluZyk6IFByb21pc2U8RmxhdENvbmZpZz4ge1xuICBjb25zdCBhYnNvbHV0ZVBhdGggPSBwYXRoLmlzQWJzb2x1dGUoY29uZmlnUGF0aClcbiAgICA/IGNvbmZpZ1BhdGhcbiAgICA6IHBhdGguam9pbihwcm9jZXNzLmN3ZCgpLCBjb25maWdQYXRoKTtcbiAgY29uc3QgdXJsID0gcGF0aFRvRmlsZVVSTChhYnNvbHV0ZVBhdGgpLnRvU3RyaW5nKCk7XG4gIGNvbnN0IG1vZCA9IChhd2FpdCBpbXBvcnQodXJsKSkgYXMgRmxhdENvbmZpZyB8IHsgZGVmYXVsdDogRmxhdENvbmZpZyB9O1xuICByZXR1cm4gJ2RlZmF1bHQnIGluIG1vZCA/IG1vZC5kZWZhdWx0IDogbW9kO1xufVxuXG5mdW5jdGlvbiBmaW5kRW5hYmxlZFJ1bGVzV2l0aE9wdGlvbnMoXG4gIGNvbmZpZ3M6IExpbnRlci5Db25maWdbXSxcbik6IE9taXQ8UnVsZURhdGEsICdtZXRhJz5bXSB7XG4gIGNvbnN0IGVuYWJsZWRSdWxlcyA9IGNvbmZpZ3NcbiAgICAuZmxhdE1hcCgoeyBydWxlcyB9KSA9PiBPYmplY3QuZW50cmllcyhydWxlcyA/PyB7fSkpXG4gICAgLmZpbHRlcigoWywgZW50cnldKSA9PiBlbnRyeSAhPSBudWxsICYmICFpc1J1bGVPZmYoZW50cnkpKVxuICAgIC5tYXAoKFtpZCwgZW50cnldKSA9PiAoe1xuICAgICAgaWQsXG4gICAgICBvcHRpb25zOiBlbnRyeSA/IG9wdGlvbnNGcm9tUnVsZUVudHJ5KGVudHJ5KSA6IFtdLFxuICAgIH0pKTtcbiAgY29uc3QgdW5pcXVlUnVsZXNNYXAgPSBuZXcgTWFwKFxuICAgIGVuYWJsZWRSdWxlcy5tYXAoKHsgaWQsIG9wdGlvbnMgfSkgPT4gW1xuICAgICAgYCR7aWR9Ojoke2pzb25IYXNoKG9wdGlvbnMpfWAsXG4gICAgICB7IGlkLCBvcHRpb25zIH0sXG4gICAgXSksXG4gICk7XG4gIHJldHVybiBbLi4udW5pcXVlUnVsZXNNYXAudmFsdWVzKCldO1xufVxuXG5mdW5jdGlvbiBmaW5kUnVsZU1ldGEoXG4gIHJ1bGVJZDogc3RyaW5nLFxuICBjb25maWdzOiBMaW50ZXIuQ29uZmlnW10sXG4pOiBSdWxlLlJ1bGVNZXRhRGF0YSB8IHVuZGVmaW5lZCB7XG4gIGNvbnN0IHsgcGx1Z2luLCBuYW1lIH0gPSBwYXJzZVJ1bGVJZChydWxlSWQpO1xuICBpZiAoIXBsdWdpbikge1xuICAgIHJldHVybiBmaW5kQnVpbHRpblJ1bGVNZXRhKG5hbWUpO1xuICB9XG4gIHJldHVybiBmaW5kUGx1Z2luUnVsZU1ldGEocGx1Z2luLCBuYW1lLCBjb25maWdzKTtcbn1cblxuZnVuY3Rpb24gZmluZEJ1aWx0aW5SdWxlTWV0YShuYW1lOiBzdHJpbmcpOiBSdWxlLlJ1bGVNZXRhRGF0YSB8IHVuZGVmaW5lZCB7XG4gIGNvbnN0IHJ1bGUgPSBidWlsdGluUnVsZXMuZ2V0KG5hbWUpO1xuICByZXR1cm4gcnVsZT8ubWV0YTtcbn1cblxuZnVuY3Rpb24gZmluZFBsdWdpblJ1bGVNZXRhKFxuICBwbHVnaW46IHN0cmluZyxcbiAgbmFtZTogc3RyaW5nLFxuICBjb25maWdzOiBMaW50ZXIuQ29uZmlnW10sXG4pOiBSdWxlLlJ1bGVNZXRhRGF0YSB8IHVuZGVmaW5lZCB7XG4gIGNvbnN0IGNvbmZpZyA9IGNvbmZpZ3MuZmluZCgoeyBwbHVnaW5zID0ge30gfSkgPT4gcGx1Z2luIGluIHBsdWdpbnMpO1xuICBjb25zdCBydWxlID0gY29uZmlnPy5wbHVnaW5zPy5bcGx1Z2luXT8ucnVsZXM/LltuYW1lXTtcblxuICBpZiAodHlwZW9mIHJ1bGUgPT09ICdmdW5jdGlvbicpIHtcbiAgICB1aSgpLmxvZ2dlci53YXJuaW5nKFxuICAgICAgYENhbm5vdCBwYXJzZSBtZXRhZGF0YSBmb3IgcnVsZSAke3BsdWdpbn0vJHtuYW1lfSwgcGx1Z2luIHJlZ2lzdGVycyBpdCBhcyBhIGZ1bmN0aW9uYCxcbiAgICApO1xuICAgIHJldHVybiB1bmRlZmluZWQ7XG4gIH1cblxuICByZXR1cm4gcnVsZT8ubWV0YTtcbn1cbiIsICJjb25zdCBfX2luamVjdGVkX2ZpbGVuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1lc2xpbnQvc3JjL2xpYi9tZXRhL3ZlcnNpb25zL2xlZ2FjeS50c1wiO2NvbnN0IF9faW5qZWN0ZWRfZGlybmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tZXNsaW50L3NyYy9saWIvbWV0YS92ZXJzaW9uc1wiO2NvbnN0IF9faW5qZWN0ZWRfaW1wb3J0X21ldGFfdXJsX18gPSBcImZpbGU6Ly8vaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tZXNsaW50L3NyYy9saWIvbWV0YS92ZXJzaW9ucy9sZWdhY3kudHNcIjtpbXBvcnQgdHlwZSB7IEVTTGludCwgTGludGVyIH0gZnJvbSAnZXNsaW50JztcbmltcG9ydCB7IGRpc3RpbmN0LCBleGlzdHMsIHRvQXJyYXksIHVpIH0gZnJvbSAnQGNvZGUtcHVzaHVwL3V0aWxzJztcbmltcG9ydCB0eXBlIHsgRVNMaW50VGFyZ2V0IH0gZnJvbSAnLi4vLi4vY29uZmlnLmpzJztcbmltcG9ydCB7IHNldHVwRVNMaW50IH0gZnJvbSAnLi4vLi4vc2V0dXAuanMnO1xuaW1wb3J0IHsgdHlwZSBSdWxlRGF0YSwgaXNSdWxlT2ZmLCBvcHRpb25zRnJvbVJ1bGVFbnRyeSB9IGZyb20gJy4uL3BhcnNlLmpzJztcblxuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGxvYWRSdWxlc0ZvckxlZ2FjeUNvbmZpZyh7XG4gIGVzbGludHJjLFxuICBwYXR0ZXJucyxcbn06IEVTTGludFRhcmdldCk6IFByb21pc2U8UnVsZURhdGFbXT4ge1xuICBjb25zdCBlc2xpbnQgPSBhd2FpdCBzZXR1cEVTTGludChlc2xpbnRyYyk7XG5cbiAgY29uc3QgY29uZmlncyA9IGF3YWl0IHRvQXJyYXkocGF0dGVybnMpLnJlZHVjZShcbiAgICBhc3luYyAoYWNjLCBwYXR0ZXJuKSA9PiBbXG4gICAgICAuLi4oYXdhaXQgYWNjKSxcbiAgICAgIChhd2FpdCBlc2xpbnQuY2FsY3VsYXRlQ29uZmlnRm9yRmlsZShwYXR0ZXJuKSkgYXMgTGludGVyLkxlZ2FjeUNvbmZpZyxcbiAgICBdLFxuICAgIFByb21pc2UucmVzb2x2ZTxMaW50ZXIuTGVnYWN5Q29uZmlnW10+KFtdKSxcbiAgKTtcblxuICBjb25zdCBydWxlc0lkcyA9IGRpc3RpbmN0KFxuICAgIGNvbmZpZ3MuZmxhdE1hcChjb25maWcgPT4gT2JqZWN0LmtleXMoY29uZmlnLnJ1bGVzID8/IHt9KSksXG4gICk7XG4gIGNvbnN0IHJ1bGVzTWV0YSA9IGVzbGludC5nZXRSdWxlc01ldGFGb3JSZXN1bHRzKFtcbiAgICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgQHR5cGVzY3JpcHQtZXNsaW50L2NvbnNpc3RlbnQtdHlwZS1hc3NlcnRpb25zXG4gICAge1xuICAgICAgbWVzc2FnZXM6IHJ1bGVzSWRzLm1hcChydWxlSWQgPT4gKHsgcnVsZUlkIH0pKSxcbiAgICAgIHN1cHByZXNzZWRNZXNzYWdlczogW10gYXMgTGludGVyLlN1cHByZXNzZWRMaW50TWVzc2FnZVtdLFxuICAgIH0gYXMgRVNMaW50LkxpbnRSZXN1bHQsXG4gIF0pO1xuXG4gIHJldHVybiBjb25maWdzXG4gICAgLmZsYXRNYXAoY29uZmlnID0+IE9iamVjdC5lbnRyaWVzKGNvbmZpZy5ydWxlcyA/PyB7fSkpXG4gICAgLm1hcCgoW2lkLCBlbnRyeV0pOiBSdWxlRGF0YSB8IG51bGwgPT4ge1xuICAgICAgaWYgKGVudHJ5ID09IG51bGwgfHwgaXNSdWxlT2ZmKGVudHJ5KSkge1xuICAgICAgICByZXR1cm4gbnVsbDtcbiAgICAgIH1cbiAgICAgIGNvbnN0IHJ1bGVNZXRhID0gcnVsZXNNZXRhW2lkXTtcbiAgICAgIGlmICghcnVsZU1ldGEpIHtcbiAgICAgICAgdWkoKS5sb2dnZXIud2FybmluZyhgTWV0YWRhdGEgbm90IGZvdW5kIGZvciBFU0xpbnQgcnVsZSAke2lkfWApO1xuICAgICAgICByZXR1cm4gbnVsbDtcbiAgICAgIH1cbiAgICAgIC8vIGlnbm9yaW5nIG1ldGEuZGVmYXVsdE9wdGlvbnMgdG8gbWF0Y2ggbGVnYWN5IGNvbmZpZyBoYW5kbGluZyBpbiBjYWxjdWxhdGVDb25maWdGb3JGaWxlXG4gICAgICBjb25zdCB7IGRlZmF1bHRPcHRpb25zOiBfLCAuLi5tZXRhIH0gPSBydWxlTWV0YTtcbiAgICAgIGNvbnN0IG9wdGlvbnMgPSBvcHRpb25zRnJvbVJ1bGVFbnRyeShlbnRyeSk7XG4gICAgICByZXR1cm4geyBpZCwgbWV0YSwgb3B0aW9ucyB9O1xuICAgIH0pXG4gICAgLmZpbHRlcihleGlzdHMpO1xufVxuIiwgImNvbnN0IF9faW5qZWN0ZWRfZmlsZW5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWVzbGludC9zcmMvbGliL3NldHVwLnRzXCI7Y29uc3QgX19pbmplY3RlZF9kaXJuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1lc2xpbnQvc3JjL2xpYlwiO2NvbnN0IF9faW5qZWN0ZWRfaW1wb3J0X21ldGFfdXJsX18gPSBcImZpbGU6Ly8vaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tZXNsaW50L3NyYy9saWIvc2V0dXAudHNcIjtpbXBvcnQgeyBFU0xpbnQgfSBmcm9tICdlc2xpbnQnO1xuaW1wb3J0IHR5cGUgeyBFU0xpbnRUYXJnZXQgfSBmcm9tICcuL2NvbmZpZy5qcyc7XG5cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBzZXR1cEVTTGludChlc2xpbnRyYzogRVNMaW50VGFyZ2V0Wydlc2xpbnRyYyddKSB7XG4gIGNvbnN0IGVzbGludENvbnN0cnVjdG9yID0gYXdhaXQgbG9hZEVTTGludCgpO1xuICByZXR1cm4gbmV3IGVzbGludENvbnN0cnVjdG9yKHtcbiAgICBvdmVycmlkZUNvbmZpZ0ZpbGU6IGVzbGludHJjLFxuICAgIGVycm9yT25Vbm1hdGNoZWRQYXR0ZXJuOiBmYWxzZSxcbiAgfSk7XG59XG5cbmFzeW5jIGZ1bmN0aW9uIGxvYWRFU0xpbnQoKSB7XG4gIGNvbnN0IGVzbGludCA9IGF3YWl0IGltcG9ydCgnZXNsaW50Jyk7XG4gIC8vIGxvYWRFU0xpbnQgYWRkZWQgdG8gcHVibGljIEFQSSBpbiB2OSwgc2VsZWN0cyBFU0xpbnQgb3IgTGVnYWN5RVNMaW50IGJhc2VkIG9uIGVudmlyb25tZW50XG4gIGlmICgnbG9hZEVTTGludCcgaW4gZXNsaW50ICYmIHR5cGVvZiBlc2xpbnQubG9hZEVTTGludCA9PT0gJ2Z1bmN0aW9uJykge1xuICAgIHJldHVybiAoYXdhaXQgZXNsaW50LmxvYWRFU0xpbnQoKSkgYXMgdHlwZW9mIEVTTGludDtcbiAgfVxuICByZXR1cm4gRVNMaW50O1xufVxuIiwgImNvbnN0IF9faW5qZWN0ZWRfZmlsZW5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWVzbGludC9zcmMvbGliL21ldGEvdmVyc2lvbnMvZGV0ZWN0LnRzXCI7Y29uc3QgX19pbmplY3RlZF9kaXJuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1lc2xpbnQvc3JjL2xpYi9tZXRhL3ZlcnNpb25zXCI7Y29uc3QgX19pbmplY3RlZF9pbXBvcnRfbWV0YV91cmxfXyA9IFwiZmlsZTovLy9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1lc2xpbnQvc3JjL2xpYi9tZXRhL3ZlcnNpb25zL2RldGVjdC50c1wiO2ltcG9ydCB7IEVTTGludCB9IGZyb20gJ2VzbGludCc7XG5pbXBvcnQgeyBmaWxlRXhpc3RzIH0gZnJvbSAnQGNvZGUtcHVzaHVwL3V0aWxzJztcbmltcG9ydCB0eXBlIHsgQ29uZmlnRm9ybWF0IH0gZnJvbSAnLi9mb3JtYXRzLmpzJztcblxuLy8gcmVsZXZhbnQgRVNMaW50IGRvY3M6XG4vLyAtIGh0dHBzOi8vZXNsaW50Lm9yZy9kb2NzL2xhdGVzdC91c2UvY29uZmlndXJlL2NvbmZpZ3VyYXRpb24tZmlsZXNcbi8vIC0gaHR0cHM6Ly9lc2xpbnQub3JnL2RvY3MvbGF0ZXN0L3VzZS9jb25maWd1cmUvY29uZmlndXJhdGlvbi1maWxlcy1kZXByZWNhdGVkXG4vLyAtIGh0dHBzOi8vZXNsaW50Lm9yZy9kb2NzL3Y4LngvdXNlL2NvbmZpZ3VyZS9jb25maWd1cmF0aW9uLWZpbGVzLW5ld1xuXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gZGV0ZWN0Q29uZmlnVmVyc2lvbigpOiBQcm9taXNlPENvbmZpZ0Zvcm1hdD4ge1xuICBpZiAocHJvY2Vzcy5lbnZbJ0VTTElOVF9VU0VfRkxBVF9DT05GSUcnXSA9PT0gJ3RydWUnKSB7XG4gICAgcmV0dXJuICdmbGF0JztcbiAgfVxuICBpZiAocHJvY2Vzcy5lbnZbJ0VTTElOVF9VU0VfRkxBVF9DT05GSUcnXSA9PT0gJ2ZhbHNlJykge1xuICAgIHJldHVybiAnbGVnYWN5JztcbiAgfVxuICBpZiAoRVNMaW50LnZlcnNpb24uc3RhcnRzV2l0aCgnOC4nKSkge1xuICAgIGlmIChhd2FpdCBmaWxlRXhpc3RzKCdlc2xpbnQuY29uZmlnLmpzJykpIHtcbiAgICAgIHJldHVybiAnZmxhdCc7XG4gICAgfVxuICAgIHJldHVybiAnbGVnYWN5JztcbiAgfVxuICByZXR1cm4gJ2ZsYXQnO1xufVxuIiwgImNvbnN0IF9faW5qZWN0ZWRfZmlsZW5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWVzbGludC9zcmMvbGliL21ldGEvdHJhbnNmb3JtLnRzXCI7Y29uc3QgX19pbmplY3RlZF9kaXJuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1lc2xpbnQvc3JjL2xpYi9tZXRhXCI7Y29uc3QgX19pbmplY3RlZF9pbXBvcnRfbWV0YV91cmxfXyA9IFwiZmlsZTovLy9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1lc2xpbnQvc3JjL2xpYi9tZXRhL3RyYW5zZm9ybS50c1wiO2ltcG9ydCB0eXBlIHsgQXVkaXQgfSBmcm9tICdAY29kZS1wdXNodXAvbW9kZWxzJztcbmltcG9ydCB7IHRydW5jYXRlRGVzY3JpcHRpb24sIHRydW5jYXRlVGl0bGUgfSBmcm9tICdAY29kZS1wdXNodXAvdXRpbHMnO1xuaW1wb3J0IHsgcnVsZVRvU2x1ZyB9IGZyb20gJy4vaGFzaC5qcyc7XG5pbXBvcnQgdHlwZSB7IFJ1bGVEYXRhIH0gZnJvbSAnLi9wYXJzZS5qcyc7XG5cbmV4cG9ydCBmdW5jdGlvbiBydWxlVG9BdWRpdChydWxlOiBSdWxlRGF0YSk6IEF1ZGl0IHtcbiAgY29uc3QgbmFtZSA9IHJ1bGUuaWQuc3BsaXQoJy8nKS5hdCgtMSkgPz8gcnVsZS5pZDtcbiAgY29uc3QgcGx1Z2luID1cbiAgICBuYW1lID09PSBydWxlLmlkID8gbnVsbCA6IHJ1bGUuaWQuc2xpY2UoMCwgcnVsZS5pZC5sYXN0SW5kZXhPZignLycpKTtcbiAgY29uc3QgcGx1Z2luQ29udGV4dCA9IHBsdWdpbiA/IGAsIGZyb20gXyR7cGx1Z2lufV8gcGx1Z2luYCA6ICcnO1xuXG4gIGNvbnN0IGxpbmVzOiBzdHJpbmdbXSA9IFtcbiAgICBgRVNMaW50IHJ1bGUgKioke25hbWV9Kioke3BsdWdpbkNvbnRleHR9LmAsXG4gICAgLi4uKHJ1bGUub3B0aW9ucz8ubGVuZ3RoID8gWydDdXN0b20gb3B0aW9uczonXSA6IFtdKSxcbiAgICAuLi4ocnVsZS5vcHRpb25zPy5tYXAob3B0aW9uID0+XG4gICAgICBbJ2BgYGpzb24nLCBKU09OLnN0cmluZ2lmeShvcHRpb24sIG51bGwsIDIpLCAnYGBgJ10uam9pbignXFxuJyksXG4gICAgKSA/PyBbXSksXG4gIF07XG5cbiAgcmV0dXJuIHtcbiAgICBzbHVnOiBydWxlVG9TbHVnKHJ1bGUpLFxuICAgIHRpdGxlOiB0cnVuY2F0ZVRpdGxlKHJ1bGUubWV0YS5kb2NzPy5kZXNjcmlwdGlvbiA/PyBuYW1lKSxcbiAgICBkZXNjcmlwdGlvbjogdHJ1bmNhdGVEZXNjcmlwdGlvbihsaW5lcy5qb2luKCdcXG5cXG4nKSksXG4gICAgLi4uKHJ1bGUubWV0YS5kb2NzPy51cmwgJiYge1xuICAgICAgZG9jc1VybDogcnVsZS5tZXRhLmRvY3MudXJsLFxuICAgIH0pLFxuICB9O1xufVxuIiwgImNvbnN0IF9faW5qZWN0ZWRfZmlsZW5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWVzbGludC9zcmMvbGliL3J1bm5lci9pbmRleC50c1wiO2NvbnN0IF9faW5qZWN0ZWRfZGlybmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tZXNsaW50L3NyYy9saWIvcnVubmVyXCI7Y29uc3QgX19pbmplY3RlZF9pbXBvcnRfbWV0YV91cmxfXyA9IFwiZmlsZTovLy9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1lc2xpbnQvc3JjL2xpYi9ydW5uZXIvaW5kZXgudHNcIjtpbXBvcnQgeyB3cml0ZUZpbGUgfSBmcm9tICdub2RlOmZzL3Byb21pc2VzJztcbmltcG9ydCBwYXRoIGZyb20gJ25vZGU6cGF0aCc7XG5pbXBvcnQgdHlwZSB7IEF1ZGl0LCBBdWRpdE91dHB1dCwgUnVubmVyQ29uZmlnIH0gZnJvbSAnQGNvZGUtcHVzaHVwL21vZGVscyc7XG5pbXBvcnQge1xuICBlbnN1cmVEaXJlY3RvcnlFeGlzdHMsXG4gIGZpbGVQYXRoVG9DbGlBcmcsXG4gIHBsdWdpbldvcmtEaXIsXG4gIHJlYWRKc29uRmlsZSxcbn0gZnJvbSAnQGNvZGUtcHVzaHVwL3V0aWxzJztcbmltcG9ydCB0eXBlIHsgRVNMaW50UGx1Z2luUnVubmVyQ29uZmlnLCBFU0xpbnRUYXJnZXQgfSBmcm9tICcuLi9jb25maWcuanMnO1xuaW1wb3J0IHsgbGludCB9IGZyb20gJy4vbGludC5qcyc7XG5pbXBvcnQgeyBsaW50UmVzdWx0c1RvQXVkaXRzLCBtZXJnZUxpbnRlck91dHB1dHMgfSBmcm9tICcuL3RyYW5zZm9ybS5qcyc7XG5pbXBvcnQgdHlwZSB7IExpbnRlck91dHB1dCB9IGZyb20gJy4vdHlwZXMuanMnO1xuXG5leHBvcnQgY29uc3QgV09SS0RJUiA9IHBsdWdpbldvcmtEaXIoJ2VzbGludCcpO1xuZXhwb3J0IGNvbnN0IFJVTk5FUl9PVVRQVVRfUEFUSCA9IHBhdGguam9pbihXT1JLRElSLCAncnVubmVyLW91dHB1dC5qc29uJyk7XG5leHBvcnQgY29uc3QgUExVR0lOX0NPTkZJR19QQVRIID0gcGF0aC5qb2luKFxuICBwcm9jZXNzLmN3ZCgpLFxuICBXT1JLRElSLFxuICAncGx1Z2luLWNvbmZpZy5qc29uJyxcbik7XG5cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBleGVjdXRlUnVubmVyKCk6IFByb21pc2U8dm9pZD4ge1xuICBjb25zdCB7IHNsdWdzLCB0YXJnZXRzIH0gPVxuICAgIGF3YWl0IHJlYWRKc29uRmlsZTxFU0xpbnRQbHVnaW5SdW5uZXJDb25maWc+KFBMVUdJTl9DT05GSUdfUEFUSCk7XG5cbiAgY29uc3QgbGludGVyT3V0cHV0cyA9IGF3YWl0IHRhcmdldHMucmVkdWNlKFxuICAgIGFzeW5jIChhY2MsIHRhcmdldCkgPT4gWy4uLihhd2FpdCBhY2MpLCBhd2FpdCBsaW50KHRhcmdldCldLFxuICAgIFByb21pc2UucmVzb2x2ZTxMaW50ZXJPdXRwdXRbXT4oW10pLFxuICApO1xuICBjb25zdCBsaW50UmVzdWx0cyA9IG1lcmdlTGludGVyT3V0cHV0cyhsaW50ZXJPdXRwdXRzKTtcbiAgY29uc3QgZmFpbGVkQXVkaXRzID0gbGludFJlc3VsdHNUb0F1ZGl0cyhsaW50UmVzdWx0cyk7XG5cbiAgY29uc3QgYXVkaXRzID0gc2x1Z3MubWFwKFxuICAgIChzbHVnKTogQXVkaXRPdXRwdXQgPT5cbiAgICAgIGZhaWxlZEF1ZGl0cy5maW5kKGF1ZGl0ID0+IGF1ZGl0LnNsdWcgPT09IHNsdWcpID8/IHtcbiAgICAgICAgc2x1ZyxcbiAgICAgICAgc2NvcmU6IDEsXG4gICAgICAgIHZhbHVlOiAwLFxuICAgICAgICBkaXNwbGF5VmFsdWU6ICdwYXNzZWQnLFxuICAgICAgICBkZXRhaWxzOiB7IGlzc3VlczogW10gfSxcbiAgICAgIH0sXG4gICk7XG5cbiAgYXdhaXQgZW5zdXJlRGlyZWN0b3J5RXhpc3RzKHBhdGguZGlybmFtZShSVU5ORVJfT1VUUFVUX1BBVEgpKTtcbiAgYXdhaXQgd3JpdGVGaWxlKFJVTk5FUl9PVVRQVVRfUEFUSCwgSlNPTi5zdHJpbmdpZnkoYXVkaXRzKSk7XG59XG5cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBjcmVhdGVSdW5uZXJDb25maWcoXG4gIHNjcmlwdFBhdGg6IHN0cmluZyxcbiAgYXVkaXRzOiBBdWRpdFtdLFxuICB0YXJnZXRzOiBFU0xpbnRUYXJnZXRbXSxcbik6IFByb21pc2U8UnVubmVyQ29uZmlnPiB7XG4gIGNvbnN0IGNvbmZpZzogRVNMaW50UGx1Z2luUnVubmVyQ29uZmlnID0ge1xuICAgIHRhcmdldHMsXG4gICAgc2x1Z3M6IGF1ZGl0cy5tYXAoYXVkaXQgPT4gYXVkaXQuc2x1ZyksXG4gIH07XG4gIGF3YWl0IGVuc3VyZURpcmVjdG9yeUV4aXN0cyhwYXRoLmRpcm5hbWUoUExVR0lOX0NPTkZJR19QQVRIKSk7XG4gIGF3YWl0IHdyaXRlRmlsZShQTFVHSU5fQ09ORklHX1BBVEgsIEpTT04uc3RyaW5naWZ5KGNvbmZpZykpO1xuXG4gIHJldHVybiB7XG4gICAgY29tbWFuZDogJ25vZGUnLFxuICAgIGFyZ3M6IFtmaWxlUGF0aFRvQ2xpQXJnKHNjcmlwdFBhdGgpXSxcbiAgICBvdXRwdXRGaWxlOiBSVU5ORVJfT1VUUFVUX1BBVEgsXG4gIH07XG59XG4iLCAiY29uc3QgX19pbmplY3RlZF9maWxlbmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tZXNsaW50L3NyYy9saWIvcnVubmVyL2xpbnQudHNcIjtjb25zdCBfX2luamVjdGVkX2Rpcm5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWVzbGludC9zcmMvbGliL3J1bm5lclwiO2NvbnN0IF9faW5qZWN0ZWRfaW1wb3J0X21ldGFfdXJsX18gPSBcImZpbGU6Ly8vaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tZXNsaW50L3NyYy9saWIvcnVubmVyL2xpbnQudHNcIjtpbXBvcnQgdHlwZSB7IEVTTGludCwgTGludGVyIH0gZnJvbSAnZXNsaW50JztcbmltcG9ydCB7IHBsYXRmb3JtIH0gZnJvbSAnbm9kZTpvcyc7XG5pbXBvcnQge1xuICBkaXN0aW5jdCxcbiAgZXhlY3V0ZVByb2Nlc3MsXG4gIGZpbGVQYXRoVG9DbGlBcmcsXG4gIHRvQXJyYXksXG59IGZyb20gJ0Bjb2RlLXB1c2h1cC91dGlscyc7XG5pbXBvcnQgdHlwZSB7IEVTTGludFRhcmdldCB9IGZyb20gJy4uL2NvbmZpZy5qcyc7XG5pbXBvcnQgeyBzZXR1cEVTTGludCB9IGZyb20gJy4uL3NldHVwLmpzJztcbmltcG9ydCB0eXBlIHsgTGludGVyT3V0cHV0LCBSdWxlT3B0aW9uc1BlckZpbGUgfSBmcm9tICcuL3R5cGVzLmpzJztcblxuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGxpbnQoe1xuICBlc2xpbnRyYyxcbiAgcGF0dGVybnMsXG59OiBFU0xpbnRUYXJnZXQpOiBQcm9taXNlPExpbnRlck91dHB1dD4ge1xuICBjb25zdCByZXN1bHRzID0gYXdhaXQgZXhlY3V0ZUxpbnQoeyBlc2xpbnRyYywgcGF0dGVybnMgfSk7XG4gIGNvbnN0IGVzbGludCA9IGF3YWl0IHNldHVwRVNMaW50KGVzbGludHJjKTtcbiAgY29uc3QgcnVsZU9wdGlvbnNQZXJGaWxlID0gYXdhaXQgbG9hZFJ1bGVPcHRpb25zUGVyRmlsZShlc2xpbnQsIHJlc3VsdHMpO1xuICByZXR1cm4geyByZXN1bHRzLCBydWxlT3B0aW9uc1BlckZpbGUgfTtcbn1cblxuYXN5bmMgZnVuY3Rpb24gZXhlY3V0ZUxpbnQoe1xuICBlc2xpbnRyYyxcbiAgcGF0dGVybnMsXG59OiBFU0xpbnRUYXJnZXQpOiBQcm9taXNlPEVTTGludC5MaW50UmVzdWx0W10+IHtcbiAgLy8gcnVubmluZyBhcyBDTEkgYmVjYXVzZSBFU0xpbnQjbGludEZpbGVzKCkgcnVucyBvdXQgb2YgbWVtb3J5XG4gIGNvbnN0IHsgc3Rkb3V0IH0gPSBhd2FpdCBleGVjdXRlUHJvY2Vzcyh7XG4gICAgY29tbWFuZDogJ25weCcsXG4gICAgYXJnczogW1xuICAgICAgJ2VzbGludCcsXG4gICAgICAuLi4oZXNsaW50cmMgPyBbYC0tY29uZmlnPSR7ZmlsZVBhdGhUb0NsaUFyZyhlc2xpbnRyYyl9YF0gOiBbXSksXG4gICAgICAuLi4odHlwZW9mIGVzbGludHJjID09PSAnb2JqZWN0JyA/IFsnLS1uby1lc2xpbnRyYyddIDogW10pLFxuICAgICAgJy0tbm8tZXJyb3Itb24tdW5tYXRjaGVkLXBhdHRlcm4nLFxuICAgICAgJy0tZm9ybWF0PWpzb24nLFxuICAgICAgLi4udG9BcnJheShwYXR0ZXJucykubWFwKHBhdHRlcm4gPT5cbiAgICAgICAgLy8gZ2xvYnMgbmVlZCB0byBiZSBlc2NhcGVkIG9uIFVuaXhcbiAgICAgICAgcGxhdGZvcm0oKSA9PT0gJ3dpbjMyJyA/IHBhdHRlcm4gOiBgJyR7cGF0dGVybn0nYCxcbiAgICAgICksXG4gICAgXSxcbiAgICBpZ25vcmVFeGl0Q29kZTogdHJ1ZSxcbiAgICBjd2Q6IHByb2Nlc3MuY3dkKCksXG4gIH0pO1xuXG4gIHJldHVybiBKU09OLnBhcnNlKHN0ZG91dCkgYXMgRVNMaW50LkxpbnRSZXN1bHRbXTtcbn1cblxuZnVuY3Rpb24gbG9hZFJ1bGVPcHRpb25zUGVyRmlsZShcbiAgZXNsaW50OiBFU0xpbnQsXG4gIHJlc3VsdHM6IEVTTGludC5MaW50UmVzdWx0W10sXG4pOiBQcm9taXNlPFJ1bGVPcHRpb25zUGVyRmlsZT4ge1xuICByZXR1cm4gcmVzdWx0cy5yZWR1Y2UoYXN5bmMgKGFjYywgeyBmaWxlUGF0aCwgbWVzc2FnZXMgfSkgPT4ge1xuICAgIGNvbnN0IGZpbGVzTWFwID0gYXdhaXQgYWNjO1xuICAgIGNvbnN0IGNvbmZpZyA9IChhd2FpdCBlc2xpbnQuY2FsY3VsYXRlQ29uZmlnRm9yRmlsZShcbiAgICAgIGZpbGVQYXRoLFxuICAgICkpIGFzIExpbnRlci5Db25maWc7XG4gICAgY29uc3QgcnVsZUlkcyA9IGRpc3RpbmN0KFxuICAgICAgbWVzc2FnZXNcbiAgICAgICAgLm1hcCgoeyBydWxlSWQgfSkgPT4gcnVsZUlkKVxuICAgICAgICAuZmlsdGVyKChydWxlSWQpOiBydWxlSWQgaXMgc3RyaW5nID0+IHJ1bGVJZCAhPSBudWxsKSxcbiAgICApO1xuICAgIGNvbnN0IHJ1bGVzTWFwID0gT2JqZWN0LmZyb21FbnRyaWVzKFxuICAgICAgcnVsZUlkcy5tYXAocnVsZUlkID0+IFtcbiAgICAgICAgcnVsZUlkLFxuICAgICAgICB0b0FycmF5KGNvbmZpZy5ydWxlcz8uW3J1bGVJZF0gPz8gW10pLnNsaWNlKDEpLFxuICAgICAgXSksXG4gICAgKTtcbiAgICByZXR1cm4ge1xuICAgICAgLi4uZmlsZXNNYXAsXG4gICAgICBbZmlsZVBhdGhdOiB7XG4gICAgICAgIC4uLmZpbGVzTWFwW2ZpbGVQYXRoXSxcbiAgICAgICAgLi4ucnVsZXNNYXAsXG4gICAgICB9LFxuICAgIH07XG4gIH0sIFByb21pc2UucmVzb2x2ZTxSdWxlT3B0aW9uc1BlckZpbGU+KHt9KSk7XG59XG4iLCAiY29uc3QgX19pbmplY3RlZF9maWxlbmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tZXNsaW50L3NyYy9saWIvcnVubmVyL3RyYW5zZm9ybS50c1wiO2NvbnN0IF9faW5qZWN0ZWRfZGlybmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tZXNsaW50L3NyYy9saWIvcnVubmVyXCI7Y29uc3QgX19pbmplY3RlZF9pbXBvcnRfbWV0YV91cmxfXyA9IFwiZmlsZTovLy9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1lc2xpbnQvc3JjL2xpYi9ydW5uZXIvdHJhbnNmb3JtLnRzXCI7aW1wb3J0IHR5cGUgeyBMaW50ZXIgfSBmcm9tICdlc2xpbnQnO1xuaW1wb3J0IHR5cGUgeyBBdWRpdE91dHB1dCwgSXNzdWUsIElzc3VlU2V2ZXJpdHkgfSBmcm9tICdAY29kZS1wdXNodXAvbW9kZWxzJztcbmltcG9ydCB7XG4gIGNvbXBhcmVJc3N1ZVNldmVyaXR5LFxuICBjb3VudE9jY3VycmVuY2VzLFxuICBvYmplY3RUb0VudHJpZXMsXG4gIHBsdXJhbGl6ZVRva2VuLFxuICB0cnVuY2F0ZUlzc3VlTWVzc2FnZSxcbiAgdWksXG59IGZyb20gJ0Bjb2RlLXB1c2h1cC91dGlscyc7XG5pbXBvcnQgeyBydWxlSWRUb1NsdWcgfSBmcm9tICcuLi9tZXRhL2luZGV4LmpzJztcbmltcG9ydCB0eXBlIHsgTGludGVyT3V0cHV0IH0gZnJvbSAnLi90eXBlcy5qcyc7XG5cbnR5cGUgTGludElzc3VlID0gTGludGVyLkxpbnRNZXNzYWdlICYge1xuICBmaWxlUGF0aDogc3RyaW5nO1xufTtcblxuZXhwb3J0IGZ1bmN0aW9uIG1lcmdlTGludGVyT3V0cHV0cyhvdXRwdXRzOiBMaW50ZXJPdXRwdXRbXSk6IExpbnRlck91dHB1dCB7XG4gIHJldHVybiBvdXRwdXRzLnJlZHVjZTxMaW50ZXJPdXRwdXQ+KFxuICAgIChhY2MsIHsgcmVzdWx0cywgcnVsZU9wdGlvbnNQZXJGaWxlIH0pID0+ICh7XG4gICAgICByZXN1bHRzOiBbLi4uYWNjLnJlc3VsdHMsIC4uLnJlc3VsdHNdLFxuICAgICAgcnVsZU9wdGlvbnNQZXJGaWxlOiB7IC4uLmFjYy5ydWxlT3B0aW9uc1BlckZpbGUsIC4uLnJ1bGVPcHRpb25zUGVyRmlsZSB9LFxuICAgIH0pLFxuICAgIHsgcmVzdWx0czogW10sIHJ1bGVPcHRpb25zUGVyRmlsZToge30gfSxcbiAgKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGxpbnRSZXN1bHRzVG9BdWRpdHMoe1xuICByZXN1bHRzLFxuICBydWxlT3B0aW9uc1BlckZpbGUsXG59OiBMaW50ZXJPdXRwdXQpOiBBdWRpdE91dHB1dFtdIHtcbiAgY29uc3QgaXNzdWVzUGVyQXVkaXQgPSByZXN1bHRzXG4gICAgLmZsYXRNYXAoKHsgbWVzc2FnZXMsIGZpbGVQYXRoIH0pID0+XG4gICAgICBtZXNzYWdlcy5tYXAoKG1lc3NhZ2UpOiBMaW50SXNzdWUgPT4gKHsgLi4ubWVzc2FnZSwgZmlsZVBhdGggfSkpLFxuICAgIClcbiAgICAucmVkdWNlPFJlY29yZDxzdHJpbmcsIExpbnRJc3N1ZVtdPj4oKGFjYywgaXNzdWUpID0+IHtcbiAgICAgIGNvbnN0IHsgcnVsZUlkLCBtZXNzYWdlLCBmaWxlUGF0aCB9ID0gaXNzdWU7XG4gICAgICBpZiAoIXJ1bGVJZCkge1xuICAgICAgICB1aSgpLmxvZ2dlci53YXJuaW5nKFxuICAgICAgICAgIGBFU0xpbnQgY29yZSBlcnJvciAtICR7bWVzc2FnZX0gKGZpbGU6ICR7ZmlsZVBhdGh9KWAsXG4gICAgICAgICk7XG4gICAgICAgIHJldHVybiBhY2M7XG4gICAgICB9XG4gICAgICBjb25zdCBvcHRpb25zID0gcnVsZU9wdGlvbnNQZXJGaWxlW2ZpbGVQYXRoXT8uW3J1bGVJZF0gPz8gW107XG4gICAgICBjb25zdCBhdWRpdFNsdWcgPSBydWxlSWRUb1NsdWcocnVsZUlkLCBvcHRpb25zKTtcbiAgICAgIHJldHVybiB7IC4uLmFjYywgW2F1ZGl0U2x1Z106IFsuLi4oYWNjW2F1ZGl0U2x1Z10gPz8gW10pLCBpc3N1ZV0gfTtcbiAgICB9LCB7fSk7XG5cbiAgcmV0dXJuIE9iamVjdC5lbnRyaWVzKGlzc3Vlc1BlckF1ZGl0KS5tYXAoZW50cnkgPT4gdG9BdWRpdCguLi5lbnRyeSkpO1xufVxuXG5mdW5jdGlvbiB0b0F1ZGl0KHNsdWc6IHN0cmluZywgaXNzdWVzOiBMaW50SXNzdWVbXSk6IEF1ZGl0T3V0cHV0IHtcbiAgY29uc3QgYXVkaXRJc3N1ZXMgPSBpc3N1ZXMubWFwKGNvbnZlcnRJc3N1ZSk7XG4gIGNvbnN0IHNldmVyaXR5Q291bnRzID0gY291bnRPY2N1cnJlbmNlcyhcbiAgICBhdWRpdElzc3Vlcy5tYXAoKHsgc2V2ZXJpdHkgfSkgPT4gc2V2ZXJpdHkpLFxuICApO1xuICBjb25zdCBzZXZlcml0aWVzID0gb2JqZWN0VG9FbnRyaWVzKHNldmVyaXR5Q291bnRzKTtcbiAgY29uc3Qgc3VtbWFyeVRleHQgPSBzZXZlcml0aWVzXG4gICAgLnRvU29ydGVkKChhLCBiKSA9PiAtY29tcGFyZUlzc3VlU2V2ZXJpdHkoYVswXSwgYlswXSkpXG4gICAgLm1hcCgoW3NldmVyaXR5LCBjb3VudCA9IDBdKSA9PiBwbHVyYWxpemVUb2tlbihzZXZlcml0eSwgY291bnQpKVxuICAgIC5qb2luKCcsICcpO1xuXG4gIHJldHVybiB7XG4gICAgc2x1ZyxcbiAgICBzY29yZTogTnVtYmVyKGF1ZGl0SXNzdWVzLmxlbmd0aCA9PT0gMCksXG4gICAgdmFsdWU6IGF1ZGl0SXNzdWVzLmxlbmd0aCxcbiAgICBkaXNwbGF5VmFsdWU6IHN1bW1hcnlUZXh0LFxuICAgIGRldGFpbHM6IHtcbiAgICAgIGlzc3VlczogYXVkaXRJc3N1ZXMsXG4gICAgfSxcbiAgfTtcbn1cblxuZnVuY3Rpb24gY29udmVydElzc3VlKGlzc3VlOiBMaW50SXNzdWUpOiBJc3N1ZSB7XG4gIHJldHVybiB7XG4gICAgbWVzc2FnZTogdHJ1bmNhdGVJc3N1ZU1lc3NhZ2UoaXNzdWUubWVzc2FnZSksXG4gICAgc2V2ZXJpdHk6IGNvbnZlcnRTZXZlcml0eShpc3N1ZS5zZXZlcml0eSksXG4gICAgc291cmNlOiB7XG4gICAgICBmaWxlOiBpc3N1ZS5maWxlUGF0aCxcbiAgICAgIC4uLihpc3N1ZS5saW5lID4gMCAmJiB7XG4gICAgICAgIHBvc2l0aW9uOiB7XG4gICAgICAgICAgc3RhcnRMaW5lOiBpc3N1ZS5saW5lLFxuICAgICAgICAgIC4uLihpc3N1ZS5jb2x1bW4gPiAwICYmIHsgc3RhcnRDb2x1bW46IGlzc3VlLmNvbHVtbiB9KSxcbiAgICAgICAgICAuLi4oaXNzdWUuZW5kTGluZSAmJlxuICAgICAgICAgICAgaXNzdWUuZW5kTGluZSA+IDAgJiYge1xuICAgICAgICAgICAgICBlbmRMaW5lOiBpc3N1ZS5lbmRMaW5lLFxuICAgICAgICAgICAgfSksXG4gICAgICAgICAgLi4uKGlzc3VlLmVuZENvbHVtbiAmJlxuICAgICAgICAgICAgaXNzdWUuZW5kQ29sdW1uID4gMCAmJiB7IGVuZENvbHVtbjogaXNzdWUuZW5kQ29sdW1uIH0pLFxuICAgICAgICB9LFxuICAgICAgfSksXG4gICAgfSxcbiAgfTtcbn1cblxuZnVuY3Rpb24gY29udmVydFNldmVyaXR5KHNldmVyaXR5OiBMaW50ZXIuU2V2ZXJpdHkpOiBJc3N1ZVNldmVyaXR5IHtcbiAgc3dpdGNoIChzZXZlcml0eSkge1xuICAgIGNhc2UgMjpcbiAgICAgIHJldHVybiAnZXJyb3InO1xuICAgIGNhc2UgMTpcbiAgICAgIHJldHVybiAnd2FybmluZyc7XG4gICAgY2FzZSAwOlxuICAgICAgLy8gc2hvdWxkbid0IGhhcHBlblxuICAgICAgdGhyb3cgbmV3IEVycm9yKGBVbmV4cGVjdGVkIHNldmVyaXR5ICR7c2V2ZXJpdHl9IGluIEVTTGludCByZXN1bHRzYCk7XG4gIH1cbn1cbiIsICJjb25zdCBfX2luamVjdGVkX2ZpbGVuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1lc2xpbnQvc3JjL2xpYi9ueC91dGlscy50c1wiO2NvbnN0IF9faW5qZWN0ZWRfZGlybmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tZXNsaW50L3NyYy9saWIvbnhcIjtjb25zdCBfX2luamVjdGVkX2ltcG9ydF9tZXRhX3VybF9fID0gXCJmaWxlOi8vL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWVzbGludC9zcmMvbGliL254L3V0aWxzLnRzXCI7aW1wb3J0IHR5cGUgeyBQcm9qZWN0Q29uZmlndXJhdGlvbiB9IGZyb20gJ0BueC9kZXZraXQnO1xuaW1wb3J0IHBhdGggZnJvbSAnbm9kZTpwYXRoJztcbmltcG9ydCB7IGZpbGVFeGlzdHMsIHRvQXJyYXkgfSBmcm9tICdAY29kZS1wdXNodXAvdXRpbHMnO1xuaW1wb3J0IHR5cGUgeyBDb25maWdGb3JtYXQgfSBmcm9tICcuLi9tZXRhL2luZGV4LmpzJztcblxuY29uc3QgRVNMSU5UX0NPTkZJR19FWFRFTlNJT05TOiBSZWNvcmQ8Q29uZmlnRm9ybWF0LCBzdHJpbmdbXT4gPSB7XG4gIC8vIGh0dHBzOi8vZXNsaW50Lm9yZy9kb2NzL2xhdGVzdC91c2UvY29uZmlndXJlL2NvbmZpZ3VyYXRpb24tZmlsZXMjY29uZmlndXJhdGlvbi1maWxlLWZvcm1hdHNcbiAgZmxhdDogWydqcycsICdtanMnLCAnY2pzJ10sXG4gIC8vIGh0dHBzOi8vZXNsaW50Lm9yZy9kb2NzL2xhdGVzdC91c2UvY29uZmlndXJlL2NvbmZpZ3VyYXRpb24tZmlsZXMtZGVwcmVjYXRlZFxuICBsZWdhY3k6IFsnanNvbicsICdqcycsICdjanMnLCAneW1sJywgJ3lhbWwnXSxcbn07XG5jb25zdCBFU0xJTlRfQ09ORklHX05BTUVTOiBSZWNvcmQ8Q29uZmlnRm9ybWF0LCBzdHJpbmdbXT4gPSB7XG4gIC8vIGh0dHBzOi8vZXNsaW50Lm9yZy9kb2NzL2xhdGVzdC91c2UvY29uZmlndXJlL2NvbmZpZ3VyYXRpb24tZmlsZXMjY29uZmlndXJhdGlvbi1maWxlLWZvcm1hdHNcbiAgZmxhdDogWydlc2xpbnQuY29uZmlnJ10sXG4gIC8vIGh0dHBzOi8vZXNsaW50Lm9yZy9kb2NzL2xhdGVzdC91c2UvY29uZmlndXJlL2NvbmZpZ3VyYXRpb24tZmlsZXMtZGVwcmVjYXRlZFxuICBsZWdhY3k6IFsnLmVzbGludHJjJ10sXG59O1xuXG5jb25zdCBDUF9FU0xJTlRfQ09ORklHX05BTUVTOiBSZWNvcmQ8Q29uZmlnRm9ybWF0LCBzdHJpbmdbXT4gPSB7XG4gIGZsYXQ6IFtcbiAgICAnY29kZS1wdXNodXAuZXNsaW50LmNvbmZpZycsXG4gICAgJ2VzbGludC5jb2RlLXB1c2h1cC5jb25maWcnLFxuICAgICdlc2xpbnQuY29uZmlnLmNvZGUtcHVzaHVwJyxcbiAgICAnZXNsaW50LnN0cmljdC5jb25maWcnLFxuICAgICdlc2xpbnQuY29uZmlnLnN0cmljdCcsXG4gIF0sXG4gIGxlZ2FjeTogWydjb2RlLXB1c2h1cC5lc2xpbnRyYycsICcuZXNsaW50cmMuY29kZS1wdXNodXAnLCAnLmVzbGludHJjLnN0cmljdCddLFxufTtcblxuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGZpbmRDb2RlUHVzaHVwRXNsaW50Q29uZmlnKFxuICBwcm9qZWN0OiBQcm9qZWN0Q29uZmlndXJhdGlvbixcbiAgZm9ybWF0OiBDb25maWdGb3JtYXQsXG4pOiBQcm9taXNlPHN0cmluZyB8IHVuZGVmaW5lZD4ge1xuICByZXR1cm4gZmluZFByb2plY3RGaWxlKHByb2plY3QsIHtcbiAgICBuYW1lczogQ1BfRVNMSU5UX0NPTkZJR19OQU1FU1tmb3JtYXRdLFxuICAgIGV4dGVuc2lvbnM6IEVTTElOVF9DT05GSUdfRVhURU5TSU9OU1tmb3JtYXRdLFxuICB9KTtcbn1cblxuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGZpbmRFc2xpbnRDb25maWcoXG4gIHByb2plY3Q6IFByb2plY3RDb25maWd1cmF0aW9uLFxuICBmb3JtYXQ6IENvbmZpZ0Zvcm1hdCxcbik6IFByb21pc2U8c3RyaW5nIHwgdW5kZWZpbmVkPiB7XG4gIGNvbnN0IG9wdGlvbnMgPSBwcm9qZWN0LnRhcmdldHM/LlsnbGludCddPy5vcHRpb25zIGFzXG4gICAgfCB7IGVzbGludENvbmZpZz86IHN0cmluZyB9XG4gICAgfCB1bmRlZmluZWQ7XG4gIHJldHVybiAoXG4gICAgb3B0aW9ucz8uZXNsaW50Q29uZmlnID8/XG4gICAgKGF3YWl0IGZpbmRQcm9qZWN0RmlsZShwcm9qZWN0LCB7XG4gICAgICBuYW1lczogRVNMSU5UX0NPTkZJR19OQU1FU1tmb3JtYXRdLFxuICAgICAgZXh0ZW5zaW9uczogRVNMSU5UX0NPTkZJR19FWFRFTlNJT05TW2Zvcm1hdF0sXG4gICAgfSkpXG4gICk7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBnZXRMaW50RmlsZVBhdHRlcm5zKFxuICBwcm9qZWN0OiBQcm9qZWN0Q29uZmlndXJhdGlvbixcbiAgZm9ybWF0OiBDb25maWdGb3JtYXQsXG4pOiBzdHJpbmdbXSB7XG4gIGNvbnN0IG9wdGlvbnMgPSBwcm9qZWN0LnRhcmdldHM/LlsnbGludCddPy5vcHRpb25zIGFzXG4gICAgfCB7IGxpbnRGaWxlUGF0dGVybnM/OiBzdHJpbmcgfCBzdHJpbmdbXSB9XG4gICAgfCB1bmRlZmluZWQ7XG4gIC8vIGxpbnRGaWxlUGF0dGVybnMgZGVmYXVsdHMgdG8gW1wie3Byb2plY3RSb290fVwiXSAtIGh0dHBzOi8vZ2l0aHViLmNvbS9ucndsL254L3B1bGwvMjAzMTNcbiAgY29uc3QgZGVmYXVsdFBhdHRlcm5zID1cbiAgICBmb3JtYXQgPT09ICdsZWdhY3knXG4gICAgICA/IGAke3Byb2plY3Qucm9vdH0vKiovKmAgLy8gZmlsZXMgbm90IGZvbGRlciBuZWVkZWQgZm9yIGxlZ2FjeSBiZWNhdXNlIHJ1bGVzIGRldGVjdGVkIHdpdGggRVNMaW50LmNhbGN1bGF0ZUNvbmZpZ0ZvckZpbGVcbiAgICAgIDogcHJvamVjdC5yb290O1xuICBjb25zdCBwYXR0ZXJucyA9XG4gICAgb3B0aW9ucz8ubGludEZpbGVQYXR0ZXJucyA9PSBudWxsXG4gICAgICA/IFtkZWZhdWx0UGF0dGVybnNdXG4gICAgICA6IHRvQXJyYXkob3B0aW9ucy5saW50RmlsZVBhdHRlcm5zKTtcbiAgaWYgKGZvcm1hdCA9PT0gJ2xlZ2FjeScpIHtcbiAgICByZXR1cm4gW1xuICAgICAgLi4ucGF0dGVybnMsXG4gICAgICAvLyBIQUNLOiBFU0xpbnQuY2FsY3VsYXRlQ29uZmlnRm9yRmlsZSB3b24ndCBmaW5kIHJ1bGVzIGluY2x1ZGVkIG9ubHkgZm9yIHN1YnNldHMgb2YgKi50cyB3aGVuIGdsb2JzIHVzZWRcbiAgICAgIC8vIHNvIHdlIGV4cGxpY2l0bHkgcHJvdmlkZSBhZGRpdGlvbmFsIHBhdHRlcm5zIHVzZWQgYnkgQGNvZGUtcHVzaHVwL2VzbGludC1jb25maWcgdG8gZW5zdXJlIHRob3NlIHJ1bGVzIGFyZSBpbmNsdWRlZFxuICAgICAgLy8gdGhpcyB3b3JrYXJvdW5kIGlzIG9ubHkgbmVjZXNzYXJ5IGZvciBsZWdhY3kgY29uZmlncyAocnVsZXMgYXJlIGRldGVjdGVkIG1vcmUgcmVsaWFibHkgaW4gZmxhdCBjb25maWdzKVxuICAgICAgYCR7cHJvamVjdC5yb290fS8qLnNwZWMudHNgLCAvLyBqZXN0LyogYW5kIHZpdGVzdC8qIHJ1bGVzXG4gICAgICBgJHtwcm9qZWN0LnJvb3R9LyouY3kudHNgLCAvLyBjeXByZXNzLyogcnVsZXNcbiAgICAgIGAke3Byb2plY3Qucm9vdH0vKi5zdG9yaWVzLnRzYCwgLy8gc3Rvcnlib29rLyogcnVsZXNcbiAgICAgIGAke3Byb2plY3Qucm9vdH0vLnN0b3J5Ym9vay9tYWluLnRzYCwgLy8gc3Rvcnlib29rL25vLXVuaW5zdGFsbGVkLWFkZG9ucyBydWxlXG4gICAgXTtcbiAgfVxuICByZXR1cm4gcGF0dGVybnM7XG59XG5cbmFzeW5jIGZ1bmN0aW9uIGZpbmRQcm9qZWN0RmlsZShcbiAgcHJvamVjdDogUHJvamVjdENvbmZpZ3VyYXRpb24sXG4gIGZpbGU6IHtcbiAgICBuYW1lczogc3RyaW5nW107XG4gICAgZXh0ZW5zaW9uczogc3RyaW5nW107XG4gIH0sXG4pOiBQcm9taXNlPHN0cmluZyB8IHVuZGVmaW5lZD4ge1xuICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgZnVuY3Rpb25hbC9uby1sb29wLXN0YXRlbWVudHNcbiAgZm9yIChjb25zdCBuYW1lIG9mIGZpbGUubmFtZXMpIHtcbiAgICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgZnVuY3Rpb25hbC9uby1sb29wLXN0YXRlbWVudHNcbiAgICBmb3IgKGNvbnN0IGV4dCBvZiBmaWxlLmV4dGVuc2lvbnMpIHtcbiAgICAgIGNvbnN0IGZpbGVuYW1lID0gYC4vJHtwcm9qZWN0LnJvb3R9LyR7bmFtZX0uJHtleHR9YDtcbiAgICAgIGlmIChhd2FpdCBmaWxlRXhpc3RzKHBhdGguam9pbihwcm9jZXNzLmN3ZCgpLCBmaWxlbmFtZSkpKSB7XG4gICAgICAgIHJldHVybiBmaWxlbmFtZTtcbiAgICAgIH1cbiAgICB9XG4gIH1cbiAgcmV0dXJuIHVuZGVmaW5lZDtcbn1cbiIsICJjb25zdCBfX2luamVjdGVkX2ZpbGVuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1qcy1wYWNrYWdlcy9zcmMvbGliL2pzLXBhY2thZ2VzLXBsdWdpbi50c1wiO2NvbnN0IF9faW5qZWN0ZWRfZGlybmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tanMtcGFja2FnZXMvc3JjL2xpYlwiO2NvbnN0IF9faW5qZWN0ZWRfaW1wb3J0X21ldGFfdXJsX18gPSBcImZpbGU6Ly8vaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tanMtcGFja2FnZXMvc3JjL2xpYi9qcy1wYWNrYWdlcy1wbHVnaW4udHNcIjtpbXBvcnQgeyBjcmVhdGVSZXF1aXJlIH0gZnJvbSAnbm9kZTptb2R1bGUnO1xuaW1wb3J0IHBhdGggZnJvbSAnbm9kZTpwYXRoJztcbmltcG9ydCB7IGZpbGVVUkxUb1BhdGggfSBmcm9tICdub2RlOnVybCc7XG5pbXBvcnQgdHlwZSB7IEF1ZGl0LCBHcm91cCwgUGx1Z2luQ29uZmlnIH0gZnJvbSAnQGNvZGUtcHVzaHVwL21vZGVscyc7XG5pbXBvcnQge1xuICB0eXBlIERlcGVuZGVuY3lHcm91cCxcbiAgdHlwZSBKU1BhY2thZ2VzUGx1Z2luQ29uZmlnLFxuICB0eXBlIFBhY2thZ2VDb21tYW5kLFxuICB0eXBlIFBhY2thZ2VNYW5hZ2VySWQsXG4gIGRlcGVuZGVuY3lHcm91cHMsXG59IGZyb20gJy4vY29uZmlnLmpzJztcbmltcG9ydCB7IGRlcGVuZGVuY3lEb2NzLCBkZXBlbmRlbmN5R3JvdXBXZWlnaHRzIH0gZnJvbSAnLi9jb25zdGFudHMuanMnO1xuaW1wb3J0IHsgcGFja2FnZU1hbmFnZXJzIH0gZnJvbSAnLi9wYWNrYWdlLW1hbmFnZXJzL3BhY2thZ2UtbWFuYWdlcnMuanMnO1xuaW1wb3J0IHsgY3JlYXRlUnVubmVyQ29uZmlnIH0gZnJvbSAnLi9ydW5uZXIvaW5kZXguanMnO1xuaW1wb3J0IHsgbm9ybWFsaXplQ29uZmlnIH0gZnJvbSAnLi91dGlscy5qcyc7XG5cbi8qKlxuICogSW5zdGFudGlhdGVzIENvZGUgUHVzaFVwIEpTIHBhY2thZ2VzIHBsdWdpbiBmb3IgY29yZSBjb25maWcuXG4gKlxuICogQGV4YW1wbGVcbiAqIGltcG9ydCBqc1BhY2thZ2VzUGx1Z2luIGZyb20gJ0Bjb2RlLXB1c2h1cC9qcy1wYWNrYWdlcy1wbHVnaW4nXG4gKlxuICogZXhwb3J0IGRlZmF1bHQge1xuICogICAvLyAuLi4gY29yZSBjb25maWcgLi4uXG4gKiAgIHBsdWdpbnM6IFtcbiAqICAgICAvLyAuLi4gb3RoZXIgcGx1Z2lucyAuLi5cbiAqICAgICBhd2FpdCBqc1BhY2thZ2VzUGx1Z2luKHsgcGFja2FnZU1hbmFnZXI6ICducG0nIH0pXG4gKiAgIF1cbiAqIH1cbiAqXG4gKiBAcmV0dXJucyBQbHVnaW4gY29uZmlndXJhdGlvbi5cbiAqL1xuXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24ganNQYWNrYWdlc1BsdWdpbihcbiAgY29uZmlnPzogSlNQYWNrYWdlc1BsdWdpbkNvbmZpZyxcbik6IFByb21pc2U8UGx1Z2luQ29uZmlnPiB7XG4gIGNvbnN0IHsgcGFja2FnZU1hbmFnZXIsIGNoZWNrcywgZGVwR3JvdXBzLCAuLi5qc1BhY2thZ2VzUGx1Z2luQ29uZmlnUmVzdCB9ID1cbiAgICBhd2FpdCBub3JtYWxpemVDb25maWcoY29uZmlnKTtcblxuICBjb25zdCBydW5uZXJTY3JpcHRQYXRoID0gcGF0aC5qb2luKFxuICAgIGZpbGVVUkxUb1BhdGgocGF0aC5kaXJuYW1lKGltcG9ydC5tZXRhLnVybCkpLFxuICAgICcuLicsXG4gICAgJ2Jpbi5qcycsXG4gICk7XG5cbiAgY29uc3QgcGFja2FnZUpzb24gPSBjcmVhdGVSZXF1aXJlKGltcG9ydC5tZXRhLnVybCkoXG4gICAgJy4uLy4uL3BhY2thZ2UuanNvbicsXG4gICkgYXMgdHlwZW9mIGltcG9ydCgnLi4vLi4vcGFja2FnZS5qc29uJyk7XG5cbiAgcmV0dXJuIHtcbiAgICBzbHVnOiAnanMtcGFja2FnZXMnLFxuICAgIHRpdGxlOiAnSlMgUGFja2FnZXMnLFxuICAgIGljb246IHBhY2thZ2VNYW5hZ2VyLmljb24sXG4gICAgZGVzY3JpcHRpb246XG4gICAgICAnVGhpcyBwbHVnaW4gcnVucyBhdWRpdCB0byB1bmNvdmVyIHZ1bG5lcmFiaWxpdGllcyBhbmQgbGlzdHMgb3V0ZGF0ZWQgZGVwZW5kZW5jaWVzLiBJdCBzdXBwb3J0cyBucG0sIHlhcm4gY2xhc3NpYywgeWFybiBtb2Rlcm4sIGFuZCBwbnBtIHBhY2thZ2UgbWFuYWdlcnMuJyxcbiAgICBkb2NzVXJsOiBwYWNrYWdlTWFuYWdlci5kb2NzLmhvbWVwYWdlLFxuICAgIHBhY2thZ2VOYW1lOiBwYWNrYWdlSnNvbi5uYW1lLFxuICAgIHZlcnNpb246IHBhY2thZ2VKc29uLnZlcnNpb24sXG4gICAgYXVkaXRzOiBjcmVhdGVBdWRpdHMocGFja2FnZU1hbmFnZXIuc2x1ZywgY2hlY2tzLCBkZXBHcm91cHMpLFxuICAgIGdyb3VwczogY3JlYXRlR3JvdXBzKHBhY2thZ2VNYW5hZ2VyLnNsdWcsIGNoZWNrcywgZGVwR3JvdXBzKSxcbiAgICBydW5uZXI6IGF3YWl0IGNyZWF0ZVJ1bm5lckNvbmZpZyhydW5uZXJTY3JpcHRQYXRoLCB7XG4gICAgICAuLi5qc1BhY2thZ2VzUGx1Z2luQ29uZmlnUmVzdCxcbiAgICAgIGNoZWNrcyxcbiAgICAgIHBhY2thZ2VNYW5hZ2VyOiBwYWNrYWdlTWFuYWdlci5zbHVnLFxuICAgICAgZGVwZW5kZW5jeUdyb3VwczogZGVwR3JvdXBzLFxuICAgIH0pLFxuICB9O1xufVxuXG5mdW5jdGlvbiBjcmVhdGVHcm91cHMoXG4gIGlkOiBQYWNrYWdlTWFuYWdlcklkLFxuICBjaGVja3M6IFBhY2thZ2VDb21tYW5kW10sXG4gIGRlcEdyb3VwczogRGVwZW5kZW5jeUdyb3VwW10sXG4pOiBHcm91cFtdIHtcbiAgY29uc3QgcG0gPSBwYWNrYWdlTWFuYWdlcnNbaWRdO1xuICBjb25zdCBzdXBwb3J0ZWRBdWRpdERlcEdyb3VwcyA9XG4gICAgcG0uYXVkaXQuc3VwcG9ydGVkRGVwR3JvdXBzID8/IGRlcGVuZGVuY3lHcm91cHM7XG4gIGNvbnN0IGNvbXBhdGlibGVBdWRpdERlcEdyb3VwcyA9IGRlcEdyb3Vwcy5maWx0ZXIoZ3JvdXAgPT5cbiAgICBzdXBwb3J0ZWRBdWRpdERlcEdyb3Vwcy5pbmNsdWRlcyhncm91cCksXG4gICk7XG5cbiAgY29uc3QgZ3JvdXBzOiBSZWNvcmQ8UGFja2FnZUNvbW1hbmQsIEdyb3VwPiA9IHtcbiAgICBhdWRpdDoge1xuICAgICAgc2x1ZzogYCR7cG0uc2x1Z30tYXVkaXRgLFxuICAgICAgdGl0bGU6IGAke3BtLm5hbWV9IGF1ZGl0YCxcbiAgICAgIGRlc2NyaXB0aW9uOiBgR3JvdXAgY29udGFpbmluZyAke3BtLm5hbWV9IHZ1bG5lcmFiaWxpdGllcy5gLFxuICAgICAgZG9jc1VybDogcG0uZG9jcy5hdWRpdCxcbiAgICAgIHJlZnM6IGNvbXBhdGlibGVBdWRpdERlcEdyb3Vwcy5tYXAoZGVwR3JvdXAgPT4gKHtcbiAgICAgICAgc2x1ZzogYCR7cG0uc2x1Z30tYXVkaXQtJHtkZXBHcm91cH1gLFxuICAgICAgICB3ZWlnaHQ6IGRlcGVuZGVuY3lHcm91cFdlaWdodHNbZGVwR3JvdXBdLFxuICAgICAgfSkpLFxuICAgIH0sXG4gICAgb3V0ZGF0ZWQ6IHtcbiAgICAgIHNsdWc6IGAke3BtLnNsdWd9LW91dGRhdGVkYCxcbiAgICAgIHRpdGxlOiBgJHtwbS5uYW1lfSBvdXRkYXRlZCBkZXBlbmRlbmNpZXNgLFxuICAgICAgZGVzY3JpcHRpb246IGBHcm91cCBjb250YWluaW5nIG91dGRhdGVkICR7cG0ubmFtZX0gZGVwZW5kZW5jaWVzLmAsXG4gICAgICBkb2NzVXJsOiBwbS5kb2NzLm91dGRhdGVkLFxuICAgICAgcmVmczogZGVwR3JvdXBzLm1hcChkZXBHcm91cCA9PiAoe1xuICAgICAgICBzbHVnOiBgJHtwbS5zbHVnfS1vdXRkYXRlZC0ke2RlcEdyb3VwfWAsXG4gICAgICAgIHdlaWdodDogZGVwZW5kZW5jeUdyb3VwV2VpZ2h0c1tkZXBHcm91cF0sXG4gICAgICB9KSksXG4gICAgfSxcbiAgfTtcblxuICByZXR1cm4gY2hlY2tzLm1hcChjaGVjayA9PiBncm91cHNbY2hlY2tdKTtcbn1cblxuZnVuY3Rpb24gY3JlYXRlQXVkaXRzKFxuICBpZDogUGFja2FnZU1hbmFnZXJJZCxcbiAgY2hlY2tzOiBQYWNrYWdlQ29tbWFuZFtdLFxuICBkZXBHcm91cHM6IERlcGVuZGVuY3lHcm91cFtdLFxuKTogQXVkaXRbXSB7XG4gIGNvbnN0IHsgc2x1ZyB9ID0gcGFja2FnZU1hbmFnZXJzW2lkXTtcbiAgcmV0dXJuIGNoZWNrcy5mbGF0TWFwKGNoZWNrID0+IHtcbiAgICBjb25zdCBzdXBwb3J0ZWRBdWRpdERlcEdyb3VwcyA9XG4gICAgICBwYWNrYWdlTWFuYWdlcnNbaWRdLmF1ZGl0LnN1cHBvcnRlZERlcEdyb3VwcyA/PyBkZXBlbmRlbmN5R3JvdXBzO1xuXG4gICAgY29uc3QgY29tcGF0aWJsZURlcEdyb3VwcyA9XG4gICAgICBjaGVjayA9PT0gJ2F1ZGl0J1xuICAgICAgICA/IGRlcEdyb3Vwcy5maWx0ZXIoZ3JvdXAgPT4gc3VwcG9ydGVkQXVkaXREZXBHcm91cHMuaW5jbHVkZXMoZ3JvdXApKVxuICAgICAgICA6IGRlcEdyb3VwcztcblxuICAgIHJldHVybiBjb21wYXRpYmxlRGVwR3JvdXBzLm1hcChkZXBHcm91cCA9PiAoe1xuICAgICAgc2x1ZzogYCR7c2x1Z30tJHtjaGVja30tJHtkZXBHcm91cH1gLFxuICAgICAgdGl0bGU6IGdldEF1ZGl0VGl0bGUoc2x1ZywgY2hlY2ssIGRlcEdyb3VwKSxcbiAgICAgIGRlc2NyaXB0aW9uOiBnZXRBdWRpdERlc2NyaXB0aW9uKGNoZWNrLCBkZXBHcm91cCksXG4gICAgICBkb2NzVXJsOiBkZXBlbmRlbmN5RG9jc1tkZXBHcm91cF0sXG4gICAgfSkpO1xuICB9KTtcbn1cblxuZnVuY3Rpb24gZ2V0QXVkaXRUaXRsZShcbiAgaWQ6IFBhY2thZ2VNYW5hZ2VySWQsXG4gIGNoZWNrOiBQYWNrYWdlQ29tbWFuZCxcbiAgZGVwR3JvdXA6IERlcGVuZGVuY3lHcm91cCxcbikge1xuICBjb25zdCBwbSA9IHBhY2thZ2VNYW5hZ2Vyc1tpZF07XG4gIHJldHVybiBjaGVjayA9PT0gJ2F1ZGl0J1xuICAgID8gYFZ1bG5lcmFiaWxpdGllcyBmb3IgJHtwbS5uYW1lfSAke2RlcEdyb3VwfSBkZXBlbmRlbmNpZXMuYFxuICAgIDogYE91dGRhdGVkICR7cG0ubmFtZX0gJHtkZXBHcm91cH0gZGVwZW5kZW5jaWVzLmA7XG59XG5cbmZ1bmN0aW9uIGdldEF1ZGl0RGVzY3JpcHRpb24oY2hlY2s6IFBhY2thZ2VDb21tYW5kLCBkZXBHcm91cDogRGVwZW5kZW5jeUdyb3VwKSB7XG4gIHJldHVybiBjaGVjayA9PT0gJ2F1ZGl0J1xuICAgID8gYFJ1bnMgc2VjdXJpdHkgYXVkaXQgb24gJHtkZXBHcm91cH0gZGVwZW5kZW5jaWVzLmBcbiAgICA6IGBDaGVja3MgZm9yIG91dGRhdGVkICR7ZGVwR3JvdXB9IGRlcGVuZGVuY2llc2A7XG59XG4iLCAiY29uc3QgX19pbmplY3RlZF9maWxlbmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tanMtcGFja2FnZXMvc3JjL2xpYi9jb25maWcudHNcIjtjb25zdCBfX2luamVjdGVkX2Rpcm5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWpzLXBhY2thZ2VzL3NyYy9saWJcIjtjb25zdCBfX2luamVjdGVkX2ltcG9ydF9tZXRhX3VybF9fID0gXCJmaWxlOi8vL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWpzLXBhY2thZ2VzL3NyYy9saWIvY29uZmlnLnRzXCI7aW1wb3J0IHsgeiB9IGZyb20gJ3pvZCc7XG5pbXBvcnQgeyB0eXBlIElzc3VlU2V2ZXJpdHksIGlzc3VlU2V2ZXJpdHlTY2hlbWEgfSBmcm9tICdAY29kZS1wdXNodXAvbW9kZWxzJztcbmltcG9ydCB7IGRlZmF1bHRBdWRpdExldmVsTWFwcGluZyB9IGZyb20gJy4vY29uc3RhbnRzLmpzJztcblxuZXhwb3J0IGNvbnN0IGRlcGVuZGVuY3lHcm91cHMgPSBbJ3Byb2QnLCAnZGV2JywgJ29wdGlvbmFsJ10gYXMgY29uc3Q7XG5jb25zdCBkZXBlbmRlbmN5R3JvdXBTY2hlbWEgPSB6LmVudW0oZGVwZW5kZW5jeUdyb3Vwcyk7XG5leHBvcnQgdHlwZSBEZXBlbmRlbmN5R3JvdXAgPSAodHlwZW9mIGRlcGVuZGVuY3lHcm91cHMpW251bWJlcl07XG5cbmNvbnN0IHBhY2thZ2VDb21tYW5kU2NoZW1hID0gei5lbnVtKFsnYXVkaXQnLCAnb3V0ZGF0ZWQnXSk7XG5leHBvcnQgdHlwZSBQYWNrYWdlQ29tbWFuZCA9IHouaW5mZXI8dHlwZW9mIHBhY2thZ2VDb21tYW5kU2NoZW1hPjtcblxuY29uc3QgcGFja2FnZU1hbmFnZXJJZFNjaGVtYSA9IHouZW51bShbXG4gICducG0nLFxuICAneWFybi1jbGFzc2ljJyxcbiAgJ3lhcm4tbW9kZXJuJyxcbiAgJ3BucG0nLFxuXSk7XG5leHBvcnQgdHlwZSBQYWNrYWdlTWFuYWdlcklkID0gei5pbmZlcjx0eXBlb2YgcGFja2FnZU1hbmFnZXJJZFNjaGVtYT47XG5cbmNvbnN0IHBhY2thZ2VKc29uUGF0aFNjaGVtYSA9IHpcbiAgLnVuaW9uKFtcbiAgICB6LmFycmF5KHouc3RyaW5nKCkpLm1pbigxKSxcbiAgICB6Lm9iamVjdCh7IGF1dG9TZWFyY2g6IHoubGl0ZXJhbCh0cnVlKSB9KSxcbiAgXSlcbiAgLmRlc2NyaWJlKFxuICAgICdGaWxlIHBhdGhzIHRvIHBhY2thZ2UuanNvbi4gTG9va3Mgb25seSBhdCByb290IHBhY2thZ2UuanNvbiBieSBkZWZhdWx0JyxcbiAgKVxuICAuZGVmYXVsdChbJ3BhY2thZ2UuanNvbiddKTtcblxuZXhwb3J0IHR5cGUgUGFja2FnZUpzb25QYXRocyA9IHouaW5mZXI8dHlwZW9mIHBhY2thZ2VKc29uUGF0aFNjaGVtYT47XG5cbmV4cG9ydCBjb25zdCBwYWNrYWdlQXVkaXRMZXZlbHMgPSBbXG4gICdjcml0aWNhbCcsXG4gICdoaWdoJyxcbiAgJ21vZGVyYXRlJyxcbiAgJ2xvdycsXG4gICdpbmZvJyxcbl0gYXMgY29uc3Q7XG5jb25zdCBwYWNrYWdlQXVkaXRMZXZlbFNjaGVtYSA9IHouZW51bShwYWNrYWdlQXVkaXRMZXZlbHMpO1xuZXhwb3J0IHR5cGUgUGFja2FnZUF1ZGl0TGV2ZWwgPSB6LmluZmVyPHR5cGVvZiBwYWNrYWdlQXVkaXRMZXZlbFNjaGVtYT47XG5cbmV4cG9ydCB0eXBlIEF1ZGl0U2V2ZXJpdHkgPSBSZWNvcmQ8UGFja2FnZUF1ZGl0TGV2ZWwsIElzc3VlU2V2ZXJpdHk+O1xuXG5leHBvcnQgZnVuY3Rpb24gZmlsbEF1ZGl0TGV2ZWxNYXBwaW5nKFxuICBtYXBwaW5nOiBQYXJ0aWFsPEF1ZGl0U2V2ZXJpdHk+LFxuKTogQXVkaXRTZXZlcml0eSB7XG4gIHJldHVybiB7XG4gICAgY3JpdGljYWw6IG1hcHBpbmcuY3JpdGljYWwgPz8gZGVmYXVsdEF1ZGl0TGV2ZWxNYXBwaW5nLmNyaXRpY2FsLFxuICAgIGhpZ2g6IG1hcHBpbmcuaGlnaCA/PyBkZWZhdWx0QXVkaXRMZXZlbE1hcHBpbmcuaGlnaCxcbiAgICBtb2RlcmF0ZTogbWFwcGluZy5tb2RlcmF0ZSA/PyBkZWZhdWx0QXVkaXRMZXZlbE1hcHBpbmcubW9kZXJhdGUsXG4gICAgbG93OiBtYXBwaW5nLmxvdyA/PyBkZWZhdWx0QXVkaXRMZXZlbE1hcHBpbmcubG93LFxuICAgIGluZm86IG1hcHBpbmcuaW5mbyA/PyBkZWZhdWx0QXVkaXRMZXZlbE1hcHBpbmcuaW5mbyxcbiAgfTtcbn1cblxuZXhwb3J0IGNvbnN0IGpzUGFja2FnZXNQbHVnaW5Db25maWdTY2hlbWEgPSB6Lm9iamVjdCh7XG4gIGNoZWNrczogelxuICAgIC5hcnJheShwYWNrYWdlQ29tbWFuZFNjaGVtYSwge1xuICAgICAgZGVzY3JpcHRpb246XG4gICAgICAgICdQYWNrYWdlIG1hbmFnZXIgY29tbWFuZHMgdG8gYmUgcnVuLiBEZWZhdWx0cyB0byBib3RoIGF1ZGl0IGFuZCBvdXRkYXRlZC4nLFxuICAgIH0pXG4gICAgLm1pbigxKVxuICAgIC5kZWZhdWx0KFsnYXVkaXQnLCAnb3V0ZGF0ZWQnXSksXG4gIHBhY2thZ2VNYW5hZ2VyOiBwYWNrYWdlTWFuYWdlcklkU2NoZW1hXG4gICAgLmRlc2NyaWJlKCdQYWNrYWdlIG1hbmFnZXIgdG8gYmUgdXNlZC4nKVxuICAgIC5vcHRpb25hbCgpLFxuICBkZXBlbmRlbmN5R3JvdXBzOiB6XG4gICAgLmFycmF5KGRlcGVuZGVuY3lHcm91cFNjaGVtYSlcbiAgICAubWluKDEpXG4gICAgLmRlZmF1bHQoWydwcm9kJywgJ2RldiddKSxcbiAgYXVkaXRMZXZlbE1hcHBpbmc6IHpcbiAgICAucmVjb3JkKHBhY2thZ2VBdWRpdExldmVsU2NoZW1hLCBpc3N1ZVNldmVyaXR5U2NoZW1hLCB7XG4gICAgICBkZXNjcmlwdGlvbjpcbiAgICAgICAgJ01hcHBpbmcgb2YgYXVkaXQgbGV2ZWxzIHRvIGlzc3VlIHNldmVyaXR5LiBDdXN0b20gbWFwcGluZyBvciBvdmVycmlkZXMgbWF5IGJlIGVudGVyZWQgbWFudWFsbHksIG90aGVyd2lzZSBoYXMgYSBkZWZhdWx0IHByZXNldC4nLFxuICAgIH0pXG4gICAgLmRlZmF1bHQoZGVmYXVsdEF1ZGl0TGV2ZWxNYXBwaW5nKVxuICAgIC50cmFuc2Zvcm0oZmlsbEF1ZGl0TGV2ZWxNYXBwaW5nKSxcbiAgcGFja2FnZUpzb25QYXRoczogcGFja2FnZUpzb25QYXRoU2NoZW1hLFxufSk7XG5cbmV4cG9ydCB0eXBlIEpTUGFja2FnZXNQbHVnaW5Db25maWcgPSB6LmlucHV0PFxuICB0eXBlb2YganNQYWNrYWdlc1BsdWdpbkNvbmZpZ1NjaGVtYVxuPjtcblxuZXhwb3J0IHR5cGUgRmluYWxKU1BhY2thZ2VzUGx1Z2luQ29uZmlnID0gUmVxdWlyZWQ8XG4gIHouaW5mZXI8dHlwZW9mIGpzUGFja2FnZXNQbHVnaW5Db25maWdTY2hlbWE+XG4+O1xuIiwgImNvbnN0IF9faW5qZWN0ZWRfZmlsZW5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWpzLXBhY2thZ2VzL3NyYy9saWIvY29uc3RhbnRzLnRzXCI7Y29uc3QgX19pbmplY3RlZF9kaXJuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1qcy1wYWNrYWdlcy9zcmMvbGliXCI7Y29uc3QgX19pbmplY3RlZF9pbXBvcnRfbWV0YV91cmxfXyA9IFwiZmlsZTovLy9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1qcy1wYWNrYWdlcy9zcmMvbGliL2NvbnN0YW50cy50c1wiO2ltcG9ydCB0eXBlIHsgSXNzdWVTZXZlcml0eSB9IGZyb20gJ0Bjb2RlLXB1c2h1cC9tb2RlbHMnO1xuaW1wb3J0IHR5cGUgeyBEZXBlbmRlbmN5R3JvdXAsIFBhY2thZ2VBdWRpdExldmVsIH0gZnJvbSAnLi9jb25maWcuanMnO1xuaW1wb3J0IHR5cGUgeyBEZXBlbmRlbmN5R3JvdXBMb25nIH0gZnJvbSAnLi9ydW5uZXIvb3V0ZGF0ZWQvdHlwZXMuanMnO1xuXG5leHBvcnQgY29uc3QgZGVmYXVsdEF1ZGl0TGV2ZWxNYXBwaW5nOiBSZWNvcmQ8XG4gIFBhY2thZ2VBdWRpdExldmVsLFxuICBJc3N1ZVNldmVyaXR5XG4+ID0ge1xuICBjcml0aWNhbDogJ2Vycm9yJyxcbiAgaGlnaDogJ2Vycm9yJyxcbiAgbW9kZXJhdGU6ICd3YXJuaW5nJyxcbiAgbG93OiAnd2FybmluZycsXG4gIGluZm86ICdpbmZvJyxcbn07XG5cbmV4cG9ydCBjb25zdCBkZXBlbmRlbmN5R3JvdXBUb0xvbmc6IFJlY29yZDxcbiAgRGVwZW5kZW5jeUdyb3VwLFxuICBEZXBlbmRlbmN5R3JvdXBMb25nXG4+ID0ge1xuICBwcm9kOiAnZGVwZW5kZW5jaWVzJyxcbiAgZGV2OiAnZGV2RGVwZW5kZW5jaWVzJyxcbiAgb3B0aW9uYWw6ICdvcHRpb25hbERlcGVuZGVuY2llcycsXG59O1xuXG5leHBvcnQgY29uc3QgZGVwZW5kZW5jeUdyb3VwV2VpZ2h0czogUmVjb3JkPERlcGVuZGVuY3lHcm91cCwgbnVtYmVyPiA9IHtcbiAgLyogZXNsaW50LWRpc2FibGUgQHR5cGVzY3JpcHQtZXNsaW50L25vLW1hZ2ljLW51bWJlcnMgKi9cbiAgcHJvZDogODAsXG4gIGRldjogMTUsXG4gIG9wdGlvbmFsOiA1LFxuICAvKiBlc2xpbnQtZW5hYmxlIEB0eXBlc2NyaXB0LWVzbGludC9uby1tYWdpYy1udW1iZXJzICovXG59O1xuXG5leHBvcnQgY29uc3QgZGVwZW5kZW5jeURvY3M6IFJlY29yZDxEZXBlbmRlbmN5R3JvdXAsIHN0cmluZz4gPSB7XG4gIHByb2Q6ICdodHRwczovL2NsYXNzaWMueWFybnBrZy5jb20vZG9jcy9kZXBlbmRlbmN5LXR5cGVzI3RvYy1kZXBlbmRlbmNpZXMnLFxuICBkZXY6ICdodHRwczovL2NsYXNzaWMueWFybnBrZy5jb20vZG9jcy9kZXBlbmRlbmN5LXR5cGVzI3RvYy1kZXZkZXBlbmRlbmNpZXMnLFxuICBvcHRpb25hbDpcbiAgICAnaHR0cHM6Ly9jbGFzc2ljLnlhcm5wa2cuY29tL2RvY3MvZGVwZW5kZW5jeS10eXBlcyN0b2Mtb3B0aW9uYWxkZXBlbmRlbmNpZXMnLFxufTtcbiIsICJjb25zdCBfX2luamVjdGVkX2ZpbGVuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1qcy1wYWNrYWdlcy9zcmMvbGliL3BhY2thZ2UtbWFuYWdlcnMvbnBtL25wbS50c1wiO2NvbnN0IF9faW5qZWN0ZWRfZGlybmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tanMtcGFja2FnZXMvc3JjL2xpYi9wYWNrYWdlLW1hbmFnZXJzL25wbVwiO2NvbnN0IF9faW5qZWN0ZWRfaW1wb3J0X21ldGFfdXJsX18gPSBcImZpbGU6Ly8vaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tanMtcGFja2FnZXMvc3JjL2xpYi9wYWNrYWdlLW1hbmFnZXJzL25wbS9ucG0udHNcIjtpbXBvcnQgeyBvYmplY3RUb0tleXMgfSBmcm9tICdAY29kZS1wdXNodXAvdXRpbHMnO1xuaW1wb3J0IHR5cGUgeyBEZXBlbmRlbmN5R3JvdXAgfSBmcm9tICcuLi8uLi9jb25maWcuanMnO1xuaW1wb3J0IHsgZmlsdGVyQXVkaXRSZXN1bHQgfSBmcm9tICcuLi8uLi9ydW5uZXIvdXRpbHMuanMnO1xuaW1wb3J0IHsgQ09NTU9OX0FVRElUX0FSR1MsIENPTU1PTl9PVVREQVRFRF9BUkdTIH0gZnJvbSAnLi4vY29uc3RhbnRzLmpzJztcbmltcG9ydCB0eXBlIHsgQXVkaXRSZXN1bHRzLCBQYWNrYWdlTWFuYWdlciB9IGZyb20gJy4uL3R5cGVzLmpzJztcbmltcG9ydCB7IG5wbVRvQXVkaXRSZXN1bHQgfSBmcm9tICcuL2F1ZGl0LXJlc3VsdC5qcyc7XG5pbXBvcnQgeyBucG1Ub091dGRhdGVkUmVzdWx0IH0gZnJvbSAnLi9vdXRkYXRlZC1yZXN1bHQuanMnO1xuXG5jb25zdCBucG1EZXBlbmRlbmN5T3B0aW9uczogUmVjb3JkPERlcGVuZGVuY3lHcm91cCwgc3RyaW5nW10+ID0ge1xuICBwcm9kOiBbJy0tb21pdD1kZXYnLCAnLS1vbWl0PW9wdGlvbmFsJ10sXG4gIGRldjogWyctLWluY2x1ZGU9ZGV2JywgJy0tb21pdD1vcHRpb25hbCddLFxuICBvcHRpb25hbDogWyctLWluY2x1ZGU9b3B0aW9uYWwnLCAnLS1vbWl0PWRldiddLFxufTtcblxuZXhwb3J0IGNvbnN0IG5wbVBhY2thZ2VNYW5hZ2VyOiBQYWNrYWdlTWFuYWdlciA9IHtcbiAgc2x1ZzogJ25wbScsXG4gIG5hbWU6ICdOUE0nLFxuICBjb21tYW5kOiAnbnBtJyxcbiAgaWNvbjogJ25wbScsXG4gIGRvY3M6IHtcbiAgICBob21lcGFnZTogJ2h0dHBzOi8vZG9jcy5ucG1qcy5jb20vJyxcbiAgICBhdWRpdDogJ2h0dHBzOi8vZG9jcy5ucG1qcy5jb20vY2xpL2NvbW1hbmRzL25wbS1hdWRpdCcsXG4gICAgb3V0ZGF0ZWQ6ICdodHRwczovL2RvY3MubnBtanMuY29tL2NsaS9jb21tYW5kcy9ucG0tb3V0ZGF0ZWQnLFxuICB9LFxuICBhdWRpdDoge1xuICAgIGdldENvbW1hbmRBcmdzOiBncm91cERlcCA9PiBbXG4gICAgICAuLi5DT01NT05fQVVESVRfQVJHUyxcbiAgICAgIC4uLm5wbURlcGVuZGVuY3lPcHRpb25zW2dyb3VwRGVwXSxcbiAgICAgICctLWF1ZGl0LWxldmVsPW5vbmUnLFxuICAgIF0sXG4gICAgdW5pZnlSZXN1bHQ6IG5wbVRvQXVkaXRSZXN1bHQsXG4gICAgLy8gcHJvZCBkZXBlbmRlbmNpZXMgbmVlZCB0byBiZSBmaWx0ZXJlZCBvdXQgbWFudWFsbHkgc2luY2UgdjEwXG4gICAgcG9zdFByb2Nlc3NSZXN1bHQ6IChyZXN1bHRzOiBBdWRpdFJlc3VsdHMpID0+IHtcbiAgICAgIGNvbnN0IGRlcEdyb3VwcyA9IG9iamVjdFRvS2V5cyhyZXN1bHRzKTtcbiAgICAgIGNvbnN0IGRldkZpbHRlciA9XG4gICAgICAgIHJlc3VsdHMuZGV2ICYmIHJlc3VsdHMucHJvZFxuICAgICAgICAgID8gZmlsdGVyQXVkaXRSZXN1bHQocmVzdWx0cy5kZXYsICduYW1lJywgcmVzdWx0cy5wcm9kKVxuICAgICAgICAgIDogcmVzdWx0cy5kZXY7XG4gICAgICBjb25zdCBvcHRpb25hbEZpbHRlciA9XG4gICAgICAgIHJlc3VsdHMub3B0aW9uYWwgJiYgcmVzdWx0cy5wcm9kXG4gICAgICAgICAgPyBmaWx0ZXJBdWRpdFJlc3VsdChyZXN1bHRzLm9wdGlvbmFsLCAnbmFtZScsIHJlc3VsdHMucHJvZClcbiAgICAgICAgICA6IHJlc3VsdHMub3B0aW9uYWw7XG5cbiAgICAgIHJldHVybiB7XG4gICAgICAgIC4uLihkZXBHcm91cHMuaW5jbHVkZXMoJ3Byb2QnKSAmJiB7IHByb2Q6IHJlc3VsdHMucHJvZCB9KSxcbiAgICAgICAgLi4uKGRlcEdyb3Vwcy5pbmNsdWRlcygnZGV2JykgJiYgeyBkZXY6IGRldkZpbHRlciB9KSxcbiAgICAgICAgLi4uKGRlcEdyb3Vwcy5pbmNsdWRlcygnb3B0aW9uYWwnKSAmJiB7IG9wdGlvbmFsOiBvcHRpb25hbEZpbHRlciB9KSxcbiAgICAgIH07XG4gICAgfSxcbiAgfSxcbiAgb3V0ZGF0ZWQ6IHtcbiAgICBjb21tYW5kQXJnczogWy4uLkNPTU1PTl9PVVREQVRFRF9BUkdTLCAnLS1sb25nJ10sXG4gICAgdW5pZnlSZXN1bHQ6IG5wbVRvT3V0ZGF0ZWRSZXN1bHQsXG4gIH0sXG59O1xuIiwgImNvbnN0IF9faW5qZWN0ZWRfZmlsZW5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWpzLXBhY2thZ2VzL3NyYy9saWIvcnVubmVyL3V0aWxzLnRzXCI7Y29uc3QgX19pbmplY3RlZF9kaXJuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1qcy1wYWNrYWdlcy9zcmMvbGliL3J1bm5lclwiO2NvbnN0IF9faW5qZWN0ZWRfaW1wb3J0X21ldGFfdXJsX18gPSBcImZpbGU6Ly8vaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tanMtcGFja2FnZXMvc3JjL2xpYi9ydW5uZXIvdXRpbHMudHNcIjtpbXBvcnQgcGF0aCBmcm9tICdub2RlOnBhdGgnO1xuaW1wb3J0IHtcbiAgY3Jhd2xGaWxlU3lzdGVtLFxuICBvYmplY3RGcm9tRW50cmllcyxcbiAgb2JqZWN0VG9LZXlzLFxuICByZWFkSnNvbkZpbGUsXG59IGZyb20gJ0Bjb2RlLXB1c2h1cC91dGlscyc7XG5pbXBvcnQgdHlwZSB7IEF1ZGl0UmVzdWx0LCBWdWxuZXJhYmlsaXR5IH0gZnJvbSAnLi9hdWRpdC90eXBlcy5qcyc7XG5pbXBvcnQge1xuICB0eXBlIERlcGVuZGVuY3lHcm91cExvbmcsXG4gIHR5cGUgRGVwZW5kZW5jeVRvdGFscyxcbiAgdHlwZSBQYWNrYWdlSnNvbixcbiAgZGVwZW5kZW5jeUdyb3VwTG9uZyxcbn0gZnJvbSAnLi9vdXRkYXRlZC90eXBlcy5qcyc7XG5cbmV4cG9ydCBmdW5jdGlvbiBmaWx0ZXJBdWRpdFJlc3VsdChcbiAgcmVzdWx0OiBBdWRpdFJlc3VsdCxcbiAga2V5OiBrZXlvZiBWdWxuZXJhYmlsaXR5LFxuICByZWZlcmVuY2VSZXN1bHQ/OiBBdWRpdFJlc3VsdCxcbik6IEF1ZGl0UmVzdWx0IHtcbiAgaWYgKHJlc3VsdC52dWxuZXJhYmlsaXRpZXMubGVuZ3RoID09PSAwKSB7XG4gICAgcmV0dXJuIHJlc3VsdDtcbiAgfVxuXG4gIGNvbnN0IHVuaXF1ZVJlc3VsdCA9IHJlc3VsdC52dWxuZXJhYmlsaXRpZXMucmVkdWNlPEF1ZGl0UmVzdWx0PihcbiAgICAoYWNjLCByZWYpID0+IHtcbiAgICAgIGNvbnN0IG1hdGNoUmVmZXJlbmNlID0gcmVmZXJlbmNlUmVzdWx0ID8/IGFjYztcbiAgICAgIGNvbnN0IGlzTWF0Y2ggPSBtYXRjaFJlZmVyZW5jZS52dWxuZXJhYmlsaXRpZXNcbiAgICAgICAgLm1hcCh2dWxuZXJhYmlsaXR5ID0+IHZ1bG5lcmFiaWxpdHlba2V5XSlcbiAgICAgICAgLmluY2x1ZGVzKHJlZltrZXldKTtcblxuICAgICAgaWYgKGlzTWF0Y2gpIHtcbiAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICB2dWxuZXJhYmlsaXRpZXM6IGFjYy52dWxuZXJhYmlsaXRpZXMsXG4gICAgICAgICAgc3VtbWFyeToge1xuICAgICAgICAgICAgLi4uYWNjLnN1bW1hcnksXG4gICAgICAgICAgICBbcmVmLnNldmVyaXR5XTogYWNjLnN1bW1hcnlbcmVmLnNldmVyaXR5XSAtIDEsXG4gICAgICAgICAgICB0b3RhbDogYWNjLnN1bW1hcnkudG90YWwgLSAxLFxuICAgICAgICAgIH0sXG4gICAgICAgIH07XG4gICAgICB9XG5cbiAgICAgIHJldHVybiB7XG4gICAgICAgIHZ1bG5lcmFiaWxpdGllczogWy4uLmFjYy52dWxuZXJhYmlsaXRpZXMsIHJlZl0sXG4gICAgICAgIHN1bW1hcnk6IGFjYy5zdW1tYXJ5LFxuICAgICAgfTtcbiAgICB9LFxuICAgIHsgdnVsbmVyYWJpbGl0aWVzOiBbXSwgc3VtbWFyeTogcmVzdWx0LnN1bW1hcnkgfSxcbiAgKTtcblxuICByZXR1cm4ge1xuICAgIHZ1bG5lcmFiaWxpdGllczogdW5pcXVlUmVzdWx0LnZ1bG5lcmFiaWxpdGllcyxcbiAgICBzdW1tYXJ5OiB1bmlxdWVSZXN1bHQuc3VtbWFyeSxcbiAgfTtcbn1cblxuLy8gVE9ETzogdXNlIC5naXRpZ25vcmVcbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBmaW5kQWxsUGFja2FnZUpzb24oKTogUHJvbWlzZTxzdHJpbmdbXT4ge1xuICByZXR1cm4gKFxuICAgIGF3YWl0IGNyYXdsRmlsZVN5c3RlbSh7XG4gICAgICBkaXJlY3Rvcnk6ICcuJyxcbiAgICAgIHBhdHRlcm46IC8oXnxbXFxcXC9dKXBhY2thZ2VcXC5qc29uJC8sXG4gICAgfSlcbiAgKS5maWx0ZXIoXG4gICAgZmlsZVBhdGggPT5cbiAgICAgICFmaWxlUGF0aC5zdGFydHNXaXRoKGBub2RlX21vZHVsZXMke3BhdGguc2VwfWApICYmXG4gICAgICAhZmlsZVBhdGguaW5jbHVkZXMoYCR7cGF0aC5zZXB9bm9kZV9tb2R1bGVzJHtwYXRoLnNlcH1gKSAmJlxuICAgICAgIWZpbGVQYXRoLnN0YXJ0c1dpdGgoYC5ueCR7cGF0aC5zZXB9YCksXG4gICk7XG59XG5cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBnZXRUb3RhbERlcGVuZGVuY2llcyhcbiAgcGFja2FnZUpzb25QYXRoczogc3RyaW5nW10sXG4pOiBQcm9taXNlPERlcGVuZGVuY3lUb3RhbHM+IHtcbiAgY29uc3QgcGFyc2VkRGVwcyA9IGF3YWl0IFByb21pc2UuYWxsKFxuICAgIHBhY2thZ2VKc29uUGF0aHMubWFwKHJlYWRKc29uRmlsZTxQYWNrYWdlSnNvbj4pLFxuICApO1xuXG4gIGNvbnN0IG1lcmdlZERlcHMgPSBwYXJzZWREZXBzLnJlZHVjZTxSZWNvcmQ8RGVwZW5kZW5jeUdyb3VwTG9uZywgc3RyaW5nW10+PihcbiAgICAoYWNjLCBkZXBNYXBwZXIpID0+XG4gICAgICBvYmplY3RGcm9tRW50cmllcyhcbiAgICAgICAgZGVwZW5kZW5jeUdyb3VwTG9uZy5tYXAoZ3JvdXAgPT4ge1xuICAgICAgICAgIGNvbnN0IGRlcHMgPSBkZXBNYXBwZXJbZ3JvdXBdO1xuICAgICAgICAgIHJldHVybiBbXG4gICAgICAgICAgICBncm91cCxcbiAgICAgICAgICAgIFsuLi5hY2NbZ3JvdXBdLCAuLi4oZGVwcyA9PSBudWxsID8gW10gOiBvYmplY3RUb0tleXMoZGVwcykpXSxcbiAgICAgICAgICBdO1xuICAgICAgICB9KSxcbiAgICAgICksXG4gICAgeyBkZXBlbmRlbmNpZXM6IFtdLCBkZXZEZXBlbmRlbmNpZXM6IFtdLCBvcHRpb25hbERlcGVuZGVuY2llczogW10gfSxcbiAgKTtcbiAgcmV0dXJuIG9iamVjdEZyb21FbnRyaWVzKFxuICAgIG9iamVjdFRvS2V5cyhtZXJnZWREZXBzKS5tYXAoZGVwcyA9PiBbXG4gICAgICBkZXBzLFxuICAgICAgbmV3IFNldChtZXJnZWREZXBzW2RlcHNdKS5zaXplLFxuICAgIF0pLFxuICApO1xufVxuIiwgImNvbnN0IF9faW5qZWN0ZWRfZmlsZW5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWpzLXBhY2thZ2VzL3NyYy9saWIvcGFja2FnZS1tYW5hZ2Vycy9jb25zdGFudHMudHNcIjtjb25zdCBfX2luamVjdGVkX2Rpcm5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWpzLXBhY2thZ2VzL3NyYy9saWIvcGFja2FnZS1tYW5hZ2Vyc1wiO2NvbnN0IF9faW5qZWN0ZWRfaW1wb3J0X21ldGFfdXJsX18gPSBcImZpbGU6Ly8vaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tanMtcGFja2FnZXMvc3JjL2xpYi9wYWNrYWdlLW1hbmFnZXJzL2NvbnN0YW50cy50c1wiO2V4cG9ydCBjb25zdCBDT01NT05fQVVESVRfQVJHUyA9IFsnYXVkaXQnLCAnLS1qc29uJ107XG5leHBvcnQgY29uc3QgQ09NTU9OX09VVERBVEVEX0FSR1MgPSBbJ291dGRhdGVkJywgJy0tanNvbiddO1xuIiwgImNvbnN0IF9faW5qZWN0ZWRfZmlsZW5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWpzLXBhY2thZ2VzL3NyYy9saWIvcGFja2FnZS1tYW5hZ2Vycy9ucG0vYXVkaXQtcmVzdWx0LnRzXCI7Y29uc3QgX19pbmplY3RlZF9kaXJuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1qcy1wYWNrYWdlcy9zcmMvbGliL3BhY2thZ2UtbWFuYWdlcnMvbnBtXCI7Y29uc3QgX19pbmplY3RlZF9pbXBvcnRfbWV0YV91cmxfXyA9IFwiZmlsZTovLy9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1qcy1wYWNrYWdlcy9zcmMvbGliL3BhY2thZ2UtbWFuYWdlcnMvbnBtL2F1ZGl0LXJlc3VsdC50c1wiO2ltcG9ydCB7IG9iamVjdFRvRW50cmllcyB9IGZyb20gJ0Bjb2RlLXB1c2h1cC91dGlscyc7XG5pbXBvcnQgdHlwZSB7IEF1ZGl0UmVzdWx0LCBWdWxuZXJhYmlsaXR5IH0gZnJvbSAnLi4vLi4vcnVubmVyL2F1ZGl0L3R5cGVzLmpzJztcbmltcG9ydCB0eXBlIHtcbiAgTnBtQWR2aXNvcnksXG4gIE5wbUF1ZGl0UmVzdWx0SnNvbixcbiAgTnBtRml4SW5mb3JtYXRpb24sXG4gIE5wbVZ1bG5lcmFiaWxpdGllcyxcbn0gZnJvbSAnLi90eXBlcy5qcyc7XG5cbmV4cG9ydCBmdW5jdGlvbiBucG1Ub0F1ZGl0UmVzdWx0KG91dHB1dDogc3RyaW5nKTogQXVkaXRSZXN1bHQge1xuICBjb25zdCBucG1BdWRpdCA9IEpTT04ucGFyc2Uob3V0cHV0KSBhcyBOcG1BdWRpdFJlc3VsdEpzb247XG5cbiAgY29uc3QgdnVsbmVyYWJpbGl0aWVzID0gb2JqZWN0VG9FbnRyaWVzKG5wbUF1ZGl0LnZ1bG5lcmFiaWxpdGllcykubWFwKFxuICAgIChbbmFtZSwgZGV0YWlsXSk6IFZ1bG5lcmFiaWxpdHkgPT4ge1xuICAgICAgY29uc3QgYWR2aXNvcnkgPSBucG1Ub0Fkdmlzb3J5KG5hbWUsIG5wbUF1ZGl0LnZ1bG5lcmFiaWxpdGllcyk7XG4gICAgICByZXR1cm4ge1xuICAgICAgICBuYW1lOiBuYW1lLnRvU3RyaW5nKCksXG4gICAgICAgIHNldmVyaXR5OiBkZXRhaWwuc2V2ZXJpdHksXG4gICAgICAgIHZlcnNpb25SYW5nZTogZGV0YWlsLnJhbmdlLFxuICAgICAgICBkaXJlY3REZXBlbmRlbmN5OiBkZXRhaWwuaXNEaXJlY3QgPyB0cnVlIDogKGRldGFpbC5lZmZlY3RzWzBdID8/ICcnKSxcbiAgICAgICAgZml4SW5mb3JtYXRpb246IG5wbVRvRml4SW5mb3JtYXRpb24oZGV0YWlsLmZpeEF2YWlsYWJsZSksXG4gICAgICAgIC4uLihhZHZpc29yeSAhPSBudWxsICYmIHtcbiAgICAgICAgICB0aXRsZTogYWR2aXNvcnkudGl0bGUsXG4gICAgICAgICAgdXJsOiBhZHZpc29yeS51cmwsXG4gICAgICAgIH0pLFxuICAgICAgfTtcbiAgICB9LFxuICApO1xuXG4gIHJldHVybiB7XG4gICAgdnVsbmVyYWJpbGl0aWVzLFxuICAgIHN1bW1hcnk6IG5wbUF1ZGl0Lm1ldGFkYXRhLnZ1bG5lcmFiaWxpdGllcyxcbiAgfTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIG5wbVRvRml4SW5mb3JtYXRpb24oXG4gIGZpeEF2YWlsYWJsZTogYm9vbGVhbiB8IE5wbUZpeEluZm9ybWF0aW9uLFxuKTogc3RyaW5nIHtcbiAgaWYgKHR5cGVvZiBmaXhBdmFpbGFibGUgPT09ICdib29sZWFuJykge1xuICAgIHJldHVybiBmaXhBdmFpbGFibGUgPyAnRml4IGlzIGF2YWlsYWJsZS4nIDogJyc7XG4gIH1cblxuICByZXR1cm4gYEZpeCBhdmFpbGFibGU6IFVwZGF0ZSBcXGAke2ZpeEF2YWlsYWJsZS5uYW1lfVxcYCB0byB2ZXJzaW9uICoqJHtcbiAgICBmaXhBdmFpbGFibGUudmVyc2lvblxuICB9Kioke2ZpeEF2YWlsYWJsZS5pc1NlbVZlck1ham9yID8gJyAoYnJlYWtpbmcgY2hhbmdlKS4nIDogJy4nfWA7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBucG1Ub0Fkdmlzb3J5KFxuICBuYW1lOiBzdHJpbmcsXG4gIHZ1bG5lcmFiaWxpdGllczogTnBtVnVsbmVyYWJpbGl0aWVzLFxuICBwcmV2Tm9kZXMgPSBuZXcgU2V0PHN0cmluZz4oKSxcbik6IE5wbUFkdmlzb3J5IHwgbnVsbCB7XG4gIGNvbnN0IGFkdmlzb3J5ID0gdnVsbmVyYWJpbGl0aWVzW25hbWVdPy52aWE7XG5cbiAgaWYgKFxuICAgIEFycmF5LmlzQXJyYXkoYWR2aXNvcnkpICYmXG4gICAgYWR2aXNvcnkubGVuZ3RoID4gMCAmJlxuICAgIHR5cGVvZiBhZHZpc29yeVswXSA9PT0gJ29iamVjdCdcbiAgKSB7XG4gICAgcmV0dXJuIHsgdGl0bGU6IGFkdmlzb3J5WzBdLnRpdGxlLCB1cmw6IGFkdmlzb3J5WzBdLnVybCB9O1xuICB9XG5cbiAgLy8gQ3Jvc3MtcmVmZXJlbmNlcyBhbm90aGVyIHZ1bG5lcmFiaWxpdHlcbiAgaWYgKFxuICAgIEFycmF5LmlzQXJyYXkoYWR2aXNvcnkpICYmXG4gICAgYWR2aXNvcnkubGVuZ3RoID4gMCAmJlxuICAgIGFkdmlzb3J5LmV2ZXJ5KCh2YWx1ZSk6IHZhbHVlIGlzIHN0cmluZyA9PiB0eXBlb2YgdmFsdWUgPT09ICdzdHJpbmcnKVxuICApIHtcbiAgICAvKiBlc2xpbnQtZGlzYWJsZSBmdW5jdGlvbmFsL25vLWxldCwgZnVuY3Rpb25hbC9pbW11dGFibGUtZGF0YSwgZnVuY3Rpb25hbC9uby1sb29wLXN0YXRlbWVudHMsIHByZWZlci1jb25zdCAqL1xuICAgIGxldCBhZHZpc29yeUluZm86IE5wbUFkdmlzb3J5IHwgbnVsbCA9IG51bGw7XG4gICAgbGV0IG5ld1JlZmVyZW5jZXM6IHN0cmluZ1tdID0gW107XG4gICAgbGV0IGFkdmlzb3J5SW5mb0ZvdW5kID0gZmFsc2U7XG4gICAgLyogZXNsaW50LWVuYWJsZSBmdW5jdGlvbmFsL25vLWxldCwgcHJlZmVyLWNvbnN0ICovXG5cbiAgICBmb3IgKGNvbnN0IHZpYSBvZiBhZHZpc29yeSkge1xuICAgICAgaWYgKCFwcmV2Tm9kZXMuaGFzKHZpYSkpIHtcbiAgICAgICAgbmV3UmVmZXJlbmNlcy5wdXNoKHZpYSk7XG4gICAgICB9XG4gICAgfVxuXG4gICAgd2hpbGUgKG5ld1JlZmVyZW5jZXMubGVuZ3RoID4gMCAmJiAhYWR2aXNvcnlJbmZvRm91bmQpIHtcbiAgICAgIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBAdHlwZXNjcmlwdC1lc2xpbnQvbm8tbm9uLW51bGwtYXNzZXJ0aW9uXG4gICAgICBjb25zdCByZWYgPSBuZXdSZWZlcmVuY2VzLnBvcCgpITtcbiAgICAgIHByZXZOb2Rlcy5hZGQocmVmKTtcbiAgICAgIGNvbnN0IHJlc3VsdCA9IG5wbVRvQWR2aXNvcnkocmVmLCB2dWxuZXJhYmlsaXRpZXMsIHByZXZOb2Rlcyk7XG5cbiAgICAgIGlmIChyZXN1bHQgIT0gbnVsbCkge1xuICAgICAgICBhZHZpc29yeUluZm8gPSB7IHRpdGxlOiByZXN1bHQudGl0bGUsIHVybDogcmVzdWx0LnVybCB9O1xuICAgICAgICBhZHZpc29yeUluZm9Gb3VuZCA9IHRydWU7XG4gICAgICB9XG4gICAgfVxuICAgIC8qIGVzbGludC1lbmFibGUgZnVuY3Rpb25hbC9pbW11dGFibGUtZGF0YSwgZnVuY3Rpb25hbC9uby1sb29wLXN0YXRlbWVudHMgKi9cblxuICAgIHJldHVybiBhZHZpc29yeUluZm87XG4gIH1cblxuICByZXR1cm4gbnVsbDtcbn1cbiIsICJjb25zdCBfX2luamVjdGVkX2ZpbGVuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1qcy1wYWNrYWdlcy9zcmMvbGliL3BhY2thZ2UtbWFuYWdlcnMvbnBtL291dGRhdGVkLXJlc3VsdC50c1wiO2NvbnN0IF9faW5qZWN0ZWRfZGlybmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tanMtcGFja2FnZXMvc3JjL2xpYi9wYWNrYWdlLW1hbmFnZXJzL25wbVwiO2NvbnN0IF9faW5qZWN0ZWRfaW1wb3J0X21ldGFfdXJsX18gPSBcImZpbGU6Ly8vaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tanMtcGFja2FnZXMvc3JjL2xpYi9wYWNrYWdlLW1hbmFnZXJzL25wbS9vdXRkYXRlZC1yZXN1bHQudHNcIjtpbXBvcnQgeyBvYmplY3RUb0VudHJpZXMgfSBmcm9tICdAY29kZS1wdXNodXAvdXRpbHMnO1xuaW1wb3J0IHR5cGUgeyBPdXRkYXRlZFJlc3VsdCB9IGZyb20gJy4uLy4uL3J1bm5lci9vdXRkYXRlZC90eXBlcy5qcyc7XG5pbXBvcnQgdHlwZSB7IE5wbU5vcm1hbGl6ZWRPdmVydmlldywgTnBtT3V0ZGF0ZWRSZXN1bHRKc29uIH0gZnJvbSAnLi90eXBlcy5qcyc7XG5cbmV4cG9ydCBmdW5jdGlvbiBucG1Ub091dGRhdGVkUmVzdWx0KG91dHB1dDogc3RyaW5nKTogT3V0ZGF0ZWRSZXN1bHQge1xuICBjb25zdCBucG1PdXRkYXRlZCA9IEpTT04ucGFyc2Uob3V0cHV0KSBhcyBOcG1PdXRkYXRlZFJlc3VsdEpzb247XG4gIC8vIGN1cnJlbnQgbWlnaHQgYmUgbWlzc2luZyBpbiBzb21lIGNhc2VzXG4gIC8vIGh0dHBzOi8vc3RhY2tvdmVyZmxvdy5jb20vcXVlc3Rpb25zLzQyMjY3MTAxL25wbS1vdXRkYXRlZC1jb21tYW5kLXNob3dzLW1pc3NpbmctaW4tY3VycmVudC12ZXJzaW9uXG4gIHJldHVybiBvYmplY3RUb0VudHJpZXMobnBtT3V0ZGF0ZWQpXG4gICAgLmZpbHRlcihcbiAgICAgIChlbnRyeSk6IGVudHJ5IGlzIFtzdHJpbmcsIE5wbU5vcm1hbGl6ZWRPdmVydmlld10gPT5cbiAgICAgICAgZW50cnlbMV0uY3VycmVudCAhPSBudWxsLFxuICAgIClcbiAgICAubWFwKChbbmFtZSwgb3ZlcnZpZXddKSA9PiAoe1xuICAgICAgbmFtZSxcbiAgICAgIGN1cnJlbnQ6IG92ZXJ2aWV3LmN1cnJlbnQsXG4gICAgICBsYXRlc3Q6IG92ZXJ2aWV3LmxhdGVzdCxcbiAgICAgIHR5cGU6IG92ZXJ2aWV3LnR5cGUsXG4gICAgICAuLi4ob3ZlcnZpZXcuaG9tZXBhZ2UgIT0gbnVsbCAmJiB7IHVybDogb3ZlcnZpZXcuaG9tZXBhZ2UgfSksXG4gICAgfSkpO1xufVxuIiwgImNvbnN0IF9faW5qZWN0ZWRfZmlsZW5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWpzLXBhY2thZ2VzL3NyYy9saWIvcGFja2FnZS1tYW5hZ2Vycy9wbnBtL3BucG0udHNcIjtjb25zdCBfX2luamVjdGVkX2Rpcm5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWpzLXBhY2thZ2VzL3NyYy9saWIvcGFja2FnZS1tYW5hZ2Vycy9wbnBtXCI7Y29uc3QgX19pbmplY3RlZF9pbXBvcnRfbWV0YV91cmxfXyA9IFwiZmlsZTovLy9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1qcy1wYWNrYWdlcy9zcmMvbGliL3BhY2thZ2UtbWFuYWdlcnMvcG5wbS9wbnBtLnRzXCI7aW1wb3J0IHsgb2JqZWN0VG9LZXlzIH0gZnJvbSAnQGNvZGUtcHVzaHVwL3V0aWxzJztcbmltcG9ydCB0eXBlIHsgRGVwZW5kZW5jeUdyb3VwIH0gZnJvbSAnLi4vLi4vY29uZmlnLmpzJztcbmltcG9ydCB7IGZpbHRlckF1ZGl0UmVzdWx0IH0gZnJvbSAnLi4vLi4vcnVubmVyL3V0aWxzLmpzJztcbmltcG9ydCB7IENPTU1PTl9BVURJVF9BUkdTLCBDT01NT05fT1VUREFURURfQVJHUyB9IGZyb20gJy4uL2NvbnN0YW50cy5qcyc7XG5pbXBvcnQgdHlwZSB7IEF1ZGl0UmVzdWx0cywgUGFja2FnZU1hbmFnZXIgfSBmcm9tICcuLi90eXBlcy5qcyc7XG5pbXBvcnQgeyBwbnBtVG9BdWRpdFJlc3VsdCB9IGZyb20gJy4vYXVkaXQtcmVzdWx0LmpzJztcbmltcG9ydCB7IHBucG1Ub091dGRhdGVkUmVzdWx0IH0gZnJvbSAnLi9vdXRkYXRlZC1yZXN1bHQuanMnO1xuXG5jb25zdCBwbnBtRGVwZW5kZW5jeU9wdGlvbnM6IFJlY29yZDxEZXBlbmRlbmN5R3JvdXAsIHN0cmluZ1tdPiA9IHtcbiAgcHJvZDogWyctLXByb2QnLCAnLS1uby1vcHRpb25hbCddLFxuICBkZXY6IFsnLS1kZXYnLCAnLS1uby1vcHRpb25hbCddLFxuICBvcHRpb25hbDogW10sXG59O1xuXG5leHBvcnQgY29uc3QgcG5wbVBhY2thZ2VNYW5hZ2VyOiBQYWNrYWdlTWFuYWdlciA9IHtcbiAgc2x1ZzogJ3BucG0nLFxuICBuYW1lOiAncG5wbScsXG4gIGNvbW1hbmQ6ICdwbnBtJyxcbiAgaWNvbjogJ3BucG0nLFxuICBkb2NzOiB7XG4gICAgaG9tZXBhZ2U6ICdodHRwczovL3BucG0uaW8vcG5wbS1jbGknLFxuICAgIGF1ZGl0OiAnaHR0cHM6Ly9wbnBtLmlvL2NsaS9hdWRpdC8nLFxuICAgIG91dGRhdGVkOiAnaHR0cHM6Ly9wbnBtLmlvL2NsaS9vdXRkYXRlZCcsXG4gIH0sXG4gIGF1ZGl0OiB7XG4gICAgZ2V0Q29tbWFuZEFyZ3M6IGdyb3VwRGVwID0+IFtcbiAgICAgIC4uLkNPTU1PTl9BVURJVF9BUkdTLFxuICAgICAgLi4ucG5wbURlcGVuZGVuY3lPcHRpb25zW2dyb3VwRGVwXSxcbiAgICBdLFxuICAgIGlnbm9yZUV4aXRDb2RlOiB0cnVlLFxuICAgIHVuaWZ5UmVzdWx0OiBwbnBtVG9BdWRpdFJlc3VsdCxcbiAgICAvLyBvcHRpb25hbCBkZXBlbmRlbmNpZXMgZG9uJ3QgaGF2ZSBhbiBleGNsdXNpdmUgb3B0aW9uIHNvIHRoZXkgbmVlZCBkdXBsaWNhdGVzIGZpbHRlcmVkIG91dFxuICAgIHBvc3RQcm9jZXNzUmVzdWx0OiAocmVzdWx0czogQXVkaXRSZXN1bHRzKSA9PiB7XG4gICAgICBjb25zdCBkZXBHcm91cHMgPSBvYmplY3RUb0tleXMocmVzdWx0cyk7XG4gICAgICBjb25zdCBwcm9kRmlsdGVyID1cbiAgICAgICAgcmVzdWx0cy5vcHRpb25hbCAmJiByZXN1bHRzLnByb2RcbiAgICAgICAgICA/IGZpbHRlckF1ZGl0UmVzdWx0KHJlc3VsdHMub3B0aW9uYWwsICdpZCcsIHJlc3VsdHMucHJvZClcbiAgICAgICAgICA6IHJlc3VsdHMub3B0aW9uYWw7XG4gICAgICBjb25zdCBkZXZGaWx0ZXIgPVxuICAgICAgICBwcm9kRmlsdGVyICYmIHJlc3VsdHMuZGV2XG4gICAgICAgICAgPyBmaWx0ZXJBdWRpdFJlc3VsdChwcm9kRmlsdGVyLCAnaWQnLCByZXN1bHRzLmRldilcbiAgICAgICAgICA6IHJlc3VsdHMub3B0aW9uYWw7XG5cbiAgICAgIHJldHVybiB7XG4gICAgICAgIC4uLihkZXBHcm91cHMuaW5jbHVkZXMoJ3Byb2QnKSAmJiB7IHByb2Q6IHJlc3VsdHMucHJvZCB9KSxcbiAgICAgICAgLi4uKGRlcEdyb3Vwcy5pbmNsdWRlcygnZGV2JykgJiYgeyBkZXY6IHJlc3VsdHMuZGV2IH0pLFxuICAgICAgICAuLi4ocmVzdWx0cy5vcHRpb25hbCAmJiB7IG9wdGlvbmFsOiBkZXZGaWx0ZXIgfSksXG4gICAgICB9O1xuICAgIH0sXG4gIH0sXG4gIG91dGRhdGVkOiB7XG4gICAgY29tbWFuZEFyZ3M6IENPTU1PTl9PVVREQVRFRF9BUkdTLFxuICAgIHVuaWZ5UmVzdWx0OiBwbnBtVG9PdXRkYXRlZFJlc3VsdCxcbiAgfSxcbn07XG4iLCAiY29uc3QgX19pbmplY3RlZF9maWxlbmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tanMtcGFja2FnZXMvc3JjL2xpYi9wYWNrYWdlLW1hbmFnZXJzL3BucG0vb3V0ZGF0ZWQtcmVzdWx0LnRzXCI7Y29uc3QgX19pbmplY3RlZF9kaXJuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1qcy1wYWNrYWdlcy9zcmMvbGliL3BhY2thZ2UtbWFuYWdlcnMvcG5wbVwiO2NvbnN0IF9faW5qZWN0ZWRfaW1wb3J0X21ldGFfdXJsX18gPSBcImZpbGU6Ly8vaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tanMtcGFja2FnZXMvc3JjL2xpYi9wYWNrYWdlLW1hbmFnZXJzL3BucG0vb3V0ZGF0ZWQtcmVzdWx0LnRzXCI7aW1wb3J0IHsgb2JqZWN0VG9FbnRyaWVzIH0gZnJvbSAnQGNvZGUtcHVzaHVwL3V0aWxzJztcbmltcG9ydCB0eXBlIHsgT3V0ZGF0ZWRSZXN1bHQgfSBmcm9tICcuLi8uLi9ydW5uZXIvb3V0ZGF0ZWQvdHlwZXMuanMnO1xuaW1wb3J0IHR5cGUgeyBQbnBtT3V0ZGF0ZWRSZXN1bHRKc29uIH0gZnJvbSAnLi90eXBlcy5qcyc7XG5pbXBvcnQgeyBmaWx0ZXJPdXRXYXJuaW5ncyB9IGZyb20gJy4vdXRpbHMuanMnO1xuXG5leHBvcnQgZnVuY3Rpb24gcG5wbVRvT3V0ZGF0ZWRSZXN1bHQob3V0cHV0OiBzdHJpbmcpOiBPdXRkYXRlZFJlc3VsdCB7XG4gIGNvbnN0IHBucG1PdXRkYXRlZCA9IEpTT04ucGFyc2UoXG4gICAgZmlsdGVyT3V0V2FybmluZ3Mob3V0cHV0KSxcbiAgKSBhcyBQbnBtT3V0ZGF0ZWRSZXN1bHRKc29uO1xuXG4gIHJldHVybiBvYmplY3RUb0VudHJpZXMocG5wbU91dGRhdGVkKS5tYXAoXG4gICAgKFtuYW1lLCB7IGN1cnJlbnQsIGxhdGVzdCwgZGVwZW5kZW5jeVR5cGU6IHR5cGUgfV0pID0+ICh7XG4gICAgICBuYW1lLFxuICAgICAgY3VycmVudCxcbiAgICAgIGxhdGVzdCxcbiAgICAgIHR5cGUsXG4gICAgfSksXG4gICk7XG59XG4iLCAiY29uc3QgX19pbmplY3RlZF9maWxlbmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tanMtcGFja2FnZXMvc3JjL2xpYi9wYWNrYWdlLW1hbmFnZXJzL3lhcm4tY2xhc3NpYy9hdWRpdC1yZXN1bHQudHNcIjtjb25zdCBfX2luamVjdGVkX2Rpcm5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWpzLXBhY2thZ2VzL3NyYy9saWIvcGFja2FnZS1tYW5hZ2Vycy95YXJuLWNsYXNzaWNcIjtjb25zdCBfX2luamVjdGVkX2ltcG9ydF9tZXRhX3VybF9fID0gXCJmaWxlOi8vL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWpzLXBhY2thZ2VzL3NyYy9saWIvcGFja2FnZS1tYW5hZ2Vycy95YXJuLWNsYXNzaWMvYXVkaXQtcmVzdWx0LnRzXCI7aW1wb3J0IHsgZnJvbUpzb25MaW5lcyB9IGZyb20gJ0Bjb2RlLXB1c2h1cC91dGlscyc7XG5pbXBvcnQgdHlwZSB7IEF1ZGl0UmVzdWx0LCBWdWxuZXJhYmlsaXR5IH0gZnJvbSAnLi4vLi4vcnVubmVyL2F1ZGl0L3R5cGVzLmpzJztcbmltcG9ydCB7IGZpbHRlckF1ZGl0UmVzdWx0IH0gZnJvbSAnLi4vLi4vcnVubmVyL3V0aWxzLmpzJztcbmltcG9ydCB0eXBlIHtcbiAgWWFybnYxQXVkaXRBZHZpc29yeSxcbiAgWWFybnYxQXVkaXRSZXN1bHRKc29uLFxuICBZYXJudjFBdWRpdFN1bW1hcnksXG59IGZyb20gJy4vdHlwZXMuanMnO1xuXG5leHBvcnQgZnVuY3Rpb24geWFybnYxVG9BdWRpdFJlc3VsdChvdXRwdXQ6IHN0cmluZyk6IEF1ZGl0UmVzdWx0IHtcbiAgY29uc3QgeWFybnYxUmVzdWx0ID0gZnJvbUpzb25MaW5lczxZYXJudjFBdWRpdFJlc3VsdEpzb24+KG91dHB1dCk7XG4gIGNvbnN0IFt5YXJudjFBZHZpc29yeSwgeWFybnYxU3VtbWFyeV0gPSB2YWxpZGF0ZVlhcm52MVJlc3VsdCh5YXJudjFSZXN1bHQpO1xuXG4gIGNvbnN0IHZ1bG5lcmFiaWxpdGllcyA9IHlhcm52MUFkdmlzb3J5Lm1hcChcbiAgICAoeyBkYXRhOiB7IHJlc29sdXRpb24sIGFkdmlzb3J5IH0gfSk6IFZ1bG5lcmFiaWxpdHkgPT4ge1xuICAgICAgY29uc3QgeyBpZCwgcGF0aCB9ID0gcmVzb2x1dGlvbjtcbiAgICAgIGNvbnN0IGRpcmVjdERlcGVuZGVuY3kgPSBwYXRoLnNsaWNlKDAsIHBhdGguaW5kZXhPZignPicpKTtcblxuICAgICAgY29uc3Qge1xuICAgICAgICBtb2R1bGVfbmFtZTogbmFtZSxcbiAgICAgICAgdGl0bGUsXG4gICAgICAgIHVybCxcbiAgICAgICAgc2V2ZXJpdHksXG4gICAgICAgIHZ1bG5lcmFibGVfdmVyc2lvbnM6IHZlcnNpb25SYW5nZSxcbiAgICAgICAgcmVjb21tZW5kYXRpb246IGZpeEluZm9ybWF0aW9uLFxuICAgICAgfSA9IGFkdmlzb3J5O1xuXG4gICAgICByZXR1cm4ge1xuICAgICAgICBuYW1lLFxuICAgICAgICB0aXRsZSxcbiAgICAgICAgaWQsXG4gICAgICAgIHVybCxcbiAgICAgICAgc2V2ZXJpdHksXG4gICAgICAgIHZlcnNpb25SYW5nZSxcbiAgICAgICAgZGlyZWN0RGVwZW5kZW5jeTogbmFtZSA9PT0gZGlyZWN0RGVwZW5kZW5jeSA/IHRydWUgOiBkaXJlY3REZXBlbmRlbmN5LFxuICAgICAgICBmaXhJbmZvcm1hdGlvbixcbiAgICAgIH07XG4gICAgfSxcbiAgKTtcblxuICBjb25zdCBzdW1tYXJ5ID0ge1xuICAgIC4uLnlhcm52MVN1bW1hcnkuZGF0YS52dWxuZXJhYmlsaXRpZXMsXG4gICAgdG90YWw6IE9iamVjdC52YWx1ZXMoeWFybnYxU3VtbWFyeS5kYXRhLnZ1bG5lcmFiaWxpdGllcykucmVkdWNlKFxuICAgICAgKGFjYywgYW1vdW50KSA9PiBhY2MgKyBhbW91bnQsXG4gICAgICAwLFxuICAgICksXG4gIH07XG5cbiAgLy8gZHVwbGljYXRlcyBhcmUgZmlsdGVyZWQgb3V0IGJhc2VkIG9uIHRoZWlyIElEXG4gIHJldHVybiBmaWx0ZXJBdWRpdFJlc3VsdCh7IHZ1bG5lcmFiaWxpdGllcywgc3VtbWFyeSB9LCAnaWQnKTtcbn1cblxuZnVuY3Rpb24gdmFsaWRhdGVZYXJudjFSZXN1bHQoXG4gIHJlc3VsdDogWWFybnYxQXVkaXRSZXN1bHRKc29uLFxuKTogW1lhcm52MUF1ZGl0QWR2aXNvcnlbXSwgWWFybnYxQXVkaXRTdW1tYXJ5XSB7XG4gIGNvbnN0IHN1bW1hcnkgPSByZXN1bHQuYXQoLTEpO1xuICBpZiAoc3VtbWFyeT8udHlwZSAhPT0gJ2F1ZGl0U3VtbWFyeScpIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoJ0ludmFsaWQgWWFybiB2MSBhdWRpdCByZXN1bHQgLSBubyBzdW1tYXJ5IGZvdW5kLicpO1xuICB9XG5cbiAgY29uc3QgdnVsbmVyYWJpbGl0aWVzID0gcmVzdWx0LmZpbHRlcihcbiAgICAoaXRlbSk6IGl0ZW0gaXMgWWFybnYxQXVkaXRBZHZpc29yeSA9PiBpdGVtLnR5cGUgPT09ICdhdWRpdEFkdmlzb3J5JyxcbiAgKTtcblxuICByZXR1cm4gW3Z1bG5lcmFiaWxpdGllcywgc3VtbWFyeV07XG59XG4iLCAiY29uc3QgX19pbmplY3RlZF9maWxlbmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tanMtcGFja2FnZXMvc3JjL2xpYi9wYWNrYWdlLW1hbmFnZXJzL3lhcm4tY2xhc3NpYy9vdXRkYXRlZC1yZXN1bHQudHNcIjtjb25zdCBfX2luamVjdGVkX2Rpcm5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWpzLXBhY2thZ2VzL3NyYy9saWIvcGFja2FnZS1tYW5hZ2Vycy95YXJuLWNsYXNzaWNcIjtjb25zdCBfX2luamVjdGVkX2ltcG9ydF9tZXRhX3VybF9fID0gXCJmaWxlOi8vL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWpzLXBhY2thZ2VzL3NyYy9saWIvcGFja2FnZS1tYW5hZ2Vycy95YXJuLWNsYXNzaWMvb3V0ZGF0ZWQtcmVzdWx0LnRzXCI7aW1wb3J0IHtcbiAgZnJvbUpzb25MaW5lcyxcbiAgb2JqZWN0RnJvbUVudHJpZXMsXG4gIG9iamVjdFRvRW50cmllcyxcbiAgb2JqZWN0VG9LZXlzLFxufSBmcm9tICdAY29kZS1wdXNodXAvdXRpbHMnO1xuaW1wb3J0IHR5cGUge1xuICBPdXRkYXRlZERlcGVuZGVuY3ksXG4gIE91dGRhdGVkUmVzdWx0LFxufSBmcm9tICcuLi8uLi9ydW5uZXIvb3V0ZGF0ZWQvdHlwZXMuanMnO1xuaW1wb3J0IHtcbiAgUkVRVUlSRURfT1VUREFURURfRklFTERTLFxuICBvdXRkYXRlZHRvRmllbGRNYXBwZXIsXG59IGZyb20gJy4vY29uc3RhbnRzLmpzJztcbmltcG9ydCB7XG4gIHR5cGUgWWFybnYxRmllbGROYW1lLFxuICB0eXBlIFlhcm52MU91dGRhdGVkUmVzdWx0SnNvbixcbiAgeWFybnYxRmllbGROYW1lcyxcbn0gZnJvbSAnLi90eXBlcy5qcyc7XG5cbmV4cG9ydCBmdW5jdGlvbiB5YXJudjFUb091dGRhdGVkUmVzdWx0KG91dHB1dDogc3RyaW5nKTogT3V0ZGF0ZWRSZXN1bHQge1xuICBjb25zdCB5YXJudjFPdXRkYXRlZCA9IGZyb21Kc29uTGluZXM8WWFybnYxT3V0ZGF0ZWRSZXN1bHRKc29uPihvdXRwdXQpO1xuICBjb25zdCBmaWVsZHMgPSB5YXJudjFPdXRkYXRlZFsxXS5kYXRhLmhlYWQ7XG4gIGNvbnN0IGRlcGVuZGVuY2llcyA9IHlhcm52MU91dGRhdGVkWzFdLmRhdGEuYm9keTtcblxuICAvLyBubyBvdXRkYXRlZCBkZXBlbmRlbmNpZXNcbiAgaWYgKGRlcGVuZGVuY2llcy5sZW5ndGggPT09IDApIHtcbiAgICByZXR1cm4gW107XG4gIH1cblxuICAvLyBtYXAgZHluYW1pYyBmaWVsZHNcbiAgdmFsaWRhdGVPdXRkYXRlZEZpZWxkcyhmaWVsZHMpO1xuICBjb25zdCBpbmRleE1hcHBpbmcgPSBnZXRPdXRkYXRlZEZpZWxkSW5kZXhlcyhmaWVsZHMpO1xuXG4gIHJldHVybiBkZXBlbmRlbmNpZXMubWFwKFxuICAgIGRlcCA9PlxuICAgICAgb2JqZWN0RnJvbUVudHJpZXMoXG4gICAgICAgIG9iamVjdFRvS2V5cyhpbmRleE1hcHBpbmcpXG4gICAgICAgICAgLm1hcChmaWVsZCA9PiBbZmllbGQsIGRlcFtpbmRleE1hcHBpbmdbZmllbGRdXV0gYXMgY29uc3QpXG4gICAgICAgICAgLmZpbHRlcihcbiAgICAgICAgICAgIChlbnRyeSk6IGVudHJ5IGlzIFtrZXlvZiBPdXRkYXRlZERlcGVuZGVuY3ksIHN0cmluZ10gPT5cbiAgICAgICAgICAgICAgZW50cnlbMV0gIT0gbnVsbCxcbiAgICAgICAgICApLFxuICAgICAgKSBhcyBPdXRkYXRlZERlcGVuZGVuY3ksXG4gICk7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiB2YWxpZGF0ZU91dGRhdGVkRmllbGRzKGhlYWQ6IHN0cmluZ1tdKSB7XG4gIGNvbnN0IHJlbGV2YW50RmllbGRzID0gaGVhZC5maWx0ZXIoaXNZYXJudjFGaWVsZE5hbWUpO1xuICBpZiAoaGFzQWxsUmVxdWlyZWRGaWVsZHMocmVsZXZhbnRGaWVsZHMpKSB7XG4gICAgcmV0dXJuIHRydWU7XG4gIH1cblxuICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgYFlhcm4gdjEgb3V0ZGF0ZWQ6IFRlbXBsYXRlIFske2hlYWQuam9pbihcbiAgICAgICcsICcsXG4gICAgKX1dIGRvZXMgbm90IGNvbnRhaW4gYWxsIHJlcXVpcmVkIGZpZWxkcyBbJHt5YXJudjFGaWVsZE5hbWVzLmpvaW4oJywgJyl9XWAsXG4gICk7XG59XG5cbmZ1bmN0aW9uIGlzWWFybnYxRmllbGROYW1lKHZhbHVlOiBzdHJpbmcpOiB2YWx1ZSBpcyBZYXJudjFGaWVsZE5hbWUge1xuICBjb25zdCBuYW1lczogcmVhZG9ubHkgc3RyaW5nW10gPSB5YXJudjFGaWVsZE5hbWVzO1xuICByZXR1cm4gbmFtZXMuaW5jbHVkZXModmFsdWUpO1xufVxuXG5mdW5jdGlvbiBoYXNBbGxSZXF1aXJlZEZpZWxkcyhoZWFkOiBZYXJudjFGaWVsZE5hbWVbXSkge1xuICByZXR1cm4gUkVRVUlSRURfT1VUREFURURfRklFTERTLmV2ZXJ5KGZpZWxkID0+IGhlYWQuaW5jbHVkZXMoZmllbGQpKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGdldE91dGRhdGVkRmllbGRJbmRleGVzKGFsbDogc3RyaW5nW10pIHtcbiAgcmV0dXJuIG9iamVjdEZyb21FbnRyaWVzKFxuICAgIG9iamVjdFRvRW50cmllcyhvdXRkYXRlZHRvRmllbGRNYXBwZXIpLm1hcCgoW291dGRhdGVkRmllbGQsIHlhcm5GaWVsZF0pID0+IFtcbiAgICAgIG91dGRhdGVkRmllbGQsXG4gICAgICBhbGwuaW5kZXhPZih5YXJuRmllbGQpLFxuICAgIF0pLFxuICApO1xufVxuIiwgImNvbnN0IF9faW5qZWN0ZWRfZmlsZW5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWpzLXBhY2thZ2VzL3NyYy9saWIvcnVubmVyL2luZGV4LnRzXCI7Y29uc3QgX19pbmplY3RlZF9kaXJuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1qcy1wYWNrYWdlcy9zcmMvbGliL3J1bm5lclwiO2NvbnN0IF9faW5qZWN0ZWRfaW1wb3J0X21ldGFfdXJsX18gPSBcImZpbGU6Ly8vaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tanMtcGFja2FnZXMvc3JjL2xpYi9ydW5uZXIvaW5kZXgudHNcIjtpbXBvcnQgeyB3cml0ZUZpbGUgfSBmcm9tICdub2RlOmZzL3Byb21pc2VzJztcbmltcG9ydCBwYXRoIGZyb20gJ25vZGU6cGF0aCc7XG5pbXBvcnQgdHlwZSB7IFJ1bm5lckNvbmZpZyB9IGZyb20gJ0Bjb2RlLXB1c2h1cC9tb2RlbHMnO1xuaW1wb3J0IHtcbiAgZW5zdXJlRGlyZWN0b3J5RXhpc3RzLFxuICBleGVjdXRlUHJvY2VzcyxcbiAgZmlsZVBhdGhUb0NsaUFyZyxcbiAgaXNQcm9taXNlRnVsZmlsbGVkUmVzdWx0LFxuICBpc1Byb21pc2VSZWplY3RlZFJlc3VsdCxcbiAgb2JqZWN0RnJvbUVudHJpZXMsXG4gIHJlYWRKc29uRmlsZSxcbn0gZnJvbSAnQGNvZGUtcHVzaHVwL3V0aWxzJztcbmltcG9ydCB7XG4gIHR5cGUgQXVkaXRTZXZlcml0eSxcbiAgdHlwZSBEZXBlbmRlbmN5R3JvdXAsXG4gIHR5cGUgRmluYWxKU1BhY2thZ2VzUGx1Z2luQ29uZmlnLFxuICB0eXBlIFBhY2thZ2VKc29uUGF0aHMsXG4gIHR5cGUgUGFja2FnZU1hbmFnZXJJZCxcbiAgZGVwZW5kZW5jeUdyb3Vwcyxcbn0gZnJvbSAnLi4vY29uZmlnLmpzJztcbmltcG9ydCB7IGRlcGVuZGVuY3lHcm91cFRvTG9uZyB9IGZyb20gJy4uL2NvbnN0YW50cy5qcyc7XG5pbXBvcnQgeyBwYWNrYWdlTWFuYWdlcnMgfSBmcm9tICcuLi9wYWNrYWdlLW1hbmFnZXJzL3BhY2thZ2UtbWFuYWdlcnMuanMnO1xuaW1wb3J0IHsgYXVkaXRSZXN1bHRUb0F1ZGl0T3V0cHV0IH0gZnJvbSAnLi9hdWRpdC90cmFuc2Zvcm0uanMnO1xuaW1wb3J0IHR5cGUgeyBBdWRpdFJlc3VsdCB9IGZyb20gJy4vYXVkaXQvdHlwZXMuanMnO1xuaW1wb3J0IHsgUExVR0lOX0NPTkZJR19QQVRILCBSVU5ORVJfT1VUUFVUX1BBVEggfSBmcm9tICcuL2NvbnN0YW50cy5qcyc7XG5pbXBvcnQgeyBvdXRkYXRlZFJlc3VsdFRvQXVkaXRPdXRwdXQgfSBmcm9tICcuL291dGRhdGVkL3RyYW5zZm9ybS5qcyc7XG5pbXBvcnQgeyBmaW5kQWxsUGFja2FnZUpzb24sIGdldFRvdGFsRGVwZW5kZW5jaWVzIH0gZnJvbSAnLi91dGlscy5qcyc7XG5cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBjcmVhdGVSdW5uZXJDb25maWcoXG4gIHNjcmlwdFBhdGg6IHN0cmluZyxcbiAgY29uZmlnOiBGaW5hbEpTUGFja2FnZXNQbHVnaW5Db25maWcsXG4pOiBQcm9taXNlPFJ1bm5lckNvbmZpZz4ge1xuICBhd2FpdCBlbnN1cmVEaXJlY3RvcnlFeGlzdHMocGF0aC5kaXJuYW1lKFBMVUdJTl9DT05GSUdfUEFUSCkpO1xuICBhd2FpdCB3cml0ZUZpbGUoUExVR0lOX0NPTkZJR19QQVRILCBKU09OLnN0cmluZ2lmeShjb25maWcpKTtcblxuICByZXR1cm4ge1xuICAgIGNvbW1hbmQ6ICdub2RlJyxcbiAgICBhcmdzOiBbZmlsZVBhdGhUb0NsaUFyZyhzY3JpcHRQYXRoKV0sXG4gICAgb3V0cHV0RmlsZTogUlVOTkVSX09VVFBVVF9QQVRILFxuICB9O1xufVxuXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gZXhlY3V0ZVJ1bm5lcigpOiBQcm9taXNlPHZvaWQ+IHtcbiAgY29uc3Qge1xuICAgIHBhY2thZ2VNYW5hZ2VyLFxuICAgIGNoZWNrcyxcbiAgICBhdWRpdExldmVsTWFwcGluZyxcbiAgICBwYWNrYWdlSnNvblBhdGhzLFxuICAgIGRlcGVuZGVuY3lHcm91cHM6IGRlcEdyb3VwcyxcbiAgfSA9IGF3YWl0IHJlYWRKc29uRmlsZTxGaW5hbEpTUGFja2FnZXNQbHVnaW5Db25maWc+KFBMVUdJTl9DT05GSUdfUEFUSCk7XG5cbiAgY29uc3QgYXVkaXRSZXN1bHRzID0gY2hlY2tzLmluY2x1ZGVzKCdhdWRpdCcpXG4gICAgPyBhd2FpdCBwcm9jZXNzQXVkaXQocGFja2FnZU1hbmFnZXIsIGRlcEdyb3VwcywgYXVkaXRMZXZlbE1hcHBpbmcpXG4gICAgOiBbXTtcblxuICBjb25zdCBvdXRkYXRlZFJlc3VsdHMgPSBjaGVja3MuaW5jbHVkZXMoJ291dGRhdGVkJylcbiAgICA/IGF3YWl0IHByb2Nlc3NPdXRkYXRlZChwYWNrYWdlTWFuYWdlciwgZGVwR3JvdXBzLCBwYWNrYWdlSnNvblBhdGhzKVxuICAgIDogW107XG4gIGNvbnN0IGNoZWNrUmVzdWx0cyA9IFsuLi5hdWRpdFJlc3VsdHMsIC4uLm91dGRhdGVkUmVzdWx0c107XG5cbiAgYXdhaXQgZW5zdXJlRGlyZWN0b3J5RXhpc3RzKHBhdGguZGlybmFtZShSVU5ORVJfT1VUUFVUX1BBVEgpKTtcbiAgYXdhaXQgd3JpdGVGaWxlKFJVTk5FUl9PVVRQVVRfUEFUSCwgSlNPTi5zdHJpbmdpZnkoY2hlY2tSZXN1bHRzKSk7XG59XG5cbmFzeW5jIGZ1bmN0aW9uIHByb2Nlc3NPdXRkYXRlZChcbiAgaWQ6IFBhY2thZ2VNYW5hZ2VySWQsXG4gIGRlcEdyb3VwczogRGVwZW5kZW5jeUdyb3VwW10sXG4gIHBhY2thZ2VKc29uUGF0aHM6IFBhY2thZ2VKc29uUGF0aHMsXG4pIHtcbiAgY29uc3QgcG0gPSBwYWNrYWdlTWFuYWdlcnNbaWRdO1xuICBjb25zdCB7IHN0ZG91dCwgc3RkZXJyIH0gPSBhd2FpdCBleGVjdXRlUHJvY2Vzcyh7XG4gICAgY29tbWFuZDogcG0uY29tbWFuZCxcbiAgICBhcmdzOiBwbS5vdXRkYXRlZC5jb21tYW5kQXJncyxcbiAgICBjd2Q6IHByb2Nlc3MuY3dkKCksXG4gICAgaWdub3JlRXhpdENvZGU6IHRydWUsIC8vIG91dGRhdGVkIHJldHVybnMgZXhpdCBjb2RlIDEgd2hlbiBvdXRkYXRlZCBkZXBlbmRlbmNpZXMgYXJlIGZvdW5kXG4gIH0pO1xuXG4gIC8vIFN1Y2Nlc3NmdWwgb3V0ZGF0ZWQgY2hlY2sgaGFzIGVtcHR5IHN0ZGVyclxuICBpZiAoc3RkZXJyKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKGBKUyBwYWNrYWdlcyBwbHVnaW46IG91dGRhdGVkIGVycm9yOiAke3N0ZGVycn1gKTtcbiAgfVxuXG4gIC8vIExvY2F0ZSBhbGwgcGFja2FnZS5qc29uIGZpbGVzIGluIHRoZSByZXBvc2l0b3J5IGlmIG5vdCBwcm92aWRlZFxuICBjb25zdCBmaW5hbFBhdGhzID0gQXJyYXkuaXNBcnJheShwYWNrYWdlSnNvblBhdGhzKVxuICAgID8gcGFja2FnZUpzb25QYXRoc1xuICAgIDogYXdhaXQgZmluZEFsbFBhY2thZ2VKc29uKCk7XG4gIGNvbnN0IGRlcFRvdGFscyA9IGF3YWl0IGdldFRvdGFsRGVwZW5kZW5jaWVzKGZpbmFsUGF0aHMpO1xuXG4gIGNvbnN0IG5vcm1hbGl6ZWRSZXN1bHQgPSBwbS5vdXRkYXRlZC51bmlmeVJlc3VsdChzdGRvdXQpO1xuICByZXR1cm4gZGVwR3JvdXBzLm1hcChkZXBHcm91cCA9PlxuICAgIG91dGRhdGVkUmVzdWx0VG9BdWRpdE91dHB1dChcbiAgICAgIG5vcm1hbGl6ZWRSZXN1bHQsXG4gICAgICBpZCxcbiAgICAgIGRlcEdyb3VwLFxuICAgICAgZGVwVG90YWxzW2RlcGVuZGVuY3lHcm91cFRvTG9uZ1tkZXBHcm91cF1dLFxuICAgICksXG4gICk7XG59XG5cbmFzeW5jIGZ1bmN0aW9uIHByb2Nlc3NBdWRpdChcbiAgaWQ6IFBhY2thZ2VNYW5hZ2VySWQsXG4gIGRlcEdyb3VwczogRGVwZW5kZW5jeUdyb3VwW10sXG4gIGF1ZGl0TGV2ZWxNYXBwaW5nOiBBdWRpdFNldmVyaXR5LFxuKSB7XG4gIGNvbnN0IHBtID0gcGFja2FnZU1hbmFnZXJzW2lkXTtcbiAgY29uc3Qgc3VwcG9ydGVkQXVkaXREZXBHcm91cHMgPVxuICAgIHBtLmF1ZGl0LnN1cHBvcnRlZERlcEdyb3VwcyA/PyBkZXBlbmRlbmN5R3JvdXBzO1xuICBjb25zdCBjb21wYXRpYmxlQXVkaXREZXBHcm91cHMgPSBkZXBHcm91cHMuZmlsdGVyKGdyb3VwID0+XG4gICAgc3VwcG9ydGVkQXVkaXREZXBHcm91cHMuaW5jbHVkZXMoZ3JvdXApLFxuICApO1xuXG4gIGNvbnN0IGF1ZGl0UmVzdWx0cyA9IGF3YWl0IFByb21pc2UuYWxsU2V0dGxlZChcbiAgICBjb21wYXRpYmxlQXVkaXREZXBHcm91cHMubWFwKFxuICAgICAgYXN5bmMgKGRlcEdyb3VwKTogUHJvbWlzZTxbRGVwZW5kZW5jeUdyb3VwLCBBdWRpdFJlc3VsdF0+ID0+IHtcbiAgICAgICAgY29uc3QgeyBzdGRvdXQsIHN0ZGVyciB9ID0gYXdhaXQgZXhlY3V0ZVByb2Nlc3Moe1xuICAgICAgICAgIGNvbW1hbmQ6IHBtLmNvbW1hbmQsXG4gICAgICAgICAgYXJnczogcG0uYXVkaXQuZ2V0Q29tbWFuZEFyZ3MoZGVwR3JvdXApLFxuICAgICAgICAgIGN3ZDogcHJvY2Vzcy5jd2QoKSxcbiAgICAgICAgICBpZ25vcmVFeGl0Q29kZTogcG0uYXVkaXQuaWdub3JlRXhpdENvZGUsXG4gICAgICAgIH0pO1xuICAgICAgICAvLyBTdWNjZXNzZnVsIGF1ZGl0IGNoZWNrIGhhcyBlbXB0eSBzdGRlcnJcbiAgICAgICAgaWYgKHN0ZGVycikge1xuICAgICAgICAgIHRocm93IG5ldyBFcnJvcihgSlMgcGFja2FnZXMgcGx1Z2luOiBhdWRpdCBlcnJvcjogJHtzdGRlcnJ9YCk7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIFtkZXBHcm91cCwgcG0uYXVkaXQudW5pZnlSZXN1bHQoc3Rkb3V0KV07XG4gICAgICB9LFxuICAgICksXG4gICk7XG5cbiAgY29uc3QgcmVqZWN0ZWQgPSBhdWRpdFJlc3VsdHMuZmlsdGVyKGlzUHJvbWlzZVJlamVjdGVkUmVzdWx0KTtcbiAgaWYgKHJlamVjdGVkLmxlbmd0aCA+IDApIHtcbiAgICByZWplY3RlZC5mb3JFYWNoKHJlc3VsdCA9PiB7XG4gICAgICBjb25zb2xlLmVycm9yKHJlc3VsdC5yZWFzb24pO1xuICAgIH0pO1xuXG4gICAgdGhyb3cgbmV3IEVycm9yKGBKUyBQYWNrYWdlcyBwbHVnaW46IFJ1bm5pbmcgJHtwbS5uYW1lfSBhdWRpdCBmYWlsZWQuYCk7XG4gIH1cblxuICBjb25zdCBmdWxmaWxsZWQgPSBvYmplY3RGcm9tRW50cmllcyhcbiAgICBhdWRpdFJlc3VsdHMuZmlsdGVyKGlzUHJvbWlzZUZ1bGZpbGxlZFJlc3VsdCkubWFwKHggPT4geC52YWx1ZSksXG4gICk7XG5cbiAgY29uc3QgdW5pcXVlUmVzdWx0cyA9IHBtLmF1ZGl0LnBvc3RQcm9jZXNzUmVzdWx0Py4oZnVsZmlsbGVkKSA/PyBmdWxmaWxsZWQ7XG5cbiAgcmV0dXJuIGNvbXBhdGlibGVBdWRpdERlcEdyb3Vwcy5tYXAoZGVwR3JvdXAgPT5cbiAgICBhdWRpdFJlc3VsdFRvQXVkaXRPdXRwdXQoXG4gICAgICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgQHR5cGVzY3JpcHQtZXNsaW50L25vLW5vbi1udWxsLWFzc2VydGlvblxuICAgICAgdW5pcXVlUmVzdWx0c1tkZXBHcm91cF0hLFxuICAgICAgaWQsXG4gICAgICBkZXBHcm91cCxcbiAgICAgIGF1ZGl0TGV2ZWxNYXBwaW5nLFxuICAgICksXG4gICk7XG59XG4iLCAiY29uc3QgX19pbmplY3RlZF9maWxlbmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tanMtcGFja2FnZXMvc3JjL2xpYi9ydW5uZXIvYXVkaXQvdHJhbnNmb3JtLnRzXCI7Y29uc3QgX19pbmplY3RlZF9kaXJuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1qcy1wYWNrYWdlcy9zcmMvbGliL3J1bm5lci9hdWRpdFwiO2NvbnN0IF9faW5qZWN0ZWRfaW1wb3J0X21ldGFfdXJsX18gPSBcImZpbGU6Ly8vaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tanMtcGFja2FnZXMvc3JjL2xpYi9ydW5uZXIvYXVkaXQvdHJhbnNmb3JtLnRzXCI7aW1wb3J0IHsgbWQgfSBmcm9tICdidWlsZC1tZCc7XG5pbXBvcnQgdHlwZSB7IEF1ZGl0T3V0cHV0LCBJc3N1ZSB9IGZyb20gJ0Bjb2RlLXB1c2h1cC9tb2RlbHMnO1xuaW1wb3J0IHsgb2JqZWN0VG9FbnRyaWVzIH0gZnJvbSAnQGNvZGUtcHVzaHVwL3V0aWxzJztcbmltcG9ydCB7XG4gIHR5cGUgQXVkaXRTZXZlcml0eSxcbiAgdHlwZSBEZXBlbmRlbmN5R3JvdXAsXG4gIHR5cGUgUGFja2FnZU1hbmFnZXJJZCxcbiAgcGFja2FnZUF1ZGl0TGV2ZWxzLFxufSBmcm9tICcuLi8uLi9jb25maWcuanMnO1xuaW1wb3J0IHsgYXVkaXRTY29yZU1vZGlmaWVycyB9IGZyb20gJy4vY29uc3RhbnRzLmpzJztcbmltcG9ydCB0eXBlIHsgQXVkaXRSZXN1bHQsIEF1ZGl0U3VtbWFyeSwgVnVsbmVyYWJpbGl0eSB9IGZyb20gJy4vdHlwZXMuanMnO1xuXG5leHBvcnQgZnVuY3Rpb24gYXVkaXRSZXN1bHRUb0F1ZGl0T3V0cHV0KFxuICByZXN1bHQ6IEF1ZGl0UmVzdWx0LFxuICBpZDogUGFja2FnZU1hbmFnZXJJZCxcbiAgZGVwR3JvdXA6IERlcGVuZGVuY3lHcm91cCxcbiAgYXVkaXRMZXZlbE1hcHBpbmc6IEF1ZGl0U2V2ZXJpdHksXG4pOiBBdWRpdE91dHB1dCB7XG4gIGNvbnN0IGlzc3VlcyA9IHZ1bG5lcmFiaWxpdGllc1RvSXNzdWVzKFxuICAgIHJlc3VsdC52dWxuZXJhYmlsaXRpZXMsXG4gICAgYXVkaXRMZXZlbE1hcHBpbmcsXG4gICk7XG5cbiAgcmV0dXJuIHtcbiAgICBzbHVnOiBgJHtpZH0tYXVkaXQtJHtkZXBHcm91cH1gLFxuICAgIHNjb3JlOiBjYWxjdWxhdGVBdWRpdFNjb3JlKHJlc3VsdC5zdW1tYXJ5KSxcbiAgICB2YWx1ZTogcmVzdWx0LnN1bW1hcnkudG90YWwsXG4gICAgZGlzcGxheVZhbHVlOiBzdW1tYXJ5VG9EaXNwbGF5VmFsdWUocmVzdWx0LnN1bW1hcnkpLFxuICAgIGRldGFpbHM6IHsgaXNzdWVzIH0sXG4gIH07XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBjYWxjdWxhdGVBdWRpdFNjb3JlKHN0YXRzOiBBdWRpdFN1bW1hcnkpIHtcbiAgaWYgKHN0YXRzLnRvdGFsID09PSAwKSB7XG4gICAgcmV0dXJuIDE7XG4gIH1cblxuICByZXR1cm4gb2JqZWN0VG9FbnRyaWVzKHN0YXRzKS5yZWR1Y2U8bnVtYmVyPihcbiAgICAoc2NvcmUsIFtsZXZlbCwgdnVsbmVyYWJpbGl0aWVzXSkgPT4ge1xuICAgICAgaWYgKGxldmVsID09PSAndG90YWwnKSB7XG4gICAgICAgIHJldHVybiBzY29yZTtcbiAgICAgIH1cblxuICAgICAgY29uc3QgcmVkdWNlZFNjb3JlID0gc2NvcmUgLSBhdWRpdFNjb3JlTW9kaWZpZXJzW2xldmVsXSAqIHZ1bG5lcmFiaWxpdGllcztcbiAgICAgIHJldHVybiBNYXRoLm1heChyZWR1Y2VkU2NvcmUsIDApO1xuICAgIH0sXG4gICAgMSxcbiAgKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHN1bW1hcnlUb0Rpc3BsYXlWYWx1ZShzdW1tYXJ5OiBBdWRpdFN1bW1hcnkpOiBzdHJpbmcge1xuICBpZiAoc3VtbWFyeS50b3RhbCA9PT0gMCkge1xuICAgIHJldHVybiAnMCB2dWxuZXJhYmlsaXRpZXMnO1xuICB9XG5cbiAgY29uc3QgdnVsbmVyYWJpbGl0eVN0YXRzID0gcGFja2FnZUF1ZGl0TGV2ZWxzXG4gICAgLm1hcChsZXZlbCA9PiAoc3VtbWFyeVtsZXZlbF0gPiAwID8gYCR7c3VtbWFyeVtsZXZlbF19ICR7bGV2ZWx9YCA6ICcnKSlcbiAgICAuZmlsdGVyKHRleHQgPT4gdGV4dCAhPT0gJycpXG4gICAgLmpvaW4oJywgJyk7XG4gIHJldHVybiBgJHtzdW1tYXJ5LnRvdGFsfSAke1xuICAgIHN1bW1hcnkudG90YWwgPT09IDEgPyAndnVsbmVyYWJpbGl0eScgOiAndnVsbmVyYWJpbGl0aWVzJ1xuICB9ICgke3Z1bG5lcmFiaWxpdHlTdGF0c30pYDtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHZ1bG5lcmFiaWxpdGllc1RvSXNzdWVzKFxuICB2dWxuZXJhYmlsaXRpZXM6IFZ1bG5lcmFiaWxpdHlbXSxcbiAgYXVkaXRMZXZlbE1hcHBpbmc6IEF1ZGl0U2V2ZXJpdHksXG4pOiBJc3N1ZVtdIHtcbiAgaWYgKHZ1bG5lcmFiaWxpdGllcy5sZW5ndGggPT09IDApIHtcbiAgICByZXR1cm4gW107XG4gIH1cblxuICByZXR1cm4gdnVsbmVyYWJpbGl0aWVzLm1hcCgoZGV0YWlsKTogSXNzdWUgPT4ge1xuICAgIGNvbnN0IHZlcnNpb25SYW5nZSA9XG4gICAgICBkZXRhaWwudmVyc2lvblJhbmdlID09PSAnKidcbiAgICAgICAgPyBtZGAke21kLmJvbGQoJ2FsbCcpfSB2ZXJzaW9uc2BcbiAgICAgICAgOiBtZGB2ZXJzaW9ucyAke21kLmJvbGQoZGV0YWlsLnZlcnNpb25SYW5nZSl9YDtcbiAgICBjb25zdCBkaXJlY3REZXBlbmRlbmN5ID1cbiAgICAgIHR5cGVvZiBkZXRhaWwuZGlyZWN0RGVwZW5kZW5jeSA9PT0gJ3N0cmluZycgJiZcbiAgICAgIGRldGFpbC5kaXJlY3REZXBlbmRlbmN5ICE9PSAnJ1xuICAgICAgICA/IG1kLmNvZGUoZGV0YWlsLmRpcmVjdERlcGVuZGVuY3kpXG4gICAgICAgIDogJyc7XG4gICAgY29uc3QgZGVwSGllcmFyY2h5ID0gZGlyZWN0RGVwZW5kZW5jeVxuICAgICAgPyBtZGAke2RpcmVjdERlcGVuZGVuY3l9J3MgZGVwZW5kZW5jeSAke21kLmNvZGUoZGV0YWlsLm5hbWUpfWBcbiAgICAgIDogbWRgJHttZC5jb2RlKGRldGFpbC5uYW1lKX0gZGVwZW5kZW5jeWA7XG5cbiAgICBjb25zdCB2dWxuZXJhYmlsaXR5U3VtbWFyeSA9IG1kYGhhcyBhICR7bWQuYm9sZChcbiAgICAgIGRldGFpbC5zZXZlcml0eSxcbiAgICApfSB2dWxuZXJhYmlsaXR5IGluICR7dmVyc2lvblJhbmdlfS5gO1xuICAgIGNvbnN0IGZpeEluZm8gPSBkZXRhaWwuZml4SW5mb3JtYXRpb24gPyBgICR7ZGV0YWlsLmZpeEluZm9ybWF0aW9ufWAgOiAnJztcbiAgICBjb25zdCBhZGRpdGlvbmFsSW5mbyA9XG4gICAgICBkZXRhaWwudGl0bGUgIT0gbnVsbCAmJiBkZXRhaWwudXJsICE9IG51bGxcbiAgICAgICAgPyBtZGAgTW9yZSBpbmZvcm1hdGlvbjogJHttZC5saW5rKGRldGFpbC51cmwsIGRldGFpbC50aXRsZSl9YFxuICAgICAgICA6ICcnO1xuXG4gICAgcmV0dXJuIHtcbiAgICAgIG1lc3NhZ2U6XG4gICAgICAgIG1kYCR7ZGVwSGllcmFyY2h5fSAke3Z1bG5lcmFiaWxpdHlTdW1tYXJ5fSR7Zml4SW5mb30ke2FkZGl0aW9uYWxJbmZvfWAudG9TdHJpbmcoKSxcbiAgICAgIHNldmVyaXR5OiBhdWRpdExldmVsTWFwcGluZ1tkZXRhaWwuc2V2ZXJpdHldLFxuICAgIH07XG4gIH0pO1xufVxuIiwgImNvbnN0IF9faW5qZWN0ZWRfZmlsZW5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWpzLXBhY2thZ2VzL3NyYy9saWIvcnVubmVyL2NvbnN0YW50cy50c1wiO2NvbnN0IF9faW5qZWN0ZWRfZGlybmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tanMtcGFja2FnZXMvc3JjL2xpYi9ydW5uZXJcIjtjb25zdCBfX2luamVjdGVkX2ltcG9ydF9tZXRhX3VybF9fID0gXCJmaWxlOi8vL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWpzLXBhY2thZ2VzL3NyYy9saWIvcnVubmVyL2NvbnN0YW50cy50c1wiO2ltcG9ydCBwYXRoIGZyb20gJ25vZGU6cGF0aCc7XG5pbXBvcnQgeyBwbHVnaW5Xb3JrRGlyIH0gZnJvbSAnQGNvZGUtcHVzaHVwL3V0aWxzJztcblxuZXhwb3J0IGNvbnN0IFdPUktESVIgPSBwbHVnaW5Xb3JrRGlyKCdqcy1wYWNrYWdlcycpO1xuZXhwb3J0IGNvbnN0IFJVTk5FUl9PVVRQVVRfUEFUSCA9IHBhdGguam9pbihXT1JLRElSLCAncnVubmVyLW91dHB1dC5qc29uJyk7XG5leHBvcnQgY29uc3QgUExVR0lOX0NPTkZJR19QQVRIID0gcGF0aC5qb2luKFxuICBwcm9jZXNzLmN3ZCgpLFxuICBXT1JLRElSLFxuICAncGx1Z2luLWNvbmZpZy5qc29uJyxcbik7XG4iLCAiY29uc3QgX19pbmplY3RlZF9maWxlbmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tanMtcGFja2FnZXMvc3JjL2xpYi9ydW5uZXIvb3V0ZGF0ZWQvdHJhbnNmb3JtLnRzXCI7Y29uc3QgX19pbmplY3RlZF9kaXJuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1qcy1wYWNrYWdlcy9zcmMvbGliL3J1bm5lci9vdXRkYXRlZFwiO2NvbnN0IF9faW5qZWN0ZWRfaW1wb3J0X21ldGFfdXJsX18gPSBcImZpbGU6Ly8vaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tanMtcGFja2FnZXMvc3JjL2xpYi9ydW5uZXIvb3V0ZGF0ZWQvdHJhbnNmb3JtLnRzXCI7aW1wb3J0IHsgbWQgfSBmcm9tICdidWlsZC1tZCc7XG5pbXBvcnQgeyBjbGVhbiwgZGlmZiwgbmVxIH0gZnJvbSAnc2VtdmVyJztcbmltcG9ydCB0eXBlIHsgQXVkaXRPdXRwdXQsIElzc3VlIH0gZnJvbSAnQGNvZGUtcHVzaHVwL21vZGVscyc7XG5pbXBvcnQgeyBvYmplY3RGcm9tRW50cmllcywgcGx1cmFsaXplIH0gZnJvbSAnQGNvZGUtcHVzaHVwL3V0aWxzJztcbmltcG9ydCB0eXBlIHsgRGVwZW5kZW5jeUdyb3VwLCBQYWNrYWdlTWFuYWdlcklkIH0gZnJvbSAnLi4vLi4vY29uZmlnLmpzJztcbmltcG9ydCB7IGRlcGVuZGVuY3lHcm91cFRvTG9uZyB9IGZyb20gJy4uLy4uL2NvbnN0YW50cy5qcyc7XG5pbXBvcnQgeyBSRUxFQVNFX1RZUEVTLCBvdXRkYXRlZFNldmVyaXR5IH0gZnJvbSAnLi9jb25zdGFudHMuanMnO1xuaW1wb3J0IHR5cGUgeyBPdXRkYXRlZFJlc3VsdCwgUGFja2FnZVZlcnNpb24gfSBmcm9tICcuL3R5cGVzLmpzJztcblxuZXhwb3J0IGZ1bmN0aW9uIG91dGRhdGVkUmVzdWx0VG9BdWRpdE91dHB1dChcbiAgcmVzdWx0OiBPdXRkYXRlZFJlc3VsdCxcbiAgcGFja2FnZU1hbmFnZXI6IFBhY2thZ2VNYW5hZ2VySWQsXG4gIGRlcEdyb3VwOiBEZXBlbmRlbmN5R3JvdXAsXG4gIHRvdGFsRGVwczogbnVtYmVyLFxuKTogQXVkaXRPdXRwdXQge1xuICBjb25zdCByZWxldmFudERlcGVuZGVuY2llczogT3V0ZGF0ZWRSZXN1bHQgPSByZXN1bHQuZmlsdGVyKFxuICAgIGRlcCA9PiBkZXAudHlwZSA9PT0gZGVwZW5kZW5jeUdyb3VwVG9Mb25nW2RlcEdyb3VwXSxcbiAgKTtcblxuICBjb25zdCB2YWxpZERlcGVuZGVuY2llcyA9IHJlbGV2YW50RGVwZW5kZW5jaWVzXG4gICAgLm1hcChkZXAgPT4gKHtcbiAgICAgIC4uLmRlcCxcbiAgICAgIGN1cnJlbnQ6IGNsZWFuKGRlcC5jdXJyZW50KSxcbiAgICAgIGxhdGVzdDogY2xlYW4oZGVwLmxhdGVzdCksXG4gICAgfSkpXG4gICAgLmZpbHRlcihcbiAgICAgIChkZXApOiBkZXAgaXMgT3V0ZGF0ZWRSZXN1bHRbbnVtYmVyXSA9PlxuICAgICAgICBkZXAuY3VycmVudCAhPSBudWxsICYmIGRlcC5sYXRlc3QgIT0gbnVsbCxcbiAgICApO1xuXG4gIGNvbnN0IG91dGRhdGVkRGVwZW5kZW5jaWVzID0gdmFsaWREZXBlbmRlbmNpZXMuZmlsdGVyKGRlcCA9PlxuICAgIG5lcShkZXAuY3VycmVudCwgZGVwLmxhdGVzdCksXG4gICk7XG5cbiAgY29uc3Qgb3V0ZGF0ZWRTdGF0cyA9IG91dGRhdGVkRGVwZW5kZW5jaWVzLnJlZHVjZShcbiAgICAoYWNjLCBkZXApID0+IHtcbiAgICAgIGNvbnN0IG91dGRhdGVkTGV2ZWwgPSBkaWZmKGRlcC5jdXJyZW50LCBkZXAubGF0ZXN0KTtcbiAgICAgIGlmIChvdXRkYXRlZExldmVsID09IG51bGwpIHtcbiAgICAgICAgcmV0dXJuIGFjYztcbiAgICAgIH1cbiAgICAgIHJldHVybiB7IC4uLmFjYywgW291dGRhdGVkTGV2ZWxdOiBhY2Nbb3V0ZGF0ZWRMZXZlbF0gKyAxIH07XG4gICAgfSxcbiAgICBvYmplY3RGcm9tRW50cmllcyhSRUxFQVNFX1RZUEVTLm1hcCh2ZXJzaW9uVHlwZSA9PiBbdmVyc2lvblR5cGUsIDBdKSksXG4gICk7XG5cbiAgY29uc3QgaXNzdWVzID1cbiAgICBvdXRkYXRlZERlcGVuZGVuY2llcy5sZW5ndGggPT09IDBcbiAgICAgID8gW11cbiAgICAgIDogb3V0ZGF0ZWRUb0lzc3VlcyhvdXRkYXRlZERlcGVuZGVuY2llcyk7XG5cbiAgcmV0dXJuIHtcbiAgICBzbHVnOiBgJHtwYWNrYWdlTWFuYWdlcn0tb3V0ZGF0ZWQtJHtkZXBHcm91cH1gLFxuICAgIHNjb3JlOiBjYWxjdWxhdGVPdXRkYXRlZFNjb3JlKG91dGRhdGVkU3RhdHMubWFqb3IsIHRvdGFsRGVwcyksXG4gICAgdmFsdWU6IG91dGRhdGVkRGVwZW5kZW5jaWVzLmxlbmd0aCxcbiAgICBkaXNwbGF5VmFsdWU6IG91dGRhdGVkVG9EaXNwbGF5VmFsdWUob3V0ZGF0ZWRTdGF0cyksXG4gICAgZGV0YWlsczogeyBpc3N1ZXMgfSxcbiAgfTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGNhbGN1bGF0ZU91dGRhdGVkU2NvcmUoXG4gIG1ham9yT3V0ZGF0ZWQ6IG51bWJlcixcbiAgdG90YWxEZXBzOiBudW1iZXIsXG4pIHtcbiAgcmV0dXJuIHRvdGFsRGVwcyA+IDAgPyAodG90YWxEZXBzIC0gbWFqb3JPdXRkYXRlZCkgLyB0b3RhbERlcHMgOiAxO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gb3V0ZGF0ZWRUb0Rpc3BsYXlWYWx1ZShzdGF0czogUGFja2FnZVZlcnNpb24pIHtcbiAgY29uc3QgdG90YWwgPSBPYmplY3QudmFsdWVzKHN0YXRzKS5yZWR1Y2UoKGFjYywgdmFsdWUpID0+IGFjYyArIHZhbHVlLCAwKTtcblxuICBjb25zdCB2ZXJzaW9uQnJlYWtkb3duID0gUkVMRUFTRV9UWVBFUy5tYXAodmVyc2lvbiA9PlxuICAgIHN0YXRzW3ZlcnNpb25dID4gMCA/IGAke3N0YXRzW3ZlcnNpb25dfSAke3ZlcnNpb259YCA6ICcnLFxuICApLmZpbHRlcih0ZXh0ID0+IHRleHQgIT09ICcnKTtcblxuICBpZiAodmVyc2lvbkJyZWFrZG93bi5sZW5ndGggPT09IDApIHtcbiAgICByZXR1cm4gJ2FsbCBkZXBlbmRlbmNpZXMgYXJlIHVwIHRvIGRhdGUnO1xuICB9XG5cbiAgaWYgKHZlcnNpb25CcmVha2Rvd24ubGVuZ3RoID4gMSkge1xuICAgIHJldHVybiBgJHt0b3RhbH0gb3V0ZGF0ZWQgcGFja2FnZSB2ZXJzaW9ucyAoJHt2ZXJzaW9uQnJlYWtkb3duLmpvaW4oXG4gICAgICAnLCAnLFxuICAgICl9KWA7XG4gIH1cblxuICByZXR1cm4gYCR7dmVyc2lvbkJyZWFrZG93blswXX0gb3V0ZGF0ZWQgcGFja2FnZSAke3BsdXJhbGl6ZShcbiAgICAndmVyc2lvbicsXG4gICAgdG90YWwsXG4gICl9YDtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIG91dGRhdGVkVG9Jc3N1ZXMoZGVwZW5kZW5jaWVzOiBPdXRkYXRlZFJlc3VsdCk6IElzc3VlW10ge1xuICByZXR1cm4gZGVwZW5kZW5jaWVzLm1hcDxJc3N1ZT4oZGVwID0+IHtcbiAgICBjb25zdCB7IG5hbWUsIGN1cnJlbnQsIGxhdGVzdCwgdXJsIH0gPSBkZXA7XG4gICAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIEB0eXBlc2NyaXB0LWVzbGludC9uby1ub24tbnVsbC1hc3NlcnRpb25cbiAgICBjb25zdCBvdXRkYXRlZExldmVsID0gZGlmZihjdXJyZW50LCBsYXRlc3QpITtcbiAgICBjb25zdCBwYWNrYWdlUmVmZXJlbmNlID1cbiAgICAgIHVybCA9PSBudWxsID8gbWQuY29kZShuYW1lKSA6IG1kLmxpbmsodXJsLCBtZC5jb2RlKG5hbWUpKTtcblxuICAgIHJldHVybiB7XG4gICAgICBtZXNzYWdlOiBtZGBQYWNrYWdlICR7cGFja2FnZVJlZmVyZW5jZX0gcmVxdWlyZXMgYSAke21kLmJvbGQoXG4gICAgICAgIG91dGRhdGVkTGV2ZWwsXG4gICAgICApfSB1cGRhdGUgZnJvbSAke21kLmJvbGQoY3VycmVudCl9IHRvICR7bWQuYm9sZChsYXRlc3QpfS5gLnRvU3RyaW5nKCksXG4gICAgICBzZXZlcml0eTogb3V0ZGF0ZWRTZXZlcml0eVtvdXRkYXRlZExldmVsXSxcbiAgICB9O1xuICB9KTtcbn1cbiIsICJjb25zdCBfX2luamVjdGVkX2ZpbGVuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1qcy1wYWNrYWdlcy9zcmMvbGliL3J1bm5lci9vdXRkYXRlZC9jb25zdGFudHMudHNcIjtjb25zdCBfX2luamVjdGVkX2Rpcm5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWpzLXBhY2thZ2VzL3NyYy9saWIvcnVubmVyL291dGRhdGVkXCI7Y29uc3QgX19pbmplY3RlZF9pbXBvcnRfbWV0YV91cmxfXyA9IFwiZmlsZTovLy9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1qcy1wYWNrYWdlcy9zcmMvbGliL3J1bm5lci9vdXRkYXRlZC9jb25zdGFudHMudHNcIjtpbXBvcnQgdHlwZSB7IFJlbGVhc2VUeXBlIH0gZnJvbSAnc2VtdmVyJztcbmltcG9ydCB0eXBlIHsgSXNzdWVTZXZlcml0eSB9IGZyb20gJ0Bjb2RlLXB1c2h1cC9tb2RlbHMnO1xuaW1wb3J0IHsgb2JqZWN0VG9LZXlzIH0gZnJvbSAnQGNvZGUtcHVzaHVwL3V0aWxzJztcblxuZXhwb3J0IGNvbnN0IG91dGRhdGVkU2V2ZXJpdHk6IFJlY29yZDxSZWxlYXNlVHlwZSwgSXNzdWVTZXZlcml0eT4gPSB7XG4gIG1ham9yOiAnZXJyb3InLFxuICBwcmVtYWpvcjogJ2luZm8nLFxuICBtaW5vcjogJ3dhcm5pbmcnLFxuICBwcmVtaW5vcjogJ2luZm8nLFxuICBwYXRjaDogJ2luZm8nLFxuICBwcmVwYXRjaDogJ2luZm8nLFxuICBwcmVyZWxlYXNlOiAnaW5mbycsXG59O1xuXG4vLyBSRUxFQVNFX1RZUEVTIGRpcmVjdGx5IGV4cG9ydGVkIGZyb20gc2VtdmVyIGRvbid0IHdvcmsgb3V0IG9mIHRoZSBib3hcbmV4cG9ydCBjb25zdCBSRUxFQVNFX1RZUEVTID0gb2JqZWN0VG9LZXlzKG91dGRhdGVkU2V2ZXJpdHkpO1xuIiwgImNvbnN0IF9faW5qZWN0ZWRfZmlsZW5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWpzLXBhY2thZ2VzL3NyYy9saWIvcGFja2FnZS1tYW5hZ2Vycy9kZXJpdmUtcGFja2FnZS1tYW5hZ2VyLnRzXCI7Y29uc3QgX19pbmplY3RlZF9kaXJuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1qcy1wYWNrYWdlcy9zcmMvbGliL3BhY2thZ2UtbWFuYWdlcnNcIjtjb25zdCBfX2luamVjdGVkX2ltcG9ydF9tZXRhX3VybF9fID0gXCJmaWxlOi8vL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWpzLXBhY2thZ2VzL3NyYy9saWIvcGFja2FnZS1tYW5hZ2Vycy9kZXJpdmUtcGFja2FnZS1tYW5hZ2VyLnRzXCI7aW1wb3J0IHsgcmVhZEZpbGUgfSBmcm9tICdub2RlOmZzL3Byb21pc2VzJztcbmltcG9ydCBwYXRoIGZyb20gJ25vZGU6cGF0aCc7XG5pbXBvcnQgeyBmaWxlRXhpc3RzIH0gZnJvbSAnQGNvZGUtcHVzaHVwL3V0aWxzJztcbmltcG9ydCB0eXBlIHsgUGFja2FnZU1hbmFnZXJJZCB9IGZyb20gJy4uL2NvbmZpZy5qcyc7XG5pbXBvcnQgeyBkZXJpdmVZYXJuVmVyc2lvbiB9IGZyb20gJy4vZGVyaXZlLXlhcm4uanMnO1xuXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gZGVyaXZlUGFja2FnZU1hbmFnZXJJblBhY2thZ2VKc29uKFxuICBjdXJyZW50RGlyID0gcHJvY2Vzcy5jd2QoKSxcbikge1xuICBpZiAoYXdhaXQgZmlsZUV4aXN0cyhwYXRoLmpvaW4oY3VycmVudERpciwgJ3BhY2thZ2UuanNvbicpKSkge1xuICAgIGNvbnN0IGNvbnRlbnQgPSBKU09OLnBhcnNlKFxuICAgICAgKGF3YWl0IHJlYWRGaWxlKHBhdGguam9pbigncGFja2FnZS5qc29uJykpKS50b1N0cmluZygpLFxuICAgICkgYXMgeyBwYWNrYWdlTWFuYWdlcj86IHN0cmluZyB9O1xuICAgIGNvbnN0IHsgcGFja2FnZU1hbmFnZXI6IHBhY2thZ2VNYW5hZ2VyRGF0YSA9ICcnIH0gPSBjb250ZW50O1xuXG4gICAgY29uc3QgW21hbmFnZXIgPSAnJywgdmVyc2lvbiA9ICcnXSA9IHBhY2thZ2VNYW5hZ2VyRGF0YS5zcGxpdCgnQCcpO1xuXG4gICAgaWYgKG1hbmFnZXIgPT09ICducG0nKSB7XG4gICAgICByZXR1cm4gbWFuYWdlcjtcbiAgICB9XG4gICAgaWYgKG1hbmFnZXIgPT09ICdwbnBtJykge1xuICAgICAgcmV0dXJuIG1hbmFnZXI7XG4gICAgfVxuICAgIGlmIChtYW5hZ2VyID09PSAneWFybicpIHtcbiAgICAgIGNvbnN0IG1ham9yVmVyc2lvbiA9IE51bWJlcih2ZXJzaW9uLnNwbGl0KCcuJylbMF0pO1xuICAgICAgcmV0dXJuIG1ham9yVmVyc2lvbiA+IDEgPyAneWFybi1tb2Rlcm4nIDogJ3lhcm4tY2xhc3NpYyc7XG4gICAgfVxuICB9XG4gIHJldHVybiBmYWxzZTtcbn1cblxuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGRlcml2ZVBhY2thZ2VNYW5hZ2VyKFxuICBjdXJyZW50RGlyID0gcHJvY2Vzcy5jd2QoKSxcbik6IFByb21pc2U8UGFja2FnZU1hbmFnZXJJZD4ge1xuICBjb25zdCBwa2dNYW5hZ2VyRnJvbVBhY2thZ2VKc29uID1cbiAgICBhd2FpdCBkZXJpdmVQYWNrYWdlTWFuYWdlckluUGFja2FnZUpzb24oY3VycmVudERpcik7XG4gIGlmIChwa2dNYW5hZ2VyRnJvbVBhY2thZ2VKc29uKSB7XG4gICAgcmV0dXJuIHBrZ01hbmFnZXJGcm9tUGFja2FnZUpzb247XG4gIH1cblxuICAvLyBDaGVjayBmb3IgbG9jayBmaWxlc1xuICBpZiAoYXdhaXQgZmlsZUV4aXN0cyhwYXRoLmpvaW4oY3VycmVudERpciwgJ3BhY2thZ2UtbG9jay5qc29uJykpKSB7XG4gICAgcmV0dXJuICducG0nO1xuICB9IGVsc2UgaWYgKGF3YWl0IGZpbGVFeGlzdHMocGF0aC5qb2luKGN1cnJlbnREaXIsICdwbnBtLWxvY2sueWFtbCcpKSkge1xuICAgIHJldHVybiAncG5wbSc7XG4gIH0gZWxzZSBpZiAoYXdhaXQgZmlsZUV4aXN0cyhwYXRoLmpvaW4oY3VycmVudERpciwgJ3lhcm4ubG9jaycpKSkge1xuICAgIGNvbnN0IHlhcm5WZXJzaW9uID0gYXdhaXQgZGVyaXZlWWFyblZlcnNpb24oKTtcbiAgICBpZiAoeWFyblZlcnNpb24pIHtcbiAgICAgIHJldHVybiB5YXJuVmVyc2lvbjtcbiAgICB9XG4gIH1cblxuICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgJ0NvdWxkIG5vdCBkZXRlY3QgcGFja2FnZSBtYW5hZ2VyLiBQbGVhc2UgcHJvdmlkZSBpdCBpbiB0aGUganMtcGFja2FnZXMgcGx1Z2luIGNvbmZpZy4nLFxuICApO1xufVxuIiwgImNvbnN0IF9faW5qZWN0ZWRfZmlsZW5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWpzLXBhY2thZ2VzL3NyYy9saWIvcGFja2FnZS1tYW5hZ2Vycy9kZXJpdmUteWFybi50c1wiO2NvbnN0IF9faW5qZWN0ZWRfZGlybmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tanMtcGFja2FnZXMvc3JjL2xpYi9wYWNrYWdlLW1hbmFnZXJzXCI7Y29uc3QgX19pbmplY3RlZF9pbXBvcnRfbWV0YV91cmxfXyA9IFwiZmlsZTovLy9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1qcy1wYWNrYWdlcy9zcmMvbGliL3BhY2thZ2UtbWFuYWdlcnMvZGVyaXZlLXlhcm4udHNcIjtpbXBvcnQgeyBleGVjdXRlUHJvY2VzcyB9IGZyb20gJ0Bjb2RlLXB1c2h1cC91dGlscyc7XG5cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBkZXJpdmVZYXJuVmVyc2lvbigpIHtcbiAgY29uc3QgeyBzdGRvdXQgfSA9IGF3YWl0IGV4ZWN1dGVQcm9jZXNzKHtcbiAgICBjb21tYW5kOiAneWFybicsXG4gICAgYXJnczogWyctdiddLFxuICB9KTtcblxuICBjb25zdCB5YXJuVmVyc2lvbiA9IE51bWJlci5wYXJzZUludChzdGRvdXQudG9TdHJpbmcoKS50cmltKCkuYXQoMCkgPz8gJycsIDEwKTtcbiAgaWYgKHlhcm5WZXJzaW9uID49IDIpIHtcbiAgICByZXR1cm4gJ3lhcm4tbW9kZXJuJztcbiAgfSBlbHNlIGlmICh5YXJuVmVyc2lvbiA9PT0gMSkge1xuICAgIHJldHVybiAneWFybi1jbGFzc2ljJztcbiAgfVxuICByZXR1cm4gZmFsc2U7XG59XG4iLCAiY29uc3QgX19pbmplY3RlZF9maWxlbmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tbGlnaHRob3VzZS9zcmMvbGliL2xpZ2h0aG91c2UtcGx1Z2luLnRzXCI7Y29uc3QgX19pbmplY3RlZF9kaXJuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1saWdodGhvdXNlL3NyYy9saWJcIjtjb25zdCBfX2luamVjdGVkX2ltcG9ydF9tZXRhX3VybF9fID0gXCJmaWxlOi8vL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWxpZ2h0aG91c2Uvc3JjL2xpYi9saWdodGhvdXNlLXBsdWdpbi50c1wiO2ltcG9ydCB7IGNyZWF0ZVJlcXVpcmUgfSBmcm9tICdub2RlOm1vZHVsZSc7XG5pbXBvcnQgdHlwZSB7IFBsdWdpbkNvbmZpZyB9IGZyb20gJ0Bjb2RlLXB1c2h1cC9tb2RlbHMnO1xuaW1wb3J0IHsgTElHSFRIT1VTRV9QTFVHSU5fU0xVRyB9IGZyb20gJy4vY29uc3RhbnRzLmpzJztcbmltcG9ydCB7IG5vcm1hbGl6ZUZsYWdzIH0gZnJvbSAnLi9ub3JtYWxpemUtZmxhZ3MuanMnO1xuaW1wb3J0IHtcbiAgTElHSFRIT1VTRV9HUk9VUFMsXG4gIExJR0hUSE9VU0VfTkFWSUdBVElPTl9BVURJVFMsXG59IGZyb20gJy4vcnVubmVyL2NvbnN0YW50cy5qcyc7XG5pbXBvcnQgeyBjcmVhdGVSdW5uZXJGdW5jdGlvbiB9IGZyb20gJy4vcnVubmVyL3J1bm5lci5qcyc7XG5pbXBvcnQgdHlwZSB7IExpZ2h0aG91c2VPcHRpb25zIH0gZnJvbSAnLi90eXBlcy5qcyc7XG5pbXBvcnQgeyBmaWx0ZXJBdWRpdHNBbmRHcm91cHNCeU9ubHlPcHRpb25zIH0gZnJvbSAnLi91dGlscy5qcyc7XG5cbmV4cG9ydCBmdW5jdGlvbiBsaWdodGhvdXNlUGx1Z2luKFxuICB1cmw6IHN0cmluZyxcbiAgZmxhZ3M/OiBMaWdodGhvdXNlT3B0aW9ucyxcbik6IFBsdWdpbkNvbmZpZyB7XG4gIGNvbnN0IHsgc2tpcEF1ZGl0cywgb25seUF1ZGl0cywgb25seUNhdGVnb3JpZXMsIC4uLnVucGFyc2VkRmxhZ3MgfSA9XG4gICAgbm9ybWFsaXplRmxhZ3MoZmxhZ3MgPz8ge30pO1xuXG4gIGNvbnN0IHsgYXVkaXRzLCBncm91cHMgfSA9IGZpbHRlckF1ZGl0c0FuZEdyb3Vwc0J5T25seU9wdGlvbnMoXG4gICAgTElHSFRIT1VTRV9OQVZJR0FUSU9OX0FVRElUUyxcbiAgICBMSUdIVEhPVVNFX0dST1VQUyxcbiAgICB7IHNraXBBdWRpdHMsIG9ubHlBdWRpdHMsIG9ubHlDYXRlZ29yaWVzIH0sXG4gICk7XG5cbiAgY29uc3QgcGFja2FnZUpzb24gPSBjcmVhdGVSZXF1aXJlKGltcG9ydC5tZXRhLnVybCkoXG4gICAgJy4uLy4uL3BhY2thZ2UuanNvbicsXG4gICkgYXMgdHlwZW9mIGltcG9ydCgnLi4vLi4vcGFja2FnZS5qc29uJyk7XG5cbiAgcmV0dXJuIHtcbiAgICBzbHVnOiBMSUdIVEhPVVNFX1BMVUdJTl9TTFVHLFxuICAgIHBhY2thZ2VOYW1lOiBwYWNrYWdlSnNvbi5uYW1lLFxuICAgIHZlcnNpb246IHBhY2thZ2VKc29uLnZlcnNpb24sXG4gICAgdGl0bGU6ICdMaWdodGhvdXNlJyxcbiAgICBpY29uOiAnbGlnaHRob3VzZScsXG4gICAgYXVkaXRzLFxuICAgIGdyb3VwcyxcbiAgICBydW5uZXI6IGNyZWF0ZVJ1bm5lckZ1bmN0aW9uKHVybCwge1xuICAgICAgc2tpcEF1ZGl0cyxcbiAgICAgIG9ubHlBdWRpdHMsXG4gICAgICBvbmx5Q2F0ZWdvcmllcyxcbiAgICAgIC4uLnVucGFyc2VkRmxhZ3MsXG4gICAgfSksXG4gIH07XG59XG4iLCAiY29uc3QgX19pbmplY3RlZF9maWxlbmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tbGlnaHRob3VzZS9zcmMvbGliL2NvbnN0YW50cy50c1wiO2NvbnN0IF9faW5qZWN0ZWRfZGlybmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tbGlnaHRob3VzZS9zcmMvbGliXCI7Y29uc3QgX19pbmplY3RlZF9pbXBvcnRfbWV0YV91cmxfXyA9IFwiZmlsZTovLy9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1saWdodGhvdXNlL3NyYy9saWIvY29uc3RhbnRzLnRzXCI7aW1wb3J0IHsgREVGQVVMVF9GTEFHUyB9IGZyb20gJ2Nocm9tZS1sYXVuY2hlci9kaXN0L2ZsYWdzLmpzJztcbmltcG9ydCBwYXRoIGZyb20gJ25vZGU6cGF0aCc7XG5pbXBvcnQgeyBERUZBVUxUX1BFUlNJU1RfT1VUUFVUX0RJUiB9IGZyb20gJ0Bjb2RlLXB1c2h1cC9tb2RlbHMnO1xuXG4vLyBoZWFkbGVzcyBpcyBuZWVkZWQgdG8gcGFzcyBDSSBvbiBMaW51eCBhbmQgV2luZG93cyAobG9jYWxseSBpdCB3b3JrcyB3aXRob3V0IGhlYWRsZXNzIHRvbylcbmV4cG9ydCBjb25zdCBERUZBVUxUX0NIUk9NRV9GTEFHUyA9IFsuLi5ERUZBVUxUX0ZMQUdTLCAnLS1oZWFkbGVzcyddO1xuXG5leHBvcnQgY29uc3QgTElHSFRIT1VTRV9QTFVHSU5fU0xVRyA9ICdsaWdodGhvdXNlJztcbmV4cG9ydCBjb25zdCBMSUdIVEhPVVNFX09VVFBVVF9QQVRIID0gcGF0aC5qb2luKFxuICBERUZBVUxUX1BFUlNJU1RfT1VUUFVUX0RJUixcbiAgTElHSFRIT1VTRV9QTFVHSU5fU0xVRyxcbik7XG4iLCAiY29uc3QgX19pbmplY3RlZF9maWxlbmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tbGlnaHRob3VzZS9zcmMvbGliL25vcm1hbGl6ZS1mbGFncy50c1wiO2NvbnN0IF9faW5qZWN0ZWRfZGlybmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tbGlnaHRob3VzZS9zcmMvbGliXCI7Y29uc3QgX19pbmplY3RlZF9pbXBvcnRfbWV0YV91cmxfXyA9IFwiZmlsZTovLy9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1saWdodGhvdXNlL3NyYy9saWIvbm9ybWFsaXplLWZsYWdzLnRzXCI7aW1wb3J0IHsgYm9sZCwgeWVsbG93IH0gZnJvbSAnYW5zaXMnO1xuaW1wb3J0IHsgdWkgfSBmcm9tICdAY29kZS1wdXNodXAvdXRpbHMnO1xuaW1wb3J0IHsgTElHSFRIT1VTRV9QTFVHSU5fU0xVRyB9IGZyb20gJy4vY29uc3RhbnRzLmpzJztcbmltcG9ydCB7IERFRkFVTFRfQ0xJX0ZMQUdTIH0gZnJvbSAnLi9ydW5uZXIvY29uc3RhbnRzLmpzJztcbmltcG9ydCB0eXBlIHsgTGlnaHRob3VzZUNsaUZsYWdzIH0gZnJvbSAnLi9ydW5uZXIvdHlwZXMuanMnO1xuaW1wb3J0IHR5cGUgeyBMaWdodGhvdXNlT3B0aW9ucyB9IGZyb20gJy4vdHlwZXMuanMnO1xuXG5jb25zdCB7IG9ubHlDYXRlZ29yaWVzLCAuLi5vcmlnaW5hbERlZmF1bHRDbGlGbGFncyB9ID0gREVGQVVMVF9DTElfRkxBR1M7XG5leHBvcnQgY29uc3QgREVGQVVMVF9MSUdIVEhPVVNFX09QVElPTlMgPSB7XG4gIC4uLm9yaWdpbmFsRGVmYXVsdENsaUZsYWdzLFxuICBvbmx5R3JvdXBzOiBvbmx5Q2F0ZWdvcmllcyxcbn0gc2F0aXNmaWVzIExpZ2h0aG91c2VPcHRpb25zO1xuXG4vLyBOT1RFOlxuLy8gVGhpcyBpcyBhbiBpbnRlcm1lZGlhdGUgdmFyaWFibGUgdG8gZ2V0IGBVbnN1cHBvcnRlZENsaUZsYWdzYC4gRm9yIHVua25vd24gcmVhc29ucyBgdHlwZXNjcmlwdEA1LjMuM2AgZG9lc24ndCB3b3JrIG90aGVyd2lzZS5cbmNvbnN0IGxpZ2h0aG91c2VVbnN1cHBvcnRlZENsaUZsYWdzID0gW1xuICAncHJlY29tcHV0ZWRMYW50ZXJuRGF0YVBhdGgnLCAvLyBQYXRoIHRvIHRoZSBmaWxlIHdoZXJlIHByZWNvbXB1dGVkIGxhbnRlcm4gZGF0YSBzaG91bGQgYmUgcmVhZCBmcm9tLlxuICAnY2hyb21lSWdub3JlRGVmYXVsdEZsYWdzJywgLy8gaWdub3JlIGRlZmF1bHQgZmxhZ3MgZnJvbSBMaWdodGhvdXNlIENMSVxuICAvLyBObyBlcnJvciByZXBvcnRpbmcgaW1wbGVtZW50ZWQgYXMgaW4gdGhlIHNvdXJjZSBTZW50cnkgd2FzIGludm9sdmVkXG4gIC8vIFNlZTogaHR0cHM6Ly9naXRodWIuY29tL0dvb2dsZUNocm9tZS9saWdodGhvdXNlL2Jsb2IvZDhjY2Y3MDY5MjIxNmI3ZmEwNDdhNGVhYTJkMTI3N2IwYjdmZTk0Ny9jbGkvYmluLmpzI0wxMjRcbiAgJ2VuYWJsZUVycm9yUmVwb3J0aW5nJywgLy8gZW5hYmxlIGVycm9yIHJlcG9ydGluZ1xuICAvLyBsaWdodGhvdXNlIENMSSBzcGVjaWZpYyBkZWJ1ZyBsb2dzXG4gICdsaXN0LWFsbC1hdWRpdHMnLCAvLyBQcmludHMgYSBsaXN0IG9mIGFsbCBhdmFpbGFibGUgYXVkaXRzIGFuZCBleGl0cy5cbiAgJ2xpc3QtbG9jYWxlcycsIC8vIFByaW50cyBhIGxpc3Qgb2YgYWxsIHN1cHBvcnRlZCBsb2NhbGVzIGFuZCBleGl0cy5cbiAgJ2xpc3QtdHJhY2UtY2F0ZWdvcmllcycsIC8vIFByaW50cyBhIGxpc3Qgb2YgYWxsIHJlcXVpcmVkIHRyYWNlIGNhdGVnb3JpZXMgYW5kIGV4aXRzLlxuXSBhcyBjb25zdDtcbnR5cGUgVW5zdXBwb3J0ZWRDbGlGbGFncyA9ICh0eXBlb2YgbGlnaHRob3VzZVVuc3VwcG9ydGVkQ2xpRmxhZ3MpW251bWJlcl07XG5cbmNvbnN0IExJR0hUSE9VU0VfVU5TVVBQT1JURURfQ0xJX0ZMQUdTID0gbmV3IFNldChsaWdodGhvdXNlVW5zdXBwb3J0ZWRDbGlGbGFncyk7XG5cbmNvbnN0IFJFRklORURfU1RSSU5HX09SX1NUUklOR19BUlJBWSA9IG5ldyBTZXQoW1xuICAnb25seUF1ZGl0cycsXG4gICdvbmx5Q2F0ZWdvcmllcycsXG4gICdza2lwQXVkaXRzJyxcbiAgJ2J1ZGdldHMnLFxuICAnY2hyb21lRmxhZ3MnLFxuXSk7XG5cbmV4cG9ydCBmdW5jdGlvbiBub3JtYWxpemVGbGFncyhmbGFncz86IExpZ2h0aG91c2VPcHRpb25zKTogTGlnaHRob3VzZUNsaUZsYWdzIHtcbiAgY29uc3QgcHJlZmlsbGVkRmxhZ3MgPSB7IC4uLkRFRkFVTFRfTElHSFRIT1VTRV9PUFRJT05TLCAuLi5mbGFncyB9O1xuXG4gIGxvZ1Vuc3VwcG9ydGVkRmxhZ3NJblVzZShwcmVmaWxsZWRGbGFncyk7XG5cbiAgcmV0dXJuIE9iamVjdC5mcm9tRW50cmllcyhcbiAgICBPYmplY3QuZW50cmllcyhwcmVmaWxsZWRGbGFncylcbiAgICAgIC5maWx0ZXIoXG4gICAgICAgIChbZmxhZ05hbWVdKSA9PlxuICAgICAgICAgICFMSUdIVEhPVVNFX1VOU1VQUE9SVEVEX0NMSV9GTEFHUy5oYXMoXG4gICAgICAgICAgICBmbGFnTmFtZSBhcyBVbnN1cHBvcnRlZENsaUZsYWdzLFxuICAgICAgICAgICksXG4gICAgICApXG4gICAgICAvLyBpbiBjb2RlLXB1c2h1cCBsaWdodGhvdXNlIGNhdGVnb3JpZXMgYXJlIG1hcHBlZCBhcyBncm91cHMsIHRoZXJlZm9yIHdlIGhhZCB0byByZW5hbWUgXCJvbmx5Q2F0ZWdvcmllc1wiIHRvIFwib25seUdyb3Vwc1wiIGZvciB0aGUgdXNlciBvZiB0aGUgcGx1Z2luIGFzIGl0IHdhcyBjb25mdXNpbmdcbiAgICAgIC5tYXAoKFtrZXksIHZdKSA9PiBba2V5ID09PSAnb25seUdyb3VwcycgPyAnb25seUNhdGVnb3JpZXMnIDoga2V5LCB2XSlcbiAgICAgIC8vIG9ubHlBdWRpdHMgYW5kIG9ubHlDYXRlZ29yaWVzIGNhbm5vdCBiZSBlbXB0eSBhcnJheXMsIG90aGVyd2lzZSBza2lwQXVkaXRzIGlzIGlnbm9yZWQgYnkgbGlnaHRob3VzZVxuICAgICAgLmZpbHRlcigoW18sIHZdKSA9PiAhKEFycmF5LmlzQXJyYXkodikgJiYgdi5sZW5ndGggPT09IDApKVxuICAgICAgLy8gdW5kZWZpbmVkIHwgc3RyaW5nIHwgc3RyaW5nW10gPT4gc3RyaW5nW10gKGVtcHR5IGZvciB1bmRlZmluZWQpXG4gICAgICAubWFwKChba2V5LCB2XSkgPT4ge1xuICAgICAgICBpZiAoIVJFRklORURfU1RSSU5HX09SX1NUUklOR19BUlJBWS5oYXMoa2V5IGFzIG5ldmVyKSkge1xuICAgICAgICAgIHJldHVybiBba2V5LCB2XTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gW2tleSwgQXJyYXkuaXNBcnJheSh2KSA/IHYgOiB2ID09IG51bGwgPyBbXSA6IFt2XV07XG4gICAgICB9KSxcbiAgKSBhcyBMaWdodGhvdXNlQ2xpRmxhZ3M7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBsb2dVbnN1cHBvcnRlZEZsYWdzSW5Vc2UoXG4gIGZsYWdzOiBMaWdodGhvdXNlT3B0aW9ucyxcbiAgZGlzcGxheUNvdW50ID0gMyxcbikge1xuICBjb25zdCB1bnN1cHBvcnRlZEZsYWdzSW5Vc2UgPSBPYmplY3Qua2V5cyhmbGFncykuZmlsdGVyKGZsYWcgPT5cbiAgICBMSUdIVEhPVVNFX1VOU1VQUE9SVEVEX0NMSV9GTEFHUy5oYXMoZmxhZyBhcyBVbnN1cHBvcnRlZENsaUZsYWdzKSxcbiAgKTtcbiAgaWYgKHVuc3VwcG9ydGVkRmxhZ3NJblVzZS5sZW5ndGggPiAwKSB7XG4gICAgY29uc3QgcG9zdEZpeCA9IChjb3VudDogbnVtYmVyKSA9PlxuICAgICAgY291bnQgPiBkaXNwbGF5Q291bnQgPyBgIGFuZCAke2NvdW50IC0gZGlzcGxheUNvdW50fSBtb3JlLmAgOiAnJztcbiAgICB1aSgpLmxvZ2dlci5kZWJ1ZyhcbiAgICAgIGAke3llbGxvdygnXHUyNkEwJyl9IFBsdWdpbiAke2JvbGQoXG4gICAgICAgIExJR0hUSE9VU0VfUExVR0lOX1NMVUcsXG4gICAgICApfSB1c2VkIHVuc3VwcG9ydGVkIGZsYWdzOiAke2JvbGQoXG4gICAgICAgIHVuc3VwcG9ydGVkRmxhZ3NJblVzZS5zbGljZSgwLCBkaXNwbGF5Q291bnQpLmpvaW4oJywgJyksXG4gICAgICApfSR7cG9zdEZpeCh1bnN1cHBvcnRlZEZsYWdzSW5Vc2UubGVuZ3RoKX1gLFxuICAgICk7XG4gIH1cbn1cbiIsICJjb25zdCBfX2luamVjdGVkX2ZpbGVuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1saWdodGhvdXNlL3NyYy9saWIvcnVubmVyL2NvbnN0YW50cy50c1wiO2NvbnN0IF9faW5qZWN0ZWRfZGlybmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tbGlnaHRob3VzZS9zcmMvbGliL3J1bm5lclwiO2NvbnN0IF9faW5qZWN0ZWRfaW1wb3J0X21ldGFfdXJsX18gPSBcImZpbGU6Ly8vaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tbGlnaHRob3VzZS9zcmMvbGliL3J1bm5lci9jb25zdGFudHMudHNcIjtpbXBvcnQge1xuICB0eXBlIENsaUZsYWdzLFxuICB0eXBlIENvbmZpZyxcbiAgdHlwZSBJY3VNZXNzYWdlLFxuICB0eXBlIEF1ZGl0IGFzIExIQXVkaXQsXG4gIGRlZmF1bHRDb25maWcsXG59IGZyb20gJ2xpZ2h0aG91c2UnO1xuaW1wb3J0IHBhdGggZnJvbSAnbm9kZTpwYXRoJztcbmltcG9ydCB0eXBlIHsgQXVkaXQsIEdyb3VwIH0gZnJvbSAnQGNvZGUtcHVzaHVwL21vZGVscyc7XG5pbXBvcnQgeyBERUZBVUxUX0NIUk9NRV9GTEFHUywgTElHSFRIT1VTRV9PVVRQVVRfUEFUSCB9IGZyb20gJy4uL2NvbnN0YW50cy5qcyc7XG5cbmNvbnN0IHsgYXVkaXRzLCBjYXRlZ29yaWVzIH0gPSBkZWZhdWx0Q29uZmlnO1xuXG4vLyBpbnRlcm5hbCBpbnRlcm1lZGlhdGUgdmFyaWFibGUgdG8gZGVyaXZlIHRoZSByZWxldmFudCBhdWRpdHNcbmNvbnN0IGFsbFJhd0xpZ2h0aG91c2VBdWRpdHMgPSBhd2FpdCBQcm9taXNlLmFsbChcbiAgKGF1ZGl0cyA/PyBbXSkubWFwKGxvYWRMaWdodGhvdXNlQXVkaXQpLFxuKTtcblxuZXhwb3J0IGNvbnN0IFBMVUdJTl9TTFVHID0gJ2xpZ2h0aG91c2UnO1xuZXhwb3J0IGNvbnN0IExJR0hUSE9VU0VfTkFWSUdBVElPTl9BVURJVFM6IEF1ZGl0W10gPSBhbGxSYXdMaWdodGhvdXNlQXVkaXRzXG4gIC8vIFRoaXMgcGx1Z2luIG9ubHkgc3VwcG9ydHMgdGhlIFwibmF2aWdhdGlvblwiIG1vZGUgb2YgTGlnaHRob3VzZSBpbiB0aGUgY3VycmVudCBpbXBsZW1lbnRhdGlvblxuICAvLyBJZiB3ZSBkb24ndCBleGNsdWRlIG90aGVyIGF1ZGl0cyB3ZSB0aHJvdyBpbiB0aGUgcGx1Z2luIG91dHB1dCB2YWxpZGF0aW9uIGFzIHNvbWUgb2YgdGhlIHByb3ZpZGVkIGF1ZGl0cyBhcmUgbm90IGluY2x1ZGVkIGluIGBsaWdodGhvdXNlLXJlcG9ydC5qc29uYFxuICAuZmlsdGVyKFxuICAgIGF1ZGl0ID0+XG4gICAgICBhdWRpdC5tZXRhLnN1cHBvcnRlZE1vZGVzID09IG51bGwgfHxcbiAgICAgIChBcnJheS5pc0FycmF5KGF1ZGl0Lm1ldGEuc3VwcG9ydGVkTW9kZXMpICYmXG4gICAgICAgIGF1ZGl0Lm1ldGEuc3VwcG9ydGVkTW9kZXMuaW5jbHVkZXMoJ25hdmlnYXRpb24nKSksXG4gIClcbiAgLm1hcChhdWRpdCA9PiAoe1xuICAgIHNsdWc6IGF1ZGl0Lm1ldGEuaWQsXG4gICAgdGl0bGU6IGdldE1ldGFTdHJpbmcoYXVkaXQubWV0YS50aXRsZSksXG4gICAgZGVzY3JpcHRpb246IGdldE1ldGFTdHJpbmcoYXVkaXQubWV0YS5kZXNjcmlwdGlvbiksXG4gIH0pKTtcblxuY29uc3QgbmF2aWdhdGlvbkF1ZGl0U2x1Z3MgPSBuZXcgU2V0KFxuICBMSUdIVEhPVVNFX05BVklHQVRJT05fQVVESVRTLm1hcCgoeyBzbHVnIH0pID0+IHNsdWcpLFxuKTtcbmV4cG9ydCBjb25zdCBMSUdIVEhPVVNFX0dST1VQUzogR3JvdXBbXSA9IE9iamVjdC5lbnRyaWVzKGNhdGVnb3JpZXMgPz8ge30pLm1hcChcbiAgKFtpZCwgY2F0ZWdvcnldKSA9PiAoe1xuICAgIHNsdWc6IGlkLFxuICAgIHRpdGxlOiBnZXRNZXRhU3RyaW5nKGNhdGVnb3J5LnRpdGxlKSxcbiAgICAuLi4oY2F0ZWdvcnkuZGVzY3JpcHRpb24gJiYge1xuICAgICAgZGVzY3JpcHRpb246IGdldE1ldGFTdHJpbmcoY2F0ZWdvcnkuZGVzY3JpcHRpb24pLFxuICAgIH0pLFxuICAgIHJlZnM6IGNhdGVnb3J5LmF1ZGl0UmVmc1xuICAgICAgLmZpbHRlcigoeyBpZDogYXVkaXRTbHVnIH0pID0+IG5hdmlnYXRpb25BdWRpdFNsdWdzLmhhcyhhdWRpdFNsdWcpKVxuICAgICAgLm1hcChyZWYgPT4gKHtcbiAgICAgICAgc2x1ZzogcmVmLmlkLFxuICAgICAgICB3ZWlnaHQ6IHJlZi53ZWlnaHQsXG4gICAgICB9KSksXG4gIH0pLFxuKTtcblxuZnVuY3Rpb24gZ2V0TWV0YVN0cmluZyh2YWx1ZTogc3RyaW5nIHwgSWN1TWVzc2FnZSk6IHN0cmluZyB7XG4gIGlmICh0eXBlb2YgdmFsdWUgPT09ICdzdHJpbmcnKSB7XG4gICAgcmV0dXJuIHZhbHVlO1xuICB9XG4gIHJldHVybiB2YWx1ZS5mb3JtYXR0ZWREZWZhdWx0O1xufVxuXG5hc3luYyBmdW5jdGlvbiBsb2FkTGlnaHRob3VzZUF1ZGl0KFxuICB2YWx1ZTogQ29uZmlnLkF1ZGl0SnNvbixcbik6IFByb21pc2U8dHlwZW9mIExIQXVkaXQ+IHtcbiAgLy8gdGhlIHBhc3NlZCB2YWx1ZSBkaXJlY3RseSBpbmNsdWRlcyB0aGUgaW1wbGVtZW50YXRpb24gYXMgSlMgb2JqZWN0XG4gIC8vICAgc2hhcGU6IHsgaW1wbGVtZW50YXRpb246IHR5cGVvZiBMSEF1ZGl0OyBvcHRpb25zPzoge307IH1cbiAgaWYgKHR5cGVvZiB2YWx1ZSA9PT0gJ29iamVjdCcgJiYgJ2ltcGxlbWVudGF0aW9uJyBpbiB2YWx1ZSkge1xuICAgIHJldHVybiB2YWx1ZS5pbXBsZW1lbnRhdGlvbjtcbiAgfVxuICAvLyB0aGUgcGFzc2VkIHZhbHVlIGlzIGEgYExILkF1ZGl0YCBjbGFzcyBpbnN0YW5jZVxuICAvLyAgIHNoYXBlOiBMSEF1ZGl0XG4gIGlmICh0eXBlb2YgdmFsdWUgPT09ICdmdW5jdGlvbicpIHtcbiAgICByZXR1cm4gdmFsdWU7XG4gIH1cbiAgLy8gdGhlIHBhc3NlZCB2YWx1ZSBpcyB0aGUgcGF0aCBkaXJlY3RseVxuICAvLyAgIHNoYXBlOiBzdHJpbmdcbiAgLy8gb3RoZXJ3aXNlIGl0IGlzIGEgSlMgb2JqZWN0IG1haW50YWluaW5nIGEgYHBhdGhgIHByb3BlcnR5XG4gIC8vICAgc2hhcGU6IHsgcGF0aDogc3RyaW5nLCBvcHRpb25zPzoge307IH1cbiAgY29uc3QgZmlsZSA9IHR5cGVvZiB2YWx1ZSA9PT0gJ3N0cmluZycgPyB2YWx1ZSA6IHZhbHVlLnBhdGg7XG4gIGNvbnN0IG1vZHVsZSA9IChhd2FpdCBpbXBvcnQoYGxpZ2h0aG91c2UvY29yZS9hdWRpdHMvJHtmaWxlfS5qc2ApKSBhcyB7XG4gICAgZGVmYXVsdDogdHlwZW9mIExIQXVkaXQ7XG4gIH07XG4gIHJldHVybiBtb2R1bGUuZGVmYXVsdDtcbn1cblxuZXhwb3J0IGNvbnN0IExJR0hUSE9VU0VfUkVQT1JUX05BTUUgPSAnbGlnaHRob3VzZS1yZXBvcnQuanNvbic7XG5cbmV4cG9ydCBjb25zdCBERUZBVUxUX0NMSV9GTEFHUyA9IHtcbiAgLy8gZGVmYXVsdCB2YWx1ZXMgZXh0cmFjdGVkIGZyb21cbiAgLy8gaHR0cHM6Ly9naXRodWIuY29tL0dvb2dsZUNocm9tZS9saWdodGhvdXNlL2Jsb2IvN2Q4MDE3OGMzN2ExYjYwMGVhOGYwOTJmYzBiMDk4MDI5Nzk5YTY1OS9jbGkvY2xpLWZsYWdzLmpzI0w4MFxuICB2ZXJib3NlOiBmYWxzZSxcbiAgc2F2ZUFzc2V0czogZmFsc2UsXG4gIGNocm9tZUZsYWdzOiBERUZBVUxUX0NIUk9NRV9GTEFHUyxcbiAgcG9ydDogMCxcbiAgaG9zdG5hbWU6ICcxMjcuMC4wLjEnLFxuICB2aWV3OiBmYWxzZSxcbiAgY2hhbm5lbDogJ2NsaScsXG4gIC8vIGN1c3RvbSBvdmVyd3JpdGVzIGluIGZhdm91ciBvZiB0aGUgcGx1Z2luXG4gIC8vIGhpZGUgbG9ncyBieSBkZWZhdWx0XG4gIHF1aWV0OiB0cnVlLFxuICBvbmx5QXVkaXRzOiBbXSxcbiAgc2tpcEF1ZGl0czogW10sXG4gIG9ubHlDYXRlZ29yaWVzOiBbXSxcbiAgb3V0cHV0OiBbJ2pzb24nXSxcbiAgb3V0cHV0UGF0aDogcGF0aC5qb2luKExJR0hUSE9VU0VfT1VUUFVUX1BBVEgsIExJR0hUSE9VU0VfUkVQT1JUX05BTUUpLFxufSBzYXRpc2ZpZXMgUGFydGlhbDxDbGlGbGFncz47XG4iLCAiY29uc3QgX19pbmplY3RlZF9maWxlbmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tbGlnaHRob3VzZS9zcmMvbGliL3J1bm5lci9ydW5uZXIudHNcIjtjb25zdCBfX2luamVjdGVkX2Rpcm5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWxpZ2h0aG91c2Uvc3JjL2xpYi9ydW5uZXJcIjtjb25zdCBfX2luamVjdGVkX2ltcG9ydF9tZXRhX3VybF9fID0gXCJmaWxlOi8vL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWxpZ2h0aG91c2Uvc3JjL2xpYi9ydW5uZXIvcnVubmVyLnRzXCI7aW1wb3J0IHR5cGUgeyBSdW5uZXJSZXN1bHQgfSBmcm9tICdsaWdodGhvdXNlJztcbmltcG9ydCB7IHJ1bkxpZ2h0aG91c2UgfSBmcm9tICdsaWdodGhvdXNlL2NsaS9ydW4uanMnO1xuaW1wb3J0IHBhdGggZnJvbSAnbm9kZTpwYXRoJztcbmltcG9ydCB0eXBlIHsgQXVkaXRPdXRwdXRzLCBSdW5uZXJGdW5jdGlvbiB9IGZyb20gJ0Bjb2RlLXB1c2h1cC9tb2RlbHMnO1xuaW1wb3J0IHsgZW5zdXJlRGlyZWN0b3J5RXhpc3RzIH0gZnJvbSAnQGNvZGUtcHVzaHVwL3V0aWxzJztcbmltcG9ydCB7IERFRkFVTFRfQ0xJX0ZMQUdTIH0gZnJvbSAnLi9jb25zdGFudHMuanMnO1xuaW1wb3J0IHR5cGUgeyBMaWdodGhvdXNlQ2xpRmxhZ3MgfSBmcm9tICcuL3R5cGVzLmpzJztcbmltcG9ydCB7XG4gIGRldGVybWluZUFuZFNldExvZ0xldmVsLFxuICBnZXRDb25maWcsXG4gIG5vcm1hbGl6ZUF1ZGl0T3V0cHV0cyxcbiAgdG9BdWRpdE91dHB1dHMsXG59IGZyb20gJy4vdXRpbHMuanMnO1xuXG5leHBvcnQgZnVuY3Rpb24gY3JlYXRlUnVubmVyRnVuY3Rpb24oXG4gIHVybFVuZGVyVGVzdDogc3RyaW5nLFxuICBmbGFnczogTGlnaHRob3VzZUNsaUZsYWdzID0gREVGQVVMVF9DTElfRkxBR1MsXG4pOiBSdW5uZXJGdW5jdGlvbiB7XG4gIHJldHVybiBhc3luYyAoKTogUHJvbWlzZTxBdWRpdE91dHB1dHM+ID0+IHtcbiAgICBjb25zdCB7XG4gICAgICBjb25maWdQYXRoLFxuICAgICAgcHJlc2V0LFxuICAgICAgb3V0cHV0UGF0aCxcbiAgICAgIC4uLnBhcnNlZEZsYWdzXG4gICAgfTogUGFydGlhbDxMaWdodGhvdXNlQ2xpRmxhZ3M+ID0gZmxhZ3M7XG5cbiAgICBjb25zdCBsb2dMZXZlbCA9IGRldGVybWluZUFuZFNldExvZ0xldmVsKHBhcnNlZEZsYWdzKTtcblxuICAgIGNvbnN0IGNvbmZpZyA9IGF3YWl0IGdldENvbmZpZyh7IGNvbmZpZ1BhdGgsIHByZXNldCB9KTtcbiAgICBpZiAob3V0cHV0UGF0aCkge1xuICAgICAgYXdhaXQgZW5zdXJlRGlyZWN0b3J5RXhpc3RzKHBhdGguZGlybmFtZShvdXRwdXRQYXRoKSk7XG4gICAgfVxuXG4gICAgY29uc3QgZW5yaWNoZWRGbGFncyA9IHtcbiAgICAgIC4uLnBhcnNlZEZsYWdzLFxuICAgICAgbG9nTGV2ZWwsXG4gICAgICBvdXRwdXRQYXRoLFxuICAgIH07XG5cbiAgICBjb25zdCBydW5uZXJSZXN1bHQ6IHVua25vd24gPSBhd2FpdCBydW5MaWdodGhvdXNlKFxuICAgICAgdXJsVW5kZXJUZXN0LFxuICAgICAgZW5yaWNoZWRGbGFncyxcbiAgICAgIGNvbmZpZyxcbiAgICApO1xuXG4gICAgaWYgKHJ1bm5lclJlc3VsdCA9PSBudWxsKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoJ0xpZ2h0aG91c2UgZGlkIG5vdCBwcm9kdWNlIGEgcmVzdWx0LicpO1xuICAgIH1cblxuICAgIGNvbnN0IHsgbGhyIH0gPSBydW5uZXJSZXN1bHQgYXMgUnVubmVyUmVzdWx0O1xuICAgIGNvbnN0IGF1ZGl0T3V0cHV0cyA9IHRvQXVkaXRPdXRwdXRzKE9iamVjdC52YWx1ZXMobGhyLmF1ZGl0cyksIGZsYWdzKTtcblxuICAgIHJldHVybiBub3JtYWxpemVBdWRpdE91dHB1dHMoYXVkaXRPdXRwdXRzLCBlbnJpY2hlZEZsYWdzKTtcbiAgfTtcbn1cbiIsICJjb25zdCBfX2luamVjdGVkX2ZpbGVuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1saWdodGhvdXNlL3NyYy9saWIvcnVubmVyL3V0aWxzLnRzXCI7Y29uc3QgX19pbmplY3RlZF9kaXJuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1saWdodGhvdXNlL3NyYy9saWIvcnVubmVyXCI7Y29uc3QgX19pbmplY3RlZF9pbXBvcnRfbWV0YV91cmxfXyA9IFwiZmlsZTovLy9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1saWdodGhvdXNlL3NyYy9saWIvcnVubmVyL3V0aWxzLnRzXCI7aW1wb3J0IHsgYm9sZCB9IGZyb20gJ2Fuc2lzJztcbmltcG9ydCB0eXBlIHsgQ29uZmlnLCBGb3JtYXR0ZWRJY3UgfSBmcm9tICdsaWdodGhvdXNlJztcbmltcG9ydCBsb2cgZnJvbSAnbGlnaHRob3VzZS1sb2dnZXInO1xuaW1wb3J0IGRlc2t0b3BDb25maWcgZnJvbSAnbGlnaHRob3VzZS9jb3JlL2NvbmZpZy9kZXNrdG9wLWNvbmZpZy5qcyc7XG5pbXBvcnQgZXhwZXJpbWVudGFsQ29uZmlnIGZyb20gJ2xpZ2h0aG91c2UvY29yZS9jb25maWcvZXhwZXJpbWVudGFsLWNvbmZpZy5qcyc7XG5pbXBvcnQgcGVyZkNvbmZpZyBmcm9tICdsaWdodGhvdXNlL2NvcmUvY29uZmlnL3BlcmYtY29uZmlnLmpzJztcbmltcG9ydCB0eXBlIERldGFpbHMgZnJvbSAnbGlnaHRob3VzZS90eXBlcy9saHIvYXVkaXQtZGV0YWlscyc7XG5pbXBvcnQgdHlwZSB7IFJlc3VsdCB9IGZyb20gJ2xpZ2h0aG91c2UvdHlwZXMvbGhyL2F1ZGl0LXJlc3VsdCc7XG5pbXBvcnQgdHlwZSB7IEF1ZGl0T3V0cHV0LCBBdWRpdE91dHB1dHMgfSBmcm9tICdAY29kZS1wdXNodXAvbW9kZWxzJztcbmltcG9ydCB7XG4gIGZvcm1hdFJlcG9ydFNjb3JlLFxuICBpbXBvcnRNb2R1bGUsXG4gIHJlYWRKc29uRmlsZSxcbiAgdWksXG59IGZyb20gJ0Bjb2RlLXB1c2h1cC91dGlscyc7XG5pbXBvcnQgdHlwZSB7IExpZ2h0aG91c2VPcHRpb25zIH0gZnJvbSAnLi4vdHlwZXMuanMnO1xuaW1wb3J0IHsgbG9nVW5zdXBwb3J0ZWREZXRhaWxzLCB0b0F1ZGl0RGV0YWlscyB9IGZyb20gJy4vZGV0YWlscy9kZXRhaWxzLmpzJztcbmltcG9ydCB0eXBlIHsgTGlnaHRob3VzZUNsaUZsYWdzIH0gZnJvbSAnLi90eXBlcy5qcyc7XG5cbmV4cG9ydCBmdW5jdGlvbiBub3JtYWxpemVBdWRpdE91dHB1dHMoXG4gIGF1ZGl0T3V0cHV0czogQXVkaXRPdXRwdXRzLFxuICBmbGFnczogTGlnaHRob3VzZU9wdGlvbnMgPSB7IHNraXBBdWRpdHM6IFtdIH0sXG4pOiBBdWRpdE91dHB1dHMge1xuICBjb25zdCB0b1NraXAgPSBuZXcgU2V0KGZsYWdzLnNraXBBdWRpdHMgPz8gW10pO1xuICByZXR1cm4gYXVkaXRPdXRwdXRzLmZpbHRlcigoeyBzbHVnIH0pID0+ICF0b1NraXAuaGFzKHNsdWcpKTtcbn1cblxuZXhwb3J0IGNsYXNzIExpZ2h0aG91c2VBdWRpdFBhcnNpbmdFcnJvciBleHRlbmRzIEVycm9yIHtcbiAgY29uc3RydWN0b3Ioc2x1Zzogc3RyaW5nLCBlcnJvcjogRXJyb3IpIHtcbiAgICBzdXBlcihgXFxuQXVkaXQgJHtib2xkKHNsdWcpfSBmYWlsZWQgcGFyc2luZyBkZXRhaWxzOiBcXG4ke2Vycm9yLm1lc3NhZ2V9YCk7XG4gIH1cbn1cblxuZnVuY3Rpb24gZm9ybWF0QmFzZUF1ZGl0T3V0cHV0KGxockF1ZGl0OiBSZXN1bHQpOiBBdWRpdE91dHB1dCB7XG4gIGNvbnN0IHtcbiAgICBpZDogc2x1ZyxcbiAgICBzY29yZSxcbiAgICBudW1lcmljVmFsdWUsXG4gICAgZGlzcGxheVZhbHVlLFxuICAgIHNjb3JlRGlzcGxheU1vZGUsXG4gIH0gPSBsaHJBdWRpdDtcbiAgcmV0dXJuIHtcbiAgICBzbHVnLFxuICAgIHNjb3JlOiBzY29yZSA/PyAxLFxuICAgIHZhbHVlOiBudW1lcmljVmFsdWUgPz8gc2NvcmUgPz8gMCxcbiAgICBkaXNwbGF5VmFsdWU6XG4gICAgICBkaXNwbGF5VmFsdWUgPz9cbiAgICAgIChzY29yZURpc3BsYXlNb2RlID09PSAnYmluYXJ5J1xuICAgICAgICA/IHNjb3JlID09PSAxXG4gICAgICAgICAgPyAncGFzc2VkJ1xuICAgICAgICAgIDogJ2ZhaWxlZCdcbiAgICAgICAgOiBzY29yZVxuICAgICAgICAgID8gYCR7Zm9ybWF0UmVwb3J0U2NvcmUoc2NvcmUpfSVgXG4gICAgICAgICAgOiB1bmRlZmluZWQpLFxuICB9O1xufVxuXG5mdW5jdGlvbiBwcm9jZXNzQXVkaXREZXRhaWxzKFxuICBhdWRpdE91dHB1dDogQXVkaXRPdXRwdXQsXG4gIGRldGFpbHM6IEZvcm1hdHRlZEljdTxEZXRhaWxzPixcbik6IEF1ZGl0T3V0cHV0IHtcbiAgdHJ5IHtcbiAgICBjb25zdCBwYXJzZWREZXRhaWxzID0gdG9BdWRpdERldGFpbHMoZGV0YWlscyk7XG4gICAgcmV0dXJuIE9iamVjdC5rZXlzKHBhcnNlZERldGFpbHMpLmxlbmd0aCA+IDBcbiAgICAgID8geyAuLi5hdWRpdE91dHB1dCwgZGV0YWlsczogcGFyc2VkRGV0YWlscyB9XG4gICAgICA6IGF1ZGl0T3V0cHV0O1xuICB9IGNhdGNoIChlcnJvcikge1xuICAgIHRocm93IG5ldyBMaWdodGhvdXNlQXVkaXRQYXJzaW5nRXJyb3IoYXVkaXRPdXRwdXQuc2x1ZywgZXJyb3IgYXMgRXJyb3IpO1xuICB9XG59XG5cbmV4cG9ydCBmdW5jdGlvbiB0b0F1ZGl0T3V0cHV0cyhcbiAgbGhyQXVkaXRzOiBSZXN1bHRbXSxcbiAgeyB2ZXJib3NlID0gZmFsc2UgfTogeyB2ZXJib3NlPzogYm9vbGVhbiB9ID0ge30sXG4pOiBBdWRpdE91dHB1dHMge1xuICBpZiAodmVyYm9zZSkge1xuICAgIGxvZ1Vuc3VwcG9ydGVkRGV0YWlscyhsaHJBdWRpdHMpO1xuICB9XG4gIHJldHVybiBsaHJBdWRpdHMubWFwKGF1ZGl0ID0+IHtcbiAgICBjb25zdCBhdWRpdE91dHB1dCA9IGZvcm1hdEJhc2VBdWRpdE91dHB1dChhdWRpdCk7XG5cbiAgICByZXR1cm4gYXVkaXQuZGV0YWlscyA9PSBudWxsXG4gICAgICA/IGF1ZGl0T3V0cHV0XG4gICAgICA6IHByb2Nlc3NBdWRpdERldGFpbHMoYXVkaXRPdXRwdXQsIGF1ZGl0LmRldGFpbHMpO1xuICB9KTtcbn1cblxuZXhwb3J0IHR5cGUgTGlnaHRob3VzZUxvZ0xldmVsID1cbiAgfCAndmVyYm9zZSdcbiAgfCAnZXJyb3InXG4gIHwgJ2luZm8nXG4gIHwgJ3NpbGVudCdcbiAgfCAnd2FybidcbiAgfCB1bmRlZmluZWQ7XG5leHBvcnQgZnVuY3Rpb24gZGV0ZXJtaW5lQW5kU2V0TG9nTGV2ZWwoe1xuICB2ZXJib3NlLFxuICBxdWlldCxcbn06IHtcbiAgdmVyYm9zZT86IGJvb2xlYW47XG4gIHF1aWV0PzogYm9vbGVhbjtcbn0gPSB7fSk6IExpZ2h0aG91c2VMb2dMZXZlbCB7XG4gIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBmdW5jdGlvbmFsL25vLWxldFxuICBsZXQgbG9nTGV2ZWw6IExpZ2h0aG91c2VMb2dMZXZlbCA9ICdpbmZvJztcbiAgLy8gc2V0IGxvZ2dpbmcgcHJlZmVyZW5jZXNcbiAgaWYgKHZlcmJvc2UpIHtcbiAgICBsb2dMZXZlbCA9ICd2ZXJib3NlJztcbiAgfSBlbHNlIGlmIChxdWlldCkge1xuICAgIGxvZ0xldmVsID0gJ3NpbGVudCc7XG4gIH1cblxuICBsb2cuc2V0TGV2ZWwobG9nTGV2ZWwpO1xuXG4gIHJldHVybiBsb2dMZXZlbDtcbn1cblxuZXhwb3J0IHR5cGUgQ29uZmlnT3B0aW9ucyA9IFBhcnRpYWw8XG4gIFBpY2s8TGlnaHRob3VzZUNsaUZsYWdzLCAnY29uZmlnUGF0aCcgfCAncHJlc2V0Jz5cbj47XG5cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBnZXRDb25maWcoXG4gIG9wdGlvbnM6IENvbmZpZ09wdGlvbnMgPSB7fSxcbik6IFByb21pc2U8Q29uZmlnIHwgdW5kZWZpbmVkPiB7XG4gIGNvbnN0IHsgY29uZmlnUGF0aDogZmlsZXBhdGgsIHByZXNldCB9ID0gb3B0aW9ucztcblxuICBpZiAoZmlsZXBhdGggIT0gbnVsbCkge1xuICAgIGlmIChmaWxlcGF0aC5lbmRzV2l0aCgnLmpzb24nKSkge1xuICAgICAgLy8gUmVzb2x2ZSB0aGUgY29uZmlnIGZpbGUgcGF0aCByZWxhdGl2ZSB0byB3aGVyZSBjbGkgd2FzIGNhbGxlZC5cbiAgICAgIHJldHVybiByZWFkSnNvbkZpbGU8Q29uZmlnPihmaWxlcGF0aCk7XG4gICAgfSBlbHNlIGlmICgvXFwuKHRzfGpzfG1qcykkLy50ZXN0KGZpbGVwYXRoKSkge1xuICAgICAgcmV0dXJuIGltcG9ydE1vZHVsZTxDb25maWc+KHsgZmlsZXBhdGgsIGZvcm1hdDogJ2VzbScgfSk7XG4gICAgfSBlbHNlIHtcbiAgICAgIHVpKCkubG9nZ2VyLmluZm8oYEZvcm1hdCBvZiBmaWxlICR7ZmlsZXBhdGh9IG5vdCBzdXBwb3J0ZWRgKTtcbiAgICB9XG4gIH0gZWxzZSBpZiAocHJlc2V0ICE9IG51bGwpIHtcbiAgICBzd2l0Y2ggKHByZXNldCkge1xuICAgICAgY2FzZSAnZGVza3RvcCc6XG4gICAgICAgIHJldHVybiBkZXNrdG9wQ29uZmlnO1xuICAgICAgY2FzZSAncGVyZic6XG4gICAgICAgIHJldHVybiBwZXJmQ29uZmlnIGFzIENvbmZpZztcbiAgICAgIGNhc2UgJ2V4cGVyaW1lbnRhbCc6XG4gICAgICAgIHJldHVybiBleHBlcmltZW50YWxDb25maWcgYXMgQ29uZmlnO1xuICAgICAgZGVmYXVsdDpcbiAgICAgICAgLy8gYXMgcHJlc2V0IGlzIGEgc3RyaW5nIGxpdGVyYWwgdGhlIGRlZmF1bHQgY2FzZSBoZXJlIGlzIG5vcm1hbGx5IGNhdWdodCBieSBUUyBhbmQgbm90IHBvc3NpYmxlIHRvIGhhcHBlbi4gTm93IGluIHJlYWxpdHkgaXQgY2FuIGhhcHBlbiBhbmQgcHJlc2V0IGNvdWxkIGJlIGEgc3RyaW5nIG5vdCBpbmNsdWRlZCBpbiB0aGUgbGl0ZXJhbC5cbiAgICAgICAgLy8gVGhlcmVmb3JlLCB3ZSBoYXZlIHRvIHVzZSBgYXMgc3RyaW5nYC4gT3RoZXJ3aXNlLCBpdCB3aWxsIGNvbnNpZGVyIHByZXNldCBhcyB0eXBlIG5ldmVyXG4gICAgICAgIHVpKCkubG9nZ2VyLmluZm8oYFByZXNldCBcIiR7cHJlc2V0IGFzIHN0cmluZ31cIiBpcyBub3Qgc3VwcG9ydGVkYCk7XG4gICAgfVxuICB9XG4gIHJldHVybiB1bmRlZmluZWQ7XG59XG4iLCAiY29uc3QgX19pbmplY3RlZF9maWxlbmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tbGlnaHRob3VzZS9zcmMvbGliL3J1bm5lci9kZXRhaWxzL2RldGFpbHMudHNcIjtjb25zdCBfX2luamVjdGVkX2Rpcm5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWxpZ2h0aG91c2Uvc3JjL2xpYi9ydW5uZXIvZGV0YWlsc1wiO2NvbnN0IF9faW5qZWN0ZWRfaW1wb3J0X21ldGFfdXJsX18gPSBcImZpbGU6Ly8vaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tbGlnaHRob3VzZS9zcmMvbGliL3J1bm5lci9kZXRhaWxzL2RldGFpbHMudHNcIjtpbXBvcnQgeyBib2xkLCB5ZWxsb3cgfSBmcm9tICdhbnNpcyc7XG5pbXBvcnQgdHlwZSB7IEZvcm1hdHRlZEljdSB9IGZyb20gJ2xpZ2h0aG91c2UnO1xuaW1wb3J0IHR5cGUgRGV0YWlscyBmcm9tICdsaWdodGhvdXNlL3R5cGVzL2xoci9hdWRpdC1kZXRhaWxzJztcbmltcG9ydCB0eXBlIHsgUmVzdWx0IH0gZnJvbSAnbGlnaHRob3VzZS90eXBlcy9saHIvYXVkaXQtcmVzdWx0JztcbmltcG9ydCB0eXBlIHsgQXVkaXREZXRhaWxzLCBUYWJsZSB9IGZyb20gJ0Bjb2RlLXB1c2h1cC9tb2RlbHMnO1xuaW1wb3J0IHsgdWkgfSBmcm9tICdAY29kZS1wdXNodXAvdXRpbHMnO1xuaW1wb3J0IHsgUExVR0lOX1NMVUcgfSBmcm9tICcuLi9jb25zdGFudHMuanMnO1xuaW1wb3J0IHsgcGFyc2VPcHBvcnR1bml0eVRvQXVkaXREZXRhaWxzVGFibGUgfSBmcm9tICcuL29wcG9ydHVuaXR5LnR5cGUuanMnO1xuaW1wb3J0IHsgcGFyc2VUYWJsZVRvQXVkaXREZXRhaWxzVGFibGUgfSBmcm9tICcuL3RhYmxlLnR5cGUuanMnO1xuXG5leHBvcnQgZnVuY3Rpb24gdG9BdWRpdERldGFpbHM8VCBleHRlbmRzIEZvcm1hdHRlZEljdTxEZXRhaWxzPj4oXG4gIGRldGFpbHM6IFQgfCB1bmRlZmluZWQsXG4pOiBBdWRpdERldGFpbHMge1xuICBpZiAoZGV0YWlscyA9PSBudWxsKSB7XG4gICAgcmV0dXJuIHt9O1xuICB9XG5cbiAgY29uc3QgeyB0eXBlIH0gPSBkZXRhaWxzO1xuXG4gIHN3aXRjaCAodHlwZSkge1xuICAgIGNhc2UgJ3RhYmxlJzpcbiAgICAgIGNvbnN0IHRhYmxlOiBUYWJsZSB8IHVuZGVmaW5lZCA9IHBhcnNlVGFibGVUb0F1ZGl0RGV0YWlsc1RhYmxlKGRldGFpbHMpO1xuICAgICAgcmV0dXJuIHRhYmxlID8geyB0YWJsZSB9IDoge307XG4gICAgY2FzZSAnb3Bwb3J0dW5pdHknOlxuICAgICAgY29uc3Qgb3Bwb3J0dW5pdHk6IFRhYmxlIHwgdW5kZWZpbmVkID1cbiAgICAgICAgcGFyc2VPcHBvcnR1bml0eVRvQXVkaXREZXRhaWxzVGFibGUoZGV0YWlscyk7XG4gICAgICByZXR1cm4gb3Bwb3J0dW5pdHkgPyB7IHRhYmxlOiBvcHBvcnR1bml0eSB9IDoge307XG4gIH1cbiAgcmV0dXJuIHt9O1xufVxuXG4vLyBAVE9ETyBpbXBsZW1lbnQgYWxsIGRldGFpbHNcbmV4cG9ydCBjb25zdCB1bnN1cHBvcnRlZERldGFpbFR5cGVzID0gbmV3IFNldChbXG4gICdkZWJ1Z2RhdGEnLFxuICAndHJlZW1hcC1kYXRhJyxcbiAgJ3NjcmVlbnNob3QnLFxuICAnZmlsbXN0cmlwJyxcbiAgJ2NyaXRpY2FscmVxdWVzdGNoYWluJyxcbl0pO1xuXG5leHBvcnQgZnVuY3Rpb24gbG9nVW5zdXBwb3J0ZWREZXRhaWxzKFxuICBsaHJBdWRpdHM6IFJlc3VsdFtdLFxuICB7IGRpc3BsYXlDb3VudCA9IDMgfTogeyBkaXNwbGF5Q291bnQ/OiBudW1iZXIgfSA9IHt9LFxuKSB7XG4gIGNvbnN0IHNsdWdzV2l0aERldGFpbFBhcnNpbmdFcnJvcnMgPSBbXG4gICAgLi4ubmV3IFNldChcbiAgICAgIGxockF1ZGl0c1xuICAgICAgICAuZmlsdGVyKCh7IGRldGFpbHMgfSkgPT5cbiAgICAgICAgICB1bnN1cHBvcnRlZERldGFpbFR5cGVzLmhhcyhkZXRhaWxzPy50eXBlIGFzIHN0cmluZyksXG4gICAgICAgIClcbiAgICAgICAgLm1hcCgoeyBkZXRhaWxzIH0pID0+IGRldGFpbHM/LnR5cGUpLFxuICAgICksXG4gIF07XG4gIGlmIChzbHVnc1dpdGhEZXRhaWxQYXJzaW5nRXJyb3JzLmxlbmd0aCA+IDApIHtcbiAgICBjb25zdCBwb3N0Rml4ID0gKGNvdW50OiBudW1iZXIpID0+XG4gICAgICBjb3VudCA+IGRpc3BsYXlDb3VudCA/IGAgYW5kICR7Y291bnQgLSBkaXNwbGF5Q291bnR9IG1vcmUuYCA6ICcnO1xuICAgIHVpKCkubG9nZ2VyLmRlYnVnKFxuICAgICAgYCR7eWVsbG93KCdcdTI2QTAnKX0gUGx1Z2luICR7Ym9sZChcbiAgICAgICAgUExVR0lOX1NMVUcsXG4gICAgICApfSBza2lwcGVkIHBhcnNpbmcgb2YgdW5zdXBwb3J0ZWQgYXVkaXQgZGV0YWlsczogJHtib2xkKFxuICAgICAgICBzbHVnc1dpdGhEZXRhaWxQYXJzaW5nRXJyb3JzLnNsaWNlKDAsIGRpc3BsYXlDb3VudCkuam9pbignLCAnKSxcbiAgICAgICl9JHtwb3N0Rml4KHNsdWdzV2l0aERldGFpbFBhcnNpbmdFcnJvcnMubGVuZ3RoKX1gLFxuICAgICk7XG4gIH1cbn1cbiIsICJjb25zdCBfX2luamVjdGVkX2ZpbGVuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1saWdodGhvdXNlL3NyYy9saWIvcnVubmVyL2RldGFpbHMvb3Bwb3J0dW5pdHkudHlwZS50c1wiO2NvbnN0IF9faW5qZWN0ZWRfZGlybmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tbGlnaHRob3VzZS9zcmMvbGliL3J1bm5lci9kZXRhaWxzXCI7Y29uc3QgX19pbmplY3RlZF9pbXBvcnRfbWV0YV91cmxfXyA9IFwiZmlsZTovLy9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1saWdodGhvdXNlL3NyYy9saWIvcnVubmVyL2RldGFpbHMvb3Bwb3J0dW5pdHkudHlwZS50c1wiO2ltcG9ydCB0eXBlIERldGFpbHMgZnJvbSAnbGlnaHRob3VzZS90eXBlcy9saHIvYXVkaXQtZGV0YWlscyc7XG5pbXBvcnQge1xuICB0eXBlIFRhYmxlLFxuICB0eXBlIFRhYmxlUm93T2JqZWN0LFxuICB0YWJsZVNjaGVtYSxcbn0gZnJvbSAnQGNvZGUtcHVzaHVwL21vZGVscyc7XG5pbXBvcnQgeyBmb3JtYXRCeXRlcywgZm9ybWF0RHVyYXRpb24sIGh0bWwgfSBmcm9tICdAY29kZS1wdXNodXAvdXRpbHMnO1xuaW1wb3J0IHsgcGFyc2VUYWJsZUNvbHVtbnMsIHBhcnNlVGFibGVFbnRyeSB9IGZyb20gJy4vdGFibGUudHlwZS5qcyc7XG5pbXBvcnQgeyBMaWdodGhvdXNlQXVkaXREZXRhaWxzUGFyc2luZ0Vycm9yIH0gZnJvbSAnLi91dGlscy5qcyc7XG5cbmV4cG9ydCBmdW5jdGlvbiBwYXJzZU9wcG9ydHVuaXR5VG9BdWRpdERldGFpbHNUYWJsZShcbiAgZGV0YWlsczogRGV0YWlscy5PcHBvcnR1bml0eSxcbik6IFRhYmxlIHwgdW5kZWZpbmVkIHtcbiAgY29uc3QgeyBoZWFkaW5nczogcmF3SGVhZGluZ3MsIGl0ZW1zIH0gPSBkZXRhaWxzO1xuXG4gIGlmIChpdGVtcy5sZW5ndGggPT09IDApIHtcbiAgICByZXR1cm4gdW5kZWZpbmVkO1xuICB9XG5cbiAgdHJ5IHtcbiAgICByZXR1cm4gdGFibGVTY2hlbWEoKS5wYXJzZSh7XG4gICAgICB0aXRsZTogJ09wcG9ydHVuaXR5JyxcbiAgICAgIGNvbHVtbnM6IHBhcnNlVGFibGVDb2x1bW5zKHJhd0hlYWRpbmdzKSxcbiAgICAgIHJvd3M6IGl0ZW1zLm1hcChyb3cgPT4gcGFyc2VPcHBvcnR1bml0eUl0ZW1Ub1RhYmxlUm93KHJvdywgcmF3SGVhZGluZ3MpKSxcbiAgICB9KTtcbiAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICB0aHJvdyBuZXcgTGlnaHRob3VzZUF1ZGl0RGV0YWlsc1BhcnNpbmdFcnJvcihcbiAgICAgICdvcHBvcnR1bml0eScsXG4gICAgICB7IGl0ZW1zLCBoZWFkaW5nczogcmF3SGVhZGluZ3MgfSxcbiAgICAgIChlcnJvciBhcyBFcnJvcikubWVzc2FnZS50b1N0cmluZygpLFxuICAgICk7XG4gIH1cbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHBhcnNlT3Bwb3J0dW5pdHlJdGVtVG9UYWJsZVJvdyhcbiAgb3Bwb3J0dW5pdHlJdGVtOiBEZXRhaWxzLk9wcG9ydHVuaXR5SXRlbSxcbiAgaGVhZGluZ3M6IERldGFpbHMuVGFibGVDb2x1bW5IZWFkaW5nW10sXG4pOiBUYWJsZVJvd09iamVjdCB7XG4gIGNvbnN0IGtleXMgPSBuZXcgU2V0KGhlYWRpbmdzLm1hcCgoeyBrZXkgfSkgPT4ga2V5KSk7XG4gIGNvbnN0IHZhbHVlVHlwZXNCeUtleSA9IG5ldyBNYXAoXG4gICAgaGVhZGluZ3MubWFwKCh7IGtleSwgdmFsdWVUeXBlIH0pID0+IFtrZXksIHZhbHVlVHlwZV0pLFxuICApO1xuXG4gIHJldHVybiB7XG4gICAgLi4uKE9iamVjdC5mcm9tRW50cmllcyhcbiAgICAgIE9iamVjdC5lbnRyaWVzKG9wcG9ydHVuaXR5SXRlbSlcbiAgICAgICAgLy8gZm9yd2FyZCBvbmx5IHByb3BlcnRpZXMgd2l0aCBhIGdpdmVuIHZhbHVlXG4gICAgICAgIC5maWx0ZXIoKFtrZXldKSA9PiBrZXlzLmhhcyhrZXkpKVxuICAgICAgICAubWFwKChba2V5LCB2YWx1ZV0pID0+IHtcbiAgICAgICAgICBjb25zdCB2YWx1ZVR5cGUgPSB2YWx1ZVR5cGVzQnlLZXkuZ2V0KGtleSkgYXMgRGV0YWlscy5JdGVtVmFsdWVUeXBlO1xuICAgICAgICAgIHJldHVybiBwYXJzZU9wcG9ydHVuaXR5RW50cnkoW2tleSwgdmFsdWVdLCB2YWx1ZVR5cGUpO1xuICAgICAgICB9KSxcbiAgICApIGFzIFRhYmxlUm93T2JqZWN0KSxcbiAgfTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHBhcnNlT3Bwb3J0dW5pdHlFbnRyeShcbiAgW2tleSwgdmFsdWVdOiBbXG4gICAga2V5b2YgRGV0YWlscy5PcHBvcnR1bml0eUl0ZW0sXG4gICAgRGV0YWlscy5PcHBvcnR1bml0eUl0ZW1bc3RyaW5nXSxcbiAgXSxcbiAgdmFsdWVUeXBlOiBEZXRhaWxzLkl0ZW1WYWx1ZVR5cGUsXG4pIHtcbiAgc3dpdGNoIChrZXkpIHtcbiAgICBjYXNlICd1cmwnOlxuICAgICAgcmV0dXJuIFtrZXksIGh0bWwubGluayhTdHJpbmcodmFsdWUpKV07XG4gICAgY2FzZSAnd2FzdGVkUGVyY2VudCc6XG4gICAgICByZXR1cm4gW2tleSwgYCR7TnVtYmVyKHZhbHVlKS50b0ZpeGVkKDIpfSAlYF07XG4gICAgY2FzZSAndG90YWxCeXRlcyc6XG4gICAgY2FzZSAnd2FzdGVkQnl0ZXMnOlxuICAgICAgcmV0dXJuIFtrZXksIGZvcm1hdEJ5dGVzKE51bWJlcih2YWx1ZSkpXTtcbiAgICBjYXNlICd3YXN0ZWRNcyc6XG4gICAgICByZXR1cm4gW2tleSwgZm9ybWF0RHVyYXRpb24oTnVtYmVyKHZhbHVlKSldO1xuICAgIGRlZmF1bHQ6XG4gICAgICByZXR1cm4gcGFyc2VUYWJsZUVudHJ5KFtrZXksIHZhbHVlXSwgdmFsdWVUeXBlKTtcbiAgfVxufVxuIiwgImNvbnN0IF9faW5qZWN0ZWRfZmlsZW5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWxpZ2h0aG91c2Uvc3JjL2xpYi9ydW5uZXIvZGV0YWlscy90YWJsZS50eXBlLnRzXCI7Y29uc3QgX19pbmplY3RlZF9kaXJuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1saWdodGhvdXNlL3NyYy9saWIvcnVubmVyL2RldGFpbHNcIjtjb25zdCBfX2luamVjdGVkX2ltcG9ydF9tZXRhX3VybF9fID0gXCJmaWxlOi8vL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWxpZ2h0aG91c2Uvc3JjL2xpYi9ydW5uZXIvZGV0YWlscy90YWJsZS50eXBlLnRzXCI7aW1wb3J0IHR5cGUgRGV0YWlscyBmcm9tICdsaWdodGhvdXNlL3R5cGVzL2xoci9hdWRpdC1kZXRhaWxzJztcbmltcG9ydCB7XG4gIHR5cGUgVGFibGUsXG4gIHR5cGUgVGFibGVDb2x1bW5PYmplY3QsXG4gIHR5cGUgVGFibGVSb3dPYmplY3QsXG4gIHRhYmxlU2NoZW1hLFxufSBmcm9tICdAY29kZS1wdXNodXAvbW9kZWxzJztcbmltcG9ydCB7IGZvcm1hdFRhYmxlSXRlbVByb3BlcnR5VmFsdWUgfSBmcm9tICcuL2l0ZW0tdmFsdWUuanMnO1xuaW1wb3J0IHsgTGlnaHRob3VzZUF1ZGl0RGV0YWlsc1BhcnNpbmdFcnJvciB9IGZyb20gJy4vdXRpbHMuanMnO1xuXG5leHBvcnQgZnVuY3Rpb24gcGFyc2VUYWJsZVRvQXVkaXREZXRhaWxzVGFibGUoXG4gIGRldGFpbHM6IERldGFpbHMuVGFibGUsXG4pOiBUYWJsZSB8IHVuZGVmaW5lZCB7XG4gIGNvbnN0IHsgaGVhZGluZ3M6IHJhd0hlYWRpbmdzLCBpdGVtcyB9ID0gZGV0YWlscztcblxuICBpZiAoaXRlbXMubGVuZ3RoID09PSAwKSB7XG4gICAgcmV0dXJuIHVuZGVmaW5lZDtcbiAgfVxuXG4gIHRyeSB7XG4gICAgcmV0dXJuIHRhYmxlU2NoZW1hKCkucGFyc2Uoe1xuICAgICAgY29sdW1uczogcGFyc2VUYWJsZUNvbHVtbnMocmF3SGVhZGluZ3MpLFxuICAgICAgcm93czogaXRlbXMubWFwKHJvdyA9PiBwYXJzZVRhYmxlUm93KHJvdywgcmF3SGVhZGluZ3MpKSxcbiAgICB9KTtcbiAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICB0aHJvdyBuZXcgTGlnaHRob3VzZUF1ZGl0RGV0YWlsc1BhcnNpbmdFcnJvcihcbiAgICAgICd0YWJsZScsXG4gICAgICB7IGl0ZW1zLCBoZWFkaW5nczogcmF3SGVhZGluZ3MgfSxcbiAgICAgIChlcnJvciBhcyBFcnJvcikubWVzc2FnZS50b1N0cmluZygpLFxuICAgICk7XG4gIH1cbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHBhcnNlVGFibGVDb2x1bW5zKFxuICByYXdIZWFkaW5nczogRGV0YWlscy5UYWJsZUNvbHVtbkhlYWRpbmdbXSxcbik6IFRhYmxlQ29sdW1uT2JqZWN0W10ge1xuICByZXR1cm4gcmF3SGVhZGluZ3MubWFwKCh7IGtleSwgbGFiZWwgfSkgPT4gKHtcbiAgICBrZXk6IGtleSA/PyAnJyxcbiAgICAuLi4odHlwZW9mIGxhYmVsID09PSAnc3RyaW5nJyAmJiBsYWJlbC5sZW5ndGggPiAwID8geyBsYWJlbCB9IDoge30pLFxuICAgIGFsaWduOiAnbGVmdCcsXG4gIH0pKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHBhcnNlVGFibGVSb3coXG4gIHRhYmxlSXRlbTogRGV0YWlscy5UYWJsZUl0ZW0sXG4gIGhlYWRpbmdzOiBEZXRhaWxzLlRhYmxlQ29sdW1uSGVhZGluZ1tdLFxuKTogVGFibGVSb3dPYmplY3Qge1xuICBjb25zdCBrZXlzID0gbmV3IFNldChoZWFkaW5ncy5tYXAoKHsga2V5IH0pID0+IGtleSkpO1xuICBjb25zdCB2YWx1ZVR5cGVzQnlLZXkgPSBuZXcgTWFwKFxuICAgIGhlYWRpbmdzLm1hcCgoeyBrZXksIHZhbHVlVHlwZSB9KSA9PiBba2V5LCB2YWx1ZVR5cGVdKSxcbiAgKTtcblxuICByZXR1cm4gT2JqZWN0LmZyb21FbnRyaWVzKFxuICAgIE9iamVjdC5lbnRyaWVzKHRhYmxlSXRlbSlcbiAgICAgIC5maWx0ZXIoKFtrZXldKSA9PiBrZXlzLmhhcyhrZXkpKVxuICAgICAgLm1hcCgoW2tleSwgdmFsdWVdKSA9PiB7XG4gICAgICAgIGNvbnN0IHZhbHVlVHlwZSA9IHZhbHVlVHlwZXNCeUtleS5nZXQoa2V5KTtcbiAgICAgICAgcmV0dXJuIHBhcnNlVGFibGVFbnRyeShba2V5LCB2YWx1ZV0sIHZhbHVlVHlwZSk7XG4gICAgICB9KSxcbiAgKSBhcyBUYWJsZVJvd09iamVjdDtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHBhcnNlVGFibGVFbnRyeTxUIGV4dGVuZHMgRGV0YWlscy5UYWJsZUl0ZW0+KFxuICBba2V5LCB2YWx1ZV06IFtrZXlvZiBULCBUW2tleW9mIFRdXSxcbiAgdmFsdWVUeXBlPzogRGV0YWlscy5JdGVtVmFsdWVUeXBlLFxuKTogW2tleW9mIFQsIERldGFpbHMuSXRlbVZhbHVlIHwgdW5kZWZpbmVkXSB7XG4gIGlmICh2YWx1ZSA9PSBudWxsKSB7XG4gICAgcmV0dXJuIFtrZXksIHZhbHVlXTtcbiAgfVxuXG4gIHJldHVybiBba2V5LCBmb3JtYXRUYWJsZUl0ZW1Qcm9wZXJ0eVZhbHVlKHZhbHVlLCB2YWx1ZVR5cGUpXTtcbn1cbiIsICJjb25zdCBfX2luamVjdGVkX2ZpbGVuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1saWdodGhvdXNlL3NyYy9saWIvcnVubmVyL2RldGFpbHMvaXRlbS12YWx1ZS50c1wiO2NvbnN0IF9faW5qZWN0ZWRfZGlybmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tbGlnaHRob3VzZS9zcmMvbGliL3J1bm5lci9kZXRhaWxzXCI7Y29uc3QgX19pbmplY3RlZF9pbXBvcnRfbWV0YV91cmxfXyA9IFwiZmlsZTovLy9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1saWdodGhvdXNlL3NyYy9saWIvcnVubmVyL2RldGFpbHMvaXRlbS12YWx1ZS50c1wiO2ltcG9ydCB7IGJvbGQgfSBmcm9tICdhbnNpcyc7XG5pbXBvcnQgdHlwZSB7IEljdU1lc3NhZ2UgfSBmcm9tICdsaWdodGhvdXNlJztcbmltcG9ydCB0eXBlIERldGFpbHMgZnJvbSAnbGlnaHRob3VzZS90eXBlcy9saHIvYXVkaXQtZGV0YWlscyc7XG5pbXBvcnQge1xuICBmb3JtYXRCeXRlcyxcbiAgZm9ybWF0RHVyYXRpb24sXG4gIGh0bWwsXG4gIHRydW5jYXRlVGV4dCxcbiAgdWksXG59IGZyb20gJ0Bjb2RlLXB1c2h1cC91dGlscyc7XG5cbmV4cG9ydCB0eXBlIFByaW1pdGl2ZUl0ZW1WYWx1ZSA9IHN0cmluZyB8IG51bWJlciB8IGJvb2xlYW47XG5leHBvcnQgdHlwZSBPYmplY3RJdGVtVmFsdWUgPSBFeGNsdWRlPFxuICBEZXRhaWxzLkl0ZW1WYWx1ZSxcbiAgUHJpbWl0aXZlSXRlbVZhbHVlIHwgSWN1TWVzc2FnZVxuPjtcbmV4cG9ydCB0eXBlIFNpbXBsZUl0ZW1WYWx1ZSA9XG4gIHwgRXh0cmFjdDxcbiAgICAgIE9iamVjdEl0ZW1WYWx1ZSxcbiAgICAgIERldGFpbHMuTnVtZXJpY1ZhbHVlIHwgRGV0YWlscy5Db2RlVmFsdWUgfCBEZXRhaWxzLlVybFZhbHVlXG4gICAgPlxuICB8IFByaW1pdGl2ZUl0ZW1WYWx1ZTtcblxuZXhwb3J0IGZ1bmN0aW9uIHRyaW1TbGljZShpdGVtPzogUHJpbWl0aXZlSXRlbVZhbHVlLCBtYXhMZW5ndGggPSAwKSB7XG4gIGNvbnN0IHN0ciA9IFN0cmluZyhpdGVtKS50cmltKCk7XG4gIHJldHVybiBtYXhMZW5ndGggPiAwID8gc3RyLnNsaWNlKDAsIG1heExlbmd0aCkgOiBzdHI7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBwYXJzZU5vZGVWYWx1ZShub2RlPzogRGV0YWlscy5Ob2RlVmFsdWUpOiBzdHJpbmcge1xuICBjb25zdCB7IHNlbGVjdG9yID0gJycgfSA9IG5vZGUgPz8ge307XG4gIHJldHVybiBzZWxlY3Rvcjtcbn1cblxuLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIG1heC1saW5lcy1wZXItZnVuY3Rpb25cbmV4cG9ydCBmdW5jdGlvbiBmb3JtYXRUYWJsZUl0ZW1Qcm9wZXJ0eVZhbHVlKFxuICBpdGVtVmFsdWU/OiBEZXRhaWxzLkl0ZW1WYWx1ZSxcbiAgaXRlbVZhbHVlRm9ybWF0PzogRGV0YWlscy5JdGVtVmFsdWVUeXBlLFxuKSB7XG4gIC8vIG51bGxcbiAgaWYgKGl0ZW1WYWx1ZSA9PSBudWxsKSB7XG4gICAgcmV0dXJuICcnO1xuICB9XG5cbiAgLy8gUHJpbWl0aXZlIFZhbHVlc1xuICBpZiAoaXRlbVZhbHVlRm9ybWF0ID09IG51bGwpIHtcbiAgICBpZiAodHlwZW9mIGl0ZW1WYWx1ZSA9PT0gJ3N0cmluZycpIHtcbiAgICAgIHJldHVybiB0cmltU2xpY2UoaXRlbVZhbHVlKTtcbiAgICB9XG5cbiAgICBpZiAodHlwZW9mIGl0ZW1WYWx1ZSA9PT0gJ251bWJlcicpIHtcbiAgICAgIHJldHVybiBOdW1iZXIoaXRlbVZhbHVlKTtcbiAgICB9XG5cbiAgICBpZiAodHlwZW9mIGl0ZW1WYWx1ZSA9PT0gJ2Jvb2xlYW4nKSB7XG4gICAgICByZXR1cm4gaXRlbVZhbHVlO1xuICAgIH1cbiAgfVxuXG4gIGNvbnN0IHBhcnNlZEl0ZW1WYWx1ZSA9IHBhcnNlVGFibGVJdGVtUHJvcGVydHlWYWx1ZShpdGVtVmFsdWUpO1xuXG4gIC8qIGVzbGludC1kaXNhYmxlIEB0eXBlc2NyaXB0LWVzbGludC9uby1tYWdpYy1udW1iZXJzICovXG4gIHN3aXRjaCAoaXRlbVZhbHVlRm9ybWF0KSB7XG4gICAgY2FzZSAnYnl0ZXMnOlxuICAgICAgcmV0dXJuIGZvcm1hdEJ5dGVzKE51bWJlcihwYXJzZWRJdGVtVmFsdWUpKTtcbiAgICBjYXNlICdjb2RlJzpcbiAgICAgIHJldHVybiBodG1sLmNvZGUodHJpbVNsaWNlKHBhcnNlZEl0ZW1WYWx1ZSBhcyBzdHJpbmcpKTtcbiAgICBjYXNlICdsaW5rJzpcbiAgICAgIGNvbnN0IGxpbmsgPSBwYXJzZWRJdGVtVmFsdWUgYXMgRGV0YWlscy5MaW5rVmFsdWU7XG4gICAgICByZXR1cm4gaHRtbC5saW5rKGxpbmsudXJsLCBsaW5rLnRleHQpO1xuICAgIGNhc2UgJ3VybCc6XG4gICAgICBjb25zdCB1cmwgPSBwYXJzZWRJdGVtVmFsdWUgYXMgc3RyaW5nO1xuICAgICAgcmV0dXJuIGh0bWwubGluayh1cmwpO1xuICAgIGNhc2UgJ3RpbWVzcGFuTXMnOlxuICAgIGNhc2UgJ21zJzpcbiAgICAgIHJldHVybiBmb3JtYXREdXJhdGlvbihOdW1iZXIocGFyc2VkSXRlbVZhbHVlKSk7XG4gICAgY2FzZSAnbm9kZSc6XG4gICAgICByZXR1cm4gcGFyc2VOb2RlVmFsdWUoaXRlbVZhbHVlIGFzIERldGFpbHMuTm9kZVZhbHVlKTtcbiAgICBjYXNlICdzb3VyY2UtbG9jYXRpb24nOlxuICAgICAgcmV0dXJuIHRydW5jYXRlVGV4dChTdHJpbmcocGFyc2VkSXRlbVZhbHVlKSwgMjAwKTtcbiAgICBjYXNlICdudW1lcmljJzpcbiAgICAgIGNvbnN0IG51bSA9IE51bWJlcihwYXJzZWRJdGVtVmFsdWUpO1xuICAgICAgaWYgKG51bS50b0ZpeGVkKDMpLnRvU3RyaW5nKCkuZW5kc1dpdGgoJy4wMDAnKSkge1xuICAgICAgICByZXR1cm4gU3RyaW5nKG51bSk7XG4gICAgICB9XG4gICAgICByZXR1cm4gU3RyaW5nKG51bS50b0ZpeGVkKDMpKTtcbiAgICBjYXNlICd0ZXh0JzpcbiAgICAgIHJldHVybiB0cnVuY2F0ZVRleHQoU3RyaW5nKHBhcnNlZEl0ZW1WYWx1ZSksIDUwMCk7XG4gICAgY2FzZSAnbXVsdGknOiAvLyBAVE9ET1xuICAgICAgLy8gQFRPRE8gbG9nIHZlcmJvc2UgZmlyc3QsIHRoZW4gaW1wbGVtZW50IGRhdGEgdHlwZVxuICAgICAgdWkoKS5sb2dnZXIuaW5mbyhgRm9ybWF0IHR5cGUgJHtib2xkKCdtdWx0aScpfSBpcyBub3QgaW1wbGVtZW50ZWRgKTtcbiAgICAgIHJldHVybiAnJztcbiAgICBjYXNlICd0aHVtYm5haWwnOiAvLyBAVE9ET1xuICAgICAgLy8gQFRPRE8gbG9nIHZlcmJvc2UgZmlyc3QsIHRoZW4gaW1wbGVtZW50IGRhdGEgdHlwZVxuICAgICAgdWkoKS5sb2dnZXIuaW5mbyhgRm9ybWF0IHR5cGUgJHtib2xkKCd0aHVtYm5haWwnKX0gaXMgbm90IGltcGxlbWVudGVkYCk7XG4gICAgICByZXR1cm4gJyc7XG4gIH1cbiAgLyogZXNsaW50LWVuYWJsZSBAdHlwZXNjcmlwdC1lc2xpbnQvbm8tbWFnaWMtbnVtYmVycyAqL1xuXG4gIHJldHVybiBpdGVtVmFsdWU7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBwYXJzZVNpbXBsZUl0ZW1WYWx1ZShcbiAgaXRlbTogU2ltcGxlSXRlbVZhbHVlLFxuKTogUHJpbWl0aXZlSXRlbVZhbHVlIHtcbiAgaWYgKHR5cGVvZiBpdGVtID09PSAnb2JqZWN0Jykge1xuICAgIGNvbnN0IHZhbHVlID0gaXRlbS52YWx1ZTtcbiAgICBpZiAodHlwZW9mIHZhbHVlID09PSAnb2JqZWN0Jykge1xuICAgICAgcmV0dXJuIHZhbHVlLmZvcm1hdHRlZERlZmF1bHQ7XG4gICAgfVxuICAgIHJldHVybiB2YWx1ZTtcbiAgfVxuICByZXR1cm4gaXRlbTtcbn1cblxuLy8gQFRPRE8gZXh0cmFjdCBMaW5rIHR5cGUgZnJvbSBsb2dpY1xuZXhwb3J0IGZ1bmN0aW9uIHBhcnNlVGFibGVJdGVtUHJvcGVydHlWYWx1ZShcbiAgaXRlbVZhbHVlPzogRGV0YWlscy5JdGVtVmFsdWUsXG4pOiBQcmltaXRpdmVJdGVtVmFsdWUgfCBEZXRhaWxzLkxpbmtWYWx1ZSB7XG4gIGlmIChpdGVtVmFsdWUgPT0gbnVsbCkge1xuICAgIHJldHVybiAnJztcbiAgfVxuXG4gIC8vIFByaW1pdGl2ZSBWYWx1ZXNcbiAgaWYgKFxuICAgIHR5cGVvZiBpdGVtVmFsdWUgPT09ICdzdHJpbmcnIHx8XG4gICAgdHlwZW9mIGl0ZW1WYWx1ZSA9PT0gJ251bWJlcicgfHxcbiAgICB0eXBlb2YgaXRlbVZhbHVlID09PSAnYm9vbGVhbidcbiAgKSB7XG4gICAgcmV0dXJuIHBhcnNlU2ltcGxlSXRlbVZhbHVlKGl0ZW1WYWx1ZSk7XG4gIH1cblxuICAvLyBPYmplY3QgVmFsdWVzXG4gIGNvbnN0IG9iamVjdFZhbHVlID0gaXRlbVZhbHVlIGFzIE9iamVjdEl0ZW1WYWx1ZTtcbiAgY29uc3QgeyB0eXBlIH0gPSBvYmplY3RWYWx1ZTtcbiAgc3dpdGNoICh0eXBlKSB7XG4gICAgY2FzZSAnY29kZSc6XG4gICAgY2FzZSAndXJsJzpcbiAgICAgIHJldHVybiBTdHJpbmcocGFyc2VTaW1wbGVJdGVtVmFsdWUob2JqZWN0VmFsdWUpKTtcbiAgICBjYXNlICdub2RlJzpcbiAgICAgIHJldHVybiBwYXJzZU5vZGVWYWx1ZShvYmplY3RWYWx1ZSk7XG4gICAgY2FzZSAnbGluayc6XG4gICAgICByZXR1cm4gb2JqZWN0VmFsdWU7XG4gICAgY2FzZSAnbnVtZXJpYyc6XG4gICAgICByZXR1cm4gTnVtYmVyKHBhcnNlU2ltcGxlSXRlbVZhbHVlKG9iamVjdFZhbHVlKSk7XG4gICAgY2FzZSAnc291cmNlLWxvY2F0aW9uJzpcbiAgICAgIGNvbnN0IHsgdXJsIH0gPSBvYmplY3RWYWx1ZTtcbiAgICAgIHJldHVybiBTdHJpbmcodXJsKTtcbiAgICBjYXNlICdzdWJpdGVtcyc6XG4gICAgICAvLyBAVE9ETyBsb2cgdmVyYm9zZSBmaXJzdCwgdGhlbiBpbXBsZW1lbnQgZGF0YSB0eXBlXG4gICAgICB1aSgpLmxvZ2dlci5pbmZvKGBWYWx1ZSB0eXBlICR7Ym9sZCgnc3ViaXRlbXMnKX0gaXMgbm90IGltcGxlbWVudGVkYCk7XG4gICAgICByZXR1cm4gJyc7XG4gICAgY2FzZSAnZGVidWdkYXRhJzpcbiAgICAgIC8vIEBUT0RPIGxvZyB2ZXJib3NlIGZpcnN0LCB0aGVuIGltcGxlbWVudCBkYXRhIHR5cGVcbiAgICAgIHVpKCkubG9nZ2VyLmluZm8oYFZhbHVlIHR5cGUgJHtib2xkKCdkZWJ1Z2RhdGEnKX0gaXMgbm90IGltcGxlbWVudGVkYCwge1xuICAgICAgICBzaWxlbnQ6IHRydWUsXG4gICAgICB9KTtcbiAgICAgIHJldHVybiAnJztcbiAgfVxuICAvLyBJY3VNZXNzYWdlXG4gIHJldHVybiBwYXJzZVNpbXBsZUl0ZW1WYWx1ZShvYmplY3RWYWx1ZSBhcyBTaW1wbGVJdGVtVmFsdWUpO1xufVxuIiwgImNvbnN0IF9faW5qZWN0ZWRfZmlsZW5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWxpZ2h0aG91c2Uvc3JjL2xpYi9ydW5uZXIvZGV0YWlscy91dGlscy50c1wiO2NvbnN0IF9faW5qZWN0ZWRfZGlybmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tbGlnaHRob3VzZS9zcmMvbGliL3J1bm5lci9kZXRhaWxzXCI7Y29uc3QgX19pbmplY3RlZF9pbXBvcnRfbWV0YV91cmxfXyA9IFwiZmlsZTovLy9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1saWdodGhvdXNlL3NyYy9saWIvcnVubmVyL2RldGFpbHMvdXRpbHMudHNcIjtpbXBvcnQgeyBib2xkIH0gZnJvbSAnYW5zaXMnO1xuaW1wb3J0IHR5cGUgRGV0YWlscyBmcm9tICdsaWdodGhvdXNlL3R5cGVzL2xoci9hdWRpdC1kZXRhaWxzJztcblxuZXhwb3J0IGNsYXNzIExpZ2h0aG91c2VBdWRpdERldGFpbHNQYXJzaW5nRXJyb3IgZXh0ZW5kcyBFcnJvciB7XG4gIGNvbnN0cnVjdG9yKFxuICAgIHR5cGU6IERldGFpbHNbJ3R5cGUnXSxcbiAgICByYXdUYWJsZTogUmVjb3JkPHN0cmluZywgdW5rbm93bj4sXG4gICAgZXJyb3I6IHN0cmluZyxcbiAgKSB7XG4gICAgc3VwZXIoXG4gICAgICBgUGFyc2luZyBsaWdodGhvdXNlIHJlcG9ydCBkZXRhaWxzICR7Ym9sZChcbiAgICAgICAgdHlwZSxcbiAgICAgICl9IGZhaWxlZDogXFxuUmF3IGRhdGE6XFxuICR7SlNPTi5zdHJpbmdpZnkocmF3VGFibGUsIG51bGwsIDIpfVxcbiR7ZXJyb3J9YCxcbiAgICApO1xuICB9XG59XG4iLCAiY29uc3QgX19pbmplY3RlZF9maWxlbmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy9wbHVnaW4tbGlnaHRob3VzZS9zcmMvbGliL3V0aWxzLnRzXCI7Y29uc3QgX19pbmplY3RlZF9kaXJuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3BsdWdpbi1saWdodGhvdXNlL3NyYy9saWJcIjtjb25zdCBfX2luamVjdGVkX2ltcG9ydF9tZXRhX3VybF9fID0gXCJmaWxlOi8vL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvcGx1Z2luLWxpZ2h0aG91c2Uvc3JjL2xpYi91dGlscy50c1wiO2ltcG9ydCB0eXBlIHsgQXVkaXQsIENhdGVnb3J5UmVmLCBHcm91cCB9IGZyb20gJ0Bjb2RlLXB1c2h1cC9tb2RlbHMnO1xuaW1wb3J0IHsgZmlsdGVySXRlbVJlZnNCeSwgdG9BcnJheSB9IGZyb20gJ0Bjb2RlLXB1c2h1cC91dGlscyc7XG5pbXBvcnQgeyBMSUdIVEhPVVNFX1BMVUdJTl9TTFVHIH0gZnJvbSAnLi9jb25zdGFudHMuanMnO1xuaW1wb3J0IHR5cGUgeyBMaWdodGhvdXNlQ2xpRmxhZ3MgfSBmcm9tICcuL3J1bm5lci90eXBlcy5qcyc7XG5cbmV4cG9ydCB0eXBlIExpZ2h0aG91c2VHcm91cFNsdWdzID1cbiAgfCAncGVyZm9ybWFuY2UnXG4gIHwgJ2FjY2Vzc2liaWxpdHknXG4gIHwgJ2Jlc3QtcHJhY3RpY2VzJ1xuICB8ICdzZW8nXG4gIHwgJ3B3YSc7XG5cbmV4cG9ydCBmdW5jdGlvbiBsaWdodGhvdXNlR3JvdXBSZWYoXG4gIGdyb3VwU2x1ZzogTGlnaHRob3VzZUdyb3VwU2x1Z3MsXG4gIHdlaWdodCA9IDEsXG4pOiBDYXRlZ29yeVJlZiB7XG4gIHJldHVybiB7XG4gICAgcGx1Z2luOiBMSUdIVEhPVVNFX1BMVUdJTl9TTFVHLFxuICAgIHNsdWc6IGdyb3VwU2x1ZyxcbiAgICB0eXBlOiAnZ3JvdXAnLFxuICAgIHdlaWdodCxcbiAgfTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGxpZ2h0aG91c2VBdWRpdFJlZihhdWRpdFNsdWc6IHN0cmluZywgd2VpZ2h0ID0gMSk6IENhdGVnb3J5UmVmIHtcbiAgcmV0dXJuIHtcbiAgICBwbHVnaW46IExJR0hUSE9VU0VfUExVR0lOX1NMVUcsXG4gICAgc2x1ZzogYXVkaXRTbHVnLFxuICAgIHR5cGU6ICdhdWRpdCcsXG4gICAgd2VpZ2h0LFxuICB9O1xufVxuXG5leHBvcnQgY2xhc3MgQXVkaXRzTm90SW1wbGVtZW50ZWRFcnJvciBleHRlbmRzIEVycm9yIHtcbiAgY29uc3RydWN0b3IoYXVkaXRTbHVnczogc3RyaW5nW10pIHtcbiAgICBzdXBlcihgYXVkaXRzOiBcIiR7YXVkaXRTbHVncy5qb2luKCcsICcpfVwiIG5vdCBpbXBsZW1lbnRlZGApO1xuICB9XG59XG5cbmV4cG9ydCBmdW5jdGlvbiB2YWxpZGF0ZUF1ZGl0cyhhdWRpdHM6IEF1ZGl0W10sIG9ubHlBdWRpdHM6IHN0cmluZ1tdKTogYm9vbGVhbiB7XG4gIGNvbnN0IG1pc3NpbmdBdWR0aXMgPSB0b0FycmF5KG9ubHlBdWRpdHMpLmZpbHRlcihcbiAgICBzbHVnID0+ICFhdWRpdHMuc29tZShhdWRpdCA9PiBhdWRpdC5zbHVnID09PSBzbHVnKSxcbiAgKTtcbiAgaWYgKG1pc3NpbmdBdWR0aXMubGVuZ3RoID4gMCkge1xuICAgIHRocm93IG5ldyBBdWRpdHNOb3RJbXBsZW1lbnRlZEVycm9yKG1pc3NpbmdBdWR0aXMpO1xuICB9XG4gIHJldHVybiB0cnVlO1xufVxuXG5leHBvcnQgY2xhc3MgQ2F0ZWdvcmllc05vdEltcGxlbWVudGVkRXJyb3IgZXh0ZW5kcyBFcnJvciB7XG4gIGNvbnN0cnVjdG9yKGNhdGVnb3J5U2x1Z3M6IHN0cmluZ1tdKSB7XG4gICAgc3VwZXIoYGNhdGVnb3JpZXM6IFwiJHtjYXRlZ29yeVNsdWdzLmpvaW4oJywgJyl9XCIgbm90IGltcGxlbWVudGVkYCk7XG4gIH1cbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHZhbGlkYXRlT25seUNhdGVnb3JpZXMoXG4gIGdyb3VwczogR3JvdXBbXSxcbiAgb25seUNhdGVnb3JpZXM6IHN0cmluZyB8IHN0cmluZ1tdLFxuKTogYm9vbGVhbiB7XG4gIGNvbnN0IG1pc3NpbmdDYXRlZ29yaWVzID0gdG9BcnJheShvbmx5Q2F0ZWdvcmllcykuZmlsdGVyKHNsdWcgPT5cbiAgICBncm91cHMuZXZlcnkoZ3JvdXAgPT4gZ3JvdXAuc2x1ZyAhPT0gc2x1ZyksXG4gICk7XG4gIGlmIChtaXNzaW5nQ2F0ZWdvcmllcy5sZW5ndGggPiAwKSB7XG4gICAgdGhyb3cgbmV3IENhdGVnb3JpZXNOb3RJbXBsZW1lbnRlZEVycm9yKG1pc3NpbmdDYXRlZ29yaWVzKTtcbiAgfVxuICByZXR1cm4gdHJ1ZTtcbn1cblxuZXhwb3J0IHR5cGUgRmlsdGVyT3B0aW9ucyA9IFBhcnRpYWw8XG4gIFBpY2s8TGlnaHRob3VzZUNsaUZsYWdzLCAnb25seUF1ZGl0cycgfCAnb25seUNhdGVnb3JpZXMnIHwgJ3NraXBBdWRpdHMnPlxuPjtcblxuZXhwb3J0IGZ1bmN0aW9uIGZpbHRlckF1ZGl0c0FuZEdyb3Vwc0J5T25seU9wdGlvbnMoXG4gIGF1ZGl0czogQXVkaXRbXSxcbiAgZ3JvdXBzOiBHcm91cFtdLFxuICBvcHRpb25zPzogRmlsdGVyT3B0aW9ucyxcbik6IHtcbiAgYXVkaXRzOiBBdWRpdFtdO1xuICBncm91cHM6IEdyb3VwW107XG59IHtcbiAgY29uc3Qge1xuICAgIG9ubHlBdWRpdHMgPSBbXSxcbiAgICBza2lwQXVkaXRzID0gW10sXG4gICAgb25seUNhdGVnb3JpZXMgPSBbXSxcbiAgfSA9IG9wdGlvbnMgPz8ge307XG5cbiAgLy8gY2F0ZWdvcnkgd2lucyBvdmVyIGF1ZGl0c1xuICBpZiAob25seUNhdGVnb3JpZXMubGVuZ3RoID4gMCkge1xuICAgIHZhbGlkYXRlT25seUNhdGVnb3JpZXMoZ3JvdXBzLCBvbmx5Q2F0ZWdvcmllcyk7XG5cbiAgICBjb25zdCBjYXRlZ29yeVNsdWdzID0gbmV3IFNldChvbmx5Q2F0ZWdvcmllcyk7XG4gICAgY29uc3QgZmlsdGVyZWRHcm91cHM6IEdyb3VwW10gPSBncm91cHMuZmlsdGVyKCh7IHNsdWcgfSkgPT5cbiAgICAgIGNhdGVnb3J5U2x1Z3MuaGFzKHNsdWcpLFxuICAgICk7XG4gICAgY29uc3QgYXVkaXRTbHVnc0Zyb21SZW1haW5pbmdHcm91cHMgPSBuZXcgU2V0KFxuICAgICAgZmlsdGVyZWRHcm91cHMuZmxhdE1hcCgoeyByZWZzIH0pID0+IHJlZnMubWFwKCh7IHNsdWcgfSkgPT4gc2x1ZykpLFxuICAgICk7XG4gICAgcmV0dXJuIHtcbiAgICAgIGF1ZGl0czogYXVkaXRzLmZpbHRlcigoeyBzbHVnIH0pID0+XG4gICAgICAgIGF1ZGl0U2x1Z3NGcm9tUmVtYWluaW5nR3JvdXBzLmhhcyhzbHVnKSxcbiAgICAgICksXG4gICAgICBncm91cHM6IGZpbHRlcmVkR3JvdXBzLFxuICAgIH07XG4gIH0gZWxzZSBpZiAob25seUF1ZGl0cy5sZW5ndGggPiAwIHx8IHNraXBBdWRpdHMubGVuZ3RoID4gMCkge1xuICAgIHZhbGlkYXRlQXVkaXRzKGF1ZGl0cywgb25seUF1ZGl0cyk7XG4gICAgdmFsaWRhdGVBdWRpdHMoYXVkaXRzLCBza2lwQXVkaXRzKTtcbiAgICBjb25zdCBvbmx5QXVkaXRTbHVncyA9IG5ldyBTZXQob25seUF1ZGl0cyk7XG4gICAgY29uc3Qgc2tpcEF1ZGl0U2x1Z3MgPSBuZXcgU2V0KHNraXBBdWRpdHMpO1xuICAgIGNvbnN0IGZpbHRlckF1ZGl0cyA9ICh7IHNsdWcgfTogUGljazxBdWRpdCwgJ3NsdWcnPikgPT5cbiAgICAgICEoXG4gICAgICAgIC8vIGF1ZGl0IGlzIE5PVCBpbiBnaXZlbiBvbmx5QXVkaXRTbHVnc1xuICAgICAgICAoXG4gICAgICAgICAgKG9ubHlBdWRpdHMubGVuZ3RoID4gMCAmJiAhb25seUF1ZGl0U2x1Z3MuaGFzKHNsdWcpKSB8fFxuICAgICAgICAgIC8vIGF1ZGl0IElTIGluIGdpdmVuIHNraXBBdWRpdFNsdWdzXG4gICAgICAgICAgKHNraXBBdWRpdHMubGVuZ3RoID4gMCAmJiBza2lwQXVkaXRTbHVncy5oYXMoc2x1ZykpXG4gICAgICAgIClcbiAgICAgICk7XG4gICAgcmV0dXJuIHtcbiAgICAgIGF1ZGl0czogYXVkaXRzLmZpbHRlcihmaWx0ZXJBdWRpdHMpLFxuICAgICAgZ3JvdXBzOiBmaWx0ZXJJdGVtUmVmc0J5KGdyb3VwcywgZmlsdGVyQXVkaXRzKSxcbiAgICB9O1xuICB9XG4gIC8vIHJldHVybiB1bmNoYW5nZWRcbiAgcmV0dXJuIHtcbiAgICBhdWRpdHMsXG4gICAgZ3JvdXBzLFxuICB9O1xufVxuIiwgImNvbnN0IF9faW5qZWN0ZWRfZmlsZW5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvY29kZS1wdXNodXAucHJlc2V0LnRzXCI7Y29uc3QgX19pbmplY3RlZF9kaXJuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpXCI7Y29uc3QgX19pbmplY3RlZF9pbXBvcnRfbWV0YV91cmxfXyA9IFwiZmlsZTovLy9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL2NvZGUtcHVzaHVwLnByZXNldC50c1wiO2ltcG9ydCB0eXBlIHtcbiAgQ2F0ZWdvcnlDb25maWcsXG4gIENvcmVDb25maWcsXG59IGZyb20gJy4vcGFja2FnZXMvbW9kZWxzL3NyYy9pbmRleC5qcyc7XG5pbXBvcnQgY292ZXJhZ2VQbHVnaW4sIHtcbiAgZ2V0TnhDb3ZlcmFnZVBhdGhzLFxufSBmcm9tICcuL3BhY2thZ2VzL3BsdWdpbi1jb3ZlcmFnZS9zcmMvaW5kZXguanMnO1xuaW1wb3J0IGRvY0NvdmVyYWdlUGx1Z2luLCB7IERvY0NvdmVyYWdlUGx1Z2luQ29uZmlnIH0gZnJvbSAnLi9wYWNrYWdlcy9wbHVnaW4tZG9jLWNvdmVyYWdlL3NyYy9pbmRleC5qcyc7XG5pbXBvcnQgeyBncm91cHMsIFBMVUdJTl9TTFVHIH0gZnJvbSAnLi9wYWNrYWdlcy9wbHVnaW4tZG9jLWNvdmVyYWdlL3NyYy9saWIvY29uc3RhbnRzLmpzJztcbmltcG9ydCB7IGZpbHRlckdyb3Vwc0J5T25seUF1ZGl0cyB9IGZyb20gJy4vcGFja2FnZXMvcGx1Z2luLWRvYy1jb3ZlcmFnZS9zcmMvbGliL3V0aWxzLmpzJztcbmltcG9ydCBlc2xpbnRQbHVnaW4sIHtcbiAgZXNsaW50Q29uZmlnRnJvbUFsbE54UHJvamVjdHMsXG4gIGVzbGludENvbmZpZ0Zyb21OeFByb2plY3QsXG59IGZyb20gJy4vcGFja2FnZXMvcGx1Z2luLWVzbGludC9zcmMvaW5kZXguanMnO1xuaW1wb3J0IGpzUGFja2FnZXNQbHVnaW4gZnJvbSAnLi9wYWNrYWdlcy9wbHVnaW4tanMtcGFja2FnZXMvc3JjL2luZGV4LmpzJztcbmltcG9ydCBsaWdodGhvdXNlUGx1Z2luLCB7XG4gIGxpZ2h0aG91c2VHcm91cFJlZixcbn0gZnJvbSAnLi9wYWNrYWdlcy9wbHVnaW4tbGlnaHRob3VzZS9zcmMvaW5kZXguanMnO1xuXG5leHBvcnQgY29uc3QganNQYWNrYWdlc0NhdGVnb3JpZXM6IENhdGVnb3J5Q29uZmlnW10gPSBbXG4gIHtcbiAgICBzbHVnOiAnc2VjdXJpdHknLFxuICAgIHRpdGxlOiAnU2VjdXJpdHknLFxuICAgIGRlc2NyaXB0aW9uOiAnRmluZHMga25vd24gKip2dWxuZXJhYmlsaXRpZXMqKiBpbiAzcmQtcGFydHkgcGFja2FnZXMuJyxcbiAgICByZWZzOiBbXG4gICAgICB7XG4gICAgICAgIHR5cGU6ICdncm91cCcsXG4gICAgICAgIHBsdWdpbjogJ2pzLXBhY2thZ2VzJyxcbiAgICAgICAgc2x1ZzogJ25wbS1hdWRpdCcsXG4gICAgICAgIHdlaWdodDogMSxcbiAgICAgIH0sXG4gICAgXSxcbiAgfSxcbiAge1xuICAgIHNsdWc6ICd1cGRhdGVzJyxcbiAgICB0aXRsZTogJ1VwZGF0ZXMnLFxuICAgIGRlc2NyaXB0aW9uOiAnRmluZHMgKipvdXRkYXRlZCoqIDNyZC1wYXJ0eSBwYWNrYWdlcy4nLFxuICAgIHJlZnM6IFtcbiAgICAgIHtcbiAgICAgICAgdHlwZTogJ2dyb3VwJyxcbiAgICAgICAgcGx1Z2luOiAnanMtcGFja2FnZXMnLFxuICAgICAgICBzbHVnOiAnbnBtLW91dGRhdGVkJyxcbiAgICAgICAgd2VpZ2h0OiAxLFxuICAgICAgfSxcbiAgICBdLFxuICB9LFxuXTtcblxuZXhwb3J0IGNvbnN0IGxpZ2h0aG91c2VDYXRlZ29yaWVzOiBDYXRlZ29yeUNvbmZpZ1tdID0gW1xuICB7XG4gICAgc2x1ZzogJ3BlcmZvcm1hbmNlJyxcbiAgICB0aXRsZTogJ1BlcmZvcm1hbmNlJyxcbiAgICByZWZzOiBbbGlnaHRob3VzZUdyb3VwUmVmKCdwZXJmb3JtYW5jZScpXSxcbiAgfSxcbiAge1xuICAgIHNsdWc6ICdhMTF5JyxcbiAgICB0aXRsZTogJ0FjY2Vzc2liaWxpdHknLFxuICAgIHJlZnM6IFtsaWdodGhvdXNlR3JvdXBSZWYoJ2FjY2Vzc2liaWxpdHknKV0sXG4gIH0sXG4gIHtcbiAgICBzbHVnOiAnYmVzdC1wcmFjdGljZXMnLFxuICAgIHRpdGxlOiAnQmVzdCBQcmFjdGljZXMnLFxuICAgIHJlZnM6IFtsaWdodGhvdXNlR3JvdXBSZWYoJ2Jlc3QtcHJhY3RpY2VzJyldLFxuICB9LFxuICB7XG4gICAgc2x1ZzogJ3NlbycsXG4gICAgdGl0bGU6ICdTRU8nLFxuICAgIHJlZnM6IFtsaWdodGhvdXNlR3JvdXBSZWYoJ3NlbycpXSxcbiAgfSxcbl07XG5cbmV4cG9ydCBjb25zdCBlc2xpbnRDYXRlZ29yaWVzOiBDYXRlZ29yeUNvbmZpZ1tdID0gW1xuICB7XG4gICAgc2x1ZzogJ2J1Zy1wcmV2ZW50aW9uJyxcbiAgICB0aXRsZTogJ0J1ZyBwcmV2ZW50aW9uJyxcbiAgICBkZXNjcmlwdGlvbjogJ0xpbnQgcnVsZXMgdGhhdCBmaW5kICoqcG90ZW50aWFsIGJ1Z3MqKiBpbiB5b3VyIGNvZGUuJyxcbiAgICByZWZzOiBbeyB0eXBlOiAnZ3JvdXAnLCBwbHVnaW46ICdlc2xpbnQnLCBzbHVnOiAncHJvYmxlbXMnLCB3ZWlnaHQ6IDEgfV0sXG4gIH0sXG4gIHtcbiAgICBzbHVnOiAnY29kZS1zdHlsZScsXG4gICAgdGl0bGU6ICdDb2RlIHN0eWxlJyxcbiAgICBkZXNjcmlwdGlvbjpcbiAgICAgICdMaW50IHJ1bGVzIHRoYXQgcHJvbW90ZSAqKmdvb2QgcHJhY3RpY2VzKiogYW5kIGNvbnNpc3RlbmN5IGluIHlvdXIgY29kZS4nLFxuICAgIHJlZnM6IFt7IHR5cGU6ICdncm91cCcsIHBsdWdpbjogJ2VzbGludCcsIHNsdWc6ICdzdWdnZXN0aW9ucycsIHdlaWdodDogMSB9XSxcbiAgfSxcbl07XG5cbmV4cG9ydCBmdW5jdGlvbiBnZXREb2NDb3ZlcmFnZUNhdGVnb3JpZXMoY29uZmlnOiBEb2NDb3ZlcmFnZVBsdWdpbkNvbmZpZyk6IENhdGVnb3J5Q29uZmlnW10ge1xuICByZXR1cm4gW3tcbiAgICBzbHVnOiAnZG9jLWNvdmVyYWdlLWNhdCcsXG4gICAgdGl0bGU6ICdEb2N1bWVudGF0aW9uIGNvdmVyYWdlJyxcbiAgICBkZXNjcmlwdGlvbjogJ01lYXN1cmVzIGhvdyBtdWNoIG9mIHlvdXIgY29kZSBpcyAqKmRvY3VtZW50ZWQqKi4nLFxuICAgIHJlZnM6IGZpbHRlckdyb3Vwc0J5T25seUF1ZGl0cyhncm91cHMsIGNvbmZpZykubWFwKGdyb3VwID0+ICh7XG4gICAgICB3ZWlnaHQ6IDEsXG4gICAgICB0eXBlOiAnZ3JvdXAnLFxuICAgICAgcGx1Z2luOiBQTFVHSU5fU0xVRyxcbiAgICAgIHNsdWc6IGdyb3VwLnNsdWcsXG4gICAgfSkpLFxuICB9XTtcbn1cblxuXG5cbmV4cG9ydCBjb25zdCBjb3ZlcmFnZUNhdGVnb3JpZXM6IENhdGVnb3J5Q29uZmlnW10gPSBbXG4gIHtcbiAgICBzbHVnOiAnY29kZS1jb3ZlcmFnZScsXG4gICAgdGl0bGU6ICdDb2RlIGNvdmVyYWdlJyxcbiAgICBkZXNjcmlwdGlvbjogJ01lYXN1cmVzIGhvdyBtdWNoIG9mIHlvdXIgY29kZSBpcyAqKmNvdmVyZWQgYnkgdGVzdHMqKi4nLFxuICAgIHJlZnM6IFtcbiAgICAgIHtcbiAgICAgICAgdHlwZTogJ2dyb3VwJyxcbiAgICAgICAgcGx1Z2luOiAnY292ZXJhZ2UnLFxuICAgICAgICBzbHVnOiAnY292ZXJhZ2UnLFxuICAgICAgICB3ZWlnaHQ6IDEsXG4gICAgICB9LFxuICAgIF0sXG4gIH0sXG5dO1xuXG5leHBvcnQgY29uc3QganNQYWNrYWdlc0NvcmVDb25maWcgPSBhc3luYyAoKTogUHJvbWlzZTxDb3JlQ29uZmlnPiA9PiB7XG4gIHJldHVybiB7XG4gICAgcGx1Z2luczogW2F3YWl0IGpzUGFja2FnZXNQbHVnaW4oKV0sXG4gICAgY2F0ZWdvcmllczoganNQYWNrYWdlc0NhdGVnb3JpZXMsXG4gIH07XG59O1xuXG5leHBvcnQgY29uc3QgbGlnaHRob3VzZUNvcmVDb25maWcgPSBhc3luYyAoXG4gIHVybDogc3RyaW5nLFxuKTogUHJvbWlzZTxDb3JlQ29uZmlnPiA9PiB7XG4gIHJldHVybiB7XG4gICAgcGx1Z2luczogW2F3YWl0IGxpZ2h0aG91c2VQbHVnaW4odXJsKV0sXG4gICAgY2F0ZWdvcmllczogbGlnaHRob3VzZUNhdGVnb3JpZXMsXG4gIH07XG59O1xuXG5leHBvcnQgY29uc3QgZG9jQ292ZXJhZ2VDb3JlQ29uZmlnID0gYXN5bmMgKGNvbmZpZzogRG9jQ292ZXJhZ2VQbHVnaW5Db25maWcpOiBQcm9taXNlPENvcmVDb25maWc+ID0+IHtcbiAgcmV0dXJuIHtcbiAgICBwbHVnaW5zOiBbYXdhaXQgZG9jQ292ZXJhZ2VQbHVnaW4oY29uZmlnKV0sXG4gICAgY2F0ZWdvcmllczogZ2V0RG9jQ292ZXJhZ2VDYXRlZ29yaWVzKGNvbmZpZyksXG4gIH07XG59O1xuXG5leHBvcnQgY29uc3QgZXNsaW50Q29yZUNvbmZpZ054ID0gYXN5bmMgKFxuICBwcm9qZWN0TmFtZT86IHN0cmluZyxcbik6IFByb21pc2U8Q29yZUNvbmZpZz4gPT4ge1xuICByZXR1cm4ge1xuICAgIHBsdWdpbnM6IFtcbiAgICAgIGF3YWl0IGVzbGludFBsdWdpbihcbiAgICAgICAgYXdhaXQgKHByb2plY3ROYW1lXG4gICAgICAgICAgPyBlc2xpbnRDb25maWdGcm9tTnhQcm9qZWN0KHByb2plY3ROYW1lKVxuICAgICAgICAgIDogZXNsaW50Q29uZmlnRnJvbUFsbE54UHJvamVjdHMoKSksXG4gICAgICApLFxuICAgIF0sXG4gICAgY2F0ZWdvcmllczogZXNsaW50Q2F0ZWdvcmllcyxcbiAgfTtcbn07XG5cbmV4cG9ydCBjb25zdCBjb3ZlcmFnZUNvcmVDb25maWdOeCA9IGFzeW5jIChcbiAgcHJvamVjdE5hbWU/OiBzdHJpbmcsXG4pOiBQcm9taXNlPENvcmVDb25maWc+ID0+IHtcbiAgaWYgKHByb2plY3ROYW1lKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKCdjb3ZlcmFnZUNvcmVDb25maWdOeCBmb3Igc2luZ2xlIHByb2plY3RzIG5vdCBpbXBsZW1lbnRlZCcpO1xuICB9XG4gIGNvbnN0IHRhcmdldE5hbWVzID0gWyd1bml0LXRlc3QnLCAnaW50ZWdyYXRpb24tdGVzdCddO1xuICBjb25zdCB0YXJnZXRBcmdzID0gW1xuICAgICctdCcsXG4gICAgJ3VuaXQtdGVzdCcsXG4gICAgJ2ludGVncmF0aW9uLXRlc3QnLFxuICAgICctLWNvdmVyYWdlLmVuYWJsZWQnLFxuICAgICctLXNraXBOeENhY2hlJyxcbiAgXTtcbiAgcmV0dXJuIHtcbiAgICBwbHVnaW5zOiBbXG4gICAgICBhd2FpdCBjb3ZlcmFnZVBsdWdpbih7XG4gICAgICAgIGNvdmVyYWdlVG9vbENvbW1hbmQ6IHtcbiAgICAgICAgICBjb21tYW5kOiAnbnB4JyxcbiAgICAgICAgICBhcmdzOiBbXG4gICAgICAgICAgICAnbngnLFxuICAgICAgICAgICAgcHJvamVjdE5hbWUgPyBgcnVuIC0tcHJvamVjdCAke3Byb2plY3ROYW1lfWAgOiAncnVuLW1hbnknLFxuICAgICAgICAgICAgLi4udGFyZ2V0QXJncyxcbiAgICAgICAgICBdLFxuICAgICAgICB9LFxuICAgICAgICByZXBvcnRzOiBhd2FpdCBnZXROeENvdmVyYWdlUGF0aHModGFyZ2V0TmFtZXMpLFxuICAgICAgfSksXG4gICAgXSxcbiAgICBjYXRlZ29yaWVzOiBjb3ZlcmFnZUNhdGVnb3JpZXMsXG4gIH07XG59O1xuIiwgImNvbnN0IF9faW5qZWN0ZWRfZmlsZW5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvdXRpbHMvc3JjL2luZGV4LnRzXCI7Y29uc3QgX19pbmplY3RlZF9kaXJuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3V0aWxzL3NyY1wiO2NvbnN0IF9faW5qZWN0ZWRfaW1wb3J0X21ldGFfdXJsX18gPSBcImZpbGU6Ly8vaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy91dGlscy9zcmMvaW5kZXgudHNcIjtleHBvcnQgeyBleGlzdHMgfSBmcm9tICdAY29kZS1wdXNodXAvbW9kZWxzJztcbmV4cG9ydCB7IGNvbXBhcmVQYWlycywgbWF0Y2hBcnJheUl0ZW1zQnlLZXksIHR5cGUgRGlmZiB9IGZyb20gJy4vbGliL2RpZmYuanMnO1xuZXhwb3J0IHsgc3RyaW5naWZ5RXJyb3IgfSBmcm9tICcuL2xpYi9lcnJvcnMuanMnO1xuZXhwb3J0IHtcbiAgUHJvY2Vzc0Vycm9yLFxuICBleGVjdXRlUHJvY2VzcyxcbiAgdHlwZSBQcm9jZXNzQ29uZmlnLFxuICB0eXBlIFByb2Nlc3NPYnNlcnZlcixcbiAgdHlwZSBQcm9jZXNzUmVzdWx0LFxufSBmcm9tICcuL2xpYi9leGVjdXRlLXByb2Nlc3MuanMnO1xuZXhwb3J0IHtcbiAgY3Jhd2xGaWxlU3lzdGVtLFxuICBkaXJlY3RvcnlFeGlzdHMsXG4gIGVuc3VyZURpcmVjdG9yeUV4aXN0cyxcbiAgZmlsZUV4aXN0cyxcbiAgZmlsZVBhdGhUb0NsaUFyZyxcbiAgZmluZExpbmVOdW1iZXJJblRleHQsXG4gIGZpbmROZWFyZXN0RmlsZSxcbiAgaW1wb3J0TW9kdWxlLFxuICBsb2dNdWx0aXBsZUZpbGVSZXN1bHRzLFxuICBwbHVnaW5Xb3JrRGlyLFxuICBwcm9qZWN0VG9GaWxlbmFtZSxcbiAgcmVhZEpzb25GaWxlLFxuICByZWFkVGV4dEZpbGUsXG4gIHJlbW92ZURpcmVjdG9yeUlmRXhpc3RzLFxuICB0eXBlIENyYXdsRmlsZVN5c3RlbU9wdGlvbnMsXG4gIHR5cGUgRmlsZVJlc3VsdCxcbiAgdHlwZSBNdWx0aXBsZUZpbGVSZXN1bHRzLFxufSBmcm9tICcuL2xpYi9maWxlLXN5c3RlbS5qcyc7XG5leHBvcnQgeyBmaWx0ZXJJdGVtUmVmc0J5IH0gZnJvbSAnLi9saWIvZmlsdGVyLmpzJztcbmV4cG9ydCB7XG4gIGZvcm1hdEJ5dGVzLFxuICBmb3JtYXREdXJhdGlvbixcbiAgcGx1cmFsaXplLFxuICBwbHVyYWxpemVUb2tlbixcbiAgc2x1Z2lmeSxcbiAgdHJ1bmNhdGVEZXNjcmlwdGlvbixcbiAgdHJ1bmNhdGVJc3N1ZU1lc3NhZ2UsXG4gIHRydW5jYXRlVGV4dCxcbiAgdHJ1bmNhdGVUaXRsZSxcbn0gZnJvbSAnLi9saWIvZm9ybWF0dGluZy5qcyc7XG5leHBvcnQge1xuICBnZXRDdXJyZW50QnJhbmNoT3JUYWcsXG4gIGdldEhhc2hGcm9tVGFnLFxuICBnZXRIYXNoZXMsXG4gIGdldExhdGVzdENvbW1pdCxcbiAgZ2V0U2VtdmVyVGFncyxcbiAgdHlwZSBMb2dSZXN1bHQsXG59IGZyb20gJy4vbGliL2dpdC9naXQuY29tbWl0cy1hbmQtdGFncy5qcyc7XG5leHBvcnQge1xuICBmb3JtYXRHaXRQYXRoLFxuICBnZXRHaXRSb290LFxuICBndWFyZEFnYWluc3RMb2NhbENoYW5nZXMsXG4gIHNhZmVDaGVja291dCxcbiAgdG9HaXRQYXRoLFxufSBmcm9tICcuL2xpYi9naXQvZ2l0LmpzJztcbmV4cG9ydCB7IGdyb3VwQnlTdGF0dXMgfSBmcm9tICcuL2xpYi9ncm91cC1ieS1zdGF0dXMuanMnO1xuZXhwb3J0IHtcbiAgaXNQcm9taXNlRnVsZmlsbGVkUmVzdWx0LFxuICBpc1Byb21pc2VSZWplY3RlZFJlc3VsdCxcbiAgaGFzTm9OdWxsYWJsZVByb3BzLFxufSBmcm9tICcuL2xpYi9ndWFyZHMuanMnO1xuZXhwb3J0IHsgbG9nTXVsdGlwbGVSZXN1bHRzIH0gZnJvbSAnLi9saWIvbG9nLXJlc3VsdHMuanMnO1xuZXhwb3J0IHsgbGluaywgdWksIHR5cGUgQ2xpVWksIHR5cGUgQ29sdW1uIH0gZnJvbSAnLi9saWIvbG9nZ2luZy5qcyc7XG5leHBvcnQgeyBtZXJnZUNvbmZpZ3MgfSBmcm9tICcuL2xpYi9tZXJnZS1jb25maWdzLmpzJztcbmV4cG9ydCB7IGdldFByb2dyZXNzQmFyLCB0eXBlIFByb2dyZXNzQmFyIH0gZnJvbSAnLi9saWIvcHJvZ3Jlc3MuanMnO1xuZXhwb3J0IHtcbiAgQ09ERV9QVVNIVVBfRE9NQUlOLFxuICBDT0RFX1BVU0hVUF9VTklDT0RFX0xPR08sXG4gIEZPT1RFUl9QUkVGSVgsXG4gIFJFQURNRV9MSU5LLFxuICBURVJNSU5BTF9XSURUSCxcbn0gZnJvbSAnLi9saWIvcmVwb3J0cy9jb25zdGFudHMuanMnO1xuZXhwb3J0IHtcbiAgbGlzdEF1ZGl0c0Zyb21BbGxQbHVnaW5zLFxuICBsaXN0R3JvdXBzRnJvbUFsbFBsdWdpbnMsXG59IGZyb20gJy4vbGliL3JlcG9ydHMvZmxhdHRlbi1wbHVnaW5zLmpzJztcbmV4cG9ydCB7IGdlbmVyYXRlTWRSZXBvcnQgfSBmcm9tICcuL2xpYi9yZXBvcnRzL2dlbmVyYXRlLW1kLXJlcG9ydC5qcyc7XG5leHBvcnQge1xuICBnZW5lcmF0ZU1kUmVwb3J0c0RpZmYsXG4gIGdlbmVyYXRlTWRSZXBvcnRzRGlmZkZvck1vbm9yZXBvLFxufSBmcm9tICcuL2xpYi9yZXBvcnRzL2dlbmVyYXRlLW1kLXJlcG9ydHMtZGlmZi5qcyc7XG5leHBvcnQgeyBsb2FkUmVwb3J0IH0gZnJvbSAnLi9saWIvcmVwb3J0cy9sb2FkLXJlcG9ydC5qcyc7XG5leHBvcnQgeyBsb2dTdGRvdXRTdW1tYXJ5IH0gZnJvbSAnLi9saWIvcmVwb3J0cy9sb2ctc3Rkb3V0LXN1bW1hcnkuanMnO1xuZXhwb3J0IHsgc2NvcmVSZXBvcnQgfSBmcm9tICcuL2xpYi9yZXBvcnRzL3Njb3JpbmcuanMnO1xuZXhwb3J0IHsgc29ydFJlcG9ydCB9IGZyb20gJy4vbGliL3JlcG9ydHMvc29ydGluZy5qcyc7XG5leHBvcnQgdHlwZSB7XG4gIFNjb3JlZENhdGVnb3J5Q29uZmlnLFxuICBTY29yZWRHcm91cCxcbiAgU2NvcmVkUmVwb3J0LFxufSBmcm9tICcuL2xpYi9yZXBvcnRzL3R5cGVzLmpzJztcbmV4cG9ydCB7XG4gIGNhbGNEdXJhdGlvbixcbiAgY29tcGFyZUlzc3VlU2V2ZXJpdHksXG4gIGZvcm1hdFJlcG9ydFNjb3JlLFxufSBmcm9tICcuL2xpYi9yZXBvcnRzL3V0aWxzLmpzJztcbmV4cG9ydCB7IGlzU2VtdmVyLCBub3JtYWxpemVTZW12ZXIsIHNvcnRTZW12ZXJzIH0gZnJvbSAnLi9saWIvc2VtdmVyLmpzJztcbmV4cG9ydCAqIGZyb20gJy4vbGliL3RleHQtZm9ybWF0cy9pbmRleC5qcyc7XG5leHBvcnQge1xuICBjYXBpdGFsaXplLFxuICBjb3VudE9jY3VycmVuY2VzLFxuICBkaXN0aW5jdCxcbiAgZmFjdG9yT2YsXG4gIGZyb21Kc29uTGluZXMsXG4gIG9iamVjdEZyb21FbnRyaWVzLFxuICBvYmplY3RUb0NsaUFyZ3MsXG4gIG9iamVjdFRvRW50cmllcyxcbiAgb2JqZWN0VG9LZXlzLFxuICB0b0FycmF5LFxuICB0b0pzb25MaW5lcyxcbiAgdG9OdW1iZXJQcmVjaXNpb24sXG4gIHRvT3JkaW5hbCxcbiAgdG9Vbml4TmV3bGluZXMsXG4gIHRvVW5peFBhdGgsXG4gIHR5cGUgQ2xpQXJnc09iamVjdCxcbn0gZnJvbSAnLi9saWIvdHJhbnNmb3JtLmpzJztcbmV4cG9ydCB0eXBlIHtcbiAgRXhjbHVkZU51bGxhYmxlUHJvcHMsXG4gIEV4dHJhY3RBcnJheSxcbiAgRXh0cmFjdEFycmF5cyxcbiAgSXRlbU9yQXJyYXksXG4gIFByZXR0aWZ5LFxuICBXaXRoUmVxdWlyZWQsXG59IGZyb20gJy4vbGliL3R5cGVzLmpzJztcbmV4cG9ydCB7IHZlcmJvc2VVdGlscyB9IGZyb20gJy4vbGliL3ZlcmJvc2UtdXRpbHMuanMnO1xuZXhwb3J0IHsgem9kRXJyb3JNZXNzYWdlQnVpbGRlciB9IGZyb20gJy4vbGliL3pvZC12YWxpZGF0aW9uLmpzJztcbiIsICJjb25zdCBfX2luamVjdGVkX2ZpbGVuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3V0aWxzL3NyYy9saWIvZXhlY3V0ZS1wcm9jZXNzLnRzXCI7Y29uc3QgX19pbmplY3RlZF9kaXJuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3V0aWxzL3NyYy9saWJcIjtjb25zdCBfX2luamVjdGVkX2ltcG9ydF9tZXRhX3VybF9fID0gXCJmaWxlOi8vL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvdXRpbHMvc3JjL2xpYi9leGVjdXRlLXByb2Nlc3MudHNcIjtpbXBvcnQge1xuICB0eXBlIENoaWxkUHJvY2VzcyxcbiAgdHlwZSBDaGlsZFByb2Nlc3NCeVN0ZGlvLFxuICB0eXBlIFNwYXduT3B0aW9uc1dpdGhTdGRpb1R1cGxlLFxuICB0eXBlIFN0ZGlvUGlwZSxcbiAgc3Bhd24sXG59IGZyb20gJ25vZGU6Y2hpbGRfcHJvY2Vzcyc7XG5pbXBvcnQgdHlwZSB7IFJlYWRhYmxlLCBXcml0YWJsZSB9IGZyb20gJ25vZGU6c3RyZWFtJztcbmltcG9ydCB7IGNhbGNEdXJhdGlvbiB9IGZyb20gJy4vcmVwb3J0cy91dGlscy5qcyc7XG5cbi8qKlxuICogUmVwcmVzZW50cyB0aGUgcHJvY2VzcyByZXN1bHQuXG4gKiBAY2F0ZWdvcnkgVHlwZXNcbiAqIEBwdWJsaWNcbiAqIEBwcm9wZXJ0eSB7c3RyaW5nfSBzdGRvdXQgLSBUaGUgc3Rkb3V0IG9mIHRoZSBwcm9jZXNzLlxuICogQHByb3BlcnR5IHtzdHJpbmd9IHN0ZGVyciAtIFRoZSBzdGRlcnIgb2YgdGhlIHByb2Nlc3MuXG4gKiBAcHJvcGVydHkge251bWJlciB8IG51bGx9IGNvZGUgLSBUaGUgZXhpdCBjb2RlIG9mIHRoZSBwcm9jZXNzLlxuICovXG5leHBvcnQgdHlwZSBQcm9jZXNzUmVzdWx0ID0ge1xuICBzdGRvdXQ6IHN0cmluZztcbiAgc3RkZXJyOiBzdHJpbmc7XG4gIGNvZGU6IG51bWJlciB8IG51bGw7XG4gIGRhdGU6IHN0cmluZztcbiAgZHVyYXRpb246IG51bWJlcjtcbn07XG5cbi8qKlxuICogRXJyb3IgY2xhc3MgZm9yIHByb2Nlc3MgZXJyb3JzLlxuICogQ29udGFpbnMgYWRkaXRpb25hbCBpbmZvcm1hdGlvbiBhYm91dCB0aGUgcHJvY2VzcyByZXN1bHQuXG4gKiBAY2F0ZWdvcnkgRXJyb3JcbiAqIEBwdWJsaWNcbiAqIEBjbGFzc1xuICogQGV4dGVuZHMgRXJyb3JcbiAqIEBleGFtcGxlXG4gKiBjb25zdCByZXN1bHQgPSBhd2FpdCBleGVjdXRlUHJvY2Vzcyh7fSlcbiAqIC5jYXRjaCgoZXJyb3IpID0+IHtcbiAqICAgaWYgKGVycm9yIGluc3RhbmNlb2YgUHJvY2Vzc0Vycm9yKSB7XG4gKiAgIGNvbnNvbGUuZXJyb3IoZXJyb3IuY29kZSk7XG4gKiAgIGNvbnNvbGUuZXJyb3IoZXJyb3Iuc3RkZXJyKTtcbiAqICAgY29uc29sZS5lcnJvcihlcnJvci5zdGRvdXQpO1xuICogICB9XG4gKiB9KTtcbiAqXG4gKi9cbmV4cG9ydCBjbGFzcyBQcm9jZXNzRXJyb3IgZXh0ZW5kcyBFcnJvciB7XG4gIGNvZGU6IG51bWJlciB8IG51bGw7XG4gIHN0ZGVycjogc3RyaW5nO1xuICBzdGRvdXQ6IHN0cmluZztcblxuICBjb25zdHJ1Y3RvcihyZXN1bHQ6IFByb2Nlc3NSZXN1bHQpIHtcbiAgICBzdXBlcihyZXN1bHQuc3RkZXJyKTtcbiAgICB0aGlzLmNvZGUgPSByZXN1bHQuY29kZTtcbiAgICB0aGlzLnN0ZGVyciA9IHJlc3VsdC5zdGRlcnI7XG4gICAgdGhpcy5zdGRvdXQgPSByZXN1bHQuc3Rkb3V0O1xuICB9XG59XG5cbi8qKlxuICogUHJvY2VzcyBjb25maWcgb2JqZWN0LiBDb250YWlucyB0aGUgY29tbWFuZCwgYXJncyBhbmQgb2JzZXJ2ZXIuXG4gKiBAcGFyYW0gY2ZnIC0gcHJvY2VzcyBjb25maWcgb2JqZWN0IHdpdGggY29tbWFuZCwgYXJncyBhbmQgb2JzZXJ2ZXIgKG9wdGlvbmFsKVxuICogQGNhdGVnb3J5IFR5cGVzXG4gKiBAcHVibGljXG4gKiBAcHJvcGVydHkge3N0cmluZ30gY29tbWFuZCAtIFRoZSBjb21tYW5kIHRvIGV4ZWN1dGUuXG4gKiBAcHJvcGVydHkge3N0cmluZ1tdfSBhcmdzIC0gVGhlIGFyZ3VtZW50cyBmb3IgdGhlIGNvbW1hbmQuXG4gKiBAcHJvcGVydHkge1Byb2Nlc3NPYnNlcnZlcn0gb2JzZXJ2ZXIgLSBUaGUgb2JzZXJ2ZXIgZm9yIHRoZSBwcm9jZXNzLlxuICpcbiAqIEBleGFtcGxlXG4gKlxuICogLy8gYmFzaCBjb21tYW5kXG4gKiBjb25zdCBjZmcgPSB7XG4gKiAgIGNvbW1hbmQ6ICdiYXNoJyxcbiAqICAgYXJnczogWyctYycsICdlY2hvIFwiaGVsbG8gd29ybGRcIiddXG4gKiB9O1xuICpcbiAqIC8vIG5vZGUgY29tbWFuZFxuICogY29uc3QgY2ZnID0ge1xuICogY29tbWFuZDogJ25vZGUnLFxuICogYXJnczogWyctLXZlcnNpb24nXVxuICogfTtcbiAqXG4gKiAvLyBucHggY29tbWFuZFxuICogY29uc3QgY2ZnID0ge1xuICogY29tbWFuZDogJ25weCcsXG4gKiBhcmdzOiBbJy0tdmVyc2lvbiddXG4gKlxuICovXG5leHBvcnQgdHlwZSBQcm9jZXNzQ29uZmlnID0gT21pdDxcbiAgU3Bhd25PcHRpb25zV2l0aFN0ZGlvVHVwbGU8U3RkaW9QaXBlLCBTdGRpb1BpcGUsIFN0ZGlvUGlwZT4sXG4gICdzdGRpbydcbj4gJiB7XG4gIGNvbW1hbmQ6IHN0cmluZztcbiAgYXJncz86IHN0cmluZ1tdO1xuICBvYnNlcnZlcj86IFByb2Nlc3NPYnNlcnZlcjtcbiAgaWdub3JlRXhpdENvZGU/OiBib29sZWFuO1xufTtcblxuLyoqXG4gKiBQcm9jZXNzIG9ic2VydmVyIG9iamVjdC4gQ29udGFpbnMgdGhlIG9uU3Rkb3V0LCBlcnJvciBhbmQgY29tcGxldGUgZnVuY3Rpb24uXG4gKiBAY2F0ZWdvcnkgVHlwZXNcbiAqIEBwdWJsaWNcbiAqIEBwcm9wZXJ0eSB7ZnVuY3Rpb259IG9uU3Rkb3V0IC0gVGhlIG9uU3Rkb3V0IGZ1bmN0aW9uIG9mIHRoZSBvYnNlcnZlciAob3B0aW9uYWwpLlxuICogQHByb3BlcnR5IHtmdW5jdGlvbn0gb25FcnJvciAtIFRoZSBlcnJvciBmdW5jdGlvbiBvZiB0aGUgb2JzZXJ2ZXIgKG9wdGlvbmFsKS5cbiAqIEBwcm9wZXJ0eSB7ZnVuY3Rpb259IG9uQ29tcGxldGUgLSBUaGUgY29tcGxldGUgZnVuY3Rpb24gb2YgdGhlIG9ic2VydmVyIChvcHRpb25hbCkuXG4gKlxuICogQGV4YW1wbGVcbiAqIGNvbnN0IG9ic2VydmVyID0ge1xuICogIG9uU3Rkb3V0OiAoc3Rkb3V0KSA9PiBjb25zb2xlLmluZm8oc3Rkb3V0KVxuICogIH1cbiAqL1xuZXhwb3J0IHR5cGUgUHJvY2Vzc09ic2VydmVyID0ge1xuICBvblN0ZG91dD86IChzdGRvdXQ6IHN0cmluZywgc291cmNlUHJvY2Vzcz86IENoaWxkUHJvY2VzcykgPT4gdm9pZDtcbiAgb25TdGRlcnI/OiAoc3RkZXJyOiBzdHJpbmcsIHNvdXJjZVByb2Nlc3M/OiBDaGlsZFByb2Nlc3MpID0+IHZvaWQ7XG4gIG9uRXJyb3I/OiAoZXJyb3I6IFByb2Nlc3NFcnJvcikgPT4gdm9pZDtcbiAgb25Db21wbGV0ZT86ICgpID0+IHZvaWQ7XG59O1xuXG4vKipcbiAqIEV4ZWN1dGVzIGEgcHJvY2VzcyBhbmQgcmV0dXJucyBhIHByb21pc2Ugd2l0aCB0aGUgcmVzdWx0IGFzIGBQcm9jZXNzUmVzdWx0YC5cbiAqXG4gKiBAZXhhbXBsZVxuICpcbiAqIC8vIHN5bmMgcHJvY2VzcyBleGVjdXRpb25cbiAqIGNvbnN0IHJlc3VsdCA9IGF3YWl0IGV4ZWN1dGVQcm9jZXNzKHtcbiAqICBjb21tYW5kOiAnbm9kZScsXG4gKiAgYXJnczogWyctLXZlcnNpb24nXVxuICogfSk7XG4gKlxuICogY29uc29sZS5pbmZvKHJlc3VsdCk7XG4gKlxuICogLy8gYXN5bmMgcHJvY2VzcyBleGVjdXRpb25cbiAqIGNvbnN0IHJlc3VsdCA9IGF3YWl0IGV4ZWN1dGVQcm9jZXNzKHtcbiAqICAgIGNvbW1hbmQ6ICdub2RlJyxcbiAqICAgIGFyZ3M6IFsnZG93bmxvYWQtZGF0YS5qcyddLFxuICogICAgb2JzZXJ2ZXI6IHtcbiAqICAgICAgb25TdGRvdXQ6IHVwZGF0ZVByb2dyZXNzLFxuICogICAgICBlcnJvcjogaGFuZGxlRXJyb3IsXG4gKiAgICAgIGNvbXBsZXRlOiBjbGVhbkxvZ3MsXG4gKiAgICB9XG4gKiB9KTtcbiAqXG4gKiBjb25zb2xlLmluZm8ocmVzdWx0KTtcbiAqXG4gKiBAcGFyYW0gY2ZnIC0gc2VlIHtAbGluayBQcm9jZXNzQ29uZmlnfVxuICovXG5leHBvcnQgZnVuY3Rpb24gZXhlY3V0ZVByb2Nlc3MoY2ZnOiBQcm9jZXNzQ29uZmlnKTogUHJvbWlzZTxQcm9jZXNzUmVzdWx0PiB7XG4gIGNvbnN0IHsgY29tbWFuZCwgYXJncywgb2JzZXJ2ZXIsIGlnbm9yZUV4aXRDb2RlID0gZmFsc2UsIC4uLm9wdGlvbnMgfSA9IGNmZztcbiAgY29uc3QgeyBvblN0ZG91dCwgb25TdGRlcnIsIG9uRXJyb3IsIG9uQ29tcGxldGUgfSA9IG9ic2VydmVyID8/IHt9O1xuICBjb25zdCBkYXRlID0gbmV3IERhdGUoKS50b0lTT1N0cmluZygpO1xuICBjb25zdCBzdGFydCA9IHBlcmZvcm1hbmNlLm5vdygpO1xuXG4gIHJldHVybiBuZXcgUHJvbWlzZSgocmVzb2x2ZSwgcmVqZWN0KSA9PiB7XG4gICAgLy8gc2hlbGw6dHJ1ZSB0ZWxscyBXaW5kb3dzIHRvIHVzZSBzaGVsbCBjb21tYW5kIGZvciBzcGF3bmluZyBhIGNoaWxkIHByb2Nlc3NcbiAgICBjb25zdCBzcGF3bmVkUHJvY2VzcyA9IHNwYXduKGNvbW1hbmQsIGFyZ3MgPz8gW10sIHtcbiAgICAgIHNoZWxsOiB0cnVlLFxuICAgICAgd2luZG93c0hpZGU6IHRydWUsXG4gICAgICAuLi5vcHRpb25zLFxuICAgIH0pIGFzIENoaWxkUHJvY2Vzc0J5U3RkaW88V3JpdGFibGUsIFJlYWRhYmxlLCBSZWFkYWJsZT47XG5cbiAgICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgZnVuY3Rpb25hbC9uby1sZXRcbiAgICBsZXQgc3Rkb3V0ID0gJyc7XG4gICAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIGZ1bmN0aW9uYWwvbm8tbGV0XG4gICAgbGV0IHN0ZGVyciA9ICcnO1xuXG4gICAgc3Bhd25lZFByb2Nlc3Muc3Rkb3V0Lm9uKCdkYXRhJywgZGF0YSA9PiB7XG4gICAgICBzdGRvdXQgKz0gU3RyaW5nKGRhdGEpO1xuICAgICAgb25TdGRvdXQ/LihTdHJpbmcoZGF0YSksIHNwYXduZWRQcm9jZXNzKTtcbiAgICB9KTtcblxuICAgIHNwYXduZWRQcm9jZXNzLnN0ZGVyci5vbignZGF0YScsIGRhdGEgPT4ge1xuICAgICAgc3RkZXJyICs9IFN0cmluZyhkYXRhKTtcbiAgICAgIG9uU3RkZXJyPy4oU3RyaW5nKGRhdGEpLCBzcGF3bmVkUHJvY2Vzcyk7XG4gICAgfSk7XG5cbiAgICBzcGF3bmVkUHJvY2Vzcy5vbignZXJyb3InLCBlcnIgPT4ge1xuICAgICAgc3RkZXJyICs9IGVyci50b1N0cmluZygpO1xuICAgIH0pO1xuXG4gICAgc3Bhd25lZFByb2Nlc3Mub24oJ2Nsb3NlJywgY29kZSA9PiB7XG4gICAgICBjb25zdCB0aW1pbmdzID0geyBkYXRlLCBkdXJhdGlvbjogY2FsY0R1cmF0aW9uKHN0YXJ0KSB9O1xuICAgICAgaWYgKGNvZGUgPT09IDAgfHwgaWdub3JlRXhpdENvZGUpIHtcbiAgICAgICAgb25Db21wbGV0ZT8uKCk7XG4gICAgICAgIHJlc29sdmUoeyBjb2RlLCBzdGRvdXQsIHN0ZGVyciwgLi4udGltaW5ncyB9KTtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIGNvbnN0IGVycm9yTXNnID0gbmV3IFByb2Nlc3NFcnJvcih7IGNvZGUsIHN0ZG91dCwgc3RkZXJyLCAuLi50aW1pbmdzIH0pO1xuICAgICAgICBvbkVycm9yPy4oZXJyb3JNc2cpO1xuICAgICAgICByZWplY3QoZXJyb3JNc2cpO1xuICAgICAgfVxuICAgIH0pO1xuICB9KTtcbn1cbiIsICJjb25zdCBfX2luamVjdGVkX2ZpbGVuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3V0aWxzL3NyYy9saWIvcmVwb3J0cy91dGlscy50c1wiO2NvbnN0IF9faW5qZWN0ZWRfZGlybmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy91dGlscy9zcmMvbGliL3JlcG9ydHNcIjtjb25zdCBfX2luamVjdGVkX2ltcG9ydF9tZXRhX3VybF9fID0gXCJmaWxlOi8vL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvdXRpbHMvc3JjL2xpYi9yZXBvcnRzL3V0aWxzLnRzXCI7aW1wb3J0IGFuc2lzLCB7IHR5cGUgQW5zaXMgfSBmcm9tICdhbnNpcyc7XG5pbXBvcnQgeyB0eXBlIElubGluZVRleHQsIG1kIH0gZnJvbSAnYnVpbGQtbWQnO1xuaW1wb3J0IHR5cGUge1xuICBBdWRpdERpZmYsXG4gIEF1ZGl0UmVwb3J0LFxuICBDYXRlZ29yeVJlZixcbiAgSXNzdWVTZXZlcml0eSBhcyBDbGlJc3N1ZVNldmVyaXR5LFxuICBHcm91cCxcbiAgSXNzdWUsXG59IGZyb20gJ0Bjb2RlLXB1c2h1cC9tb2RlbHMnO1xuaW1wb3J0IHsgU0NPUkVfQ09MT1JfUkFOR0UgfSBmcm9tICcuL2NvbnN0YW50cy5qcyc7XG5pbXBvcnQgdHlwZSB7XG4gIFNjb3JlZFJlcG9ydCxcbiAgU29ydGFibGVBdWRpdFJlcG9ydCxcbiAgU29ydGFibGVHcm91cCxcbn0gZnJvbSAnLi90eXBlcy5qcyc7XG5cbmV4cG9ydCBmdW5jdGlvbiBmb3JtYXRSZXBvcnRTY29yZShzY29yZTogbnVtYmVyKTogc3RyaW5nIHtcbiAgY29uc3Qgc2NhbGVkU2NvcmUgPSBzY29yZSAqIDEwMDtcbiAgY29uc3Qgcm91bmRlZFNjb3JlID0gTWF0aC5yb3VuZChzY2FsZWRTY29yZSk7XG5cbiAgcmV0dXJuIHJvdW5kZWRTY29yZSA9PT0gMTAwICYmIHNjb3JlICE9PSAxXG4gICAgPyBNYXRoLmZsb29yKHNjYWxlZFNjb3JlKS50b1N0cmluZygpXG4gICAgOiByb3VuZGVkU2NvcmUudG9TdHJpbmcoKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGZvcm1hdFNjb3JlV2l0aENvbG9yKFxuICBzY29yZTogbnVtYmVyLFxuICBvcHRpb25zPzogeyBza2lwQm9sZD86IGJvb2xlYW4gfSxcbik6IElubGluZVRleHQge1xuICBjb25zdCBzdHlsZWROdW1iZXIgPSBvcHRpb25zPy5za2lwQm9sZFxuICAgID8gZm9ybWF0UmVwb3J0U2NvcmUoc2NvcmUpXG4gICAgOiBtZC5ib2xkKGZvcm1hdFJlcG9ydFNjb3JlKHNjb3JlKSk7XG4gIHJldHVybiBtZGAke3Njb3JlTWFya2VyKHNjb3JlKX0gJHtzdHlsZWROdW1iZXJ9YDtcbn1cblxuZXhwb3J0IHR5cGUgTWFya2VyU2hhcGUgPSAnY2lyY2xlJyB8ICdzcXVhcmUnO1xuZXhwb3J0IHR5cGUgU2NvcmVDb2xvcnMgPSAncmVkJyB8ICd5ZWxsb3cnIHwgJ2dyZWVuJztcbmV4cG9ydCBjb25zdCBNQVJLRVJTOiBSZWNvcmQ8TWFya2VyU2hhcGUsIFJlY29yZDxTY29yZUNvbG9ycywgc3RyaW5nPj4gPSB7XG4gIGNpcmNsZToge1xuICAgIHJlZDogJ1x1RDgzRFx1REQzNCcsXG4gICAgeWVsbG93OiAnXHVEODNEXHVERkUxJyxcbiAgICBncmVlbjogJ1x1RDgzRFx1REZFMicsXG4gIH0sXG4gIHNxdWFyZToge1xuICAgIHJlZDogJ1x1RDgzRFx1REZFNScsXG4gICAgeWVsbG93OiAnXHVEODNEXHVERkU4JyxcbiAgICBncmVlbjogJ1x1RDgzRFx1REZFOScsXG4gIH0sXG59O1xuXG5leHBvcnQgZnVuY3Rpb24gc2NvcmVNYXJrZXIoXG4gIHNjb3JlOiBudW1iZXIsXG4gIG1hcmtlclR5cGU6IE1hcmtlclNoYXBlID0gJ2NpcmNsZScsXG4pOiBzdHJpbmcge1xuICBpZiAoc2NvcmUgPj0gU0NPUkVfQ09MT1JfUkFOR0UuR1JFRU5fTUlOKSB7XG4gICAgcmV0dXJuIE1BUktFUlNbbWFya2VyVHlwZV0uZ3JlZW47XG4gIH1cbiAgaWYgKHNjb3JlID49IFNDT1JFX0NPTE9SX1JBTkdFLllFTExPV19NSU4pIHtcbiAgICByZXR1cm4gTUFSS0VSU1ttYXJrZXJUeXBlXS55ZWxsb3c7XG4gIH1cbiAgcmV0dXJuIE1BUktFUlNbbWFya2VyVHlwZV0ucmVkO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gZ2V0RGlmZk1hcmtlcihkaWZmOiBudW1iZXIpOiBzdHJpbmcge1xuICBpZiAoZGlmZiA+IDApIHtcbiAgICByZXR1cm4gJ1x1MjE5MSc7XG4gIH1cbiAgaWYgKGRpZmYgPCAwKSB7XG4gICAgcmV0dXJuICdcdTIxOTMnO1xuICB9XG4gIHJldHVybiAnJztcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGNvbG9yQnlTY29yZURpZmYodGV4dDogc3RyaW5nLCBkaWZmOiBudW1iZXIpOiBJbmxpbmVUZXh0IHtcbiAgY29uc3QgY29sb3IgPSBkaWZmID4gMCA/ICdncmVlbicgOiBkaWZmIDwgMCA/ICdyZWQnIDogJ2dyYXknO1xuICByZXR1cm4gc2hpZWxkc0JhZGdlKHRleHQsIGNvbG9yKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHNoaWVsZHNCYWRnZSh0ZXh0OiBzdHJpbmcsIGNvbG9yOiBzdHJpbmcpOiBJbmxpbmVUZXh0IHtcbiAgcmV0dXJuIG1kLmltYWdlKFxuICAgIGBodHRwczovL2ltZy5zaGllbGRzLmlvL2JhZGdlLyR7ZW5jb2RlVVJJQ29tcG9uZW50KHRleHQpfS0ke2NvbG9yfWAsXG4gICAgdGV4dCxcbiAgKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGZvcm1hdERpZmZOdW1iZXIoZGlmZjogbnVtYmVyKTogc3RyaW5nIHtcbiAgY29uc3QgbnVtYmVyID1cbiAgICBNYXRoLmFicyhkaWZmKSA9PT0gTnVtYmVyLlBPU0lUSVZFX0lORklOSVRZID8gJ1x1MjIxRScgOiBgJHtNYXRoLmFicyhkaWZmKX1gO1xuICBjb25zdCBzaWduID0gZGlmZiA8IDAgPyAnXHUyMjEyJyA6ICcrJztcbiAgcmV0dXJuIGAke3NpZ259JHtudW1iZXJ9YDtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHNldmVyaXR5TWFya2VyKHNldmVyaXR5OiAnaW5mbycgfCAnd2FybmluZycgfCAnZXJyb3InKTogc3RyaW5nIHtcbiAgaWYgKHNldmVyaXR5ID09PSAnZXJyb3InKSB7XG4gICAgcmV0dXJuICdcdUQ4M0RcdURFQTgnO1xuICB9XG4gIGlmIChzZXZlcml0eSA9PT0gJ3dhcm5pbmcnKSB7XG4gICAgcmV0dXJuICdcdTI2QTBcdUZFMEYnO1xuICB9XG4gIHJldHVybiAnXHUyMTM5XHVGRTBGJztcbn1cblxuY29uc3QgTUlOX05PTl9aRVJPX1JFU1VMVCA9IDAuMTtcblxuZXhwb3J0IGZ1bmN0aW9uIHJvdW5kVmFsdWUodmFsdWU6IG51bWJlcik6IG51bWJlciB7XG4gIGNvbnN0IHJvdW5kZWRWYWx1ZSA9IE1hdGgucm91bmQodmFsdWUgKiAxMCkgLyAxMDsgLy8gcm91bmQgd2l0aCBtYXggMSBkZWNpbWFsXG4gIGlmIChyb3VuZGVkVmFsdWUgPT09IDAgJiYgdmFsdWUgIT09IDApIHtcbiAgICByZXR1cm4gTUlOX05PTl9aRVJPX1JFU1VMVCAqIE1hdGguc2lnbih2YWx1ZSk7XG4gIH1cbiAgcmV0dXJuIHJvdW5kZWRWYWx1ZTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGZvcm1hdFNjb3JlQ2hhbmdlKGRpZmY6IG51bWJlcik6IElubGluZVRleHQge1xuICBjb25zdCBtYXJrZXIgPSBnZXREaWZmTWFya2VyKGRpZmYpO1xuICBjb25zdCB0ZXh0ID0gZm9ybWF0RGlmZk51bWJlcihyb3VuZFZhbHVlKGRpZmYgKiAxMDApKTtcbiAgcmV0dXJuIGNvbG9yQnlTY29yZURpZmYoYCR7bWFya2VyfSAke3RleHR9YCwgZGlmZik7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBmb3JtYXRWYWx1ZUNoYW5nZSh7XG4gIHZhbHVlcyxcbiAgc2NvcmVzLFxufTogUGljazxBdWRpdERpZmYsICd2YWx1ZXMnIHwgJ3Njb3Jlcyc+KTogSW5saW5lVGV4dCB7XG4gIGNvbnN0IG1hcmtlciA9IGdldERpZmZNYXJrZXIodmFsdWVzLmRpZmYpO1xuICBjb25zdCBwZXJjZW50YWdlID1cbiAgICB2YWx1ZXMuYmVmb3JlID09PSAwXG4gICAgICA/IHZhbHVlcy5kaWZmID4gMFxuICAgICAgICA/IE51bWJlci5QT1NJVElWRV9JTkZJTklUWVxuICAgICAgICA6IE51bWJlci5ORUdBVElWRV9JTkZJTklUWVxuICAgICAgOiByb3VuZFZhbHVlKCh2YWx1ZXMuZGlmZiAvIHZhbHVlcy5iZWZvcmUpICogMTAwKTtcbiAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIG5vLWlycmVndWxhci13aGl0ZXNwYWNlXG4gIGNvbnN0IHRleHQgPSBgJHtmb3JtYXREaWZmTnVtYmVyKHBlcmNlbnRhZ2UpfVx1MjAwOSVgO1xuICByZXR1cm4gY29sb3JCeVNjb3JlRGlmZihgJHttYXJrZXJ9ICR7dGV4dH1gLCBzY29yZXMuZGlmZik7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBjYWxjRHVyYXRpb24oc3RhcnQ6IG51bWJlciwgc3RvcD86IG51bWJlcik6IG51bWJlciB7XG4gIHJldHVybiBNYXRoLnJvdW5kKChzdG9wID8/IHBlcmZvcm1hbmNlLm5vdygpKSAtIHN0YXJ0KTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGNvdW50V2VpZ2h0ZWRSZWZzKHJlZnM6IENhdGVnb3J5UmVmW10pIHtcbiAgcmV0dXJuIHJlZnNcbiAgICAuZmlsdGVyKCh7IHdlaWdodCB9KSA9PiB3ZWlnaHQgPiAwKVxuICAgIC5yZWR1Y2UoKHN1bSwgeyB3ZWlnaHQgfSkgPT4gc3VtICsgd2VpZ2h0LCAwKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGNvdW50Q2F0ZWdvcnlBdWRpdHMoXG4gIHJlZnM6IENhdGVnb3J5UmVmW10sXG4gIHBsdWdpbnM6IFNjb3JlZFJlcG9ydFsncGx1Z2lucyddLFxuKTogbnVtYmVyIHtcbiAgLy8gQ3JlYXRlIGxvb2t1cCBvYmplY3QgZm9yIGdyb3VwcyB3aXRoaW4gZWFjaCBwbHVnaW5cbiAgY29uc3QgZ3JvdXBMb29rdXAgPSBwbHVnaW5zLnJlZHVjZTxSZWNvcmQ8c3RyaW5nLCBSZWNvcmQ8c3RyaW5nLCBHcm91cD4+PihcbiAgICAobG9va3VwLCBwbHVnaW4pID0+IHtcbiAgICAgIGlmIChwbHVnaW4uZ3JvdXBzID09IG51bGwgfHwgcGx1Z2luLmdyb3Vwcy5sZW5ndGggPT09IDApIHtcbiAgICAgICAgcmV0dXJuIGxvb2t1cDtcbiAgICAgIH1cblxuICAgICAgcmV0dXJuIHtcbiAgICAgICAgLi4ubG9va3VwLFxuICAgICAgICBbcGx1Z2luLnNsdWddOiBPYmplY3QuZnJvbUVudHJpZXMoXG4gICAgICAgICAgcGx1Z2luLmdyb3Vwcy5tYXAoZ3JvdXAgPT4gW2dyb3VwLnNsdWcsIGdyb3VwXSksXG4gICAgICAgICksXG4gICAgICB9O1xuICAgIH0sXG4gICAge30sXG4gICk7XG5cbiAgLy8gQ291bnQgYXVkaXRzXG4gIHJldHVybiByZWZzLnJlZHVjZSgoYWNjLCByZWYpID0+IHtcbiAgICBpZiAocmVmLnR5cGUgPT09ICdncm91cCcpIHtcbiAgICAgIGNvbnN0IGdyb3VwUmVmcyA9IGdyb3VwTG9va3VwW3JlZi5wbHVnaW5dPy5bcmVmLnNsdWddPy5yZWZzO1xuICAgICAgcmV0dXJuIGFjYyArIChncm91cFJlZnM/Lmxlbmd0aCA/PyAwKTtcbiAgICB9XG4gICAgcmV0dXJuIGFjYyArIDE7XG4gIH0sIDApO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gY29tcGFyZUNhdGVnb3J5QXVkaXRzQW5kR3JvdXBzKFxuICBhOiBTb3J0YWJsZUF1ZGl0UmVwb3J0IHwgU29ydGFibGVHcm91cCxcbiAgYjogU29ydGFibGVBdWRpdFJlcG9ydCB8IFNvcnRhYmxlR3JvdXAsXG4pOiBudW1iZXIge1xuICBpZiAoYS5zY29yZSAhPT0gYi5zY29yZSkge1xuICAgIHJldHVybiBhLnNjb3JlIC0gYi5zY29yZTtcbiAgfVxuXG4gIGlmIChhLndlaWdodCAhPT0gYi53ZWlnaHQpIHtcbiAgICByZXR1cm4gYi53ZWlnaHQgLSBhLndlaWdodDtcbiAgfVxuXG4gIGlmICgndmFsdWUnIGluIGEgJiYgJ3ZhbHVlJyBpbiBiICYmIGEudmFsdWUgIT09IGIudmFsdWUpIHtcbiAgICByZXR1cm4gYi52YWx1ZSAtIGEudmFsdWU7XG4gIH1cblxuICByZXR1cm4gYS50aXRsZS5sb2NhbGVDb21wYXJlKGIudGl0bGUpO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gY29tcGFyZUF1ZGl0cyhhOiBBdWRpdFJlcG9ydCwgYjogQXVkaXRSZXBvcnQpOiBudW1iZXIge1xuICBpZiAoYS5zY29yZSAhPT0gYi5zY29yZSkge1xuICAgIHJldHVybiBhLnNjb3JlIC0gYi5zY29yZTtcbiAgfVxuXG4gIGlmIChhLnZhbHVlICE9PSBiLnZhbHVlKSB7XG4gICAgcmV0dXJuIGIudmFsdWUgLSBhLnZhbHVlO1xuICB9XG5cbiAgcmV0dXJuIGEudGl0bGUubG9jYWxlQ29tcGFyZShiLnRpdGxlKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGNvbXBhcmVJc3N1ZVNldmVyaXR5KFxuICBzZXZlcml0eTE6IENsaUlzc3VlU2V2ZXJpdHksXG4gIHNldmVyaXR5MjogQ2xpSXNzdWVTZXZlcml0eSxcbik6IG51bWJlciB7XG4gIGNvbnN0IGxldmVsczogUmVjb3JkPENsaUlzc3VlU2V2ZXJpdHksIG51bWJlcj4gPSB7XG4gICAgaW5mbzogMCxcbiAgICB3YXJuaW5nOiAxLFxuICAgIGVycm9yOiAyLFxuICB9O1xuICByZXR1cm4gbGV2ZWxzW3NldmVyaXR5MV0gLSBsZXZlbHNbc2V2ZXJpdHkyXTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHRocm93SXNOb3RQcmVzZW50RXJyb3IoXG4gIGl0ZW1OYW1lOiBzdHJpbmcsXG4gIHByZXNlbnRQbGFjZTogc3RyaW5nLFxuKTogbmV2ZXIge1xuICB0aHJvdyBuZXcgRXJyb3IoYCR7aXRlbU5hbWV9IGlzIG5vdCBwcmVzZW50IGluICR7cHJlc2VudFBsYWNlfWApO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gZ2V0UGx1Z2luTmFtZUZyb21TbHVnKFxuICBzbHVnOiBzdHJpbmcsXG4gIHBsdWdpbnM6IFNjb3JlZFJlcG9ydFsncGx1Z2lucyddLFxuKTogc3RyaW5nIHtcbiAgcmV0dXJuIChcbiAgICBwbHVnaW5zLmZpbmQoKHsgc2x1ZzogcGx1Z2luU2x1ZyB9KSA9PiBwbHVnaW5TbHVnID09PSBzbHVnKT8udGl0bGUgfHwgc2x1Z1xuICApO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gY29tcGFyZUlzc3VlcyhhOiBJc3N1ZSwgYjogSXNzdWUpOiBudW1iZXIge1xuICBpZiAoYS5zZXZlcml0eSAhPT0gYi5zZXZlcml0eSkge1xuICAgIHJldHVybiAtY29tcGFyZUlzc3VlU2V2ZXJpdHkoYS5zZXZlcml0eSwgYi5zZXZlcml0eSk7XG4gIH1cbiAgaWYgKCFhLnNvdXJjZSAmJiBiLnNvdXJjZSkge1xuICAgIHJldHVybiAtMTtcbiAgfVxuICBpZiAoYS5zb3VyY2UgJiYgIWIuc291cmNlKSB7XG4gICAgcmV0dXJuIDE7XG4gIH1cbiAgaWYgKGEuc291cmNlPy5maWxlICE9PSBiLnNvdXJjZT8uZmlsZSkge1xuICAgIHJldHVybiBhLnNvdXJjZT8uZmlsZS5sb2NhbGVDb21wYXJlKGIuc291cmNlPy5maWxlIHx8ICcnKSA/PyAwO1xuICB9XG4gIHJldHVybiBjb21wYXJlU291cmNlRmlsZVBvc2l0aW9uKGEuc291cmNlPy5wb3NpdGlvbiwgYi5zb3VyY2U/LnBvc2l0aW9uKTtcbn1cblxuZnVuY3Rpb24gY29tcGFyZVNvdXJjZUZpbGVQb3NpdGlvbihcbiAgYTogTm9uTnVsbGFibGU8SXNzdWVbJ3NvdXJjZSddPlsncG9zaXRpb24nXSxcbiAgYjogTm9uTnVsbGFibGU8SXNzdWVbJ3NvdXJjZSddPlsncG9zaXRpb24nXSxcbik6IG51bWJlciB7XG4gIGlmICghYSAmJiBiKSB7XG4gICAgcmV0dXJuIC0xO1xuICB9XG4gIGlmIChhICYmICFiKSB7XG4gICAgcmV0dXJuIDE7XG4gIH1cbiAgaWYgKGE/LnN0YXJ0TGluZSAhPT0gYj8uc3RhcnRMaW5lKSB7XG4gICAgcmV0dXJuIChhPy5zdGFydExpbmUgPz8gMCkgLSAoYj8uc3RhcnRMaW5lID8/IDApO1xuICB9XG4gIHJldHVybiAwO1xufVxuXG4vLyBAVE9ETyByZXRoaW5rIGltcGxlbWVudGF0aW9uXG5leHBvcnQgZnVuY3Rpb24gYXBwbHlTY29yZUNvbG9yKFxuICB7IHNjb3JlLCB0ZXh0IH06IHsgc2NvcmU6IG51bWJlcjsgdGV4dD86IHN0cmluZyB9LFxuICBzdHlsZTogQW5zaXMgPSBhbnNpcyxcbikge1xuICBjb25zdCBmb3JtYXR0ZWRTY29yZSA9IHRleHQgPz8gZm9ybWF0UmVwb3J0U2NvcmUoc2NvcmUpO1xuXG4gIGlmIChzY29yZSA+PSBTQ09SRV9DT0xPUl9SQU5HRS5HUkVFTl9NSU4pIHtcbiAgICByZXR1cm4gdGV4dFxuICAgICAgPyBzdHlsZS5ncmVlbihmb3JtYXR0ZWRTY29yZSlcbiAgICAgIDogc3R5bGUuYm9sZChzdHlsZS5ncmVlbihmb3JtYXR0ZWRTY29yZSkpO1xuICB9XG5cbiAgaWYgKHNjb3JlID49IFNDT1JFX0NPTE9SX1JBTkdFLllFTExPV19NSU4pIHtcbiAgICByZXR1cm4gdGV4dFxuICAgICAgPyBzdHlsZS55ZWxsb3coZm9ybWF0dGVkU2NvcmUpXG4gICAgICA6IHN0eWxlLmJvbGQoc3R5bGUueWVsbG93KGZvcm1hdHRlZFNjb3JlKSk7XG4gIH1cblxuICByZXR1cm4gdGV4dFxuICAgID8gc3R5bGUucmVkKGZvcm1hdHRlZFNjb3JlKVxuICAgIDogc3R5bGUuYm9sZChzdHlsZS5yZWQoZm9ybWF0dGVkU2NvcmUpKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHRhcmdldFNjb3JlSWNvbihcbiAgc2NvcmU6IG51bWJlcixcbiAgdGFyZ2V0U2NvcmU/OiBudW1iZXIsXG4gIG9wdGlvbnM6IHtcbiAgICBwYXNzSWNvbj86IHN0cmluZztcbiAgICBmYWlsSWNvbj86IHN0cmluZztcbiAgICBwcmVmaXg/OiBzdHJpbmc7XG4gICAgcG9zdGZpeD86IHN0cmluZztcbiAgfSA9IHt9LFxuKTogc3RyaW5nIHtcbiAgaWYgKHRhcmdldFNjb3JlICE9IG51bGwpIHtcbiAgICBjb25zdCB7XG4gICAgICBwYXNzSWNvbiA9ICdcdTI3MDUnLFxuICAgICAgZmFpbEljb24gPSAnXHUyNzRDJyxcbiAgICAgIHByZWZpeCA9ICcnLFxuICAgICAgcG9zdGZpeCA9ICcnLFxuICAgIH0gPSBvcHRpb25zO1xuICAgIGlmIChzY29yZSA+PSB0YXJnZXRTY29yZSkge1xuICAgICAgcmV0dXJuIGAke3ByZWZpeH0ke3Bhc3NJY29ufSR7cG9zdGZpeH1gO1xuICAgIH1cbiAgICByZXR1cm4gYCR7cHJlZml4fSR7ZmFpbEljb259JHtwb3N0Zml4fWA7XG4gIH1cbiAgcmV0dXJuICcnO1xufVxuIiwgImNvbnN0IF9faW5qZWN0ZWRfZmlsZW5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvdXRpbHMvc3JjL2xpYi9maWxlLXN5c3RlbS50c1wiO2NvbnN0IF9faW5qZWN0ZWRfZGlybmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy91dGlscy9zcmMvbGliXCI7Y29uc3QgX19pbmplY3RlZF9pbXBvcnRfbWV0YV91cmxfXyA9IFwiZmlsZTovLy9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3V0aWxzL3NyYy9saWIvZmlsZS1zeXN0ZW0udHNcIjtpbXBvcnQgeyBib2xkLCBncmF5IH0gZnJvbSAnYW5zaXMnO1xuaW1wb3J0IHsgdHlwZSBPcHRpb25zLCBidW5kbGVSZXF1aXJlIH0gZnJvbSAnYnVuZGxlLXJlcXVpcmUnO1xuaW1wb3J0IHsgbWtkaXIsIHJlYWRGaWxlLCByZWFkZGlyLCBybSwgc3RhdCB9IGZyb20gJ25vZGU6ZnMvcHJvbWlzZXMnO1xuaW1wb3J0IHBhdGggZnJvbSAnbm9kZTpwYXRoJztcbmltcG9ydCB7IGZvcm1hdEJ5dGVzIH0gZnJvbSAnLi9mb3JtYXR0aW5nLmpzJztcbmltcG9ydCB7IGxvZ011bHRpcGxlUmVzdWx0cyB9IGZyb20gJy4vbG9nLXJlc3VsdHMuanMnO1xuaW1wb3J0IHsgdWkgfSBmcm9tICcuL2xvZ2dpbmcuanMnO1xuXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gcmVhZFRleHRGaWxlKGZpbGVQYXRoOiBzdHJpbmcpOiBQcm9taXNlPHN0cmluZz4ge1xuICBjb25zdCBidWZmZXIgPSBhd2FpdCByZWFkRmlsZShmaWxlUGF0aCk7XG4gIHJldHVybiBidWZmZXIudG9TdHJpbmcoKTtcbn1cblxuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIHJlYWRKc29uRmlsZTxUID0gdW5rbm93bj4oZmlsZVBhdGg6IHN0cmluZyk6IFByb21pc2U8VD4ge1xuICBjb25zdCB0ZXh0ID0gYXdhaXQgcmVhZFRleHRGaWxlKGZpbGVQYXRoKTtcbiAgcmV0dXJuIEpTT04ucGFyc2UodGV4dCkgYXMgVDtcbn1cblxuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGZpbGVFeGlzdHMoZmlsZVBhdGg6IHN0cmluZyk6IFByb21pc2U8Ym9vbGVhbj4ge1xuICB0cnkge1xuICAgIGNvbnN0IHN0YXRzID0gYXdhaXQgc3RhdChmaWxlUGF0aCk7XG4gICAgcmV0dXJuIHN0YXRzLmlzRmlsZSgpO1xuICB9IGNhdGNoIHtcbiAgICByZXR1cm4gZmFsc2U7XG4gIH1cbn1cblxuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGRpcmVjdG9yeUV4aXN0cyhmaWxlUGF0aDogc3RyaW5nKTogUHJvbWlzZTxib29sZWFuPiB7XG4gIHRyeSB7XG4gICAgY29uc3Qgc3RhdHMgPSBhd2FpdCBzdGF0KGZpbGVQYXRoKTtcbiAgICByZXR1cm4gc3RhdHMuaXNEaXJlY3RvcnkoKTtcbiAgfSBjYXRjaCB7XG4gICAgcmV0dXJuIGZhbHNlO1xuICB9XG59XG5cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBlbnN1cmVEaXJlY3RvcnlFeGlzdHMoYmFzZURpcjogc3RyaW5nKSB7XG4gIHRyeSB7XG4gICAgYXdhaXQgbWtkaXIoYmFzZURpciwgeyByZWN1cnNpdmU6IHRydWUgfSk7XG4gICAgcmV0dXJuO1xuICB9IGNhdGNoIChlcnJvcikge1xuICAgIHVpKCkubG9nZ2VyLmluZm8oKGVycm9yIGFzIHsgY29kZTogc3RyaW5nOyBtZXNzYWdlOiBzdHJpbmcgfSkubWVzc2FnZSk7XG4gICAgaWYgKChlcnJvciBhcyB7IGNvZGU6IHN0cmluZyB9KS5jb2RlICE9PSAnRUVYSVNUJykge1xuICAgICAgdGhyb3cgZXJyb3I7XG4gICAgfVxuICB9XG59XG5cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiByZW1vdmVEaXJlY3RvcnlJZkV4aXN0cyhkaXI6IHN0cmluZykge1xuICBpZiAoYXdhaXQgZGlyZWN0b3J5RXhpc3RzKGRpcikpIHtcbiAgICBhd2FpdCBybShkaXIsIHsgcmVjdXJzaXZlOiB0cnVlLCBmb3JjZTogdHJ1ZSB9KTtcbiAgfVxufVxuXG5leHBvcnQgdHlwZSBGaWxlUmVzdWx0ID0gcmVhZG9ubHkgW3N0cmluZ10gfCByZWFkb25seSBbc3RyaW5nLCBudW1iZXJdO1xuZXhwb3J0IHR5cGUgTXVsdGlwbGVGaWxlUmVzdWx0cyA9IFByb21pc2VTZXR0bGVkUmVzdWx0PEZpbGVSZXN1bHQ+W107XG5cbmV4cG9ydCBmdW5jdGlvbiBsb2dNdWx0aXBsZUZpbGVSZXN1bHRzKFxuICBmaWxlUmVzdWx0czogTXVsdGlwbGVGaWxlUmVzdWx0cyxcbiAgbWVzc2FnZVByZWZpeDogc3RyaW5nLFxuKTogdm9pZCB7XG4gIGNvbnN0IHN1Y2NlZWRlZFRyYW5zZm9ybSA9IChyZXN1bHQ6IFByb21pc2VGdWxmaWxsZWRSZXN1bHQ8RmlsZVJlc3VsdD4pID0+IHtcbiAgICBjb25zdCBbZmlsZU5hbWUsIHNpemVdID0gcmVzdWx0LnZhbHVlO1xuICAgIGNvbnN0IGZvcm1hdHRlZFNpemUgPSBzaXplID8gYCAoJHtncmF5KGZvcm1hdEJ5dGVzKHNpemUpKX0pYCA6ICcnO1xuICAgIHJldHVybiBgLSAke2JvbGQoZmlsZU5hbWUpfSR7Zm9ybWF0dGVkU2l6ZX1gO1xuICB9O1xuICBjb25zdCBmYWlsZWRUcmFuc2Zvcm0gPSAocmVzdWx0OiBQcm9taXNlUmVqZWN0ZWRSZXN1bHQpID0+XG4gICAgYC0gJHtib2xkKHJlc3VsdC5yZWFzb24gYXMgc3RyaW5nKX1gO1xuXG4gIGxvZ011bHRpcGxlUmVzdWx0czxGaWxlUmVzdWx0PihcbiAgICBmaWxlUmVzdWx0cyxcbiAgICBtZXNzYWdlUHJlZml4LFxuICAgIHN1Y2NlZWRlZFRyYW5zZm9ybSxcbiAgICBmYWlsZWRUcmFuc2Zvcm0sXG4gICk7XG59XG5cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBpbXBvcnRNb2R1bGU8VCA9IHVua25vd24+KG9wdGlvbnM6IE9wdGlvbnMpOiBQcm9taXNlPFQ+IHtcbiAgY29uc3QgeyBtb2QgfSA9IGF3YWl0IGJ1bmRsZVJlcXVpcmU8b2JqZWN0PihvcHRpb25zKTtcblxuICBpZiAodHlwZW9mIG1vZCA9PT0gJ29iamVjdCcgJiYgJ2RlZmF1bHQnIGluIG1vZCkge1xuICAgIHJldHVybiBtb2QuZGVmYXVsdCBhcyBUO1xuICB9XG4gIHJldHVybiBtb2QgYXMgVDtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHBsdWdpbldvcmtEaXIoc2x1Zzogc3RyaW5nKTogc3RyaW5nIHtcbiAgcmV0dXJuIHBhdGguam9pbignbm9kZV9tb2R1bGVzJywgJy5jb2RlLXB1c2h1cCcsIHNsdWcpO1xufVxuXG5leHBvcnQgdHlwZSBDcmF3bEZpbGVTeXN0ZW1PcHRpb25zPFQ+ID0ge1xuICBkaXJlY3Rvcnk6IHN0cmluZztcbiAgcGF0dGVybj86IHN0cmluZyB8IFJlZ0V4cDtcbiAgZmlsZVRyYW5zZm9ybT86IChmaWxlUGF0aDogc3RyaW5nKSA9PiBQcm9taXNlPFQ+IHwgVDtcbn07XG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gY3Jhd2xGaWxlU3lzdGVtPFQgPSBzdHJpbmc+KFxuICBvcHRpb25zOiBDcmF3bEZpbGVTeXN0ZW1PcHRpb25zPFQ+LFxuKTogUHJvbWlzZTxUW10+IHtcbiAgY29uc3Qge1xuICAgIGRpcmVjdG9yeSxcbiAgICBwYXR0ZXJuLFxuICAgIGZpbGVUcmFuc2Zvcm0gPSAoZmlsZVBhdGg6IHN0cmluZykgPT4gZmlsZVBhdGggYXMgVCxcbiAgfSA9IG9wdGlvbnM7XG5cbiAgY29uc3QgZmlsZXMgPSBhd2FpdCByZWFkZGlyKGRpcmVjdG9yeSk7XG4gIGNvbnN0IHByb21pc2VzID0gZmlsZXMubWFwKGFzeW5jIChmaWxlKTogUHJvbWlzZTxUIHwgVFtdPiA9PiB7XG4gICAgY29uc3QgZmlsZVBhdGggPSBwYXRoLmpvaW4oZGlyZWN0b3J5LCBmaWxlKTtcbiAgICBjb25zdCBzdGF0cyA9IGF3YWl0IHN0YXQoZmlsZVBhdGgpO1xuXG4gICAgaWYgKHN0YXRzLmlzRGlyZWN0b3J5KCkpIHtcbiAgICAgIHJldHVybiBjcmF3bEZpbGVTeXN0ZW0oeyBkaXJlY3Rvcnk6IGZpbGVQYXRoLCBwYXR0ZXJuLCBmaWxlVHJhbnNmb3JtIH0pO1xuICAgIH1cbiAgICBpZiAoc3RhdHMuaXNGaWxlKCkgJiYgKCFwYXR0ZXJuIHx8IG5ldyBSZWdFeHAocGF0dGVybikudGVzdChmaWxlKSkpIHtcbiAgICAgIHJldHVybiBmaWxlVHJhbnNmb3JtKGZpbGVQYXRoKTtcbiAgICB9XG4gICAgcmV0dXJuIFtdO1xuICB9KTtcblxuICBjb25zdCByZXN1bHRzTmVzdGVkQXJyYXkgPSBhd2FpdCBQcm9taXNlLmFsbChwcm9taXNlcyk7XG4gIHJldHVybiByZXN1bHRzTmVzdGVkQXJyYXkuZmxhdCgpIGFzIFRbXTtcbn1cblxuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGZpbmROZWFyZXN0RmlsZShcbiAgZmlsZU5hbWVzOiBzdHJpbmdbXSxcbiAgY3dkID0gcHJvY2Vzcy5jd2QoKSxcbik6IFByb21pc2U8c3RyaW5nIHwgdW5kZWZpbmVkPiB7XG4gIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBmdW5jdGlvbmFsL25vLWxvb3Atc3RhdGVtZW50c1xuICBmb3IgKFxuICAgIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBmdW5jdGlvbmFsL25vLWxldFxuICAgIGxldCBkaXJlY3RvcnkgPSBjd2Q7XG4gICAgZGlyZWN0b3J5ICE9PSBwYXRoLmRpcm5hbWUoZGlyZWN0b3J5KTtcbiAgICBkaXJlY3RvcnkgPSBwYXRoLmRpcm5hbWUoZGlyZWN0b3J5KVxuICApIHtcbiAgICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgZnVuY3Rpb25hbC9uby1sb29wLXN0YXRlbWVudHNcbiAgICBmb3IgKGNvbnN0IGZpbGUgb2YgZmlsZU5hbWVzKSB7XG4gICAgICBpZiAoYXdhaXQgZmlsZUV4aXN0cyhwYXRoLmpvaW4oZGlyZWN0b3J5LCBmaWxlKSkpIHtcbiAgICAgICAgcmV0dXJuIHBhdGguam9pbihkaXJlY3RvcnksIGZpbGUpO1xuICAgICAgfVxuICAgIH1cbiAgfVxuICByZXR1cm4gdW5kZWZpbmVkO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gZmluZExpbmVOdW1iZXJJblRleHQoXG4gIGNvbnRlbnQ6IHN0cmluZyxcbiAgcGF0dGVybjogc3RyaW5nLFxuKTogbnVtYmVyIHwgbnVsbCB7XG4gIGNvbnN0IGxpbmVzID0gY29udGVudC5zcGxpdCgvXFxyP1xcbi8pOyAvLyBTcGxpdCBsaW5lcywgaGFuZGxlIGJvdGggV2luZG93cyBhbmQgVU5JWCBsaW5lIGVuZGluZ3NcblxuICBjb25zdCBsaW5lTnVtYmVyID0gbGluZXMuZmluZEluZGV4KGxpbmUgPT4gbGluZS5pbmNsdWRlcyhwYXR0ZXJuKSkgKyAxOyAvLyArMSBiZWNhdXNlIGxpbmUgbnVtYmVycyBhcmUgMS1iYXNlZFxuICByZXR1cm4gbGluZU51bWJlciA9PT0gMCA/IG51bGwgOiBsaW5lTnVtYmVyOyAvLyBJZiB0aGUgcGFja2FnZSBpc24ndCBmb3VuZCwgcmV0dXJuIG51bGxcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGZpbGVQYXRoVG9DbGlBcmcoZmlsZVBhdGg6IHN0cmluZyk6IHN0cmluZyB7XG4gIC8vIG5lZWRzIHRvIGJlIGVzY2FwZWQgaWYgc3BhY2VzIGluY2x1ZGVkXG4gIHJldHVybiBgXCIke2ZpbGVQYXRofVwiYDtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHByb2plY3RUb0ZpbGVuYW1lKHByb2plY3Q6IHN0cmluZyk6IHN0cmluZyB7XG4gIHJldHVybiBwcm9qZWN0LnJlcGxhY2UoL1svXFxcXFxcc10rL2csICctJykucmVwbGFjZSgvQC9nLCAnJyk7XG59XG4iLCAiY29uc3QgX19pbmplY3RlZF9maWxlbmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy91dGlscy9zcmMvbGliL2Zvcm1hdHRpbmcudHNcIjtjb25zdCBfX2luamVjdGVkX2Rpcm5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvdXRpbHMvc3JjL2xpYlwiO2NvbnN0IF9faW5qZWN0ZWRfaW1wb3J0X21ldGFfdXJsX18gPSBcImZpbGU6Ly8vaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy91dGlscy9zcmMvbGliL2Zvcm1hdHRpbmcudHNcIjtpbXBvcnQge1xuICBNQVhfREVTQ1JJUFRJT05fTEVOR1RILFxuICBNQVhfSVNTVUVfTUVTU0FHRV9MRU5HVEgsXG4gIE1BWF9USVRMRV9MRU5HVEgsXG59IGZyb20gJ0Bjb2RlLXB1c2h1cC9tb2RlbHMnO1xuXG5leHBvcnQgZnVuY3Rpb24gc2x1Z2lmeSh0ZXh0OiBzdHJpbmcpOiBzdHJpbmcge1xuICByZXR1cm4gdGV4dFxuICAgIC50cmltKClcbiAgICAudG9Mb3dlckNhc2UoKVxuICAgIC5yZXBsYWNlKC9cXHMrfFxcLy9nLCAnLScpXG4gICAgLnJlcGxhY2UoL1teYS16XFxkLV0vZywgJycpO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gcGx1cmFsaXplKHRleHQ6IHN0cmluZywgYW1vdW50PzogbnVtYmVyKTogc3RyaW5nIHtcbiAgaWYgKGFtb3VudCAhPSBudWxsICYmIE1hdGguYWJzKGFtb3VudCkgPT09IDEpIHtcbiAgICByZXR1cm4gdGV4dDtcbiAgfVxuXG4gIGlmICh0ZXh0LmVuZHNXaXRoKCd5JykpIHtcbiAgICByZXR1cm4gYCR7dGV4dC5zbGljZSgwLCAtMSl9aWVzYDtcbiAgfVxuICBpZiAodGV4dC5lbmRzV2l0aCgncycpKSB7XG4gICAgcmV0dXJuIGAke3RleHR9ZXNgO1xuICB9XG4gIHJldHVybiBgJHt0ZXh0fXNgO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gZm9ybWF0Qnl0ZXMoYnl0ZXM6IG51bWJlciwgZGVjaW1hbHMgPSAyKSB7XG4gIGNvbnN0IHBvc2l0aXZlQnl0ZXMgPSBNYXRoLm1heChieXRlcywgMCk7XG5cbiAgLy8gZWFybHkgZXhpdFxuICBpZiAocG9zaXRpdmVCeXRlcyA9PT0gMCkge1xuICAgIHJldHVybiAnMCBCJztcbiAgfVxuXG4gIGNvbnN0IGsgPSAxMDI0O1xuICBjb25zdCBkbSA9IE1hdGgubWF4KGRlY2ltYWxzLCAwKTtcbiAgY29uc3Qgc2l6ZXMgPSBbJ0InLCAna0InLCAnTUInLCAnR0InLCAnVEInLCAnUEInLCAnRUInLCAnWkInLCAnWUInXTtcblxuICBjb25zdCBpID0gTWF0aC5mbG9vcihNYXRoLmxvZyhwb3NpdGl2ZUJ5dGVzKSAvIE1hdGgubG9nKGspKTtcblxuICByZXR1cm4gYCR7TnVtYmVyLnBhcnNlRmxvYXQoKHBvc2l0aXZlQnl0ZXMgLyBNYXRoLnBvdyhrLCBpKSkudG9GaXhlZChkbSkpfSAke1xuICAgIHNpemVzW2ldXG4gIH1gO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gcGx1cmFsaXplVG9rZW4odG9rZW46IHN0cmluZywgdGltZXM6IG51bWJlcik6IHN0cmluZyB7XG4gIHJldHVybiBgJHt0aW1lc30gJHtNYXRoLmFicyh0aW1lcykgPT09IDEgPyB0b2tlbiA6IHBsdXJhbGl6ZSh0b2tlbil9YDtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGZvcm1hdER1cmF0aW9uKGR1cmF0aW9uOiBudW1iZXIsIGdyYW51bGFyaXR5ID0gMCk6IHN0cmluZyB7XG4gIGlmIChkdXJhdGlvbiA8IDEwMDApIHtcbiAgICByZXR1cm4gYCR7Z3JhbnVsYXJpdHkgPyBkdXJhdGlvbi50b0ZpeGVkKGdyYW51bGFyaXR5KSA6IGR1cmF0aW9ufSBtc2A7XG4gIH1cbiAgcmV0dXJuIGAkeyhkdXJhdGlvbiAvIDEwMDApLnRvRml4ZWQoMil9IHNgO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gZm9ybWF0RGF0ZShkYXRlOiBEYXRlKTogc3RyaW5nIHtcbiAgY29uc3QgbG9jYWxlID0gJ2VuLVVTJzsgLy8gZml4ZWQgbG9jYWxlIHRvIGVuc3VyZSBjb25zaXN0ZW5jeSBhY3Jvc3MgbG9jYWwgZGVmYXVsdHMgZXhlY3V0aW9uXG4gIHJldHVybiBkYXRlXG4gICAgLnRvTG9jYWxlU3RyaW5nKGxvY2FsZSwge1xuICAgICAgd2Vla2RheTogJ3Nob3J0JyxcbiAgICAgIG1vbnRoOiAnc2hvcnQnLFxuICAgICAgZGF5OiAnbnVtZXJpYycsXG4gICAgICB5ZWFyOiAnbnVtZXJpYycsXG4gICAgICBob3VyOiAnbnVtZXJpYycsXG4gICAgICBtaW51dGU6ICcyLWRpZ2l0JyxcbiAgICAgIHRpbWVab25lTmFtZTogJ3Nob3J0JyxcbiAgICB9KVxuICAgIC5yZXBsYWNlKC9cXHUyMDJGL2csICcgJyk7IC8vIHNlZSBodHRwczovL2dpdGh1Yi5jb20vbm9kZWpzL25vZGUvaXNzdWVzLzQ1MTcxXG59XG5cbmV4cG9ydCBmdW5jdGlvbiB0cnVuY2F0ZVRleHQoXG4gIHRleHQ6IHN0cmluZyxcbiAgb3B0aW9uczpcbiAgICB8IG51bWJlclxuICAgIHwge1xuICAgICAgICBtYXhDaGFyczogbnVtYmVyO1xuICAgICAgICBwb3NpdGlvbj86ICdzdGFydCcgfCAnbWlkZGxlJyB8ICdlbmQnO1xuICAgICAgICBlbGxpcHNpcz86IHN0cmluZztcbiAgICAgIH0sXG4pOiBzdHJpbmcge1xuICBjb25zdCB7XG4gICAgbWF4Q2hhcnMsXG4gICAgcG9zaXRpb24gPSAnZW5kJyxcbiAgICBlbGxpcHNpcyA9ICcuLi4nLFxuICB9ID0gdHlwZW9mIG9wdGlvbnMgPT09ICdudW1iZXInID8geyBtYXhDaGFyczogb3B0aW9ucyB9IDogb3B0aW9ucztcbiAgaWYgKHRleHQubGVuZ3RoIDw9IG1heENoYXJzKSB7XG4gICAgcmV0dXJuIHRleHQ7XG4gIH1cblxuICBjb25zdCBtYXhMZW5ndGggPSBtYXhDaGFycyAtIGVsbGlwc2lzLmxlbmd0aDtcbiAgc3dpdGNoIChwb3NpdGlvbikge1xuICAgIGNhc2UgJ3N0YXJ0JzpcbiAgICAgIHJldHVybiBlbGxpcHNpcyArIHRleHQuc2xpY2UoLW1heExlbmd0aCkudHJpbSgpO1xuICAgIGNhc2UgJ21pZGRsZSc6XG4gICAgICBjb25zdCBoYWxmTWF4Q2hhcnMgPSBNYXRoLmZsb29yKG1heExlbmd0aCAvIDIpO1xuICAgICAgcmV0dXJuIChcbiAgICAgICAgdGV4dC5zbGljZSgwLCBoYWxmTWF4Q2hhcnMpLnRyaW0oKSArXG4gICAgICAgIGVsbGlwc2lzICtcbiAgICAgICAgdGV4dC5zbGljZSgtaGFsZk1heENoYXJzKS50cmltKClcbiAgICAgICk7XG4gICAgY2FzZSAnZW5kJzpcbiAgICAgIHJldHVybiB0ZXh0LnNsaWNlKDAsIG1heExlbmd0aCkudHJpbSgpICsgZWxsaXBzaXM7XG4gIH1cbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHRydW5jYXRlVGl0bGUodGV4dDogc3RyaW5nKTogc3RyaW5nIHtcbiAgcmV0dXJuIHRydW5jYXRlVGV4dCh0ZXh0LCBNQVhfVElUTEVfTEVOR1RIKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHRydW5jYXRlRGVzY3JpcHRpb24odGV4dDogc3RyaW5nKTogc3RyaW5nIHtcbiAgcmV0dXJuIHRydW5jYXRlVGV4dCh0ZXh0LCBNQVhfREVTQ1JJUFRJT05fTEVOR1RIKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHRydW5jYXRlSXNzdWVNZXNzYWdlKHRleHQ6IHN0cmluZyk6IHN0cmluZyB7XG4gIHJldHVybiB0cnVuY2F0ZVRleHQodGV4dCwgTUFYX0lTU1VFX01FU1NBR0VfTEVOR1RIKTtcbn1cbiIsICJjb25zdCBfX2luamVjdGVkX2ZpbGVuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3V0aWxzL3NyYy9saWIvbG9nZ2luZy50c1wiO2NvbnN0IF9faW5qZWN0ZWRfZGlybmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy91dGlscy9zcmMvbGliXCI7Y29uc3QgX19pbmplY3RlZF9pbXBvcnRfbWV0YV91cmxfXyA9IFwiZmlsZTovLy9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3V0aWxzL3NyYy9saWIvbG9nZ2luZy50c1wiO2ltcG9ydCBpc2FhY3NfY2xpdWkgZnJvbSAnQGlzYWFjcy9jbGl1aSc7XG5pbXBvcnQgeyBjbGl1aSB9IGZyb20gJ0Bwb3BwaW5zcy9jbGl1aSc7XG5pbXBvcnQgeyB1bmRlcmxpbmUgfSBmcm9tICdhbnNpcyc7XG5pbXBvcnQgeyBURVJNSU5BTF9XSURUSCB9IGZyb20gJy4vcmVwb3J0cy9jb25zdGFudHMuanMnO1xuXG4vLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgIEB0eXBlc2NyaXB0LWVzbGludC9uby1leHBsaWNpdC1hbnlcbnR5cGUgQXJndW1lbnRzVHlwZTxUPiA9IFQgZXh0ZW5kcyAoLi4uYXJnczogaW5mZXIgVSkgPT4gYW55ID8gVSA6IG5ldmVyO1xuZXhwb3J0IHR5cGUgQ2xpVWlCYXNlID0gUmV0dXJuVHlwZTx0eXBlb2YgY2xpdWk+O1xudHlwZSBVSSA9IFJldHVyblR5cGU8dHlwZW9mIGlzYWFjc19jbGl1aT47XG50eXBlIENsaUV4dGVuc2lvbiA9IHtcbiAgcm93OiAocjogQXJndW1lbnRzVHlwZTxVSVsnZGl2J10+KSA9PiB2b2lkO1xufTtcbmV4cG9ydCB0eXBlIENvbHVtbiA9IHtcbiAgdGV4dDogc3RyaW5nO1xuICB3aWR0aD86IG51bWJlcjtcbiAgYWxpZ24/OiAncmlnaHQnIHwgJ2xlZnQnIHwgJ2NlbnRlcic7XG4gIHBhZGRpbmc6IG51bWJlcltdO1xuICBib3JkZXI/OiBib29sZWFuO1xufTtcbmV4cG9ydCB0eXBlIENsaVVpID0gQ2xpVWlCYXNlICYgQ2xpRXh0ZW5zaW9uO1xuXG4vLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgaW1wb3J0L25vLW11dGFibGUtZXhwb3J0cyxmdW5jdGlvbmFsL25vLWxldFxuZXhwb3J0IGxldCBzaW5nbGV0b25VaUluc3RhbmNlOiBDbGlVaUJhc2UgfCB1bmRlZmluZWQ7XG5cbmV4cG9ydCBmdW5jdGlvbiB1aSgpOiBDbGlVaSB7XG4gIGlmIChzaW5nbGV0b25VaUluc3RhbmNlID09PSB1bmRlZmluZWQpIHtcbiAgICBzaW5nbGV0b25VaUluc3RhbmNlID0gY2xpdWkoKTtcbiAgfVxuICByZXR1cm4ge1xuICAgIC4uLnNpbmdsZXRvblVpSW5zdGFuY2UsXG4gICAgcm93OiBhcmdzID0+IHtcbiAgICAgIGxvZ0xpc3RJdGVtKGFyZ3MpO1xuICAgIH0sXG4gIH07XG59XG5cbi8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBmdW5jdGlvbmFsL25vLWxldFxubGV0IHNpbmdsZXRvbmlzYWFjVWk6IFVJIHwgdW5kZWZpbmVkO1xuZXhwb3J0IGZ1bmN0aW9uIGxvZ0xpc3RJdGVtKGFyZ3M6IEFyZ3VtZW50c1R5cGU8VUlbJ2RpdiddPikge1xuICBpZiAoc2luZ2xldG9uaXNhYWNVaSA9PT0gdW5kZWZpbmVkKSB7XG4gICAgc2luZ2xldG9uaXNhYWNVaSA9IGlzYWFjc19jbGl1aSh7IHdpZHRoOiBURVJNSU5BTF9XSURUSCB9KTtcbiAgfVxuICBzaW5nbGV0b25pc2FhY1VpLmRpdiguLi5hcmdzKTtcbiAgY29uc3QgY29udGVudCA9IHNpbmdsZXRvbmlzYWFjVWkudG9TdHJpbmcoKTtcbiAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIGZ1bmN0aW9uYWwvaW1tdXRhYmxlLWRhdGFcbiAgc2luZ2xldG9uaXNhYWNVaS5yb3dzID0gW107XG4gIHNpbmdsZXRvblVpSW5zdGFuY2U/LmxvZ2dlci5sb2coY29udGVudCk7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBsaW5rKHRleHQ6IHN0cmluZykge1xuICByZXR1cm4gdW5kZXJsaW5lLmJsdWVCcmlnaHQodGV4dCk7XG59XG4iLCAiY29uc3QgX19pbmplY3RlZF9maWxlbmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy91dGlscy9zcmMvbGliL2dpdC9naXQuY29tbWl0cy1hbmQtdGFncy50c1wiO2NvbnN0IF9faW5qZWN0ZWRfZGlybmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy91dGlscy9zcmMvbGliL2dpdFwiO2NvbnN0IF9faW5qZWN0ZWRfaW1wb3J0X21ldGFfdXJsX18gPSBcImZpbGU6Ly8vaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy91dGlscy9zcmMvbGliL2dpdC9naXQuY29tbWl0cy1hbmQtdGFncy50c1wiO2ltcG9ydCB7IHR5cGUgTG9nT3B0aW9ucyBhcyBTaW1wbGVHaXRMb2dPcHRpb25zLCBzaW1wbGVHaXQgfSBmcm9tICdzaW1wbGUtZ2l0JztcbmltcG9ydCB7IHR5cGUgQ29tbWl0LCBjb21taXRTY2hlbWEgfSBmcm9tICdAY29kZS1wdXNodXAvbW9kZWxzJztcbmltcG9ydCB7IGlzU2VtdmVyIH0gZnJvbSAnLi4vc2VtdmVyLmpzJztcblxuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGdldExhdGVzdENvbW1pdChcbiAgZ2l0ID0gc2ltcGxlR2l0KCksXG4pOiBQcm9taXNlPENvbW1pdCB8IG51bGw+IHtcbiAgY29uc3QgbG9nID0gYXdhaXQgZ2l0LmxvZyh7XG4gICAgbWF4Q291bnQ6IDEsXG4gICAgLy8gZ2l0IGxvZyAtMSAtLXByZXR0eT1mb3JtYXQ6XCIlSCAlcyAlYW4gJWFJXCIgLSBTZWU6IGh0dHBzOi8vZ2l0LXNjbS5jb20vZG9jcy9wcmV0dHktZm9ybWF0c1xuICAgIGZvcm1hdDogeyBoYXNoOiAnJUgnLCBtZXNzYWdlOiAnJXMnLCBhdXRob3I6ICclYW4nLCBkYXRlOiAnJWFJJyB9LFxuICB9KTtcbiAgcmV0dXJuIGNvbW1pdFNjaGVtYS5wYXJzZShsb2cubGF0ZXN0KTtcbn1cblxuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGdldEN1cnJlbnRCcmFuY2hPclRhZyhcbiAgZ2l0ID0gc2ltcGxlR2l0KCksXG4pOiBQcm9taXNlPHN0cmluZz4ge1xuICByZXR1cm4gKFxuICAgIChhd2FpdCBnaXQuYnJhbmNoKCkudGhlbihyID0+IHIuY3VycmVudCkpIHx8XG4gICAgLy8gSWYgbm8gY3VycmVudCBicmFuY2gsIHRyeSB0byBnZXQgdGhlIHRhZ1xuICAgIC8vIEBUT0RPIHVzZSBzaW1wbGUgZ2l0XG4gICAgKGF3YWl0IGdpdFxuICAgICAgLnJhdyhbJ2Rlc2NyaWJlJywgJy0tdGFncycsICctLWV4YWN0LW1hdGNoJ10pXG4gICAgICAudGhlbihvdXQgPT4gb3V0LnRyaW0oKSkpXG4gICk7XG59XG5cbmV4cG9ydCB0eXBlIExvZ1Jlc3VsdCA9IHsgaGFzaDogc3RyaW5nOyBtZXNzYWdlOiBzdHJpbmcgfTtcblxuZnVuY3Rpb24gdmFsaWRhdGVGaWx0ZXIoeyBmcm9tLCB0byB9OiBMb2dPcHRpb25zKSB7XG4gIGlmICh0byAmJiAhZnJvbSkge1xuICAgIC8vIHRocm93IG1vcmUgdXNlci1mcmllbmRseSBlcnJvciBpbnN0ZWFkIG9mOlxuICAgIC8vIGZhdGFsOiBhbWJpZ3VvdXMgYXJndW1lbnQgJy4uLmEnOiB1bmtub3duIHJldmlzaW9uIG9yIHBhdGggbm90IGluIHRoZSB3b3JraW5nIHRyZWUuXG4gICAgLy8gVXNlICctLScgdG8gc2VwYXJhdGUgcGF0aHMgZnJvbSByZXZpc2lvbnMsIGxpa2UgdGhpczpcbiAgICAvLyAnZ2l0IDxjb21tYW5kPiBbPHJldmlzaW9uPi4uLl0gLS0gWzxmaWxlPi4uLl0nXG4gICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgYGZpbHRlciBuZWVkcyB0aGUgXCJmcm9tXCIgb3B0aW9uIGRlZmluZWQgdG8gYWNjZXB0IHRoZSBcInRvXCIgb3B0aW9uLlxcbmAsXG4gICAgKTtcbiAgfVxufVxuXG5leHBvcnQgZnVuY3Rpb24gZmlsdGVyTG9ncyhcbiAgYWxsVGFnczogc3RyaW5nW10sXG4gIG9wdD86IFBpY2s8TG9nT3B0aW9ucywgJ2Zyb20nIHwgJ3RvJyB8ICdtYXhDb3VudCc+LFxuKSB7XG4gIGlmICghb3B0KSB7XG4gICAgcmV0dXJuIGFsbFRhZ3M7XG4gIH1cbiAgdmFsaWRhdGVGaWx0ZXIob3B0KTtcbiAgY29uc3QgeyBmcm9tLCB0bywgbWF4Q291bnQgfSA9IG9wdDtcbiAgY29uc3QgZmluSW5kZXggPSA8VD4odGFnTmFtZT86IHN0cmluZywgZmFsbGJhY2s/OiBUKSA9PiB7XG4gICAgY29uc3QgaWR4ID0gYWxsVGFncy5pbmRleE9mKHRhZ05hbWUgPz8gJycpO1xuICAgIGlmIChpZHggIT09IC0xKSB7XG4gICAgICByZXR1cm4gaWR4O1xuICAgIH1cbiAgICByZXR1cm4gZmFsbGJhY2s7XG4gIH07XG4gIGNvbnN0IGZyb21JbmRleCA9IGZpbkluZGV4KGZyb20sIDApO1xuICBjb25zdCB0b0luZGV4ID0gZmluSW5kZXgodG8sIHVuZGVmaW5lZCk7XG4gIHJldHVybiBhbGxUYWdzXG4gICAgLnNsaWNlKGZyb21JbmRleCwgdG9JbmRleCA/IHRvSW5kZXggKyAxIDogdG9JbmRleClcbiAgICAuc2xpY2UoMCwgbWF4Q291bnQgPz8gdW5kZWZpbmVkKTtcbn1cblxuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGdldEhhc2hGcm9tVGFnKFxuICB0YWc6IHN0cmluZyxcbiAgZ2l0ID0gc2ltcGxlR2l0KCksXG4pOiBQcm9taXNlPExvZ1Jlc3VsdD4ge1xuICBjb25zdCB0YWdEZXRhaWxzID0gYXdhaXQgZ2l0LnNob3coWyctLW5vLXBhdGNoJywgJy0tZm9ybWF0PSVIJywgdGFnXSk7XG4gIGNvbnN0IGhhc2ggPSB0YWdEZXRhaWxzLnRyaW0oKTsgLy8gUmVtb3ZlIHF1b3RlcyBhbmQgdHJpbSB3aGl0ZXNwYWNlXG4gIHJldHVybiB7XG4gICAgaGFzaDogaGFzaC5zcGxpdCgnXFxuJykuYXQoLTEpID8/ICcnLFxuICAgIG1lc3NhZ2U6IHRhZyxcbiAgfTtcbn1cblxuZXhwb3J0IHR5cGUgTG9nT3B0aW9ucyA9IHtcbiAgdGFyZ2V0QnJhbmNoPzogc3RyaW5nO1xuICBmcm9tPzogc3RyaW5nO1xuICB0bz86IHN0cmluZztcbiAgbWF4Q291bnQ/OiBudW1iZXI7XG59O1xuXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gZ2V0U2VtdmVyVGFncyhcbiAgb3B0OiBMb2dPcHRpb25zID0ge30sXG4gIGdpdCA9IHNpbXBsZUdpdCgpLFxuKTogUHJvbWlzZTxMb2dSZXN1bHRbXT4ge1xuICB2YWxpZGF0ZUZpbHRlcihvcHQpO1xuICBjb25zdCB7IHRhcmdldEJyYW5jaCwgLi4ub3B0aW9ucyB9ID0gb3B0O1xuICAvLyBtYWtlIHN1cmUgd2UgaGF2ZSBhIHRhcmdldCBicmFuY2hcbiAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIGZ1bmN0aW9uYWwvbm8tbGV0XG4gIGxldCBjdXJyZW50QnJhbmNoO1xuICBpZiAodGFyZ2V0QnJhbmNoKSB7XG4gICAgY3VycmVudEJyYW5jaCA9IGF3YWl0IGdldEN1cnJlbnRCcmFuY2hPclRhZyhnaXQpO1xuICAgIGF3YWl0IGdpdC5jaGVja291dCh0YXJnZXRCcmFuY2gpO1xuICB9XG5cbiAgLy8gRmV0Y2ggYWxsIHRhZ3MgbWVyZ2VkIGludG8gdGhlIHRhcmdldCBicmFuY2hcbiAgY29uc3QgdGFnc1JhdyA9IGF3YWl0IGdpdC50YWcoW1xuICAgICctLW1lcmdlZCcsXG4gICAgdGFyZ2V0QnJhbmNoID8/IChhd2FpdCBnZXRDdXJyZW50QnJhbmNoT3JUYWcoZ2l0KSksXG4gIF0pO1xuXG4gIGNvbnN0IGFsbFRhZ3MgPSB0YWdzUmF3XG4gICAgLnNwbGl0KC9cXG4vKVxuICAgIC5tYXAodGFnID0+IHRhZy50cmltKCkpXG4gICAgLmZpbHRlcihCb29sZWFuKVxuICAgIC5maWx0ZXIoaXNTZW12ZXIpO1xuXG4gIGNvbnN0IHJlbGV2YW50VGFncyA9IGZpbHRlckxvZ3MoYWxsVGFncywgb3B0aW9ucyk7XG5cbiAgY29uc3QgdGFnc1dpdGhIYXNoZXM6IExvZ1Jlc3VsdFtdID0gYXdhaXQgUHJvbWlzZS5hbGwoXG4gICAgcmVsZXZhbnRUYWdzLm1hcCh0YWcgPT4gZ2V0SGFzaEZyb21UYWcodGFnLCBnaXQpKSxcbiAgKTtcblxuICBpZiAoY3VycmVudEJyYW5jaCkge1xuICAgIGF3YWl0IGdpdC5jaGVja291dChjdXJyZW50QnJhbmNoKTtcbiAgfVxuXG4gIHJldHVybiB0YWdzV2l0aEhhc2hlcztcbn1cblxuLyoqXG4gKiBgZ2V0SGFzaGVzYCByZXR1cm5zIGEgbGlzdCBvZiBjb21taXQgaGFzaGVzLiBJbnRlcm5hbGx5IGl0IHVzZXMgYGdpdC5sb2coKWAgdG8gZGV0ZXJtaW5lIHRoZSBjb21taXRzIHdpdGhpbiBhIHJhbmdlLlxuICogVGhlIGFtb3VudCBjYW4gYmUgbGltaXRlZCB0byBhIG1heGltdW0gbnVtYmVyIG9mIGNvbW1pdHMgc3BlY2lmaWVkIGJ5IGBtYXhDb3VudGAuXG4gKiBXaXRoIGBmcm9tYCBhbmQgYHRvYCwgeW91IGNhbiBzcGVjaWZ5IGEgcmFuZ2Ugb2YgY29tbWl0cy5cbiAqXG4gKiAqKk5PVEU6KipcbiAqIEluIEdpdCwgc3BlY2lmeWluZyBhIHJhbmdlIHdpdGggdHdvIGRvdHMgKGBmcm9tLi50b2ApIHNlbGVjdHMgY29tbWl0cyB0aGF0IGFyZSByZWFjaGFibGUgZnJvbSBgdG9gIGJ1dCBub3QgZnJvbSBgZnJvbWAuXG4gKiBFc3NlbnRpYWxseSwgaXQgc2hvd3MgdGhlIGNvbW1pdHMgdGhhdCBhcmUgaW4gYHRvYCBidXQgbm90IGluIGBmcm9tYCwgZXhjbHVkaW5nIHRoZSBjb21taXRzIHVuaXF1ZSB0byBgZnJvbWAuXG4gKlxuICogRXhhbXBsZTpcbiAqXG4gKiBMZXQncyBjb25zaWRlciB0aGUgZm9sbG93aW5nIGNvbW1pdCBoaXN0b3J5OlxuICpcbiAqICAgQS0tLUItLS1DLS0tRC0tLUUgKG1haW4pXG4gKlxuICogVXNpbmcgYGdpdCBsb2cgQi4uRGAsIHlvdSB3b3VsZCBnZXQgdGhlIGNvbW1pdHMgQyBhbmQgRDpcbiAqXG4gKiAgIEMtLS1EXG4gKlxuICogVGhpcyBpcyBiZWNhdXNlIHRoZXNlIGNvbW1pdHMgYXJlIHJlYWNoYWJsZSBmcm9tIEQgYnV0IG5vdCBmcm9tIEIuXG4gKlxuICogQVNDSUkgUmVwcmVzZW50YXRpb246XG4gKlxuICogICBNYWluIEJyYW5jaDogICAgQS0tLUItLS1DLS0tRC0tLUVcbiAqICAgICAgICAgICAgICAgICAgICAgICBcXCAgICAgICBcXFxuICogICAgICAgICAgICAgICAgICAgICAgICBcXCAgICAgICArLS0tIENvbW1pdHMgaW5jbHVkZWQgaW4gYGdpdCBsb2cgQi4uRGBcbiAqICAgICAgICAgICAgICAgICAgICAgICAgIFxcXG4gKiAgICAgICAgICAgICAgICAgICAgICAgICAgKy0tLSBFeGNsdWRlZCBieSB0aGUgYGZyb21gIHBhcmFtZXRlclxuICpcbiAqIFdpdGggYHNpbXBsZS1naXRgLCB3aGVuIHlvdSBzcGVjaWZ5IGEgYGZyb21gIGFuZCBgdG9gIHJhbmdlIGxpa2UgdGhpczpcbiAqXG4gKiAgIGdpdC5sb2coeyBmcm9tOiAnQicsIHRvOiAnRCcgfSk7XG4gKlxuICogSXQgaW50ZXJwcmV0cyBpdCBzaW1pbGFybHksIHNlbGVjdGluZyBjb21taXRzIGJldHdlZW4gQiBhbmQgRCwgaW5jbHVzaXZlIG9mIEQgYnV0IGV4Y2x1c2l2ZSBvZiBCLlxuICogRm9yIGBnaXQubG9nKHsgZnJvbTogJ0InLCB0bzogJ0QnIH0pYCBvciBgZ2l0IGxvZyBCLi5EYCwgY29tbWl0cyBDIGFuZCBEIGFyZSBzZWxlY3RlZC5cbiAqXG4gKiBAcGFyYW0gb3B0aW9ucyBPYmplY3QgY29udGFpbmluZyBgZnJvbWAsIGB0b2AsIGFuZCBvcHRpb25hbGx5IGBtYXhDb3VudGAgdG8gc3BlY2lmeSB0aGUgY29tbWl0IHJhbmdlIGFuZCBsaW1pdC5cbiAqIEBwYXJhbSBnaXQgVGhlIGBzaW1wbGUtZ2l0YCBpbnN0YW5jZSB1c2VkIHRvIGV4ZWN1dGUgR2l0IGNvbW1hbmRzLlxuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gZ2V0SGFzaGVzKFxuICBvcHRpb25zOiBTaW1wbGVHaXRMb2dPcHRpb25zICYgUGljazxMb2dPcHRpb25zLCAndGFyZ2V0QnJhbmNoJz4gPSB7fSxcbiAgZ2l0ID0gc2ltcGxlR2l0KCksXG4pOiBQcm9taXNlPExvZ1Jlc3VsdFtdPiB7XG4gIGNvbnN0IHsgdGFyZ2V0QnJhbmNoLCBmcm9tLCB0bywgbWF4Q291bnQsIC4uLm9wdCB9ID0gb3B0aW9ucztcblxuICB2YWxpZGF0ZUZpbHRlcih7IGZyb20sIHRvIH0pO1xuXG4gIC8vIEVuc3VyZSB5b3UgYXJlIG9uIHRoZSBjb3JyZWN0IGJyYW5jaFxuICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgZnVuY3Rpb25hbC9uby1sZXRcbiAgbGV0IGN1cnJlbnRCcmFuY2g7XG4gIGlmICh0YXJnZXRCcmFuY2gpIHtcbiAgICBjdXJyZW50QnJhbmNoID0gYXdhaXQgZ2V0Q3VycmVudEJyYW5jaE9yVGFnKGdpdCk7XG4gICAgYXdhaXQgZ2l0LmNoZWNrb3V0KHRhcmdldEJyYW5jaCk7XG4gIH1cblxuICBjb25zdCBsb2dzID0gYXdhaXQgZ2l0LmxvZyh7XG4gICAgLi4ub3B0LFxuICAgIGZvcm1hdDoge1xuICAgICAgaGFzaDogJyVIJyxcbiAgICAgIG1lc3NhZ2U6ICclcycsXG4gICAgfSxcbiAgICBmcm9tLFxuICAgIHRvLFxuICAgIG1heENvdW50LFxuICB9KTtcblxuICAvLyBFbnN1cmUgeW91IGFyZSBiYWNrIHRvIHRoZSBpbml0aWFsIGJyYW5jaFxuICBpZiAodGFyZ2V0QnJhbmNoKSB7XG4gICAgYXdhaXQgZ2l0LmNoZWNrb3V0KGN1cnJlbnRCcmFuY2ggYXMgc3RyaW5nKTtcbiAgfVxuXG4gIHJldHVybiBbLi4ubG9ncy5hbGxdO1xufVxuIiwgImNvbnN0IF9faW5qZWN0ZWRfZmlsZW5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvdXRpbHMvc3JjL2xpYi9zZW12ZXIudHNcIjtjb25zdCBfX2luamVjdGVkX2Rpcm5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvdXRpbHMvc3JjL2xpYlwiO2NvbnN0IF9faW5qZWN0ZWRfaW1wb3J0X21ldGFfdXJsX18gPSBcImZpbGU6Ly8vaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy91dGlscy9zcmMvbGliL3NlbXZlci50c1wiO2ltcG9ydCB7IHJjb21wYXJlLCB2YWxpZCB9IGZyb20gJ3NlbXZlcic7XG5cbmV4cG9ydCBmdW5jdGlvbiBub3JtYWxpemVTZW12ZXIoc2VtdmVyU3RyaW5nOiBzdHJpbmcpOiBzdHJpbmcge1xuICBpZiAoc2VtdmVyU3RyaW5nLnN0YXJ0c1dpdGgoJ3YnKSB8fCBzZW12ZXJTdHJpbmcuc3RhcnRzV2l0aCgnVicpKSB7XG4gICAgcmV0dXJuIHNlbXZlclN0cmluZy5zbGljZSgxKTtcbiAgfVxuXG4gIGlmIChzZW12ZXJTdHJpbmcuaW5jbHVkZXMoJ0AnKSkge1xuICAgIHJldHVybiBzZW12ZXJTdHJpbmcuc3BsaXQoJ0AnKS5hdCgtMSkgPz8gJyc7XG4gIH1cblxuICByZXR1cm4gc2VtdmVyU3RyaW5nO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gaXNTZW12ZXIoc2VtdmVyU3RyaW5nID0gJycpOiBib29sZWFuIHtcbiAgcmV0dXJuIHZhbGlkKG5vcm1hbGl6ZVNlbXZlcihzZW12ZXJTdHJpbmcpKSAhPSBudWxsO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gc29ydFNlbXZlcnMoc2VtdmVyU3RyaW5nczogc3RyaW5nW10pOiBzdHJpbmdbXSB7XG4gIHJldHVybiBzZW12ZXJTdHJpbmdzLm1hcChub3JtYWxpemVTZW12ZXIpLmZpbHRlcihpc1NlbXZlcikuc29ydChyY29tcGFyZSk7XG59XG4iLCAiY29uc3QgX19pbmplY3RlZF9maWxlbmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy91dGlscy9zcmMvbGliL2dpdC9naXQudHNcIjtjb25zdCBfX2luamVjdGVkX2Rpcm5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvdXRpbHMvc3JjL2xpYi9naXRcIjtjb25zdCBfX2luamVjdGVkX2ltcG9ydF9tZXRhX3VybF9fID0gXCJmaWxlOi8vL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvdXRpbHMvc3JjL2xpYi9naXQvZ2l0LnRzXCI7aW1wb3J0IHBhdGggZnJvbSAnbm9kZTpwYXRoJztcbmltcG9ydCB7IHR5cGUgU3RhdHVzUmVzdWx0LCBzaW1wbGVHaXQgfSBmcm9tICdzaW1wbGUtZ2l0JztcbmltcG9ydCB7IHVpIH0gZnJvbSAnLi4vbG9nZ2luZy5qcyc7XG5pbXBvcnQgeyB0b1VuaXhQYXRoIH0gZnJvbSAnLi4vdHJhbnNmb3JtLmpzJztcblxuZXhwb3J0IGZ1bmN0aW9uIGdldEdpdFJvb3QoZ2l0ID0gc2ltcGxlR2l0KCkpOiBQcm9taXNlPHN0cmluZz4ge1xuICByZXR1cm4gZ2l0LnJldnBhcnNlKCctLXNob3ctdG9wbGV2ZWwnKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGZvcm1hdEdpdFBhdGgoZmlsZVBhdGg6IHN0cmluZywgZ2l0Um9vdDogc3RyaW5nKTogc3RyaW5nIHtcbiAgY29uc3QgYWJzb2x1dGVQYXRoID0gcGF0aC5pc0Fic29sdXRlKGZpbGVQYXRoKVxuICAgID8gZmlsZVBhdGhcbiAgICA6IHBhdGguam9pbihwcm9jZXNzLmN3ZCgpLCBmaWxlUGF0aCk7XG4gIGNvbnN0IHJlbGF0aXZlUGF0aCA9IHBhdGgucmVsYXRpdmUoZ2l0Um9vdCwgYWJzb2x1dGVQYXRoKTtcbiAgcmV0dXJuIHRvVW5peFBhdGgocmVsYXRpdmVQYXRoKTtcbn1cblxuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIHRvR2l0UGF0aChcbiAgZmlsZVBhdGg6IHN0cmluZyxcbiAgZ2l0ID0gc2ltcGxlR2l0KCksXG4pOiBQcm9taXNlPHN0cmluZz4ge1xuICBjb25zdCBnaXRSb290ID0gYXdhaXQgZ2V0R2l0Um9vdChnaXQpO1xuICByZXR1cm4gZm9ybWF0R2l0UGF0aChmaWxlUGF0aCwgZ2l0Um9vdCk7XG59XG5cbmV4cG9ydCBjbGFzcyBHaXRTdGF0dXNFcnJvciBleHRlbmRzIEVycm9yIHtcbiAgc3RhdGljIGlnbm9yZWRQcm9wcyA9IG5ldyBTZXQoWydjdXJyZW50JywgJ3RyYWNraW5nJ10pO1xuXG4gIHN0YXRpYyBnZXRSZWR1Y2VkU3RhdHVzKHN0YXR1czogU3RhdHVzUmVzdWx0KSB7XG4gICAgcmV0dXJuIE9iamVjdC5mcm9tRW50cmllcyhcbiAgICAgIE9iamVjdC5lbnRyaWVzKHN0YXR1cylcbiAgICAgICAgLmZpbHRlcigoW2tleV0pID0+ICF0aGlzLmlnbm9yZWRQcm9wcy5oYXMoa2V5KSlcbiAgICAgICAgLmZpbHRlcihcbiAgICAgICAgICAoXG4gICAgICAgICAgICBlbnRyeTogW1xuICAgICAgICAgICAgICBzdHJpbmcsXG4gICAgICAgICAgICAgIG51bWJlciB8IHN0cmluZyB8IGJvb2xlYW4gfCBudWxsIHwgdW5kZWZpbmVkIHwgdW5rbm93bltdLFxuICAgICAgICAgICAgXSxcbiAgICAgICAgICApID0+IHtcbiAgICAgICAgICAgIGNvbnN0IHZhbHVlID0gZW50cnlbMV07XG4gICAgICAgICAgICBpZiAodmFsdWUgPT0gbnVsbCkge1xuICAgICAgICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBpZiAoQXJyYXkuaXNBcnJheSh2YWx1ZSkgJiYgdmFsdWUubGVuZ3RoID09PSAwKSB7XG4gICAgICAgICAgICAgIHJldHVybiBmYWxzZTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGlmICh0eXBlb2YgdmFsdWUgPT09ICdudW1iZXInICYmIHZhbHVlID09PSAwKSB7XG4gICAgICAgICAgICAgIHJldHVybiBmYWxzZTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIHJldHVybiAhKHR5cGVvZiB2YWx1ZSA9PT0gJ2Jvb2xlYW4nICYmICF2YWx1ZSk7XG4gICAgICAgICAgfSxcbiAgICAgICAgKSxcbiAgICApO1xuICB9XG5cbiAgY29uc3RydWN0b3Ioc3RhdHVzOiBTdGF0dXNSZXN1bHQpIHtcbiAgICBzdXBlcihcbiAgICAgIGBXb3JraW5nIGRpcmVjdG9yeSBuZWVkcyB0byBiZSBjbGVhbiBiZWZvcmUgd2UgeW91IGNhbiBwcm9jZWVkLiBDb21taXQgeW91ciBsb2NhbCBjaGFuZ2VzIG9yIHN0YXNoIHRoZW06IFxcbiAke0pTT04uc3RyaW5naWZ5KFxuICAgICAgICBHaXRTdGF0dXNFcnJvci5nZXRSZWR1Y2VkU3RhdHVzKHN0YXR1cyksXG4gICAgICAgIG51bGwsXG4gICAgICAgIDIsXG4gICAgICApfWAsXG4gICAgKTtcbiAgfVxufVxuXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gZ3VhcmRBZ2FpbnN0TG9jYWxDaGFuZ2VzKFxuICBnaXQgPSBzaW1wbGVHaXQoKSxcbik6IFByb21pc2U8dm9pZD4ge1xuICBjb25zdCBzdGF0dXMgPSBhd2FpdCBnaXQuc3RhdHVzKFsnLXMnXSk7XG4gIGlmIChzdGF0dXMuZmlsZXMubGVuZ3RoID4gMCkge1xuICAgIHRocm93IG5ldyBHaXRTdGF0dXNFcnJvcihzdGF0dXMpO1xuICB9XG59XG5cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBzYWZlQ2hlY2tvdXQoXG4gIGJyYW5jaE9ySGFzaDogc3RyaW5nLFxuICBmb3JjZUNsZWFuU3RhdHVzID0gZmFsc2UsXG4gIGdpdCA9IHNpbXBsZUdpdCgpLFxuKTogUHJvbWlzZTx2b2lkPiB7XG4gIC8vIGdpdCByZXF1aXJlcyBhIGNsZWFuIGhpc3RvcnkgdG8gY2hlY2sgb3V0IGEgYnJhbmNoXG4gIGlmIChmb3JjZUNsZWFuU3RhdHVzKSB7XG4gICAgYXdhaXQgZ2l0LnJhdyhbJ3Jlc2V0JywgJy0taGFyZCddKTtcbiAgICBhd2FpdCBnaXQuY2xlYW4oWydmJywgJ2QnXSk7XG4gICAgdWkoKS5sb2dnZXIuaW5mbyhgZ2l0IHN0YXR1cyBjbGVhbmVkYCk7XG4gIH1cbiAgYXdhaXQgZ3VhcmRBZ2FpbnN0TG9jYWxDaGFuZ2VzKGdpdCk7XG4gIGF3YWl0IGdpdC5jaGVja291dChicmFuY2hPckhhc2gpO1xufVxuIiwgImNvbnN0IF9faW5qZWN0ZWRfZmlsZW5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvdXRpbHMvc3JjL2xpYi90cmFuc2Zvcm0udHNcIjtjb25zdCBfX2luamVjdGVkX2Rpcm5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvdXRpbHMvc3JjL2xpYlwiO2NvbnN0IF9faW5qZWN0ZWRfaW1wb3J0X21ldGFfdXJsX18gPSBcImZpbGU6Ly8vaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy91dGlscy9zcmMvbGliL3RyYW5zZm9ybS50c1wiO2ltcG9ydCB7IHBsYXRmb3JtIH0gZnJvbSAnbm9kZTpvcyc7XG5cbmV4cG9ydCBmdW5jdGlvbiB0b0FycmF5PFQ+KHZhbDogVCB8IFRbXSk6IFRbXSB7XG4gIHJldHVybiBBcnJheS5pc0FycmF5KHZhbCkgPyB2YWwgOiBbdmFsXTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIG9iamVjdFRvS2V5czxUIGV4dGVuZHMgb2JqZWN0PihvYmo6IFQpIHtcbiAgcmV0dXJuIE9iamVjdC5rZXlzKG9iaikgYXMgKGtleW9mIFQpW107XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBvYmplY3RUb0VudHJpZXM8VCBleHRlbmRzIG9iamVjdD4ob2JqOiBUKSB7XG4gIHJldHVybiBPYmplY3QuZW50cmllcyhvYmopIGFzIFtrZXlvZiBULCBUW2tleW9mIFRdXVtdO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gb2JqZWN0RnJvbUVudHJpZXM8SyBleHRlbmRzIFByb3BlcnR5S2V5LCBWPihlbnRyaWVzOiBbSywgVl1bXSkge1xuICByZXR1cm4gT2JqZWN0LmZyb21FbnRyaWVzKGVudHJpZXMpIGFzIFJlY29yZDxLLCBWPjtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGNvdW50T2NjdXJyZW5jZXM8VCBleHRlbmRzIFByb3BlcnR5S2V5PihcbiAgdmFsdWVzOiBUW10sXG4pOiBQYXJ0aWFsPFJlY29yZDxULCBudW1iZXI+PiB7XG4gIHJldHVybiB2YWx1ZXMucmVkdWNlPFBhcnRpYWw8UmVjb3JkPFQsIG51bWJlcj4+PihcbiAgICAoYWNjLCB2YWx1ZSkgPT4gKHsgLi4uYWNjLCBbdmFsdWVdOiAoYWNjW3ZhbHVlXSA/PyAwKSArIDEgfSksXG4gICAge30sXG4gICk7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBkaXN0aW5jdDxUIGV4dGVuZHMgc3RyaW5nIHwgbnVtYmVyIHwgYm9vbGVhbj4oYXJyYXk6IFRbXSk6IFRbXSB7XG4gIHJldHVybiBbLi4ubmV3IFNldChhcnJheSldO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gZGVlcENsb25lPFQ+KG9iajogVCk6IFQge1xuICByZXR1cm4gb2JqID09IG51bGwgfHwgdHlwZW9mIG9iaiAhPT0gJ29iamVjdCcgPyBvYmogOiBzdHJ1Y3R1cmVkQ2xvbmUob2JqKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGZhY3Rvck9mPFQ+KGl0ZW1zOiBUW10sIGZpbHRlckZuOiAoaTogVCkgPT4gYm9vbGVhbik6IG51bWJlciB7XG4gIGNvbnN0IGl0ZW1Db3VudCA9IGl0ZW1zLmxlbmd0aDtcbiAgLy8gZWFybHkgZXhpdCBmb3IgZW1wdHkgcm93c1xuICBpZiAoIWl0ZW1Db3VudCkge1xuICAgIHJldHVybiAxO1xuICB9XG4gIGNvbnN0IGZpbHRlckNvdW50ID0gaXRlbXMuZmlsdGVyKGZpbHRlckZuKS5sZW5ndGg7XG4gIC8vIGlmIG5vIHJvd3MgcmVzdWx0IGZyb20gdGhlIGZpbHRlciBmbiB3ZSBmb3J3YXJkIHJldHVybiAxIGFzIGZhY3RvclxuICByZXR1cm4gZmlsdGVyQ291bnQgPT09IDAgPyAxIDogKGl0ZW1Db3VudCAtIGZpbHRlckNvdW50KSAvIGl0ZW1Db3VudDtcbn1cblxudHlwZSBBcmd1bWVudFZhbHVlID0gbnVtYmVyIHwgc3RyaW5nIHwgYm9vbGVhbiB8IHN0cmluZ1tdO1xuZXhwb3J0IHR5cGUgQ2xpQXJnc09iamVjdDxUIGV4dGVuZHMgb2JqZWN0ID0gUmVjb3JkPHN0cmluZywgQXJndW1lbnRWYWx1ZT4+ID1cbiAgVCBleHRlbmRzIG5ldmVyXG4gICAgPyBSZWNvcmQ8c3RyaW5nLCBBcmd1bWVudFZhbHVlIHwgdW5kZWZpbmVkPiB8IHsgXzogc3RyaW5nIH1cbiAgICA6IFQ7XG5cbi8qKlxuICogQ29udmVydHMgYW4gb2JqZWN0IHdpdGggZGlmZmVyZW50IHR5cGVzIG9mIHZhbHVlcyBpbnRvIGFuIGFycmF5IG9mIGNvbW1hbmQtbGluZSBhcmd1bWVudHMuXG4gKlxuICogQGV4YW1wbGVcbiAqIGNvbnN0IGFyZ3MgPSBvYmplY3RUb1Byb2Nlc3NBcmdzKHtcbiAqICAgXzogWydub2RlJywgJ2luZGV4LmpzJ10sIC8vIG5vZGUgaW5kZXguanNcbiAqICAgbmFtZTogJ0p1YW5pdGEnLCAvLyAtLW5hbWU9SnVhbml0YVxuICogICBmb3JtYXRzOiBbJ2pzb24nLCAnbWQnXSAvLyAtLWZvcm1hdD1qc29uIC0tZm9ybWF0PW1kXG4gKiB9KTtcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIG9iamVjdFRvQ2xpQXJnczxcbiAgVCBleHRlbmRzIG9iamVjdCA9IFJlY29yZDxzdHJpbmcsIEFyZ3VtZW50VmFsdWU+LFxuPihwYXJhbXM/OiBDbGlBcmdzT2JqZWN0PFQ+KTogc3RyaW5nW10ge1xuICBpZiAoIXBhcmFtcykge1xuICAgIHJldHVybiBbXTtcbiAgfVxuXG4gIHJldHVybiBPYmplY3QuZW50cmllcyhwYXJhbXMpLmZsYXRNYXAoKFtrZXksIHZhbHVlXSkgPT4ge1xuICAgIC8vIHByb2Nlc3MvZmlsZS9zY3JpcHRcbiAgICBpZiAoa2V5ID09PSAnXycpIHtcbiAgICAgIHJldHVybiBBcnJheS5pc0FycmF5KHZhbHVlKSA/IHZhbHVlIDogW2Ake3ZhbHVlfWBdO1xuICAgIH1cbiAgICBjb25zdCBwcmVmaXggPSBrZXkubGVuZ3RoID09PSAxID8gJy0nIDogJy0tJztcbiAgICAvLyBcIi0qXCIgYXJndW1lbnRzIChzaG9ydGhhbmRzKVxuICAgIGlmIChBcnJheS5pc0FycmF5KHZhbHVlKSkge1xuICAgICAgcmV0dXJuIHZhbHVlLm1hcCh2ID0+IGAke3ByZWZpeH0ke2tleX09XCIke3Z9XCJgKTtcbiAgICB9XG4gICAgLy8gXCItLSpcIiBhcmd1bWVudHMgPT09PT09PT09PVxuXG4gICAgaWYgKEFycmF5LmlzQXJyYXkodmFsdWUpKSB7XG4gICAgICByZXR1cm4gdmFsdWUubWFwKHYgPT4gYCR7cHJlZml4fSR7a2V5fT1cIiR7dn1cImApO1xuICAgIH1cblxuICAgIGlmICh0eXBlb2YgdmFsdWUgPT09ICdvYmplY3QnKSB7XG4gICAgICByZXR1cm4gT2JqZWN0LmVudHJpZXModmFsdWUgYXMgUmVjb3JkPHN0cmluZywgQXJndW1lbnRWYWx1ZT4pLmZsYXRNYXAoXG4gICAgICAgIC8vIHRyYW5zZm9ybSBuZXN0ZWQgb2JqZWN0cyB0byB0aGUgZG90IG5vdGF0aW9uIGBrZXkuc3Via2V5YFxuICAgICAgICAoW2ssIHZdKSA9PiBvYmplY3RUb0NsaUFyZ3MoeyBbYCR7a2V5fS4ke2t9YF06IHYgfSksXG4gICAgICApO1xuICAgIH1cblxuICAgIGlmICh0eXBlb2YgdmFsdWUgPT09ICdzdHJpbmcnKSB7XG4gICAgICByZXR1cm4gW2Ake3ByZWZpeH0ke2tleX09XCIke3ZhbHVlfVwiYF07XG4gICAgfVxuXG4gICAgaWYgKHR5cGVvZiB2YWx1ZSA9PT0gJ251bWJlcicpIHtcbiAgICAgIHJldHVybiBbYCR7cHJlZml4fSR7a2V5fT0ke3ZhbHVlfWBdO1xuICAgIH1cblxuICAgIGlmICh0eXBlb2YgdmFsdWUgPT09ICdib29sZWFuJykge1xuICAgICAgcmV0dXJuIFtgJHtwcmVmaXh9JHt2YWx1ZSA/ICcnIDogJ25vLSd9JHtrZXl9YF07XG4gICAgfVxuXG4gICAgdGhyb3cgbmV3IEVycm9yKGBVbnN1cHBvcnRlZCB0eXBlICR7dHlwZW9mIHZhbHVlfSBmb3Iga2V5ICR7a2V5fWApO1xuICB9KTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHRvVW5peFBhdGgocGF0aDogc3RyaW5nKTogc3RyaW5nIHtcbiAgcmV0dXJuIHBhdGgucmVwbGFjZSgvXFxcXC9nLCAnLycpO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gdG9Vbml4TmV3bGluZXModGV4dDogc3RyaW5nKTogc3RyaW5nIHtcbiAgcmV0dXJuIHBsYXRmb3JtKCkgPT09ICd3aW4zMicgPyB0ZXh0LnJlcGxhY2UoL1xcclxcbi9nLCAnXFxuJykgOiB0ZXh0O1xufVxuXG5leHBvcnQgZnVuY3Rpb24gZnJvbUpzb25MaW5lczxUID0gdW5rbm93bj4oanNvbkxpbmVzOiBzdHJpbmcpIHtcbiAgY29uc3QgdW5pZmllZE5ld0xpbmVzID0gdG9Vbml4TmV3bGluZXMoanNvbkxpbmVzKS50cmltKCk7XG4gIHJldHVybiBKU09OLnBhcnNlKGBbJHt1bmlmaWVkTmV3TGluZXMuc3BsaXQoJ1xcbicpLmpvaW4oJywnKX1dYCkgYXMgVDtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHRvSnNvbkxpbmVzPFQ+KGpzb246IFRbXSkge1xuICByZXR1cm4ganNvbi5tYXAoaXRlbSA9PiBKU09OLnN0cmluZ2lmeShpdGVtKSkuam9pbignXFxuJyk7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBjYXBpdGFsaXplPFQgZXh0ZW5kcyBzdHJpbmc+KHRleHQ6IFQpOiBDYXBpdGFsaXplPFQ+IHtcbiAgcmV0dXJuIGAke3RleHQuY2hhckF0KDApLnRvTG9jYWxlVXBwZXJDYXNlKCl9JHt0ZXh0LnNsaWNlKFxuICAgIDEsXG4gICl9YCBhcyBDYXBpdGFsaXplPFQ+O1xufVxuXG5leHBvcnQgZnVuY3Rpb24gdG9OdW1iZXJQcmVjaXNpb24oXG4gIHZhbHVlOiBudW1iZXIsXG4gIGRlY2ltYWxQbGFjZXM6IG51bWJlcixcbik6IG51bWJlciB7XG4gIHJldHVybiBOdW1iZXIoXG4gICAgYCR7TWF0aC5yb3VuZChcbiAgICAgIE51bWJlci5wYXJzZUZsb2F0KGAke3ZhbHVlfWUke2RlY2ltYWxQbGFjZXN9YCksXG4gICAgKX1lLSR7ZGVjaW1hbFBsYWNlc31gLFxuICApO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gdG9PcmRpbmFsKHZhbHVlOiBudW1iZXIpOiBzdHJpbmcge1xuICAvKiBlc2xpbnQtZGlzYWJsZSBAdHlwZXNjcmlwdC1lc2xpbnQvbm8tbWFnaWMtbnVtYmVycyAqL1xuICBpZiAodmFsdWUgJSAxMCA9PT0gMSAmJiB2YWx1ZSAlIDEwMCAhPT0gMTEpIHtcbiAgICByZXR1cm4gYCR7dmFsdWV9c3RgO1xuICB9XG5cbiAgaWYgKHZhbHVlICUgMTAgPT09IDIgJiYgdmFsdWUgJSAxMDAgIT09IDEyKSB7XG4gICAgcmV0dXJuIGAke3ZhbHVlfW5kYDtcbiAgfVxuXG4gIGlmICh2YWx1ZSAlIDEwID09PSAzICYmIHZhbHVlICUgMTAwICE9PSAxMykge1xuICAgIHJldHVybiBgJHt2YWx1ZX1yZGA7XG4gIH1cbiAgLyogZXNsaW50LWVuYWJsZSBAdHlwZXNjcmlwdC1lc2xpbnQvbm8tbWFnaWMtbnVtYmVycyAqL1xuXG4gIHJldHVybiBgJHt2YWx1ZX10aGA7XG59XG4iLCAiY29uc3QgX19pbmplY3RlZF9maWxlbmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy91dGlscy9zcmMvbGliL21lcmdlLWNvbmZpZ3MudHNcIjtjb25zdCBfX2luamVjdGVkX2Rpcm5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvdXRpbHMvc3JjL2xpYlwiO2NvbnN0IF9faW5qZWN0ZWRfaW1wb3J0X21ldGFfdXJsX18gPSBcImZpbGU6Ly8vaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy91dGlscy9zcmMvbGliL21lcmdlLWNvbmZpZ3MudHNcIjtpbXBvcnQgdHlwZSB7XG4gIENhdGVnb3J5Q29uZmlnLFxuICBDb3JlQ29uZmlnLFxuICBQZXJzaXN0Q29uZmlnLFxuICBQbHVnaW5Db25maWcsXG4gIFVwbG9hZENvbmZpZyxcbn0gZnJvbSAnQGNvZGUtcHVzaHVwL21vZGVscyc7XG5cbmV4cG9ydCBmdW5jdGlvbiBtZXJnZUNvbmZpZ3MoXG4gIGNvbmZpZzogQ29yZUNvbmZpZyxcbiAgLi4uY29uZmlnczogUGFydGlhbDxDb3JlQ29uZmlnPltdXG4pOiBQYXJ0aWFsPENvcmVDb25maWc+IHtcbiAgcmV0dXJuIGNvbmZpZ3MucmVkdWNlKFxuICAgIChhY2MsIG9iaikgPT4gKHtcbiAgICAgIC4uLmFjYyxcbiAgICAgIC4uLm1lcmdlQ2F0ZWdvcmllcyhhY2MuY2F0ZWdvcmllcywgb2JqLmNhdGVnb3JpZXMpLFxuICAgICAgLi4ubWVyZ2VQbHVnaW5zKGFjYy5wbHVnaW5zLCBvYmoucGx1Z2lucyksXG4gICAgICAuLi5tZXJnZVBlcnNpc3QoYWNjLnBlcnNpc3QsIG9iai5wZXJzaXN0KSxcbiAgICAgIC4uLm1lcmdlVXBsb2FkKGFjYy51cGxvYWQsIG9iai51cGxvYWQpLFxuICAgIH0pLFxuICAgIGNvbmZpZyxcbiAgKTtcbn1cblxuZnVuY3Rpb24gbWVyZ2VDYXRlZ29yaWVzKFxuICBhOiBDYXRlZ29yeUNvbmZpZ1tdIHwgdW5kZWZpbmVkLFxuICBiOiBDYXRlZ29yeUNvbmZpZ1tdIHwgdW5kZWZpbmVkLFxuKTogUGljazxDb3JlQ29uZmlnLCAnY2F0ZWdvcmllcyc+IHtcbiAgaWYgKCFhICYmICFiKSB7XG4gICAgcmV0dXJuIHt9O1xuICB9XG5cbiAgY29uc3QgbWVyZ2VkTWFwID0gbmV3IE1hcDxzdHJpbmcsIENhdGVnb3J5Q29uZmlnPigpO1xuXG4gIGNvbnN0IGFkZFRvTWFwID0gKGNhdGVnb3JpZXM6IENhdGVnb3J5Q29uZmlnW10pID0+IHtcbiAgICBjYXRlZ29yaWVzLmZvckVhY2gobmV3T2JqZWN0ID0+IHtcbiAgICAgIGlmIChtZXJnZWRNYXAuaGFzKG5ld09iamVjdC5zbHVnKSkge1xuICAgICAgICBjb25zdCBleGlzdGluZ09iamVjdDogQ2F0ZWdvcnlDb25maWcgfCB1bmRlZmluZWQgPSBtZXJnZWRNYXAuZ2V0KFxuICAgICAgICAgIG5ld09iamVjdC5zbHVnLFxuICAgICAgICApO1xuXG4gICAgICAgIG1lcmdlZE1hcC5zZXQobmV3T2JqZWN0LnNsdWcsIHtcbiAgICAgICAgICAuLi5leGlzdGluZ09iamVjdCxcbiAgICAgICAgICAuLi5uZXdPYmplY3QsXG5cbiAgICAgICAgICByZWZzOiBtZXJnZUJ5VW5pcXVlQ2F0ZWdvcnlSZWZDb21iaW5hdGlvbihcbiAgICAgICAgICAgIGV4aXN0aW5nT2JqZWN0Py5yZWZzLFxuICAgICAgICAgICAgbmV3T2JqZWN0LnJlZnMsXG4gICAgICAgICAgKSxcbiAgICAgICAgfSk7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICBtZXJnZWRNYXAuc2V0KG5ld09iamVjdC5zbHVnLCBuZXdPYmplY3QpO1xuICAgICAgfVxuICAgIH0pO1xuICB9O1xuXG4gIGlmIChhKSB7XG4gICAgYWRkVG9NYXAoYSk7XG4gIH1cbiAgaWYgKGIpIHtcbiAgICBhZGRUb01hcChiKTtcbiAgfVxuXG4gIC8vIENvbnZlcnQgdGhlIG1hcCBiYWNrIHRvIGFuIGFycmF5XG4gIHJldHVybiB7IGNhdGVnb3JpZXM6IFsuLi5tZXJnZWRNYXAudmFsdWVzKCldIH07XG59XG5cbmZ1bmN0aW9uIG1lcmdlUGx1Z2lucyhcbiAgYTogUGx1Z2luQ29uZmlnW10gfCB1bmRlZmluZWQsXG4gIGI6IFBsdWdpbkNvbmZpZ1tdIHwgdW5kZWZpbmVkLFxuKTogUGljazxDb3JlQ29uZmlnLCAncGx1Z2lucyc+IHtcbiAgaWYgKCFhICYmICFiKSB7XG4gICAgcmV0dXJuIHsgcGx1Z2luczogW10gfTtcbiAgfVxuXG4gIGNvbnN0IG1lcmdlZE1hcCA9IG5ldyBNYXA8c3RyaW5nLCBQbHVnaW5Db25maWc+KCk7XG5cbiAgY29uc3QgYWRkVG9NYXAgPSAocGx1Z2luczogUGx1Z2luQ29uZmlnW10pID0+IHtcbiAgICBwbHVnaW5zLmZvckVhY2gobmV3T2JqZWN0ID0+IHtcbiAgICAgIG1lcmdlZE1hcC5zZXQobmV3T2JqZWN0LnNsdWcsIG5ld09iamVjdCk7XG4gICAgfSk7XG4gIH07XG5cbiAgaWYgKGEpIHtcbiAgICBhZGRUb01hcChhKTtcbiAgfVxuICBpZiAoYikge1xuICAgIGFkZFRvTWFwKGIpO1xuICB9XG5cbiAgcmV0dXJuIHsgcGx1Z2luczogWy4uLm1lcmdlZE1hcC52YWx1ZXMoKV0gfTtcbn1cblxuZnVuY3Rpb24gbWVyZ2VQZXJzaXN0KFxuICBhOiBQZXJzaXN0Q29uZmlnIHwgdW5kZWZpbmVkLFxuICBiOiBQZXJzaXN0Q29uZmlnIHwgdW5kZWZpbmVkLFxuKTogUGljazxDb3JlQ29uZmlnLCAncGVyc2lzdCc+IHtcbiAgaWYgKCFhICYmICFiKSB7XG4gICAgcmV0dXJuIHt9O1xuICB9XG5cbiAgaWYgKGEpIHtcbiAgICByZXR1cm4gYiA/IHsgcGVyc2lzdDogeyAuLi5hLCAuLi5iIH0gfSA6IHt9O1xuICB9IGVsc2Uge1xuICAgIHJldHVybiB7IHBlcnNpc3Q6IGIgfTtcbiAgfVxufVxuXG5mdW5jdGlvbiBtZXJnZUJ5VW5pcXVlQ2F0ZWdvcnlSZWZDb21iaW5hdGlvbjxcbiAgVCBleHRlbmRzIHsgc2x1Zzogc3RyaW5nOyB0eXBlOiBzdHJpbmc7IHBsdWdpbjogc3RyaW5nIH0sXG4+KGE6IFRbXSB8IHVuZGVmaW5lZCwgYjogVFtdIHwgdW5kZWZpbmVkKSB7XG4gIGNvbnN0IG1hcCA9IG5ldyBNYXA8c3RyaW5nLCBUPigpO1xuXG4gIGNvbnN0IGFkZFRvTWFwID0gKHJlZnM6IFRbXSkgPT4ge1xuICAgIHJlZnMuZm9yRWFjaChyZWYgPT4ge1xuICAgICAgY29uc3QgdW5pcXVlSWRlbnRpZmljYXRpb24gPSBgJHtyZWYudHlwZX06JHtyZWYucGx1Z2lufToke3JlZi5zbHVnfWA7XG4gICAgICBpZiAobWFwLmhhcyh1bmlxdWVJZGVudGlmaWNhdGlvbikpIHtcbiAgICAgICAgbWFwLnNldCh1bmlxdWVJZGVudGlmaWNhdGlvbiwge1xuICAgICAgICAgIC4uLm1hcC5nZXQodW5pcXVlSWRlbnRpZmljYXRpb24pLFxuICAgICAgICAgIC4uLnJlZixcbiAgICAgICAgfSk7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICBtYXAuc2V0KHVuaXF1ZUlkZW50aWZpY2F0aW9uLCByZWYpO1xuICAgICAgfVxuICAgIH0pO1xuICB9O1xuXG4gIC8vIEFkZCBvYmplY3RzIGZyb20gYm90aCBhcnJheXMgdG8gdGhlIG1hcFxuICBpZiAoYSkge1xuICAgIGFkZFRvTWFwKGEpO1xuICB9XG4gIGlmIChiKSB7XG4gICAgYWRkVG9NYXAoYik7XG4gIH1cblxuICByZXR1cm4gWy4uLm1hcC52YWx1ZXMoKV07XG59XG5cbmZ1bmN0aW9uIG1lcmdlVXBsb2FkKFxuICBhOiBVcGxvYWRDb25maWcgfCB1bmRlZmluZWQsXG4gIGI6IFVwbG9hZENvbmZpZyB8IHVuZGVmaW5lZCxcbik6IFBpY2s8Q29yZUNvbmZpZywgJ3VwbG9hZCc+IHtcbiAgaWYgKCFhICYmICFiKSB7XG4gICAgcmV0dXJuIHt9O1xuICB9XG5cbiAgaWYgKGEpIHtcbiAgICByZXR1cm4gYiA/IHsgdXBsb2FkOiB7IC4uLmEsIC4uLmIgfSB9IDoge307XG4gIH0gZWxzZSB7XG4gICAgcmV0dXJuIHsgdXBsb2FkOiBiIH07XG4gIH1cbn1cbiIsICJjb25zdCBfX2luamVjdGVkX2ZpbGVuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3V0aWxzL3NyYy9saWIvcHJvZ3Jlc3MudHNcIjtjb25zdCBfX2luamVjdGVkX2Rpcm5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvdXRpbHMvc3JjL2xpYlwiO2NvbnN0IF9faW5qZWN0ZWRfaW1wb3J0X21ldGFfdXJsX18gPSBcImZpbGU6Ly8vaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy91dGlscy9zcmMvbGliL3Byb2dyZXNzLnRzXCI7aW1wb3J0IHsgYmxhY2ssIGJvbGQsIGdyYXksIGdyZWVuIH0gZnJvbSAnYW5zaXMnO1xuaW1wb3J0IHsgdHlwZSBDdG9yT3B0aW9ucywgTXVsdGlQcm9ncmVzc0JhcnMgfSBmcm9tICdtdWx0aS1wcm9ncmVzcy1iYXJzJztcbmltcG9ydCB7IFRFUk1JTkFMX1dJRFRIIH0gZnJvbSAnLi9yZXBvcnRzL2NvbnN0YW50cy5qcyc7XG5cbnR5cGUgQmFyU3R5bGVzID0gJ2FjdGl2ZScgfCAnZG9uZScgfCAnaWRsZSc7XG50eXBlIFN0YXR1c1N0eWxlcyA9IFJlY29yZDxCYXJTdHlsZXMsIChzOiBzdHJpbmcpID0+IHN0cmluZz47XG5leHBvcnQgY29uc3QgYmFyU3R5bGVzOiBTdGF0dXNTdHlsZXMgPSB7XG4gIGFjdGl2ZTogKHM6IHN0cmluZykgPT4gZ3JlZW4ocyksXG4gIGRvbmU6IChzOiBzdHJpbmcpID0+IGdyYXkocyksXG4gIGlkbGU6IChzOiBzdHJpbmcpID0+IGdyYXkocyksXG59O1xuXG5leHBvcnQgY29uc3QgbWVzc2FnZVN0eWxlczogU3RhdHVzU3R5bGVzID0ge1xuICBhY3RpdmU6IChzOiBzdHJpbmcpID0+IGJsYWNrKHMpLFxuICBkb25lOiAoczogc3RyaW5nKSA9PiBib2xkLmdyZWVuKHMpLFxuICBpZGxlOiAoczogc3RyaW5nKSA9PiBncmF5KHMpLFxufTtcblxuZXhwb3J0IHR5cGUgUHJvZ3Jlc3NCYXIgPSB7XG4gIC8vIEBUT0RPIGZpbmQgYmV0dGVyIG5hbWluZ1xuICBpbmNyZW1lbnRJblN0ZXBzOiAobnVtU3RlcHM6IG51bWJlcikgPT4gdm9pZDtcbiAgdXBkYXRlVGl0bGU6ICh0aXRsZTogc3RyaW5nKSA9PiB2b2lkO1xuICBlbmRQcm9ncmVzczogKG1lc3NhZ2U/OiBzdHJpbmcpID0+IHZvaWQ7XG59O1xuXG4vLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgZnVuY3Rpb25hbC9uby1sZXRcbmxldCBtcGI6IE11bHRpUHJvZ3Jlc3NCYXJzO1xuXG5leHBvcnQgZnVuY3Rpb24gZ2V0U2luZ2xldG9uUHJvZ3Jlc3NCYXJzKFxuICBvcHRpb25zPzogUGFydGlhbDxDdG9yT3B0aW9ucz4sXG4pOiBNdWx0aVByb2dyZXNzQmFycyB7XG4gIGlmICghbXBiKSB7XG4gICAgbXBiID0gbmV3IE11bHRpUHJvZ3Jlc3NCYXJzKHtcbiAgICAgIHByb2dyZXNzV2lkdGg6IFRFUk1JTkFMX1dJRFRILFxuICAgICAgaW5pdE1lc3NhZ2U6ICcnLFxuICAgICAgYm9yZGVyOiB0cnVlLFxuICAgICAgLi4ub3B0aW9ucyxcbiAgICB9KTtcbiAgfVxuICByZXR1cm4gbXBiO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gZ2V0UHJvZ3Jlc3NCYXIodGFza05hbWU6IHN0cmluZyk6IFByb2dyZXNzQmFyIHtcbiAgY29uc3QgdGFza3MgPSBnZXRTaW5nbGV0b25Qcm9ncmVzc0JhcnMoKTtcblxuICAvLyBJbml0aWFsaXplIHByb2dyZXNzIGJhciBpZiBub3Qgc2V0XG4gIHRhc2tzLmFkZFRhc2sodGFza05hbWUsIHtcbiAgICB0eXBlOiAncGVyY2VudGFnZScsXG4gICAgcGVyY2VudGFnZTogMCxcbiAgICBtZXNzYWdlOiAnJyxcbiAgICBiYXJUcmFuc2Zvcm1GbjogYmFyU3R5bGVzLmlkbGUsXG4gIH0pO1xuXG4gIHJldHVybiB7XG4gICAgaW5jcmVtZW50SW5TdGVwczogKG51bVBsdWdpbnM6IG51bWJlcikgPT4ge1xuICAgICAgdGFza3MuaW5jcmVtZW50VGFzayh0YXNrTmFtZSwge1xuICAgICAgICBwZXJjZW50YWdlOiAxIC8gbnVtUGx1Z2lucyxcbiAgICAgICAgYmFyVHJhbnNmb3JtRm46IGJhclN0eWxlcy5hY3RpdmUsXG4gICAgICB9KTtcbiAgICB9LFxuICAgIHVwZGF0ZVRpdGxlOiAodGl0bGU6IHN0cmluZykgPT4ge1xuICAgICAgdGFza3MudXBkYXRlVGFzayh0YXNrTmFtZSwge1xuICAgICAgICBtZXNzYWdlOiB0aXRsZSxcbiAgICAgICAgYmFyVHJhbnNmb3JtRm46IGJhclN0eWxlcy5hY3RpdmUsXG4gICAgICB9KTtcbiAgICB9LFxuICAgIGVuZFByb2dyZXNzOiAobWVzc2FnZSA9ICcnKSA9PiB7XG4gICAgICB0YXNrcy5kb25lKHRhc2tOYW1lLCB7XG4gICAgICAgIG1lc3NhZ2U6IG1lc3NhZ2VTdHlsZXMuZG9uZShtZXNzYWdlKSxcbiAgICAgICAgYmFyVHJhbnNmb3JtRm46IGJhclN0eWxlcy5kb25lLFxuICAgICAgfSk7XG4gICAgfSxcbiAgfTtcbn1cbiIsICJjb25zdCBfX2luamVjdGVkX2ZpbGVuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3V0aWxzL3NyYy9saWIvcmVwb3J0cy9nZW5lcmF0ZS1tZC1yZXBvcnQudHNcIjtjb25zdCBfX2luamVjdGVkX2Rpcm5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvdXRpbHMvc3JjL2xpYi9yZXBvcnRzXCI7Y29uc3QgX19pbmplY3RlZF9pbXBvcnRfbWV0YV91cmxfXyA9IFwiZmlsZTovLy9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3V0aWxzL3NyYy9saWIvcmVwb3J0cy9nZW5lcmF0ZS1tZC1yZXBvcnQudHNcIjtpbXBvcnQgeyB0eXBlIElubGluZVRleHQsIE1hcmtkb3duRG9jdW1lbnQsIG1kIH0gZnJvbSAnYnVpbGQtbWQnO1xuaW1wb3J0IHR5cGUgeyBBdWRpdFJlcG9ydCwgSXNzdWUsIFJlcG9ydCB9IGZyb20gJ0Bjb2RlLXB1c2h1cC9tb2RlbHMnO1xuaW1wb3J0IHsgZm9ybWF0RGF0ZSwgZm9ybWF0RHVyYXRpb24gfSBmcm9tICcuLi9mb3JtYXR0aW5nLmpzJztcbmltcG9ydCB7IEhJRVJBUkNIWSB9IGZyb20gJy4uL3RleHQtZm9ybWF0cy9pbmRleC5qcyc7XG5pbXBvcnQge1xuICBGT09URVJfUFJFRklYLFxuICBSRUFETUVfTElOSyxcbiAgUkVQT1JUX0hFQURMSU5FX1RFWFQsXG59IGZyb20gJy4vY29uc3RhbnRzLmpzJztcbmltcG9ydCB7XG4gIGZvcm1hdFNvdXJjZUxpbmUsXG4gIGxpbmtUb0xvY2FsU291cmNlRm9ySWRlLFxuICBtZXRhRGVzY3JpcHRpb24sXG4gIHRhYmxlU2VjdGlvbixcbn0gZnJvbSAnLi9mb3JtYXR0aW5nLmpzJztcbmltcG9ydCB7XG4gIGNhdGVnb3JpZXNEZXRhaWxzU2VjdGlvbixcbiAgY2F0ZWdvcmllc092ZXJ2aWV3U2VjdGlvbixcbn0gZnJvbSAnLi9nZW5lcmF0ZS1tZC1yZXBvcnQtY2F0ZWdveS1zZWN0aW9uLmpzJztcbmltcG9ydCB0eXBlIHsgTWRSZXBvcnRPcHRpb25zLCBTY29yZWRSZXBvcnQgfSBmcm9tICcuL3R5cGVzLmpzJztcbmltcG9ydCB7IGZvcm1hdFJlcG9ydFNjb3JlLCBzY29yZU1hcmtlciwgc2V2ZXJpdHlNYXJrZXIgfSBmcm9tICcuL3V0aWxzLmpzJztcblxuZXhwb3J0IGZ1bmN0aW9uIGF1ZGl0RGV0YWlsc0F1ZGl0VmFsdWUoe1xuICBzY29yZSxcbiAgdmFsdWUsXG4gIGRpc3BsYXlWYWx1ZSxcbn06IEF1ZGl0UmVwb3J0KTogSW5saW5lVGV4dCB7XG4gIHJldHVybiBtZGAke3Njb3JlTWFya2VyKHNjb3JlLCAnc3F1YXJlJyl9ICR7bWQuYm9sZChcbiAgICBTdHJpbmcoZGlzcGxheVZhbHVlID8/IHZhbHVlKSxcbiAgKX0gKHNjb3JlOiAke2Zvcm1hdFJlcG9ydFNjb3JlKHNjb3JlKX0pYDtcbn1cblxuZnVuY3Rpb24gaGFzQ2F0ZWdvcmllcyhcbiAgcmVwb3J0OiBTY29yZWRSZXBvcnQsXG4pOiByZXBvcnQgaXMgU2NvcmVkUmVwb3J0ICYgUmVxdWlyZWQ8UGljazxTY29yZWRSZXBvcnQsICdjYXRlZ29yaWVzJz4+IHtcbiAgcmV0dXJuICEhcmVwb3J0LmNhdGVnb3JpZXMgJiYgcmVwb3J0LmNhdGVnb3JpZXMubGVuZ3RoID4gMDtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGdlbmVyYXRlTWRSZXBvcnQoXG4gIHJlcG9ydDogU2NvcmVkUmVwb3J0LFxuICBvcHRpb25zPzogTWRSZXBvcnRPcHRpb25zLFxuKTogc3RyaW5nIHtcbiAgcmV0dXJuIG5ldyBNYXJrZG93bkRvY3VtZW50KClcbiAgICAuaGVhZGluZyhISUVSQVJDSFkubGV2ZWxfMSwgUkVQT1JUX0hFQURMSU5FX1RFWFQpXG4gICAgLiRjb25jYXQoXG4gICAgICAuLi4oaGFzQ2F0ZWdvcmllcyhyZXBvcnQpXG4gICAgICAgID8gW2NhdGVnb3JpZXNPdmVydmlld1NlY3Rpb24ocmVwb3J0KSwgY2F0ZWdvcmllc0RldGFpbHNTZWN0aW9uKHJlcG9ydCldXG4gICAgICAgIDogW10pLFxuICAgICAgYXVkaXRzU2VjdGlvbihyZXBvcnQsIG9wdGlvbnMpLFxuICAgICAgYWJvdXRTZWN0aW9uKHJlcG9ydCksXG4gICAgKVxuICAgIC5ydWxlKClcbiAgICAucGFyYWdyYXBoKG1kYCR7Rk9PVEVSX1BSRUZJWH0gJHttZC5saW5rKFJFQURNRV9MSU5LLCAnQ29kZSBQdXNoVXAnKX1gKVxuICAgIC50b1N0cmluZygpO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gYXVkaXREZXRhaWxzSXNzdWVzKFxuICBpc3N1ZXM6IElzc3VlW10gPSBbXSxcbiAgb3B0aW9ucz86IE1kUmVwb3J0T3B0aW9ucyxcbik6IE1hcmtkb3duRG9jdW1lbnQgfCBudWxsIHtcbiAgaWYgKGlzc3Vlcy5sZW5ndGggPT09IDApIHtcbiAgICByZXR1cm4gbnVsbDtcbiAgfVxuICByZXR1cm4gbmV3IE1hcmtkb3duRG9jdW1lbnQoKS5oZWFkaW5nKEhJRVJBUkNIWS5sZXZlbF80LCAnSXNzdWVzJykudGFibGUoXG4gICAgW1xuICAgICAgeyBoZWFkaW5nOiAnU2V2ZXJpdHknLCBhbGlnbm1lbnQ6ICdjZW50ZXInIH0sXG4gICAgICB7IGhlYWRpbmc6ICdNZXNzYWdlJywgYWxpZ25tZW50OiAnbGVmdCcgfSxcbiAgICAgIHsgaGVhZGluZzogJ1NvdXJjZSBmaWxlJywgYWxpZ25tZW50OiAnbGVmdCcgfSxcbiAgICAgIHsgaGVhZGluZzogJ0xpbmUocyknLCBhbGlnbm1lbnQ6ICdjZW50ZXInIH0sXG4gICAgXSxcbiAgICBpc3N1ZXMubWFwKCh7IHNldmVyaXR5OiBsZXZlbCwgbWVzc2FnZSwgc291cmNlIH06IElzc3VlKSA9PiB7XG4gICAgICBjb25zdCBzZXZlcml0eSA9IG1kYCR7c2V2ZXJpdHlNYXJrZXIobGV2ZWwpfSAke21kLml0YWxpYyhsZXZlbCl9YDtcblxuICAgICAgaWYgKCFzb3VyY2UpIHtcbiAgICAgICAgcmV0dXJuIFtzZXZlcml0eSwgbWVzc2FnZV07XG4gICAgICB9XG4gICAgICBjb25zdCBmaWxlID0gbGlua1RvTG9jYWxTb3VyY2VGb3JJZGUoc291cmNlLCBvcHRpb25zKTtcbiAgICAgIGlmICghc291cmNlLnBvc2l0aW9uKSB7XG4gICAgICAgIHJldHVybiBbc2V2ZXJpdHksIG1lc3NhZ2UsIGZpbGVdO1xuICAgICAgfVxuICAgICAgY29uc3QgbGluZSA9IGZvcm1hdFNvdXJjZUxpbmUoc291cmNlLnBvc2l0aW9uKTtcbiAgICAgIHJldHVybiBbc2V2ZXJpdHksIG1lc3NhZ2UsIGZpbGUsIGxpbmVdO1xuICAgIH0pLFxuICApO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gYXVkaXREZXRhaWxzKFxuICBhdWRpdDogQXVkaXRSZXBvcnQsXG4gIG9wdGlvbnM/OiBNZFJlcG9ydE9wdGlvbnMsXG4pOiBNYXJrZG93bkRvY3VtZW50IHtcbiAgY29uc3QgeyB0YWJsZSwgaXNzdWVzID0gW10gfSA9IGF1ZGl0LmRldGFpbHMgPz8ge307XG4gIGNvbnN0IGRldGFpbHNWYWx1ZSA9IGF1ZGl0RGV0YWlsc0F1ZGl0VmFsdWUoYXVkaXQpO1xuXG4gIC8vIHVuZGVmaW5lZCBkZXRhaWxzIE9SIGVtcHR5IGRldGFpbHMgKHVuZGVmaW5lZCBpc3N1ZXMgT1IgZW1wdHkgaXNzdWVzIEFORCBlbXB0eSB0YWJsZSlcbiAgaWYgKGlzc3Vlcy5sZW5ndGggPT09IDAgJiYgIXRhYmxlPy5yb3dzLmxlbmd0aCkge1xuICAgIHJldHVybiBuZXcgTWFya2Rvd25Eb2N1bWVudCgpLnBhcmFncmFwaChkZXRhaWxzVmFsdWUpO1xuICB9XG5cbiAgY29uc3QgdGFibGVTZWN0aW9uQ29udGVudCA9IHRhYmxlICYmIHRhYmxlU2VjdGlvbih0YWJsZSk7XG4gIGNvbnN0IGlzc3Vlc1NlY3Rpb25Db250ZW50ID1cbiAgICBpc3N1ZXMubGVuZ3RoID4gMCAmJiBhdWRpdERldGFpbHNJc3N1ZXMoaXNzdWVzLCBvcHRpb25zKTtcblxuICByZXR1cm4gbmV3IE1hcmtkb3duRG9jdW1lbnQoKS5kZXRhaWxzKFxuICAgIGRldGFpbHNWYWx1ZSxcbiAgICBuZXcgTWFya2Rvd25Eb2N1bWVudCgpLiRjb25jYXQodGFibGVTZWN0aW9uQ29udGVudCwgaXNzdWVzU2VjdGlvbkNvbnRlbnQpLFxuICApO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gYXVkaXRzU2VjdGlvbihcbiAgeyBwbHVnaW5zIH06IFBpY2s8U2NvcmVkUmVwb3J0LCAncGx1Z2lucyc+LFxuICBvcHRpb25zPzogTWRSZXBvcnRPcHRpb25zLFxuKTogTWFya2Rvd25Eb2N1bWVudCB7XG4gIHJldHVybiBuZXcgTWFya2Rvd25Eb2N1bWVudCgpXG4gICAgLmhlYWRpbmcoSElFUkFSQ0hZLmxldmVsXzIsICdcdUQ4M0RcdURFRTFcdUZFMEYgQXVkaXRzJylcbiAgICAuJGZvcmVhY2goXG4gICAgICBwbHVnaW5zLmZsYXRNYXAocGx1Z2luID0+XG4gICAgICAgIHBsdWdpbi5hdWRpdHMubWFwKGF1ZGl0ID0+ICh7IC4uLmF1ZGl0LCBwbHVnaW4gfSkpLFxuICAgICAgKSxcbiAgICAgIChkb2MsIHsgcGx1Z2luLCAuLi5hdWRpdCB9KSA9PiB7XG4gICAgICAgIGNvbnN0IGF1ZGl0VGl0bGUgPSBgJHthdWRpdC50aXRsZX0gKCR7cGx1Z2luLnRpdGxlfSlgO1xuICAgICAgICBjb25zdCBkZXRhaWxzQ29udGVudCA9IGF1ZGl0RGV0YWlscyhhdWRpdCwgb3B0aW9ucyk7XG4gICAgICAgIGNvbnN0IGRlc2NyaXB0aW9uQ29udGVudCA9IG1ldGFEZXNjcmlwdGlvbihhdWRpdCk7XG5cbiAgICAgICAgcmV0dXJuIGRvY1xuICAgICAgICAgIC5oZWFkaW5nKEhJRVJBUkNIWS5sZXZlbF8zLCBhdWRpdFRpdGxlKVxuICAgICAgICAgIC4kY29uY2F0KGRldGFpbHNDb250ZW50KVxuICAgICAgICAgIC5wYXJhZ3JhcGgoZGVzY3JpcHRpb25Db250ZW50KTtcbiAgICAgIH0sXG4gICAgKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGFib3V0U2VjdGlvbihcbiAgcmVwb3J0OiBPbWl0PFNjb3JlZFJlcG9ydCwgJ3BhY2thZ2VOYW1lJz4sXG4pOiBNYXJrZG93bkRvY3VtZW50IHtcbiAgY29uc3QgeyBkYXRlLCBwbHVnaW5zIH0gPSByZXBvcnQ7XG4gIHJldHVybiBuZXcgTWFya2Rvd25Eb2N1bWVudCgpXG4gICAgLmhlYWRpbmcoSElFUkFSQ0hZLmxldmVsXzIsICdBYm91dCcpXG4gICAgLnBhcmFncmFwaChcbiAgICAgIG1kYFJlcG9ydCB3YXMgY3JlYXRlZCBieSAke21kLmxpbmsoXG4gICAgICAgIFJFQURNRV9MSU5LLFxuICAgICAgICAnQ29kZSBQdXNoVXAnLFxuICAgICAgKX0gb24gJHtmb3JtYXREYXRlKG5ldyBEYXRlKGRhdGUpKX0uYCxcbiAgICApXG4gICAgLnRhYmxlKC4uLnBsdWdpbk1ldGFUYWJsZSh7IHBsdWdpbnMgfSkpXG4gICAgLnRhYmxlKC4uLnJlcG9ydE1ldGFUYWJsZShyZXBvcnQpKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHBsdWdpbk1ldGFUYWJsZSh7XG4gIHBsdWdpbnMsXG59OiBQaWNrPFJlcG9ydCwgJ3BsdWdpbnMnPik6IFBhcmFtZXRlcnM8TWFya2Rvd25Eb2N1bWVudFsndGFibGUnXT4ge1xuICByZXR1cm4gW1xuICAgIFtcbiAgICAgIHsgaGVhZGluZzogJ1BsdWdpbicsIGFsaWdubWVudDogJ2xlZnQnIH0sXG4gICAgICB7IGhlYWRpbmc6ICdBdWRpdHMnLCBhbGlnbm1lbnQ6ICdjZW50ZXInIH0sXG4gICAgICB7IGhlYWRpbmc6ICdWZXJzaW9uJywgYWxpZ25tZW50OiAnY2VudGVyJyB9LFxuICAgICAgeyBoZWFkaW5nOiAnRHVyYXRpb24nLCBhbGlnbm1lbnQ6ICdyaWdodCcgfSxcbiAgICBdLFxuICAgIHBsdWdpbnMubWFwKCh7IHRpdGxlLCBhdWRpdHMsIHZlcnNpb24gPSAnJywgZHVyYXRpb24gfSkgPT4gW1xuICAgICAgdGl0bGUsXG4gICAgICBhdWRpdHMubGVuZ3RoLnRvU3RyaW5nKCksXG4gICAgICB2ZXJzaW9uICYmIG1kLmNvZGUodmVyc2lvbiksXG4gICAgICBmb3JtYXREdXJhdGlvbihkdXJhdGlvbiksXG4gICAgXSksXG4gIF07XG59XG5cbmV4cG9ydCBmdW5jdGlvbiByZXBvcnRNZXRhVGFibGUoe1xuICBjb21taXQsXG4gIHZlcnNpb24sXG4gIGR1cmF0aW9uLFxuICBwbHVnaW5zLFxuICBjYXRlZ29yaWVzLFxufTogUGljazxcbiAgU2NvcmVkUmVwb3J0LFxuICAnZGF0ZScgfCAnZHVyYXRpb24nIHwgJ3ZlcnNpb24nIHwgJ2NvbW1pdCcgfCAncGx1Z2lucycgfCAnY2F0ZWdvcmllcydcbj4pOiBQYXJhbWV0ZXJzPE1hcmtkb3duRG9jdW1lbnRbJ3RhYmxlJ10+IHtcbiAgcmV0dXJuIFtcbiAgICBbXG4gICAgICB7IGhlYWRpbmc6ICdDb21taXQnLCBhbGlnbm1lbnQ6ICdsZWZ0JyB9LFxuICAgICAgeyBoZWFkaW5nOiAnVmVyc2lvbicsIGFsaWdubWVudDogJ2NlbnRlcicgfSxcbiAgICAgIHsgaGVhZGluZzogJ0R1cmF0aW9uJywgYWxpZ25tZW50OiAncmlnaHQnIH0sXG4gICAgICB7IGhlYWRpbmc6ICdQbHVnaW5zJywgYWxpZ25tZW50OiAnY2VudGVyJyB9LFxuICAgICAgeyBoZWFkaW5nOiAnQ2F0ZWdvcmllcycsIGFsaWdubWVudDogJ2NlbnRlcicgfSxcbiAgICAgIHsgaGVhZGluZzogJ0F1ZGl0cycsIGFsaWdubWVudDogJ2NlbnRlcicgfSxcbiAgICBdLFxuICAgIFtcbiAgICAgIFtcbiAgICAgICAgY29tbWl0ID8gYCR7Y29tbWl0Lm1lc3NhZ2V9ICgke2NvbW1pdC5oYXNofSlgIDogJ04vQScsXG4gICAgICAgIG1kLmNvZGUodmVyc2lvbiksXG4gICAgICAgIGZvcm1hdER1cmF0aW9uKGR1cmF0aW9uKSxcbiAgICAgICAgcGx1Z2lucy5sZW5ndGgudG9TdHJpbmcoKSxcbiAgICAgICAgKGNhdGVnb3JpZXM/Lmxlbmd0aCA/PyAwKS50b1N0cmluZygpLFxuICAgICAgICBwbHVnaW5zLnJlZHVjZSgoYWNjLCB7IGF1ZGl0cyB9KSA9PiBhY2MgKyBhdWRpdHMubGVuZ3RoLCAwKS50b1N0cmluZygpLFxuICAgICAgXSxcbiAgICBdLFxuICBdO1xufVxuIiwgImNvbnN0IF9faW5qZWN0ZWRfZmlsZW5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvdXRpbHMvc3JjL2xpYi9yZXBvcnRzL2Zvcm1hdHRpbmcudHNcIjtjb25zdCBfX2luamVjdGVkX2Rpcm5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvdXRpbHMvc3JjL2xpYi9yZXBvcnRzXCI7Y29uc3QgX19pbmplY3RlZF9pbXBvcnRfbWV0YV91cmxfXyA9IFwiZmlsZTovLy9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3V0aWxzL3NyYy9saWIvcmVwb3J0cy9mb3JtYXR0aW5nLnRzXCI7aW1wb3J0IHtcbiAgdHlwZSBIZWFkaW5nTGV2ZWwsXG4gIHR5cGUgSW5saW5lVGV4dCxcbiAgTWFya2Rvd25Eb2N1bWVudCxcbiAgbWQsXG59IGZyb20gJ2J1aWxkLW1kJztcbmltcG9ydCBwYXRoIGZyb20gJ25vZGU6cGF0aCc7XG5pbXBvcnQgdHlwZSB7XG4gIEF1ZGl0UmVwb3J0LFxuICBTb3VyY2VGaWxlTG9jYXRpb24sXG4gIFRhYmxlLFxufSBmcm9tICdAY29kZS1wdXNodXAvbW9kZWxzJztcbmltcG9ydCB7IEhJRVJBUkNIWSB9IGZyb20gJy4uL3RleHQtZm9ybWF0cy9pbmRleC5qcyc7XG5pbXBvcnQge1xuICBjb2x1bW5zVG9TdHJpbmdBcnJheSxcbiAgZ2V0Q29sdW1uQWxpZ25tZW50cyxcbiAgcm93VG9TdHJpbmdBcnJheSxcbn0gZnJvbSAnLi4vdGV4dC1mb3JtYXRzL3RhYmxlLmpzJztcbmltcG9ydCB7XG4gIGdldEVudmlyb25tZW50VHlwZSxcbiAgZ2V0R2l0SHViQmFzZVVybCxcbiAgZ2V0R2l0TGFiQmFzZVVybCxcbn0gZnJvbSAnLi9lbnZpcm9ubWVudC10eXBlLmpzJztcbmltcG9ydCB0eXBlIHsgTWRSZXBvcnRPcHRpb25zIH0gZnJvbSAnLi90eXBlcy5qcyc7XG5cbmV4cG9ydCBmdW5jdGlvbiB0YWJsZVNlY3Rpb24oXG4gIHRhYmxlRGF0YTogVGFibGUsXG4gIG9wdGlvbnM/OiB7XG4gICAgbGV2ZWw/OiBIZWFkaW5nTGV2ZWw7XG4gIH0sXG4pOiBNYXJrZG93bkRvY3VtZW50IHwgbnVsbCB7XG4gIGlmICh0YWJsZURhdGEucm93cy5sZW5ndGggPT09IDApIHtcbiAgICByZXR1cm4gbnVsbDtcbiAgfVxuICBjb25zdCB7IGxldmVsID0gSElFUkFSQ0hZLmxldmVsXzQgfSA9IG9wdGlvbnMgPz8ge307XG4gIGNvbnN0IGNvbHVtbnMgPSBjb2x1bW5zVG9TdHJpbmdBcnJheSh0YWJsZURhdGEpO1xuICBjb25zdCBhbGlnbm1lbnRzID0gZ2V0Q29sdW1uQWxpZ25tZW50cyh0YWJsZURhdGEpO1xuICBjb25zdCByb3dzID0gcm93VG9TdHJpbmdBcnJheSh0YWJsZURhdGEpO1xuICByZXR1cm4gbmV3IE1hcmtkb3duRG9jdW1lbnQoKS5oZWFkaW5nKGxldmVsLCB0YWJsZURhdGEudGl0bGUpLnRhYmxlKFxuICAgIGNvbHVtbnMubWFwKChoZWFkaW5nLCBpKSA9PiB7XG4gICAgICBjb25zdCBhbGlnbm1lbnQgPSBhbGlnbm1lbnRzW2ldO1xuICAgICAgaWYgKGFsaWdubWVudCkge1xuICAgICAgICByZXR1cm4geyBoZWFkaW5nLCBhbGlnbm1lbnQgfTtcbiAgICAgIH1cbiAgICAgIHJldHVybiBoZWFkaW5nO1xuICAgIH0pLFxuICAgIHJvd3MsXG4gICk7XG59XG5cbi8vIEBUT0RPIGV4dHJhY3QgYFBpY2s8QXVkaXRSZXBvcnQsICdkb2NzVXJsJyB8ICdkZXNjcmlwdGlvbic+YCB0byBhIHJldXNhYmxlIHNjaGVtYSBhbmQgdHlwZVxuZXhwb3J0IGZ1bmN0aW9uIG1ldGFEZXNjcmlwdGlvbihcbiAgYXVkaXQ6IFBpY2s8QXVkaXRSZXBvcnQsICdkb2NzVXJsJyB8ICdkZXNjcmlwdGlvbic+LFxuKTogSW5saW5lVGV4dCB7XG4gIGNvbnN0IGRvY3NVcmwgPSBhdWRpdC5kb2NzVXJsO1xuICBjb25zdCBkZXNjcmlwdGlvbiA9IGF1ZGl0LmRlc2NyaXB0aW9uPy50cmltKCk7XG4gIGlmIChkb2NzVXJsKSB7XG4gICAgY29uc3QgZG9jc0xpbmsgPSBtZC5saW5rKGRvY3NVcmwsICdcdUQ4M0RcdURDRDYgRG9jcycpO1xuICAgIGlmICghZGVzY3JpcHRpb24pIHtcbiAgICAgIHJldHVybiBkb2NzTGluaztcbiAgICB9XG4gICAgY29uc3QgcGFyc2VkRGVzY3JpcHRpb24gPSBkZXNjcmlwdGlvbi5lbmRzV2l0aCgnYGBgJylcbiAgICAgID8gYCR7ZGVzY3JpcHRpb259XFxuXFxuYFxuICAgICAgOiBgJHtkZXNjcmlwdGlvbn0gYDtcbiAgICByZXR1cm4gbWRgJHtwYXJzZWREZXNjcmlwdGlvbn0ke2RvY3NMaW5rfWA7XG4gIH1cbiAgaWYgKGRlc2NyaXB0aW9uICYmIGRlc2NyaXB0aW9uLnRyaW0oKS5sZW5ndGggPiAwKSB7XG4gICAgcmV0dXJuIGRlc2NyaXB0aW9uO1xuICB9XG4gIHJldHVybiAnJztcbn1cblxuLyoqXG4gKiBMaW5rIHRvIGxvY2FsIHNvdXJjZSBmb3IgSURFXG4gKiBAcGFyYW0gc291cmNlXG4gKiBAcGFyYW0gcmVwb3J0TG9jYXRpb25cbiAqXG4gKiBAZXhhbXBsZVxuICogbGlua1RvTG9jYWxTb3VyY2VJbklkZSh7IGZpbGU6ICdzcmMvaW5kZXgudHMnIH0sIHsgb3V0cHV0RGlyOiAnLmNvZGUtcHVzaHVwJyB9KSAvLyBbYHNyYy9pbmRleC50c2BdKC4uL3NyYy9pbmRleC50cylcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGxpbmtUb0xvY2FsU291cmNlRm9ySWRlKFxuICBzb3VyY2U6IFNvdXJjZUZpbGVMb2NhdGlvbixcbiAgb3B0aW9ucz86IFBpY2s8TWRSZXBvcnRPcHRpb25zLCAnb3V0cHV0RGlyJz4sXG4pOiBJbmxpbmVUZXh0IHtcbiAgY29uc3QgeyBmaWxlLCBwb3NpdGlvbiB9ID0gc291cmNlO1xuICBjb25zdCB7IG91dHB1dERpciB9ID0gb3B0aW9ucyA/PyB7fTtcblxuICAvLyBOT1QgbGlua2FibGVcbiAgaWYgKCFvdXRwdXREaXIpIHtcbiAgICByZXR1cm4gbWQuY29kZShmaWxlKTtcbiAgfVxuXG4gIHJldHVybiBtZC5saW5rKGZvcm1hdEZpbGVMaW5rKGZpbGUsIHBvc2l0aW9uLCBvdXRwdXREaXIpLCBtZC5jb2RlKGZpbGUpKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGZvcm1hdFNvdXJjZUxpbmUoXG4gIHBvc2l0aW9uOiBTb3VyY2VGaWxlTG9jYXRpb25bJ3Bvc2l0aW9uJ10sXG4pOiBzdHJpbmcge1xuICBpZiAoIXBvc2l0aW9uKSB7XG4gICAgcmV0dXJuICcnO1xuICB9XG4gIGNvbnN0IHsgc3RhcnRMaW5lLCBlbmRMaW5lIH0gPSBwb3NpdGlvbjtcbiAgcmV0dXJuIGVuZExpbmUgJiYgc3RhcnRMaW5lICE9PSBlbmRMaW5lXG4gICAgPyBgJHtzdGFydExpbmV9LSR7ZW5kTGluZX1gXG4gICAgOiBgJHtzdGFydExpbmV9YDtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGZvcm1hdEdpdEh1YkxpbmsoXG4gIGZpbGU6IHN0cmluZyxcbiAgcG9zaXRpb246IFNvdXJjZUZpbGVMb2NhdGlvblsncG9zaXRpb24nXSxcbik6IHN0cmluZyB7XG4gIGNvbnN0IGJhc2VVcmwgPSBnZXRHaXRIdWJCYXNlVXJsKCk7XG4gIGlmICghcG9zaXRpb24pIHtcbiAgICByZXR1cm4gYCR7YmFzZVVybH0vJHtmaWxlfWA7XG4gIH1cbiAgY29uc3QgeyBzdGFydExpbmUsIGVuZExpbmUsIHN0YXJ0Q29sdW1uLCBlbmRDb2x1bW4gfSA9IHBvc2l0aW9uO1xuICBjb25zdCBzdGFydCA9IHN0YXJ0Q29sdW1uID8gYEwke3N0YXJ0TGluZX1DJHtzdGFydENvbHVtbn1gIDogYEwke3N0YXJ0TGluZX1gO1xuICBjb25zdCBlbmQgPSBlbmRMaW5lXG4gICAgPyBlbmRDb2x1bW5cbiAgICAgID8gYEwke2VuZExpbmV9QyR7ZW5kQ29sdW1ufWBcbiAgICAgIDogYEwke2VuZExpbmV9YFxuICAgIDogJyc7XG4gIGNvbnN0IGxpbmVSYW5nZSA9IGVuZCAmJiBzdGFydCAhPT0gZW5kID8gYCR7c3RhcnR9LSR7ZW5kfWAgOiBzdGFydDtcbiAgcmV0dXJuIGAke2Jhc2VVcmx9LyR7ZmlsZX0jJHtsaW5lUmFuZ2V9YDtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGZvcm1hdEdpdExhYkxpbmsoXG4gIGZpbGU6IHN0cmluZyxcbiAgcG9zaXRpb246IFNvdXJjZUZpbGVMb2NhdGlvblsncG9zaXRpb24nXSxcbik6IHN0cmluZyB7XG4gIGNvbnN0IGJhc2VVcmwgPSBnZXRHaXRMYWJCYXNlVXJsKCk7XG4gIGlmICghcG9zaXRpb24pIHtcbiAgICByZXR1cm4gYCR7YmFzZVVybH0vJHtmaWxlfWA7XG4gIH1cbiAgY29uc3QgeyBzdGFydExpbmUsIGVuZExpbmUgfSA9IHBvc2l0aW9uO1xuICBjb25zdCBsaW5lUmFuZ2UgPVxuICAgIGVuZExpbmUgJiYgc3RhcnRMaW5lICE9PSBlbmRMaW5lID8gYCR7c3RhcnRMaW5lfS0ke2VuZExpbmV9YCA6IHN0YXJ0TGluZTtcbiAgcmV0dXJuIGAke2Jhc2VVcmx9LyR7ZmlsZX0jTCR7bGluZVJhbmdlfWA7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBmb3JtYXRGaWxlTGluayhcbiAgZmlsZTogc3RyaW5nLFxuICBwb3NpdGlvbjogU291cmNlRmlsZUxvY2F0aW9uWydwb3NpdGlvbiddLFxuICBvdXRwdXREaXI6IHN0cmluZyxcbik6IHN0cmluZyB7XG4gIGNvbnN0IHJlbGF0aXZlUGF0aCA9IHBhdGgucG9zaXgucmVsYXRpdmUob3V0cHV0RGlyLCBmaWxlKTtcbiAgY29uc3QgZW52ID0gZ2V0RW52aXJvbm1lbnRUeXBlKCk7XG5cbiAgc3dpdGNoIChlbnYpIHtcbiAgICBjYXNlICd2c2NvZGUnOlxuICAgICAgcmV0dXJuIHBvc2l0aW9uID8gYCR7cmVsYXRpdmVQYXRofSNMJHtwb3NpdGlvbi5zdGFydExpbmV9YCA6IHJlbGF0aXZlUGF0aDtcbiAgICBjYXNlICdnaXRodWInOlxuICAgICAgcmV0dXJuIGZvcm1hdEdpdEh1YkxpbmsoZmlsZSwgcG9zaXRpb24pO1xuICAgIGNhc2UgJ2dpdGxhYic6XG4gICAgICByZXR1cm4gZm9ybWF0R2l0TGFiTGluayhmaWxlLCBwb3NpdGlvbik7XG4gICAgZGVmYXVsdDpcbiAgICAgIHJldHVybiByZWxhdGl2ZVBhdGg7XG4gIH1cbn1cbiIsICJjb25zdCBfX2luamVjdGVkX2ZpbGVuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3V0aWxzL3NyYy9saWIvcmVwb3J0cy9nZW5lcmF0ZS1tZC1yZXBvcnQtY2F0ZWdveS1zZWN0aW9uLnRzXCI7Y29uc3QgX19pbmplY3RlZF9kaXJuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3V0aWxzL3NyYy9saWIvcmVwb3J0c1wiO2NvbnN0IF9faW5qZWN0ZWRfaW1wb3J0X21ldGFfdXJsX18gPSBcImZpbGU6Ly8vaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy91dGlscy9zcmMvbGliL3JlcG9ydHMvZ2VuZXJhdGUtbWQtcmVwb3J0LWNhdGVnb3ktc2VjdGlvbi50c1wiO2ltcG9ydCB7IHR5cGUgSW5saW5lVGV4dCwgTWFya2Rvd25Eb2N1bWVudCwgbWQgfSBmcm9tICdidWlsZC1tZCc7XG5pbXBvcnQgdHlwZSB7IEF1ZGl0UmVwb3J0IH0gZnJvbSAnQGNvZGUtcHVzaHVwL21vZGVscyc7XG5pbXBvcnQgeyBzbHVnaWZ5IH0gZnJvbSAnLi4vZm9ybWF0dGluZy5qcyc7XG5pbXBvcnQgeyBISUVSQVJDSFkgfSBmcm9tICcuLi90ZXh0LWZvcm1hdHMvaW5kZXguanMnO1xuaW1wb3J0IHsgbWV0YURlc2NyaXB0aW9uIH0gZnJvbSAnLi9mb3JtYXR0aW5nLmpzJztcbmltcG9ydCB7IGdldFNvcnRhYmxlQXVkaXRCeVJlZiwgZ2V0U29ydGFibGVHcm91cEJ5UmVmIH0gZnJvbSAnLi9zb3J0aW5nLmpzJztcbmltcG9ydCB0eXBlIHsgU2NvcmVkR3JvdXAsIFNjb3JlZFJlcG9ydCB9IGZyb20gJy4vdHlwZXMuanMnO1xuaW1wb3J0IHtcbiAgY291bnRDYXRlZ29yeUF1ZGl0cyxcbiAgZm9ybWF0UmVwb3J0U2NvcmUsXG4gIGdldFBsdWdpbk5hbWVGcm9tU2x1ZyxcbiAgc2NvcmVNYXJrZXIsXG4gIHRhcmdldFNjb3JlSWNvbixcbn0gZnJvbSAnLi91dGlscy5qcyc7XG5cbmV4cG9ydCBmdW5jdGlvbiBjYXRlZ29yaWVzT3ZlcnZpZXdTZWN0aW9uKFxuICByZXBvcnQ6IFJlcXVpcmVkPFBpY2s8U2NvcmVkUmVwb3J0LCAncGx1Z2lucycgfCAnY2F0ZWdvcmllcyc+Pixcbik6IE1hcmtkb3duRG9jdW1lbnQge1xuICBjb25zdCB7IGNhdGVnb3JpZXMsIHBsdWdpbnMgfSA9IHJlcG9ydDtcbiAgcmV0dXJuIG5ldyBNYXJrZG93bkRvY3VtZW50KCkudGFibGUoXG4gICAgW1xuICAgICAgeyBoZWFkaW5nOiAnXHVEODNDXHVERkY3IENhdGVnb3J5JywgYWxpZ25tZW50OiAnbGVmdCcgfSxcbiAgICAgIHsgaGVhZGluZzogJ1x1MkI1MCBTY29yZScsIGFsaWdubWVudDogJ2NlbnRlcicgfSxcbiAgICAgIHsgaGVhZGluZzogJ1x1RDgzRFx1REVFMSBBdWRpdHMnLCBhbGlnbm1lbnQ6ICdjZW50ZXInIH0sXG4gICAgXSxcbiAgICBjYXRlZ29yaWVzLm1hcCgoeyB0aXRsZSwgcmVmcywgc2NvcmUsIGlzQmluYXJ5IH0pID0+IFtcbiAgICAgIC8vIEBUT0RPIHJlZmFjdG9yIGBpc0JpbmFyeTogYm9vbGVhbmAgdG8gYHRhcmdldFNjb3JlOiBudW1iZXJgICM3MTNcbiAgICAgIC8vIFRoZSBoZWFkaW5nIFwiSURcIiBpcyBpbmZlcnJlZCBmcm9tIHRoZSBoZWFkaW5nIHRleHQgaW4gTWFya2Rvd24uXG4gICAgICBtZC5saW5rKGAjJHtzbHVnaWZ5KHRpdGxlKX1gLCB0aXRsZSksXG4gICAgICBtZGAke3Njb3JlTWFya2VyKHNjb3JlKX0gJHttZC5ib2xkKFxuICAgICAgICBmb3JtYXRSZXBvcnRTY29yZShzY29yZSksXG4gICAgICApfSR7YmluYXJ5SWNvblN1ZmZpeChzY29yZSwgaXNCaW5hcnkpfWAsXG4gICAgICBjb3VudENhdGVnb3J5QXVkaXRzKHJlZnMsIHBsdWdpbnMpLnRvU3RyaW5nKCksXG4gICAgXSksXG4gICk7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBjYXRlZ29yaWVzRGV0YWlsc1NlY3Rpb24oXG4gIHJlcG9ydDogUmVxdWlyZWQ8UGljazxTY29yZWRSZXBvcnQsICdwbHVnaW5zJyB8ICdjYXRlZ29yaWVzJz4+LFxuKTogTWFya2Rvd25Eb2N1bWVudCB7XG4gIGNvbnN0IHsgY2F0ZWdvcmllcywgcGx1Z2lucyB9ID0gcmVwb3J0O1xuXG4gIHJldHVybiBuZXcgTWFya2Rvd25Eb2N1bWVudCgpXG4gICAgLmhlYWRpbmcoSElFUkFSQ0hZLmxldmVsXzIsICdcdUQ4M0NcdURGRjcgQ2F0ZWdvcmllcycpXG4gICAgLiRmb3JlYWNoKGNhdGVnb3JpZXMsIChkb2MsIGNhdGVnb3J5KSA9PlxuICAgICAgZG9jXG4gICAgICAgIC5oZWFkaW5nKEhJRVJBUkNIWS5sZXZlbF8zLCBjYXRlZ29yeS50aXRsZSlcbiAgICAgICAgLnBhcmFncmFwaChtZXRhRGVzY3JpcHRpb24oY2F0ZWdvcnkpKVxuICAgICAgICAucGFyYWdyYXBoKFxuICAgICAgICAgIG1kYCR7c2NvcmVNYXJrZXIoY2F0ZWdvcnkuc2NvcmUpfSBTY29yZTogJHttZC5ib2xkKFxuICAgICAgICAgICAgZm9ybWF0UmVwb3J0U2NvcmUoY2F0ZWdvcnkuc2NvcmUpLFxuICAgICAgICAgICl9JHtiaW5hcnlJY29uU3VmZml4KGNhdGVnb3J5LnNjb3JlLCBjYXRlZ29yeS5pc0JpbmFyeSl9YCxcbiAgICAgICAgKVxuICAgICAgICAubGlzdChcbiAgICAgICAgICBjYXRlZ29yeS5yZWZzLm1hcChyZWYgPT4ge1xuICAgICAgICAgICAgLy8gQWRkIGdyb3VwIGRldGFpbHNcbiAgICAgICAgICAgIGlmIChyZWYudHlwZSA9PT0gJ2dyb3VwJykge1xuICAgICAgICAgICAgICBjb25zdCBncm91cCA9IGdldFNvcnRhYmxlR3JvdXBCeVJlZihyZWYsIHBsdWdpbnMpO1xuICAgICAgICAgICAgICBjb25zdCBncm91cEF1ZGl0cyA9IGdyb3VwLnJlZnMubWFwKGdyb3VwUmVmID0+XG4gICAgICAgICAgICAgICAgZ2V0U29ydGFibGVBdWRpdEJ5UmVmKFxuICAgICAgICAgICAgICAgICAgeyAuLi5ncm91cFJlZiwgcGx1Z2luOiBncm91cC5wbHVnaW4sIHR5cGU6ICdhdWRpdCcgfSxcbiAgICAgICAgICAgICAgICAgIHBsdWdpbnMsXG4gICAgICAgICAgICAgICAgKSxcbiAgICAgICAgICAgICAgKTtcbiAgICAgICAgICAgICAgY29uc3QgcGx1Z2luVGl0bGUgPSBnZXRQbHVnaW5OYW1lRnJvbVNsdWcocmVmLnBsdWdpbiwgcGx1Z2lucyk7XG4gICAgICAgICAgICAgIHJldHVybiBjYXRlZ29yeUdyb3VwSXRlbShncm91cCwgZ3JvdXBBdWRpdHMsIHBsdWdpblRpdGxlKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIC8vIEFkZCBhdWRpdCBkZXRhaWxzXG4gICAgICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgICAgY29uc3QgYXVkaXQgPSBnZXRTb3J0YWJsZUF1ZGl0QnlSZWYocmVmLCBwbHVnaW5zKTtcbiAgICAgICAgICAgICAgY29uc3QgcGx1Z2luVGl0bGUgPSBnZXRQbHVnaW5OYW1lRnJvbVNsdWcocmVmLnBsdWdpbiwgcGx1Z2lucyk7XG4gICAgICAgICAgICAgIHJldHVybiBjYXRlZ29yeVJlZihhdWRpdCwgcGx1Z2luVGl0bGUpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgIH0pLFxuICAgICAgICApLFxuICAgICk7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBjYXRlZ29yeVJlZihcbiAgeyB0aXRsZSwgc2NvcmUsIHZhbHVlLCBkaXNwbGF5VmFsdWUgfTogQXVkaXRSZXBvcnQsXG4gIHBsdWdpblRpdGxlOiBzdHJpbmcsXG4pOiBJbmxpbmVUZXh0IHtcbiAgY29uc3QgYXVkaXRUaXRsZUFzTGluayA9IG1kLmxpbmsoXG4gICAgYCMke3NsdWdpZnkodGl0bGUpfS0ke3NsdWdpZnkocGx1Z2luVGl0bGUpfWAsXG4gICAgdGl0bGUsXG4gICk7XG4gIGNvbnN0IG1hcmtlciA9IHNjb3JlTWFya2VyKHNjb3JlLCAnc3F1YXJlJyk7XG4gIHJldHVybiBtZGAke21hcmtlcn0gJHthdWRpdFRpdGxlQXNMaW5rfSAoJHttZC5pdGFsaWMoXG4gICAgcGx1Z2luVGl0bGUsXG4gICl9KSAtICR7bWQuYm9sZCgoZGlzcGxheVZhbHVlIHx8IHZhbHVlKS50b1N0cmluZygpKX1gO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gY2F0ZWdvcnlHcm91cEl0ZW0oXG4gIHsgc2NvcmUgPSAwLCB0aXRsZSB9OiBTY29yZWRHcm91cCxcbiAgZ3JvdXBBdWRpdHM6IEF1ZGl0UmVwb3J0W10sXG4gIHBsdWdpblRpdGxlOiBzdHJpbmcsXG4pOiBJbmxpbmVUZXh0IHtcbiAgY29uc3QgZ3JvdXBUaXRsZSA9IG1kYCR7c2NvcmVNYXJrZXIoc2NvcmUpfSAke3RpdGxlfSAoJHttZC5pdGFsaWMoXG4gICAgcGx1Z2luVGl0bGUsXG4gICl9KWA7XG5cbiAgY29uc3QgYXVkaXRzTGlzdCA9IG1kLmxpc3QoXG4gICAgZ3JvdXBBdWRpdHMubWFwKFxuICAgICAgKHsgdGl0bGU6IGF1ZGl0VGl0bGUsIHNjb3JlOiBhdWRpdFNjb3JlLCB2YWx1ZSwgZGlzcGxheVZhbHVlIH0pID0+IHtcbiAgICAgICAgY29uc3QgYXVkaXRUaXRsZUxpbmsgPSBtZC5saW5rKFxuICAgICAgICAgIGAjJHtzbHVnaWZ5KGF1ZGl0VGl0bGUpfS0ke3NsdWdpZnkocGx1Z2luVGl0bGUpfWAsXG4gICAgICAgICAgYXVkaXRUaXRsZSxcbiAgICAgICAgKTtcbiAgICAgICAgY29uc3QgbWFya2VyID0gc2NvcmVNYXJrZXIoYXVkaXRTY29yZSwgJ3NxdWFyZScpO1xuICAgICAgICByZXR1cm4gbWRgJHttYXJrZXJ9ICR7YXVkaXRUaXRsZUxpbmt9IC0gJHttZC5ib2xkKFxuICAgICAgICAgIFN0cmluZyhkaXNwbGF5VmFsdWUgPz8gdmFsdWUpLFxuICAgICAgICApfWA7XG4gICAgICB9LFxuICAgICksXG4gICk7XG5cbiAgcmV0dXJuIG1kYCR7Z3JvdXBUaXRsZX0ke2F1ZGl0c0xpc3R9YDtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGJpbmFyeUljb25TdWZmaXgoXG4gIHNjb3JlOiBudW1iZXIsXG4gIGlzQmluYXJ5OiBib29sZWFuIHwgdW5kZWZpbmVkLFxuKTogc3RyaW5nIHtcbiAgLy8gQFRPRE8gcmVmYWN0b3IgYGlzQmluYXJ5OiBib29sZWFuYCB0byBgdGFyZ2V0U2NvcmU6IG51bWJlcmAgIzcxM1xuICByZXR1cm4gdGFyZ2V0U2NvcmVJY29uKHNjb3JlLCBpc0JpbmFyeSA/IDEgOiB1bmRlZmluZWQsIHsgcHJlZml4OiAnICcgfSk7XG59XG4iLCAiY29uc3QgX19pbmplY3RlZF9maWxlbmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy91dGlscy9zcmMvbGliL3JlcG9ydHMvZ2VuZXJhdGUtbWQtcmVwb3J0cy1kaWZmLnRzXCI7Y29uc3QgX19pbmplY3RlZF9kaXJuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3V0aWxzL3NyYy9saWIvcmVwb3J0c1wiO2NvbnN0IF9faW5qZWN0ZWRfaW1wb3J0X21ldGFfdXJsX18gPSBcImZpbGU6Ly8vaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy91dGlscy9zcmMvbGliL3JlcG9ydHMvZ2VuZXJhdGUtbWQtcmVwb3J0cy1kaWZmLnRzXCI7aW1wb3J0IHtcbiAgdHlwZSBIZWFkaW5nTGV2ZWwsXG4gIE1hcmtkb3duRG9jdW1lbnQsXG4gIHR5cGUgVGFibGVDb2x1bW5PYmplY3QsXG4gIHR5cGUgVGFibGVSb3csXG4gIG1kLFxufSBmcm9tICdidWlsZC1tZCc7XG5pbXBvcnQgdHlwZSB7IFJlcG9ydHNEaWZmIH0gZnJvbSAnQGNvZGUtcHVzaHVwL21vZGVscyc7XG5pbXBvcnQgeyBISUVSQVJDSFkgfSBmcm9tICcuLi90ZXh0LWZvcm1hdHMvaW5kZXguanMnO1xuaW1wb3J0IHsgdG9BcnJheSB9IGZyb20gJy4uL3RyYW5zZm9ybS5qcyc7XG5pbXBvcnQgdHlwZSB7IFdpdGhSZXF1aXJlZCB9IGZyb20gJy4uL3R5cGVzLmpzJztcbmltcG9ydCB7XG4gIGNoYW5nZXNUb0RpZmZPdXRjb21lcyxcbiAgY29tcGFyZURpZmZzQnksXG4gIGNyZWF0ZUdyb3Vwc09yQXVkaXRzRGV0YWlscyxcbiAgZm9ybWF0UG9ydGFsTGluayxcbiAgZm9ybWF0UmVwb3J0T3V0Y29tZSxcbiAgZm9ybWF0VGl0bGUsXG4gIGdldERpZmZDaGFuZ2VzLFxuICBtZXJnZURpZmZPdXRjb21lcyxcbiAgc29ydENoYW5nZXMsXG4gIHN1bW1hcml6ZURpZmZPdXRjb21lcyxcbiAgc3VtbWFyaXplVW5jaGFuZ2VkLFxufSBmcm9tICcuL2dlbmVyYXRlLW1kLXJlcG9ydHMtZGlmZi11dGlscy5qcyc7XG5pbXBvcnQgdHlwZSB7IERpZmZPdXRjb21lIH0gZnJvbSAnLi90eXBlcy5qcyc7XG5pbXBvcnQge1xuICBmb3JtYXRTY29yZUNoYW5nZSxcbiAgZm9ybWF0U2NvcmVXaXRoQ29sb3IsXG4gIGZvcm1hdFZhbHVlQ2hhbmdlLFxuICBzY29yZU1hcmtlcixcbn0gZnJvbSAnLi91dGlscy5qcyc7XG5cbmV4cG9ydCBmdW5jdGlvbiBnZW5lcmF0ZU1kUmVwb3J0c0RpZmYoZGlmZjogUmVwb3J0c0RpZmYpOiBzdHJpbmcge1xuICByZXR1cm4gbmV3IE1hcmtkb3duRG9jdW1lbnQoKVxuICAgIC4kY29uY2F0KFxuICAgICAgY3JlYXRlRGlmZkhlYWRlclNlY3Rpb24oZGlmZiksXG4gICAgICBjcmVhdGVEaWZmQ2F0ZWdvcmllc1NlY3Rpb24oZGlmZiksXG4gICAgICBjcmVhdGVEaWZmRGV0YWlsc1NlY3Rpb24oZGlmZiksXG4gICAgKVxuICAgIC50b1N0cmluZygpO1xufVxuXG5leHBvcnQgdHlwZSBMYWJlbGVkRGlmZiA9IFdpdGhSZXF1aXJlZDxSZXBvcnRzRGlmZiwgJ2xhYmVsJz47XG5cbmV4cG9ydCBmdW5jdGlvbiBnZW5lcmF0ZU1kUmVwb3J0c0RpZmZGb3JNb25vcmVwbyhkaWZmczogTGFiZWxlZERpZmZbXSk6IHN0cmluZyB7XG4gIGNvbnN0IGRpZmZzV2l0aE91dGNvbWVzID0gZGlmZnNcbiAgICAubWFwKGRpZmYgPT4gKHtcbiAgICAgIC4uLmRpZmYsXG4gICAgICBvdXRjb21lOiBtZXJnZURpZmZPdXRjb21lcyhjaGFuZ2VzVG9EaWZmT3V0Y29tZXMoZ2V0RGlmZkNoYW5nZXMoZGlmZikpKSxcbiAgICB9KSlcbiAgICAuc29ydChcbiAgICAgIChhLCBiKSA9PlxuICAgICAgICBjb21wYXJlRGlmZnNCeSgnY2F0ZWdvcmllcycsIGEsIGIpIHx8XG4gICAgICAgIGNvbXBhcmVEaWZmc0J5KCdncm91cHMnLCBhLCBiKSB8fFxuICAgICAgICBjb21wYXJlRGlmZnNCeSgnYXVkaXRzJywgYSwgYikgfHxcbiAgICAgICAgYS5sYWJlbC5sb2NhbGVDb21wYXJlKGIubGFiZWwpLFxuICAgICk7XG4gIGNvbnN0IHVuY2hhbmdlZCA9IGRpZmZzV2l0aE91dGNvbWVzLmZpbHRlcihcbiAgICAoeyBvdXRjb21lIH0pID0+IG91dGNvbWUgPT09ICd1bmNoYW5nZWQnLFxuICApO1xuICBjb25zdCBjaGFuZ2VkID0gZGlmZnNXaXRoT3V0Y29tZXMuZmlsdGVyKGRpZmYgPT4gIXVuY2hhbmdlZC5pbmNsdWRlcyhkaWZmKSk7XG5cbiAgcmV0dXJuIG5ldyBNYXJrZG93bkRvY3VtZW50KClcbiAgICAuJGNvbmNhdChcbiAgICAgIGNyZWF0ZURpZmZIZWFkZXJTZWN0aW9uKGRpZmZzKSxcbiAgICAgIC4uLmNoYW5nZWQubWFwKGNyZWF0ZURpZmZQcm9qZWN0U2VjdGlvbiksXG4gICAgKVxuICAgIC4kaWYodW5jaGFuZ2VkLmxlbmd0aCA+IDAsIGRvYyA9PlxuICAgICAgZG9jXG4gICAgICAgIC5ydWxlKClcbiAgICAgICAgLnBhcmFncmFwaChzdW1tYXJpemVVbmNoYW5nZWQoJ3Byb2plY3QnLCB7IHVuY2hhbmdlZCwgY2hhbmdlZCB9KSksXG4gICAgKVxuICAgIC50b1N0cmluZygpO1xufVxuXG5mdW5jdGlvbiBjcmVhdGVEaWZmSGVhZGVyU2VjdGlvbihcbiAgZGlmZjogUmVwb3J0c0RpZmYgfCBSZXBvcnRzRGlmZltdLFxuKTogTWFya2Rvd25Eb2N1bWVudCB7XG4gIGNvbnN0IG91dGNvbWUgPSBtZXJnZURpZmZPdXRjb21lcyhcbiAgICBjaGFuZ2VzVG9EaWZmT3V0Y29tZXModG9BcnJheShkaWZmKS5mbGF0TWFwKGdldERpZmZDaGFuZ2VzKSksXG4gICk7XG4gIC8vIFRPRE86IHdoYXQgaWYgYXJyYXkgY29udGFpbnMgZGlmZmVyZW50IGNvbW1pdCBwYWlycz9cbiAgY29uc3QgY29tbWl0cyA9IEFycmF5LmlzQXJyYXkoZGlmZikgPyBkaWZmWzBdPy5jb21taXRzIDogZGlmZi5jb21taXRzO1xuICBjb25zdCBwb3J0YWxVcmwgPSBBcnJheS5pc0FycmF5KGRpZmYpID8gdW5kZWZpbmVkIDogZGlmZi5wb3J0YWxVcmw7XG5cbiAgcmV0dXJuIG5ldyBNYXJrZG93bkRvY3VtZW50KClcbiAgICAuaGVhZGluZyhISUVSQVJDSFkubGV2ZWxfMSwgJ0NvZGUgUHVzaFVwJylcbiAgICAucGFyYWdyYXBoKGZvcm1hdFJlcG9ydE91dGNvbWUob3V0Y29tZSwgY29tbWl0cykpXG4gICAgLnBhcmFncmFwaChmb3JtYXRQb3J0YWxMaW5rKHBvcnRhbFVybCkpO1xufVxuXG5mdW5jdGlvbiBjcmVhdGVEaWZmUHJvamVjdFNlY3Rpb24oXG4gIGRpZmY6IExhYmVsZWREaWZmICYgeyBvdXRjb21lOiBEaWZmT3V0Y29tZSB9LFxuKTogTWFya2Rvd25Eb2N1bWVudCB7XG4gIHJldHVybiBuZXcgTWFya2Rvd25Eb2N1bWVudCgpXG4gICAgLmhlYWRpbmcoSElFUkFSQ0hZLmxldmVsXzIsIG1kYFx1RDgzRFx1RENCQyBQcm9qZWN0ICR7bWQuY29kZShkaWZmLmxhYmVsKX1gKVxuICAgIC5wYXJhZ3JhcGgoZm9ybWF0UmVwb3J0T3V0Y29tZShkaWZmLm91dGNvbWUpKVxuICAgIC5wYXJhZ3JhcGgoZm9ybWF0UG9ydGFsTGluayhkaWZmLnBvcnRhbFVybCkpXG4gICAgLiRjb25jYXQoXG4gICAgICBjcmVhdGVEaWZmQ2F0ZWdvcmllc1NlY3Rpb24oZGlmZiwge1xuICAgICAgICBza2lwSGVhZGluZzogdHJ1ZSxcbiAgICAgICAgc2tpcFVuY2hhbmdlZDogdHJ1ZSxcbiAgICAgIH0pLFxuICAgICAgY3JlYXRlRGlmZkRldGFpbHNTZWN0aW9uKGRpZmYsIEhJRVJBUkNIWS5sZXZlbF8zKSxcbiAgICApO1xufVxuXG5mdW5jdGlvbiBjcmVhdGVEaWZmQ2F0ZWdvcmllc1NlY3Rpb24oXG4gIGRpZmY6IFJlcG9ydHNEaWZmLFxuICBvcHRpb25zPzogeyBza2lwSGVhZGluZz86IGJvb2xlYW47IHNraXBVbmNoYW5nZWQ/OiBib29sZWFuIH0sXG4pOiBNYXJrZG93bkRvY3VtZW50IHwgbnVsbCB7XG4gIGNvbnN0IHsgY2hhbmdlZCwgdW5jaGFuZ2VkLCBhZGRlZCB9ID0gZGlmZi5jYXRlZ29yaWVzO1xuICBjb25zdCB7IHNraXBIZWFkaW5nLCBza2lwVW5jaGFuZ2VkIH0gPSBvcHRpb25zID8/IHt9O1xuXG4gIGNvbnN0IGNhdGVnb3JpZXNDb3VudCA9IGNoYW5nZWQubGVuZ3RoICsgdW5jaGFuZ2VkLmxlbmd0aCArIGFkZGVkLmxlbmd0aDtcbiAgY29uc3QgaGFzQ2hhbmdlcyA9IHVuY2hhbmdlZC5sZW5ndGggPCBjYXRlZ29yaWVzQ291bnQ7XG5cbiAgaWYgKGNhdGVnb3JpZXNDb3VudCA9PT0gMCkge1xuICAgIHJldHVybiBudWxsO1xuICB9XG5cbiAgY29uc3QgW2NvbHVtbnMsIHJvd3NdID0gY3JlYXRlQ2F0ZWdvcmllc1RhYmxlKGRpZmYsIHtcbiAgICBoYXNDaGFuZ2VzLFxuICAgIHNraXBVbmNoYW5nZWQsXG4gIH0pO1xuXG4gIHJldHVybiBuZXcgTWFya2Rvd25Eb2N1bWVudCgpXG4gICAgLmhlYWRpbmcoSElFUkFSQ0hZLmxldmVsXzIsICFza2lwSGVhZGluZyAmJiAnXHVEODNDXHVERkY3XHVGRTBGIENhdGVnb3JpZXMnKVxuICAgIC50YWJsZShjb2x1bW5zLCByb3dzKVxuICAgIC5wYXJhZ3JhcGgoYWRkZWQubGVuZ3RoID4gMCAmJiBtZC5pdGFsaWMoU3RyaW5nLnJhd2AoXFwqKSBOZXcgY2F0ZWdvcnkuYCkpXG4gICAgLnBhcmFncmFwaChcbiAgICAgIHNraXBVbmNoYW5nZWQgJiZcbiAgICAgICAgdW5jaGFuZ2VkLmxlbmd0aCA+IDAgJiZcbiAgICAgICAgc3VtbWFyaXplVW5jaGFuZ2VkKCdjYXRlZ29yeScsIHsgY2hhbmdlZCwgdW5jaGFuZ2VkIH0pLFxuICAgICk7XG59XG5cbmZ1bmN0aW9uIGNyZWF0ZUNhdGVnb3JpZXNUYWJsZShcbiAgZGlmZjogUmVwb3J0c0RpZmYsXG4gIG9wdGlvbnM6IHsgaGFzQ2hhbmdlczogYm9vbGVhbjsgc2tpcFVuY2hhbmdlZD86IGJvb2xlYW4gfSxcbik6IFBhcmFtZXRlcnM8TWFya2Rvd25Eb2N1bWVudFsndGFibGUnXT4ge1xuICBjb25zdCB7IGNoYW5nZWQsIHVuY2hhbmdlZCwgYWRkZWQgfSA9IGRpZmYuY2F0ZWdvcmllcztcbiAgY29uc3QgeyBoYXNDaGFuZ2VzLCBza2lwVW5jaGFuZ2VkIH0gPSBvcHRpb25zO1xuXG4gIGNvbnN0IHJvd3M6IFRhYmxlUm93W10gPSBbXG4gICAgLi4uc29ydENoYW5nZXMoY2hhbmdlZCkubWFwKGNhdGVnb3J5ID0+IFtcbiAgICAgIGZvcm1hdFRpdGxlKGNhdGVnb3J5KSxcbiAgICAgIGZvcm1hdFNjb3JlV2l0aENvbG9yKGNhdGVnb3J5LnNjb3Jlcy5iZWZvcmUsIHtcbiAgICAgICAgc2tpcEJvbGQ6IHRydWUsXG4gICAgICB9KSxcbiAgICAgIGZvcm1hdFNjb3JlV2l0aENvbG9yKGNhdGVnb3J5LnNjb3Jlcy5hZnRlciksXG4gICAgICBmb3JtYXRTY29yZUNoYW5nZShjYXRlZ29yeS5zY29yZXMuZGlmZiksXG4gICAgXSksXG4gICAgLi4uYWRkZWQubWFwKGNhdGVnb3J5ID0+IFtcbiAgICAgIGZvcm1hdFRpdGxlKGNhdGVnb3J5KSxcbiAgICAgIG1kLml0YWxpYyhTdHJpbmcucmF3YG4vYSAoXFwqKWApLFxuICAgICAgZm9ybWF0U2NvcmVXaXRoQ29sb3IoY2F0ZWdvcnkuc2NvcmUpLFxuICAgICAgbWQuaXRhbGljKFN0cmluZy5yYXdgbi9hIChcXCopYCksXG4gICAgXSksXG4gICAgLi4uKHNraXBVbmNoYW5nZWRcbiAgICAgID8gW11cbiAgICAgIDogdW5jaGFuZ2VkLm1hcChjYXRlZ29yeSA9PiBbXG4gICAgICAgICAgZm9ybWF0VGl0bGUoY2F0ZWdvcnkpLFxuICAgICAgICAgIGZvcm1hdFNjb3JlV2l0aENvbG9yKGNhdGVnb3J5LnNjb3JlLCB7IHNraXBCb2xkOiB0cnVlIH0pLFxuICAgICAgICAgIGZvcm1hdFNjb3JlV2l0aENvbG9yKGNhdGVnb3J5LnNjb3JlKSxcbiAgICAgICAgICAnXHUyMDEzJyxcbiAgICAgICAgXSkpLFxuICBdO1xuXG4gIGlmIChyb3dzLmxlbmd0aCA9PT0gMCkge1xuICAgIHJldHVybiBbW10sIFtdXTtcbiAgfVxuXG4gIGNvbnN0IGNvbHVtbnM6IFRhYmxlQ29sdW1uT2JqZWN0W10gPSBbXG4gICAgeyBoZWFkaW5nOiAnXHVEODNDXHVERkY3XHVGRTBGIENhdGVnb3J5JywgYWxpZ25tZW50OiAnbGVmdCcgfSxcbiAgICB7XG4gICAgICBoZWFkaW5nOiBoYXNDaGFuZ2VzID8gJ1x1MkI1MCBQcmV2aW91cyBzY29yZScgOiAnXHUyQjUwIFNjb3JlJyxcbiAgICAgIGFsaWdubWVudDogJ2NlbnRlcicsXG4gICAgfSxcbiAgICB7IGhlYWRpbmc6ICdcdTJCNTAgQ3VycmVudCBzY29yZScsIGFsaWdubWVudDogJ2NlbnRlcicgfSxcbiAgICB7IGhlYWRpbmc6ICdcdUQ4M0RcdUREMDQgU2NvcmUgY2hhbmdlJywgYWxpZ25tZW50OiAnY2VudGVyJyB9LFxuICBdO1xuXG4gIHJldHVybiBbXG4gICAgaGFzQ2hhbmdlcyA/IGNvbHVtbnMgOiBjb2x1bW5zLnNsaWNlKDAsIDIpLFxuICAgIHJvd3MubWFwKHJvdyA9PiAoaGFzQ2hhbmdlcyA/IHJvdyA6IHJvdy5zbGljZSgwLCAyKSkpLFxuICBdO1xufVxuXG5mdW5jdGlvbiBjcmVhdGVEaWZmRGV0YWlsc1NlY3Rpb24oXG4gIGRpZmY6IFJlcG9ydHNEaWZmLFxuICBsZXZlbDogSGVhZGluZ0xldmVsID0gSElFUkFSQ0hZLmxldmVsXzIsXG4pOiBNYXJrZG93bkRvY3VtZW50IHwgbnVsbCB7XG4gIGlmIChkaWZmLmdyb3Vwcy5jaGFuZ2VkLmxlbmd0aCArIGRpZmYuYXVkaXRzLmNoYW5nZWQubGVuZ3RoID09PSAwKSB7XG4gICAgcmV0dXJuIG51bGw7XG4gIH1cbiAgY29uc3Qgc3VtbWFyeSA9IChbJ2dyb3VwJywgJ2F1ZGl0J10gYXMgY29uc3QpXG4gICAgLm1hcCh0b2tlbiA9PlxuICAgICAgc3VtbWFyaXplRGlmZk91dGNvbWVzKFxuICAgICAgICBjaGFuZ2VzVG9EaWZmT3V0Y29tZXMoZGlmZltgJHt0b2tlbn1zYF0uY2hhbmdlZCksXG4gICAgICAgIHRva2VuLFxuICAgICAgKSxcbiAgICApXG4gICAgLmZpbHRlcihCb29sZWFuKVxuICAgIC5qb2luKCcsICcpO1xuICBjb25zdCBkZXRhaWxzID0gbmV3IE1hcmtkb3duRG9jdW1lbnQoKS4kY29uY2F0KFxuICAgIGNyZWF0ZURpZmZHcm91cHNTZWN0aW9uKGRpZmYsIGxldmVsKSxcbiAgICBjcmVhdGVEaWZmQXVkaXRzU2VjdGlvbihkaWZmLCBsZXZlbCksXG4gICk7XG4gIHJldHVybiBuZXcgTWFya2Rvd25Eb2N1bWVudCgpLmRldGFpbHMoc3VtbWFyeSwgZGV0YWlscyk7XG59XG5cbmZ1bmN0aW9uIGNyZWF0ZURpZmZHcm91cHNTZWN0aW9uKFxuICBkaWZmOiBSZXBvcnRzRGlmZixcbiAgbGV2ZWw6IEhlYWRpbmdMZXZlbCxcbik6IE1hcmtkb3duRG9jdW1lbnQgfCBudWxsIHtcbiAgaWYgKGRpZmYuZ3JvdXBzLmNoYW5nZWQubGVuZ3RoICsgZGlmZi5ncm91cHMudW5jaGFuZ2VkLmxlbmd0aCA9PT0gMCkge1xuICAgIHJldHVybiBudWxsO1xuICB9XG4gIHJldHVybiBuZXcgTWFya2Rvd25Eb2N1bWVudCgpLmhlYWRpbmcobGV2ZWwsICdcdUQ4M0RcdUREQzNcdUZFMEYgR3JvdXBzJykuJGNvbmNhdChcbiAgICBjcmVhdGVHcm91cHNPckF1ZGl0c0RldGFpbHMoXG4gICAgICAnZ3JvdXAnLFxuICAgICAgZGlmZi5ncm91cHMsXG4gICAgICBbXG4gICAgICAgIHsgaGVhZGluZzogJ1x1RDgzRFx1REQwQyBQbHVnaW4nLCBhbGlnbm1lbnQ6ICdsZWZ0JyB9LFxuICAgICAgICB7IGhlYWRpbmc6ICdcdUQ4M0RcdUREQzNcdUZFMEYgR3JvdXAnLCBhbGlnbm1lbnQ6ICdsZWZ0JyB9LFxuICAgICAgICB7IGhlYWRpbmc6ICdcdTJCNTAgUHJldmlvdXMgc2NvcmUnLCBhbGlnbm1lbnQ6ICdjZW50ZXInIH0sXG4gICAgICAgIHsgaGVhZGluZzogJ1x1MkI1MCBDdXJyZW50IHNjb3JlJywgYWxpZ25tZW50OiAnY2VudGVyJyB9LFxuICAgICAgICB7IGhlYWRpbmc6ICdcdUQ4M0RcdUREMDQgU2NvcmUgY2hhbmdlJywgYWxpZ25tZW50OiAnY2VudGVyJyB9LFxuICAgICAgXSxcbiAgICAgIHNvcnRDaGFuZ2VzKGRpZmYuZ3JvdXBzLmNoYW5nZWQpLm1hcChncm91cCA9PiBbXG4gICAgICAgIGZvcm1hdFRpdGxlKGdyb3VwLnBsdWdpbiksXG4gICAgICAgIGZvcm1hdFRpdGxlKGdyb3VwKSxcbiAgICAgICAgZm9ybWF0U2NvcmVXaXRoQ29sb3IoZ3JvdXAuc2NvcmVzLmJlZm9yZSwgeyBza2lwQm9sZDogdHJ1ZSB9KSxcbiAgICAgICAgZm9ybWF0U2NvcmVXaXRoQ29sb3IoZ3JvdXAuc2NvcmVzLmFmdGVyKSxcbiAgICAgICAgZm9ybWF0U2NvcmVDaGFuZ2UoZ3JvdXAuc2NvcmVzLmRpZmYpLFxuICAgICAgXSksXG4gICAgKSxcbiAgKTtcbn1cblxuZnVuY3Rpb24gY3JlYXRlRGlmZkF1ZGl0c1NlY3Rpb24oXG4gIGRpZmY6IFJlcG9ydHNEaWZmLFxuICBsZXZlbDogSGVhZGluZ0xldmVsLFxuKTogTWFya2Rvd25Eb2N1bWVudCB7XG4gIHJldHVybiBuZXcgTWFya2Rvd25Eb2N1bWVudCgpLmhlYWRpbmcobGV2ZWwsICdcdUQ4M0RcdURFRTFcdUZFMEYgQXVkaXRzJykuJGNvbmNhdChcbiAgICBjcmVhdGVHcm91cHNPckF1ZGl0c0RldGFpbHMoXG4gICAgICAnYXVkaXQnLFxuICAgICAgZGlmZi5hdWRpdHMsXG4gICAgICBbXG4gICAgICAgIHsgaGVhZGluZzogJ1x1RDgzRFx1REQwQyBQbHVnaW4nLCBhbGlnbm1lbnQ6ICdsZWZ0JyB9LFxuICAgICAgICB7IGhlYWRpbmc6ICdcdUQ4M0RcdURFRTFcdUZFMEYgQXVkaXQnLCBhbGlnbm1lbnQ6ICdsZWZ0JyB9LFxuICAgICAgICB7IGhlYWRpbmc6ICdcdUQ4M0RcdURDQ0YgUHJldmlvdXMgdmFsdWUnLCBhbGlnbm1lbnQ6ICdjZW50ZXInIH0sXG4gICAgICAgIHsgaGVhZGluZzogJ1x1RDgzRFx1RENDRiBDdXJyZW50IHZhbHVlJywgYWxpZ25tZW50OiAnY2VudGVyJyB9LFxuICAgICAgICB7IGhlYWRpbmc6ICdcdUQ4M0RcdUREMDQgVmFsdWUgY2hhbmdlJywgYWxpZ25tZW50OiAnY2VudGVyJyB9LFxuICAgICAgXSxcbiAgICAgIHNvcnRDaGFuZ2VzKGRpZmYuYXVkaXRzLmNoYW5nZWQpLm1hcChhdWRpdCA9PiBbXG4gICAgICAgIGZvcm1hdFRpdGxlKGF1ZGl0LnBsdWdpbiksXG4gICAgICAgIGZvcm1hdFRpdGxlKGF1ZGl0KSxcbiAgICAgICAgYCR7c2NvcmVNYXJrZXIoYXVkaXQuc2NvcmVzLmJlZm9yZSwgJ3NxdWFyZScpfSAke1xuICAgICAgICAgIGF1ZGl0LmRpc3BsYXlWYWx1ZXMuYmVmb3JlIHx8IGF1ZGl0LnZhbHVlcy5iZWZvcmUudG9TdHJpbmcoKVxuICAgICAgICB9YCxcbiAgICAgICAgbWRgJHtzY29yZU1hcmtlcihhdWRpdC5zY29yZXMuYWZ0ZXIsICdzcXVhcmUnKX0gJHttZC5ib2xkKFxuICAgICAgICAgIGF1ZGl0LmRpc3BsYXlWYWx1ZXMuYWZ0ZXIgfHwgYXVkaXQudmFsdWVzLmFmdGVyLnRvU3RyaW5nKCksXG4gICAgICAgICl9YCxcbiAgICAgICAgZm9ybWF0VmFsdWVDaGFuZ2UoYXVkaXQpLFxuICAgICAgXSksXG4gICAgKSxcbiAgKTtcbn1cbiIsICJjb25zdCBfX2luamVjdGVkX2ZpbGVuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3V0aWxzL3NyYy9saWIvcmVwb3J0cy9nZW5lcmF0ZS1tZC1yZXBvcnRzLWRpZmYtdXRpbHMudHNcIjtjb25zdCBfX2luamVjdGVkX2Rpcm5hbWVfXyA9IFwiL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvdXRpbHMvc3JjL2xpYi9yZXBvcnRzXCI7Y29uc3QgX19pbmplY3RlZF9pbXBvcnRfbWV0YV91cmxfXyA9IFwiZmlsZTovLy9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3V0aWxzL3NyYy9saWIvcmVwb3J0cy9nZW5lcmF0ZS1tZC1yZXBvcnRzLWRpZmYtdXRpbHMudHNcIjtpbXBvcnQgeyB0eXBlIElubGluZVRleHQsIE1hcmtkb3duRG9jdW1lbnQsIG1kIH0gZnJvbSAnYnVpbGQtbWQnO1xuaW1wb3J0IHR5cGUgeyBSZXBvcnRzRGlmZiB9IGZyb20gJ0Bjb2RlLXB1c2h1cC9tb2RlbHMnO1xuaW1wb3J0IHsgcGx1cmFsaXplLCBwbHVyYWxpemVUb2tlbiB9IGZyb20gJy4uL2Zvcm1hdHRpbmcuanMnO1xuaW1wb3J0IHsgb2JqZWN0VG9FbnRyaWVzIH0gZnJvbSAnLi4vdHJhbnNmb3JtLmpzJztcbmltcG9ydCB0eXBlIHsgRGlmZk91dGNvbWUgfSBmcm9tICcuL3R5cGVzLmpzJztcblxuLy8gdG8gcHJldmVudCBleGNlZWRpbmcgTWFya2Rvd24gY29tbWVudCBjaGFyYWN0ZXIgbGltaXRcbmNvbnN0IE1BWF9ST1dTID0gMTAwO1xuXG5leHBvcnQgZnVuY3Rpb24gc3VtbWFyaXplVW5jaGFuZ2VkKFxuICB0b2tlbjogc3RyaW5nLFxuICB7IGNoYW5nZWQsIHVuY2hhbmdlZCB9OiB7IGNoYW5nZWQ6IHVua25vd25bXTsgdW5jaGFuZ2VkOiB1bmtub3duW10gfSxcbik6IHN0cmluZyB7XG4gIGNvbnN0IHBsdXJhbGl6ZWRDb3VudCA9XG4gICAgY2hhbmdlZC5sZW5ndGggPiAwXG4gICAgICA/IHBsdXJhbGl6ZVRva2VuKGBvdGhlciAke3Rva2VufWAsIHVuY2hhbmdlZC5sZW5ndGgpXG4gICAgICA6IGBBbGwgb2YgJHtwbHVyYWxpemVUb2tlbih0b2tlbiwgdW5jaGFuZ2VkLmxlbmd0aCl9YDtcbiAgY29uc3QgcGx1cmFsaXplZFZlcmIgPSB1bmNoYW5nZWQubGVuZ3RoID09PSAxID8gJ2lzJyA6ICdhcmUnO1xuICByZXR1cm4gYCR7cGx1cmFsaXplZENvdW50fSAke3BsdXJhbGl6ZWRWZXJifSB1bmNoYW5nZWQuYDtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHN1bW1hcml6ZURpZmZPdXRjb21lcyhcbiAgb3V0Y29tZXM6IERpZmZPdXRjb21lW10sXG4gIHRva2VuOiBzdHJpbmcsXG4pOiBzdHJpbmcge1xuICByZXR1cm4gb2JqZWN0VG9FbnRyaWVzKGNvdW50RGlmZk91dGNvbWVzKG91dGNvbWVzKSlcbiAgICAuZmlsdGVyKFxuICAgICAgKGVudHJ5KTogZW50cnkgaXMgW0V4Y2x1ZGU8RGlmZk91dGNvbWUsICd1bmNoYW5nZWQnPiwgbnVtYmVyXSA9PlxuICAgICAgICBlbnRyeVswXSAhPT0gJ3VuY2hhbmdlZCcgJiYgZW50cnlbMV0gPiAwLFxuICAgIClcbiAgICAubWFwKChbb3V0Y29tZSwgY291bnRdKTogc3RyaW5nID0+IHtcbiAgICAgIGNvbnN0IGZvcm1hdHRlZENvdW50ID0gYDxzdHJvbmc+JHtjb3VudH08L3N0cm9uZz4gJHtwbHVyYWxpemUoXG4gICAgICAgIHRva2VuLFxuICAgICAgICBjb3VudCxcbiAgICAgICl9YDtcbiAgICAgIHN3aXRjaCAob3V0Y29tZSkge1xuICAgICAgICBjYXNlICdwb3NpdGl2ZSc6XG4gICAgICAgICAgcmV0dXJuIGBcdUQ4M0RcdURDNEQgJHtmb3JtYXR0ZWRDb3VudH0gaW1wcm92ZWRgO1xuICAgICAgICBjYXNlICduZWdhdGl2ZSc6XG4gICAgICAgICAgcmV0dXJuIGBcdUQ4M0RcdURDNEUgJHtmb3JtYXR0ZWRDb3VudH0gcmVncmVzc2VkYDtcbiAgICAgICAgY2FzZSAnbWl4ZWQnOlxuICAgICAgICAgIHJldHVybiBgJHtmb3JtYXR0ZWRDb3VudH0gY2hhbmdlZCB3aXRob3V0IGltcGFjdGluZyBzY29yZWA7XG4gICAgICB9XG4gICAgfSlcbiAgICAuam9pbignLCAnKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGNyZWF0ZUdyb3Vwc09yQXVkaXRzRGV0YWlsczxUIGV4dGVuZHMgJ2dyb3VwJyB8ICdhdWRpdCc+KFxuICB0b2tlbjogVCxcbiAgeyBjaGFuZ2VkLCB1bmNoYW5nZWQgfTogUmVwb3J0c0RpZmZbYCR7VH1zYF0sXG4gIC4uLltjb2x1bW5zLCByb3dzXTogUGFyYW1ldGVyczxNYXJrZG93bkRvY3VtZW50Wyd0YWJsZSddPlxuKTogTWFya2Rvd25Eb2N1bWVudCB7XG4gIGlmIChjaGFuZ2VkLmxlbmd0aCA9PT0gMCkge1xuICAgIHJldHVybiBuZXcgTWFya2Rvd25Eb2N1bWVudCgpLnBhcmFncmFwaChcbiAgICAgIHN1bW1hcml6ZVVuY2hhbmdlZCh0b2tlbiwgeyBjaGFuZ2VkLCB1bmNoYW5nZWQgfSksXG4gICAgKTtcbiAgfVxuICByZXR1cm4gbmV3IE1hcmtkb3duRG9jdW1lbnQoKVxuICAgIC50YWJsZShjb2x1bW5zLCByb3dzLnNsaWNlKDAsIE1BWF9ST1dTKSlcbiAgICAucGFyYWdyYXBoKFxuICAgICAgY2hhbmdlZC5sZW5ndGggPiBNQVhfUk9XUyAmJlxuICAgICAgICBtZC5pdGFsaWMoXG4gICAgICAgICAgYE9ubHkgdGhlICR7TUFYX1JPV1N9IG1vc3QgYWZmZWN0ZWQgJHtwbHVyYWxpemUoXG4gICAgICAgICAgICB0b2tlbixcbiAgICAgICAgICApfSBhcmUgbGlzdGVkIGFib3ZlIGZvciBicmV2aXR5LmAsXG4gICAgICAgICksXG4gICAgKVxuICAgIC5wYXJhZ3JhcGgoXG4gICAgICB1bmNoYW5nZWQubGVuZ3RoID4gMCAmJiBzdW1tYXJpemVVbmNoYW5nZWQodG9rZW4sIHsgY2hhbmdlZCwgdW5jaGFuZ2VkIH0pLFxuICAgICk7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBmb3JtYXRUaXRsZSh7XG4gIHRpdGxlLFxuICBkb2NzVXJsLFxufToge1xuICB0aXRsZTogc3RyaW5nO1xuICBkb2NzVXJsPzogc3RyaW5nO1xufSk6IElubGluZVRleHQge1xuICBpZiAoZG9jc1VybCkge1xuICAgIHJldHVybiBtZC5saW5rKGRvY3NVcmwsIHRpdGxlKTtcbiAgfVxuICByZXR1cm4gdGl0bGU7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBmb3JtYXRQb3J0YWxMaW5rKFxuICBwb3J0YWxVcmw6IHN0cmluZyB8IHVuZGVmaW5lZCxcbik6IElubGluZVRleHQgfCB1bmRlZmluZWQge1xuICByZXR1cm4gKFxuICAgIHBvcnRhbFVybCAmJlxuICAgIG1kLmxpbmsocG9ydGFsVXJsLCAnXHVEODNEXHVERDc1XHVGRTBGIFNlZSBmdWxsIGNvbXBhcmlzb24gaW4gQ29kZSBQdXNoVXAgcG9ydGFsIFx1RDgzRFx1REQwRCcpXG4gICk7XG59XG5cbnR5cGUgQ2hhbmdlID0ge1xuICBzY29yZXM6IHsgZGlmZjogbnVtYmVyIH07XG4gIHZhbHVlcz86IHsgZGlmZjogbnVtYmVyIH07XG59O1xuXG5leHBvcnQgZnVuY3Rpb24gc29ydENoYW5nZXM8VCBleHRlbmRzIENoYW5nZT4oY2hhbmdlczogVFtdKTogVFtdIHtcbiAgcmV0dXJuIGNoYW5nZXMudG9Tb3J0ZWQoXG4gICAgKGEsIGIpID0+XG4gICAgICBNYXRoLmFicyhiLnNjb3Jlcy5kaWZmKSAtIE1hdGguYWJzKGEuc2NvcmVzLmRpZmYpIHx8XG4gICAgICBNYXRoLmFicyhiLnZhbHVlcz8uZGlmZiA/PyAwKSAtIE1hdGguYWJzKGEudmFsdWVzPy5kaWZmID8/IDApLFxuICApO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gZ2V0RGlmZkNoYW5nZXMoZGlmZjogUmVwb3J0c0RpZmYpOiBDaGFuZ2VbXSB7XG4gIHJldHVybiBbXG4gICAgLi4uZGlmZi5jYXRlZ29yaWVzLmNoYW5nZWQsXG4gICAgLi4uZGlmZi5ncm91cHMuY2hhbmdlZCxcbiAgICAuLi5kaWZmLmF1ZGl0cy5jaGFuZ2VkLFxuICBdO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gY2hhbmdlc1RvRGlmZk91dGNvbWVzKGNoYW5nZXM6IENoYW5nZVtdKTogRGlmZk91dGNvbWVbXSB7XG4gIHJldHVybiBjaGFuZ2VzLm1hcCgoY2hhbmdlKTogRGlmZk91dGNvbWUgPT4ge1xuICAgIGlmIChjaGFuZ2Uuc2NvcmVzLmRpZmYgPiAwKSB7XG4gICAgICByZXR1cm4gJ3Bvc2l0aXZlJztcbiAgICB9XG4gICAgaWYgKGNoYW5nZS5zY29yZXMuZGlmZiA8IDApIHtcbiAgICAgIHJldHVybiAnbmVnYXRpdmUnO1xuICAgIH1cbiAgICBpZiAoY2hhbmdlLnZhbHVlcyAhPSBudWxsICYmIGNoYW5nZS52YWx1ZXMuZGlmZiAhPT0gMCkge1xuICAgICAgcmV0dXJuICdtaXhlZCc7XG4gICAgfVxuICAgIHJldHVybiAndW5jaGFuZ2VkJztcbiAgfSk7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBtZXJnZURpZmZPdXRjb21lcyhvdXRjb21lczogRGlmZk91dGNvbWVbXSk6IERpZmZPdXRjb21lIHtcbiAgaWYgKG91dGNvbWVzLmV2ZXJ5KG91dGNvbWUgPT4gb3V0Y29tZSA9PT0gJ3VuY2hhbmdlZCcpKSB7XG4gICAgcmV0dXJuICd1bmNoYW5nZWQnO1xuICB9XG4gIGlmIChvdXRjb21lcy5pbmNsdWRlcygncG9zaXRpdmUnKSAmJiAhb3V0Y29tZXMuaW5jbHVkZXMoJ25lZ2F0aXZlJykpIHtcbiAgICByZXR1cm4gJ3Bvc2l0aXZlJztcbiAgfVxuICBpZiAob3V0Y29tZXMuaW5jbHVkZXMoJ25lZ2F0aXZlJykgJiYgIW91dGNvbWVzLmluY2x1ZGVzKCdwb3NpdGl2ZScpKSB7XG4gICAgcmV0dXJuICduZWdhdGl2ZSc7XG4gIH1cbiAgcmV0dXJuICdtaXhlZCc7XG59XG5cbmZ1bmN0aW9uIGNvdW50RGlmZk91dGNvbWVzKFxuICBvdXRjb21lczogRGlmZk91dGNvbWVbXSxcbik6IFJlY29yZDxEaWZmT3V0Y29tZSwgbnVtYmVyPiB7XG4gIHJldHVybiB7XG4gICAgcG9zaXRpdmU6IG91dGNvbWVzLmZpbHRlcihvdXRjb21lID0+IG91dGNvbWUgPT09ICdwb3NpdGl2ZScpLmxlbmd0aCxcbiAgICBuZWdhdGl2ZTogb3V0Y29tZXMuZmlsdGVyKG91dGNvbWUgPT4gb3V0Y29tZSA9PT0gJ25lZ2F0aXZlJykubGVuZ3RoLFxuICAgIG1peGVkOiBvdXRjb21lcy5maWx0ZXIob3V0Y29tZSA9PiBvdXRjb21lID09PSAnbWl4ZWQnKS5sZW5ndGgsXG4gICAgdW5jaGFuZ2VkOiBvdXRjb21lcy5maWx0ZXIob3V0Y29tZSA9PiBvdXRjb21lID09PSAndW5jaGFuZ2VkJykubGVuZ3RoLFxuICB9O1xufVxuXG5leHBvcnQgZnVuY3Rpb24gZm9ybWF0UmVwb3J0T3V0Y29tZShcbiAgb3V0Y29tZTogRGlmZk91dGNvbWUsXG4gIGNvbW1pdHM/OiBSZXBvcnRzRGlmZlsnY29tbWl0cyddLFxuKTogSW5saW5lVGV4dCB7XG4gIGNvbnN0IG91dGNvbWVUZXh0cyA9IHtcbiAgICBwb3NpdGl2ZTogbWRgXHVEODNFXHVERDczIENvZGUgUHVzaFVwIHJlcG9ydCBoYXMgJHttZC5ib2xkKCdpbXByb3ZlZCcpfWAsXG4gICAgbmVnYXRpdmU6IG1kYFx1RDgzRFx1REUxRiBDb2RlIFB1c2hVcCByZXBvcnQgaGFzICR7bWQuYm9sZCgncmVncmVzc2VkJyl9YCxcbiAgICBtaXhlZDogbWRgXHVEODNFXHVERDI4IENvZGUgUHVzaFVwIHJlcG9ydCBoYXMgYm90aCAke21kLmJvbGQoXG4gICAgICAnaW1wcm92ZW1lbnRzIGFuZCByZWdyZXNzaW9ucycsXG4gICAgKX1gLFxuICAgIHVuY2hhbmdlZDogbWRgXHVEODNEXHVERTEwIENvZGUgUHVzaFVwIHJlcG9ydCBpcyAke21kLmJvbGQoJ3VuY2hhbmdlZCcpfWAsXG4gIH07XG5cbiAgaWYgKGNvbW1pdHMpIHtcbiAgICBjb25zdCBjb21taXRzVGV4dCA9IGBjb21wYXJlZCB0YXJnZXQgY29tbWl0ICR7Y29tbWl0cy5hZnRlci5oYXNofSB3aXRoIHNvdXJjZSBjb21taXQgJHtjb21taXRzLmJlZm9yZS5oYXNofWA7XG4gICAgcmV0dXJuIG1kYCR7b3V0Y29tZVRleHRzW291dGNvbWVdfSBcdTIwMTMgJHtjb21taXRzVGV4dH0uYDtcbiAgfVxuXG4gIHJldHVybiBtZGAke291dGNvbWVUZXh0c1tvdXRjb21lXX0uYDtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGNvbXBhcmVEaWZmc0J5PFQgZXh0ZW5kcyAnY2F0ZWdvcmllcycgfCAnZ3JvdXBzJyB8ICdhdWRpdHMnPihcbiAgdHlwZTogVCxcbiAgYTogUmVwb3J0c0RpZmYsXG4gIGI6IFJlcG9ydHNEaWZmLFxuKTogbnVtYmVyIHtcbiAgcmV0dXJuIChcbiAgICBzdW1TY29yZUNoYW5nZXMoYlt0eXBlXS5jaGFuZ2VkKSAtIHN1bVNjb3JlQ2hhbmdlcyhhW3R5cGVdLmNoYW5nZWQpIHx8XG4gICAgc3VtQ29uZmlnQ2hhbmdlcyhiW3R5cGVdKSAtIHN1bUNvbmZpZ0NoYW5nZXMoYVt0eXBlXSlcbiAgKTtcbn1cblxuZnVuY3Rpb24gc3VtU2NvcmVDaGFuZ2VzKGNoYW5nZXM6IENoYW5nZVtdKTogbnVtYmVyIHtcbiAgcmV0dXJuIGNoYW5nZXMucmVkdWNlPG51bWJlcj4oXG4gICAgKGFjYywgeyBzY29yZXMgfSkgPT4gYWNjICsgTWF0aC5hYnMoc2NvcmVzLmRpZmYpLFxuICAgIDAsXG4gICk7XG59XG5cbmZ1bmN0aW9uIHN1bUNvbmZpZ0NoYW5nZXMoe1xuICBhZGRlZCxcbiAgcmVtb3ZlZCxcbn06IHtcbiAgYWRkZWQ6IHVua25vd25bXTtcbiAgcmVtb3ZlZDogdW5rbm93bltdO1xufSk6IG51bWJlciB7XG4gIHJldHVybiBhZGRlZC5sZW5ndGggKyByZW1vdmVkLmxlbmd0aDtcbn1cbiIsICJjb25zdCBfX2luamVjdGVkX2ZpbGVuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3V0aWxzL3NyYy9saWIvcmVwb3J0cy9sb2FkLXJlcG9ydC50c1wiO2NvbnN0IF9faW5qZWN0ZWRfZGlybmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy91dGlscy9zcmMvbGliL3JlcG9ydHNcIjtjb25zdCBfX2luamVjdGVkX2ltcG9ydF9tZXRhX3VybF9fID0gXCJmaWxlOi8vL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvdXRpbHMvc3JjL2xpYi9yZXBvcnRzL2xvYWQtcmVwb3J0LnRzXCI7aW1wb3J0IHBhdGggZnJvbSAnbm9kZTpwYXRoJztcbmltcG9ydCB7XG4gIHR5cGUgRm9ybWF0LFxuICB0eXBlIFBlcnNpc3RDb25maWcsXG4gIHR5cGUgUmVwb3J0LFxuICByZXBvcnRTY2hlbWEsXG59IGZyb20gJ0Bjb2RlLXB1c2h1cC9tb2RlbHMnO1xuaW1wb3J0IHtcbiAgZW5zdXJlRGlyZWN0b3J5RXhpc3RzLFxuICByZWFkSnNvbkZpbGUsXG4gIHJlYWRUZXh0RmlsZSxcbn0gZnJvbSAnLi4vZmlsZS1zeXN0ZW0uanMnO1xuXG50eXBlIExvYWRlZFJlcG9ydEZvcm1hdDxUIGV4dGVuZHMgRm9ybWF0PiA9IFQgZXh0ZW5kcyAnanNvbicgPyBSZXBvcnQgOiBzdHJpbmc7XG5cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBsb2FkUmVwb3J0PFQgZXh0ZW5kcyBGb3JtYXQ+KFxuICBvcHRpb25zOiBSZXF1aXJlZDxPbWl0PFBlcnNpc3RDb25maWcsICdmb3JtYXQnPj4gJiB7XG4gICAgZm9ybWF0OiBUO1xuICB9LFxuKTogUHJvbWlzZTxMb2FkZWRSZXBvcnRGb3JtYXQ8VD4+IHtcbiAgY29uc3QgeyBvdXRwdXREaXIsIGZpbGVuYW1lLCBmb3JtYXQgfSA9IG9wdGlvbnM7XG4gIGF3YWl0IGVuc3VyZURpcmVjdG9yeUV4aXN0cyhvdXRwdXREaXIpO1xuICBjb25zdCBmaWxlUGF0aCA9IHBhdGguam9pbihvdXRwdXREaXIsIGAke2ZpbGVuYW1lfS4ke2Zvcm1hdH1gKTtcblxuICBpZiAoZm9ybWF0ID09PSAnanNvbicpIHtcbiAgICBjb25zdCBjb250ZW50ID0gYXdhaXQgcmVhZEpzb25GaWxlKGZpbGVQYXRoKTtcbiAgICByZXR1cm4gcmVwb3J0U2NoZW1hLnBhcnNlKGNvbnRlbnQpIGFzIExvYWRlZFJlcG9ydEZvcm1hdDxUPjtcbiAgfVxuXG4gIGNvbnN0IHRleHQgPSBhd2FpdCByZWFkVGV4dEZpbGUoZmlsZVBhdGgpO1xuICByZXR1cm4gdGV4dCBhcyBMb2FkZWRSZXBvcnRGb3JtYXQ8VD47XG59XG4iLCAiY29uc3QgX19pbmplY3RlZF9maWxlbmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy91dGlscy9zcmMvbGliL3JlcG9ydHMvbG9nLXN0ZG91dC1zdW1tYXJ5LnRzXCI7Y29uc3QgX19pbmplY3RlZF9kaXJuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3V0aWxzL3NyYy9saWIvcmVwb3J0c1wiO2NvbnN0IF9faW5qZWN0ZWRfaW1wb3J0X21ldGFfdXJsX18gPSBcImZpbGU6Ly8vaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy91dGlscy9zcmMvbGliL3JlcG9ydHMvbG9nLXN0ZG91dC1zdW1tYXJ5LnRzXCI7aW1wb3J0IHsgYm9sZCwgY3lhbiwgY3lhbkJyaWdodCwgZ3JlZW4sIHJlZCB9IGZyb20gJ2Fuc2lzJztcbmltcG9ydCB0eXBlIHsgQXVkaXRSZXBvcnQgfSBmcm9tICdAY29kZS1wdXNodXAvbW9kZWxzJztcbmltcG9ydCB7IHVpIH0gZnJvbSAnLi4vbG9nZ2luZy5qcyc7XG5pbXBvcnQge1xuICBDT0RFX1BVU0hVUF9ET01BSU4sXG4gIEZPT1RFUl9QUkVGSVgsXG4gIFJFUE9SVF9IRUFETElORV9URVhULFxuICBSRVBPUlRfUkFXX09WRVJWSUVXX1RBQkxFX0hFQURFUlMsXG4gIFRFUk1JTkFMX1dJRFRILFxufSBmcm9tICcuL2NvbnN0YW50cy5qcyc7XG5pbXBvcnQgdHlwZSB7IFNjb3JlZFJlcG9ydCB9IGZyb20gJy4vdHlwZXMuanMnO1xuaW1wb3J0IHtcbiAgYXBwbHlTY29yZUNvbG9yLFxuICBjb3VudENhdGVnb3J5QXVkaXRzLFxuICB0YXJnZXRTY29yZUljb24sXG59IGZyb20gJy4vdXRpbHMuanMnO1xuXG5mdW5jdGlvbiBsb2cobXNnID0gJycpOiB2b2lkIHtcbiAgdWkoKS5sb2dnZXIubG9nKG1zZyk7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBsb2dTdGRvdXRTdW1tYXJ5KHJlcG9ydDogU2NvcmVkUmVwb3J0LCB2ZXJib3NlID0gZmFsc2UpOiB2b2lkIHtcbiAgY29uc3QgeyBwbHVnaW5zLCBjYXRlZ29yaWVzLCBwYWNrYWdlTmFtZSwgdmVyc2lvbiB9ID0gcmVwb3J0O1xuICBsb2cocmVwb3J0VG9IZWFkZXJTZWN0aW9uKHsgcGFja2FnZU5hbWUsIHZlcnNpb24gfSkpO1xuICBsb2coKTtcbiAgbG9nUGx1Z2lucyhwbHVnaW5zLCB2ZXJib3NlKTtcbiAgaWYgKGNhdGVnb3JpZXMgJiYgY2F0ZWdvcmllcy5sZW5ndGggPiAwKSB7XG4gICAgbG9nQ2F0ZWdvcmllcyh7IHBsdWdpbnMsIGNhdGVnb3JpZXMgfSk7XG4gIH1cbiAgbG9nKGAke0ZPT1RFUl9QUkVGSVh9ICR7Q09ERV9QVVNIVVBfRE9NQUlOfWApO1xuICBsb2coKTtcbn1cblxuZnVuY3Rpb24gcmVwb3J0VG9IZWFkZXJTZWN0aW9uKHtcbiAgcGFja2FnZU5hbWUsXG4gIHZlcnNpb24sXG59OiBQaWNrPFNjb3JlZFJlcG9ydCwgJ3BhY2thZ2VOYW1lJyB8ICd2ZXJzaW9uJz4pOiBzdHJpbmcge1xuICByZXR1cm4gYCR7Ym9sZChSRVBPUlRfSEVBRExJTkVfVEVYVCl9IC0gJHtwYWNrYWdlTmFtZX1AJHt2ZXJzaW9ufWA7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBsb2dQbHVnaW5zKFxuICBwbHVnaW5zOiBTY29yZWRSZXBvcnRbJ3BsdWdpbnMnXSxcbiAgdmVyYm9zZTogYm9vbGVhbixcbik6IHZvaWQge1xuICBwbHVnaW5zLmZvckVhY2gocGx1Z2luID0+IHtcbiAgICBjb25zdCB7IHRpdGxlLCBhdWRpdHMgfSA9IHBsdWdpbjtcbiAgICBjb25zdCBmaWx0ZXJlZEF1ZGl0cyA9XG4gICAgICB2ZXJib3NlIHx8IGF1ZGl0cy5sZW5ndGggPT09IDFcbiAgICAgICAgPyBhdWRpdHNcbiAgICAgICAgOiBhdWRpdHMuZmlsdGVyKCh7IHNjb3JlIH0pID0+IHNjb3JlICE9PSAxKTtcbiAgICBjb25zdCBkaWZmID0gYXVkaXRzLmxlbmd0aCAtIGZpbHRlcmVkQXVkaXRzLmxlbmd0aDtcblxuICAgIGxvZ0F1ZGl0cyh0aXRsZSwgZmlsdGVyZWRBdWRpdHMpO1xuXG4gICAgaWYgKGRpZmYgPiAwKSB7XG4gICAgICBjb25zdCBub3RpY2UgPVxuICAgICAgICBmaWx0ZXJlZEF1ZGl0cy5sZW5ndGggPT09IDBcbiAgICAgICAgICA/IGAuLi4gQWxsICR7ZGlmZn0gYXVkaXRzIGhhdmUgcGVyZmVjdCBzY29yZXMgLi4uYFxuICAgICAgICAgIDogYC4uLiAke2RpZmZ9IGF1ZGl0cyB3aXRoIHBlcmZlY3Qgc2NvcmVzIG9taXR0ZWQgZm9yIGJyZXZpdHkgLi4uYDtcbiAgICAgIGxvZ1JvdygxLCBub3RpY2UpO1xuICAgIH1cbiAgICBsb2coKTtcbiAgfSk7XG59XG5cbmZ1bmN0aW9uIGxvZ0F1ZGl0cyhwbHVnaW5UaXRsZTogc3RyaW5nLCBhdWRpdHM6IEF1ZGl0UmVwb3J0W10pOiB2b2lkIHtcbiAgbG9nKCk7XG4gIGxvZyhib2xkLm1hZ2VudGFCcmlnaHQoYCR7cGx1Z2luVGl0bGV9IGF1ZGl0c2ApKTtcbiAgbG9nKCk7XG4gIGF1ZGl0cy5mb3JFYWNoKCh7IHNjb3JlLCB0aXRsZSwgZGlzcGxheVZhbHVlLCB2YWx1ZSB9KSA9PiB7XG4gICAgbG9nUm93KHNjb3JlLCB0aXRsZSwgZGlzcGxheVZhbHVlIHx8IGAke3ZhbHVlfWApO1xuICB9KTtcbn1cblxuZnVuY3Rpb24gbG9nUm93KHNjb3JlOiBudW1iZXIsIHRpdGxlOiBzdHJpbmcsIHZhbHVlPzogc3RyaW5nKTogdm9pZCB7XG4gIHVpKCkucm93KFtcbiAgICB7XG4gICAgICB0ZXh0OiBhcHBseVNjb3JlQ29sb3IoeyBzY29yZSwgdGV4dDogJ1x1MjVDRicgfSksXG4gICAgICB3aWR0aDogMixcbiAgICAgIHBhZGRpbmc6IFswLCAxLCAwLCAwXSxcbiAgICB9LFxuICAgIHtcbiAgICAgIHRleHQ6IHRpdGxlLFxuICAgICAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIEB0eXBlc2NyaXB0LWVzbGludC9uby1tYWdpYy1udW1iZXJzXG4gICAgICBwYWRkaW5nOiBbMCwgMywgMCwgMF0sXG4gICAgfSxcbiAgICAuLi4odmFsdWVcbiAgICAgID8gW1xuICAgICAgICAgIHtcbiAgICAgICAgICAgIHRleHQ6IGN5YW5CcmlnaHQodmFsdWUpLFxuICAgICAgICAgICAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIEB0eXBlc2NyaXB0LWVzbGludC9uby1tYWdpYy1udW1iZXJzXG4gICAgICAgICAgICB3aWR0aDogMjAsXG4gICAgICAgICAgICBwYWRkaW5nOiBbMCwgMCwgMCwgMF0sXG4gICAgICAgICAgfSxcbiAgICAgICAgXVxuICAgICAgOiBbXSksXG4gIF0pO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gbG9nQ2F0ZWdvcmllcyh7XG4gIHBsdWdpbnMsXG4gIGNhdGVnb3JpZXMsXG59OiBSZXF1aXJlZDxQaWNrPFNjb3JlZFJlcG9ydCwgJ3BsdWdpbnMnIHwgJ2NhdGVnb3JpZXMnPj4pOiB2b2lkIHtcbiAgY29uc3QgaEFsaWduID0gKGlkeDogbnVtYmVyKSA9PiAoaWR4ID09PSAwID8gJ2xlZnQnIDogJ3JpZ2h0Jyk7XG5cbiAgY29uc3Qgcm93cyA9IGNhdGVnb3JpZXMubWFwKCh7IHRpdGxlLCBzY29yZSwgcmVmcywgaXNCaW5hcnkgfSkgPT4gW1xuICAgIHRpdGxlLFxuICAgIGAke2JpbmFyeUljb25QcmVmaXgoc2NvcmUsIGlzQmluYXJ5KX0ke2FwcGx5U2NvcmVDb2xvcih7IHNjb3JlIH0pfWAsXG4gICAgY291bnRDYXRlZ29yeUF1ZGl0cyhyZWZzLCBwbHVnaW5zKSxcbiAgXSk7XG4gIGNvbnN0IHRhYmxlID0gdWkoKS50YWJsZSgpO1xuICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgQHR5cGVzY3JpcHQtZXNsaW50L25vLW1hZ2ljLW51bWJlcnNcbiAgdGFibGUuY29sdW1uV2lkdGhzKFtURVJNSU5BTF9XSURUSCAtIDkgLSAxMCAtIDQsIDksIDEwXSk7XG4gIHRhYmxlLmhlYWQoXG4gICAgUkVQT1JUX1JBV19PVkVSVklFV19UQUJMRV9IRUFERVJTLm1hcCgoaGVhZGluZywgaWR4KSA9PiAoe1xuICAgICAgY29udGVudDogY3lhbihoZWFkaW5nKSxcbiAgICAgIGhBbGlnbjogaEFsaWduKGlkeCksXG4gICAgfSkpLFxuICApO1xuICByb3dzLmZvckVhY2gocm93ID0+XG4gICAgdGFibGUucm93KFxuICAgICAgcm93Lm1hcCgoY29udGVudCwgaWR4KSA9PiAoe1xuICAgICAgICBjb250ZW50OiBjb250ZW50LnRvU3RyaW5nKCksXG4gICAgICAgIGhBbGlnbjogaEFsaWduKGlkeCksXG4gICAgICB9KSksXG4gICAgKSxcbiAgKTtcblxuICBsb2coYm9sZC5tYWdlbnRhQnJpZ2h0KCdDYXRlZ29yaWVzJykpO1xuICBsb2coKTtcbiAgdGFibGUucmVuZGVyKCk7XG4gIGxvZygpO1xufVxuXG4vLyBAVE9ETyByZWZhY3RvciBgaXNCaW5hcnk6IGJvb2xlYW5gIHRvIGB0YXJnZXRTY29yZTogbnVtYmVyYCAjNzEzXG5leHBvcnQgZnVuY3Rpb24gYmluYXJ5SWNvblByZWZpeChcbiAgc2NvcmU6IG51bWJlcixcbiAgaXNCaW5hcnk6IGJvb2xlYW4gfCB1bmRlZmluZWQsXG4pOiBzdHJpbmcge1xuICByZXR1cm4gdGFyZ2V0U2NvcmVJY29uKHNjb3JlLCBpc0JpbmFyeSA/IDEgOiB1bmRlZmluZWQsIHtcbiAgICBwYXNzSWNvbjogYm9sZChncmVlbignXHUyNzEzJykpLFxuICAgIGZhaWxJY29uOiBib2xkKHJlZCgnXHUyNzE3JykpLFxuICAgIHBvc3RmaXg6ICcgJyxcbiAgfSk7XG59XG4iLCAiY29uc3QgX19pbmplY3RlZF9maWxlbmFtZV9fID0gXCIvaG9tZS9hbGVqYW5kcm8vZGV2L2NvZGUtcHVzaHVwLWNsaS9wYWNrYWdlcy91dGlscy9zcmMvbGliL3pvZC12YWxpZGF0aW9uLnRzXCI7Y29uc3QgX19pbmplY3RlZF9kaXJuYW1lX18gPSBcIi9ob21lL2FsZWphbmRyby9kZXYvY29kZS1wdXNodXAtY2xpL3BhY2thZ2VzL3V0aWxzL3NyYy9saWJcIjtjb25zdCBfX2luamVjdGVkX2ltcG9ydF9tZXRhX3VybF9fID0gXCJmaWxlOi8vL2hvbWUvYWxlamFuZHJvL2Rldi9jb2RlLXB1c2h1cC1jbGkvcGFja2FnZXMvdXRpbHMvc3JjL2xpYi96b2QtdmFsaWRhdGlvbi50c1wiO2ltcG9ydCB7IGJvbGQsIHJlZCB9IGZyb20gJ2Fuc2lzJztcbmltcG9ydCB0eXBlIHsgTWVzc2FnZUJ1aWxkZXIgfSBmcm9tICd6b2QtdmFsaWRhdGlvbi1lcnJvcic7XG5cbmV4cG9ydCBmdW5jdGlvbiBmb3JtYXRFcnJvclBhdGgoZXJyb3JQYXRoOiAoc3RyaW5nIHwgbnVtYmVyKVtdKTogc3RyaW5nIHtcbiAgcmV0dXJuIGVycm9yUGF0aFxuICAgIC5tYXAoKGtleSwgaW5kZXgpID0+IHtcbiAgICAgIGlmICh0eXBlb2Yga2V5ID09PSAnbnVtYmVyJykge1xuICAgICAgICByZXR1cm4gYFske2tleX1dYDtcbiAgICAgIH1cbiAgICAgIHJldHVybiBpbmRleCA+IDAgPyBgLiR7a2V5fWAgOiBrZXk7XG4gICAgfSlcbiAgICAuam9pbignJyk7XG59XG5cbmV4cG9ydCBjb25zdCB6b2RFcnJvck1lc3NhZ2VCdWlsZGVyOiBNZXNzYWdlQnVpbGRlciA9IGlzc3VlcyA9PlxuICBpc3N1ZXNcbiAgICAubWFwKGlzc3VlID0+IHtcbiAgICAgIGNvbnN0IGZvcm1hdHRlZE1lc3NhZ2UgPSByZWQoYCR7Ym9sZChpc3N1ZS5jb2RlKX06ICR7aXNzdWUubWVzc2FnZX1gKTtcbiAgICAgIGNvbnN0IGZvcm1hdHRlZFBhdGggPSBmb3JtYXRFcnJvclBhdGgoaXNzdWUucGF0aCk7XG4gICAgICBpZiAoZm9ybWF0dGVkUGF0aCkge1xuICAgICAgICByZXR1cm4gYFZhbGlkYXRpb24gZXJyb3IgYXQgJHtib2xkKGZvcm1hdHRlZFBhdGgpfVxcbiR7Zm9ybWF0dGVkTWVzc2FnZX1cXG5gO1xuICAgICAgfVxuICAgICAgcmV0dXJuIGAke2Zvcm1hdHRlZE1lc3NhZ2V9XFxuYDtcbiAgICB9KVxuICAgIC5qb2luKCdcXG4nKTtcbiJdLAogICJtYXBwaW5ncyI6ICI7QUFBcVEsT0FBTztBQUM1USxTQUFTLEtBQUFB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O0FBQUEsRUFDdkQsQ0FBQztBQUNIOyIsCiAgIm5hbWVzIjogWyJ6IiwgInBhdGgiLCAicGF0aCIsICJ1aSIsICJwYXRoIiwgImJvbGQiLCAicGF0aCIsICJ1aSIsICJ6IiwgInoiLCAiY29uZmlnIiwgImdyb3VwcyIsICJhdWRpdHMiLCAiY2FsY3VsYXRlQ292ZXJhZ2UiLCAiY29uZmlnIiwgImNhbGN1bGF0ZUNvdmVyYWdlIiwgImNvbmZpZyIsICJjcmVhdGVSZXF1aXJlIiwgInBhdGgiLCAiZmlsZVVSTFRvUGF0aCIsICJ6IiwgInoiLCAic2x1Z2lmeSIsICJ0b0FycmF5IiwgInBhdGgiLCAiZXhpc3RzIiwgInRvQXJyYXkiLCAidWkiLCAiZXhpc3RzIiwgInRvQXJyYXkiLCAidWkiLCAiRVNMaW50IiwgIndyaXRlRmlsZSIsICJwYXRoIiwgImVuc3VyZURpcmVjdG9yeUV4aXN0cyIsICJmaWxlUGF0aFRvQ2xpQXJnIiwgInBsdWdpbldvcmtEaXIiLCAicmVhZEpzb25GaWxlIiwgImRpc3RpbmN0IiwgImV4ZWN1dGVQcm9jZXNzIiwgImZpbGVQYXRoVG9DbGlBcmciLCAidG9BcnJheSIsICJ1aSIsICJXT1JLRElSIiwgInBsdWdpbldvcmtEaXIiLCAiUlVOTkVSX09VVFBVVF9QQVRIIiwgInBhdGgiLCAiUExVR0lOX0NPTkZJR19QQVRIIiwgInBhdGgiLCAiZmlsZUV4aXN0cyIsICJ0b0FycmF5IiwgImNyZWF0ZVJlcXVpcmUiLCAicGF0aCIsICJmaWxlVVJMVG9QYXRoIiwgInoiLCAieiIsICJvYmplY3RUb0tleXMiLCAicGF0aCIsICJvYmplY3RUb0tleXMiLCAicmVhZEpzb25GaWxlIiwgIm9iamVjdFRvRW50cmllcyIsICJvYmplY3RUb0VudHJpZXMiLCAib2JqZWN0VG9FbnRyaWVzIiwgIm9iamVjdFRvRW50cmllcyIsICJvYmplY3RUb0tleXMiLCAib2JqZWN0VG9LZXlzIiwgIm9iamVjdFRvRW50cmllcyIsICJmcm9tSnNvbkxpbmVzIiwgIm9iamVjdEZyb21FbnRyaWVzIiwgIm9iamVjdFRvRW50cmllcyIsICJvYmplY3RUb0tleXMiLCAid3JpdGVGaWxlIiwgInBhdGgiLCAiZW5zdXJlRGlyZWN0b3J5RXhpc3RzIiwgImV4ZWN1dGVQcm9jZXNzIiwgImZpbGVQYXRoVG9DbGlBcmciLCAib2JqZWN0RnJvbUVudHJpZXMiLCAicmVhZEpzb25GaWxlIiwgIm9iamVjdFRvRW50cmllcyIsICJwYXRoIiwgInBsdWdpbldvcmtEaXIiLCAiV09SS0RJUiIsICJwbHVnaW5Xb3JrRGlyIiwgIlJVTk5FUl9PVVRQVVRfUEFUSCIsICJwYXRoIiwgIlBMVUdJTl9DT05GSUdfUEFUSCIsICJtZCIsICJvYmplY3RGcm9tRW50cmllcyIsICJvYmplY3RUb0tleXMiLCAib2JqZWN0VG9LZXlzIiwgInBhdGgiLCAiZmlsZUV4aXN0cyIsICJleGVjdXRlUHJvY2VzcyIsICJjcmVhdGVSZXF1aXJlIiwgInBhdGgiLCAicGF0aCIsICJib2xkIiwgInVpIiwgInBhdGgiLCAicGF0aCIsICJwYXRoIiwgImVuc3VyZURpcmVjdG9yeUV4aXN0cyIsICJib2xkIiwgImltcG9ydE1vZHVsZSIsICJyZWFkSnNvbkZpbGUiLCAidWkiLCAiYm9sZCIsICJ5ZWxsb3ciLCAidWkiLCAidGFibGVTY2hlbWEiLCAiZm9ybWF0Qnl0ZXMiLCAiZm9ybWF0RHVyYXRpb24iLCAiaHRtbCIsICJib2xkIiwgInVpIiwgImJvbGQiLCAidG9BcnJheSIsICJjb25maWciLCAiY29uZmlnIiwgImV4aXN0cyIsICJtZCIsICJib2xkIiwgInJlYWRGaWxlIiwgInBhdGgiLCAicGF0aCIsICJzaW1wbGVHaXQiLCAicGxhdGZvcm0iLCAiY29uZmlnIiwgImNhdGVnb3JpZXMiLCAiYm9sZCIsICJncmF5IiwgIk1hcmtkb3duRG9jdW1lbnQiLCAibWQiLCAibWQiLCAicGF0aCIsICJNYXJrZG93bkRvY3VtZW50IiwgIm1kIiwgIk1hcmtkb3duRG9jdW1lbnQiLCAibWQiLCAiTWFya2Rvd25Eb2N1bWVudCIsICJtZCIsICJwYXRoIiwgImJvbGQiLCAiZ3JlZW4iLCAiYm9sZCIsICJyZWQiLCAieiJdCn0K diff --git a/packages/plugin-doc-coverage/mocks/component-mock.ts b/packages/plugin-doc-coverage/mocks/component-mock.ts index 5b2da7c27..6f39b5118 100644 --- a/packages/plugin-doc-coverage/mocks/component-mock.ts +++ b/packages/plugin-doc-coverage/mocks/component-mock.ts @@ -9,41 +9,3 @@ export function DUMMY_FUNCTION() { export function DUMMY_FUNCTION_2() { return 'Hello World 2'; } - -// class DummyClass { -// /** -// * Dummy property that returns 'Hello World 3'. -// * @returns {string} - The string 'Hello World 3'. -// */ -// dummyProperty = 'Hello World 3'; - -// /** -// * Dummy method that returns 'Hello World 4'. -// * @returns {string} - The string 'Hello World 4'. -// */ -// dummyMethod() { -// return 'Hello World 4'; -// } - -// constructor() { -// this.dummyProperty = 'Hello World 3'; -// } -// } - -// export default DummyClass; - -// export const variableDummy = 'Hello World 5'; - -// export const variableDummy2 = 'Hello World 6'; - -// /** Dummy variable that returns 'Hello World 7'. */ -// export const variableDummy3 = 'Hello World 7'; - -// /** Dummy interface that returns 'Hello World 8'. */ -// export interface DummyInterface { -// dummyProperty: string; -// dummyMethod(): string; -// } - -// /** Dummy type that returns 'Hello World 9'. */ -// export type DummyType = string; diff --git a/packages/plugin-doc-coverage/mocks/fixtures/angular/map-event.function.ts b/packages/plugin-doc-coverage/mocks/fixtures/angular/map-event.function.ts index 9cd32ce8a..55f343e7c 100644 --- a/packages/plugin-doc-coverage/mocks/fixtures/angular/map-event.function.ts +++ b/packages/plugin-doc-coverage/mocks/fixtures/angular/map-event.function.ts @@ -1,3 +1,5 @@ +export const someVariable = 'Hello World 1'; + export function mapEventToCustomEvent(event: string) { return event; } diff --git a/packages/plugin-doc-coverage/mocks/source-files.mock.ts b/packages/plugin-doc-coverage/mocks/source-files.mock.ts index bc0ad113c..5a7ba13aa 100644 --- a/packages/plugin-doc-coverage/mocks/source-files.mock.ts +++ b/packages/plugin-doc-coverage/mocks/source-files.mock.ts @@ -6,70 +6,37 @@ import { SourceFile, SyntaxKind, TypeAliasDeclaration, + VariableStatement, } from 'ts-morph'; -import type { CoverageType } from '../src/lib/models'; +import type { CoverageType } from '../src/lib/runner/models'; export function sourceFileMock( file: string, nodes: Partial>>, ): SourceFile { + const createNodeGetter = ( + coverageType: CoverageType, + nodeData?: Record, + ) => { + if (!nodeData) return []; + return Object.entries(nodeData).map(([line, isCommented]) => + nodeMock({ coverageType, line: Number(line), file, isCommented }), + ) as unknown as T[]; + }; + return { getFilePath: () => file as any, getClasses: () => - nodes.classes - ? (Object.entries(nodes.classes).map(([line, isCommented]) => - nodeMock({ - coverageType: 'classes', - line: Number(line), - file, - isCommented, - }), - ) as unknown as ClassDeclaration[]) - : [], + createNodeGetter('classes', nodes.classes), getFunctions: () => - nodes.functions - ? (Object.entries(nodes.functions).map(([line, isCommented]) => - nodeMock({ - coverageType: 'functions', - line: Number(line), - file, - isCommented, - }), - ) as unknown as FunctionDeclaration[]) - : [], - getEnums: () => - nodes.enums - ? (Object.entries(nodes.enums).map(([line, isCommented]) => - nodeMock({ - coverageType: 'enums', - line: Number(line), - file, - isCommented, - }), - ) as unknown as EnumDeclaration[]) - : [], + createNodeGetter('functions', nodes.functions), + getEnums: () => createNodeGetter('enums', nodes.enums), getTypeAliases: () => - nodes.types - ? (Object.entries(nodes.types).map(([line, isCommented]) => - nodeMock({ - coverageType: 'types', - line: Number(line), - file, - isCommented, - }), - ) as unknown as TypeAliasDeclaration[]) - : [], + createNodeGetter('types', nodes.types), getInterfaces: () => - nodes.interfaces - ? (Object.entries(nodes.interfaces).map(([line, isCommented]) => - nodeMock({ - coverageType: 'interfaces', - line: Number(line), - file, - isCommented, - }), - ) as unknown as InterfaceDeclaration[]) - : [], + createNodeGetter('interfaces', nodes.interfaces), + getVariableStatements: () => + createNodeGetter('variables', nodes.variables), } as SourceFile; } @@ -84,6 +51,7 @@ export function nodeMock(options: { getJsDocs: () => (options.isCommented ? ['Comment'] : []), getName: () => 'test', getStartLineNumber: () => options.line, + getDeclarations: () => [], // Only for classes getMethods: () => [], getProperties: () => [], diff --git a/packages/plugin-doc-coverage/src/lib/runner/__snapshots__/doc-processer.integration.test.ts.snap b/packages/plugin-doc-coverage/src/lib/runner/__snapshots__/doc-processer.integration.test.ts.snap index 2507460f6..0b98c1e05 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/__snapshots__/doc-processer.integration.test.ts.snap +++ b/packages/plugin-doc-coverage/src/lib/runner/__snapshots__/doc-processer.integration.test.ts.snap @@ -23,7 +23,7 @@ exports[`processDocCoverage > should succesfully get the right number of ts file }, { "file": "/home/alejandro/dev/code-pushup-cli/packages/plugin-doc-coverage/mocks/fixtures/angular/map-event.function.ts", - "line": 1, + "line": 3, "name": "mapEventToCustomEvent", "type": "functions", }, @@ -40,7 +40,7 @@ exports[`processDocCoverage > should succesfully get the right number of ts file "issues": [ { "file": "/home/alejandro/dev/code-pushup-cli/packages/plugin-doc-coverage/mocks/fixtures/angular/app.component.ts", - "line": 15, + "line": 17, "name": "sendEvent", "type": "methods", }, @@ -52,7 +52,7 @@ exports[`processDocCoverage > should succesfully get the right number of ts file "issues": [ { "file": "/home/alejandro/dev/code-pushup-cli/packages/plugin-doc-coverage/mocks/fixtures/angular/app.component.ts", - "line": 5, + "line": 7, "name": "title", "type": "properties", }, @@ -65,9 +65,16 @@ exports[`processDocCoverage > should succesfully get the right number of ts file "nodesCount": 0, }, "variables": { - "coverage": 100, - "issues": [], - "nodesCount": 0, + "coverage": 0, + "issues": [ + { + "file": "/home/alejandro/dev/code-pushup-cli/packages/plugin-doc-coverage/mocks/fixtures/angular/map-event.function.ts", + "line": 1, + "name": "someVariable", + "type": "variables", + }, + ], + "nodesCount": 1, }, } `; @@ -89,7 +96,7 @@ exports[`processDocCoverage > should succesfully get the right number of ts file "issues": [ { "file": "/home/alejandro/dev/code-pushup-cli/packages/plugin-doc-coverage/mocks/fixtures/angular/map-event.function.ts", - "line": 1, + "line": 3, "name": "mapEventToCustomEvent", "type": "functions", }, @@ -106,7 +113,7 @@ exports[`processDocCoverage > should succesfully get the right number of ts file "issues": [ { "file": "/home/alejandro/dev/code-pushup-cli/packages/plugin-doc-coverage/mocks/fixtures/angular/app.component.ts", - "line": 15, + "line": 17, "name": "sendEvent", "type": "methods", }, @@ -118,7 +125,7 @@ exports[`processDocCoverage > should succesfully get the right number of ts file "issues": [ { "file": "/home/alejandro/dev/code-pushup-cli/packages/plugin-doc-coverage/mocks/fixtures/angular/app.component.ts", - "line": 5, + "line": 7, "name": "title", "type": "properties", }, @@ -131,9 +138,16 @@ exports[`processDocCoverage > should succesfully get the right number of ts file "nodesCount": 0, }, "variables": { - "coverage": 100, - "issues": [], - "nodesCount": 0, + "coverage": 0, + "issues": [ + { + "file": "/home/alejandro/dev/code-pushup-cli/packages/plugin-doc-coverage/mocks/fixtures/angular/map-event.function.ts", + "line": 1, + "name": "someVariable", + "type": "variables", + }, + ], + "nodesCount": 1, }, } `; diff --git a/packages/plugin-doc-coverage/src/lib/runner/doc-processer.ts b/packages/plugin-doc-coverage/src/lib/runner/doc-processer.ts index d510ae15f..c5d82cac7 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/doc-processer.ts +++ b/packages/plugin-doc-coverage/src/lib/runner/doc-processer.ts @@ -1,4 +1,9 @@ -import { ClassDeclaration, Project, SourceFile } from 'ts-morph'; +import { + ClassDeclaration, + Project, + SourceFile, + VariableStatement, +} from 'ts-morph'; import type { DocCoveragePluginConfig } from '../config.js'; import type { CoverageResult, @@ -11,6 +16,30 @@ import { getCoverageTypeFromKind, } from './utils.js'; +/** + * Gets the variables information from the variable statements + * @param variableStatements - The variable statements to process + * @returns {Node[]} The variables information with the right methods to get the information + */ +export function getVariablesInformation( + variableStatements: VariableStatement[], +) { + return variableStatements.flatMap(variable => { + // Get parent-level information + const parentInfo = { + getKind: () => variable.getKind(), + getJsDocs: () => variable.getJsDocs(), + getStartLineNumber: () => variable.getStartLineNumber(), + }; + + // Map each declaration to combine parent info with declaration-specific info + return variable.getDeclarations().map(declaration => ({ + ...parentInfo, + getName: () => declaration.getName(), + })); + }); +} + /** * Processes documentation coverage for TypeScript files in the specified path * @param toInclude - The file path pattern to include for documentation analysis @@ -44,6 +73,7 @@ export function getUnprocessedCoverageReport( ...sourceFile.getTypeAliases(), ...sourceFile.getEnums(), ...sourceFile.getInterfaces(), + ...getVariablesInformation(sourceFile.getVariableStatements()), ]; const coverageReportOfCurrentFile = allNodesFromFile.reduce( diff --git a/packages/plugin-doc-coverage/src/lib/runner/doc-processer.unit.test.ts b/packages/plugin-doc-coverage/src/lib/runner/doc-processer.unit.test.ts index 233723f03..dcd9ad31b 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/doc-processer.unit.test.ts +++ b/packages/plugin-doc-coverage/src/lib/runner/doc-processer.unit.test.ts @@ -1,8 +1,9 @@ -import type { ClassDeclaration } from 'ts-morph'; +import type { ClassDeclaration, VariableStatement } from 'ts-morph'; import { nodeMock, sourceFileMock } from '../../../mocks/source-files.mock'; import { getClassNodes, getUnprocessedCoverageReport, + getVariablesInformation, mergeCoverageResults, } from './doc-processer.js'; import type { UnprocessedCoverageResult } from './models.js'; @@ -196,3 +197,79 @@ describe('getClassNodes', () => { expect(propertyNodeSpy).toHaveBeenCalledTimes(1); }); }); + +describe('getVariablesInformation', () => { + it('should process variable statements correctly', () => { + const mockDeclaration = { + getName: () => 'testVariable', + }; + + const mockVariableStatement = { + getKind: () => 'const', + getJsDocs: () => ['some docs'], + getStartLineNumber: () => 42, + getDeclarations: () => [mockDeclaration], + }; + + const result = getVariablesInformation([ + mockVariableStatement as unknown as VariableStatement, + ]); + + expect(result).toHaveLength(1); + expect(result[0]).toEqual({ + getKind: expect.any(Function), + getJsDocs: expect.any(Function), + getStartLineNumber: expect.any(Function), + getName: expect.any(Function), + }); + // It must be defined + expect(result[0]!.getName()).toBe('testVariable'); + expect(result[0]!.getKind()).toBe('const'); + expect(result[0]!.getJsDocs()).toEqual(['some docs']); + expect(result[0]!.getStartLineNumber()).toBe(42); + }); + + it('should handle multiple declarations in a single variable statement', () => { + const mockDeclarations = [ + { getName: () => 'var1' }, + { getName: () => 'var2' }, + ]; + + const mockVariableStatement = { + getKind: () => 'let', + getJsDocs: () => [], + getStartLineNumber: () => 10, + getDeclarations: () => mockDeclarations, + }; + + const result = getVariablesInformation([ + mockVariableStatement as unknown as VariableStatement, + ]); + + expect(result).toHaveLength(2); + // They must be defined + expect(result[0]!.getName()).toBe('var1'); + expect(result[1]!.getName()).toBe('var2'); + expect(result[0]!.getKind()).toBe('let'); + expect(result[1]!.getKind()).toBe('let'); + }); + + it('should handle empty variable statements array', () => { + const result = getVariablesInformation([]); + expect(result).toHaveLength(0); + }); + + it('should handle variable statements without declarations', () => { + const mockVariableStatement = { + getKind: () => 'const', + getJsDocs: () => [], + getStartLineNumber: () => 1, + getDeclarations: () => [], + }; + + const result = getVariablesInformation([ + mockVariableStatement as unknown as VariableStatement, + ]); + expect(result).toHaveLength(0); + }); +}); diff --git a/packages/plugin-doc-coverage/src/lib/runner/utils.ts b/packages/plugin-doc-coverage/src/lib/runner/utils.ts index 31c312619..7b8807a2c 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/utils.ts +++ b/packages/plugin-doc-coverage/src/lib/runner/utils.ts @@ -67,6 +67,7 @@ export function getCoverageTypeFromKind(kind: SyntaxKind): CoverageType { return 'interfaces'; case SyntaxKind.EnumDeclaration: return 'enums'; + case SyntaxKind.VariableStatement: case SyntaxKind.VariableDeclaration: return 'variables'; case SyntaxKind.PropertyDeclaration: From 1a5a6149ce07aa00961a8033cdbf7556ec5d0a66 Mon Sep 17 00:00:00 2001 From: Alejandro Date: Sat, 21 Dec 2024 12:51:12 +0100 Subject: [PATCH 11/66] chore(plugin-doc-coverage): remove unused file and update readme file --- packages/plugin-doc-coverage/README.md | 19 +++++++++---------- .../mocks/documentation.json | 11 ----------- 2 files changed, 9 insertions(+), 21 deletions(-) delete mode 100644 packages/plugin-doc-coverage/mocks/documentation.json diff --git a/packages/plugin-doc-coverage/README.md b/packages/plugin-doc-coverage/README.md index 0b4fb33e6..59a039c9d 100644 --- a/packages/plugin-doc-coverage/README.md +++ b/packages/plugin-doc-coverage/README.md @@ -33,12 +33,14 @@ Measured documentation types are mapped to Code PushUp audits in the following w pnpm add --save-dev @code-pushup/doc-coverage-plugin ``` -3. Add Compodoc to your project. You can follow the instructions [here](https://compodoc.app/guides/installation.html). - -4. Add this plugin to the `plugins` array in your Code PushUp CLI config file (e.g. `code-pushup.config.js`). +3. Add this plugin to the `plugins` array in your Code PushUp CLI config file (e.g. `code-pushup.config.js`). Pass the target files to analyze and optionally specify which types of documentation you want to track. - All documentation types are measured by default. If you wish to focus on a subset of offered types, define them in `docTypes`. + You can skip for example tests by defining in the sourceGlob the path to the tests folder or pattern to match the tests files with the `!` symbol. + All documentation types are measured by default. + If you wish to focus on a subset of offered types, define them in `onlyAudits`. + Also you can skip some types by defining them in `skipAudits`. + You can only define or `onlyAudits` or `skipAudits`, not both. The configuration will look similarly to the following: @@ -50,16 +52,13 @@ Measured documentation types are mapped to Code PushUp audits in the following w plugins: [ // ... await docCoveragePlugin({ - coverageToolCommand: { - command: 'npx', - args: ['compodoc', '-p', 'tsconfig.doc.json', '-e', 'json'], - }, + sourceGlob: ['**/*.ts'], }), ], }; ``` -5. (Optional) Reference individual audits or the provided plugin group which you wish to include in custom categories (use `npx code-pushup print-config` to list audits and groups). +4. (Optional) Reference individual audits or the provided plugin group which you wish to include in custom categories (use `npx code-pushup print-config` to list audits and groups). ๐Ÿ’ก Assign weights based on what influence each documentation type should have on the overall category score (assign weight 0 to only include as extra info, without influencing category score). @@ -85,7 +84,7 @@ Measured documentation types are mapped to Code PushUp audits in the following w }; ``` -6. Run the CLI with `npx code-pushup collect` and view or upload report (refer to [CLI docs](../cli/README.md)). +5. Run the CLI with `npx code-pushup collect` and view or upload report (refer to [CLI docs](../cli/README.md)). ## About documentation coverage diff --git a/packages/plugin-doc-coverage/mocks/documentation.json b/packages/plugin-doc-coverage/mocks/documentation.json deleted file mode 100644 index 7a608253c..000000000 --- a/packages/plugin-doc-coverage/mocks/documentation.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "coverage": { - "count": 85, - "files": { - "src/app/services/my.service.ts": { - "documented": 17, - "total": 20 - } - } - } -} From 8909855f8e4a142eca8b9eeb988b68ad9f11b729 Mon Sep 17 00:00:00 2001 From: Alejandro <49059458+aramirezj@users.noreply.github.com> Date: Sun, 22 Dec 2024 14:54:41 +0100 Subject: [PATCH 12/66] Update packages/plugin-doc-coverage/src/lib/constants.ts Co-authored-by: Michael Hladky <10064416+BioPhoton@users.noreply.github.com> --- packages/plugin-doc-coverage/src/lib/constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/plugin-doc-coverage/src/lib/constants.ts b/packages/plugin-doc-coverage/src/lib/constants.ts index d199b2b55..d5d0d6b38 100644 --- a/packages/plugin-doc-coverage/src/lib/constants.ts +++ b/packages/plugin-doc-coverage/src/lib/constants.ts @@ -7,7 +7,7 @@ export const AUDITS_MAP: Record = { 'classes-coverage': { slug: 'classes-coverage', title: 'Classes coverage', - description: 'Coverage of classes', + description: 'Documentation coverage of classes', }, 'methods-coverage': { slug: 'methods-coverage', From b8cdf77de25368121d55a115aa1f457f9a55488e Mon Sep 17 00:00:00 2001 From: Alejandro <49059458+aramirezj@users.noreply.github.com> Date: Sun, 22 Dec 2024 14:54:51 +0100 Subject: [PATCH 13/66] Update packages/plugin-doc-coverage/package.json Co-authored-by: Michael Hladky <10064416+BioPhoton@users.noreply.github.com> --- packages/plugin-doc-coverage/package.json | 29 ++++++++++++----------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/packages/plugin-doc-coverage/package.json b/packages/plugin-doc-coverage/package.json index c06f92a38..4d5fc0c60 100644 --- a/packages/plugin-doc-coverage/package.json +++ b/packages/plugin-doc-coverage/package.json @@ -13,21 +13,22 @@ "directory": "packages/plugin-doc-coverage" }, "keywords": [ - "CLI", - "Code PushUp", - "plugin", - "automation", - "developer tools", - "conformance", "documentation coverage", - "documentation", - "docs", - "KPI tracking", - "automated feedback", - "regression guard", - "actionable feedback", - "audit", - "score monitoring" + "documentation quality", + "docs completeness", + "automated documentation checks", + "coverage audit", + "documentation conformance", + "docs KPI tracking", + "documentation feedback", + "actionable documentation insights", + "documentation regression guard", + "documentation score monitoring", + "developer documentation tools", + "plugin for documentation coverage", + "CLI documentation coverage", + "Code PushUp documentation", + "documentation audit" ], "publishConfig": { "access": "public" From 24ab2e04861dcd1d31566d9debe3ff1461b48a30 Mon Sep 17 00:00:00 2001 From: Alejandro Date: Sun, 22 Dec 2024 15:38:03 +0100 Subject: [PATCH 14/66] chore(plugin-doc-coverage): from Michael comments, remakes some tests, rename different stuff --- code-pushup.config.ts | 24 ++- .../mocks/component-mock.ts | 11 -- .../mocks/component-mock.unit.test.ts | 16 -- .../doc-processer.integration.test.ts.snap | 153 ------------------ .../__snapshots__/runner.unit.test.ts.snap | 147 ----------------- .../runner/doc-processer.integration.test.ts | 39 +++-- .../src/lib/runner/doc-processer.ts | 20 +-- .../src/lib/runner/doc-processer.unit.test.ts | 6 +- .../src/lib/runner/models.ts | 6 +- .../src/lib/runner/runner.ts | 11 +- .../src/lib/runner/runner.unit.test.ts | 73 ++++----- .../src/lib/runner/utils.ts | 6 +- .../src/lib/runner/utils.unit.test.ts | 15 +- 13 files changed, 95 insertions(+), 432 deletions(-) delete mode 100644 packages/plugin-doc-coverage/mocks/component-mock.ts delete mode 100644 packages/plugin-doc-coverage/mocks/component-mock.unit.test.ts delete mode 100644 packages/plugin-doc-coverage/src/lib/runner/__snapshots__/doc-processer.integration.test.ts.snap delete mode 100644 packages/plugin-doc-coverage/src/lib/runner/__snapshots__/runner.unit.test.ts.snap diff --git a/code-pushup.config.ts b/code-pushup.config.ts index a0a33cfee..6b2ea8dc4 100644 --- a/code-pushup.config.ts +++ b/code-pushup.config.ts @@ -1,12 +1,6 @@ import 'dotenv/config'; import { z } from 'zod'; -import { - coverageCoreConfigNx, - docCoverageCoreConfig, - eslintCoreConfigNx, - jsPackagesCoreConfig, - lighthouseCoreConfig, -} from './code-pushup.preset.js'; +import { docCoverageCoreConfig } from './code-pushup.preset.js'; import type { CoreConfig } from './packages/models/src/index.js'; import { mergeConfigs } from './packages/utils/src/index.js'; @@ -33,13 +27,13 @@ const config: CoreConfig = { }; export default mergeConfigs( - config, - await coverageCoreConfigNx(), - await jsPackagesCoreConfig(), - await lighthouseCoreConfig( - 'https://github.com/code-pushup/cli?tab=readme-ov-file#code-pushup-cli/', - ), - await eslintCoreConfigNx(), + // config, + // await coverageCoreConfigNx(), + // await jsPackagesCoreConfig(), + // await lighthouseCoreConfig( + // 'https://github.com/code-pushup/cli?tab=readme-ov-file#code-pushup-cli/', + // ), + // await eslintCoreConfigNx(), await docCoverageCoreConfig({ sourceGlob: [ 'packages/**/src/**/*.ts', @@ -48,6 +42,6 @@ export default mergeConfigs( '!**/implementation/**', '!**/internal/**', ], - skipAudits: ['methods-coverage'], + skipAudits: ['methodawdawdds-coverage'], }), ); diff --git a/packages/plugin-doc-coverage/mocks/component-mock.ts b/packages/plugin-doc-coverage/mocks/component-mock.ts deleted file mode 100644 index 6f39b5118..000000000 --- a/packages/plugin-doc-coverage/mocks/component-mock.ts +++ /dev/null @@ -1,11 +0,0 @@ -/** - * Dummy function that returns 'Hello World'. - * @returns {string} - The string 'Hello World'. - */ -export function DUMMY_FUNCTION() { - return 'Hello World'; -} - -export function DUMMY_FUNCTION_2() { - return 'Hello World 2'; -} diff --git a/packages/plugin-doc-coverage/mocks/component-mock.unit.test.ts b/packages/plugin-doc-coverage/mocks/component-mock.unit.test.ts deleted file mode 100644 index 648080fb0..000000000 --- a/packages/plugin-doc-coverage/mocks/component-mock.unit.test.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { describe, expect, it } from 'vitest'; -import { DUMMY_FUNCTION, DUMMY_FUNCTION_2 } from './component-mock.js'; - -export function shouldnotBeHere() { - return 'Hello World'; -} - -describe('component-mock', () => { - it('should return Hello World', () => { - expect(DUMMY_FUNCTION()).toBe('Hello World'); - }); - - it('should return Hello World 2', () => { - expect(DUMMY_FUNCTION_2()).toBe('Hello World 2'); - }); -}); diff --git a/packages/plugin-doc-coverage/src/lib/runner/__snapshots__/doc-processer.integration.test.ts.snap b/packages/plugin-doc-coverage/src/lib/runner/__snapshots__/doc-processer.integration.test.ts.snap deleted file mode 100644 index 0b98c1e05..000000000 --- a/packages/plugin-doc-coverage/src/lib/runner/__snapshots__/doc-processer.integration.test.ts.snap +++ /dev/null @@ -1,153 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`processDocCoverage > should succesfully get the right number of ts files 1`] = ` -{ - "classes": { - "coverage": 100, - "issues": [], - "nodesCount": 1, - }, - "enums": { - "coverage": 100, - "issues": [], - "nodesCount": 0, - }, - "functions": { - "coverage": 33.33, - "issues": [ - { - "file": "/home/alejandro/dev/code-pushup-cli/packages/plugin-doc-coverage/mocks/fixtures/angular/app.component.spec.ts", - "line": 1, - "name": "notRealisticFunction", - "type": "functions", - }, - { - "file": "/home/alejandro/dev/code-pushup-cli/packages/plugin-doc-coverage/mocks/fixtures/angular/map-event.function.ts", - "line": 3, - "name": "mapEventToCustomEvent", - "type": "functions", - }, - ], - "nodesCount": 3, - }, - "interfaces": { - "coverage": 100, - "issues": [], - "nodesCount": 0, - }, - "methods": { - "coverage": 50, - "issues": [ - { - "file": "/home/alejandro/dev/code-pushup-cli/packages/plugin-doc-coverage/mocks/fixtures/angular/app.component.ts", - "line": 17, - "name": "sendEvent", - "type": "methods", - }, - ], - "nodesCount": 2, - }, - "properties": { - "coverage": 0, - "issues": [ - { - "file": "/home/alejandro/dev/code-pushup-cli/packages/plugin-doc-coverage/mocks/fixtures/angular/app.component.ts", - "line": 7, - "name": "title", - "type": "properties", - }, - ], - "nodesCount": 1, - }, - "types": { - "coverage": 100, - "issues": [], - "nodesCount": 0, - }, - "variables": { - "coverage": 0, - "issues": [ - { - "file": "/home/alejandro/dev/code-pushup-cli/packages/plugin-doc-coverage/mocks/fixtures/angular/map-event.function.ts", - "line": 1, - "name": "someVariable", - "type": "variables", - }, - ], - "nodesCount": 1, - }, -} -`; - -exports[`processDocCoverage > should succesfully get the right number of ts files and not include spec files 1`] = ` -{ - "classes": { - "coverage": 100, - "issues": [], - "nodesCount": 1, - }, - "enums": { - "coverage": 100, - "issues": [], - "nodesCount": 0, - }, - "functions": { - "coverage": 50, - "issues": [ - { - "file": "/home/alejandro/dev/code-pushup-cli/packages/plugin-doc-coverage/mocks/fixtures/angular/map-event.function.ts", - "line": 3, - "name": "mapEventToCustomEvent", - "type": "functions", - }, - ], - "nodesCount": 2, - }, - "interfaces": { - "coverage": 100, - "issues": [], - "nodesCount": 0, - }, - "methods": { - "coverage": 50, - "issues": [ - { - "file": "/home/alejandro/dev/code-pushup-cli/packages/plugin-doc-coverage/mocks/fixtures/angular/app.component.ts", - "line": 17, - "name": "sendEvent", - "type": "methods", - }, - ], - "nodesCount": 2, - }, - "properties": { - "coverage": 0, - "issues": [ - { - "file": "/home/alejandro/dev/code-pushup-cli/packages/plugin-doc-coverage/mocks/fixtures/angular/app.component.ts", - "line": 7, - "name": "title", - "type": "properties", - }, - ], - "nodesCount": 1, - }, - "types": { - "coverage": 100, - "issues": [], - "nodesCount": 0, - }, - "variables": { - "coverage": 0, - "issues": [ - { - "file": "/home/alejandro/dev/code-pushup-cli/packages/plugin-doc-coverage/mocks/fixtures/angular/map-event.function.ts", - "line": 1, - "name": "someVariable", - "type": "variables", - }, - ], - "nodesCount": 1, - }, -} -`; diff --git a/packages/plugin-doc-coverage/src/lib/runner/__snapshots__/runner.unit.test.ts.snap b/packages/plugin-doc-coverage/src/lib/runner/__snapshots__/runner.unit.test.ts.snap deleted file mode 100644 index 9db5313d1..000000000 --- a/packages/plugin-doc-coverage/src/lib/runner/__snapshots__/runner.unit.test.ts.snap +++ /dev/null @@ -1,147 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`trasformCoverageReportToAudits > should filter audits when onlyAudits is provided 1`] = ` -[ - { - "details": { - "issues": [ - { - "message": "Missing documentation", - "severity": "warning", - "source": { - "file": "test.ts", - "position": { - "startLine": 10, - }, - }, - }, - ], - }, - "displayValue": "75 %", - "score": 0.75, - "slug": "functions-coverage", - "value": 75, - }, -] -`; - -exports[`trasformCoverageReportToAudits > should filter audits when skipAudits is provided 1`] = ` -[ - { - "details": { - "issues": [ - { - "message": "Missing documentation", - "severity": "warning", - "source": { - "file": "test.ts", - "position": { - "startLine": 10, - }, - }, - }, - ], - }, - "displayValue": "75 %", - "score": 0.75, - "slug": "functions-coverage", - "value": 75, - }, -] -`; - -exports[`trasformCoverageReportToAudits > should handle coverage result with multiple issues 1`] = ` -[ - { - "details": { - "issues": [ - { - "message": "Missing documentation", - "severity": "warning", - "source": { - "file": "test1.ts", - "position": { - "startLine": 10, - }, - }, - }, - { - "message": "Missing documentation", - "severity": "warning", - "source": { - "file": "test2.ts", - "position": { - "startLine": 20, - }, - }, - }, - ], - }, - "displayValue": "50 %", - "score": 0.5, - "slug": "functions-coverage", - "value": 50, - }, -] -`; - -exports[`trasformCoverageReportToAudits > should handle empty coverage result 1`] = `[]`; - -exports[`trasformCoverageReportToAudits > should prioritize onlyAudits over skipAudits when both are provided 1`] = ` -[ - { - "details": { - "issues": [ - { - "message": "Missing documentation", - "severity": "warning", - "source": { - "file": "test.ts", - "position": { - "startLine": 10, - }, - }, - }, - ], - }, - "displayValue": "75 %", - "score": 0.75, - "slug": "functions-coverage", - "value": 75, - }, -] -`; - -exports[`trasformCoverageReportToAudits > should transform coverage report to audit outputs with no filters 1`] = ` -[ - { - "details": { - "issues": [ - { - "message": "Missing documentation", - "severity": "warning", - "source": { - "file": "test.ts", - "position": { - "startLine": 10, - }, - }, - }, - ], - }, - "displayValue": "75 %", - "score": 0.75, - "slug": "functions-coverage", - "value": 75, - }, - { - "details": { - "issues": [], - }, - "displayValue": "100 %", - "score": 1, - "slug": "classes-coverage", - "value": 100, - }, -] -`; diff --git a/packages/plugin-doc-coverage/src/lib/runner/doc-processer.integration.test.ts b/packages/plugin-doc-coverage/src/lib/runner/doc-processer.integration.test.ts index 00bdc83a7..0a93347f9 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/doc-processer.integration.test.ts +++ b/packages/plugin-doc-coverage/src/lib/runner/doc-processer.integration.test.ts @@ -1,23 +1,34 @@ import { processDocCoverage } from './doc-processer.js'; describe('processDocCoverage', () => { - it('should succesfully get the right number of ts files', () => { - const results = processDocCoverage({ - sourceGlob: [ - 'packages/plugin-doc-coverage/mocks/fixtures/angular/**/*.ts', - ], - }); - expect(results).toMatchSnapshot(); + const sourcePath = + 'packages/plugin-doc-coverage/mocks/fixtures/angular/**/*.ts'; + + it('should count total nodes from TypeScript files correctly', () => { + const expectedNodeCount = 8; + + const results = processDocCoverage({ sourceGlob: [sourcePath] }); + + const totalNodeCount = Object.values(results).reduce( + (acc, node) => acc + node.nodesCount, + 0, + ); + + expect(totalNodeCount).toBe(expectedNodeCount); }); - it('should succesfully get the right number of ts files and not include spec files', () => { + it('should count total nodes from TypeScript files correctly and not include spec files when specified', () => { + const expectedNodeCount = 7; + const results = processDocCoverage({ - sourceGlob: [ - 'packages/plugin-doc-coverage/mocks/fixtures/angular/**/*.ts', - '!**/*.spec.ts', - '!**/*.test.ts', - ], + sourceGlob: [sourcePath, '!**/*.spec.ts', '!**/*.test.ts'], }); - expect(results).toMatchSnapshot(); + + const totalNodeCount = Object.values(results).reduce( + (acc, node) => acc + node.nodesCount, + 0, + ); + + expect(totalNodeCount).toBe(expectedNodeCount); }); }); diff --git a/packages/plugin-doc-coverage/src/lib/runner/doc-processer.ts b/packages/plugin-doc-coverage/src/lib/runner/doc-processer.ts index c5d82cac7..d63c1f469 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/doc-processer.ts +++ b/packages/plugin-doc-coverage/src/lib/runner/doc-processer.ts @@ -6,13 +6,13 @@ import { } from 'ts-morph'; import type { DocCoveragePluginConfig } from '../config.js'; import type { + CoverageReportShape, CoverageResult, CoverageType, - UnprocessedCoverageResult, } from './models.js'; import { calculateCoverage, - createEmptyUnprocessedCoverageReport, + createEmptyCoverageData, getCoverageTypeFromKind, } from './utils.js'; @@ -56,7 +56,7 @@ export function processDocCoverage( /** * Gets the unprocessed coverage report from the source files * @param sourceFiles - The source files to process - * @returns {UnprocessedCoverageResult} The unprocessed coverage report + * @returns {CoverageReportShape} The unprocessed coverage report */ export function getUnprocessedCoverageReport( sourceFiles: SourceFile[], @@ -102,7 +102,7 @@ export function getUnprocessedCoverageReport( }, }; }, - createEmptyUnprocessedCoverageReport(), + createEmptyCoverageData(), ); return mergeCoverageResults( @@ -110,7 +110,7 @@ export function getUnprocessedCoverageReport( coverageReportOfCurrentFile, ); }, - createEmptyUnprocessedCoverageReport(), + createEmptyCoverageData(), ); return calculateCoverage(unprocessedCoverageReport); @@ -120,12 +120,12 @@ export function getUnprocessedCoverageReport( * Merges two coverage results * @param results - The first empty coverage result * @param current - The second coverage result - * @returns {UnprocessedCoverageResult} The merged coverage result + * @returns {CoverageReportShape} The merged coverage result */ export function mergeCoverageResults( - results: UnprocessedCoverageResult, - current: Partial, -): UnprocessedCoverageResult { + results: CoverageReportShape, + current: Partial, +): CoverageReportShape { return Object.fromEntries( Object.entries(results).map(([key, value]) => { const node = value as CoverageResult[CoverageType]; @@ -138,7 +138,7 @@ export function mergeCoverageResults( }, ]; }), - ) as UnprocessedCoverageResult; + ) as CoverageReportShape; } /** diff --git a/packages/plugin-doc-coverage/src/lib/runner/doc-processer.unit.test.ts b/packages/plugin-doc-coverage/src/lib/runner/doc-processer.unit.test.ts index dcd9ad31b..2c6fc0c17 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/doc-processer.unit.test.ts +++ b/packages/plugin-doc-coverage/src/lib/runner/doc-processer.unit.test.ts @@ -6,7 +6,7 @@ import { getVariablesInformation, mergeCoverageResults, } from './doc-processer.js'; -import type { UnprocessedCoverageResult } from './models.js'; +import type { CoverageReportShape } from './models.js'; describe('getUnprocessedCoverageReport', () => { it('should produce a full report', () => { @@ -72,7 +72,7 @@ describe('getUnprocessedCoverageReport', () => { }); describe('mergeCoverageResults', () => { - const emptyResult: UnprocessedCoverageResult = { + const emptyResult: CoverageReportShape = { enums: { nodesCount: 0, issues: [] }, interfaces: { nodesCount: 0, issues: [] }, types: { nodesCount: 0, issues: [] }, @@ -102,7 +102,7 @@ describe('mergeCoverageResults', () => { const results = mergeCoverageResults( emptyResult, - secondResult as Partial, + secondResult as Partial, ); expect(results).toStrictEqual( expect.objectContaining({ diff --git a/packages/plugin-doc-coverage/src/lib/runner/models.ts b/packages/plugin-doc-coverage/src/lib/runner/models.ts index c8119b4db..547efab56 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/models.ts +++ b/packages/plugin-doc-coverage/src/lib/runner/models.ts @@ -25,14 +25,14 @@ export type UndocumentedNode = { class?: string; }; -/** The coverage data is the data that is used to create the coverage report. Without coverage stats yet */ +/** The coverage data is the data that is used to create the coverage report. Without coverage stats. */ export type CoverageData = { issues: UndocumentedNode[]; nodesCount: number; }; -/** The unprocessed coverage result CoverageData but for each coverage type. */ -export type UnprocessedCoverageResult = Record; +/** The coverage report shape the report of every CoverageType without coverage stats. */ +export type CoverageReportShape = Record; /** The processed coverage result CoverageData but for each coverage type and with coverage stats. */ export type CoverageResult = Record< diff --git a/packages/plugin-doc-coverage/src/lib/runner/runner.ts b/packages/plugin-doc-coverage/src/lib/runner/runner.ts index 8bbf54066..1019687e8 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/runner.ts +++ b/packages/plugin-doc-coverage/src/lib/runner/runner.ts @@ -1,7 +1,7 @@ import type { AuditOutputs, RunnerFunction } from '@code-pushup/models'; import type { DocCoveragePluginConfig } from '../config.js'; import { processDocCoverage } from './doc-processer.js'; -import type { CoverageResult, CoverageType } from './models.js'; +import type { CoverageResult } from './models.js'; export function createRunnerFunction( config: DocCoveragePluginConfig, @@ -33,17 +33,16 @@ export function trasformCoverageReportToAudits( } return true; }) - .map(([type, items]) => { - const coverageType = type as CoverageType; - const coverage = items.coverage; + .map(([type, item]) => { + const { coverage } = item; return { - slug: `${coverageType}-coverage`, + slug: `${type}-coverage`, value: coverage, score: coverage / 100, displayValue: `${coverage} %`, details: { - issues: items.issues.map(({ file, line }) => ({ + issues: item.issues.map(({ file, line }) => ({ message: 'Missing documentation', source: { file, position: { startLine: line } }, severity: 'warning', diff --git a/packages/plugin-doc-coverage/src/lib/runner/runner.unit.test.ts b/packages/plugin-doc-coverage/src/lib/runner/runner.unit.test.ts index d81748ab3..aab40d08c 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/runner.unit.test.ts +++ b/packages/plugin-doc-coverage/src/lib/runner/runner.unit.test.ts @@ -18,71 +18,58 @@ describe('trasformCoverageReportToAudits', () => { classes: { coverage: 100, nodesCount: 2, - issues: [], + issues: [ + { + file: 'test.ts', + line: 10, + name: 'testClass', + type: 'classes', + }, + ], }, } as unknown as CoverageResult; - it('should transform coverage report to audit outputs with no filters', () => { + it('should return all audits from the coverage result when no filters are provided', () => { const result = trasformCoverageReportToAudits(mockCoverageResult, {}); - expect(result).toMatchSnapshot(); + expect(result.map(item => item.slug)).toStrictEqual([ + 'functions-coverage', + 'classes-coverage', + ]); }); it('should filter audits when onlyAudits is provided', () => { const result = trasformCoverageReportToAudits(mockCoverageResult, { onlyAudits: ['functions-coverage'], }); - expect(result).toMatchSnapshot(); + expect(result).toHaveLength(1); + expect(result.map(item => item.slug)).toStrictEqual(['functions-coverage']); }); it('should filter audits when skipAudits is provided', () => { const result = trasformCoverageReportToAudits(mockCoverageResult, { - skipAudits: ['classes-coverage'], + skipAudits: ['functions-coverage'], }); - expect(result).toMatchSnapshot(); + expect(result).toHaveLength(1); + expect(result.map(item => item.slug)).toStrictEqual(['classes-coverage']); }); - it('should handle empty coverage result', () => { + it('should handle properly empty coverage result', () => { const result = trasformCoverageReportToAudits( {} as unknown as CoverageResult, {}, ); - expect(result).toMatchSnapshot(); - }); - - it('should handle coverage result with multiple issues', () => { - const coverageWithMultipleIssues = { - functions: { - coverage: 50, - nodesCount: 4, - issues: [ - { - file: 'test1.ts', - line: 10, - name: 'function1', - type: 'functions', - }, - { - file: 'test2.ts', - line: 20, - name: 'function2', - type: 'functions', - }, - ], - }, - } as unknown as CoverageResult; - - const result = trasformCoverageReportToAudits( - coverageWithMultipleIssues, - {}, - ); - expect(result).toMatchSnapshot(); + expect(result).toEqual([]); }); - it('should prioritize onlyAudits over skipAudits when both are provided', () => { - const result = trasformCoverageReportToAudits(mockCoverageResult, { - onlyAudits: ['functions-coverage'], - skipAudits: ['functions-coverage'], - }); - expect(result).toMatchSnapshot(); + it('should handle coverage result with multiple issues and add them to the details.issue of the report', () => { + const expectedIssues = 2; + const result = trasformCoverageReportToAudits(mockCoverageResult, {}); + expect(result).toHaveLength(2); + expect( + result.reduce( + (acc, item) => acc + (item.details?.issues?.length ?? 0), + 0, + ), + ).toBe(expectedIssues); }); }); diff --git a/packages/plugin-doc-coverage/src/lib/runner/utils.ts b/packages/plugin-doc-coverage/src/lib/runner/utils.ts index 7b8807a2c..cdc202991 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/utils.ts +++ b/packages/plugin-doc-coverage/src/lib/runner/utils.ts @@ -1,15 +1,15 @@ import { SyntaxKind } from 'ts-morph'; import type { + CoverageReportShape, CoverageResult, CoverageType, - UnprocessedCoverageResult, } from './models.js'; /** * Creates an empty unprocessed coverage report. * @returns The empty unprocessed coverage report. */ -export function createEmptyUnprocessedCoverageReport(): UnprocessedCoverageResult { +export function createEmptyCoverageData(): CoverageReportShape { return { enums: { nodesCount: 0, issues: [] }, interfaces: { nodesCount: 0, issues: [] }, @@ -27,7 +27,7 @@ export function createEmptyUnprocessedCoverageReport(): UnprocessedCoverageResul * @param result - The unprocessed coverage result. * @returns The processed coverage result. */ -export function calculateCoverage(result: UnprocessedCoverageResult) { +export function calculateCoverage(result: CoverageReportShape) { return Object.fromEntries( Object.entries(result).map(([key, value]) => { const type = key as CoverageType; diff --git a/packages/plugin-doc-coverage/src/lib/runner/utils.unit.test.ts b/packages/plugin-doc-coverage/src/lib/runner/utils.unit.test.ts index 731d5280c..d45b1600c 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/utils.unit.test.ts +++ b/packages/plugin-doc-coverage/src/lib/runner/utils.unit.test.ts @@ -1,14 +1,14 @@ import { SyntaxKind } from 'ts-morph'; -import type { UnprocessedCoverageResult } from './models.js'; +import type { CoverageReportShape } from './models.js'; import { calculateCoverage, - createEmptyUnprocessedCoverageReport, + createEmptyCoverageData, getCoverageTypeFromKind, } from './utils.js'; -describe('createEmptyUnprocessedCoverageReport', () => { +describe('createEmptyCoverageData', () => { it('should create an empty report with all categories initialized', () => { - const result = createEmptyUnprocessedCoverageReport(); + const result = createEmptyCoverageData(); expect(result).toStrictEqual({ enums: { nodesCount: 0, issues: [] }, @@ -25,8 +25,7 @@ describe('createEmptyUnprocessedCoverageReport', () => { describe('calculateCoverage', () => { it('should calculate 100% coverage when there are no nodes', () => { - const input: UnprocessedCoverageResult = - createEmptyUnprocessedCoverageReport(); + const input = createEmptyCoverageData(); const result = calculateCoverage(input); Object.values(result).forEach(category => { @@ -37,8 +36,8 @@ describe('calculateCoverage', () => { }); it('should calculate correct coverage percentage with issues', () => { - const input: UnprocessedCoverageResult = { - ...createEmptyUnprocessedCoverageReport(), + const input: CoverageReportShape = { + ...createEmptyCoverageData(), functions: { nodesCount: 4, issues: [ From 6c83208016f9e7c922823e13ade088c5efc1854f Mon Sep 17 00:00:00 2001 From: Alejandro Date: Sun, 22 Dec 2024 15:46:14 +0100 Subject: [PATCH 15/66] chore(plugin-doc-coverage): remove extra line From 8c2d274074e30d151a495cc37ea8e8083acdc809 Mon Sep 17 00:00:00 2001 From: Alejandro Date: Sun, 22 Dec 2024 15:46:55 +0100 Subject: [PATCH 16/66] chore: put back plugin config --- code-pushup.config.ts | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/code-pushup.config.ts b/code-pushup.config.ts index 6b2ea8dc4..699aee3c3 100644 --- a/code-pushup.config.ts +++ b/code-pushup.config.ts @@ -1,6 +1,12 @@ import 'dotenv/config'; import { z } from 'zod'; -import { docCoverageCoreConfig } from './code-pushup.preset.js'; +import { + coverageCoreConfigNx, + docCoverageCoreConfig, + eslintCoreConfigNx, + jsPackagesCoreConfig, + lighthouseCoreConfig, +} from './code-pushup.preset.js'; import type { CoreConfig } from './packages/models/src/index.js'; import { mergeConfigs } from './packages/utils/src/index.js'; @@ -27,13 +33,13 @@ const config: CoreConfig = { }; export default mergeConfigs( - // config, - // await coverageCoreConfigNx(), - // await jsPackagesCoreConfig(), - // await lighthouseCoreConfig( - // 'https://github.com/code-pushup/cli?tab=readme-ov-file#code-pushup-cli/', - // ), - // await eslintCoreConfigNx(), + config, + await coverageCoreConfigNx(), + await jsPackagesCoreConfig(), + await lighthouseCoreConfig( + 'https://github.com/code-pushup/cli?tab=readme-ov-file#code-pushup-cli/', + ), + await eslintCoreConfigNx(), await docCoverageCoreConfig({ sourceGlob: [ 'packages/**/src/**/*.ts', From a70c03b23bd5bcd850b069d440262d2004ddac9a Mon Sep 17 00:00:00 2001 From: Alejandro Date: Sun, 22 Dec 2024 15:49:36 +0100 Subject: [PATCH 17/66] chore: fix plugin config, remove unused snapshot --- code-pushup.config.ts | 2 +- .../runner/doc-processer.unit.test.ts.snap | 125 ------------------ 2 files changed, 1 insertion(+), 126 deletions(-) delete mode 100644 packages/plugin-doc-coverage/src/lib/runner/doc-processer.unit.test.ts.snap diff --git a/code-pushup.config.ts b/code-pushup.config.ts index 699aee3c3..a0a33cfee 100644 --- a/code-pushup.config.ts +++ b/code-pushup.config.ts @@ -48,6 +48,6 @@ export default mergeConfigs( '!**/implementation/**', '!**/internal/**', ], - skipAudits: ['methodawdawdds-coverage'], + skipAudits: ['methods-coverage'], }), ); diff --git a/packages/plugin-doc-coverage/src/lib/runner/doc-processer.unit.test.ts.snap b/packages/plugin-doc-coverage/src/lib/runner/doc-processer.unit.test.ts.snap deleted file mode 100644 index 220467a98..000000000 --- a/packages/plugin-doc-coverage/src/lib/runner/doc-processer.unit.test.ts.snap +++ /dev/null @@ -1,125 +0,0 @@ -{ - "classes": { - "coverage": 0, - "issues": [ - { - "file": "test.ts", - "line": 4, - "name": "test", - "type": "classes", - }, - { - "file": "test.ts", - "line": 5, - "name": "test", - "type": "classes", - }, - { - "file": "test.ts", - "line": 6, - "name": "test", - "type": "classes", - }, - ], - "nodesCount": 3, - }, - "enums": { - "coverage": 0, - "issues": [ - { - "file": "test.ts", - "line": 7, - "name": "test", - "type": "enums", - }, - { - "file": "test.ts", - "line": 8, - "name": "test", - "type": "enums", - }, - { - "file": "test.ts", - "line": 9, - "name": "test", - "type": "enums", - }, - ], - "nodesCount": 3, - }, - "functions": { - "coverage": 66.66666666666667, - "issues": [ - { - "file": "test.ts", - "line": 3, - "name": "test", - "type": "functions", - }, - ], - "nodesCount": 3, - }, - "interfaces": { - "coverage": 0, - "issues": [ - { - "file": "test.ts", - "line": 13, - "name": "test", - "type": "interfaces", - }, - { - "file": "test.ts", - "line": 14, - "name": "test", - "type": "interfaces", - }, - { - "file": "test.ts", - "line": 15, - "name": "test", - "type": "interfaces", - }, - ], - "nodesCount": 3, - }, - "methods": { - "coverage": 100, - "issues": [], - "nodesCount": 0, - }, - "properties": { - "coverage": 100, - "issues": [], - "nodesCount": 0, - }, - "types": { - "coverage": 0, - "issues": [ - { - "file": "test.ts", - "line": 10, - "name": "test", - "type": "types", - }, - { - "file": "test.ts", - "line": 11, - "name": "test", - "type": "types", - }, - { - "file": "test.ts", - "line": 12, - "name": "test", - "type": "types", - }, - ], - "nodesCount": 3, - }, - "variables": { - "coverage": 100, - "issues": [], - "nodesCount": 0, - }, -} \ No newline at end of file From 6be3803c67b505a27950fab13b0ba9a10ce038e5 Mon Sep 17 00:00:00 2001 From: Alejandro Date: Sun, 22 Dec 2024 16:04:43 +0100 Subject: [PATCH 18/66] chore(plugin-doc-coverage): stuff of the pr and rename file --- ...mock.ts => source-files-mock.generator.ts} | 0 .../plugin-doc-coverage/src/lib/config.ts | 17 +- .../src/lib/config.unit.test.ts | 156 +++++++++--------- .../src/lib/runner/doc-processer.unit.test.ts | 5 +- 4 files changed, 96 insertions(+), 82 deletions(-) rename packages/plugin-doc-coverage/mocks/{source-files.mock.ts => source-files-mock.generator.ts} (100%) diff --git a/packages/plugin-doc-coverage/mocks/source-files.mock.ts b/packages/plugin-doc-coverage/mocks/source-files-mock.generator.ts similarity index 100% rename from packages/plugin-doc-coverage/mocks/source-files.mock.ts rename to packages/plugin-doc-coverage/mocks/source-files-mock.generator.ts diff --git a/packages/plugin-doc-coverage/src/lib/config.ts b/packages/plugin-doc-coverage/src/lib/config.ts index 648420a1d..1204ca7f7 100644 --- a/packages/plugin-doc-coverage/src/lib/config.ts +++ b/packages/plugin-doc-coverage/src/lib/config.ts @@ -2,11 +2,22 @@ import { z } from 'zod'; export const docCoveragePluginConfigSchema = z .object({ - skipAudits: z.array(z.string()).optional(), - onlyAudits: z.array(z.string()).optional(), + skipAudits: z + .array(z.string()) + .optional() + .describe( + 'List of audit slugs to exclude from evaluation. When specified, all audits except these will be evaluated.', + ), + onlyAudits: z + .array(z.string()) + .optional() + .describe( + 'List of audit slugs to evaluate. When specified, only these audits will be evaluated.', + ), sourceGlob: z .array(z.string()) - .default(['src/**/*.{ts,tsx}', '!**/*.spec.ts', '!**/*.test.ts']), + .default(['src/**/*.{ts,tsx}', '!**/*.spec.ts', '!**/*.test.ts']) + .describe('Glob pattern to match source files to evaluate.'), }) .refine(data => !(data.skipAudits && data.onlyAudits), { message: "You can't define 'skipAudits' and 'onlyAudits' simultaneously", diff --git a/packages/plugin-doc-coverage/src/lib/config.unit.test.ts b/packages/plugin-doc-coverage/src/lib/config.unit.test.ts index 1d28d2a7d..f9f777d15 100644 --- a/packages/plugin-doc-coverage/src/lib/config.unit.test.ts +++ b/packages/plugin-doc-coverage/src/lib/config.unit.test.ts @@ -5,6 +5,15 @@ import { } from './config.js'; describe('docCoveragePluginConfigSchema', () => { + it('accepts a complete valid configuration', () => { + expect(() => + docCoveragePluginConfigSchema.parse({ + sourceGlob: ['src/**/*.ts'], + onlyAudits: ['functions-coverage'], + } satisfies DocCoveragePluginConfig), + ).not.toThrow(); + }); + it('throws when skipAudits and onlyAudits are defined', () => { expect(() => docCoveragePluginConfigSchema.parse({ @@ -13,100 +22,91 @@ describe('docCoveragePluginConfigSchema', () => { }), ).toThrow("You can't define 'skipAudits' and 'onlyAudits' simultaneously"); }); +}); - describe('sourceGlob', () => { - it('accepts a valid source glob pattern', () => { - expect(() => - docCoveragePluginConfigSchema.parse({ - sourceGlob: ['src/**/*.{ts,tsx}', '!**/*.spec.ts', '!**/*.test.ts'], - } satisfies DocCoveragePluginConfig), - ).not.toThrow(); - }); +describe('sourceGlob', () => { + it('accepts a valid source glob pattern', () => { + expect(() => + docCoveragePluginConfigSchema.parse({ + sourceGlob: ['src/**/*.{ts,tsx}', '!**/*.spec.ts', '!**/*.test.ts'], + } satisfies DocCoveragePluginConfig), + ).not.toThrow(); + }); - it('uses default value for missing sourceGlob', () => { - const result = docCoveragePluginConfigSchema.parse({}); - expect(result.sourceGlob).toEqual([ - 'src/**/*.{ts,tsx}', - '!**/*.spec.ts', - '!**/*.test.ts', - ]); - }); + it('uses default value for missing sourceGlob', () => { + const result = docCoveragePluginConfigSchema.parse({}); + expect(result.sourceGlob).toEqual([ + 'src/**/*.{ts,tsx}', + '!**/*.spec.ts', + '!**/*.test.ts', + ]); + }); - it('throws for invalid sourceGlob type', () => { - expect(() => - docCoveragePluginConfigSchema.parse({ - sourceGlob: 123, - }), - ).toThrow('Expected array'); - }); + it('throws for invalid sourceGlob type', () => { + expect(() => + docCoveragePluginConfigSchema.parse({ + sourceGlob: 123, + }), + ).toThrow('Expected array'); }); +}); - it('accepts a complete valid configuration', () => { +describe('onlyAudits', () => { + it('accepts valid audit slugs array', () => { expect(() => docCoveragePluginConfigSchema.parse({ + onlyAudits: ['functions-coverage', 'classes-coverage'], sourceGlob: ['src/**/*.ts'], - onlyAudits: ['functions-coverage'], - } satisfies DocCoveragePluginConfig), + }), ).not.toThrow(); }); - describe('onlyAudits', () => { - it('accepts valid audit slugs array', () => { - expect(() => - docCoveragePluginConfigSchema.parse({ - onlyAudits: ['functions-coverage', 'classes-coverage'], - sourceGlob: ['src/**/*.ts'], - }), - ).not.toThrow(); - }); - - it('accepts empty array for onlyAudits', () => { - expect(() => - docCoveragePluginConfigSchema.parse({ - onlyAudits: [], - sourceGlob: ['src/**/*.ts'], - }), - ).not.toThrow(); - }); + it('accepts empty array for onlyAudits', () => { + expect(() => + docCoveragePluginConfigSchema.parse({ + onlyAudits: [], + sourceGlob: ['src/**/*.ts'], + }), + ).not.toThrow(); + }); - it('allows onlyAudits to be undefined', () => { - const result = docCoveragePluginConfigSchema.parse({}); - expect(result.onlyAudits).toBeUndefined(); - }); + it('allows onlyAudits to be undefined', () => { + const result = docCoveragePluginConfigSchema.parse({}); + expect(result.onlyAudits).toBeUndefined(); + }); - it('throws for invalid onlyAudits type', () => { - expect(() => - docCoveragePluginConfigSchema.parse({ - onlyAudits: 'functions-coverage', - }), - ).toThrow('Expected array'); - }); + it('throws for invalid onlyAudits type', () => { + expect(() => + docCoveragePluginConfigSchema.parse({ + onlyAudits: 'functions-coverage', + }), + ).toThrow('Expected array'); + }); - it('throws for array with non-string elements', () => { - expect(() => - docCoveragePluginConfigSchema.parse({ - onlyAudits: [123, true], - }), - ).toThrow('Expected string'); - }); + it('throws for array with non-string elements', () => { + expect(() => + docCoveragePluginConfigSchema.parse({ + onlyAudits: [123, true], + }), + ).toThrow('Expected string'); }); +}); - describe('skipAudits', () => { - it('accepts valid audit slugs array', () => { - expect(() => - docCoveragePluginConfigSchema.parse({ - skipAudits: ['functions-coverage', 'classes-coverage'], - sourceGlob: ['src/**/*.ts'], - }), - ).not.toThrow(); - }); +describe('skipAudits', () => { + it('accepts valid audit slugs array', () => { + expect(() => + docCoveragePluginConfigSchema.parse({ + skipAudits: ['functions-coverage', 'classes-coverage'], + sourceGlob: ['src/**/*.ts'], + }), + ).not.toThrow(); + }); - it('throws for array with non-string elements', () => { - expect(() => - docCoveragePluginConfigSchema.parse({ - skipAudits: [123, true], - }), - ).toThrow('Expected string'); - }); + it('throws for array with non-string elements', () => { + expect(() => + docCoveragePluginConfigSchema.parse({ + skipAudits: [123, true], + }), + ).toThrow('Expected string'); }); }); diff --git a/packages/plugin-doc-coverage/src/lib/runner/doc-processer.unit.test.ts b/packages/plugin-doc-coverage/src/lib/runner/doc-processer.unit.test.ts index 2c6fc0c17..3db7abbb7 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/doc-processer.unit.test.ts +++ b/packages/plugin-doc-coverage/src/lib/runner/doc-processer.unit.test.ts @@ -1,5 +1,8 @@ import type { ClassDeclaration, VariableStatement } from 'ts-morph'; -import { nodeMock, sourceFileMock } from '../../../mocks/source-files.mock'; +import { + nodeMock, + sourceFileMock, +} from '../../../mocks/source-files-mock.generator'; import { getClassNodes, getUnprocessedCoverageReport, From 2f2972848b2ff62bdc9ba0079aa67a3a9ebd3d56 Mon Sep 17 00:00:00 2001 From: Alejandro <49059458+aramirezj@users.noreply.github.com> Date: Mon, 23 Dec 2024 11:29:44 +0100 Subject: [PATCH 19/66] Update code-pushup.config.ts Co-authored-by: Michael Hladky <10064416+BioPhoton@users.noreply.github.com> --- code-pushup.config.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/code-pushup.config.ts b/code-pushup.config.ts index a0a33cfee..ebaaf803f 100644 --- a/code-pushup.config.ts +++ b/code-pushup.config.ts @@ -43,8 +43,7 @@ export default mergeConfigs( await docCoverageCoreConfig({ sourceGlob: [ 'packages/**/src/**/*.ts', - '!**/*.spec.ts', - '!**/*.test.ts', + '!**/*.{spec,test}.ts' '!**/implementation/**', '!**/internal/**', ], From adec386a93e67a062017007464213d0e477ad0a7 Mon Sep 17 00:00:00 2001 From: Alejandro <49059458+aramirezj@users.noreply.github.com> Date: Mon, 23 Dec 2024 11:29:53 +0100 Subject: [PATCH 20/66] Update packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.unit.test.ts Co-authored-by: Michael Hladky <10064416+BioPhoton@users.noreply.github.com> --- .../src/lib/doc-coverage-plugin.unit.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.unit.test.ts b/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.unit.test.ts index fea278277..adf079fc4 100644 --- a/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.unit.test.ts +++ b/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.unit.test.ts @@ -16,7 +16,7 @@ vi.mock('./runner/index.ts', () => ({ })); describe('docCoveragePlugin', () => { - it('should initialise a Documentation coverage plugin', async () => { + it('should create a valid plugin config', async () => { await expect( docCoveragePlugin({ sourceGlob: ['src/**/*.ts', '!**/*.spec.ts', '!**/*.test.ts'], From 47cff4bb8874f85651e246d6dca31fe2f5cd5b70 Mon Sep 17 00:00:00 2001 From: Alejandro <49059458+aramirezj@users.noreply.github.com> Date: Mon, 23 Dec 2024 11:30:00 +0100 Subject: [PATCH 21/66] Update packages/plugin-doc-coverage/src/lib/runner/doc-processer.integration.test.ts Co-authored-by: Michael Hladky <10064416+BioPhoton@users.noreply.github.com> --- .../src/lib/runner/doc-processer.integration.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/plugin-doc-coverage/src/lib/runner/doc-processer.integration.test.ts b/packages/plugin-doc-coverage/src/lib/runner/doc-processer.integration.test.ts index 0a93347f9..539c3af7d 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/doc-processer.integration.test.ts +++ b/packages/plugin-doc-coverage/src/lib/runner/doc-processer.integration.test.ts @@ -17,7 +17,7 @@ describe('processDocCoverage', () => { expect(totalNodeCount).toBe(expectedNodeCount); }); - it('should count total nodes from TypeScript files correctly and not include spec files when specified', () => { + it('respect `sourceGlob` and only include matching files', () => { const expectedNodeCount = 7; const results = processDocCoverage({ From c644029b858f1469f5662db1f81c2f751a18847b Mon Sep 17 00:00:00 2001 From: Alejandro <49059458+aramirezj@users.noreply.github.com> Date: Mon, 23 Dec 2024 11:30:21 +0100 Subject: [PATCH 22/66] Update packages/plugin-doc-coverage/src/lib/constants.ts Co-authored-by: Michael Hladky <10064416+BioPhoton@users.noreply.github.com> --- packages/plugin-doc-coverage/src/lib/constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/plugin-doc-coverage/src/lib/constants.ts b/packages/plugin-doc-coverage/src/lib/constants.ts index d5d0d6b38..ddcc1471f 100644 --- a/packages/plugin-doc-coverage/src/lib/constants.ts +++ b/packages/plugin-doc-coverage/src/lib/constants.ts @@ -12,7 +12,7 @@ export const AUDITS_MAP: Record = { 'methods-coverage': { slug: 'methods-coverage', title: 'Methods coverage', - description: 'Coverage of methods', + description: 'Documentation coverage of methods', }, 'functions-coverage': { slug: 'functions-coverage', From 09d9eb0ff02c34a1d0116071a9f41a5b7591f6cc Mon Sep 17 00:00:00 2001 From: Alejandro <49059458+aramirezj@users.noreply.github.com> Date: Mon, 23 Dec 2024 11:30:48 +0100 Subject: [PATCH 23/66] Update packages/plugin-doc-coverage/src/lib/constants.ts Co-authored-by: Michael Hladky <10064416+BioPhoton@users.noreply.github.com> --- packages/plugin-doc-coverage/src/lib/constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/plugin-doc-coverage/src/lib/constants.ts b/packages/plugin-doc-coverage/src/lib/constants.ts index ddcc1471f..a09d463a9 100644 --- a/packages/plugin-doc-coverage/src/lib/constants.ts +++ b/packages/plugin-doc-coverage/src/lib/constants.ts @@ -22,7 +22,7 @@ export const AUDITS_MAP: Record = { 'interfaces-coverage': { slug: 'interfaces-coverage', title: 'Interfaces coverage', - description: 'Coverage of interfaces', + description: 'Documentation coverage of interfaces', }, 'variables-coverage': { slug: 'variables-coverage', From 39019550ae05d7d8e8d6088a35cff55b2ceb1ac5 Mon Sep 17 00:00:00 2001 From: Alejandro <49059458+aramirezj@users.noreply.github.com> Date: Mon, 23 Dec 2024 11:30:55 +0100 Subject: [PATCH 24/66] Update packages/plugin-doc-coverage/src/lib/constants.ts Co-authored-by: Michael Hladky <10064416+BioPhoton@users.noreply.github.com> --- packages/plugin-doc-coverage/src/lib/constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/plugin-doc-coverage/src/lib/constants.ts b/packages/plugin-doc-coverage/src/lib/constants.ts index a09d463a9..0db20d774 100644 --- a/packages/plugin-doc-coverage/src/lib/constants.ts +++ b/packages/plugin-doc-coverage/src/lib/constants.ts @@ -32,7 +32,7 @@ export const AUDITS_MAP: Record = { 'properties-coverage': { slug: 'properties-coverage', title: 'Properties coverage', - description: 'Coverage of properties', + description: 'Documentation coverage of properties', }, 'types-coverage': { slug: 'types-coverage', From 11ad4bbb73a38d1975b33a7a6947d05f508e2001 Mon Sep 17 00:00:00 2001 From: Alejandro <49059458+aramirezj@users.noreply.github.com> Date: Mon, 23 Dec 2024 11:31:03 +0100 Subject: [PATCH 25/66] Update packages/plugin-doc-coverage/src/lib/constants.ts Co-authored-by: Michael Hladky <10064416+BioPhoton@users.noreply.github.com> --- packages/plugin-doc-coverage/src/lib/constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/plugin-doc-coverage/src/lib/constants.ts b/packages/plugin-doc-coverage/src/lib/constants.ts index 0db20d774..a635afe66 100644 --- a/packages/plugin-doc-coverage/src/lib/constants.ts +++ b/packages/plugin-doc-coverage/src/lib/constants.ts @@ -37,7 +37,7 @@ export const AUDITS_MAP: Record = { 'types-coverage': { slug: 'types-coverage', title: 'Types coverage', - description: 'Coverage of types', + description: 'Documentation coverage of types', }, 'enums-coverage': { slug: 'enums-coverage', From 19d43a504dfc6150e1a11ad2b1adf755898c8780 Mon Sep 17 00:00:00 2001 From: Alejandro <49059458+aramirezj@users.noreply.github.com> Date: Mon, 23 Dec 2024 11:31:13 +0100 Subject: [PATCH 26/66] Update packages/plugin-doc-coverage/src/lib/constants.ts Co-authored-by: Michael Hladky <10064416+BioPhoton@users.noreply.github.com> --- packages/plugin-doc-coverage/src/lib/constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/plugin-doc-coverage/src/lib/constants.ts b/packages/plugin-doc-coverage/src/lib/constants.ts index a635afe66..55f41d47f 100644 --- a/packages/plugin-doc-coverage/src/lib/constants.ts +++ b/packages/plugin-doc-coverage/src/lib/constants.ts @@ -42,7 +42,7 @@ export const AUDITS_MAP: Record = { 'enums-coverage': { slug: 'enums-coverage', title: 'Enums coverage', - description: 'Coverage of enums', + description: 'Documentation coverage of enums', }, } as const; From 49e5b56e43f82c2c304a9315f8c6358a5906f765 Mon Sep 17 00:00:00 2001 From: Alejandro <49059458+aramirezj@users.noreply.github.com> Date: Mon, 23 Dec 2024 11:31:20 +0100 Subject: [PATCH 27/66] Update packages/plugin-doc-coverage/README.md Co-authored-by: Michael Hladky <10064416+BioPhoton@users.noreply.github.com> --- packages/plugin-doc-coverage/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/plugin-doc-coverage/README.md b/packages/plugin-doc-coverage/README.md index 59a039c9d..08b4cba51 100644 --- a/packages/plugin-doc-coverage/README.md +++ b/packages/plugin-doc-coverage/README.md @@ -11,7 +11,9 @@ It analyzes your codebase and checks for documentation on different code element Measured documentation types are mapped to Code PushUp audits in the following way: -- The value is in range 0-100 and represents the documentation coverage for all passed results (_documented / total_) +- `value`: The value is the number of undocumented nodes -> 4 +- `displayValue`: `${value} undocumented ${type}` -> 4 undocumented functions +- `score`: 0.5 -> total nodes 8 undocumented 4 -> 8/4 - The score is value converted to 0-1 range - Missing documentation is mapped to issues in the audit details (undocumented classes, functions, interfaces, etc.) From 54d162118cffbe7d72cb93319f8ffd5dbb329d7e Mon Sep 17 00:00:00 2001 From: Alejandro <49059458+aramirezj@users.noreply.github.com> Date: Mon, 23 Dec 2024 11:31:29 +0100 Subject: [PATCH 28/66] Update packages/plugin-doc-coverage/README.md Co-authored-by: Michael Hladky <10064416+BioPhoton@users.noreply.github.com> --- packages/plugin-doc-coverage/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/plugin-doc-coverage/README.md b/packages/plugin-doc-coverage/README.md index 08b4cba51..842730ab5 100644 --- a/packages/plugin-doc-coverage/README.md +++ b/packages/plugin-doc-coverage/README.md @@ -35,7 +35,7 @@ Measured documentation types are mapped to Code PushUp audits in the following w pnpm add --save-dev @code-pushup/doc-coverage-plugin ``` -3. Add this plugin to the `plugins` array in your Code PushUp CLI config file (e.g. `code-pushup.config.js`). +3. Add this plugin to the `plugins` array in your Code PushUp CLI config file (e.g. `code-pushup.config.ts`). Pass the target files to analyze and optionally specify which types of documentation you want to track. You can skip for example tests by defining in the sourceGlob the path to the tests folder or pattern to match the tests files with the `!` symbol. From b3105aefd455b4f1f151f948ba5b4d6dcb00e8a0 Mon Sep 17 00:00:00 2001 From: Alejandro <49059458+aramirezj@users.noreply.github.com> Date: Mon, 23 Dec 2024 11:31:39 +0100 Subject: [PATCH 29/66] Update packages/plugin-doc-coverage/src/lib/constants.ts Co-authored-by: Michael Hladky <10064416+BioPhoton@users.noreply.github.com> --- packages/plugin-doc-coverage/src/lib/constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/plugin-doc-coverage/src/lib/constants.ts b/packages/plugin-doc-coverage/src/lib/constants.ts index 55f41d47f..4ea1e9a14 100644 --- a/packages/plugin-doc-coverage/src/lib/constants.ts +++ b/packages/plugin-doc-coverage/src/lib/constants.ts @@ -27,7 +27,7 @@ export const AUDITS_MAP: Record = { 'variables-coverage': { slug: 'variables-coverage', title: 'Variables coverage', - description: 'Coverage of variables', + description: 'Documentation coverage of variables', }, 'properties-coverage': { slug: 'properties-coverage', From 89893a05659d19fd218e4adf4c3df5f1109dfeed Mon Sep 17 00:00:00 2001 From: Alejandro <49059458+aramirezj@users.noreply.github.com> Date: Mon, 23 Dec 2024 11:31:47 +0100 Subject: [PATCH 30/66] Update packages/plugin-doc-coverage/src/lib/constants.ts Co-authored-by: Michael Hladky <10064416+BioPhoton@users.noreply.github.com> --- packages/plugin-doc-coverage/src/lib/constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/plugin-doc-coverage/src/lib/constants.ts b/packages/plugin-doc-coverage/src/lib/constants.ts index 4ea1e9a14..cbd09b671 100644 --- a/packages/plugin-doc-coverage/src/lib/constants.ts +++ b/packages/plugin-doc-coverage/src/lib/constants.ts @@ -17,7 +17,7 @@ export const AUDITS_MAP: Record = { 'functions-coverage': { slug: 'functions-coverage', title: 'Functions coverage', - description: 'Coverage of functions', + description: 'Documentation coverage of functions', }, 'interfaces-coverage': { slug: 'interfaces-coverage', From 97d32bfc41316c8cf959756f47d4f900dac750fa Mon Sep 17 00:00:00 2001 From: Alejandro <49059458+aramirezj@users.noreply.github.com> Date: Mon, 23 Dec 2024 11:33:50 +0100 Subject: [PATCH 31/66] Update packages/plugin-doc-coverage/src/lib/config.unit.test.ts Co-authored-by: Michael Hladky <10064416+BioPhoton@users.noreply.github.com> --- packages/plugin-doc-coverage/src/lib/config.unit.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/plugin-doc-coverage/src/lib/config.unit.test.ts b/packages/plugin-doc-coverage/src/lib/config.unit.test.ts index f9f777d15..fe73aafce 100644 --- a/packages/plugin-doc-coverage/src/lib/config.unit.test.ts +++ b/packages/plugin-doc-coverage/src/lib/config.unit.test.ts @@ -5,7 +5,7 @@ import { } from './config.js'; describe('docCoveragePluginConfigSchema', () => { - it('accepts a complete valid configuration', () => { + it('accepts a valid configuration', () => { expect(() => docCoveragePluginConfigSchema.parse({ sourceGlob: ['src/**/*.ts'], From 79b6a027058446939d9ab267cdbb79a9f047693b Mon Sep 17 00:00:00 2001 From: Alejandro <49059458+aramirezj@users.noreply.github.com> Date: Mon, 23 Dec 2024 11:34:04 +0100 Subject: [PATCH 32/66] Update packages/plugin-doc-coverage/src/lib/config.unit.test.ts Co-authored-by: Michael Hladky <10064416+BioPhoton@users.noreply.github.com> --- packages/plugin-doc-coverage/src/lib/config.unit.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/plugin-doc-coverage/src/lib/config.unit.test.ts b/packages/plugin-doc-coverage/src/lib/config.unit.test.ts index fe73aafce..f58d7fa73 100644 --- a/packages/plugin-doc-coverage/src/lib/config.unit.test.ts +++ b/packages/plugin-doc-coverage/src/lib/config.unit.test.ts @@ -52,7 +52,7 @@ describe('sourceGlob', () => { }); describe('onlyAudits', () => { - it('accepts valid audit slugs array', () => { + it('accepts a valid `onlyAudits` array', () => { expect(() => docCoveragePluginConfigSchema.parse({ onlyAudits: ['functions-coverage', 'classes-coverage'], From fd3bd5eb9d691ea2b734365795e2ecb43430ed62 Mon Sep 17 00:00:00 2001 From: Alejandro Date: Mon, 23 Dec 2024 12:03:37 +0100 Subject: [PATCH 33/66] chore(plugin-doc-coverage): add js files for integration test, remove unused dependency, new test --- .../mocks/fixtures/react/component.js | 10 + .../mocks/source-files-mock.generator.ts | 2 +- packages/plugin-doc-coverage/package.json | 5 - .../src/lib/config.unit.test.ts | 206 ++++++++++-------- .../src/lib/doc-coverage-plugin.unit.test.ts | 52 ++++- .../doc-processer.unit.test.ts.snap | 2 +- .../runner/doc-processer.integration.test.ts | 37 +++- .../src/lib/runner/doc-processer.ts | 47 ++-- .../src/lib/runner/doc-processer.unit.test.ts | 34 +-- .../src/lib/runner/models.ts | 14 +- .../src/lib/runner/runner.ts | 4 +- .../src/lib/runner/runner.unit.test.ts | 6 +- .../src/lib/runner/utils.ts | 10 +- .../src/lib/runner/utils.unit.test.ts | 4 +- 14 files changed, 266 insertions(+), 167 deletions(-) create mode 100644 packages/plugin-doc-coverage/mocks/fixtures/react/component.js diff --git a/packages/plugin-doc-coverage/mocks/fixtures/react/component.js b/packages/plugin-doc-coverage/mocks/fixtures/react/component.js new file mode 100644 index 000000000..dfa1336e3 --- /dev/null +++ b/packages/plugin-doc-coverage/mocks/fixtures/react/component.js @@ -0,0 +1,10 @@ +function MyComponent() { + return ( +
+

Hello World

+

This is a basic React component

+
+ ); +} + +export default MyComponent; diff --git a/packages/plugin-doc-coverage/mocks/source-files-mock.generator.ts b/packages/plugin-doc-coverage/mocks/source-files-mock.generator.ts index 5a7ba13aa..5d700db3c 100644 --- a/packages/plugin-doc-coverage/mocks/source-files-mock.generator.ts +++ b/packages/plugin-doc-coverage/mocks/source-files-mock.generator.ts @@ -8,7 +8,7 @@ import { TypeAliasDeclaration, VariableStatement, } from 'ts-morph'; -import type { CoverageType } from '../src/lib/runner/models'; +import type { CoverageType } from '../src/lib/runner/models.js'; export function sourceFileMock( file: string, diff --git a/packages/plugin-doc-coverage/package.json b/packages/plugin-doc-coverage/package.json index 4d5fc0c60..20f0b4f52 100644 --- a/packages/plugin-doc-coverage/package.json +++ b/packages/plugin-doc-coverage/package.json @@ -38,10 +38,5 @@ "@code-pushup/models": "0.57.0", "zod": "^3.22.4", "ts-morph": "^24.0.0" - }, - "peerDependenciesMeta": { - "@nx/devkit": { - "optional": true - } } } diff --git a/packages/plugin-doc-coverage/src/lib/config.unit.test.ts b/packages/plugin-doc-coverage/src/lib/config.unit.test.ts index f58d7fa73..83449ecce 100644 --- a/packages/plugin-doc-coverage/src/lib/config.unit.test.ts +++ b/packages/plugin-doc-coverage/src/lib/config.unit.test.ts @@ -4,109 +4,135 @@ import { docCoveragePluginConfigSchema, } from './config.js'; -describe('docCoveragePluginConfigSchema', () => { - it('accepts a valid configuration', () => { - expect(() => - docCoveragePluginConfigSchema.parse({ - sourceGlob: ['src/**/*.ts'], - onlyAudits: ['functions-coverage'], - } satisfies DocCoveragePluginConfig), - ).not.toThrow(); - }); +describe('DocCoveragePlugin Configuration', () => { + describe('docCoveragePluginConfigSchema', () => { + it('accepts a valid configuration', () => { + expect(() => + docCoveragePluginConfigSchema.parse({ + sourceGlob: ['src/**/*.ts'], + onlyAudits: ['functions-coverage'], + } satisfies DocCoveragePluginConfig), + ).not.toThrow(); + }); - it('throws when skipAudits and onlyAudits are defined', () => { - expect(() => - docCoveragePluginConfigSchema.parse({ - skipAudits: ['functions-coverage'], - onlyAudits: ['classes-coverage'], - }), - ).toThrow("You can't define 'skipAudits' and 'onlyAudits' simultaneously"); + it('throws when skipAudits and onlyAudits are defined', () => { + expect(() => + docCoveragePluginConfigSchema.parse({ + skipAudits: ['functions-coverage'], + onlyAudits: ['classes-coverage'], + }), + ).toThrow( + "You can't define 'skipAudits' and 'onlyAudits' simultaneously", + ); + }); }); -}); -describe('sourceGlob', () => { - it('accepts a valid source glob pattern', () => { - expect(() => - docCoveragePluginConfigSchema.parse({ - sourceGlob: ['src/**/*.{ts,tsx}', '!**/*.spec.ts', '!**/*.test.ts'], - } satisfies DocCoveragePluginConfig), - ).not.toThrow(); - }); + describe('sourceGlob', () => { + it('accepts a valid source glob pattern', () => { + expect(() => + docCoveragePluginConfigSchema.parse({ + sourceGlob: ['src/**/*.{ts,tsx}', '!**/*.spec.ts', '!**/*.test.ts'], + } satisfies DocCoveragePluginConfig), + ).not.toThrow(); + }); - it('uses default value for missing sourceGlob', () => { - const result = docCoveragePluginConfigSchema.parse({}); - expect(result.sourceGlob).toEqual([ - 'src/**/*.{ts,tsx}', - '!**/*.spec.ts', - '!**/*.test.ts', - ]); - }); + it('uses default value for missing sourceGlob', () => { + const result = docCoveragePluginConfigSchema.parse({}); + expect(result.sourceGlob).toEqual([ + 'src/**/*.{ts,tsx}', + '!**/*.spec.ts', + '!**/*.test.ts', + ]); + }); - it('throws for invalid sourceGlob type', () => { - expect(() => - docCoveragePluginConfigSchema.parse({ - sourceGlob: 123, - }), - ).toThrow('Expected array'); + it('throws for invalid sourceGlob type', () => { + expect(() => + docCoveragePluginConfigSchema.parse({ + sourceGlob: 123, + }), + ).toThrow('Expected array'); + }); }); -}); -describe('onlyAudits', () => { - it('accepts a valid `onlyAudits` array', () => { - expect(() => - docCoveragePluginConfigSchema.parse({ - onlyAudits: ['functions-coverage', 'classes-coverage'], - sourceGlob: ['src/**/*.ts'], - }), - ).not.toThrow(); - }); + describe('onlyAudits', () => { + it('accepts a valid `onlyAudits` array', () => { + expect(() => + docCoveragePluginConfigSchema.parse({ + onlyAudits: ['functions-coverage', 'classes-coverage'], + sourceGlob: ['src/**/*.ts'], + }), + ).not.toThrow(); + }); - it('accepts empty array for onlyAudits', () => { - expect(() => - docCoveragePluginConfigSchema.parse({ - onlyAudits: [], - sourceGlob: ['src/**/*.ts'], - }), - ).not.toThrow(); - }); + it('accepts empty array for onlyAudits', () => { + expect(() => + docCoveragePluginConfigSchema.parse({ + onlyAudits: [], + sourceGlob: ['src/**/*.ts'], + }), + ).not.toThrow(); + }); - it('allows onlyAudits to be undefined', () => { - const result = docCoveragePluginConfigSchema.parse({}); - expect(result.onlyAudits).toBeUndefined(); - }); + it('allows onlyAudits to be undefined', () => { + const result = docCoveragePluginConfigSchema.parse({}); + expect(result.onlyAudits).toBeUndefined(); + }); - it('throws for invalid onlyAudits type', () => { - expect(() => - docCoveragePluginConfigSchema.parse({ - onlyAudits: 'functions-coverage', - }), - ).toThrow('Expected array'); - }); + it('throws for invalid onlyAudits type', () => { + expect(() => + docCoveragePluginConfigSchema.parse({ + onlyAudits: 'functions-coverage', + }), + ).toThrow('Expected array'); + }); - it('throws for array with non-string elements', () => { - expect(() => - docCoveragePluginConfigSchema.parse({ - onlyAudits: [123, true], - }), - ).toThrow('Expected string'); + it('throws for array with non-string elements', () => { + expect(() => + docCoveragePluginConfigSchema.parse({ + onlyAudits: [123, true], + }), + ).toThrow('Expected string'); + }); }); -}); -describe('skipAudits', () => { - it('accepts valid audit slugs array', () => { - expect(() => - docCoveragePluginConfigSchema.parse({ - skipAudits: ['functions-coverage', 'classes-coverage'], - sourceGlob: ['src/**/*.ts'], - }), - ).not.toThrow(); - }); + describe('skipAudits', () => { + it('accepts valid audit slugs array', () => { + expect(() => + docCoveragePluginConfigSchema.parse({ + skipAudits: ['functions-coverage', 'classes-coverage'], + sourceGlob: ['src/**/*.ts'], + }), + ).not.toThrow(); + }); + + it('accepts empty array for skipAudits', () => { + expect(() => + docCoveragePluginConfigSchema.parse({ + skipAudits: [], + sourceGlob: ['src/**/*.ts'], + }), + ).not.toThrow(); + }); + + it('allows skipAudits to be undefined', () => { + const result = docCoveragePluginConfigSchema.parse({}); + expect(result.skipAudits).toBeUndefined(); + }); + + it('throws for invalid skipAudits type', () => { + expect(() => + docCoveragePluginConfigSchema.parse({ + skipAudits: 'functions-coverage', + }), + ).toThrow('Expected array'); + }); - it('throws for array with non-string elements', () => { - expect(() => - docCoveragePluginConfigSchema.parse({ - skipAudits: [123, true], - }), - ).toThrow('Expected string'); + it('throws for array with non-string elements', () => { + expect(() => + docCoveragePluginConfigSchema.parse({ + skipAudits: [123, true], + }), + ).toThrow('Expected string'); + }); }); }); diff --git a/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.unit.test.ts b/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.unit.test.ts index adf079fc4..3682d515d 100644 --- a/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.unit.test.ts +++ b/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.unit.test.ts @@ -1,18 +1,24 @@ -import { describe, expect, it } from 'vitest'; -import type { RunnerConfig } from '@code-pushup/models'; -import { PLUGIN_SLUG } from './constants.js'; +import { describe, expect, it, vi } from 'vitest'; +import { PLUGIN_SLUG, groups } from './constants.js'; import { PLUGIN_DESCRIPTION, PLUGIN_DOCS_URL, PLUGIN_TITLE, docCoveragePlugin, } from './doc-coverage-plugin.js'; +import { createRunnerFunction } from './runner/runner.js'; +import { + filterAuditsByPluginConfig, + filterGroupsByOnlyAudits, +} from './utils.js'; + +vi.mock('./utils.js', () => ({ + filterAuditsByPluginConfig: vi.fn().mockReturnValue(['mockAudit']), + filterGroupsByOnlyAudits: vi.fn().mockReturnValue(['mockGroup']), +})); -vi.mock('./runner/index.ts', () => ({ - createRunnerConfig: vi.fn().mockReturnValue({ - command: 'node', - outputFile: 'runner-output.json', - } satisfies RunnerConfig), +vi.mock('./runner/runner.js', () => ({ + createRunnerFunction: vi.fn().mockReturnValue(() => Promise.resolve([])), })); describe('docCoveragePlugin', () => { @@ -34,4 +40,34 @@ describe('docCoveragePlugin', () => { }), ); }); + + it('should throw for invalid plugin options', async () => { + await expect( + docCoveragePlugin({ + // @ts-expect-error testing invalid config + sourceGlob: 123, + }), + ).rejects.toThrow('Expected array, received number'); + }); + + it('should filter groups', async () => { + const config = { sourceGlob: ['src/**/*.ts'] }; + await docCoveragePlugin(config); + + expect(filterGroupsByOnlyAudits).toHaveBeenCalledWith(groups, config); + }); + + it('should filter audits', async () => { + const config = { sourceGlob: ['src/**/*.ts'] }; + await docCoveragePlugin(config); + + expect(filterAuditsByPluginConfig).toHaveBeenCalledWith(config); + }); + + it('should forward options to runner function', async () => { + const config = { sourceGlob: ['src/**/*.ts'] }; + await docCoveragePlugin(config); + + expect(createRunnerFunction).toHaveBeenCalledWith(config); + }); }); diff --git a/packages/plugin-doc-coverage/src/lib/runner/__snapshots__/doc-processer.unit.test.ts.snap b/packages/plugin-doc-coverage/src/lib/runner/__snapshots__/doc-processer.unit.test.ts.snap index 1090891fe..3a9c6a965 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/__snapshots__/doc-processer.unit.test.ts.snap +++ b/packages/plugin-doc-coverage/src/lib/runner/__snapshots__/doc-processer.unit.test.ts.snap @@ -1,6 +1,6 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`getUnprocessedCoverageReport > should produce a full report 1`] = ` +exports[`getDocumentationReport > should produce a full report 1`] = ` { "classes": { "coverage": 33.33, diff --git a/packages/plugin-doc-coverage/src/lib/runner/doc-processer.integration.test.ts b/packages/plugin-doc-coverage/src/lib/runner/doc-processer.integration.test.ts index 539c3af7d..2f8cb4864 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/doc-processer.integration.test.ts +++ b/packages/plugin-doc-coverage/src/lib/runner/doc-processer.integration.test.ts @@ -1,10 +1,9 @@ import { processDocCoverage } from './doc-processer.js'; describe('processDocCoverage', () => { - const sourcePath = - 'packages/plugin-doc-coverage/mocks/fixtures/angular/**/*.ts'; - it('should count total nodes from TypeScript files correctly', () => { + const sourcePath = + 'packages/plugin-doc-coverage/mocks/fixtures/angular/**/*.ts'; const expectedNodeCount = 8; const results = processDocCoverage({ sourceGlob: [sourcePath] }); @@ -17,7 +16,39 @@ describe('processDocCoverage', () => { expect(totalNodeCount).toBe(expectedNodeCount); }); + it('should count total nodes from Javascript files correctly', () => { + const sourcePath = + 'packages/plugin-doc-coverage/mocks/fixtures/react/**/*.js'; + const expectedNodeCount = 1; + + const results = processDocCoverage({ sourceGlob: [sourcePath] }); + + const totalNodeCount = Object.values(results).reduce( + (acc, node) => acc + node.nodesCount, + 0, + ); + + expect(totalNodeCount).toBe(expectedNodeCount); + }); + + it('should count total nodes from Javascript and TypeScript files correctly', () => { + const sourcePath = + 'packages/plugin-doc-coverage/mocks/fixtures/**/*.{js,ts}'; + const expectedNodeCount = 9; + + const results = processDocCoverage({ sourceGlob: [sourcePath] }); + + const totalNodeCount = Object.values(results).reduce( + (acc, node) => acc + node.nodesCount, + 0, + ); + + expect(totalNodeCount).toBe(expectedNodeCount); + }); + it('respect `sourceGlob` and only include matching files', () => { + const sourcePath = + 'packages/plugin-doc-coverage/mocks/fixtures/angular/**/*.ts'; const expectedNodeCount = 7; const results = processDocCoverage({ diff --git a/packages/plugin-doc-coverage/src/lib/runner/doc-processer.ts b/packages/plugin-doc-coverage/src/lib/runner/doc-processer.ts index d63c1f469..c5bac7f78 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/doc-processer.ts +++ b/packages/plugin-doc-coverage/src/lib/runner/doc-processer.ts @@ -6,9 +6,9 @@ import { } from 'ts-morph'; import type { DocCoveragePluginConfig } from '../config.js'; import type { - CoverageReportShape, - CoverageResult, CoverageType, + DocumentationCoverageReport, + DocumentationReport, } from './models.js'; import { calculateCoverage, @@ -43,24 +43,24 @@ export function getVariablesInformation( /** * Processes documentation coverage for TypeScript files in the specified path * @param toInclude - The file path pattern to include for documentation analysis - * @returns {CoverageResult} Object containing coverage statistics and undocumented items + * @returns {DocumentationCoverageReport} Object containing coverage statistics and undocumented items */ export function processDocCoverage( config: DocCoveragePluginConfig, -): CoverageResult { +): DocumentationCoverageReport { const project = new Project(); project.addSourceFilesAtPaths(config.sourceGlob); - return getUnprocessedCoverageReport(project.getSourceFiles()); + return getDocumentationReport(project.getSourceFiles()); } /** - * Gets the unprocessed coverage report from the source files + * Gets the documentation coverage report from the source files * @param sourceFiles - The source files to process - * @returns {CoverageReportShape} The unprocessed coverage report + * @returns {DocumentationCoverageReport} The documentation coverage report */ -export function getUnprocessedCoverageReport( +export function getDocumentationReport( sourceFiles: SourceFile[], -): CoverageResult { +): DocumentationCoverageReport { const unprocessedCoverageReport = sourceFiles.reduce( (coverageReportOfAllFiles, sourceFile) => { const filePath = sourceFile.getFilePath(); @@ -105,7 +105,7 @@ export function getUnprocessedCoverageReport( createEmptyCoverageData(), ); - return mergeCoverageResults( + return mergeDocumentationReports( coverageReportOfAllFiles, coverageReportOfCurrentFile, ); @@ -117,28 +117,29 @@ export function getUnprocessedCoverageReport( } /** - * Merges two coverage results - * @param results - The first empty coverage result - * @param current - The second coverage result - * @returns {CoverageReportShape} The merged coverage result + * Merges two documentation results + * @param accumulatedReport - The first empty documentation result + * @param currentFileReport - The second documentation result + * @returns {DocumentationReport} The merged documentation result */ -export function mergeCoverageResults( - results: CoverageReportShape, - current: Partial, -): CoverageReportShape { +export function mergeDocumentationReports( + accumulatedReport: DocumentationReport, + currentFileReport: Partial, +): DocumentationReport { return Object.fromEntries( - Object.entries(results).map(([key, value]) => { - const node = value as CoverageResult[CoverageType]; + Object.entries(accumulatedReport).map(([key, value]) => { + const node = value as DocumentationCoverageReport[CoverageType]; const type = key as CoverageType; return [ type, { - nodesCount: node.nodesCount + (current[type]?.nodesCount ?? 0), - issues: [...node.issues, ...(current[type]?.issues ?? [])], + nodesCount: + node.nodesCount + (currentFileReport[type]?.nodesCount ?? 0), + issues: [...node.issues, ...(currentFileReport[type]?.issues ?? [])], }, ]; }), - ) as CoverageReportShape; + ) as DocumentationReport; } /** diff --git a/packages/plugin-doc-coverage/src/lib/runner/doc-processer.unit.test.ts b/packages/plugin-doc-coverage/src/lib/runner/doc-processer.unit.test.ts index 3db7abbb7..c8bfe7953 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/doc-processer.unit.test.ts +++ b/packages/plugin-doc-coverage/src/lib/runner/doc-processer.unit.test.ts @@ -5,15 +5,15 @@ import { } from '../../../mocks/source-files-mock.generator'; import { getClassNodes, - getUnprocessedCoverageReport, + getDocumentationReport, getVariablesInformation, - mergeCoverageResults, + mergeDocumentationReports, } from './doc-processer.js'; -import type { CoverageReportShape } from './models.js'; +import type { DocumentationReport } from './models.js'; -describe('getUnprocessedCoverageReport', () => { +describe('getDocumentationReport', () => { it('should produce a full report', () => { - const results = getUnprocessedCoverageReport([ + const results = getDocumentationReport([ sourceFileMock('test.ts', { functions: { 1: true, 2: true, 3: true }, classes: { 4: false, 5: false, 6: true }, @@ -28,14 +28,14 @@ describe('getUnprocessedCoverageReport', () => { }); it('should accept array of source files', () => { - const results = getUnprocessedCoverageReport([ + const results = getDocumentationReport([ sourceFileMock('test.ts', { functions: { 1: true, 2: true, 3: false } }), ]); expect(results).toBeDefined(); }); it('should count nodes correctly', () => { - const results = getUnprocessedCoverageReport([ + const results = getDocumentationReport([ sourceFileMock('test.ts', { functions: { 1: true, 2: true, 3: false } }), ]); @@ -43,7 +43,7 @@ describe('getUnprocessedCoverageReport', () => { }); it('should collect uncommented nodes issues', () => { - const results = getUnprocessedCoverageReport([ + const results = getDocumentationReport([ sourceFileMock('test.ts', { functions: { 1: true, 2: false, 3: false } }), ]); @@ -51,7 +51,7 @@ describe('getUnprocessedCoverageReport', () => { }); it('should collect valid issues', () => { - const results = getUnprocessedCoverageReport([ + const results = getDocumentationReport([ sourceFileMock('test.ts', { functions: { 1: false } }), ]); @@ -66,7 +66,7 @@ describe('getUnprocessedCoverageReport', () => { }); it('should calculate coverage correctly', () => { - const results = getUnprocessedCoverageReport([ + const results = getDocumentationReport([ sourceFileMock('test.ts', { functions: { 1: true, 2: false } }), ]); @@ -74,8 +74,8 @@ describe('getUnprocessedCoverageReport', () => { }); }); -describe('mergeCoverageResults', () => { - const emptyResult: CoverageReportShape = { +describe('mergeDocumentationReports', () => { + const emptyResult: DocumentationReport = { enums: { nodesCount: 0, issues: [] }, interfaces: { nodesCount: 0, issues: [] }, types: { nodesCount: 0, issues: [] }, @@ -103,9 +103,9 @@ describe('mergeCoverageResults', () => { }, }; - const results = mergeCoverageResults( + const results = mergeDocumentationReports( emptyResult, - secondResult as Partial, + secondResult as Partial, ); expect(results).toStrictEqual( expect.objectContaining({ @@ -118,12 +118,12 @@ describe('mergeCoverageResults', () => { }); it('should merge empty results', () => { - const results = mergeCoverageResults(emptyResult, emptyResult); + const results = mergeDocumentationReports(emptyResult, emptyResult); expect(results).toStrictEqual(emptyResult); }); it('should merge second level property nodesCount', () => { - const results = mergeCoverageResults( + const results = mergeDocumentationReports( { ...emptyResult, enums: { nodesCount: 1, issues: [] }, @@ -136,7 +136,7 @@ describe('mergeCoverageResults', () => { }); it('should merge second level property issues', () => { - const results = mergeCoverageResults( + const results = mergeDocumentationReports( { ...emptyResult, enums: { diff --git a/packages/plugin-doc-coverage/src/lib/runner/models.ts b/packages/plugin-doc-coverage/src/lib/runner/models.ts index 547efab56..d95b7b47b 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/models.ts +++ b/packages/plugin-doc-coverage/src/lib/runner/models.ts @@ -25,19 +25,19 @@ export type UndocumentedNode = { class?: string; }; -/** The coverage data is the data that is used to create the coverage report. Without coverage stats. */ -export type CoverageData = { +/** The documentation data has the issues and the total nodes count from a specific CoverageType. */ +export type DocumentationData = { issues: UndocumentedNode[]; nodesCount: number; }; -/** The coverage report shape the report of every CoverageType without coverage stats. */ -export type CoverageReportShape = Record; +/** The documentation report has all the documentation data for each coverage type. */ +export type DocumentationReport = Record; -/** The processed coverage result CoverageData but for each coverage type and with coverage stats. */ -export type CoverageResult = Record< +/** The processed documentation result has the documentation data for each coverage type and with coverage stats. */ +export type DocumentationCoverageReport = Record< CoverageType, - CoverageData & { + DocumentationData & { coverage: number; } >; diff --git a/packages/plugin-doc-coverage/src/lib/runner/runner.ts b/packages/plugin-doc-coverage/src/lib/runner/runner.ts index 1019687e8..b50fbdc4f 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/runner.ts +++ b/packages/plugin-doc-coverage/src/lib/runner/runner.ts @@ -1,7 +1,7 @@ import type { AuditOutputs, RunnerFunction } from '@code-pushup/models'; import type { DocCoveragePluginConfig } from '../config.js'; import { processDocCoverage } from './doc-processer.js'; -import type { CoverageResult } from './models.js'; +import type { DocumentationCoverageReport } from './models.js'; export function createRunnerFunction( config: DocCoveragePluginConfig, @@ -19,7 +19,7 @@ export function createRunnerFunction( * @returns Audit outputs with coverage scores and details about undocumented items */ export function trasformCoverageReportToAudits( - coverageResult: CoverageResult, + coverageResult: DocumentationCoverageReport, options: Pick, ): AuditOutputs { return Object.entries(coverageResult) diff --git a/packages/plugin-doc-coverage/src/lib/runner/runner.unit.test.ts b/packages/plugin-doc-coverage/src/lib/runner/runner.unit.test.ts index aab40d08c..081df241f 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/runner.unit.test.ts +++ b/packages/plugin-doc-coverage/src/lib/runner/runner.unit.test.ts @@ -1,4 +1,4 @@ -import type { CoverageResult } from './models.js'; +import type { DocumentationCoverageReport } from './models.js'; import { trasformCoverageReportToAudits } from './runner.js'; describe('trasformCoverageReportToAudits', () => { @@ -27,7 +27,7 @@ describe('trasformCoverageReportToAudits', () => { }, ], }, - } as unknown as CoverageResult; + } as unknown as DocumentationCoverageReport; it('should return all audits from the coverage result when no filters are provided', () => { const result = trasformCoverageReportToAudits(mockCoverageResult, {}); @@ -55,7 +55,7 @@ describe('trasformCoverageReportToAudits', () => { it('should handle properly empty coverage result', () => { const result = trasformCoverageReportToAudits( - {} as unknown as CoverageResult, + {} as unknown as DocumentationCoverageReport, {}, ); expect(result).toEqual([]); diff --git a/packages/plugin-doc-coverage/src/lib/runner/utils.ts b/packages/plugin-doc-coverage/src/lib/runner/utils.ts index cdc202991..298b64806 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/utils.ts +++ b/packages/plugin-doc-coverage/src/lib/runner/utils.ts @@ -1,15 +1,15 @@ import { SyntaxKind } from 'ts-morph'; import type { - CoverageReportShape, - CoverageResult, CoverageType, + DocumentationCoverageReport, + DocumentationReport, } from './models.js'; /** * Creates an empty unprocessed coverage report. * @returns The empty unprocessed coverage report. */ -export function createEmptyCoverageData(): CoverageReportShape { +export function createEmptyCoverageData(): DocumentationReport { return { enums: { nodesCount: 0, issues: [] }, interfaces: { nodesCount: 0, issues: [] }, @@ -27,7 +27,7 @@ export function createEmptyCoverageData(): CoverageReportShape { * @param result - The unprocessed coverage result. * @returns The processed coverage result. */ -export function calculateCoverage(result: CoverageReportShape) { +export function calculateCoverage(result: DocumentationReport) { return Object.fromEntries( Object.entries(result).map(([key, value]) => { const type = key as CoverageType; @@ -47,7 +47,7 @@ export function calculateCoverage(result: CoverageReportShape) { }, ]; }), - ) as CoverageResult; + ) as DocumentationCoverageReport; } /** diff --git a/packages/plugin-doc-coverage/src/lib/runner/utils.unit.test.ts b/packages/plugin-doc-coverage/src/lib/runner/utils.unit.test.ts index d45b1600c..fcf8e2f33 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/utils.unit.test.ts +++ b/packages/plugin-doc-coverage/src/lib/runner/utils.unit.test.ts @@ -1,5 +1,5 @@ import { SyntaxKind } from 'ts-morph'; -import type { CoverageReportShape } from './models.js'; +import type { DocumentationReport } from './models.js'; import { calculateCoverage, createEmptyCoverageData, @@ -36,7 +36,7 @@ describe('calculateCoverage', () => { }); it('should calculate correct coverage percentage with issues', () => { - const input: CoverageReportShape = { + const input: DocumentationReport = { ...createEmptyCoverageData(), functions: { nodesCount: 4, From 0ecc1931245cdcc8a91986f439ebd3df713088d0 Mon Sep 17 00:00:00 2001 From: Alejandro Date: Mon, 23 Dec 2024 15:43:57 +0100 Subject: [PATCH 34/66] chore: run nx format --- code-pushup.config.ts | 2 +- packages/plugin-doc-coverage/README.md | 2 +- tsconfig.base.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/code-pushup.config.ts b/code-pushup.config.ts index ebaaf803f..e511a1d76 100644 --- a/code-pushup.config.ts +++ b/code-pushup.config.ts @@ -43,7 +43,7 @@ export default mergeConfigs( await docCoverageCoreConfig({ sourceGlob: [ 'packages/**/src/**/*.ts', - '!**/*.{spec,test}.ts' + '!**/*.{spec,test}.ts', '!**/implementation/**', '!**/internal/**', ], diff --git a/packages/plugin-doc-coverage/README.md b/packages/plugin-doc-coverage/README.md index 842730ab5..ca54ce31f 100644 --- a/packages/plugin-doc-coverage/README.md +++ b/packages/plugin-doc-coverage/README.md @@ -11,7 +11,7 @@ It analyzes your codebase and checks for documentation on different code element Measured documentation types are mapped to Code PushUp audits in the following way: -- `value`: The value is the number of undocumented nodes -> 4 +- `value`: The value is the number of undocumented nodes -> 4 - `displayValue`: `${value} undocumented ${type}` -> 4 undocumented functions - `score`: 0.5 -> total nodes 8 undocumented 4 -> 8/4 - The score is value converted to 0-1 range diff --git a/tsconfig.base.json b/tsconfig.base.json index c5cddb98c..026003f3e 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -24,10 +24,10 @@ "@code-pushup/cli": ["packages/cli/src/index.ts"], "@code-pushup/core": ["packages/core/src/index.ts"], "@code-pushup/coverage-plugin": ["packages/plugin-coverage/src/index.ts"], - "@code-pushup/eslint-plugin": ["packages/plugin-eslint/src/index.ts"], "@code-pushup/doc-coverage-plugin": [ "packages/plugin-doc-coverage/src/index.ts" ], + "@code-pushup/eslint-plugin": ["packages/plugin-eslint/src/index.ts"], "@code-pushup/js-packages-plugin": [ "packages/plugin-js-packages/src/index.ts" ], From 7e0acbdf402856c49268e43d8540119508e570eb Mon Sep 17 00:00:00 2001 From: Alejandro Date: Mon, 23 Dec 2024 16:02:43 +0100 Subject: [PATCH 35/66] chore(plugin-doc-coverage): improve readme and audits --- packages/plugin-doc-coverage/README.md | 67 ++++++++++++++----- .../src/lib/runner/runner.ts | 6 +- 2 files changed, 55 insertions(+), 18 deletions(-) diff --git a/packages/plugin-doc-coverage/README.md b/packages/plugin-doc-coverage/README.md index ca54ce31f..78f30ace6 100644 --- a/packages/plugin-doc-coverage/README.md +++ b/packages/plugin-doc-coverage/README.md @@ -37,15 +37,6 @@ Measured documentation types are mapped to Code PushUp audits in the following w 3. Add this plugin to the `plugins` array in your Code PushUp CLI config file (e.g. `code-pushup.config.ts`). - Pass the target files to analyze and optionally specify which types of documentation you want to track. - You can skip for example tests by defining in the sourceGlob the path to the tests folder or pattern to match the tests files with the `!` symbol. - All documentation types are measured by default. - If you wish to focus on a subset of offered types, define them in `onlyAudits`. - Also you can skip some types by defining them in `skipAudits`. - You can only define or `onlyAudits` or `skipAudits`, not both. - - The configuration will look similarly to the following: - ```js import docCoveragePlugin from '@code-pushup/doc-coverage-plugin'; @@ -92,9 +83,16 @@ Measured documentation types are mapped to Code PushUp audits in the following w Documentation coverage is a metric that indicates what percentage of your code elements have proper documentation. It helps ensure your codebase is well-documented and maintainable. -The plugin provides a single audit that measures the overall percentage of documentation coverage across your codebase: +The plugin provides multiple audits, one for each documentation type (classes, functions, interfaces, etc.), and groups them together for an overall documentation coverage measurement. Each audit: + +- Measures the documentation coverage for its specific type (e.g., classes, functions) +- Provides a score based on the percentage of documented elements +- Includes details about which elements are missing documentation -- **Percentage coverage**: Measures how many percent of the codebase have documentation. +These audits are grouped together to provide a comprehensive view of your codebase's documentation status. You can use either: + +- The complete group of audits for overall documentation coverage +- Individual audits to focus on specific documentation types ## Plugin architecture @@ -102,10 +100,49 @@ The plugin provides a single audit that measures the overall percentage of docum The plugin accepts the following parameters: -- (optional) `coverageToolCommand`: If you wish to run your documentation coverage tool (compodoc) to generate the results first, you may define it here. - - `command`: Command to run coverage tool (e.g. `npx`). - - `args`: Arguments to be passed to the coverage tool (e.g. `['compodoc', '-p', 'tsconfig.doc.json', '-e', 'json']`). -- `outputPath`: Path to the documentation.json file. Defaults to `'documentation/documentation.json'`. +#### SourceGlob + +Required parameter. The `sourceGlob` option accepts an array of strings that define patterns to include or exclude files. You can use glob patterns to match files and the `!` symbol to exclude specific patterns. Example: + +```js +await docCoveragePlugin({ + sourceGlob: [ + 'src/**/*.ts', // include all TypeScript files in src + '!src/**/*.{spec,test}.ts', // exclude test files + '!src/**/testing/**/*.ts' // exclude testing utilities + ], +}), +``` + +#### OnlyAudits + +Optional parameter. The `onlyAudits` option allows you to specify which documentation types you want to measure. Only the specified audits will be included in the results. Example: + +```js +await docCoveragePlugin({ + sourceGlob: ['src/**/*.ts'], + onlyAudits: [ + 'classes-coverage', + 'functions-coverage' + ] // Only measure documentation for classes and functions +}), +``` + +#### SkipAudits + +Optional parameter. The `skipAudits` option allows you to exclude specific documentation types from measurement. All other types will be included in the results. + +```js +await docCoveragePlugin({ + sourceGlob: ['src/**/*.ts'], + skipAudits: [ + 'variables-coverage', + 'interfaces-coverage' + ] // Measure all documentation types except variables and interfaces +}), +``` + +> โš ๏ธ **Warning:** You cannot use both `onlyAudits` and `skipAudits` in the same configuration. Choose the one that better suits your needs. ### Audits and group diff --git a/packages/plugin-doc-coverage/src/lib/runner/runner.ts b/packages/plugin-doc-coverage/src/lib/runner/runner.ts index b50fbdc4f..8c059aac7 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/runner.ts +++ b/packages/plugin-doc-coverage/src/lib/runner/runner.ts @@ -34,13 +34,13 @@ export function trasformCoverageReportToAudits( return true; }) .map(([type, item]) => { - const { coverage } = item; + const { coverage, issues } = item; return { slug: `${type}-coverage`, - value: coverage, + value: issues.length, score: coverage / 100, - displayValue: `${coverage} %`, + displayValue: `${issues.length} undocumented ${type}`, details: { issues: item.issues.map(({ file, line }) => ({ message: 'Missing documentation', From 24468ccc1b3862e7c318ca36bafffaff8f1e5a25 Mon Sep 17 00:00:00 2001 From: Alejandro Date: Mon, 23 Dec 2024 19:08:00 +0100 Subject: [PATCH 36/66] chore(plugin-doc-coverage): remove unused async --- packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.ts b/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.ts index d452839d6..578c0c490 100644 --- a/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.ts +++ b/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.ts @@ -36,9 +36,9 @@ export const PLUGIN_DOCS_URL = * * @returns Plugin configuration. */ -export async function docCoveragePlugin( +export function docCoveragePlugin( config: DocCoveragePluginConfig, -): Promise { +): PluginConfig { const docCoverageConfig = docCoveragePluginConfigSchema.parse(config); return { From ef77df0007feaed0a0afb05bdca8bb602deda327 Mon Sep 17 00:00:00 2001 From: Alejandro Date: Mon, 23 Dec 2024 19:53:25 +0100 Subject: [PATCH 37/66] chore(plugin-doc-coverage): fix tests, fully remove unnecessary await --- code-pushup.preset.ts | 2 +- packages/plugin-doc-coverage/README.md | 8 ++--- .../src/lib/config.unit.test.ts | 2 +- .../src/lib/doc-coverage-plugin.ts | 4 +-- .../src/lib/doc-coverage-plugin.unit.test.ts | 20 +++++------ .../src/lib/runner/doc-processer.ts | 26 +++++++------- .../src/lib/runner/doc-processer.unit.test.ts | 35 +++++++++++++++++++ 7 files changed, 67 insertions(+), 30 deletions(-) diff --git a/code-pushup.preset.ts b/code-pushup.preset.ts index 1060dd1e9..c7f907f03 100644 --- a/code-pushup.preset.ts +++ b/code-pushup.preset.ts @@ -144,7 +144,7 @@ export const docCoverageCoreConfig = async ( config: DocCoveragePluginConfig, ): Promise => { return { - plugins: [await docCoveragePlugin(config)], + plugins: [docCoveragePlugin(config)], categories: getDocCoverageCategories(config), }; }; diff --git a/packages/plugin-doc-coverage/README.md b/packages/plugin-doc-coverage/README.md index 78f30ace6..d41ba4576 100644 --- a/packages/plugin-doc-coverage/README.md +++ b/packages/plugin-doc-coverage/README.md @@ -44,7 +44,7 @@ Measured documentation types are mapped to Code PushUp audits in the following w // ... plugins: [ // ... - await docCoveragePlugin({ + docCoveragePlugin({ sourceGlob: ['**/*.ts'], }), ], @@ -105,7 +105,7 @@ The plugin accepts the following parameters: Required parameter. The `sourceGlob` option accepts an array of strings that define patterns to include or exclude files. You can use glob patterns to match files and the `!` symbol to exclude specific patterns. Example: ```js -await docCoveragePlugin({ +docCoveragePlugin({ sourceGlob: [ 'src/**/*.ts', // include all TypeScript files in src '!src/**/*.{spec,test}.ts', // exclude test files @@ -119,7 +119,7 @@ await docCoveragePlugin({ Optional parameter. The `onlyAudits` option allows you to specify which documentation types you want to measure. Only the specified audits will be included in the results. Example: ```js -await docCoveragePlugin({ +docCoveragePlugin({ sourceGlob: ['src/**/*.ts'], onlyAudits: [ 'classes-coverage', @@ -133,7 +133,7 @@ await docCoveragePlugin({ Optional parameter. The `skipAudits` option allows you to exclude specific documentation types from measurement. All other types will be included in the results. ```js -await docCoveragePlugin({ +docCoveragePlugin({ sourceGlob: ['src/**/*.ts'], skipAudits: [ 'variables-coverage', diff --git a/packages/plugin-doc-coverage/src/lib/config.unit.test.ts b/packages/plugin-doc-coverage/src/lib/config.unit.test.ts index 83449ecce..4d4b4f1ea 100644 --- a/packages/plugin-doc-coverage/src/lib/config.unit.test.ts +++ b/packages/plugin-doc-coverage/src/lib/config.unit.test.ts @@ -91,7 +91,7 @@ describe('DocCoveragePlugin Configuration', () => { docCoveragePluginConfigSchema.parse({ onlyAudits: [123, true], }), - ).toThrow('Expected string'); + ).toThrow('Expected string, received number'); }); }); diff --git a/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.ts b/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.ts index 578c0c490..dc594a303 100644 --- a/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.ts +++ b/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.ts @@ -28,8 +28,8 @@ export const PLUGIN_DOCS_URL = * // ... core config ... * plugins: [ * // ... other plugins ... - * await docCoveragePlugin({ - * sourceGlob: 'src/**/*.{ts,tsx}', + * docCoveragePlugin({ + * sourceGlob: ['src/**/*.{ts,tsx}'] * }) * ] * } diff --git a/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.unit.test.ts b/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.unit.test.ts index 3682d515d..3fda06e71 100644 --- a/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.unit.test.ts +++ b/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.unit.test.ts @@ -22,12 +22,12 @@ vi.mock('./runner/runner.js', () => ({ })); describe('docCoveragePlugin', () => { - it('should create a valid plugin config', async () => { - await expect( + it('should create a valid plugin config', () => { + expect( docCoveragePlugin({ sourceGlob: ['src/**/*.ts', '!**/*.spec.ts', '!**/*.test.ts'], }), - ).resolves.toStrictEqual( + ).toStrictEqual( expect.objectContaining({ slug: PLUGIN_SLUG, title: PLUGIN_TITLE, @@ -41,32 +41,32 @@ describe('docCoveragePlugin', () => { ); }); - it('should throw for invalid plugin options', async () => { - await expect( + it('should throw for invalid plugin options', () => { + expect(() => docCoveragePlugin({ // @ts-expect-error testing invalid config sourceGlob: 123, }), - ).rejects.toThrow('Expected array, received number'); + ).toThrow('Expected array, received number'); }); - it('should filter groups', async () => { + it('should filter groups', () => { const config = { sourceGlob: ['src/**/*.ts'] }; - await docCoveragePlugin(config); + docCoveragePlugin(config); expect(filterGroupsByOnlyAudits).toHaveBeenCalledWith(groups, config); }); it('should filter audits', async () => { const config = { sourceGlob: ['src/**/*.ts'] }; - await docCoveragePlugin(config); + docCoveragePlugin(config); expect(filterAuditsByPluginConfig).toHaveBeenCalledWith(config); }); it('should forward options to runner function', async () => { const config = { sourceGlob: ['src/**/*.ts'] }; - await docCoveragePlugin(config); + docCoveragePlugin(config); expect(createRunnerFunction).toHaveBeenCalledWith(config); }); diff --git a/packages/plugin-doc-coverage/src/lib/runner/doc-processer.ts b/packages/plugin-doc-coverage/src/lib/runner/doc-processer.ts index c5bac7f78..82be50608 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/doc-processer.ts +++ b/packages/plugin-doc-coverage/src/lib/runner/doc-processer.ts @@ -53,6 +53,19 @@ export function processDocCoverage( return getDocumentationReport(project.getSourceFiles()); } +export function getAllNodesFromASourceFile(sourceFile: SourceFile) { + const classes = sourceFile.getClasses(); + return [ + ...sourceFile.getFunctions(), + ...classes, + ...getClassNodes(classes), + ...sourceFile.getTypeAliases(), + ...sourceFile.getEnums(), + ...sourceFile.getInterfaces(), + ...getVariablesInformation(sourceFile.getVariableStatements()), + ]; +} + /** * Gets the documentation coverage report from the source files * @param sourceFiles - The source files to process @@ -64,23 +77,12 @@ export function getDocumentationReport( const unprocessedCoverageReport = sourceFiles.reduce( (coverageReportOfAllFiles, sourceFile) => { const filePath = sourceFile.getFilePath(); - const classes = sourceFile.getClasses(); - - const allNodesFromFile = [ - ...sourceFile.getFunctions(), - ...classes, - ...getClassNodes(classes), - ...sourceFile.getTypeAliases(), - ...sourceFile.getEnums(), - ...sourceFile.getInterfaces(), - ...getVariablesInformation(sourceFile.getVariableStatements()), - ]; + const allNodesFromFile = getAllNodesFromASourceFile(sourceFile); const coverageReportOfCurrentFile = allNodesFromFile.reduce( (acc, node) => { const nodeType = getCoverageTypeFromKind(node.getKind()); const currentTypeReport = acc[nodeType]; - const updatedIssues = node.getJsDocs().length === 0 ? [ diff --git a/packages/plugin-doc-coverage/src/lib/runner/doc-processer.unit.test.ts b/packages/plugin-doc-coverage/src/lib/runner/doc-processer.unit.test.ts index c8bfe7953..8cf66f43f 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/doc-processer.unit.test.ts +++ b/packages/plugin-doc-coverage/src/lib/runner/doc-processer.unit.test.ts @@ -4,6 +4,7 @@ import { sourceFileMock, } from '../../../mocks/source-files-mock.generator'; import { + getAllNodesFromASourceFile, getClassNodes, getDocumentationReport, getVariablesInformation, @@ -276,3 +277,37 @@ describe('getVariablesInformation', () => { expect(result).toHaveLength(0); }); }); + +describe('getAllNodesFromASourceFile', () => { + it('should combine all node types from a source file', () => { + const mockSourceFile = sourceFileMock('test.ts', { + functions: { 1: true }, + classes: { 2: false }, + types: { 3: true }, + enums: { 4: false }, + interfaces: { 5: true }, + }); + + const result = getAllNodesFromASourceFile(mockSourceFile); + + expect(result).toHaveLength(5); + }); + + it('should handle empty source file', () => { + const mockSourceFile = sourceFileMock('empty.ts', {}); + + const result = getAllNodesFromASourceFile(mockSourceFile); + + expect(result).toHaveLength(0); + }); + + it('should handle source file with only functions', () => { + const mockSourceFile = sourceFileMock('functions.ts', { + functions: { 1: true, 2: false, 3: true }, + }); + + const result = getAllNodesFromASourceFile(mockSourceFile); + + expect(result).toHaveLength(3); + }); +}); From b09aa579b01682e6ea2eb6e885d687a3de2d01e6 Mon Sep 17 00:00:00 2001 From: Alejandro Date: Mon, 23 Dec 2024 22:26:10 +0100 Subject: [PATCH 38/66] chore: fix scope of sourceGlob --- code-pushup.config.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/code-pushup.config.ts b/code-pushup.config.ts index e511a1d76..c891c7a99 100644 --- a/code-pushup.config.ts +++ b/code-pushup.config.ts @@ -43,10 +43,11 @@ export default mergeConfigs( await docCoverageCoreConfig({ sourceGlob: [ 'packages/**/src/**/*.ts', + '!packages/**/node_modules', + '!packages/**/{mocks,mocks}', '!**/*.{spec,test}.ts', '!**/implementation/**', '!**/internal/**', ], - skipAudits: ['methods-coverage'], }), ); From b9f5333a1e99b49656a5e1b3a7c48046648b7583 Mon Sep 17 00:00:00 2001 From: Alejandro Date: Mon, 23 Dec 2024 22:26:30 +0100 Subject: [PATCH 39/66] fix: mock word --- code-pushup.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code-pushup.config.ts b/code-pushup.config.ts index c891c7a99..b48295452 100644 --- a/code-pushup.config.ts +++ b/code-pushup.config.ts @@ -44,7 +44,7 @@ export default mergeConfigs( sourceGlob: [ 'packages/**/src/**/*.ts', '!packages/**/node_modules', - '!packages/**/{mocks,mocks}', + '!packages/**/{mocks,mock}', '!**/*.{spec,test}.ts', '!**/implementation/**', '!**/internal/**', From 6f0f19c2f2e2a34e83ad2afc0b842dc0ac72eb4d Mon Sep 17 00:00:00 2001 From: Alejandro Date: Thu, 2 Jan 2025 23:54:57 +0100 Subject: [PATCH 40/66] chore: wips --- e2e/plugin-doc-coverage-e2e/eslint.config.js | 12 + .../fixtures/angular/code-pushup.config.ts | 5 + .../fixtures/angular/src/app.component.css | 4 + .../fixtures/angular/src/app.component.html | 1 + .../angular/src/app.component.spec.ts | 3 + .../fixtures/angular/src/app.component.ts | 18 ++ .../angular/src/map-event.function.ts | 10 + .../fixtures/react/code-pushup.config.ts | 5 + .../mocks/fixtures/react/component.js | 10 + e2e/plugin-doc-coverage-e2e/project.json | 23 ++ .../__snapshots__/collect.e2e.test.ts.snap | 227 ++++++++++++++++++ .../tests/collect.e2e.test.ts | 82 +++++++ e2e/plugin-doc-coverage-e2e/tsconfig.json | 20 ++ .../tsconfig.test.json | 15 ++ .../vite.config.e2e.ts | 21 ++ 15 files changed, 456 insertions(+) create mode 100644 e2e/plugin-doc-coverage-e2e/eslint.config.js create mode 100644 e2e/plugin-doc-coverage-e2e/mocks/fixtures/angular/code-pushup.config.ts create mode 100644 e2e/plugin-doc-coverage-e2e/mocks/fixtures/angular/src/app.component.css create mode 100644 e2e/plugin-doc-coverage-e2e/mocks/fixtures/angular/src/app.component.html create mode 100644 e2e/plugin-doc-coverage-e2e/mocks/fixtures/angular/src/app.component.spec.ts create mode 100644 e2e/plugin-doc-coverage-e2e/mocks/fixtures/angular/src/app.component.ts create mode 100644 e2e/plugin-doc-coverage-e2e/mocks/fixtures/angular/src/map-event.function.ts create mode 100644 e2e/plugin-doc-coverage-e2e/mocks/fixtures/react/code-pushup.config.ts create mode 100644 e2e/plugin-doc-coverage-e2e/mocks/fixtures/react/component.js create mode 100644 e2e/plugin-doc-coverage-e2e/project.json create mode 100644 e2e/plugin-doc-coverage-e2e/tests/__snapshots__/collect.e2e.test.ts.snap create mode 100644 e2e/plugin-doc-coverage-e2e/tests/collect.e2e.test.ts create mode 100644 e2e/plugin-doc-coverage-e2e/tsconfig.json create mode 100644 e2e/plugin-doc-coverage-e2e/tsconfig.test.json create mode 100644 e2e/plugin-doc-coverage-e2e/vite.config.e2e.ts diff --git a/e2e/plugin-doc-coverage-e2e/eslint.config.js b/e2e/plugin-doc-coverage-e2e/eslint.config.js new file mode 100644 index 000000000..2656b27cb --- /dev/null +++ b/e2e/plugin-doc-coverage-e2e/eslint.config.js @@ -0,0 +1,12 @@ +import tseslint from 'typescript-eslint'; +import baseConfig from '../../eslint.config.js'; + +export default tseslint.config(...baseConfig, { + files: ['**/*.ts'], + languageOptions: { + parserOptions: { + projectService: true, + tsconfigRootDir: import.meta.dirname, + }, + }, +}); diff --git a/e2e/plugin-doc-coverage-e2e/mocks/fixtures/angular/code-pushup.config.ts b/e2e/plugin-doc-coverage-e2e/mocks/fixtures/angular/code-pushup.config.ts new file mode 100644 index 000000000..ba479e9f5 --- /dev/null +++ b/e2e/plugin-doc-coverage-e2e/mocks/fixtures/angular/code-pushup.config.ts @@ -0,0 +1,5 @@ +import docCoveragePlugin from '@code-pushup/doc-coverage-plugin'; + +export default { + plugins: [await docCoveragePlugin({ sourceGlob: ['**/*.ts'] })], +}; diff --git a/e2e/plugin-doc-coverage-e2e/mocks/fixtures/angular/src/app.component.css b/e2e/plugin-doc-coverage-e2e/mocks/fixtures/angular/src/app.component.css new file mode 100644 index 000000000..f3958f2b4 --- /dev/null +++ b/e2e/plugin-doc-coverage-e2e/mocks/fixtures/angular/src/app.component.css @@ -0,0 +1,4 @@ +h1 { + color: #336699; + text-align: center; +} diff --git a/e2e/plugin-doc-coverage-e2e/mocks/fixtures/angular/src/app.component.html b/e2e/plugin-doc-coverage-e2e/mocks/fixtures/angular/src/app.component.html new file mode 100644 index 000000000..b6515528b --- /dev/null +++ b/e2e/plugin-doc-coverage-e2e/mocks/fixtures/angular/src/app.component.html @@ -0,0 +1 @@ +

{{ title }}

diff --git a/e2e/plugin-doc-coverage-e2e/mocks/fixtures/angular/src/app.component.spec.ts b/e2e/plugin-doc-coverage-e2e/mocks/fixtures/angular/src/app.component.spec.ts new file mode 100644 index 000000000..c89f47dd8 --- /dev/null +++ b/e2e/plugin-doc-coverage-e2e/mocks/fixtures/angular/src/app.component.spec.ts @@ -0,0 +1,3 @@ +function notRealisticFunction() { + return 'notRealisticFunction'; +} diff --git a/e2e/plugin-doc-coverage-e2e/mocks/fixtures/angular/src/app.component.ts b/e2e/plugin-doc-coverage-e2e/mocks/fixtures/angular/src/app.component.ts new file mode 100644 index 000000000..2fc2b165f --- /dev/null +++ b/e2e/plugin-doc-coverage-e2e/mocks/fixtures/angular/src/app.component.ts @@ -0,0 +1,18 @@ +/** + * Basic Angular component that displays a welcome message + */ +export class AppComponent { + protected readonly title = 'My Angular App'; + + /** + * Dummy method that returns a welcome message + * @returns {string} - The welcome message + */ + getWelcomeMessage() { + return 'Welcome to My Angular App!'; + } + + sendEvent() { + return 'Event sent'; + } +} diff --git a/e2e/plugin-doc-coverage-e2e/mocks/fixtures/angular/src/map-event.function.ts b/e2e/plugin-doc-coverage-e2e/mocks/fixtures/angular/src/map-event.function.ts new file mode 100644 index 000000000..55f343e7c --- /dev/null +++ b/e2e/plugin-doc-coverage-e2e/mocks/fixtures/angular/src/map-event.function.ts @@ -0,0 +1,10 @@ +export const someVariable = 'Hello World 1'; + +export function mapEventToCustomEvent(event: string) { + return event; +} + +/** Commented */ +export function mapCustomEventToEvent(event: string) { + return event; +} diff --git a/e2e/plugin-doc-coverage-e2e/mocks/fixtures/react/code-pushup.config.ts b/e2e/plugin-doc-coverage-e2e/mocks/fixtures/react/code-pushup.config.ts new file mode 100644 index 000000000..ba479e9f5 --- /dev/null +++ b/e2e/plugin-doc-coverage-e2e/mocks/fixtures/react/code-pushup.config.ts @@ -0,0 +1,5 @@ +import docCoveragePlugin from '@code-pushup/doc-coverage-plugin'; + +export default { + plugins: [await docCoveragePlugin({ sourceGlob: ['**/*.ts'] })], +}; diff --git a/e2e/plugin-doc-coverage-e2e/mocks/fixtures/react/component.js b/e2e/plugin-doc-coverage-e2e/mocks/fixtures/react/component.js new file mode 100644 index 000000000..dfa1336e3 --- /dev/null +++ b/e2e/plugin-doc-coverage-e2e/mocks/fixtures/react/component.js @@ -0,0 +1,10 @@ +function MyComponent() { + return ( +
+

Hello World

+

This is a basic React component

+
+ ); +} + +export default MyComponent; diff --git a/e2e/plugin-doc-coverage-e2e/project.json b/e2e/plugin-doc-coverage-e2e/project.json new file mode 100644 index 000000000..3932705af --- /dev/null +++ b/e2e/plugin-doc-coverage-e2e/project.json @@ -0,0 +1,23 @@ +{ + "name": "plugin-doc-coverage-e2e", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "e2e/plugin-doc-coverage-e2e/src", + "projectType": "application", + "targets": { + "lint": { + "executor": "@nx/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": ["e2e/plugin-doc-coverage-e2e/**/*.ts"] + } + }, + "e2e": { + "executor": "@nx/vite:test", + "options": { + "configFile": "e2e/plugin-doc-coverage-e2e/vite.config.e2e.ts" + } + } + }, + "implicitDependencies": ["cli", "plugin-doc-coverage"], + "tags": ["scope:plugin", "type:e2e"] +} diff --git a/e2e/plugin-doc-coverage-e2e/tests/__snapshots__/collect.e2e.test.ts.snap b/e2e/plugin-doc-coverage-e2e/tests/__snapshots__/collect.e2e.test.ts.snap new file mode 100644 index 000000000..b760fc30e --- /dev/null +++ b/e2e/plugin-doc-coverage-e2e/tests/__snapshots__/collect.e2e.test.ts.snap @@ -0,0 +1,227 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`PLUGIN collect report with eslint-plugin NPM package > should run ESLint plugin for flat config and create report.json 1`] = ` +{ + "packageName": "@code-pushup/core", + "plugins": [ + { + "audits": [ + { + "description": "ESLint rule **eqeqeq**.", + "details": { + "issues": [ + { + "message": "Expected '===' and instead saw '=='.", + "severity": "error", + "source": { + "file": "tmp/e2e/plugin-doc-coverage-e2e/__test__/flat-config/src/index.js", + "position": { + "endColumn": 15, + "endLine": 6, + "startColumn": 13, + "startLine": 6, + }, + }, + }, + ], + }, + "displayValue": "1 error", + "docsUrl": "https://eslint.org/docs/latest/rules/eqeqeq", + "score": 0, + "slug": "eqeqeq", + "title": "Require the use of \`===\` and \`!==\`", + "value": 1, + }, + { + "description": "ESLint rule **max-lines**. + +Custom options: + +\`\`\`json +100 +\`\`\`", + "details": { + "issues": [], + }, + "displayValue": "passed", + "docsUrl": "https://eslint.org/docs/latest/rules/max-lines", + "score": 1, + "slug": "max-lines-71b54366cb01f77b", + "title": "Enforce a maximum number of lines per file", + "value": 0, + }, + { + "description": "ESLint rule **no-unused-vars**.", + "details": { + "issues": [ + { + "message": "'unusedFn' is defined but never used.", + "severity": "warning", + "source": { + "file": "tmp/e2e/plugin-doc-coverage-e2e/__test__/flat-config/src/index.js", + "position": { + "endColumn": 18, + "endLine": 1, + "startColumn": 10, + "startLine": 1, + }, + }, + }, + ], + }, + "displayValue": "1 warning", + "docsUrl": "https://eslint.org/docs/latest/rules/no-unused-vars", + "score": 0, + "slug": "no-unused-vars", + "title": "Disallow unused variables", + "value": 1, + }, + ], + "description": "Official Code PushUp ESLint plugin", + "docsUrl": "https://www.npmjs.com/package/@code-pushup/eslint-plugin", + "groups": [ + { + "description": "Code that either will cause an error or may cause confusing behavior. Developers should consider this a high priority to resolve.", + "refs": [ + { + "slug": "no-unused-vars", + "weight": 1, + }, + ], + "slug": "problems", + "title": "Problems", + }, + { + "description": "Something that could be done in a better way but no errors will occur if the code isn't changed.", + "refs": [ + { + "slug": "eqeqeq", + "weight": 1, + }, + { + "slug": "max-lines-71b54366cb01f77b", + "weight": 1, + }, + ], + "slug": "suggestions", + "title": "Suggestions", + }, + ], + "icon": "eslint", + "packageName": "@code-pushup/eslint-plugin", + "slug": "eslint", + "title": "ESLint", + }, + ], +} +`; + +exports[`PLUGIN collect report with eslint-plugin NPM package > should run ESLint plugin for legacy config and create report.json 1`] = ` +{ + "packageName": "@code-pushup/core", + "plugins": [ + { + "audits": [ + { + "description": "ESLint rule **no-unused-vars**.", + "details": { + "issues": [ + { + "message": "'unusedFn' is defined but never used.", + "severity": "error", + "source": { + "file": "tmp/e2e/plugin-doc-coverage-e2e/__test__/legacy-config/src/index.js", + "position": { + "endColumn": 18, + "endLine": 1, + "startColumn": 10, + "startLine": 1, + }, + }, + }, + ], + }, + "displayValue": "1 error", + "docsUrl": "https://eslint.org/docs/latest/rules/no-unused-vars", + "score": 0, + "slug": "no-unused-vars", + "title": "Disallow unused variables", + "value": 1, + }, + { + "description": "ESLint rule **no-console**.", + "details": { + "issues": [ + { + "message": "Unexpected console statement.", + "severity": "warning", + "source": { + "file": "tmp/e2e/plugin-doc-coverage-e2e/__test__/legacy-config/src/index.js", + "position": { + "endColumn": 14, + "endLine": 5, + "startColumn": 3, + "startLine": 5, + }, + }, + }, + ], + }, + "displayValue": "1 warning", + "docsUrl": "https://eslint.org/docs/latest/rules/no-console", + "score": 0, + "slug": "no-console", + "title": "Disallow the use of \`console\`", + "value": 1, + }, + { + "description": "ESLint rule **no-undef**.", + "details": { + "issues": [], + }, + "displayValue": "passed", + "docsUrl": "https://eslint.org/docs/latest/rules/no-undef", + "score": 1, + "slug": "no-undef", + "title": "Disallow the use of undeclared variables unless mentioned in \`/*global */\` comments", + "value": 0, + }, + ], + "description": "Official Code PushUp ESLint plugin", + "docsUrl": "https://www.npmjs.com/package/@code-pushup/eslint-plugin", + "groups": [ + { + "description": "Code that either will cause an error or may cause confusing behavior. Developers should consider this a high priority to resolve.", + "refs": [ + { + "slug": "no-unused-vars", + "weight": 1, + }, + { + "slug": "no-undef", + "weight": 1, + }, + ], + "slug": "problems", + "title": "Problems", + }, + { + "description": "Something that could be done in a better way but no errors will occur if the code isn't changed.", + "refs": [ + { + "slug": "no-console", + "weight": 1, + }, + ], + "slug": "suggestions", + "title": "Suggestions", + }, + ], + "icon": "eslint", + "packageName": "@code-pushup/eslint-plugin", + "slug": "eslint", + "title": "ESLint", + }, + ], +} +`; diff --git a/e2e/plugin-doc-coverage-e2e/tests/collect.e2e.test.ts b/e2e/plugin-doc-coverage-e2e/tests/collect.e2e.test.ts new file mode 100644 index 000000000..3c4baaf07 --- /dev/null +++ b/e2e/plugin-doc-coverage-e2e/tests/collect.e2e.test.ts @@ -0,0 +1,82 @@ +import { cp } from 'node:fs/promises'; +import path from 'node:path'; +import { afterAll, afterEach, beforeAll, describe, expect, it } from 'vitest'; +import { type Report, reportSchema } from '@code-pushup/models'; +import { nxTargetProject } from '@code-pushup/test-nx-utils'; +import { teardownTestFolder } from '@code-pushup/test-setup'; +import { + E2E_ENVIRONMENTS_DIR, + TEST_OUTPUT_DIR, + omitVariableReportData, +} from '@code-pushup/test-utils'; +import { executeProcess, readJsonFile } from '@code-pushup/utils'; + +describe('PLUGIN collect report with doc-coverage-plugin NPM package', () => { + const fixturesDir = path.join( + 'e2e', + 'plugin-doc-coverage-e2e', + 'mocks', + 'fixtures', + ); + const fixturesAngularDir = path.join(fixturesDir, 'angular'); + const fixturesReactDir = path.join(fixturesDir, 'react'); + + const envRoot = path.join( + E2E_ENVIRONMENTS_DIR, + nxTargetProject(), + TEST_OUTPUT_DIR, + ); + const angularDir = path.join(envRoot, 'angular'); + const reactDir = path.join(envRoot, 'react'); + const angularOutputDir = path.join(angularDir, '.code-pushup'); + const reactOutputDir = path.join(reactDir, '.code-pushup'); + + beforeAll(async () => { + await cp(fixturesAngularDir, angularDir, { recursive: true }); + await cp(fixturesReactDir, reactDir, { recursive: true }); + }); + + afterAll(async () => { + await teardownTestFolder(angularDir); + await teardownTestFolder(reactDir); + }); + + afterEach(async () => { + await teardownTestFolder(angularOutputDir); + await teardownTestFolder(reactOutputDir); + }); + + it('should run Doc Coverage plugin for Angular example dir and create report.json', async () => { + const { code, stderr } = await executeProcess({ + command: 'npx', + args: ['@code-pushup/cli', 'collect', '--no-progress'], + cwd: angularDir, + }); + + expect(code).toBe(0); + expect(stderr).toBe(''); + + const report = await readJsonFile( + path.join(angularOutputDir, 'report.json'), + ); + + expect(() => reportSchema.parse(report)).not.toThrow(); + expect(omitVariableReportData(report as Report)).toMatchSnapshot(); + }); + + it('should run Doc Coverage plugin for React example dir and create report.json', async () => { + const { code, stderr } = await executeProcess({ + command: 'npx', + args: ['@code-pushup/cli', 'collect', '--no-progress'], + cwd: reactDir, + }); + + expect(code).toBe(0); + expect(stderr).toBe(''); + + const report = await readJsonFile(path.join(reactOutputDir, 'report.json')); + + expect(() => reportSchema.parse(report)).not.toThrow(); + expect(omitVariableReportData(report as Report)).toMatchSnapshot(); + }); +}); diff --git a/e2e/plugin-doc-coverage-e2e/tsconfig.json b/e2e/plugin-doc-coverage-e2e/tsconfig.json new file mode 100644 index 000000000..f5a2f890a --- /dev/null +++ b/e2e/plugin-doc-coverage-e2e/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "module": "ESNext", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "types": ["vitest"] + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.test.json" + } + ] +} diff --git a/e2e/plugin-doc-coverage-e2e/tsconfig.test.json b/e2e/plugin-doc-coverage-e2e/tsconfig.test.json new file mode 100644 index 000000000..34f35e30f --- /dev/null +++ b/e2e/plugin-doc-coverage-e2e/tsconfig.test.json @@ -0,0 +1,15 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "types": ["vitest/globals", "vitest/importMeta", "vite/client", "node"], + "target": "ES2020" + }, + "exclude": ["__test-env__/**"], + "include": [ + "vite.config.e2e.ts", + "tests/**/*.e2e.test.ts", + "tests/**/*.d.ts", + "mocks/**/*.ts" + ] +} diff --git a/e2e/plugin-doc-coverage-e2e/vite.config.e2e.ts b/e2e/plugin-doc-coverage-e2e/vite.config.e2e.ts new file mode 100644 index 000000000..510819007 --- /dev/null +++ b/e2e/plugin-doc-coverage-e2e/vite.config.e2e.ts @@ -0,0 +1,21 @@ +/// +import { defineConfig } from 'vite'; +import { tsconfigPathAliases } from '../../tools/vitest-tsconfig-path-aliases.js'; + +export default defineConfig({ + cacheDir: '../../node_modules/.vite/plugin-doc-coverage-e2e', + test: { + reporters: ['basic'], + testTimeout: 120_000, + globals: true, + alias: tsconfigPathAliases(), + pool: 'threads', + poolOptions: { threads: { singleThread: true } }, + cache: { + dir: '../../node_modules/.vitest', + }, + environment: 'node', + include: ['tests/**/*.e2e.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + setupFiles: ['../../testing/test-setup/src/lib/reset.mocks.ts'], + }, +}); From 6634f3c39fa15358934bf489a7532fa61ea98bfa Mon Sep 17 00:00:00 2001 From: Michael Date: Fri, 3 Jan 2025 00:14:30 +0100 Subject: [PATCH 41/66] fix e2e --- .../__snapshots__/collect.e2e.test.ts.snap | 256 ++++++++---------- .../tests/collect.e2e.test.ts | 16 -- 2 files changed, 115 insertions(+), 157 deletions(-) diff --git a/e2e/plugin-doc-coverage-e2e/tests/__snapshots__/collect.e2e.test.ts.snap b/e2e/plugin-doc-coverage-e2e/tests/__snapshots__/collect.e2e.test.ts.snap index b760fc30e..66ad36a44 100644 --- a/e2e/plugin-doc-coverage-e2e/tests/__snapshots__/collect.e2e.test.ts.snap +++ b/e2e/plugin-doc-coverage-e2e/tests/__snapshots__/collect.e2e.test.ts.snap @@ -1,226 +1,200 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`PLUGIN collect report with eslint-plugin NPM package > should run ESLint plugin for flat config and create report.json 1`] = ` +exports[`PLUGIN collect report with doc-coverage-plugin NPM package > should run Doc Coverage plugin for Angular example dir and create report.json 1`] = ` { "packageName": "@code-pushup/core", "plugins": [ { "audits": [ { - "description": "ESLint rule **eqeqeq**.", + "description": "Documentation coverage of enums", + "details": { + "issues": [], + }, + "displayValue": "0 undocumented enums", + "score": 1, + "slug": "enums-coverage", + "title": "Enums coverage", + "value": 0, + }, + { + "description": "Documentation coverage of interfaces", + "details": { + "issues": [], + }, + "displayValue": "0 undocumented interfaces", + "score": 1, + "slug": "interfaces-coverage", + "title": "Interfaces coverage", + "value": 0, + }, + { + "description": "Documentation coverage of types", + "details": { + "issues": [], + }, + "displayValue": "0 undocumented types", + "score": 1, + "slug": "types-coverage", + "title": "Types coverage", + "value": 0, + }, + { + "description": "Documentation coverage of functions", "details": { "issues": [ { - "message": "Expected '===' and instead saw '=='.", - "severity": "error", + "message": "Missing documentation", + "severity": "warning", + "source": { + "file": "tmp/e2e/plugin-doc-coverage-e2e/__test__/angular/src/app.component.spec.ts", + "position": { + "startLine": 1, + }, + }, + }, + { + "message": "Missing documentation", + "severity": "warning", "source": { - "file": "tmp/e2e/plugin-doc-coverage-e2e/__test__/flat-config/src/index.js", + "file": "tmp/e2e/plugin-doc-coverage-e2e/__test__/angular/src/map-event.function.ts", "position": { - "endColumn": 15, - "endLine": 6, - "startColumn": 13, - "startLine": 6, + "startLine": 3, }, }, }, ], }, - "displayValue": "1 error", - "docsUrl": "https://eslint.org/docs/latest/rules/eqeqeq", - "score": 0, - "slug": "eqeqeq", - "title": "Require the use of \`===\` and \`!==\`", - "value": 1, + "displayValue": "2 undocumented functions", + "score": 0.3333, + "slug": "functions-coverage", + "title": "Functions coverage", + "value": 2, }, { - "description": "ESLint rule **max-lines**. - -Custom options: - -\`\`\`json -100 -\`\`\`", - "details": { - "issues": [], - }, - "displayValue": "passed", - "docsUrl": "https://eslint.org/docs/latest/rules/max-lines", - "score": 1, - "slug": "max-lines-71b54366cb01f77b", - "title": "Enforce a maximum number of lines per file", - "value": 0, - }, - { - "description": "ESLint rule **no-unused-vars**.", + "description": "Documentation coverage of variables", "details": { "issues": [ { - "message": "'unusedFn' is defined but never used.", + "message": "Missing documentation", "severity": "warning", "source": { - "file": "tmp/e2e/plugin-doc-coverage-e2e/__test__/flat-config/src/index.js", + "file": "tmp/e2e/plugin-doc-coverage-e2e/__test__/angular/src/map-event.function.ts", "position": { - "endColumn": 18, - "endLine": 1, - "startColumn": 10, "startLine": 1, }, }, }, ], }, - "displayValue": "1 warning", - "docsUrl": "https://eslint.org/docs/latest/rules/no-unused-vars", + "displayValue": "1 undocumented variables", "score": 0, - "slug": "no-unused-vars", - "title": "Disallow unused variables", + "slug": "variables-coverage", + "title": "Variables coverage", "value": 1, }, - ], - "description": "Official Code PushUp ESLint plugin", - "docsUrl": "https://www.npmjs.com/package/@code-pushup/eslint-plugin", - "groups": [ - { - "description": "Code that either will cause an error or may cause confusing behavior. Developers should consider this a high priority to resolve.", - "refs": [ - { - "slug": "no-unused-vars", - "weight": 1, - }, - ], - "slug": "problems", - "title": "Problems", - }, { - "description": "Something that could be done in a better way but no errors will occur if the code isn't changed.", - "refs": [ - { - "slug": "eqeqeq", - "weight": 1, - }, - { - "slug": "max-lines-71b54366cb01f77b", - "weight": 1, - }, - ], - "slug": "suggestions", - "title": "Suggestions", + "description": "Documentation coverage of classes", + "details": { + "issues": [], + }, + "displayValue": "0 undocumented classes", + "score": 1, + "slug": "classes-coverage", + "title": "Classes coverage", + "value": 0, }, - ], - "icon": "eslint", - "packageName": "@code-pushup/eslint-plugin", - "slug": "eslint", - "title": "ESLint", - }, - ], -} -`; - -exports[`PLUGIN collect report with eslint-plugin NPM package > should run ESLint plugin for legacy config and create report.json 1`] = ` -{ - "packageName": "@code-pushup/core", - "plugins": [ - { - "audits": [ { - "description": "ESLint rule **no-unused-vars**.", + "description": "Documentation coverage of methods", "details": { "issues": [ { - "message": "'unusedFn' is defined but never used.", - "severity": "error", + "message": "Missing documentation", + "severity": "warning", "source": { - "file": "tmp/e2e/plugin-doc-coverage-e2e/__test__/legacy-config/src/index.js", + "file": "tmp/e2e/plugin-doc-coverage-e2e/__test__/angular/src/app.component.ts", "position": { - "endColumn": 18, - "endLine": 1, - "startColumn": 10, - "startLine": 1, + "startLine": 15, }, }, }, ], }, - "displayValue": "1 error", - "docsUrl": "https://eslint.org/docs/latest/rules/no-unused-vars", - "score": 0, - "slug": "no-unused-vars", - "title": "Disallow unused variables", + "displayValue": "1 undocumented methods", + "score": 0.5, + "slug": "methods-coverage", + "title": "Methods coverage", "value": 1, }, { - "description": "ESLint rule **no-console**.", + "description": "Documentation coverage of properties", "details": { "issues": [ { - "message": "Unexpected console statement.", + "message": "Missing documentation", "severity": "warning", "source": { - "file": "tmp/e2e/plugin-doc-coverage-e2e/__test__/legacy-config/src/index.js", + "file": "tmp/e2e/plugin-doc-coverage-e2e/__test__/angular/src/app.component.ts", "position": { - "endColumn": 14, - "endLine": 5, - "startColumn": 3, "startLine": 5, }, }, }, ], }, - "displayValue": "1 warning", - "docsUrl": "https://eslint.org/docs/latest/rules/no-console", + "displayValue": "1 undocumented properties", "score": 0, - "slug": "no-console", - "title": "Disallow the use of \`console\`", + "slug": "properties-coverage", + "title": "Properties coverage", "value": 1, }, - { - "description": "ESLint rule **no-undef**.", - "details": { - "issues": [], - }, - "displayValue": "passed", - "docsUrl": "https://eslint.org/docs/latest/rules/no-undef", - "score": 1, - "slug": "no-undef", - "title": "Disallow the use of undeclared variables unless mentioned in \`/*global */\` comments", - "value": 0, - }, ], - "description": "Official Code PushUp ESLint plugin", - "docsUrl": "https://www.npmjs.com/package/@code-pushup/eslint-plugin", + "description": "Official Code PushUp documentation coverage plugin.", + "docsUrl": "https://www.npmjs.com/package/@code-pushup/doc-coverage-plugin/", "groups": [ { - "description": "Code that either will cause an error or may cause confusing behavior. Developers should consider this a high priority to resolve.", + "description": "Documentation coverage", "refs": [ { - "slug": "no-unused-vars", + "slug": "classes-coverage", + "weight": 2, + }, + { + "slug": "methods-coverage", + "weight": 2, + }, + { + "slug": "functions-coverage", + "weight": 2, + }, + { + "slug": "interfaces-coverage", "weight": 1, }, { - "slug": "no-undef", + "slug": "variables-coverage", + "weight": 1, + }, + { + "slug": "properties-coverage", + "weight": 1, + }, + { + "slug": "types-coverage", "weight": 1, }, - ], - "slug": "problems", - "title": "Problems", - }, - { - "description": "Something that could be done in a better way but no errors will occur if the code isn't changed.", - "refs": [ { - "slug": "no-console", + "slug": "enums-coverage", "weight": 1, }, ], - "slug": "suggestions", - "title": "Suggestions", + "slug": "documentation-coverage", + "title": "Documentation coverage", }, ], - "icon": "eslint", - "packageName": "@code-pushup/eslint-plugin", - "slug": "eslint", - "title": "ESLint", + "icon": "folder-src", + "slug": "doc-coverage", + "title": "Documentation coverage", }, ], } diff --git a/e2e/plugin-doc-coverage-e2e/tests/collect.e2e.test.ts b/e2e/plugin-doc-coverage-e2e/tests/collect.e2e.test.ts index 3c4baaf07..941ac9ce6 100644 --- a/e2e/plugin-doc-coverage-e2e/tests/collect.e2e.test.ts +++ b/e2e/plugin-doc-coverage-e2e/tests/collect.e2e.test.ts @@ -54,7 +54,6 @@ describe('PLUGIN collect report with doc-coverage-plugin NPM package', () => { }); expect(code).toBe(0); - expect(stderr).toBe(''); const report = await readJsonFile( path.join(angularOutputDir, 'report.json'), @@ -64,19 +63,4 @@ describe('PLUGIN collect report with doc-coverage-plugin NPM package', () => { expect(omitVariableReportData(report as Report)).toMatchSnapshot(); }); - it('should run Doc Coverage plugin for React example dir and create report.json', async () => { - const { code, stderr } = await executeProcess({ - command: 'npx', - args: ['@code-pushup/cli', 'collect', '--no-progress'], - cwd: reactDir, - }); - - expect(code).toBe(0); - expect(stderr).toBe(''); - - const report = await readJsonFile(path.join(reactOutputDir, 'report.json')); - - expect(() => reportSchema.parse(report)).not.toThrow(); - expect(omitVariableReportData(report as Report)).toMatchSnapshot(); - }); }); From 5fc371c838c32de62f58859fdab8032267280ae5 Mon Sep 17 00:00:00 2001 From: Alejandro Date: Fri, 3 Jan 2025 00:17:20 +0100 Subject: [PATCH 42/66] chore: nx format --- e2e/plugin-doc-coverage-e2e/tests/collect.e2e.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/e2e/plugin-doc-coverage-e2e/tests/collect.e2e.test.ts b/e2e/plugin-doc-coverage-e2e/tests/collect.e2e.test.ts index 941ac9ce6..7598ee18a 100644 --- a/e2e/plugin-doc-coverage-e2e/tests/collect.e2e.test.ts +++ b/e2e/plugin-doc-coverage-e2e/tests/collect.e2e.test.ts @@ -62,5 +62,4 @@ describe('PLUGIN collect report with doc-coverage-plugin NPM package', () => { expect(() => reportSchema.parse(report)).not.toThrow(); expect(omitVariableReportData(report as Report)).toMatchSnapshot(); }); - }); From b609608a2912cdabfb0d4c0bb7bdca544099efd2 Mon Sep 17 00:00:00 2001 From: Alejandro Date: Fri, 3 Jan 2025 01:22:41 +0100 Subject: [PATCH 43/66] test(plugin-doc-coverage): create better test of integration --- .../mocks/fixtures/angular/app.component.css | 4 - .../mocks/fixtures/angular/app.component.html | 1 - .../fixtures/angular/app.component.spec.ts | 3 - .../mocks/fixtures/angular/app.component.ts | 18 -- .../fixtures/angular/map-event.function.ts | 10 - .../filled-documentation/classes-coverage.ts | 4 + .../filled-documentation/enums-coverage.ts | 4 + .../interfaces-coverage.ts | 4 + .../filled-documentation/methods-coverage.ts | 10 + .../properties-coverage.ts | 8 + .../filled-documentation/types-coverage.ts | 4 + .../variables-coverage.ts | 4 + .../missing-documentation/classes-coverage.ts | 1 + .../missing-documentation/enums-coverage.ts | 1 + .../functions-coverage.ts | 3 + .../interfaces-coverage.ts | 1 + .../missing-documentation/methods-coverage.ts | 5 + .../properties-coverage.ts | 3 + .../missing-documentation/types-coverage.ts | 1 + .../variables-coverage.ts | 1 + .../mocks/fixtures/react/component.js | 10 - .../runner/doc-processer.integration.test.ts | 256 +++++++++++++++--- .../src/lib/runner/runner.integration.test.ts | 116 ++++++++ 23 files changed, 389 insertions(+), 83 deletions(-) delete mode 100644 packages/plugin-doc-coverage/mocks/fixtures/angular/app.component.css delete mode 100644 packages/plugin-doc-coverage/mocks/fixtures/angular/app.component.html delete mode 100644 packages/plugin-doc-coverage/mocks/fixtures/angular/app.component.spec.ts delete mode 100644 packages/plugin-doc-coverage/mocks/fixtures/angular/app.component.ts delete mode 100644 packages/plugin-doc-coverage/mocks/fixtures/angular/map-event.function.ts create mode 100644 packages/plugin-doc-coverage/mocks/fixtures/filled-documentation/classes-coverage.ts create mode 100644 packages/plugin-doc-coverage/mocks/fixtures/filled-documentation/enums-coverage.ts create mode 100644 packages/plugin-doc-coverage/mocks/fixtures/filled-documentation/interfaces-coverage.ts create mode 100644 packages/plugin-doc-coverage/mocks/fixtures/filled-documentation/methods-coverage.ts create mode 100644 packages/plugin-doc-coverage/mocks/fixtures/filled-documentation/properties-coverage.ts create mode 100644 packages/plugin-doc-coverage/mocks/fixtures/filled-documentation/types-coverage.ts create mode 100644 packages/plugin-doc-coverage/mocks/fixtures/filled-documentation/variables-coverage.ts create mode 100644 packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/classes-coverage.ts create mode 100644 packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/enums-coverage.ts create mode 100644 packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/functions-coverage.ts create mode 100644 packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/interfaces-coverage.ts create mode 100644 packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/methods-coverage.ts create mode 100644 packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/properties-coverage.ts create mode 100644 packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/types-coverage.ts create mode 100644 packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/variables-coverage.ts delete mode 100644 packages/plugin-doc-coverage/mocks/fixtures/react/component.js create mode 100644 packages/plugin-doc-coverage/src/lib/runner/runner.integration.test.ts diff --git a/packages/plugin-doc-coverage/mocks/fixtures/angular/app.component.css b/packages/plugin-doc-coverage/mocks/fixtures/angular/app.component.css deleted file mode 100644 index f3958f2b4..000000000 --- a/packages/plugin-doc-coverage/mocks/fixtures/angular/app.component.css +++ /dev/null @@ -1,4 +0,0 @@ -h1 { - color: #336699; - text-align: center; -} diff --git a/packages/plugin-doc-coverage/mocks/fixtures/angular/app.component.html b/packages/plugin-doc-coverage/mocks/fixtures/angular/app.component.html deleted file mode 100644 index b6515528b..000000000 --- a/packages/plugin-doc-coverage/mocks/fixtures/angular/app.component.html +++ /dev/null @@ -1 +0,0 @@ -

{{ title }}

diff --git a/packages/plugin-doc-coverage/mocks/fixtures/angular/app.component.spec.ts b/packages/plugin-doc-coverage/mocks/fixtures/angular/app.component.spec.ts deleted file mode 100644 index c89f47dd8..000000000 --- a/packages/plugin-doc-coverage/mocks/fixtures/angular/app.component.spec.ts +++ /dev/null @@ -1,3 +0,0 @@ -function notRealisticFunction() { - return 'notRealisticFunction'; -} diff --git a/packages/plugin-doc-coverage/mocks/fixtures/angular/app.component.ts b/packages/plugin-doc-coverage/mocks/fixtures/angular/app.component.ts deleted file mode 100644 index 2fc2b165f..000000000 --- a/packages/plugin-doc-coverage/mocks/fixtures/angular/app.component.ts +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Basic Angular component that displays a welcome message - */ -export class AppComponent { - protected readonly title = 'My Angular App'; - - /** - * Dummy method that returns a welcome message - * @returns {string} - The welcome message - */ - getWelcomeMessage() { - return 'Welcome to My Angular App!'; - } - - sendEvent() { - return 'Event sent'; - } -} diff --git a/packages/plugin-doc-coverage/mocks/fixtures/angular/map-event.function.ts b/packages/plugin-doc-coverage/mocks/fixtures/angular/map-event.function.ts deleted file mode 100644 index 55f343e7c..000000000 --- a/packages/plugin-doc-coverage/mocks/fixtures/angular/map-event.function.ts +++ /dev/null @@ -1,10 +0,0 @@ -export const someVariable = 'Hello World 1'; - -export function mapEventToCustomEvent(event: string) { - return event; -} - -/** Commented */ -export function mapCustomEventToEvent(event: string) { - return event; -} diff --git a/packages/plugin-doc-coverage/mocks/fixtures/filled-documentation/classes-coverage.ts b/packages/plugin-doc-coverage/mocks/fixtures/filled-documentation/classes-coverage.ts new file mode 100644 index 000000000..861060a00 --- /dev/null +++ b/packages/plugin-doc-coverage/mocks/fixtures/filled-documentation/classes-coverage.ts @@ -0,0 +1,4 @@ +/** + * A class that serves as an example for documentation coverage testing + */ +export class ExampleClass {} diff --git a/packages/plugin-doc-coverage/mocks/fixtures/filled-documentation/enums-coverage.ts b/packages/plugin-doc-coverage/mocks/fixtures/filled-documentation/enums-coverage.ts new file mode 100644 index 000000000..1c65b280c --- /dev/null +++ b/packages/plugin-doc-coverage/mocks/fixtures/filled-documentation/enums-coverage.ts @@ -0,0 +1,4 @@ +/** + * Example enumeration representing different states or options + */ +export enum ExampleEnum {} diff --git a/packages/plugin-doc-coverage/mocks/fixtures/filled-documentation/interfaces-coverage.ts b/packages/plugin-doc-coverage/mocks/fixtures/filled-documentation/interfaces-coverage.ts new file mode 100644 index 000000000..067665a85 --- /dev/null +++ b/packages/plugin-doc-coverage/mocks/fixtures/filled-documentation/interfaces-coverage.ts @@ -0,0 +1,4 @@ +/** + * Interface representing a basic data structure + */ +export interface ExampleInterface {} diff --git a/packages/plugin-doc-coverage/mocks/fixtures/filled-documentation/methods-coverage.ts b/packages/plugin-doc-coverage/mocks/fixtures/filled-documentation/methods-coverage.ts new file mode 100644 index 000000000..1905f962b --- /dev/null +++ b/packages/plugin-doc-coverage/mocks/fixtures/filled-documentation/methods-coverage.ts @@ -0,0 +1,10 @@ +/** Example class */ +export class Container { + /** + * An example method that returns a string + * @returns A string with the value 'exampleMethod' + */ + exampleMethod(): string { + return 'exampleMethod'; + } +} diff --git a/packages/plugin-doc-coverage/mocks/fixtures/filled-documentation/properties-coverage.ts b/packages/plugin-doc-coverage/mocks/fixtures/filled-documentation/properties-coverage.ts new file mode 100644 index 000000000..85ffc1ce3 --- /dev/null +++ b/packages/plugin-doc-coverage/mocks/fixtures/filled-documentation/properties-coverage.ts @@ -0,0 +1,8 @@ +/** Example class */ +export class ExampleWithProperties { + /** + * Internal identifier that can only be accessed within this class + * @private + */ + private readonly internalId = '123'; +} diff --git a/packages/plugin-doc-coverage/mocks/fixtures/filled-documentation/types-coverage.ts b/packages/plugin-doc-coverage/mocks/fixtures/filled-documentation/types-coverage.ts new file mode 100644 index 000000000..b45c56eeb --- /dev/null +++ b/packages/plugin-doc-coverage/mocks/fixtures/filled-documentation/types-coverage.ts @@ -0,0 +1,4 @@ +/** + * A simple union type that can be either string or number + */ +export type ExampleType = string | number; diff --git a/packages/plugin-doc-coverage/mocks/fixtures/filled-documentation/variables-coverage.ts b/packages/plugin-doc-coverage/mocks/fixtures/filled-documentation/variables-coverage.ts new file mode 100644 index 000000000..e77a9afe8 --- /dev/null +++ b/packages/plugin-doc-coverage/mocks/fixtures/filled-documentation/variables-coverage.ts @@ -0,0 +1,4 @@ +/** + * A constant string variable used for example purposes + */ +export const exampleVariable = 'example'; diff --git a/packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/classes-coverage.ts b/packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/classes-coverage.ts new file mode 100644 index 000000000..5be78a174 --- /dev/null +++ b/packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/classes-coverage.ts @@ -0,0 +1 @@ +export class ExampleClass {} diff --git a/packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/enums-coverage.ts b/packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/enums-coverage.ts new file mode 100644 index 000000000..80bd662db --- /dev/null +++ b/packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/enums-coverage.ts @@ -0,0 +1 @@ +export enum ExampleEnum {} diff --git a/packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/functions-coverage.ts b/packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/functions-coverage.ts new file mode 100644 index 000000000..6f54d9672 --- /dev/null +++ b/packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/functions-coverage.ts @@ -0,0 +1,3 @@ +export function exampleFunction() { + return 'exampleFunction'; +} diff --git a/packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/interfaces-coverage.ts b/packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/interfaces-coverage.ts new file mode 100644 index 000000000..0c082001b --- /dev/null +++ b/packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/interfaces-coverage.ts @@ -0,0 +1 @@ +export interface ExampleInterface {} diff --git a/packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/methods-coverage.ts b/packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/methods-coverage.ts new file mode 100644 index 000000000..6c88053ec --- /dev/null +++ b/packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/methods-coverage.ts @@ -0,0 +1,5 @@ +export class Container { + exampleMethod(): string { + return 'exampleMethod'; + } +} diff --git a/packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/properties-coverage.ts b/packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/properties-coverage.ts new file mode 100644 index 000000000..339771042 --- /dev/null +++ b/packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/properties-coverage.ts @@ -0,0 +1,3 @@ +export class ExampleWithProperties { + private readonly internalId = '123'; +} diff --git a/packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/types-coverage.ts b/packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/types-coverage.ts new file mode 100644 index 000000000..b3c58ae9a --- /dev/null +++ b/packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/types-coverage.ts @@ -0,0 +1 @@ +export type ExampleType = string | number; diff --git a/packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/variables-coverage.ts b/packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/variables-coverage.ts new file mode 100644 index 000000000..19084a27d --- /dev/null +++ b/packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/variables-coverage.ts @@ -0,0 +1 @@ +export const exampleVariable = 'example'; diff --git a/packages/plugin-doc-coverage/mocks/fixtures/react/component.js b/packages/plugin-doc-coverage/mocks/fixtures/react/component.js deleted file mode 100644 index dfa1336e3..000000000 --- a/packages/plugin-doc-coverage/mocks/fixtures/react/component.js +++ /dev/null @@ -1,10 +0,0 @@ -function MyComponent() { - return ( -
-

Hello World

-

This is a basic React component

-
- ); -} - -export default MyComponent; diff --git a/packages/plugin-doc-coverage/src/lib/runner/doc-processer.integration.test.ts b/packages/plugin-doc-coverage/src/lib/runner/doc-processer.integration.test.ts index 2f8cb4864..64303023f 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/doc-processer.integration.test.ts +++ b/packages/plugin-doc-coverage/src/lib/runner/doc-processer.integration.test.ts @@ -1,65 +1,247 @@ import { processDocCoverage } from './doc-processer.js'; +import type { DocumentationData } from './models.js'; -describe('processDocCoverage', () => { - it('should count total nodes from TypeScript files correctly', () => { +describe('Classes', () => { + it('should detect undocumented class', () => { const sourcePath = - 'packages/plugin-doc-coverage/mocks/fixtures/angular/**/*.ts'; - const expectedNodeCount = 8; - + 'packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/classes-coverage.ts'; const results = processDocCoverage({ sourceGlob: [sourcePath] }); + expect(results.classes).toStrictEqual({ + coverage: 0, + nodesCount: 1, + issues: [ + { + file: expect.stringContaining('classes-coverage'), + type: 'classes', + name: 'ExampleClass', + line: 1, + }, + ], + } satisfies DocumentationData & { + coverage: number; + }); + }); - const totalNodeCount = Object.values(results).reduce( - (acc, node) => acc + node.nodesCount, - 0, - ); - - expect(totalNodeCount).toBe(expectedNodeCount); + it('should detect documented class', () => { + const sourcePath = + 'packages/plugin-doc-coverage/mocks/fixtures/filled-documentation/classes-coverage.ts'; + const results = processDocCoverage({ sourceGlob: [sourcePath] }); + expect(results.classes).toStrictEqual({ + coverage: 100, + nodesCount: 1, + issues: [], + } satisfies DocumentationData & { + coverage: number; + }); }); +}); - it('should count total nodes from Javascript files correctly', () => { +describe('Methods', () => { + it('should detect undocumented method', () => { const sourcePath = - 'packages/plugin-doc-coverage/mocks/fixtures/react/**/*.js'; - const expectedNodeCount = 1; + 'packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/methods-coverage.ts'; + const results = processDocCoverage({ sourceGlob: [sourcePath] }); + expect(results.methods).toStrictEqual({ + coverage: 0, + nodesCount: 1, + issues: [ + { + file: expect.stringContaining('methods-coverage'), + type: 'methods', + name: 'exampleMethod', + line: 2, + }, + ], + } satisfies DocumentationData & { + coverage: number; + }); + }); + it('should detect documented method', () => { + const sourcePath = + 'packages/plugin-doc-coverage/mocks/fixtures/filled-documentation/methods-coverage.ts'; const results = processDocCoverage({ sourceGlob: [sourcePath] }); + expect(results.methods).toStrictEqual({ + coverage: 100, + nodesCount: 1, + issues: [], + } satisfies DocumentationData & { + coverage: number; + }); + }); +}); - const totalNodeCount = Object.values(results).reduce( - (acc, node) => acc + node.nodesCount, - 0, - ); +describe('Interfaces', () => { + it('should detect undocumented interface', () => { + const sourcePath = + 'packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/interfaces-coverage.ts'; + const results = processDocCoverage({ sourceGlob: [sourcePath] }); + expect(results.interfaces).toStrictEqual({ + coverage: 0, + nodesCount: 1, + issues: [ + { + file: expect.stringContaining('interfaces-coverage'), + type: 'interfaces', + name: 'ExampleInterface', + line: 1, + }, + ], + } satisfies DocumentationData & { + coverage: number; + }); + }); - expect(totalNodeCount).toBe(expectedNodeCount); + it('should detect documented interface', () => { + const sourcePath = + 'packages/plugin-doc-coverage/mocks/fixtures/filled-documentation/interfaces-coverage.ts'; + const results = processDocCoverage({ sourceGlob: [sourcePath] }); + expect(results.interfaces).toStrictEqual({ + coverage: 100, + nodesCount: 1, + issues: [], + } satisfies DocumentationData & { + coverage: number; + }); }); +}); - it('should count total nodes from Javascript and TypeScript files correctly', () => { +describe('Variables', () => { + it('should detect undocumented variable', () => { const sourcePath = - 'packages/plugin-doc-coverage/mocks/fixtures/**/*.{js,ts}'; - const expectedNodeCount = 9; + 'packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/variables-coverage.ts'; + const results = processDocCoverage({ sourceGlob: [sourcePath] }); + expect(results.variables).toStrictEqual({ + coverage: 0, + nodesCount: 1, + issues: [ + { + file: expect.stringContaining('variables-coverage'), + type: 'variables', + name: 'exampleVariable', + line: 1, + }, + ], + } satisfies DocumentationData & { + coverage: number; + }); + }); + it('should detect documented variable', () => { + const sourcePath = + 'packages/plugin-doc-coverage/mocks/fixtures/filled-documentation/variables-coverage.ts'; const results = processDocCoverage({ sourceGlob: [sourcePath] }); + expect(results.variables).toStrictEqual({ + coverage: 100, + nodesCount: 1, + issues: [], + } satisfies DocumentationData & { + coverage: number; + }); + }); +}); - const totalNodeCount = Object.values(results).reduce( - (acc, node) => acc + node.nodesCount, - 0, - ); +describe('Properties', () => { + it('should detect undocumented property', () => { + const sourcePath = + 'packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/properties-coverage.ts'; + const results = processDocCoverage({ sourceGlob: [sourcePath] }); + expect(results.properties).toStrictEqual({ + coverage: 0, + nodesCount: 1, + issues: [ + { + file: expect.stringContaining('properties-coverage'), + type: 'properties', + name: 'internalId', + line: 2, + }, + ], + } satisfies DocumentationData & { + coverage: number; + }); + }); - expect(totalNodeCount).toBe(expectedNodeCount); + it('should detect documented property', () => { + const sourcePath = + 'packages/plugin-doc-coverage/mocks/fixtures/filled-documentation/properties-coverage.ts'; + const results = processDocCoverage({ sourceGlob: [sourcePath] }); + expect(results.properties).toStrictEqual({ + coverage: 100, + nodesCount: 1, + issues: [], + } satisfies DocumentationData & { + coverage: number; + }); }); +}); - it('respect `sourceGlob` and only include matching files', () => { +describe('Types', () => { + it('should detect undocumented type', () => { const sourcePath = - 'packages/plugin-doc-coverage/mocks/fixtures/angular/**/*.ts'; - const expectedNodeCount = 7; + 'packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/types-coverage.ts'; + const results = processDocCoverage({ sourceGlob: [sourcePath] }); + expect(results.types).toStrictEqual({ + coverage: 0, + nodesCount: 1, + issues: [ + { + file: expect.stringContaining('types-coverage'), + type: 'types', + name: 'ExampleType', + line: 1, + }, + ], + } satisfies DocumentationData & { + coverage: number; + }); + }); - const results = processDocCoverage({ - sourceGlob: [sourcePath, '!**/*.spec.ts', '!**/*.test.ts'], + it('should detect documented type', () => { + const sourcePath = + 'packages/plugin-doc-coverage/mocks/fixtures/filled-documentation/types-coverage.ts'; + const results = processDocCoverage({ sourceGlob: [sourcePath] }); + expect(results.types).toStrictEqual({ + coverage: 100, + nodesCount: 1, + issues: [], + } satisfies DocumentationData & { + coverage: number; }); + }); +}); - const totalNodeCount = Object.values(results).reduce( - (acc, node) => acc + node.nodesCount, - 0, - ); +describe('Enums', () => { + it('should detect undocumented enum', () => { + const sourcePath = + 'packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/enums-coverage.ts'; + const results = processDocCoverage({ sourceGlob: [sourcePath] }); + expect(results.enums).toStrictEqual({ + coverage: 0, + nodesCount: 1, + issues: [ + { + file: expect.stringContaining('enums-coverage'), + type: 'enums', + name: 'ExampleEnum', + line: 1, + }, + ], + } satisfies DocumentationData & { + coverage: number; + }); + }); - expect(totalNodeCount).toBe(expectedNodeCount); + it('should detect documented enum', () => { + const sourcePath = + 'packages/plugin-doc-coverage/mocks/fixtures/filled-documentation/enums-coverage.ts'; + const results = processDocCoverage({ sourceGlob: [sourcePath] }); + expect(results.enums).toStrictEqual({ + coverage: 100, + nodesCount: 1, + issues: [], + } satisfies DocumentationData & { + coverage: number; + }); }); }); diff --git a/packages/plugin-doc-coverage/src/lib/runner/runner.integration.test.ts b/packages/plugin-doc-coverage/src/lib/runner/runner.integration.test.ts new file mode 100644 index 000000000..5e96e5f8c --- /dev/null +++ b/packages/plugin-doc-coverage/src/lib/runner/runner.integration.test.ts @@ -0,0 +1,116 @@ +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { describe, expect, it } from 'vitest'; +import { AUDITS_MAP } from '../constants.js'; +import { createRunnerFunction } from './runner.js'; + +describe('createRunnerFunction', () => { + const fixturesDir = path.join( + fileURLToPath(path.dirname(import.meta.url)), + '..', + '..', + '..', + 'mocks', + 'fixtures', + ); + + const undocumentedSourceGlob = [ + path.join(fixturesDir, 'missing-documentation/**/*.ts'), + ]; + const documentedSourceGlob = [ + path.join(fixturesDir, 'filled-documentation/**/*.ts'), + ]; + + it('should generate correct audit outputs for documentation coverage', async () => { + const runnerFn = createRunnerFunction({ + sourceGlob: undocumentedSourceGlob, + }); + + const results = await runnerFn({} as any); + + const expectedAudits = Object.keys(AUDITS_MAP); + + // Verify all expected audits are present + expect(results).toHaveLength(expectedAudits.length); + expect(results.map(r => r.slug).sort()).toEqual(expectedAudits.sort()); + + // Verify structure and content of each audit + results.forEach(audit => { + expect(audit).toMatchObject({ + score: 0, + value: expect.any(Number), + displayValue: expect.stringMatching(/^\d+ undocumented/), + details: { + issues: expect.arrayContaining([ + expect.objectContaining({ + message: 'Missing documentation', + severity: 'warning', + source: { + file: expect.stringContaining( + audit.slug.replace('-coverage', ''), + ), + position: expect.objectContaining({ + startLine: expect.any(Number), + }), + }, + }), + ]), + }, + }); + }); + }); + + it('should respect onlyAudits option', async () => { + const runnerFn = createRunnerFunction({ + sourceGlob: undocumentedSourceGlob, + onlyAudits: ['classes-coverage', 'methods-coverage'], + }); + + const results = await runnerFn({} as any); + + expect(results).toHaveLength(2); + expect(results.map(audit => audit.slug)).toStrictEqual([ + 'classes-coverage', + 'methods-coverage', + ]); + }); + + it('should respect skipAudits option', async () => { + const runnerFn = createRunnerFunction({ + sourceGlob: undocumentedSourceGlob, + skipAudits: ['classes-coverage', 'methods-coverage'], + }); + + const results = await runnerFn({} as any); + + const slugs = results.map(audit => audit.slug); + expect(slugs).not.toContain('classes-coverage'); + expect(slugs).not.toContain('methods-coverage'); + }); + + it('should generate correct audit outputs for undocumented code', async () => { + const runnerFn = createRunnerFunction({ + sourceGlob: undocumentedSourceGlob, + }); + + const results = await runnerFn({} as any); + + results.forEach(audit => { + expect(audit.score).toStrictEqual(0); + expect(audit.details?.issues?.length).toBeGreaterThan(0); // There are multiples classes due to methods and properties + }); + }); + + it('should generate correct audit outputs for documented code', async () => { + const runnerFn = createRunnerFunction({ + sourceGlob: documentedSourceGlob, + }); + + const results = await runnerFn({} as any); + + results.forEach(audit => { + expect(audit.score).toStrictEqual(1); + expect(audit.details?.issues?.length).toStrictEqual(0); + }); + }); +}); From 2dbdb7d1db99bed323ca5c44db096e60005100f5 Mon Sep 17 00:00:00 2001 From: Alejandro Date: Fri, 3 Jan 2025 10:50:05 +0100 Subject: [PATCH 44/66] chore(plugin-doc-coverage): fix lint --- .../src/lib/runner/runner.integration.test.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/packages/plugin-doc-coverage/src/lib/runner/runner.integration.test.ts b/packages/plugin-doc-coverage/src/lib/runner/runner.integration.test.ts index 5e96e5f8c..ab1933ae8 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/runner.integration.test.ts +++ b/packages/plugin-doc-coverage/src/lib/runner/runner.integration.test.ts @@ -28,11 +28,8 @@ describe('createRunnerFunction', () => { const results = await runnerFn({} as any); - const expectedAudits = Object.keys(AUDITS_MAP); - // Verify all expected audits are present - expect(results).toHaveLength(expectedAudits.length); - expect(results.map(r => r.slug).sort()).toEqual(expectedAudits.sort()); + expect(results).toHaveLength(Object.keys(AUDITS_MAP).length); // Verify structure and content of each audit results.forEach(audit => { @@ -96,7 +93,7 @@ describe('createRunnerFunction', () => { const results = await runnerFn({} as any); results.forEach(audit => { - expect(audit.score).toStrictEqual(0); + expect(audit.score).toBe(0); expect(audit.details?.issues?.length).toBeGreaterThan(0); // There are multiples classes due to methods and properties }); }); @@ -109,8 +106,8 @@ describe('createRunnerFunction', () => { const results = await runnerFn({} as any); results.forEach(audit => { - expect(audit.score).toStrictEqual(1); - expect(audit.details?.issues?.length).toStrictEqual(0); + expect(audit.score).toBe(1); + expect(audit.details?.issues?.length).toBe(0); }); }); }); From da133e9a3365da58da983f4431ad64fac5497bb2 Mon Sep 17 00:00:00 2001 From: Alejandro Date: Fri, 3 Jan 2025 11:53:38 +0100 Subject: [PATCH 45/66] chore(plugin-doc-coverage): fix linter e2e --- e2e/plugin-doc-coverage-e2e/tests/collect.e2e.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/plugin-doc-coverage-e2e/tests/collect.e2e.test.ts b/e2e/plugin-doc-coverage-e2e/tests/collect.e2e.test.ts index 7598ee18a..d9506ae7c 100644 --- a/e2e/plugin-doc-coverage-e2e/tests/collect.e2e.test.ts +++ b/e2e/plugin-doc-coverage-e2e/tests/collect.e2e.test.ts @@ -47,7 +47,7 @@ describe('PLUGIN collect report with doc-coverage-plugin NPM package', () => { }); it('should run Doc Coverage plugin for Angular example dir and create report.json', async () => { - const { code, stderr } = await executeProcess({ + const { code } = await executeProcess({ command: 'npx', args: ['@code-pushup/cli', 'collect', '--no-progress'], cwd: angularDir, From 7a80f85e66638a435bd743890b33cff698dc4215 Mon Sep 17 00:00:00 2001 From: Michael Date: Fri, 3 Jan 2025 15:34:41 +0100 Subject: [PATCH 46/66] polish integration tests --- .../__snapshots__/collect.e2e.test.ts.snap | 201 ------------------ .../tests/collect.e2e.test.ts | 21 +- .../properties-coverage.ts | 2 +- .../missing-documentation/methods-coverage.ts | 5 +- .../properties-coverage.ts | 5 +- .../plugin-doc-coverage/mocks/node.mock.ts | 43 ++++ ...mock.generator.ts => source-files.mock.ts} | 42 +--- .../runner/doc-processer.integration.test.ts | 32 +-- .../src/lib/runner/doc-processer.unit.test.ts | 6 +- .../src/lib/runner/runner.integration.test.ts | 140 ++++++------ .../src/lib/runner/runner.ts | 8 +- .../src/lib/runner/runner.unit.test.ts | 12 +- 12 files changed, 166 insertions(+), 351 deletions(-) delete mode 100644 e2e/plugin-doc-coverage-e2e/tests/__snapshots__/collect.e2e.test.ts.snap create mode 100644 packages/plugin-doc-coverage/mocks/node.mock.ts rename packages/plugin-doc-coverage/mocks/{source-files-mock.generator.ts => source-files.mock.ts} (53%) diff --git a/e2e/plugin-doc-coverage-e2e/tests/__snapshots__/collect.e2e.test.ts.snap b/e2e/plugin-doc-coverage-e2e/tests/__snapshots__/collect.e2e.test.ts.snap deleted file mode 100644 index 66ad36a44..000000000 --- a/e2e/plugin-doc-coverage-e2e/tests/__snapshots__/collect.e2e.test.ts.snap +++ /dev/null @@ -1,201 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`PLUGIN collect report with doc-coverage-plugin NPM package > should run Doc Coverage plugin for Angular example dir and create report.json 1`] = ` -{ - "packageName": "@code-pushup/core", - "plugins": [ - { - "audits": [ - { - "description": "Documentation coverage of enums", - "details": { - "issues": [], - }, - "displayValue": "0 undocumented enums", - "score": 1, - "slug": "enums-coverage", - "title": "Enums coverage", - "value": 0, - }, - { - "description": "Documentation coverage of interfaces", - "details": { - "issues": [], - }, - "displayValue": "0 undocumented interfaces", - "score": 1, - "slug": "interfaces-coverage", - "title": "Interfaces coverage", - "value": 0, - }, - { - "description": "Documentation coverage of types", - "details": { - "issues": [], - }, - "displayValue": "0 undocumented types", - "score": 1, - "slug": "types-coverage", - "title": "Types coverage", - "value": 0, - }, - { - "description": "Documentation coverage of functions", - "details": { - "issues": [ - { - "message": "Missing documentation", - "severity": "warning", - "source": { - "file": "tmp/e2e/plugin-doc-coverage-e2e/__test__/angular/src/app.component.spec.ts", - "position": { - "startLine": 1, - }, - }, - }, - { - "message": "Missing documentation", - "severity": "warning", - "source": { - "file": "tmp/e2e/plugin-doc-coverage-e2e/__test__/angular/src/map-event.function.ts", - "position": { - "startLine": 3, - }, - }, - }, - ], - }, - "displayValue": "2 undocumented functions", - "score": 0.3333, - "slug": "functions-coverage", - "title": "Functions coverage", - "value": 2, - }, - { - "description": "Documentation coverage of variables", - "details": { - "issues": [ - { - "message": "Missing documentation", - "severity": "warning", - "source": { - "file": "tmp/e2e/plugin-doc-coverage-e2e/__test__/angular/src/map-event.function.ts", - "position": { - "startLine": 1, - }, - }, - }, - ], - }, - "displayValue": "1 undocumented variables", - "score": 0, - "slug": "variables-coverage", - "title": "Variables coverage", - "value": 1, - }, - { - "description": "Documentation coverage of classes", - "details": { - "issues": [], - }, - "displayValue": "0 undocumented classes", - "score": 1, - "slug": "classes-coverage", - "title": "Classes coverage", - "value": 0, - }, - { - "description": "Documentation coverage of methods", - "details": { - "issues": [ - { - "message": "Missing documentation", - "severity": "warning", - "source": { - "file": "tmp/e2e/plugin-doc-coverage-e2e/__test__/angular/src/app.component.ts", - "position": { - "startLine": 15, - }, - }, - }, - ], - }, - "displayValue": "1 undocumented methods", - "score": 0.5, - "slug": "methods-coverage", - "title": "Methods coverage", - "value": 1, - }, - { - "description": "Documentation coverage of properties", - "details": { - "issues": [ - { - "message": "Missing documentation", - "severity": "warning", - "source": { - "file": "tmp/e2e/plugin-doc-coverage-e2e/__test__/angular/src/app.component.ts", - "position": { - "startLine": 5, - }, - }, - }, - ], - }, - "displayValue": "1 undocumented properties", - "score": 0, - "slug": "properties-coverage", - "title": "Properties coverage", - "value": 1, - }, - ], - "description": "Official Code PushUp documentation coverage plugin.", - "docsUrl": "https://www.npmjs.com/package/@code-pushup/doc-coverage-plugin/", - "groups": [ - { - "description": "Documentation coverage", - "refs": [ - { - "slug": "classes-coverage", - "weight": 2, - }, - { - "slug": "methods-coverage", - "weight": 2, - }, - { - "slug": "functions-coverage", - "weight": 2, - }, - { - "slug": "interfaces-coverage", - "weight": 1, - }, - { - "slug": "variables-coverage", - "weight": 1, - }, - { - "slug": "properties-coverage", - "weight": 1, - }, - { - "slug": "types-coverage", - "weight": 1, - }, - { - "slug": "enums-coverage", - "weight": 1, - }, - ], - "slug": "documentation-coverage", - "title": "Documentation coverage", - }, - ], - "icon": "folder-src", - "slug": "doc-coverage", - "title": "Documentation coverage", - }, - ], -} -`; diff --git a/e2e/plugin-doc-coverage-e2e/tests/collect.e2e.test.ts b/e2e/plugin-doc-coverage-e2e/tests/collect.e2e.test.ts index d9506ae7c..5f9325e16 100644 --- a/e2e/plugin-doc-coverage-e2e/tests/collect.e2e.test.ts +++ b/e2e/plugin-doc-coverage-e2e/tests/collect.e2e.test.ts @@ -8,8 +8,9 @@ import { E2E_ENVIRONMENTS_DIR, TEST_OUTPUT_DIR, omitVariableReportData, + removeColorCodes, } from '@code-pushup/test-utils'; -import { executeProcess, readJsonFile } from '@code-pushup/utils'; +import { executeProcess, readJsonFile, readTextFile } from '@code-pushup/utils'; describe('PLUGIN collect report with doc-coverage-plugin NPM package', () => { const fixturesDir = path.join( @@ -47,7 +48,7 @@ describe('PLUGIN collect report with doc-coverage-plugin NPM package', () => { }); it('should run Doc Coverage plugin for Angular example dir and create report.json', async () => { - const { code } = await executeProcess({ + const { code, stdout } = await executeProcess({ command: 'npx', args: ['@code-pushup/cli', 'collect', '--no-progress'], cwd: angularDir, @@ -55,11 +56,25 @@ describe('PLUGIN collect report with doc-coverage-plugin NPM package', () => { expect(code).toBe(0); + expect(removeColorCodes(stdout)).toMatchFileSnapshot( + '__snapshots__/report.txt', + ); + const report = await readJsonFile( path.join(angularOutputDir, 'report.json'), ); expect(() => reportSchema.parse(report)).not.toThrow(); - expect(omitVariableReportData(report as Report)).toMatchSnapshot(); + expect( + JSON.stringify(omitVariableReportData(report as Report), null, 2), + ).toMatchFileSnapshot('__snapshots__/report.json'); + + const reportMd = await readTextFile( + path.join(angularOutputDir, 'report.md'), + ); + + expect(reportMd.replace(/## About\.*/gm, '')).toMatchFileSnapshot( + '__snapshots__/report.md', + ); }); }); diff --git a/packages/plugin-doc-coverage/mocks/fixtures/filled-documentation/properties-coverage.ts b/packages/plugin-doc-coverage/mocks/fixtures/filled-documentation/properties-coverage.ts index 85ffc1ce3..df7b35603 100644 --- a/packages/plugin-doc-coverage/mocks/fixtures/filled-documentation/properties-coverage.ts +++ b/packages/plugin-doc-coverage/mocks/fixtures/filled-documentation/properties-coverage.ts @@ -4,5 +4,5 @@ export class ExampleWithProperties { * Internal identifier that can only be accessed within this class * @private */ - private readonly internalId = '123'; + private readonly exampleProperty = '123'; } diff --git a/packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/methods-coverage.ts b/packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/methods-coverage.ts index 6c88053ec..19920937f 100644 --- a/packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/methods-coverage.ts +++ b/packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/methods-coverage.ts @@ -1,4 +1,7 @@ -export class Container { +/** + * A class that serves as an example for documentation coverage testing + */ +export class ExampleClass { exampleMethod(): string { return 'exampleMethod'; } diff --git a/packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/properties-coverage.ts b/packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/properties-coverage.ts index 339771042..48b1254d0 100644 --- a/packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/properties-coverage.ts +++ b/packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/properties-coverage.ts @@ -1,3 +1,6 @@ +/** + * A class that serves as an example for documentation coverage testing + */ export class ExampleWithProperties { - private readonly internalId = '123'; + private readonly exampleProperty = '123'; } diff --git a/packages/plugin-doc-coverage/mocks/node.mock.ts b/packages/plugin-doc-coverage/mocks/node.mock.ts new file mode 100644 index 000000000..15ad64d18 --- /dev/null +++ b/packages/plugin-doc-coverage/mocks/node.mock.ts @@ -0,0 +1,43 @@ +import {SyntaxKind,} from 'ts-morph'; +import type {CoverageType} from '../src/lib/runner/models.js'; + +export function nodeMock(options: { + coverageType: CoverageType; + line: number; + file: string; + isCommented: boolean; +}) { + return { + getKind: () => getKindFromCoverageType(options.coverageType), + getJsDocs: () => (options.isCommented ? ['Comment'] : []), + getName: () => 'test', + getStartLineNumber: () => options.line, + getDeclarations: () => [], + // Only for classes + getMethods: () => [], + getProperties: () => [], + }; +} + +function getKindFromCoverageType(coverageType: CoverageType): SyntaxKind { + switch (coverageType) { + case 'classes': + return SyntaxKind.ClassDeclaration; + case 'methods': + return SyntaxKind.MethodDeclaration; + case 'functions': + return SyntaxKind.FunctionDeclaration; + case 'interfaces': + return SyntaxKind.InterfaceDeclaration; + case 'enums': + return SyntaxKind.EnumDeclaration; + case 'variables': + return SyntaxKind.VariableDeclaration; + case 'properties': + return SyntaxKind.PropertyDeclaration; + case 'types': + return SyntaxKind.TypeAliasDeclaration; + default: + throw new Error(`Unsupported syntax kind: ${coverageType}`); + } +} diff --git a/packages/plugin-doc-coverage/mocks/source-files-mock.generator.ts b/packages/plugin-doc-coverage/mocks/source-files.mock.ts similarity index 53% rename from packages/plugin-doc-coverage/mocks/source-files-mock.generator.ts rename to packages/plugin-doc-coverage/mocks/source-files.mock.ts index 5d700db3c..2b96edb56 100644 --- a/packages/plugin-doc-coverage/mocks/source-files-mock.generator.ts +++ b/packages/plugin-doc-coverage/mocks/source-files.mock.ts @@ -9,6 +9,7 @@ import { VariableStatement, } from 'ts-morph'; import type { CoverageType } from '../src/lib/runner/models.js'; +import {nodeMock} from "./node.mock"; export function sourceFileMock( file: string, @@ -39,44 +40,3 @@ export function sourceFileMock( createNodeGetter('variables', nodes.variables), } as SourceFile; } - -export function nodeMock(options: { - coverageType: CoverageType; - line: number; - file: string; - isCommented: boolean; -}) { - return { - getKind: () => getKindFromCoverageType(options.coverageType), - getJsDocs: () => (options.isCommented ? ['Comment'] : []), - getName: () => 'test', - getStartLineNumber: () => options.line, - getDeclarations: () => [], - // Only for classes - getMethods: () => [], - getProperties: () => [], - }; -} - -function getKindFromCoverageType(coverageType: CoverageType): SyntaxKind { - switch (coverageType) { - case 'classes': - return SyntaxKind.ClassDeclaration; - case 'methods': - return SyntaxKind.MethodDeclaration; - case 'functions': - return SyntaxKind.FunctionDeclaration; - case 'interfaces': - return SyntaxKind.InterfaceDeclaration; - case 'enums': - return SyntaxKind.EnumDeclaration; - case 'variables': - return SyntaxKind.VariableDeclaration; - case 'properties': - return SyntaxKind.PropertyDeclaration; - case 'types': - return SyntaxKind.TypeAliasDeclaration; - default: - throw new Error(`Unsupported syntax kind: ${coverageType}`); - } -} diff --git a/packages/plugin-doc-coverage/src/lib/runner/doc-processer.integration.test.ts b/packages/plugin-doc-coverage/src/lib/runner/doc-processer.integration.test.ts index 64303023f..78a144490 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/doc-processer.integration.test.ts +++ b/packages/plugin-doc-coverage/src/lib/runner/doc-processer.integration.test.ts @@ -1,7 +1,7 @@ import { processDocCoverage } from './doc-processer.js'; import type { DocumentationData } from './models.js'; -describe('Classes', () => { +describe('processDocCoverage', () => { it('should detect undocumented class', () => { const sourcePath = 'packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/classes-coverage.ts'; @@ -11,7 +11,7 @@ describe('Classes', () => { nodesCount: 1, issues: [ { - file: expect.stringContaining('classes-coverage'), + file: expect.stringContaining('classes-coverage.ts'), type: 'classes', name: 'ExampleClass', line: 1, @@ -34,9 +34,7 @@ describe('Classes', () => { coverage: number; }); }); -}); -describe('Methods', () => { it('should detect undocumented method', () => { const sourcePath = 'packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/methods-coverage.ts'; @@ -46,10 +44,10 @@ describe('Methods', () => { nodesCount: 1, issues: [ { - file: expect.stringContaining('methods-coverage'), + file: expect.stringContaining('methods-coverage.ts'), type: 'methods', name: 'exampleMethod', - line: 2, + line: 5, }, ], } satisfies DocumentationData & { @@ -69,9 +67,7 @@ describe('Methods', () => { coverage: number; }); }); -}); -describe('Interfaces', () => { it('should detect undocumented interface', () => { const sourcePath = 'packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/interfaces-coverage.ts'; @@ -81,7 +77,7 @@ describe('Interfaces', () => { nodesCount: 1, issues: [ { - file: expect.stringContaining('interfaces-coverage'), + file: expect.stringContaining('interfaces-coverage.ts'), type: 'interfaces', name: 'ExampleInterface', line: 1, @@ -104,9 +100,7 @@ describe('Interfaces', () => { coverage: number; }); }); -}); -describe('Variables', () => { it('should detect undocumented variable', () => { const sourcePath = 'packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/variables-coverage.ts'; @@ -116,7 +110,7 @@ describe('Variables', () => { nodesCount: 1, issues: [ { - file: expect.stringContaining('variables-coverage'), + file: expect.stringContaining('variables-coverage.ts'), type: 'variables', name: 'exampleVariable', line: 1, @@ -139,9 +133,7 @@ describe('Variables', () => { coverage: number; }); }); -}); -describe('Properties', () => { it('should detect undocumented property', () => { const sourcePath = 'packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/properties-coverage.ts'; @@ -151,10 +143,10 @@ describe('Properties', () => { nodesCount: 1, issues: [ { - file: expect.stringContaining('properties-coverage'), + file: expect.stringContaining('properties-coverage.ts'), type: 'properties', name: 'internalId', - line: 2, + line: 5, }, ], } satisfies DocumentationData & { @@ -174,9 +166,7 @@ describe('Properties', () => { coverage: number; }); }); -}); -describe('Types', () => { it('should detect undocumented type', () => { const sourcePath = 'packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/types-coverage.ts'; @@ -186,7 +176,7 @@ describe('Types', () => { nodesCount: 1, issues: [ { - file: expect.stringContaining('types-coverage'), + file: expect.stringContaining('types-coverage.ts'), type: 'types', name: 'ExampleType', line: 1, @@ -209,9 +199,7 @@ describe('Types', () => { coverage: number; }); }); -}); -describe('Enums', () => { it('should detect undocumented enum', () => { const sourcePath = 'packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/enums-coverage.ts'; @@ -221,7 +209,7 @@ describe('Enums', () => { nodesCount: 1, issues: [ { - file: expect.stringContaining('enums-coverage'), + file: expect.stringContaining('enums-coverage.ts'), type: 'enums', name: 'ExampleEnum', line: 1, diff --git a/packages/plugin-doc-coverage/src/lib/runner/doc-processer.unit.test.ts b/packages/plugin-doc-coverage/src/lib/runner/doc-processer.unit.test.ts index 8cf66f43f..08ed4f95c 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/doc-processer.unit.test.ts +++ b/packages/plugin-doc-coverage/src/lib/runner/doc-processer.unit.test.ts @@ -1,8 +1,6 @@ import type { ClassDeclaration, VariableStatement } from 'ts-morph'; -import { - nodeMock, - sourceFileMock, -} from '../../../mocks/source-files-mock.generator'; +import { nodeMock } from '../../../mocks/node.mock'; +import { sourceFileMock } from '../../../mocks/source-files.mock'; import { getAllNodesFromASourceFile, getClassNodes, diff --git a/packages/plugin-doc-coverage/src/lib/runner/runner.integration.test.ts b/packages/plugin-doc-coverage/src/lib/runner/runner.integration.test.ts index ab1933ae8..8ba35bdd3 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/runner.integration.test.ts +++ b/packages/plugin-doc-coverage/src/lib/runner/runner.integration.test.ts @@ -14,52 +14,84 @@ describe('createRunnerFunction', () => { 'fixtures', ); - const undocumentedSourceGlob = [ - path.join(fixturesDir, 'missing-documentation/**/*.ts'), - ]; - const documentedSourceGlob = [ - path.join(fixturesDir, 'filled-documentation/**/*.ts'), - ]; - - it('should generate correct audit outputs for documentation coverage', async () => { - const runnerFn = createRunnerFunction({ - sourceGlob: undocumentedSourceGlob, - }); - - const results = await runnerFn({} as any); - - // Verify all expected audits are present - expect(results).toHaveLength(Object.keys(AUDITS_MAP).length); + const AUDIT_SLUGS = Object.keys(AUDITS_MAP).map(key => [ + key.replace(/-coverage/g, ''), + ]); + + it.each(AUDIT_SLUGS)( + 'should generate issues for %s coverage if undocumented', + async coverageType => { + const filePath = path.join( + fixturesDir, + `missing-documentation/${coverageType}-coverage.ts`, + ); + const runnerFn = createRunnerFunction({ + sourceGlob: [filePath], + }); - // Verify structure and content of each audit - results.forEach(audit => { - expect(audit).toMatchObject({ - score: 0, - value: expect.any(Number), - displayValue: expect.stringMatching(/^\d+ undocumented/), - details: { - issues: expect.arrayContaining([ - expect.objectContaining({ - message: 'Missing documentation', - severity: 'warning', - source: { - file: expect.stringContaining( - audit.slug.replace('-coverage', ''), + const results = await runnerFn(() => void 0); + + expect( + results.find(({ slug }) => slug === `${coverageType}-coverage`), + ).toStrictEqual( + expect.objectContaining({ + slug: `${coverageType}-coverage`, + score: 0, + value: 1, + displayValue: `1 undocumented ${coverageType}`, + details: { + issues: [ + expect.objectContaining({ + message: expect.stringContaining( + `Missing ${coverageType} documentation for`, ), - position: expect.objectContaining({ - startLine: expect.any(Number), - }), - }, - }), - ]), - }, + severity: 'warning', + source: { + file: expect.stringContaining(path.basename(filePath)), + position: { + startLine: expect.any(Number), + }, + }, + }), + ], + }, + }), + ); + }, + ); + + it.each(AUDIT_SLUGS)( + 'should not generate issues for %s coverage if documented', + async coverageType => { + const filePath = path.join( + fixturesDir, + `filled-documentation/${coverageType}-coverage.ts`, + ); + const runnerFn = createRunnerFunction({ + sourceGlob: [filePath], }); - }); - }); + + const results = await runnerFn(() => void 0); + + expect( + results.find(({ slug }) => slug === `${coverageType}-coverage`), + ).toStrictEqual( + expect.objectContaining({ + slug: `${coverageType}-coverage`, + score: 1, + value: 0, + displayValue: `0 undocumented ${coverageType}`, + details: { + issues: [], + }, + }), + ); + }, + ); it('should respect onlyAudits option', async () => { const runnerFn = createRunnerFunction({ - sourceGlob: undocumentedSourceGlob, + sourceGlob: [], onlyAudits: ['classes-coverage', 'methods-coverage'], }); @@ -74,7 +106,7 @@ describe('createRunnerFunction', () => { it('should respect skipAudits option', async () => { const runnerFn = createRunnerFunction({ - sourceGlob: undocumentedSourceGlob, + sourceGlob: [], skipAudits: ['classes-coverage', 'methods-coverage'], }); @@ -84,30 +116,4 @@ describe('createRunnerFunction', () => { expect(slugs).not.toContain('classes-coverage'); expect(slugs).not.toContain('methods-coverage'); }); - - it('should generate correct audit outputs for undocumented code', async () => { - const runnerFn = createRunnerFunction({ - sourceGlob: undocumentedSourceGlob, - }); - - const results = await runnerFn({} as any); - - results.forEach(audit => { - expect(audit.score).toBe(0); - expect(audit.details?.issues?.length).toBeGreaterThan(0); // There are multiples classes due to methods and properties - }); - }); - - it('should generate correct audit outputs for documented code', async () => { - const runnerFn = createRunnerFunction({ - sourceGlob: documentedSourceGlob, - }); - - const results = await runnerFn({} as any); - - results.forEach(audit => { - expect(audit.score).toBe(1); - expect(audit.details?.issues?.length).toBe(0); - }); - }); }); diff --git a/packages/plugin-doc-coverage/src/lib/runner/runner.ts b/packages/plugin-doc-coverage/src/lib/runner/runner.ts index 8c059aac7..739b10814 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/runner.ts +++ b/packages/plugin-doc-coverage/src/lib/runner/runner.ts @@ -8,7 +8,7 @@ export function createRunnerFunction( ): RunnerFunction { return (): AuditOutputs => { const coverageResult = processDocCoverage(config); - return trasformCoverageReportToAudits(coverageResult, config); + return trasformCoverageReportToAuditOutputs(coverageResult, config); }; } @@ -18,7 +18,7 @@ export function createRunnerFunction( * @param options - Configuration options specifying which audits to include and exclude * @returns Audit outputs with coverage scores and details about undocumented items */ -export function trasformCoverageReportToAudits( +export function trasformCoverageReportToAuditOutputs( coverageResult: DocumentationCoverageReport, options: Pick, ): AuditOutputs { @@ -42,8 +42,8 @@ export function trasformCoverageReportToAudits( score: coverage / 100, displayValue: `${issues.length} undocumented ${type}`, details: { - issues: item.issues.map(({ file, line }) => ({ - message: 'Missing documentation', + issues: item.issues.map(({ file, line, name }) => ({ + message: `Missing ${type} documentation for ${name}`, source: { file, position: { startLine: line } }, severity: 'warning', })), diff --git a/packages/plugin-doc-coverage/src/lib/runner/runner.unit.test.ts b/packages/plugin-doc-coverage/src/lib/runner/runner.unit.test.ts index 081df241f..b13472d7c 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/runner.unit.test.ts +++ b/packages/plugin-doc-coverage/src/lib/runner/runner.unit.test.ts @@ -1,5 +1,5 @@ import type { DocumentationCoverageReport } from './models.js'; -import { trasformCoverageReportToAudits } from './runner.js'; +import { trasformCoverageReportToAuditOutputs } from './runner.js'; describe('trasformCoverageReportToAudits', () => { const mockCoverageResult = { @@ -30,7 +30,7 @@ describe('trasformCoverageReportToAudits', () => { } as unknown as DocumentationCoverageReport; it('should return all audits from the coverage result when no filters are provided', () => { - const result = trasformCoverageReportToAudits(mockCoverageResult, {}); + const result = trasformCoverageReportToAuditOutputs(mockCoverageResult, {}); expect(result.map(item => item.slug)).toStrictEqual([ 'functions-coverage', 'classes-coverage', @@ -38,7 +38,7 @@ describe('trasformCoverageReportToAudits', () => { }); it('should filter audits when onlyAudits is provided', () => { - const result = trasformCoverageReportToAudits(mockCoverageResult, { + const result = trasformCoverageReportToAuditOutputs(mockCoverageResult, { onlyAudits: ['functions-coverage'], }); expect(result).toHaveLength(1); @@ -46,7 +46,7 @@ describe('trasformCoverageReportToAudits', () => { }); it('should filter audits when skipAudits is provided', () => { - const result = trasformCoverageReportToAudits(mockCoverageResult, { + const result = trasformCoverageReportToAuditOutputs(mockCoverageResult, { skipAudits: ['functions-coverage'], }); expect(result).toHaveLength(1); @@ -54,7 +54,7 @@ describe('trasformCoverageReportToAudits', () => { }); it('should handle properly empty coverage result', () => { - const result = trasformCoverageReportToAudits( + const result = trasformCoverageReportToAuditOutputs( {} as unknown as DocumentationCoverageReport, {}, ); @@ -63,7 +63,7 @@ describe('trasformCoverageReportToAudits', () => { it('should handle coverage result with multiple issues and add them to the details.issue of the report', () => { const expectedIssues = 2; - const result = trasformCoverageReportToAudits(mockCoverageResult, {}); + const result = trasformCoverageReportToAuditOutputs(mockCoverageResult, {}); expect(result).toHaveLength(2); expect( result.reduce( From 12e3f4eb9951fb765455f0784f5055bcd947cddd Mon Sep 17 00:00:00 2001 From: Michael Date: Fri, 3 Jan 2025 15:45:27 +0100 Subject: [PATCH 47/66] wip --- e2e/plugin-doc-coverage-e2e/tests/collect.e2e.test.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/e2e/plugin-doc-coverage-e2e/tests/collect.e2e.test.ts b/e2e/plugin-doc-coverage-e2e/tests/collect.e2e.test.ts index 5f9325e16..5a7f7b1dc 100644 --- a/e2e/plugin-doc-coverage-e2e/tests/collect.e2e.test.ts +++ b/e2e/plugin-doc-coverage-e2e/tests/collect.e2e.test.ts @@ -69,12 +69,5 @@ describe('PLUGIN collect report with doc-coverage-plugin NPM package', () => { JSON.stringify(omitVariableReportData(report as Report), null, 2), ).toMatchFileSnapshot('__snapshots__/report.json'); - const reportMd = await readTextFile( - path.join(angularOutputDir, 'report.md'), - ); - - expect(reportMd.replace(/## About\.*/gm, '')).toMatchFileSnapshot( - '__snapshots__/report.md', - ); }); }); From c9443a0c248c857007d3d5a4360309674fd835cf Mon Sep 17 00:00:00 2001 From: Alejandro Date: Fri, 3 Jan 2025 16:03:55 +0100 Subject: [PATCH 48/66] chore(plugin-doc-coverage): change string detection --- .../lib/runner/doc-processer.integration.test.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/plugin-doc-coverage/src/lib/runner/doc-processer.integration.test.ts b/packages/plugin-doc-coverage/src/lib/runner/doc-processer.integration.test.ts index 78a144490..3c5dcad06 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/doc-processer.integration.test.ts +++ b/packages/plugin-doc-coverage/src/lib/runner/doc-processer.integration.test.ts @@ -11,7 +11,7 @@ describe('processDocCoverage', () => { nodesCount: 1, issues: [ { - file: expect.stringContaining('classes-coverage.ts'), + file: expect.stringMatching(/classes-coverage\.ts$/), type: 'classes', name: 'ExampleClass', line: 1, @@ -44,7 +44,7 @@ describe('processDocCoverage', () => { nodesCount: 1, issues: [ { - file: expect.stringContaining('methods-coverage.ts'), + file: expect.stringMatching(/methods-coverage\.ts$/), type: 'methods', name: 'exampleMethod', line: 5, @@ -77,7 +77,7 @@ describe('processDocCoverage', () => { nodesCount: 1, issues: [ { - file: expect.stringContaining('interfaces-coverage.ts'), + file: expect.stringMatching(/interfaces-coverage\.ts$/), type: 'interfaces', name: 'ExampleInterface', line: 1, @@ -110,7 +110,7 @@ describe('processDocCoverage', () => { nodesCount: 1, issues: [ { - file: expect.stringContaining('variables-coverage.ts'), + file: expect.stringMatching(/variables-coverage\.ts$/), type: 'variables', name: 'exampleVariable', line: 1, @@ -143,9 +143,9 @@ describe('processDocCoverage', () => { nodesCount: 1, issues: [ { - file: expect.stringContaining('properties-coverage.ts'), + file: expect.stringMatching(/properties-coverage\.ts$/), type: 'properties', - name: 'internalId', + name: 'exampleProperty', line: 5, }, ], @@ -176,7 +176,7 @@ describe('processDocCoverage', () => { nodesCount: 1, issues: [ { - file: expect.stringContaining('types-coverage.ts'), + file: expect.stringMatching(/types-coverage\.ts$/), type: 'types', name: 'ExampleType', line: 1, @@ -209,7 +209,7 @@ describe('processDocCoverage', () => { nodesCount: 1, issues: [ { - file: expect.stringContaining('enums-coverage.ts'), + file: expect.stringMatching(/enums-coverage\.ts$/), type: 'enums', name: 'ExampleEnum', line: 1, From ac83605e2e6240e720e01fdfb0e97a407d02d3f3 Mon Sep 17 00:00:00 2001 From: Alejandro Date: Fri, 3 Jan 2025 16:06:24 +0100 Subject: [PATCH 49/66] chore: format --- e2e/plugin-doc-coverage-e2e/tests/collect.e2e.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/e2e/plugin-doc-coverage-e2e/tests/collect.e2e.test.ts b/e2e/plugin-doc-coverage-e2e/tests/collect.e2e.test.ts index 5a7f7b1dc..a1fd11d87 100644 --- a/e2e/plugin-doc-coverage-e2e/tests/collect.e2e.test.ts +++ b/e2e/plugin-doc-coverage-e2e/tests/collect.e2e.test.ts @@ -68,6 +68,5 @@ describe('PLUGIN collect report with doc-coverage-plugin NPM package', () => { expect( JSON.stringify(omitVariableReportData(report as Report), null, 2), ).toMatchFileSnapshot('__snapshots__/report.json'); - }); }); From 1107ae44fcb2bb846068edd70b19456f35136563 Mon Sep 17 00:00:00 2001 From: Alejandro Date: Fri, 3 Jan 2025 16:12:55 +0100 Subject: [PATCH 50/66] chore: import --- e2e/plugin-doc-coverage-e2e/tests/collect.e2e.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/plugin-doc-coverage-e2e/tests/collect.e2e.test.ts b/e2e/plugin-doc-coverage-e2e/tests/collect.e2e.test.ts index a1fd11d87..846290009 100644 --- a/e2e/plugin-doc-coverage-e2e/tests/collect.e2e.test.ts +++ b/e2e/plugin-doc-coverage-e2e/tests/collect.e2e.test.ts @@ -10,7 +10,7 @@ import { omitVariableReportData, removeColorCodes, } from '@code-pushup/test-utils'; -import { executeProcess, readJsonFile, readTextFile } from '@code-pushup/utils'; +import { executeProcess, readJsonFile } from '@code-pushup/utils'; describe('PLUGIN collect report with doc-coverage-plugin NPM package', () => { const fixturesDir = path.join( From b7a42c7d7ce64e6d815cff0060d288d5df94e526 Mon Sep 17 00:00:00 2001 From: Alejandro Date: Thu, 9 Jan 2025 13:38:35 +0100 Subject: [PATCH 51/66] chore(plugin-doc-coverage): use new path matching helpers --- .../lib/runner/doc-processer.integration.test.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/plugin-doc-coverage/src/lib/runner/doc-processer.integration.test.ts b/packages/plugin-doc-coverage/src/lib/runner/doc-processer.integration.test.ts index 3c5dcad06..6cc3ae576 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/doc-processer.integration.test.ts +++ b/packages/plugin-doc-coverage/src/lib/runner/doc-processer.integration.test.ts @@ -11,7 +11,7 @@ describe('processDocCoverage', () => { nodesCount: 1, issues: [ { - file: expect.stringMatching(/classes-coverage\.ts$/), + file: expect.pathToEndWith('classes-coverage.ts'), type: 'classes', name: 'ExampleClass', line: 1, @@ -44,7 +44,7 @@ describe('processDocCoverage', () => { nodesCount: 1, issues: [ { - file: expect.stringMatching(/methods-coverage\.ts$/), + file: expect.pathToEndWith('methods-coverage.ts'), type: 'methods', name: 'exampleMethod', line: 5, @@ -77,7 +77,7 @@ describe('processDocCoverage', () => { nodesCount: 1, issues: [ { - file: expect.stringMatching(/interfaces-coverage\.ts$/), + file: expect.pathToEndWith('interfaces-coverage.ts'), type: 'interfaces', name: 'ExampleInterface', line: 1, @@ -110,7 +110,7 @@ describe('processDocCoverage', () => { nodesCount: 1, issues: [ { - file: expect.stringMatching(/variables-coverage\.ts$/), + file: expect.pathToEndWith('variables-coverage.ts'), type: 'variables', name: 'exampleVariable', line: 1, @@ -143,7 +143,7 @@ describe('processDocCoverage', () => { nodesCount: 1, issues: [ { - file: expect.stringMatching(/properties-coverage\.ts$/), + file: expect.pathToEndWith('properties-coverage.ts'), type: 'properties', name: 'exampleProperty', line: 5, @@ -176,7 +176,7 @@ describe('processDocCoverage', () => { nodesCount: 1, issues: [ { - file: expect.stringMatching(/types-coverage\.ts$/), + file: expect.pathToEndWith('types-coverage.ts'), type: 'types', name: 'ExampleType', line: 1, @@ -209,7 +209,7 @@ describe('processDocCoverage', () => { nodesCount: 1, issues: [ { - file: expect.stringMatching(/enums-coverage\.ts$/), + file: expect.pathToEndWith('enums-coverage.ts'), type: 'enums', name: 'ExampleEnum', line: 1, From 206340eab7d8f197a2050d42eed3e9e7fafb1cde Mon Sep 17 00:00:00 2001 From: Alejandro Date: Thu, 9 Jan 2025 13:46:23 +0100 Subject: [PATCH 52/66] chore(plugin-doc-coverage): add setupFile for path matcher --- packages/plugin-doc-coverage/vite.config.integration.ts | 1 + packages/plugin-doc-coverage/vite.config.unit.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/plugin-doc-coverage/vite.config.integration.ts b/packages/plugin-doc-coverage/vite.config.integration.ts index b04d62c56..d60b962ed 100644 --- a/packages/plugin-doc-coverage/vite.config.integration.ts +++ b/packages/plugin-doc-coverage/vite.config.integration.ts @@ -24,6 +24,7 @@ export default defineConfig({ setupFiles: [ '../../testing/test-setup/src/lib/console.mock.ts', '../../testing/test-setup/src/lib/reset.mocks.ts', + '../../testing/test-setup/src/lib/extend/path.matcher.ts', ], }, }); diff --git a/packages/plugin-doc-coverage/vite.config.unit.ts b/packages/plugin-doc-coverage/vite.config.unit.ts index 3cae73a58..5aef1ef13 100644 --- a/packages/plugin-doc-coverage/vite.config.unit.ts +++ b/packages/plugin-doc-coverage/vite.config.unit.ts @@ -26,6 +26,7 @@ export default defineConfig({ '../../testing/test-setup/src/lib/fs.mock.ts', '../../testing/test-setup/src/lib/console.mock.ts', '../../testing/test-setup/src/lib/reset.mocks.ts', + '../../testing/test-setup/src/lib/extend/path.matcher.ts', ], }, }); From dec6fbdd96a8e1554c332568c8dbbd42e36caae5 Mon Sep 17 00:00:00 2001 From: Alejandro <49059458+aramirezj@users.noreply.github.com> Date: Sat, 11 Jan 2025 23:03:30 +0100 Subject: [PATCH 53/66] Update code-pushup.preset.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Matฤ›j Chalk <34691111+matejchalk@users.noreply.github.com> --- code-pushup.preset.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code-pushup.preset.ts b/code-pushup.preset.ts index c7f907f03..2ac4b611e 100644 --- a/code-pushup.preset.ts +++ b/code-pushup.preset.ts @@ -95,8 +95,8 @@ export function getDocCoverageCategories( ): CategoryConfig[] { return [ { - slug: 'doc-coverage-cat', - title: 'Documentation coverage', + slug: 'docs', + title: 'Documentation', description: 'Measures how much of your code is **documented**.', refs: filterGroupsByOnlyAudits(groups, config).map(group => ({ weight: 1, From baac33c45db477958d383c8881255bb300bdaa8a Mon Sep 17 00:00:00 2001 From: Alejandro <49059458+aramirezj@users.noreply.github.com> Date: Sat, 11 Jan 2025 23:03:51 +0100 Subject: [PATCH 54/66] Update packages/plugin-doc-coverage/README.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Matฤ›j Chalk <34691111+matejchalk@users.noreply.github.com> --- packages/plugin-doc-coverage/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/plugin-doc-coverage/README.md b/packages/plugin-doc-coverage/README.md index d41ba4576..dc539891d 100644 --- a/packages/plugin-doc-coverage/README.md +++ b/packages/plugin-doc-coverage/README.md @@ -13,7 +13,7 @@ Measured documentation types are mapped to Code PushUp audits in the following w - `value`: The value is the number of undocumented nodes -> 4 - `displayValue`: `${value} undocumented ${type}` -> 4 undocumented functions -- `score`: 0.5 -> total nodes 8 undocumented 4 -> 8/4 +- `score`: 0.5 -> total nodes 8, undocumented 4 -> 4/8 - The score is value converted to 0-1 range - Missing documentation is mapped to issues in the audit details (undocumented classes, functions, interfaces, etc.) From ac724322a632c47d2e717a8341f6a26a49b3b42d Mon Sep 17 00:00:00 2001 From: Alejandro <49059458+aramirezj@users.noreply.github.com> Date: Sat, 11 Jan 2025 23:04:02 +0100 Subject: [PATCH 55/66] Update code-pushup.preset.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Matฤ›j Chalk <34691111+matejchalk@users.noreply.github.com> --- code-pushup.preset.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code-pushup.preset.ts b/code-pushup.preset.ts index 2ac4b611e..4bca36c93 100644 --- a/code-pushup.preset.ts +++ b/code-pushup.preset.ts @@ -140,9 +140,9 @@ export const lighthouseCoreConfig = async ( }; }; -export const docCoverageCoreConfig = async ( +export const docCoverageCoreConfig = ( config: DocCoveragePluginConfig, -): Promise => { +): CoreConfig => { return { plugins: [docCoveragePlugin(config)], categories: getDocCoverageCategories(config), From 177a186748df50369445a4122c30ab3457a85d67 Mon Sep 17 00:00:00 2001 From: Alejandro <49059458+aramirezj@users.noreply.github.com> Date: Sat, 11 Jan 2025 23:30:38 +0100 Subject: [PATCH 56/66] Update packages/plugin-doc-coverage/src/lib/runner/runner.unit.test.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Matฤ›j Chalk <34691111+matejchalk@users.noreply.github.com> --- packages/plugin-doc-coverage/src/lib/runner/runner.unit.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/plugin-doc-coverage/src/lib/runner/runner.unit.test.ts b/packages/plugin-doc-coverage/src/lib/runner/runner.unit.test.ts index b13472d7c..dee74fdc9 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/runner.unit.test.ts +++ b/packages/plugin-doc-coverage/src/lib/runner/runner.unit.test.ts @@ -27,7 +27,7 @@ describe('trasformCoverageReportToAudits', () => { }, ], }, - } as unknown as DocumentationCoverageReport; + } as DocumentationCoverageReport; it('should return all audits from the coverage result when no filters are provided', () => { const result = trasformCoverageReportToAuditOutputs(mockCoverageResult, {}); From 45313470bc4ce4097b8a0370aaef0439a5dbc757 Mon Sep 17 00:00:00 2001 From: Alejandro <49059458+aramirezj@users.noreply.github.com> Date: Sat, 11 Jan 2025 23:31:01 +0100 Subject: [PATCH 57/66] Update packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Matฤ›j Chalk <34691111+matejchalk@users.noreply.github.com> --- packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.ts b/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.ts index dc594a303..32728d5a2 100644 --- a/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.ts +++ b/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.ts @@ -44,7 +44,7 @@ export function docCoveragePlugin( return { slug: PLUGIN_SLUG, title: PLUGIN_TITLE, - icon: 'folder-src', + icon: 'folder-docs', description: PLUGIN_DESCRIPTION, docsUrl: PLUGIN_DOCS_URL, groups: filterGroupsByOnlyAudits(groups, docCoverageConfig), From 4700bea53fe93be3df46c57e3aa78b3ad9a3a558 Mon Sep 17 00:00:00 2001 From: Alejandro <49059458+aramirezj@users.noreply.github.com> Date: Sat, 11 Jan 2025 23:44:15 +0100 Subject: [PATCH 58/66] Update packages/plugin-doc-coverage/src/lib/runner/doc-processer.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Matฤ›j Chalk <34691111+matejchalk@users.noreply.github.com> --- .../src/lib/runner/doc-processer.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/plugin-doc-coverage/src/lib/runner/doc-processer.ts b/packages/plugin-doc-coverage/src/lib/runner/doc-processer.ts index 82be50608..7960c1bb1 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/doc-processer.ts +++ b/packages/plugin-doc-coverage/src/lib/runner/doc-processer.ts @@ -128,10 +128,10 @@ export function mergeDocumentationReports( accumulatedReport: DocumentationReport, currentFileReport: Partial, ): DocumentationReport { - return Object.fromEntries( - Object.entries(accumulatedReport).map(([key, value]) => { - const node = value as DocumentationCoverageReport[CoverageType]; - const type = key as CoverageType; + return objectFromEntries( + objectToEntries(accumulatedReport).map(([key, value]) => { + const node = value; + const type = key; return [ type, { @@ -141,7 +141,7 @@ export function mergeDocumentationReports( }, ]; }), - ) as DocumentationReport; + ); } /** From c38637367999f5a504c9801e2dbf69887837bb29 Mon Sep 17 00:00:00 2001 From: Alejandro Date: Sun, 12 Jan 2025 00:31:31 +0100 Subject: [PATCH 59/66] chore(plugin-doc-coverage): wip request in pr --- code-pushup.config.ts | 38 ++-- code-pushup.preset.ts | 14 +- packages/plugin-doc-coverage/README.md | 12 +- .../plugin-doc-coverage/src/lib/config.ts | 19 +- .../src/lib/config.unit.test.ts | 48 +++-- .../src/lib/doc-coverage-plugin.ts | 2 +- .../src/lib/doc-coverage-plugin.unit.test.ts | 12 +- .../runner/doc-processer.integration.test.ts | 179 ++++++++++-------- .../src/lib/runner/doc-processer.ts | 87 +++++---- .../src/lib/runner/models.ts | 40 ++-- .../src/lib/runner/runner.integration.test.ts | 8 +- .../src/lib/runner/runner.ts | 5 +- .../src/lib/runner/utils.ts | 43 ++--- 13 files changed, 280 insertions(+), 227 deletions(-) diff --git a/code-pushup.config.ts b/code-pushup.config.ts index b48295452..f471ba7f3 100644 --- a/code-pushup.config.ts +++ b/code-pushup.config.ts @@ -1,12 +1,6 @@ import 'dotenv/config'; import { z } from 'zod'; -import { - coverageCoreConfigNx, - docCoverageCoreConfig, - eslintCoreConfigNx, - jsPackagesCoreConfig, - lighthouseCoreConfig, -} from './code-pushup.preset.js'; +import { docCoverageCoreConfig } from './code-pushup.preset.js'; import type { CoreConfig } from './packages/models/src/index.js'; import { mergeConfigs } from './packages/utils/src/index.js'; @@ -34,20 +28,18 @@ const config: CoreConfig = { export default mergeConfigs( config, - await coverageCoreConfigNx(), - await jsPackagesCoreConfig(), - await lighthouseCoreConfig( - 'https://github.com/code-pushup/cli?tab=readme-ov-file#code-pushup-cli/', - ), - await eslintCoreConfigNx(), - await docCoverageCoreConfig({ - sourceGlob: [ - 'packages/**/src/**/*.ts', - '!packages/**/node_modules', - '!packages/**/{mocks,mock}', - '!**/*.{spec,test}.ts', - '!**/implementation/**', - '!**/internal/**', - ], - }), + // await coverageCoreConfigNx(), + // await jsPackagesCoreConfig(), + // await lighthouseCoreConfig( + // 'https://github.com/code-pushup/cli?tab=readme-ov-file#code-pushup-cli/', + // ), + // await eslintCoreConfigNx(), + docCoverageCoreConfig([ + 'packages/**/src/**/*.ts', + '!packages/**/node_modules', + '!packages/**/{mocks,mock}', + '!**/*.{spec,test}.ts', + '!**/implementation/**', + '!**/internal/**', + ]), ); diff --git a/code-pushup.preset.ts b/code-pushup.preset.ts index 4bca36c93..5fa0d7321 100644 --- a/code-pushup.preset.ts +++ b/code-pushup.preset.ts @@ -95,8 +95,8 @@ export function getDocCoverageCategories( ): CategoryConfig[] { return [ { - slug: 'docs', - title: 'Documentation', + slug: 'docs', + title: 'Documentation', description: 'Measures how much of your code is **documented**.', refs: filterGroupsByOnlyAudits(groups, config).map(group => ({ weight: 1, @@ -141,11 +141,15 @@ export const lighthouseCoreConfig = async ( }; export const docCoverageCoreConfig = ( - config: DocCoveragePluginConfig, + config: DocCoveragePluginConfig | string[], ): CoreConfig => { return { - plugins: [docCoveragePlugin(config)], - categories: getDocCoverageCategories(config), + plugins: [ + docCoveragePlugin(Array.isArray(config) ? { patterns: config } : config), + ], + categories: getDocCoverageCategories( + Array.isArray(config) ? { patterns: config } : config, + ), }; }; diff --git a/packages/plugin-doc-coverage/README.md b/packages/plugin-doc-coverage/README.md index dc539891d..6ed841b1a 100644 --- a/packages/plugin-doc-coverage/README.md +++ b/packages/plugin-doc-coverage/README.md @@ -45,7 +45,7 @@ Measured documentation types are mapped to Code PushUp audits in the following w plugins: [ // ... docCoveragePlugin({ - sourceGlob: ['**/*.ts'], + patterns: ['**/*.ts'], }), ], }; @@ -100,13 +100,13 @@ These audits are grouped together to provide a comprehensive view of your codeba The plugin accepts the following parameters: -#### SourceGlob +#### patterns -Required parameter. The `sourceGlob` option accepts an array of strings that define patterns to include or exclude files. You can use glob patterns to match files and the `!` symbol to exclude specific patterns. Example: +Required parameter. The `patterns` option accepts an array of strings that define patterns to include or exclude files. You can use glob patterns to match files and the `!` symbol to exclude specific patterns. Example: ```js docCoveragePlugin({ - sourceGlob: [ + patterns: [ 'src/**/*.ts', // include all TypeScript files in src '!src/**/*.{spec,test}.ts', // exclude test files '!src/**/testing/**/*.ts' // exclude testing utilities @@ -120,7 +120,7 @@ Optional parameter. The `onlyAudits` option allows you to specify which document ```js docCoveragePlugin({ - sourceGlob: ['src/**/*.ts'], + patterns: ['src/**/*.ts'], onlyAudits: [ 'classes-coverage', 'functions-coverage' @@ -134,7 +134,7 @@ Optional parameter. The `skipAudits` option allows you to exclude specific docum ```js docCoveragePlugin({ - sourceGlob: ['src/**/*.ts'], + patterns: ['src/**/*.ts'], skipAudits: [ 'variables-coverage', 'interfaces-coverage' diff --git a/packages/plugin-doc-coverage/src/lib/config.ts b/packages/plugin-doc-coverage/src/lib/config.ts index 1204ca7f7..6f3209c2e 100644 --- a/packages/plugin-doc-coverage/src/lib/config.ts +++ b/packages/plugin-doc-coverage/src/lib/config.ts @@ -1,6 +1,10 @@ import { z } from 'zod'; -export const docCoveragePluginConfigSchema = z +const patternsSchema = z.union([z.string(), z.array(z.string()).min(1)], { + description: 'Glob pattern to match source files to evaluate.', +}); + +const docCoverageTargetObjectSchema = z .object({ skipAudits: z .array(z.string()) @@ -14,16 +18,21 @@ export const docCoveragePluginConfigSchema = z .describe( 'List of audit slugs to evaluate. When specified, only these audits will be evaluated.', ), - sourceGlob: z - .array(z.string()) - .default(['src/**/*.{ts,tsx}', '!**/*.spec.ts', '!**/*.test.ts']) - .describe('Glob pattern to match source files to evaluate.'), + patterns: patternsSchema, }) .refine(data => !(data.skipAudits && data.onlyAudits), { message: "You can't define 'skipAudits' and 'onlyAudits' simultaneously", path: ['skipAudits', 'onlyAudits'], }); +export const docCoveragePluginConfigSchema = z + .union([patternsSchema, docCoverageTargetObjectSchema]) + .transform(target => + typeof target === 'string' || Array.isArray(target) + ? { patterns: target } + : target, + ); + export type DocCoveragePluginConfig = z.infer< typeof docCoveragePluginConfigSchema >; diff --git a/packages/plugin-doc-coverage/src/lib/config.unit.test.ts b/packages/plugin-doc-coverage/src/lib/config.unit.test.ts index 4d4b4f1ea..c10040a29 100644 --- a/packages/plugin-doc-coverage/src/lib/config.unit.test.ts +++ b/packages/plugin-doc-coverage/src/lib/config.unit.test.ts @@ -9,7 +9,7 @@ describe('DocCoveragePlugin Configuration', () => { it('accepts a valid configuration', () => { expect(() => docCoveragePluginConfigSchema.parse({ - sourceGlob: ['src/**/*.ts'], + patterns: ['src/**/*.ts'], onlyAudits: ['functions-coverage'], } satisfies DocCoveragePluginConfig), ).not.toThrow(); @@ -18,6 +18,7 @@ describe('DocCoveragePlugin Configuration', () => { it('throws when skipAudits and onlyAudits are defined', () => { expect(() => docCoveragePluginConfigSchema.parse({ + patterns: ['src/**/*.ts'], skipAudits: ['functions-coverage'], onlyAudits: ['classes-coverage'], }), @@ -27,28 +28,29 @@ describe('DocCoveragePlugin Configuration', () => { }); }); - describe('sourceGlob', () => { - it('accepts a valid source glob pattern', () => { + describe('patterns', () => { + it('accepts a valid patterns array', () => { expect(() => docCoveragePluginConfigSchema.parse({ - sourceGlob: ['src/**/*.{ts,tsx}', '!**/*.spec.ts', '!**/*.test.ts'], + patterns: ['src/**/*.{ts,tsx}', '!**/*.spec.ts', '!**/*.test.ts'], } satisfies DocCoveragePluginConfig), ).not.toThrow(); }); - it('uses default value for missing sourceGlob', () => { - const result = docCoveragePluginConfigSchema.parse({}); - expect(result.sourceGlob).toEqual([ - 'src/**/*.{ts,tsx}', - '!**/*.spec.ts', - '!**/*.test.ts', - ]); + it('accepts a valid patterns array directly', () => { + expect(() => + docCoveragePluginConfigSchema.parse([ + 'src/**/*.{ts,tsx}', + '!**/*.spec.ts', + '!**/*.test.ts', + ]), + ).not.toThrow(); }); - it('throws for invalid sourceGlob type', () => { + it('throws for invalid patterns type', () => { expect(() => docCoveragePluginConfigSchema.parse({ - sourceGlob: 123, + patterns: 123, }), ).toThrow('Expected array'); }); @@ -59,7 +61,7 @@ describe('DocCoveragePlugin Configuration', () => { expect(() => docCoveragePluginConfigSchema.parse({ onlyAudits: ['functions-coverage', 'classes-coverage'], - sourceGlob: ['src/**/*.ts'], + patterns: ['src/**/*.ts'], }), ).not.toThrow(); }); @@ -68,13 +70,15 @@ describe('DocCoveragePlugin Configuration', () => { expect(() => docCoveragePluginConfigSchema.parse({ onlyAudits: [], - sourceGlob: ['src/**/*.ts'], + patterns: ['src/**/*.ts'], }), ).not.toThrow(); }); it('allows onlyAudits to be undefined', () => { - const result = docCoveragePluginConfigSchema.parse({}); + const result = docCoveragePluginConfigSchema.parse({ + patterns: ['src/**/*.ts'], + }); expect(result.onlyAudits).toBeUndefined(); }); @@ -82,6 +86,7 @@ describe('DocCoveragePlugin Configuration', () => { expect(() => docCoveragePluginConfigSchema.parse({ onlyAudits: 'functions-coverage', + patterns: ['src/**/*.ts'], }), ).toThrow('Expected array'); }); @@ -90,6 +95,7 @@ describe('DocCoveragePlugin Configuration', () => { expect(() => docCoveragePluginConfigSchema.parse({ onlyAudits: [123, true], + patterns: ['src/**/*.ts'], }), ).toThrow('Expected string, received number'); }); @@ -100,7 +106,7 @@ describe('DocCoveragePlugin Configuration', () => { expect(() => docCoveragePluginConfigSchema.parse({ skipAudits: ['functions-coverage', 'classes-coverage'], - sourceGlob: ['src/**/*.ts'], + patterns: ['src/**/*.ts'], }), ).not.toThrow(); }); @@ -109,13 +115,15 @@ describe('DocCoveragePlugin Configuration', () => { expect(() => docCoveragePluginConfigSchema.parse({ skipAudits: [], - sourceGlob: ['src/**/*.ts'], + patterns: ['src/**/*.ts'], }), ).not.toThrow(); }); it('allows skipAudits to be undefined', () => { - const result = docCoveragePluginConfigSchema.parse({}); + const result = docCoveragePluginConfigSchema.parse({ + patterns: ['src/**/*.ts'], + }); expect(result.skipAudits).toBeUndefined(); }); @@ -123,6 +131,7 @@ describe('DocCoveragePlugin Configuration', () => { expect(() => docCoveragePluginConfigSchema.parse({ skipAudits: 'functions-coverage', + patterns: ['src/**/*.ts'], }), ).toThrow('Expected array'); }); @@ -131,6 +140,7 @@ describe('DocCoveragePlugin Configuration', () => { expect(() => docCoveragePluginConfigSchema.parse({ skipAudits: [123, true], + patterns: ['src/**/*.ts'], }), ).toThrow('Expected string'); }); diff --git a/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.ts b/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.ts index 32728d5a2..f2ff9bed0 100644 --- a/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.ts +++ b/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.ts @@ -29,7 +29,7 @@ export const PLUGIN_DOCS_URL = * plugins: [ * // ... other plugins ... * docCoveragePlugin({ - * sourceGlob: ['src/**/*.{ts,tsx}'] + * patterns: ['src/**/*.{ts,tsx}'] * }) * ] * } diff --git a/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.unit.test.ts b/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.unit.test.ts index 3fda06e71..a2154b098 100644 --- a/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.unit.test.ts +++ b/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.unit.test.ts @@ -25,13 +25,13 @@ describe('docCoveragePlugin', () => { it('should create a valid plugin config', () => { expect( docCoveragePlugin({ - sourceGlob: ['src/**/*.ts', '!**/*.spec.ts', '!**/*.test.ts'], + patterns: ['src/**/*.ts', '!**/*.spec.ts', '!**/*.test.ts'], }), ).toStrictEqual( expect.objectContaining({ slug: PLUGIN_SLUG, title: PLUGIN_TITLE, - icon: 'folder-src', + icon: 'folder-docs', description: PLUGIN_DESCRIPTION, docsUrl: PLUGIN_DOCS_URL, groups: expect.any(Array), @@ -45,27 +45,27 @@ describe('docCoveragePlugin', () => { expect(() => docCoveragePlugin({ // @ts-expect-error testing invalid config - sourceGlob: 123, + patterns: 123, }), ).toThrow('Expected array, received number'); }); it('should filter groups', () => { - const config = { sourceGlob: ['src/**/*.ts'] }; + const config = { patterns: ['src/**/*.ts'] }; docCoveragePlugin(config); expect(filterGroupsByOnlyAudits).toHaveBeenCalledWith(groups, config); }); it('should filter audits', async () => { - const config = { sourceGlob: ['src/**/*.ts'] }; + const config = { patterns: ['src/**/*.ts'] }; docCoveragePlugin(config); expect(filterAuditsByPluginConfig).toHaveBeenCalledWith(config); }); it('should forward options to runner function', async () => { - const config = { sourceGlob: ['src/**/*.ts'] }; + const config = { patterns: ['src/**/*.ts'] }; docCoveragePlugin(config); expect(createRunnerFunction).toHaveBeenCalledWith(config); diff --git a/packages/plugin-doc-coverage/src/lib/runner/doc-processer.integration.test.ts b/packages/plugin-doc-coverage/src/lib/runner/doc-processer.integration.test.ts index 6cc3ae576..b3bf1e59a 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/doc-processer.integration.test.ts +++ b/packages/plugin-doc-coverage/src/lib/runner/doc-processer.integration.test.ts @@ -1,11 +1,24 @@ +import path from 'node:path'; +import { fileURLToPath } from 'url'; import { processDocCoverage } from './doc-processer.js'; import type { DocumentationData } from './models.js'; +type DocumentationDataCovered = DocumentationData & { + coverage: number; +}; + describe('processDocCoverage', () => { + const fixturesDir = path.join( + fileURLToPath(path.dirname(import.meta.url)), + '../../../mocks/fixtures', + ); + it('should detect undocumented class', () => { - const sourcePath = - 'packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/classes-coverage.ts'; - const results = processDocCoverage({ sourceGlob: [sourcePath] }); + const sourcePath = path.join( + fixturesDir, + 'missing-documentation/classes-coverage.ts', + ); + const results = processDocCoverage({ patterns: [sourcePath] }); expect(results.classes).toStrictEqual({ coverage: 0, nodesCount: 1, @@ -17,28 +30,28 @@ describe('processDocCoverage', () => { line: 1, }, ], - } satisfies DocumentationData & { - coverage: number; - }); + } satisfies DocumentationDataCovered); }); it('should detect documented class', () => { - const sourcePath = - 'packages/plugin-doc-coverage/mocks/fixtures/filled-documentation/classes-coverage.ts'; - const results = processDocCoverage({ sourceGlob: [sourcePath] }); + const sourcePath = path.join( + fixturesDir, + 'filled-documentation/classes-coverage.ts', + ); + const results = processDocCoverage({ patterns: [sourcePath] }); expect(results.classes).toStrictEqual({ coverage: 100, nodesCount: 1, issues: [], - } satisfies DocumentationData & { - coverage: number; - }); + } satisfies DocumentationDataCovered); }); it('should detect undocumented method', () => { - const sourcePath = - 'packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/methods-coverage.ts'; - const results = processDocCoverage({ sourceGlob: [sourcePath] }); + const sourcePath = path.join( + fixturesDir, + 'missing-documentation/methods-coverage.ts', + ); + const results = processDocCoverage({ patterns: [sourcePath] }); expect(results.methods).toStrictEqual({ coverage: 0, nodesCount: 1, @@ -50,28 +63,28 @@ describe('processDocCoverage', () => { line: 5, }, ], - } satisfies DocumentationData & { - coverage: number; - }); + } satisfies DocumentationDataCovered); }); it('should detect documented method', () => { - const sourcePath = - 'packages/plugin-doc-coverage/mocks/fixtures/filled-documentation/methods-coverage.ts'; - const results = processDocCoverage({ sourceGlob: [sourcePath] }); + const sourcePath = path.join( + fixturesDir, + 'filled-documentation/methods-coverage.ts', + ); + const results = processDocCoverage({ patterns: [sourcePath] }); expect(results.methods).toStrictEqual({ coverage: 100, nodesCount: 1, issues: [], - } satisfies DocumentationData & { - coverage: number; - }); + } satisfies DocumentationDataCovered); }); it('should detect undocumented interface', () => { - const sourcePath = - 'packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/interfaces-coverage.ts'; - const results = processDocCoverage({ sourceGlob: [sourcePath] }); + const sourcePath = path.join( + fixturesDir, + 'missing-documentation/interfaces-coverage.ts', + ); + const results = processDocCoverage({ patterns: [sourcePath] }); expect(results.interfaces).toStrictEqual({ coverage: 0, nodesCount: 1, @@ -83,28 +96,28 @@ describe('processDocCoverage', () => { line: 1, }, ], - } satisfies DocumentationData & { - coverage: number; - }); + } satisfies DocumentationDataCovered); }); it('should detect documented interface', () => { - const sourcePath = - 'packages/plugin-doc-coverage/mocks/fixtures/filled-documentation/interfaces-coverage.ts'; - const results = processDocCoverage({ sourceGlob: [sourcePath] }); + const sourcePath = path.join( + fixturesDir, + 'filled-documentation/interfaces-coverage.ts', + ); + const results = processDocCoverage({ patterns: [sourcePath] }); expect(results.interfaces).toStrictEqual({ coverage: 100, nodesCount: 1, issues: [], - } satisfies DocumentationData & { - coverage: number; - }); + } satisfies DocumentationDataCovered); }); it('should detect undocumented variable', () => { - const sourcePath = - 'packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/variables-coverage.ts'; - const results = processDocCoverage({ sourceGlob: [sourcePath] }); + const sourcePath = path.join( + fixturesDir, + 'missing-documentation/variables-coverage.ts', + ); + const results = processDocCoverage({ patterns: [sourcePath] }); expect(results.variables).toStrictEqual({ coverage: 0, nodesCount: 1, @@ -116,28 +129,28 @@ describe('processDocCoverage', () => { line: 1, }, ], - } satisfies DocumentationData & { - coverage: number; - }); + } satisfies DocumentationDataCovered); }); it('should detect documented variable', () => { - const sourcePath = - 'packages/plugin-doc-coverage/mocks/fixtures/filled-documentation/variables-coverage.ts'; - const results = processDocCoverage({ sourceGlob: [sourcePath] }); + const sourcePath = path.join( + fixturesDir, + 'filled-documentation/variables-coverage.ts', + ); + const results = processDocCoverage({ patterns: [sourcePath] }); expect(results.variables).toStrictEqual({ coverage: 100, nodesCount: 1, issues: [], - } satisfies DocumentationData & { - coverage: number; - }); + } satisfies DocumentationDataCovered); }); it('should detect undocumented property', () => { - const sourcePath = - 'packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/properties-coverage.ts'; - const results = processDocCoverage({ sourceGlob: [sourcePath] }); + const sourcePath = path.join( + fixturesDir, + 'missing-documentation/properties-coverage.ts', + ); + const results = processDocCoverage({ patterns: [sourcePath] }); expect(results.properties).toStrictEqual({ coverage: 0, nodesCount: 1, @@ -149,28 +162,28 @@ describe('processDocCoverage', () => { line: 5, }, ], - } satisfies DocumentationData & { - coverage: number; - }); + } satisfies DocumentationDataCovered); }); it('should detect documented property', () => { - const sourcePath = - 'packages/plugin-doc-coverage/mocks/fixtures/filled-documentation/properties-coverage.ts'; - const results = processDocCoverage({ sourceGlob: [sourcePath] }); + const sourcePath = path.join( + fixturesDir, + 'filled-documentation/properties-coverage.ts', + ); + const results = processDocCoverage({ patterns: [sourcePath] }); expect(results.properties).toStrictEqual({ coverage: 100, nodesCount: 1, issues: [], - } satisfies DocumentationData & { - coverage: number; - }); + } satisfies DocumentationDataCovered); }); it('should detect undocumented type', () => { - const sourcePath = - 'packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/types-coverage.ts'; - const results = processDocCoverage({ sourceGlob: [sourcePath] }); + const sourcePath = path.join( + fixturesDir, + 'missing-documentation/types-coverage.ts', + ); + const results = processDocCoverage({ patterns: [sourcePath] }); expect(results.types).toStrictEqual({ coverage: 0, nodesCount: 1, @@ -182,28 +195,28 @@ describe('processDocCoverage', () => { line: 1, }, ], - } satisfies DocumentationData & { - coverage: number; - }); + } satisfies DocumentationDataCovered); }); it('should detect documented type', () => { - const sourcePath = - 'packages/plugin-doc-coverage/mocks/fixtures/filled-documentation/types-coverage.ts'; - const results = processDocCoverage({ sourceGlob: [sourcePath] }); + const sourcePath = path.join( + fixturesDir, + 'filled-documentation/types-coverage.ts', + ); + const results = processDocCoverage({ patterns: [sourcePath] }); expect(results.types).toStrictEqual({ coverage: 100, nodesCount: 1, issues: [], - } satisfies DocumentationData & { - coverage: number; - }); + } satisfies DocumentationDataCovered); }); it('should detect undocumented enum', () => { - const sourcePath = - 'packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/enums-coverage.ts'; - const results = processDocCoverage({ sourceGlob: [sourcePath] }); + const sourcePath = path.join( + fixturesDir, + 'missing-documentation/enums-coverage.ts', + ); + const results = processDocCoverage({ patterns: [sourcePath] }); expect(results.enums).toStrictEqual({ coverage: 0, nodesCount: 1, @@ -215,21 +228,19 @@ describe('processDocCoverage', () => { line: 1, }, ], - } satisfies DocumentationData & { - coverage: number; - }); + } satisfies DocumentationDataCovered); }); it('should detect documented enum', () => { - const sourcePath = - 'packages/plugin-doc-coverage/mocks/fixtures/filled-documentation/enums-coverage.ts'; - const results = processDocCoverage({ sourceGlob: [sourcePath] }); + const sourcePath = path.join( + fixturesDir, + 'filled-documentation/enums-coverage.ts', + ); + const results = processDocCoverage({ patterns: [sourcePath] }); expect(results.enums).toStrictEqual({ coverage: 100, nodesCount: 1, issues: [], - } satisfies DocumentationData & { - coverage: number; - }); + } satisfies DocumentationDataCovered); }); }); diff --git a/packages/plugin-doc-coverage/src/lib/runner/doc-processer.ts b/packages/plugin-doc-coverage/src/lib/runner/doc-processer.ts index 7960c1bb1..82075fa94 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/doc-processer.ts +++ b/packages/plugin-doc-coverage/src/lib/runner/doc-processer.ts @@ -1,12 +1,14 @@ import { ClassDeclaration, + JSDoc, Project, SourceFile, + SyntaxKind, VariableStatement, } from 'ts-morph'; +import { objectFromEntries, objectToEntries } from '@code-pushup/utils'; import type { DocCoveragePluginConfig } from '../config.js'; import type { - CoverageType, DocumentationCoverageReport, DocumentationReport, } from './models.js'; @@ -16,10 +18,17 @@ import { getCoverageTypeFromKind, } from './utils.js'; +type Node = { + getKind: () => SyntaxKind; + getName: () => string; + getStartLineNumber: () => number; + getJsDocs: () => JSDoc[]; +}; + /** * Gets the variables information from the variable statements * @param variableStatements - The variable statements to process - * @returns {Node[]} The variables information with the right methods to get the information + * @returns The variables information with the right methods to get the information */ export function getVariablesInformation( variableStatements: VariableStatement[], @@ -42,14 +51,14 @@ export function getVariablesInformation( /** * Processes documentation coverage for TypeScript files in the specified path - * @param toInclude - The file path pattern to include for documentation analysis - * @returns {DocumentationCoverageReport} Object containing coverage statistics and undocumented items + * @param config - The configuration object containing patterns to include for documentation analysis + * @returns Object containing coverage statistics and undocumented items */ export function processDocCoverage( config: DocCoveragePluginConfig, ): DocumentationCoverageReport { const project = new Project(); - project.addSourceFilesAtPaths(config.sourceGlob); + project.addSourceFilesAtPaths(config.patterns); return getDocumentationReport(project.getSourceFiles()); } @@ -69,7 +78,7 @@ export function getAllNodesFromASourceFile(sourceFile: SourceFile) { /** * Gets the documentation coverage report from the source files * @param sourceFiles - The source files to process - * @returns {DocumentationCoverageReport} The documentation coverage report + * @returns The documentation coverage report */ export function getDocumentationReport( sourceFiles: SourceFile[], @@ -79,32 +88,9 @@ export function getDocumentationReport( const filePath = sourceFile.getFilePath(); const allNodesFromFile = getAllNodesFromASourceFile(sourceFile); - const coverageReportOfCurrentFile = allNodesFromFile.reduce( - (acc, node) => { - const nodeType = getCoverageTypeFromKind(node.getKind()); - const currentTypeReport = acc[nodeType]; - const updatedIssues = - node.getJsDocs().length === 0 - ? [ - ...currentTypeReport.issues, - { - file: filePath, - type: nodeType, - name: node.getName() || '', - line: node.getStartLineNumber(), - }, - ] - : currentTypeReport.issues; - - return { - ...acc, - [nodeType]: { - nodesCount: currentTypeReport.nodesCount + 1, - issues: updatedIssues, - }, - }; - }, - createEmptyCoverageData(), + const coverageReportOfCurrentFile = getCoverageFromAllNodesOfFile( + allNodesFromFile as Node[], + filePath, ); return mergeDocumentationReports( @@ -118,11 +104,44 @@ export function getDocumentationReport( return calculateCoverage(unprocessedCoverageReport); } +/** + * Gets the coverage from all nodes of a file + * @param nodes - The nodes to process + * @param filePath - The file path where the nodes are located + * @returns The coverage report for the nodes + */ +function getCoverageFromAllNodesOfFile(nodes: Node[], filePath: string) { + return nodes.reduce((acc: DocumentationReport, node: Node) => { + const nodeType = getCoverageTypeFromKind(node.getKind()); + const currentTypeReport = acc[nodeType]; + const updatedIssues = + node.getJsDocs().length === 0 + ? [ + ...currentTypeReport.issues, + { + file: filePath, + type: nodeType, + name: node.getName() || '', + line: node.getStartLineNumber(), + }, + ] + : currentTypeReport.issues; + + return { + ...acc, + [nodeType]: { + nodesCount: currentTypeReport.nodesCount + 1, + issues: updatedIssues, + }, + }; + }, createEmptyCoverageData()); +} + /** * Merges two documentation results * @param accumulatedReport - The first empty documentation result * @param currentFileReport - The second documentation result - * @returns {DocumentationReport} The merged documentation result + * @returns The merged documentation result */ export function mergeDocumentationReports( accumulatedReport: DocumentationReport, @@ -147,7 +166,7 @@ export function mergeDocumentationReports( /** * Gets the nodes from a class * @param classNodes - The class nodes to process - * @returns {Node[]} The nodes from the class + * @returns The nodes from the class */ export function getClassNodes(classNodes: ClassDeclaration[]) { return classNodes.flatMap(classNode => [ diff --git a/packages/plugin-doc-coverage/src/lib/runner/models.ts b/packages/plugin-doc-coverage/src/lib/runner/models.ts index d95b7b47b..8081347cb 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/models.ts +++ b/packages/plugin-doc-coverage/src/lib/runner/models.ts @@ -1,20 +1,32 @@ -import type { SyntaxKind } from 'ts-morph'; +import { SyntaxKind } from 'ts-morph'; -/** Maps the SyntaxKind from the library ts-morph to the coverage type. */ -type SyntaxKindToStringLiteral = { - [SyntaxKind.ClassDeclaration]: 'classes'; - [SyntaxKind.MethodDeclaration]: 'methods'; - [SyntaxKind.FunctionDeclaration]: 'functions'; - [SyntaxKind.InterfaceDeclaration]: 'interfaces'; - [SyntaxKind.EnumDeclaration]: 'enums'; - [SyntaxKind.VariableDeclaration]: 'variables'; - [SyntaxKind.PropertyDeclaration]: 'properties'; - [SyntaxKind.TypeAliasDeclaration]: 'types'; -}; +/** The possible coverage types for documentation analysis */ +export const SINTAX_KIND_LITERAL_STRING = { + CLASSES: 'classes', + METHODS: 'methods', + FUNCTIONS: 'functions', + INTERFACES: 'interfaces', + ENUMS: 'enums', + VARIABLES: 'variables', + PROPERTIES: 'properties', + TYPES: 'types', +} as const; -/**The coverage type is the same as the SyntaxKind from the library ts-morph but as a string. */ export type CoverageType = - SyntaxKindToStringLiteral[keyof SyntaxKindToStringLiteral]; + (typeof SINTAX_KIND_LITERAL_STRING)[keyof typeof SINTAX_KIND_LITERAL_STRING]; + +/** Maps the SyntaxKind from the library ts-morph to the coverage type. */ +export const SYNTAX_COVERAGE_MAP = new Map([ + [SyntaxKind.ClassDeclaration, SINTAX_KIND_LITERAL_STRING.CLASSES], + [SyntaxKind.MethodDeclaration, SINTAX_KIND_LITERAL_STRING.METHODS], + [SyntaxKind.FunctionDeclaration, SINTAX_KIND_LITERAL_STRING.FUNCTIONS], + [SyntaxKind.InterfaceDeclaration, SINTAX_KIND_LITERAL_STRING.INTERFACES], + [SyntaxKind.EnumDeclaration, SINTAX_KIND_LITERAL_STRING.ENUMS], + [SyntaxKind.VariableDeclaration, SINTAX_KIND_LITERAL_STRING.VARIABLES], + [SyntaxKind.VariableStatement, SINTAX_KIND_LITERAL_STRING.VARIABLES], + [SyntaxKind.PropertyDeclaration, SINTAX_KIND_LITERAL_STRING.PROPERTIES], + [SyntaxKind.TypeAliasDeclaration, SINTAX_KIND_LITERAL_STRING.TYPES], +]); /** The undocumented node is the node that is not documented and has the information for the report. */ export type UndocumentedNode = { diff --git a/packages/plugin-doc-coverage/src/lib/runner/runner.integration.test.ts b/packages/plugin-doc-coverage/src/lib/runner/runner.integration.test.ts index 8ba35bdd3..38cc4b954 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/runner.integration.test.ts +++ b/packages/plugin-doc-coverage/src/lib/runner/runner.integration.test.ts @@ -26,7 +26,7 @@ describe('createRunnerFunction', () => { `missing-documentation/${coverageType}-coverage.ts`, ); const runnerFn = createRunnerFunction({ - sourceGlob: [filePath], + patterns: [filePath], }); const results = await runnerFn(() => void 0); @@ -68,7 +68,7 @@ describe('createRunnerFunction', () => { `filled-documentation/${coverageType}-coverage.ts`, ); const runnerFn = createRunnerFunction({ - sourceGlob: [filePath], + patterns: [filePath], }); const results = await runnerFn(() => void 0); @@ -91,7 +91,7 @@ describe('createRunnerFunction', () => { it('should respect onlyAudits option', async () => { const runnerFn = createRunnerFunction({ - sourceGlob: [], + patterns: [], onlyAudits: ['classes-coverage', 'methods-coverage'], }); @@ -106,7 +106,7 @@ describe('createRunnerFunction', () => { it('should respect skipAudits option', async () => { const runnerFn = createRunnerFunction({ - sourceGlob: [], + patterns: [], skipAudits: ['classes-coverage', 'methods-coverage'], }); diff --git a/packages/plugin-doc-coverage/src/lib/runner/runner.ts b/packages/plugin-doc-coverage/src/lib/runner/runner.ts index 739b10814..2e045909f 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/runner.ts +++ b/packages/plugin-doc-coverage/src/lib/runner/runner.ts @@ -1,7 +1,8 @@ import type { AuditOutputs, RunnerFunction } from '@code-pushup/models'; import type { DocCoveragePluginConfig } from '../config.js'; import { processDocCoverage } from './doc-processer.js'; -import type { DocumentationCoverageReport } from './models.js'; +import type { CoverageType, DocumentationCoverageReport } from './models.js'; +import { coverageTypeToAuditSlug } from './utils.js'; export function createRunnerFunction( config: DocCoveragePluginConfig, @@ -24,7 +25,7 @@ export function trasformCoverageReportToAuditOutputs( ): AuditOutputs { return Object.entries(coverageResult) .filter(([type]) => { - const auditSlug = `${type}-coverage`; + const auditSlug = coverageTypeToAuditSlug(type as CoverageType); if (options.onlyAudits?.length) { return options.onlyAudits.includes(auditSlug); } diff --git a/packages/plugin-doc-coverage/src/lib/runner/utils.ts b/packages/plugin-doc-coverage/src/lib/runner/utils.ts index 298b64806..d75a20752 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/utils.ts +++ b/packages/plugin-doc-coverage/src/lib/runner/utils.ts @@ -1,8 +1,9 @@ import { SyntaxKind } from 'ts-morph'; -import type { - CoverageType, - DocumentationCoverageReport, - DocumentationReport, +import { + type CoverageType, + type DocumentationCoverageReport, + type DocumentationReport, + SYNTAX_COVERAGE_MAP, } from './models.js'; /** @@ -22,6 +23,15 @@ export function createEmptyCoverageData(): DocumentationReport { }; } +/** + * Converts the coverage type to the audit slug. + * @param type - The coverage type. + * @returns The audit slug. + */ +export function coverageTypeToAuditSlug(type: CoverageType) { + return `${type}-coverage`; +} + /** * Calculates the coverage percentage for each coverage type. * @param result - The unprocessed coverage result. @@ -56,25 +66,10 @@ export function calculateCoverage(result: DocumentationReport) { * @returns The coverage type. */ export function getCoverageTypeFromKind(kind: SyntaxKind): CoverageType { - switch (kind) { - case SyntaxKind.ClassDeclaration: - return 'classes'; - case SyntaxKind.MethodDeclaration: - return 'methods'; - case SyntaxKind.FunctionDeclaration: - return 'functions'; - case SyntaxKind.InterfaceDeclaration: - return 'interfaces'; - case SyntaxKind.EnumDeclaration: - return 'enums'; - case SyntaxKind.VariableStatement: - case SyntaxKind.VariableDeclaration: - return 'variables'; - case SyntaxKind.PropertyDeclaration: - return 'properties'; - case SyntaxKind.TypeAliasDeclaration: - return 'types'; - default: - throw new Error(`Unsupported syntax kind: ${kind}`); + const type = SYNTAX_COVERAGE_MAP.get(kind); + + if (!type) { + throw new Error(`Unsupported syntax kind: ${kind}`); } + return type; } From 4a75b86b120784abc72e3d47f31991f66164c509 Mon Sep 17 00:00:00 2001 From: Alejandro Date: Sun, 12 Jan 2025 00:33:35 +0100 Subject: [PATCH 60/66] chore(plugin-doc-coverage): rename processer to processor --- .../doc-processor.unit.test.ts.snap | 92 +++++++++++++++++++ ...t.ts => doc-processor.integration.test.ts} | 2 +- .../{doc-processer.ts => doc-processor.ts} | 0 ...nit.test.ts => doc-processor.unit.test.ts} | 6 +- .../src/lib/runner/runner.ts | 2 +- .../plugin-doc-coverage/tsconfig.lib.json | 2 +- 6 files changed, 98 insertions(+), 6 deletions(-) create mode 100644 packages/plugin-doc-coverage/src/lib/runner/__snapshots__/doc-processor.unit.test.ts.snap rename packages/plugin-doc-coverage/src/lib/runner/{doc-processer.integration.test.ts => doc-processor.integration.test.ts} (99%) rename packages/plugin-doc-coverage/src/lib/runner/{doc-processer.ts => doc-processor.ts} (100%) rename packages/plugin-doc-coverage/src/lib/runner/{doc-processer.unit.test.ts => doc-processor.unit.test.ts} (98%) diff --git a/packages/plugin-doc-coverage/src/lib/runner/__snapshots__/doc-processor.unit.test.ts.snap b/packages/plugin-doc-coverage/src/lib/runner/__snapshots__/doc-processor.unit.test.ts.snap new file mode 100644 index 000000000..3a9c6a965 --- /dev/null +++ b/packages/plugin-doc-coverage/src/lib/runner/__snapshots__/doc-processor.unit.test.ts.snap @@ -0,0 +1,92 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`getDocumentationReport > should produce a full report 1`] = ` +{ + "classes": { + "coverage": 33.33, + "issues": [ + { + "file": "test.ts", + "line": 4, + "name": "test", + "type": "classes", + }, + { + "file": "test.ts", + "line": 5, + "name": "test", + "type": "classes", + }, + ], + "nodesCount": 3, + }, + "enums": { + "coverage": 33.33, + "issues": [ + { + "file": "test.ts", + "line": 8, + "name": "test", + "type": "enums", + }, + { + "file": "test.ts", + "line": 9, + "name": "test", + "type": "enums", + }, + ], + "nodesCount": 3, + }, + "functions": { + "coverage": 100, + "issues": [], + "nodesCount": 3, + }, + "interfaces": { + "coverage": 66.67, + "issues": [ + { + "file": "test.ts", + "line": 15, + "name": "test", + "type": "interfaces", + }, + ], + "nodesCount": 3, + }, + "methods": { + "coverage": 100, + "issues": [], + "nodesCount": 0, + }, + "properties": { + "coverage": 100, + "issues": [], + "nodesCount": 0, + }, + "types": { + "coverage": 50, + "issues": [ + { + "file": "test.ts", + "line": 10, + "name": "test", + "type": "types", + }, + { + "file": "test.ts", + "line": 11, + "name": "test", + "type": "types", + }, + ], + "nodesCount": 4, + }, + "variables": { + "coverage": 100, + "issues": [], + "nodesCount": 0, + }, +} +`; diff --git a/packages/plugin-doc-coverage/src/lib/runner/doc-processer.integration.test.ts b/packages/plugin-doc-coverage/src/lib/runner/doc-processor.integration.test.ts similarity index 99% rename from packages/plugin-doc-coverage/src/lib/runner/doc-processer.integration.test.ts rename to packages/plugin-doc-coverage/src/lib/runner/doc-processor.integration.test.ts index b3bf1e59a..4cda007f0 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/doc-processer.integration.test.ts +++ b/packages/plugin-doc-coverage/src/lib/runner/doc-processor.integration.test.ts @@ -1,6 +1,6 @@ import path from 'node:path'; import { fileURLToPath } from 'url'; -import { processDocCoverage } from './doc-processer.js'; +import { processDocCoverage } from './doc-processor.js'; import type { DocumentationData } from './models.js'; type DocumentationDataCovered = DocumentationData & { diff --git a/packages/plugin-doc-coverage/src/lib/runner/doc-processer.ts b/packages/plugin-doc-coverage/src/lib/runner/doc-processor.ts similarity index 100% rename from packages/plugin-doc-coverage/src/lib/runner/doc-processer.ts rename to packages/plugin-doc-coverage/src/lib/runner/doc-processor.ts diff --git a/packages/plugin-doc-coverage/src/lib/runner/doc-processer.unit.test.ts b/packages/plugin-doc-coverage/src/lib/runner/doc-processor.unit.test.ts similarity index 98% rename from packages/plugin-doc-coverage/src/lib/runner/doc-processer.unit.test.ts rename to packages/plugin-doc-coverage/src/lib/runner/doc-processor.unit.test.ts index 08ed4f95c..4200216ad 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/doc-processer.unit.test.ts +++ b/packages/plugin-doc-coverage/src/lib/runner/doc-processor.unit.test.ts @@ -1,13 +1,13 @@ import type { ClassDeclaration, VariableStatement } from 'ts-morph'; -import { nodeMock } from '../../../mocks/node.mock'; -import { sourceFileMock } from '../../../mocks/source-files.mock'; +import { nodeMock } from '../../../mocks/node.mock.js'; +import { sourceFileMock } from '../../../mocks/source-files.mock.js'; import { getAllNodesFromASourceFile, getClassNodes, getDocumentationReport, getVariablesInformation, mergeDocumentationReports, -} from './doc-processer.js'; +} from './doc-processor.js'; import type { DocumentationReport } from './models.js'; describe('getDocumentationReport', () => { diff --git a/packages/plugin-doc-coverage/src/lib/runner/runner.ts b/packages/plugin-doc-coverage/src/lib/runner/runner.ts index 2e045909f..0b086b63b 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/runner.ts +++ b/packages/plugin-doc-coverage/src/lib/runner/runner.ts @@ -1,6 +1,6 @@ import type { AuditOutputs, RunnerFunction } from '@code-pushup/models'; import type { DocCoveragePluginConfig } from '../config.js'; -import { processDocCoverage } from './doc-processer.js'; +import { processDocCoverage } from './doc-processor.js'; import type { CoverageType, DocumentationCoverageReport } from './models.js'; import { coverageTypeToAuditSlug } from './utils.js'; diff --git a/packages/plugin-doc-coverage/tsconfig.lib.json b/packages/plugin-doc-coverage/tsconfig.lib.json index 37e86c560..ef2f7e2b3 100644 --- a/packages/plugin-doc-coverage/tsconfig.lib.json +++ b/packages/plugin-doc-coverage/tsconfig.lib.json @@ -5,7 +5,7 @@ "declaration": true, "types": ["node"] }, - "include": ["src/**/*.ts", "src/lib/runner/doc-processer.js"], + "include": ["src/**/*.ts"], "exclude": [ "vite.config.unit.ts", "vite.config.integration.ts", From 0089b1f47c56d6a68d03ea713f3c6b3786b94de1 Mon Sep 17 00:00:00 2001 From: Alejandro Date: Sun, 12 Jan 2025 00:49:44 +0100 Subject: [PATCH 61/66] chore(plugin-jsdocs): rename plugin --- code-pushup.config.ts | 22 ++++++---- code-pushup.preset.ts | 28 ++++++------ e2e/plugin-doc-coverage-e2e/project.json | 6 +-- packages/plugin-doc-coverage/project.json | 42 ------------------ packages/plugin-doc-coverage/src/index.ts | 4 -- .../README.md | 28 ++++++------ .../eslint.config.js | 0 .../filled-documentation/classes-coverage.ts | 0 .../filled-documentation/enums-coverage.ts | 0 .../interfaces-coverage.ts | 0 .../filled-documentation/methods-coverage.ts | 0 .../properties-coverage.ts | 0 .../filled-documentation/types-coverage.ts | 0 .../variables-coverage.ts | 0 .../missing-documentation/classes-coverage.ts | 0 .../missing-documentation/enums-coverage.ts | 0 .../functions-coverage.ts | 0 .../interfaces-coverage.ts | 0 .../missing-documentation/methods-coverage.ts | 0 .../properties-coverage.ts | 0 .../missing-documentation/types-coverage.ts | 0 .../variables-coverage.ts | 0 .../mocks/node.mock.ts | 4 +- .../mocks/source-files.mock.ts | 2 +- .../package.json | 7 +-- packages/plugin-jsdocs/project.json | 42 ++++++++++++++++++ packages/plugin-jsdocs/src/index.ts | 4 ++ .../src/lib/config.ts | 10 ++--- .../src/lib/config.unit.test.ts | 43 +++++++++---------- .../src/lib/constants.ts | 0 .../src/lib/jsdocs-plugin.ts} | 23 ++++------ .../src/lib/jsdocs-plugin.unit.test.ts} | 16 +++---- .../src/lib/models.ts | 0 .../doc-processer.unit.test.ts.snap | 0 .../doc-processor.unit.test.ts.snap | 0 .../runner/doc-processor.integration.test.ts | 34 +++++++-------- .../src/lib/runner/doc-processor.ts | 6 +-- .../src/lib/runner/doc-processor.unit.test.ts | 0 .../src/lib/runner/models.ts | 0 .../src/lib/runner/runner.integration.test.ts | 0 .../src/lib/runner/runner.ts | 10 ++--- .../src/lib/runner/runner.unit.test.ts | 0 .../src/lib/runner/utils.ts | 0 .../src/lib/runner/utils.unit.test.ts | 0 .../src/lib/utils.ts | 6 +-- .../src/lib/utils.unit.test.ts | 0 .../tsconfig.json | 0 .../tsconfig.lib.json | 0 .../tsconfig.test.json | 0 .../vite.config.integration.ts | 0 .../vite.config.unit.ts | 0 tsconfig.base.json | 4 +- 52 files changed, 168 insertions(+), 173 deletions(-) delete mode 100644 packages/plugin-doc-coverage/project.json delete mode 100644 packages/plugin-doc-coverage/src/index.ts rename packages/{plugin-doc-coverage => plugin-jsdocs}/README.md (90%) rename packages/{plugin-doc-coverage => plugin-jsdocs}/eslint.config.js (100%) rename packages/{plugin-doc-coverage => plugin-jsdocs}/mocks/fixtures/filled-documentation/classes-coverage.ts (100%) rename packages/{plugin-doc-coverage => plugin-jsdocs}/mocks/fixtures/filled-documentation/enums-coverage.ts (100%) rename packages/{plugin-doc-coverage => plugin-jsdocs}/mocks/fixtures/filled-documentation/interfaces-coverage.ts (100%) rename packages/{plugin-doc-coverage => plugin-jsdocs}/mocks/fixtures/filled-documentation/methods-coverage.ts (100%) rename packages/{plugin-doc-coverage => plugin-jsdocs}/mocks/fixtures/filled-documentation/properties-coverage.ts (100%) rename packages/{plugin-doc-coverage => plugin-jsdocs}/mocks/fixtures/filled-documentation/types-coverage.ts (100%) rename packages/{plugin-doc-coverage => plugin-jsdocs}/mocks/fixtures/filled-documentation/variables-coverage.ts (100%) rename packages/{plugin-doc-coverage => plugin-jsdocs}/mocks/fixtures/missing-documentation/classes-coverage.ts (100%) rename packages/{plugin-doc-coverage => plugin-jsdocs}/mocks/fixtures/missing-documentation/enums-coverage.ts (100%) rename packages/{plugin-doc-coverage => plugin-jsdocs}/mocks/fixtures/missing-documentation/functions-coverage.ts (100%) rename packages/{plugin-doc-coverage => plugin-jsdocs}/mocks/fixtures/missing-documentation/interfaces-coverage.ts (100%) rename packages/{plugin-doc-coverage => plugin-jsdocs}/mocks/fixtures/missing-documentation/methods-coverage.ts (100%) rename packages/{plugin-doc-coverage => plugin-jsdocs}/mocks/fixtures/missing-documentation/properties-coverage.ts (100%) rename packages/{plugin-doc-coverage => plugin-jsdocs}/mocks/fixtures/missing-documentation/types-coverage.ts (100%) rename packages/{plugin-doc-coverage => plugin-jsdocs}/mocks/fixtures/missing-documentation/variables-coverage.ts (100%) rename packages/{plugin-doc-coverage => plugin-jsdocs}/mocks/node.mock.ts (91%) rename packages/{plugin-doc-coverage => plugin-jsdocs}/mocks/source-files.mock.ts (97%) rename packages/{plugin-doc-coverage => plugin-jsdocs}/package.json (88%) create mode 100644 packages/plugin-jsdocs/project.json create mode 100644 packages/plugin-jsdocs/src/index.ts rename packages/{plugin-doc-coverage => plugin-jsdocs}/src/lib/config.ts (79%) rename packages/{plugin-doc-coverage => plugin-jsdocs}/src/lib/config.unit.test.ts (75%) rename packages/{plugin-doc-coverage => plugin-jsdocs}/src/lib/constants.ts (100%) rename packages/{plugin-doc-coverage/src/lib/doc-coverage-plugin.ts => plugin-jsdocs/src/lib/jsdocs-plugin.ts} (60%) rename packages/{plugin-doc-coverage/src/lib/doc-coverage-plugin.unit.test.ts => plugin-jsdocs/src/lib/jsdocs-plugin.unit.test.ts} (88%) rename packages/{plugin-doc-coverage => plugin-jsdocs}/src/lib/models.ts (100%) rename packages/{plugin-doc-coverage => plugin-jsdocs}/src/lib/runner/__snapshots__/doc-processer.unit.test.ts.snap (100%) rename packages/{plugin-doc-coverage => plugin-jsdocs}/src/lib/runner/__snapshots__/doc-processor.unit.test.ts.snap (100%) rename packages/{plugin-doc-coverage => plugin-jsdocs}/src/lib/runner/doc-processor.integration.test.ts (84%) rename packages/{plugin-doc-coverage => plugin-jsdocs}/src/lib/runner/doc-processor.ts (97%) rename packages/{plugin-doc-coverage => plugin-jsdocs}/src/lib/runner/doc-processor.unit.test.ts (100%) rename packages/{plugin-doc-coverage => plugin-jsdocs}/src/lib/runner/models.ts (100%) rename packages/{plugin-doc-coverage => plugin-jsdocs}/src/lib/runner/runner.integration.test.ts (100%) rename packages/{plugin-doc-coverage => plugin-jsdocs}/src/lib/runner/runner.ts (86%) rename packages/{plugin-doc-coverage => plugin-jsdocs}/src/lib/runner/runner.unit.test.ts (100%) rename packages/{plugin-doc-coverage => plugin-jsdocs}/src/lib/runner/utils.ts (100%) rename packages/{plugin-doc-coverage => plugin-jsdocs}/src/lib/runner/utils.unit.test.ts (100%) rename packages/{plugin-doc-coverage => plugin-jsdocs}/src/lib/utils.ts (87%) rename packages/{plugin-doc-coverage => plugin-jsdocs}/src/lib/utils.unit.test.ts (100%) rename packages/{plugin-doc-coverage => plugin-jsdocs}/tsconfig.json (100%) rename packages/{plugin-doc-coverage => plugin-jsdocs}/tsconfig.lib.json (100%) rename packages/{plugin-doc-coverage => plugin-jsdocs}/tsconfig.test.json (100%) rename packages/{plugin-doc-coverage => plugin-jsdocs}/vite.config.integration.ts (100%) rename packages/{plugin-doc-coverage => plugin-jsdocs}/vite.config.unit.ts (100%) diff --git a/code-pushup.config.ts b/code-pushup.config.ts index f471ba7f3..9af68c6fb 100644 --- a/code-pushup.config.ts +++ b/code-pushup.config.ts @@ -1,6 +1,12 @@ import 'dotenv/config'; import { z } from 'zod'; -import { docCoverageCoreConfig } from './code-pushup.preset.js'; +import { + coverageCoreConfigNx, + eslintCoreConfigNx, + jsDocsCoreConfig, + jsPackagesCoreConfig, + lighthouseCoreConfig, +} from './code-pushup.preset.js'; import type { CoreConfig } from './packages/models/src/index.js'; import { mergeConfigs } from './packages/utils/src/index.js'; @@ -28,13 +34,13 @@ const config: CoreConfig = { export default mergeConfigs( config, - // await coverageCoreConfigNx(), - // await jsPackagesCoreConfig(), - // await lighthouseCoreConfig( - // 'https://github.com/code-pushup/cli?tab=readme-ov-file#code-pushup-cli/', - // ), - // await eslintCoreConfigNx(), - docCoverageCoreConfig([ + await coverageCoreConfigNx(), + await jsPackagesCoreConfig(), + await lighthouseCoreConfig( + 'https://github.com/code-pushup/cli?tab=readme-ov-file#code-pushup-cli/', + ), + await eslintCoreConfigNx(), + jsDocsCoreConfig([ 'packages/**/src/**/*.ts', '!packages/**/node_modules', '!packages/**/{mocks,mock}', diff --git a/code-pushup.preset.ts b/code-pushup.preset.ts index 5fa0d7321..9b9c462f9 100644 --- a/code-pushup.preset.ts +++ b/code-pushup.preset.ts @@ -5,19 +5,19 @@ import type { import coveragePlugin, { getNxCoveragePaths, } from './packages/plugin-coverage/src/index.js'; -import docCoveragePlugin, { - DocCoveragePluginConfig, -} from './packages/plugin-doc-coverage/src/index.js'; -import { - PLUGIN_SLUG, - groups, -} from './packages/plugin-doc-coverage/src/lib/constants.js'; -import { filterGroupsByOnlyAudits } from './packages/plugin-doc-coverage/src/lib/utils.js'; import eslintPlugin, { eslintConfigFromAllNxProjects, eslintConfigFromNxProject, } from './packages/plugin-eslint/src/index.js'; import jsPackagesPlugin from './packages/plugin-js-packages/src/index.js'; +import jsDocsPlugin, { + JsDocsPluginConfig, +} from './packages/plugin-jsdocs/src/index.js'; +import { + PLUGIN_SLUG, + groups, +} from './packages/plugin-jsdocs/src/lib/constants.js'; +import { filterGroupsByOnlyAudits } from './packages/plugin-jsdocs/src/lib/utils.js'; import lighthousePlugin, { lighthouseGroupRef, } from './packages/plugin-lighthouse/src/index.js'; @@ -90,8 +90,8 @@ export const eslintCategories: CategoryConfig[] = [ }, ]; -export function getDocCoverageCategories( - config: DocCoveragePluginConfig, +export function getJsDocsCategories( + config: JsDocsPluginConfig, ): CategoryConfig[] { return [ { @@ -140,14 +140,14 @@ export const lighthouseCoreConfig = async ( }; }; -export const docCoverageCoreConfig = ( - config: DocCoveragePluginConfig | string[], +export const jsDocsCoreConfig = ( + config: JsDocsPluginConfig | string[], ): CoreConfig => { return { plugins: [ - docCoveragePlugin(Array.isArray(config) ? { patterns: config } : config), + jsDocsPlugin(Array.isArray(config) ? { patterns: config } : config), ], - categories: getDocCoverageCategories( + categories: getJsDocsCategories( Array.isArray(config) ? { patterns: config } : config, ), }; diff --git a/e2e/plugin-doc-coverage-e2e/project.json b/e2e/plugin-doc-coverage-e2e/project.json index 3932705af..12a10ef13 100644 --- a/e2e/plugin-doc-coverage-e2e/project.json +++ b/e2e/plugin-doc-coverage-e2e/project.json @@ -3,6 +3,8 @@ "$schema": "../../node_modules/nx/schemas/project-schema.json", "sourceRoot": "e2e/plugin-doc-coverage-e2e/src", "projectType": "application", + "tags": ["scope:plugin", "type:e2e"], + "implicitDependencies": ["cli", "plugin-jsdocs"], "targets": { "lint": { "executor": "@nx/linter:eslint", @@ -17,7 +19,5 @@ "configFile": "e2e/plugin-doc-coverage-e2e/vite.config.e2e.ts" } } - }, - "implicitDependencies": ["cli", "plugin-doc-coverage"], - "tags": ["scope:plugin", "type:e2e"] + } } diff --git a/packages/plugin-doc-coverage/project.json b/packages/plugin-doc-coverage/project.json deleted file mode 100644 index ea971b133..000000000 --- a/packages/plugin-doc-coverage/project.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "name": "plugin-doc-coverage", - "$schema": "../../node_modules/nx/schemas/project-schema.json", - "sourceRoot": "packages/plugin-doc-coverage/src", - "projectType": "library", - "targets": { - "build": { - "executor": "@nx/js:tsc", - "outputs": ["{options.outputPath}"], - "options": { - "outputPath": "dist/packages/plugin-doc-coverage", - "main": "packages/plugin-doc-coverage/src/index.ts", - "tsConfig": "packages/plugin-doc-coverage/tsconfig.lib.json", - "additionalEntryPoints": ["packages/plugin-doc-coverage/src/bin.ts"], - "assets": ["packages/plugin-doc-coverage/*.md"] - } - }, - "lint": { - "executor": "@nx/linter:eslint", - "outputs": ["{options.outputFile}"], - "options": { - "lintFilePatterns": [ - "packages/plugin-doc-coverage/**/*.ts", - "packages/plugin-doc-coverage/package.json" - ] - } - }, - "unit-test": { - "executor": "@nx/vite:test", - "options": { - "configFile": "packages/plugin-doc-coverage/vite.config.unit.ts" - } - }, - "integration-test": { - "executor": "@nx/vite:test", - "options": { - "configFile": "packages/plugin-doc-coverage/vite.config.integration.ts" - } - } - }, - "tags": ["scope:plugin", "type:feature", "publishable"] -} diff --git a/packages/plugin-doc-coverage/src/index.ts b/packages/plugin-doc-coverage/src/index.ts deleted file mode 100644 index d26f57375..000000000 --- a/packages/plugin-doc-coverage/src/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { docCoveragePlugin } from './lib/doc-coverage-plugin.js'; - -export default docCoveragePlugin; -export type { DocCoveragePluginConfig } from './lib/config.js'; diff --git a/packages/plugin-doc-coverage/README.md b/packages/plugin-jsdocs/README.md similarity index 90% rename from packages/plugin-doc-coverage/README.md rename to packages/plugin-jsdocs/README.md index 6ed841b1a..f6a48110e 100644 --- a/packages/plugin-doc-coverage/README.md +++ b/packages/plugin-jsdocs/README.md @@ -1,8 +1,8 @@ -# @code-pushup/doc-coverage-plugin +# @code-pushup/jsdocs-plugin -[![npm](https://img.shields.io/npm/v/%40code-pushup%2Fdoc-coverage-plugin.svg)](https://www.npmjs.com/package/@code-pushup/doc-coverage-plugin) -[![downloads](https://img.shields.io/npm/dm/%40code-pushup%2Fdoc-coverage-plugin)](https://npmtrends.com/@code-pushup/doc-coverage-plugin) -[![dependencies](https://img.shields.io/librariesio/release/npm/%40code-pushup%2Fdoc-coverage-plugin)](https://www.npmjs.com/package/@code-pushup/doc-coverage-plugin?activeTab=dependencies) +[![npm](https://img.shields.io/npm/v/%40code-pushup%2Fdoc-coverage-plugin.svg)](https://www.npmjs.com/package/@code-pushup/jsdocs-plugin) +[![downloads](https://img.shields.io/npm/dm/%40code-pushup%2Fdoc-coverage-plugin)](https://npmtrends.com/@code-pushup/jsdocs-plugin) +[![dependencies](https://img.shields.io/librariesio/release/npm/%40code-pushup%2Fdoc-coverage-plugin)](https://www.npmjs.com/package/@code-pushup/jsdocs-plugin?activeTab=dependencies) ๐Ÿ“š **Code PushUp plugin for tracking documentation coverage.** ๐Ÿ“ @@ -24,27 +24,27 @@ Measured documentation types are mapped to Code PushUp audits in the following w 2. Install as a dev dependency with your package manager: ```sh - npm install --save-dev @code-pushup/doc-coverage-plugin + npm install --save-dev @code-pushup/jsdocs-plugin ``` ```sh - yarn add --dev @code-pushup/doc-coverage-plugin + yarn add --dev @code-pushup/jsdocs-plugin ``` ```sh - pnpm add --save-dev @code-pushup/doc-coverage-plugin + pnpm add --save-dev @code-pushup/jsdocs-plugin ``` 3. Add this plugin to the `plugins` array in your Code PushUp CLI config file (e.g. `code-pushup.config.ts`). ```js - import docCoveragePlugin from '@code-pushup/doc-coverage-plugin'; + import jsDocsPlugin from '@code-pushup/jsdocs-plugin'; export default { // ... plugins: [ // ... - docCoveragePlugin({ + jsDocsPlugin({ patterns: ['**/*.ts'], }), ], @@ -105,7 +105,7 @@ The plugin accepts the following parameters: Required parameter. The `patterns` option accepts an array of strings that define patterns to include or exclude files. You can use glob patterns to match files and the `!` symbol to exclude specific patterns. Example: ```js -docCoveragePlugin({ +jsDocsPlugin({ patterns: [ 'src/**/*.ts', // include all TypeScript files in src '!src/**/*.{spec,test}.ts', // exclude test files @@ -119,7 +119,7 @@ docCoveragePlugin({ Optional parameter. The `onlyAudits` option allows you to specify which documentation types you want to measure. Only the specified audits will be included in the results. Example: ```js -docCoveragePlugin({ +jsDocsPlugin({ patterns: ['src/**/*.ts'], onlyAudits: [ 'classes-coverage', @@ -133,7 +133,7 @@ docCoveragePlugin({ Optional parameter. The `skipAudits` option allows you to exclude specific documentation types from measurement. All other types will be included in the results. ```js -docCoveragePlugin({ +jsDocsPlugin({ patterns: ['src/**/*.ts'], skipAudits: [ 'variables-coverage', @@ -204,7 +204,7 @@ For instance, this is an example of the plugin output: ```json { - "packageName": "@code-pushup/doc-coverage-plugin", + "packageName": "@code-pushup/jsdocs-plugin", "version": "0.57.0", "title": "Documentation coverage", "slug": "doc-coverage", @@ -225,7 +225,7 @@ For instance, this is an example of the plugin output: } ], "description": "Official Code PushUp documentation coverage plugin.", - "docsUrl": "https://www.npmjs.com/package/@code-pushup/doc-coverage-plugin/", + "docsUrl": "https://www.npmjs.com/package/@code-pushup/jsdocs-plugin/", "groups": [ { "slug": "doc-coverage", diff --git a/packages/plugin-doc-coverage/eslint.config.js b/packages/plugin-jsdocs/eslint.config.js similarity index 100% rename from packages/plugin-doc-coverage/eslint.config.js rename to packages/plugin-jsdocs/eslint.config.js diff --git a/packages/plugin-doc-coverage/mocks/fixtures/filled-documentation/classes-coverage.ts b/packages/plugin-jsdocs/mocks/fixtures/filled-documentation/classes-coverage.ts similarity index 100% rename from packages/plugin-doc-coverage/mocks/fixtures/filled-documentation/classes-coverage.ts rename to packages/plugin-jsdocs/mocks/fixtures/filled-documentation/classes-coverage.ts diff --git a/packages/plugin-doc-coverage/mocks/fixtures/filled-documentation/enums-coverage.ts b/packages/plugin-jsdocs/mocks/fixtures/filled-documentation/enums-coverage.ts similarity index 100% rename from packages/plugin-doc-coverage/mocks/fixtures/filled-documentation/enums-coverage.ts rename to packages/plugin-jsdocs/mocks/fixtures/filled-documentation/enums-coverage.ts diff --git a/packages/plugin-doc-coverage/mocks/fixtures/filled-documentation/interfaces-coverage.ts b/packages/plugin-jsdocs/mocks/fixtures/filled-documentation/interfaces-coverage.ts similarity index 100% rename from packages/plugin-doc-coverage/mocks/fixtures/filled-documentation/interfaces-coverage.ts rename to packages/plugin-jsdocs/mocks/fixtures/filled-documentation/interfaces-coverage.ts diff --git a/packages/plugin-doc-coverage/mocks/fixtures/filled-documentation/methods-coverage.ts b/packages/plugin-jsdocs/mocks/fixtures/filled-documentation/methods-coverage.ts similarity index 100% rename from packages/plugin-doc-coverage/mocks/fixtures/filled-documentation/methods-coverage.ts rename to packages/plugin-jsdocs/mocks/fixtures/filled-documentation/methods-coverage.ts diff --git a/packages/plugin-doc-coverage/mocks/fixtures/filled-documentation/properties-coverage.ts b/packages/plugin-jsdocs/mocks/fixtures/filled-documentation/properties-coverage.ts similarity index 100% rename from packages/plugin-doc-coverage/mocks/fixtures/filled-documentation/properties-coverage.ts rename to packages/plugin-jsdocs/mocks/fixtures/filled-documentation/properties-coverage.ts diff --git a/packages/plugin-doc-coverage/mocks/fixtures/filled-documentation/types-coverage.ts b/packages/plugin-jsdocs/mocks/fixtures/filled-documentation/types-coverage.ts similarity index 100% rename from packages/plugin-doc-coverage/mocks/fixtures/filled-documentation/types-coverage.ts rename to packages/plugin-jsdocs/mocks/fixtures/filled-documentation/types-coverage.ts diff --git a/packages/plugin-doc-coverage/mocks/fixtures/filled-documentation/variables-coverage.ts b/packages/plugin-jsdocs/mocks/fixtures/filled-documentation/variables-coverage.ts similarity index 100% rename from packages/plugin-doc-coverage/mocks/fixtures/filled-documentation/variables-coverage.ts rename to packages/plugin-jsdocs/mocks/fixtures/filled-documentation/variables-coverage.ts diff --git a/packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/classes-coverage.ts b/packages/plugin-jsdocs/mocks/fixtures/missing-documentation/classes-coverage.ts similarity index 100% rename from packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/classes-coverage.ts rename to packages/plugin-jsdocs/mocks/fixtures/missing-documentation/classes-coverage.ts diff --git a/packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/enums-coverage.ts b/packages/plugin-jsdocs/mocks/fixtures/missing-documentation/enums-coverage.ts similarity index 100% rename from packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/enums-coverage.ts rename to packages/plugin-jsdocs/mocks/fixtures/missing-documentation/enums-coverage.ts diff --git a/packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/functions-coverage.ts b/packages/plugin-jsdocs/mocks/fixtures/missing-documentation/functions-coverage.ts similarity index 100% rename from packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/functions-coverage.ts rename to packages/plugin-jsdocs/mocks/fixtures/missing-documentation/functions-coverage.ts diff --git a/packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/interfaces-coverage.ts b/packages/plugin-jsdocs/mocks/fixtures/missing-documentation/interfaces-coverage.ts similarity index 100% rename from packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/interfaces-coverage.ts rename to packages/plugin-jsdocs/mocks/fixtures/missing-documentation/interfaces-coverage.ts diff --git a/packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/methods-coverage.ts b/packages/plugin-jsdocs/mocks/fixtures/missing-documentation/methods-coverage.ts similarity index 100% rename from packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/methods-coverage.ts rename to packages/plugin-jsdocs/mocks/fixtures/missing-documentation/methods-coverage.ts diff --git a/packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/properties-coverage.ts b/packages/plugin-jsdocs/mocks/fixtures/missing-documentation/properties-coverage.ts similarity index 100% rename from packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/properties-coverage.ts rename to packages/plugin-jsdocs/mocks/fixtures/missing-documentation/properties-coverage.ts diff --git a/packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/types-coverage.ts b/packages/plugin-jsdocs/mocks/fixtures/missing-documentation/types-coverage.ts similarity index 100% rename from packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/types-coverage.ts rename to packages/plugin-jsdocs/mocks/fixtures/missing-documentation/types-coverage.ts diff --git a/packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/variables-coverage.ts b/packages/plugin-jsdocs/mocks/fixtures/missing-documentation/variables-coverage.ts similarity index 100% rename from packages/plugin-doc-coverage/mocks/fixtures/missing-documentation/variables-coverage.ts rename to packages/plugin-jsdocs/mocks/fixtures/missing-documentation/variables-coverage.ts diff --git a/packages/plugin-doc-coverage/mocks/node.mock.ts b/packages/plugin-jsdocs/mocks/node.mock.ts similarity index 91% rename from packages/plugin-doc-coverage/mocks/node.mock.ts rename to packages/plugin-jsdocs/mocks/node.mock.ts index 15ad64d18..691c7ab26 100644 --- a/packages/plugin-doc-coverage/mocks/node.mock.ts +++ b/packages/plugin-jsdocs/mocks/node.mock.ts @@ -1,5 +1,5 @@ -import {SyntaxKind,} from 'ts-morph'; -import type {CoverageType} from '../src/lib/runner/models.js'; +import { SyntaxKind } from 'ts-morph'; +import type { CoverageType } from '../src/lib/runner/models.js'; export function nodeMock(options: { coverageType: CoverageType; diff --git a/packages/plugin-doc-coverage/mocks/source-files.mock.ts b/packages/plugin-jsdocs/mocks/source-files.mock.ts similarity index 97% rename from packages/plugin-doc-coverage/mocks/source-files.mock.ts rename to packages/plugin-jsdocs/mocks/source-files.mock.ts index 2b96edb56..9ae9e7047 100644 --- a/packages/plugin-doc-coverage/mocks/source-files.mock.ts +++ b/packages/plugin-jsdocs/mocks/source-files.mock.ts @@ -9,7 +9,7 @@ import { VariableStatement, } from 'ts-morph'; import type { CoverageType } from '../src/lib/runner/models.js'; -import {nodeMock} from "./node.mock"; +import { nodeMock } from './node.mock'; export function sourceFileMock( file: string, diff --git a/packages/plugin-doc-coverage/package.json b/packages/plugin-jsdocs/package.json similarity index 88% rename from packages/plugin-doc-coverage/package.json rename to packages/plugin-jsdocs/package.json index 20f0b4f52..88f29d3e2 100644 --- a/packages/plugin-doc-coverage/package.json +++ b/packages/plugin-jsdocs/package.json @@ -1,16 +1,16 @@ { - "name": "@code-pushup/doc-coverage-plugin", + "name": "@code-pushup/jsdocs-plugin", "version": "0.57.0", "description": "Code PushUp plugin for tracking documentation coverage ๐Ÿ“š", "license": "MIT", - "homepage": "https://github.com/code-pushup/cli/tree/main/packages/plugin-doc-coverage#readme", + "homepage": "https://github.com/code-pushup/cli/tree/main/packages/plugin-jsdocs#readme", "bugs": { "url": "https://github.com/code-pushup/cli/issues?q=is%3Aissue%20state%3Aopen%20type%3ABug%20label%3A\"๐Ÿงฉ%20doc-coverage-plugin\"" }, "repository": { "type": "git", "url": "git+https://github.com/code-pushup/cli.git", - "directory": "packages/plugin-doc-coverage" + "directory": "packages/plugin-jsdocs" }, "keywords": [ "documentation coverage", @@ -36,6 +36,7 @@ "type": "module", "dependencies": { "@code-pushup/models": "0.57.0", + "@code-pushup/utils": "0.57.0", "zod": "^3.22.4", "ts-morph": "^24.0.0" } diff --git a/packages/plugin-jsdocs/project.json b/packages/plugin-jsdocs/project.json new file mode 100644 index 000000000..d8534553e --- /dev/null +++ b/packages/plugin-jsdocs/project.json @@ -0,0 +1,42 @@ +{ + "name": "plugin-jsdocs", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/plugin-jsdocs/src", + "projectType": "library", + "tags": ["scope:plugin", "type:feature", "publishable"], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": ["{options.outputPath}"], + "options": { + "outputPath": "dist/packages/plugin-jsdocs", + "main": "packages/plugin-jsdocs/src/index.ts", + "tsConfig": "packages/plugin-jsdocs/tsconfig.lib.json", + "additionalEntryPoints": ["packages/plugin-jsdocs/src/bin.ts"], + "assets": ["packages/plugin-jsdocs/*.md"] + } + }, + "lint": { + "executor": "@nx/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": [ + "packages/plugin-jsdocs/**/*.ts", + "packages/plugin-jsdocs/package.json" + ] + } + }, + "unit-test": { + "executor": "@nx/vite:test", + "options": { + "configFile": "packages/plugin-jsdocs/vite.config.unit.ts" + } + }, + "integration-test": { + "executor": "@nx/vite:test", + "options": { + "configFile": "packages/plugin-jsdocs/vite.config.integration.ts" + } + } + } +} diff --git a/packages/plugin-jsdocs/src/index.ts b/packages/plugin-jsdocs/src/index.ts new file mode 100644 index 000000000..f0b65ec1c --- /dev/null +++ b/packages/plugin-jsdocs/src/index.ts @@ -0,0 +1,4 @@ +import { jsDocsPlugin } from './lib/jsdocs-plugin.js'; + +export default jsDocsPlugin; +export type { JsDocsPluginConfig } from './lib/config.js'; diff --git a/packages/plugin-doc-coverage/src/lib/config.ts b/packages/plugin-jsdocs/src/lib/config.ts similarity index 79% rename from packages/plugin-doc-coverage/src/lib/config.ts rename to packages/plugin-jsdocs/src/lib/config.ts index 6f3209c2e..5ab29d3f3 100644 --- a/packages/plugin-doc-coverage/src/lib/config.ts +++ b/packages/plugin-jsdocs/src/lib/config.ts @@ -4,7 +4,7 @@ const patternsSchema = z.union([z.string(), z.array(z.string()).min(1)], { description: 'Glob pattern to match source files to evaluate.', }); -const docCoverageTargetObjectSchema = z +const jsDocsTargetObjectSchema = z .object({ skipAudits: z .array(z.string()) @@ -25,14 +25,12 @@ const docCoverageTargetObjectSchema = z path: ['skipAudits', 'onlyAudits'], }); -export const docCoveragePluginConfigSchema = z - .union([patternsSchema, docCoverageTargetObjectSchema]) +export const jsDocsPluginConfigSchema = z + .union([patternsSchema, jsDocsTargetObjectSchema]) .transform(target => typeof target === 'string' || Array.isArray(target) ? { patterns: target } : target, ); -export type DocCoveragePluginConfig = z.infer< - typeof docCoveragePluginConfigSchema ->; +export type JsDocsPluginConfig = z.infer; diff --git a/packages/plugin-doc-coverage/src/lib/config.unit.test.ts b/packages/plugin-jsdocs/src/lib/config.unit.test.ts similarity index 75% rename from packages/plugin-doc-coverage/src/lib/config.unit.test.ts rename to packages/plugin-jsdocs/src/lib/config.unit.test.ts index c10040a29..38c3ddc42 100644 --- a/packages/plugin-doc-coverage/src/lib/config.unit.test.ts +++ b/packages/plugin-jsdocs/src/lib/config.unit.test.ts @@ -1,23 +1,20 @@ import { describe, expect, it } from 'vitest'; -import { - type DocCoveragePluginConfig, - docCoveragePluginConfigSchema, -} from './config.js'; +import { type JsDocsPluginConfig, jsDocsPluginConfigSchema } from './config.js'; -describe('DocCoveragePlugin Configuration', () => { - describe('docCoveragePluginConfigSchema', () => { +describe('JsDocsPlugin Configuration', () => { + describe('jsDocsPluginConfigSchema', () => { it('accepts a valid configuration', () => { expect(() => - docCoveragePluginConfigSchema.parse({ + jsDocsPluginConfigSchema.parse({ patterns: ['src/**/*.ts'], onlyAudits: ['functions-coverage'], - } satisfies DocCoveragePluginConfig), + } satisfies JsDocsPluginConfig), ).not.toThrow(); }); it('throws when skipAudits and onlyAudits are defined', () => { expect(() => - docCoveragePluginConfigSchema.parse({ + jsDocsPluginConfigSchema.parse({ patterns: ['src/**/*.ts'], skipAudits: ['functions-coverage'], onlyAudits: ['classes-coverage'], @@ -31,15 +28,15 @@ describe('DocCoveragePlugin Configuration', () => { describe('patterns', () => { it('accepts a valid patterns array', () => { expect(() => - docCoveragePluginConfigSchema.parse({ + jsDocsPluginConfigSchema.parse({ patterns: ['src/**/*.{ts,tsx}', '!**/*.spec.ts', '!**/*.test.ts'], - } satisfies DocCoveragePluginConfig), + } satisfies JsDocsPluginConfig), ).not.toThrow(); }); it('accepts a valid patterns array directly', () => { expect(() => - docCoveragePluginConfigSchema.parse([ + jsDocsPluginConfigSchema.parse([ 'src/**/*.{ts,tsx}', '!**/*.spec.ts', '!**/*.test.ts', @@ -49,7 +46,7 @@ describe('DocCoveragePlugin Configuration', () => { it('throws for invalid patterns type', () => { expect(() => - docCoveragePluginConfigSchema.parse({ + jsDocsPluginConfigSchema.parse({ patterns: 123, }), ).toThrow('Expected array'); @@ -59,7 +56,7 @@ describe('DocCoveragePlugin Configuration', () => { describe('onlyAudits', () => { it('accepts a valid `onlyAudits` array', () => { expect(() => - docCoveragePluginConfigSchema.parse({ + jsDocsPluginConfigSchema.parse({ onlyAudits: ['functions-coverage', 'classes-coverage'], patterns: ['src/**/*.ts'], }), @@ -68,7 +65,7 @@ describe('DocCoveragePlugin Configuration', () => { it('accepts empty array for onlyAudits', () => { expect(() => - docCoveragePluginConfigSchema.parse({ + jsDocsPluginConfigSchema.parse({ onlyAudits: [], patterns: ['src/**/*.ts'], }), @@ -76,7 +73,7 @@ describe('DocCoveragePlugin Configuration', () => { }); it('allows onlyAudits to be undefined', () => { - const result = docCoveragePluginConfigSchema.parse({ + const result = jsDocsPluginConfigSchema.parse({ patterns: ['src/**/*.ts'], }); expect(result.onlyAudits).toBeUndefined(); @@ -84,7 +81,7 @@ describe('DocCoveragePlugin Configuration', () => { it('throws for invalid onlyAudits type', () => { expect(() => - docCoveragePluginConfigSchema.parse({ + jsDocsPluginConfigSchema.parse({ onlyAudits: 'functions-coverage', patterns: ['src/**/*.ts'], }), @@ -93,7 +90,7 @@ describe('DocCoveragePlugin Configuration', () => { it('throws for array with non-string elements', () => { expect(() => - docCoveragePluginConfigSchema.parse({ + jsDocsPluginConfigSchema.parse({ onlyAudits: [123, true], patterns: ['src/**/*.ts'], }), @@ -104,7 +101,7 @@ describe('DocCoveragePlugin Configuration', () => { describe('skipAudits', () => { it('accepts valid audit slugs array', () => { expect(() => - docCoveragePluginConfigSchema.parse({ + jsDocsPluginConfigSchema.parse({ skipAudits: ['functions-coverage', 'classes-coverage'], patterns: ['src/**/*.ts'], }), @@ -113,7 +110,7 @@ describe('DocCoveragePlugin Configuration', () => { it('accepts empty array for skipAudits', () => { expect(() => - docCoveragePluginConfigSchema.parse({ + jsDocsPluginConfigSchema.parse({ skipAudits: [], patterns: ['src/**/*.ts'], }), @@ -121,7 +118,7 @@ describe('DocCoveragePlugin Configuration', () => { }); it('allows skipAudits to be undefined', () => { - const result = docCoveragePluginConfigSchema.parse({ + const result = jsDocsPluginConfigSchema.parse({ patterns: ['src/**/*.ts'], }); expect(result.skipAudits).toBeUndefined(); @@ -129,7 +126,7 @@ describe('DocCoveragePlugin Configuration', () => { it('throws for invalid skipAudits type', () => { expect(() => - docCoveragePluginConfigSchema.parse({ + jsDocsPluginConfigSchema.parse({ skipAudits: 'functions-coverage', patterns: ['src/**/*.ts'], }), @@ -138,7 +135,7 @@ describe('DocCoveragePlugin Configuration', () => { it('throws for array with non-string elements', () => { expect(() => - docCoveragePluginConfigSchema.parse({ + jsDocsPluginConfigSchema.parse({ skipAudits: [123, true], patterns: ['src/**/*.ts'], }), diff --git a/packages/plugin-doc-coverage/src/lib/constants.ts b/packages/plugin-jsdocs/src/lib/constants.ts similarity index 100% rename from packages/plugin-doc-coverage/src/lib/constants.ts rename to packages/plugin-jsdocs/src/lib/constants.ts diff --git a/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.ts b/packages/plugin-jsdocs/src/lib/jsdocs-plugin.ts similarity index 60% rename from packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.ts rename to packages/plugin-jsdocs/src/lib/jsdocs-plugin.ts index f2ff9bed0..fe2afba03 100644 --- a/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.ts +++ b/packages/plugin-jsdocs/src/lib/jsdocs-plugin.ts @@ -1,8 +1,5 @@ import type { PluginConfig } from '@code-pushup/models'; -import { - type DocCoveragePluginConfig, - docCoveragePluginConfigSchema, -} from './config.js'; +import { type JsDocsPluginConfig, jsDocsPluginConfigSchema } from './config.js'; import { PLUGIN_SLUG, groups } from './constants.js'; import { createRunnerFunction } from './runner/runner.js'; import { @@ -16,19 +13,19 @@ export const PLUGIN_DESCRIPTION = 'Official Code PushUp documentation coverage plugin.'; export const PLUGIN_DOCS_URL = - 'https://www.npmjs.com/package/@code-pushup/doc-coverage-plugin/'; + 'https://www.npmjs.com/package/@code-pushup/jsdocs-plugin/'; /** * Instantiates Code PushUp documentation coverage plugin for core config. * * @example - * import docCoveragePlugin from '@code-pushup/doc-coverage-plugin' + * import jsDocsPlugin from '@code-pushup/jsdocs-plugin' * * export default { * // ... core config ... * plugins: [ * // ... other plugins ... - * docCoveragePlugin({ + * jsDocsPlugin({ * patterns: ['src/**/*.{ts,tsx}'] * }) * ] @@ -36,10 +33,8 @@ export const PLUGIN_DOCS_URL = * * @returns Plugin configuration. */ -export function docCoveragePlugin( - config: DocCoveragePluginConfig, -): PluginConfig { - const docCoverageConfig = docCoveragePluginConfigSchema.parse(config); +export function jsDocsPlugin(config: JsDocsPluginConfig): PluginConfig { + const jsDocsConfig = jsDocsPluginConfigSchema.parse(config); return { slug: PLUGIN_SLUG, @@ -47,8 +42,8 @@ export function docCoveragePlugin( icon: 'folder-docs', description: PLUGIN_DESCRIPTION, docsUrl: PLUGIN_DOCS_URL, - groups: filterGroupsByOnlyAudits(groups, docCoverageConfig), - audits: filterAuditsByPluginConfig(docCoverageConfig), - runner: createRunnerFunction(docCoverageConfig), + groups: filterGroupsByOnlyAudits(groups, jsDocsConfig), + audits: filterAuditsByPluginConfig(jsDocsConfig), + runner: createRunnerFunction(jsDocsConfig), }; } diff --git a/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.unit.test.ts b/packages/plugin-jsdocs/src/lib/jsdocs-plugin.unit.test.ts similarity index 88% rename from packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.unit.test.ts rename to packages/plugin-jsdocs/src/lib/jsdocs-plugin.unit.test.ts index a2154b098..1be2c11fb 100644 --- a/packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.unit.test.ts +++ b/packages/plugin-jsdocs/src/lib/jsdocs-plugin.unit.test.ts @@ -4,8 +4,8 @@ import { PLUGIN_DESCRIPTION, PLUGIN_DOCS_URL, PLUGIN_TITLE, - docCoveragePlugin, -} from './doc-coverage-plugin.js'; + jsDocsPlugin, +} from './jsdocs-plugin.js'; import { createRunnerFunction } from './runner/runner.js'; import { filterAuditsByPluginConfig, @@ -21,10 +21,10 @@ vi.mock('./runner/runner.js', () => ({ createRunnerFunction: vi.fn().mockReturnValue(() => Promise.resolve([])), })); -describe('docCoveragePlugin', () => { +describe('jsDocsPlugin', () => { it('should create a valid plugin config', () => { expect( - docCoveragePlugin({ + jsDocsPlugin({ patterns: ['src/**/*.ts', '!**/*.spec.ts', '!**/*.test.ts'], }), ).toStrictEqual( @@ -43,7 +43,7 @@ describe('docCoveragePlugin', () => { it('should throw for invalid plugin options', () => { expect(() => - docCoveragePlugin({ + jsDocsPlugin({ // @ts-expect-error testing invalid config patterns: 123, }), @@ -52,21 +52,21 @@ describe('docCoveragePlugin', () => { it('should filter groups', () => { const config = { patterns: ['src/**/*.ts'] }; - docCoveragePlugin(config); + jsDocsPlugin(config); expect(filterGroupsByOnlyAudits).toHaveBeenCalledWith(groups, config); }); it('should filter audits', async () => { const config = { patterns: ['src/**/*.ts'] }; - docCoveragePlugin(config); + jsDocsPlugin(config); expect(filterAuditsByPluginConfig).toHaveBeenCalledWith(config); }); it('should forward options to runner function', async () => { const config = { patterns: ['src/**/*.ts'] }; - docCoveragePlugin(config); + jsDocsPlugin(config); expect(createRunnerFunction).toHaveBeenCalledWith(config); }); diff --git a/packages/plugin-doc-coverage/src/lib/models.ts b/packages/plugin-jsdocs/src/lib/models.ts similarity index 100% rename from packages/plugin-doc-coverage/src/lib/models.ts rename to packages/plugin-jsdocs/src/lib/models.ts diff --git a/packages/plugin-doc-coverage/src/lib/runner/__snapshots__/doc-processer.unit.test.ts.snap b/packages/plugin-jsdocs/src/lib/runner/__snapshots__/doc-processer.unit.test.ts.snap similarity index 100% rename from packages/plugin-doc-coverage/src/lib/runner/__snapshots__/doc-processer.unit.test.ts.snap rename to packages/plugin-jsdocs/src/lib/runner/__snapshots__/doc-processer.unit.test.ts.snap diff --git a/packages/plugin-doc-coverage/src/lib/runner/__snapshots__/doc-processor.unit.test.ts.snap b/packages/plugin-jsdocs/src/lib/runner/__snapshots__/doc-processor.unit.test.ts.snap similarity index 100% rename from packages/plugin-doc-coverage/src/lib/runner/__snapshots__/doc-processor.unit.test.ts.snap rename to packages/plugin-jsdocs/src/lib/runner/__snapshots__/doc-processor.unit.test.ts.snap diff --git a/packages/plugin-doc-coverage/src/lib/runner/doc-processor.integration.test.ts b/packages/plugin-jsdocs/src/lib/runner/doc-processor.integration.test.ts similarity index 84% rename from packages/plugin-doc-coverage/src/lib/runner/doc-processor.integration.test.ts rename to packages/plugin-jsdocs/src/lib/runner/doc-processor.integration.test.ts index 4cda007f0..bb023a6da 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/doc-processor.integration.test.ts +++ b/packages/plugin-jsdocs/src/lib/runner/doc-processor.integration.test.ts @@ -1,13 +1,13 @@ import path from 'node:path'; -import { fileURLToPath } from 'url'; -import { processDocCoverage } from './doc-processor.js'; +import { fileURLToPath } from 'node:url'; +import { processJsDocs } from './doc-processor.js'; import type { DocumentationData } from './models.js'; type DocumentationDataCovered = DocumentationData & { coverage: number; }; -describe('processDocCoverage', () => { +describe('processJsDocs', () => { const fixturesDir = path.join( fileURLToPath(path.dirname(import.meta.url)), '../../../mocks/fixtures', @@ -18,7 +18,7 @@ describe('processDocCoverage', () => { fixturesDir, 'missing-documentation/classes-coverage.ts', ); - const results = processDocCoverage({ patterns: [sourcePath] }); + const results = processJsDocs({ patterns: [sourcePath] }); expect(results.classes).toStrictEqual({ coverage: 0, nodesCount: 1, @@ -38,7 +38,7 @@ describe('processDocCoverage', () => { fixturesDir, 'filled-documentation/classes-coverage.ts', ); - const results = processDocCoverage({ patterns: [sourcePath] }); + const results = processJsDocs({ patterns: [sourcePath] }); expect(results.classes).toStrictEqual({ coverage: 100, nodesCount: 1, @@ -51,7 +51,7 @@ describe('processDocCoverage', () => { fixturesDir, 'missing-documentation/methods-coverage.ts', ); - const results = processDocCoverage({ patterns: [sourcePath] }); + const results = processJsDocs({ patterns: [sourcePath] }); expect(results.methods).toStrictEqual({ coverage: 0, nodesCount: 1, @@ -71,7 +71,7 @@ describe('processDocCoverage', () => { fixturesDir, 'filled-documentation/methods-coverage.ts', ); - const results = processDocCoverage({ patterns: [sourcePath] }); + const results = processJsDocs({ patterns: [sourcePath] }); expect(results.methods).toStrictEqual({ coverage: 100, nodesCount: 1, @@ -84,7 +84,7 @@ describe('processDocCoverage', () => { fixturesDir, 'missing-documentation/interfaces-coverage.ts', ); - const results = processDocCoverage({ patterns: [sourcePath] }); + const results = processJsDocs({ patterns: [sourcePath] }); expect(results.interfaces).toStrictEqual({ coverage: 0, nodesCount: 1, @@ -104,7 +104,7 @@ describe('processDocCoverage', () => { fixturesDir, 'filled-documentation/interfaces-coverage.ts', ); - const results = processDocCoverage({ patterns: [sourcePath] }); + const results = processJsDocs({ patterns: [sourcePath] }); expect(results.interfaces).toStrictEqual({ coverage: 100, nodesCount: 1, @@ -117,7 +117,7 @@ describe('processDocCoverage', () => { fixturesDir, 'missing-documentation/variables-coverage.ts', ); - const results = processDocCoverage({ patterns: [sourcePath] }); + const results = processJsDocs({ patterns: [sourcePath] }); expect(results.variables).toStrictEqual({ coverage: 0, nodesCount: 1, @@ -137,7 +137,7 @@ describe('processDocCoverage', () => { fixturesDir, 'filled-documentation/variables-coverage.ts', ); - const results = processDocCoverage({ patterns: [sourcePath] }); + const results = processJsDocs({ patterns: [sourcePath] }); expect(results.variables).toStrictEqual({ coverage: 100, nodesCount: 1, @@ -150,7 +150,7 @@ describe('processDocCoverage', () => { fixturesDir, 'missing-documentation/properties-coverage.ts', ); - const results = processDocCoverage({ patterns: [sourcePath] }); + const results = processJsDocs({ patterns: [sourcePath] }); expect(results.properties).toStrictEqual({ coverage: 0, nodesCount: 1, @@ -170,7 +170,7 @@ describe('processDocCoverage', () => { fixturesDir, 'filled-documentation/properties-coverage.ts', ); - const results = processDocCoverage({ patterns: [sourcePath] }); + const results = processJsDocs({ patterns: [sourcePath] }); expect(results.properties).toStrictEqual({ coverage: 100, nodesCount: 1, @@ -183,7 +183,7 @@ describe('processDocCoverage', () => { fixturesDir, 'missing-documentation/types-coverage.ts', ); - const results = processDocCoverage({ patterns: [sourcePath] }); + const results = processJsDocs({ patterns: [sourcePath] }); expect(results.types).toStrictEqual({ coverage: 0, nodesCount: 1, @@ -203,7 +203,7 @@ describe('processDocCoverage', () => { fixturesDir, 'filled-documentation/types-coverage.ts', ); - const results = processDocCoverage({ patterns: [sourcePath] }); + const results = processJsDocs({ patterns: [sourcePath] }); expect(results.types).toStrictEqual({ coverage: 100, nodesCount: 1, @@ -216,7 +216,7 @@ describe('processDocCoverage', () => { fixturesDir, 'missing-documentation/enums-coverage.ts', ); - const results = processDocCoverage({ patterns: [sourcePath] }); + const results = processJsDocs({ patterns: [sourcePath] }); expect(results.enums).toStrictEqual({ coverage: 0, nodesCount: 1, @@ -236,7 +236,7 @@ describe('processDocCoverage', () => { fixturesDir, 'filled-documentation/enums-coverage.ts', ); - const results = processDocCoverage({ patterns: [sourcePath] }); + const results = processJsDocs({ patterns: [sourcePath] }); expect(results.enums).toStrictEqual({ coverage: 100, nodesCount: 1, diff --git a/packages/plugin-doc-coverage/src/lib/runner/doc-processor.ts b/packages/plugin-jsdocs/src/lib/runner/doc-processor.ts similarity index 97% rename from packages/plugin-doc-coverage/src/lib/runner/doc-processor.ts rename to packages/plugin-jsdocs/src/lib/runner/doc-processor.ts index 82075fa94..eaae5cd71 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/doc-processor.ts +++ b/packages/plugin-jsdocs/src/lib/runner/doc-processor.ts @@ -7,7 +7,7 @@ import { VariableStatement, } from 'ts-morph'; import { objectFromEntries, objectToEntries } from '@code-pushup/utils'; -import type { DocCoveragePluginConfig } from '../config.js'; +import type { JsDocsPluginConfig } from '../config.js'; import type { DocumentationCoverageReport, DocumentationReport, @@ -54,8 +54,8 @@ export function getVariablesInformation( * @param config - The configuration object containing patterns to include for documentation analysis * @returns Object containing coverage statistics and undocumented items */ -export function processDocCoverage( - config: DocCoveragePluginConfig, +export function processJsDocs( + config: JsDocsPluginConfig, ): DocumentationCoverageReport { const project = new Project(); project.addSourceFilesAtPaths(config.patterns); diff --git a/packages/plugin-doc-coverage/src/lib/runner/doc-processor.unit.test.ts b/packages/plugin-jsdocs/src/lib/runner/doc-processor.unit.test.ts similarity index 100% rename from packages/plugin-doc-coverage/src/lib/runner/doc-processor.unit.test.ts rename to packages/plugin-jsdocs/src/lib/runner/doc-processor.unit.test.ts diff --git a/packages/plugin-doc-coverage/src/lib/runner/models.ts b/packages/plugin-jsdocs/src/lib/runner/models.ts similarity index 100% rename from packages/plugin-doc-coverage/src/lib/runner/models.ts rename to packages/plugin-jsdocs/src/lib/runner/models.ts diff --git a/packages/plugin-doc-coverage/src/lib/runner/runner.integration.test.ts b/packages/plugin-jsdocs/src/lib/runner/runner.integration.test.ts similarity index 100% rename from packages/plugin-doc-coverage/src/lib/runner/runner.integration.test.ts rename to packages/plugin-jsdocs/src/lib/runner/runner.integration.test.ts diff --git a/packages/plugin-doc-coverage/src/lib/runner/runner.ts b/packages/plugin-jsdocs/src/lib/runner/runner.ts similarity index 86% rename from packages/plugin-doc-coverage/src/lib/runner/runner.ts rename to packages/plugin-jsdocs/src/lib/runner/runner.ts index 0b086b63b..9c51a4aca 100644 --- a/packages/plugin-doc-coverage/src/lib/runner/runner.ts +++ b/packages/plugin-jsdocs/src/lib/runner/runner.ts @@ -1,14 +1,14 @@ import type { AuditOutputs, RunnerFunction } from '@code-pushup/models'; -import type { DocCoveragePluginConfig } from '../config.js'; -import { processDocCoverage } from './doc-processor.js'; +import type { JsDocsPluginConfig } from '../config.js'; +import { processJsDocs } from './doc-processor.js'; import type { CoverageType, DocumentationCoverageReport } from './models.js'; import { coverageTypeToAuditSlug } from './utils.js'; export function createRunnerFunction( - config: DocCoveragePluginConfig, + config: JsDocsPluginConfig, ): RunnerFunction { return (): AuditOutputs => { - const coverageResult = processDocCoverage(config); + const coverageResult = processJsDocs(config); return trasformCoverageReportToAuditOutputs(coverageResult, config); }; } @@ -21,7 +21,7 @@ export function createRunnerFunction( */ export function trasformCoverageReportToAuditOutputs( coverageResult: DocumentationCoverageReport, - options: Pick, + options: Pick, ): AuditOutputs { return Object.entries(coverageResult) .filter(([type]) => { diff --git a/packages/plugin-doc-coverage/src/lib/runner/runner.unit.test.ts b/packages/plugin-jsdocs/src/lib/runner/runner.unit.test.ts similarity index 100% rename from packages/plugin-doc-coverage/src/lib/runner/runner.unit.test.ts rename to packages/plugin-jsdocs/src/lib/runner/runner.unit.test.ts diff --git a/packages/plugin-doc-coverage/src/lib/runner/utils.ts b/packages/plugin-jsdocs/src/lib/runner/utils.ts similarity index 100% rename from packages/plugin-doc-coverage/src/lib/runner/utils.ts rename to packages/plugin-jsdocs/src/lib/runner/utils.ts diff --git a/packages/plugin-doc-coverage/src/lib/runner/utils.unit.test.ts b/packages/plugin-jsdocs/src/lib/runner/utils.unit.test.ts similarity index 100% rename from packages/plugin-doc-coverage/src/lib/runner/utils.unit.test.ts rename to packages/plugin-jsdocs/src/lib/runner/utils.unit.test.ts diff --git a/packages/plugin-doc-coverage/src/lib/utils.ts b/packages/plugin-jsdocs/src/lib/utils.ts similarity index 87% rename from packages/plugin-doc-coverage/src/lib/utils.ts rename to packages/plugin-jsdocs/src/lib/utils.ts index 73e04c0af..5803c8194 100644 --- a/packages/plugin-doc-coverage/src/lib/utils.ts +++ b/packages/plugin-jsdocs/src/lib/utils.ts @@ -1,5 +1,5 @@ import type { Audit, Group } from '@code-pushup/models'; -import type { DocCoveragePluginConfig } from './config.js'; +import type { JsDocsPluginConfig } from './config.js'; import { AUDITS_MAP } from './constants.js'; /** @@ -10,7 +10,7 @@ import { AUDITS_MAP } from './constants.js'; * @returns The audits. */ export function filterAuditsByPluginConfig( - config: Pick, + config: Pick, ): Audit[] { const { onlyAudits, skipAudits } = config; @@ -38,7 +38,7 @@ export function filterAuditsByPluginConfig( */ export function filterGroupsByOnlyAudits( groups: Group[], - options: Pick, + options: Pick, ): Group[] { const audits = filterAuditsByPluginConfig(options); return groups diff --git a/packages/plugin-doc-coverage/src/lib/utils.unit.test.ts b/packages/plugin-jsdocs/src/lib/utils.unit.test.ts similarity index 100% rename from packages/plugin-doc-coverage/src/lib/utils.unit.test.ts rename to packages/plugin-jsdocs/src/lib/utils.unit.test.ts diff --git a/packages/plugin-doc-coverage/tsconfig.json b/packages/plugin-jsdocs/tsconfig.json similarity index 100% rename from packages/plugin-doc-coverage/tsconfig.json rename to packages/plugin-jsdocs/tsconfig.json diff --git a/packages/plugin-doc-coverage/tsconfig.lib.json b/packages/plugin-jsdocs/tsconfig.lib.json similarity index 100% rename from packages/plugin-doc-coverage/tsconfig.lib.json rename to packages/plugin-jsdocs/tsconfig.lib.json diff --git a/packages/plugin-doc-coverage/tsconfig.test.json b/packages/plugin-jsdocs/tsconfig.test.json similarity index 100% rename from packages/plugin-doc-coverage/tsconfig.test.json rename to packages/plugin-jsdocs/tsconfig.test.json diff --git a/packages/plugin-doc-coverage/vite.config.integration.ts b/packages/plugin-jsdocs/vite.config.integration.ts similarity index 100% rename from packages/plugin-doc-coverage/vite.config.integration.ts rename to packages/plugin-jsdocs/vite.config.integration.ts diff --git a/packages/plugin-doc-coverage/vite.config.unit.ts b/packages/plugin-jsdocs/vite.config.unit.ts similarity index 100% rename from packages/plugin-doc-coverage/vite.config.unit.ts rename to packages/plugin-jsdocs/vite.config.unit.ts diff --git a/tsconfig.base.json b/tsconfig.base.json index 026003f3e..66277fc44 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -24,9 +24,6 @@ "@code-pushup/cli": ["packages/cli/src/index.ts"], "@code-pushup/core": ["packages/core/src/index.ts"], "@code-pushup/coverage-plugin": ["packages/plugin-coverage/src/index.ts"], - "@code-pushup/doc-coverage-plugin": [ - "packages/plugin-doc-coverage/src/index.ts" - ], "@code-pushup/eslint-plugin": ["packages/plugin-eslint/src/index.ts"], "@code-pushup/js-packages-plugin": [ "packages/plugin-js-packages/src/index.ts" @@ -36,6 +33,7 @@ ], "@code-pushup/models": ["packages/models/src/index.ts"], "@code-pushup/nx-plugin": ["packages/nx-plugin/src/index.ts"], + "@code-pushup/plugin-jsdocs": ["packages/plugin-jsdocs/src/index.ts"], "@code-pushup/test-nx-utils": ["testing/test-nx-utils/src/index.ts"], "@code-pushup/test-setup": ["testing/test-setup/src/index.ts"], "@code-pushup/test-utils": ["testing/test-utils/src/index.ts"], From cad42185561a71d5f2dbd928e8b8b015e9b8cf54 Mon Sep 17 00:00:00 2001 From: Alejandro Date: Fri, 17 Jan 2025 01:54:30 +0100 Subject: [PATCH 62/66] chore(plugin-jsdocs): changes from pr --- e2e/plugin-doc-coverage-e2e/project.json | 8 +- .../tests/collect.e2e.test.ts | 2 +- .../vite.config.e2e.ts | 2 +- packages/plugin-jsdocs/src/lib/config.ts | 2 +- .../doc-processer.unit.test.ts.snap | 92 ------------------- .../src/lib/runner/doc-processor.ts | 4 +- .../plugin-jsdocs/vite.config.integration.ts | 2 +- packages/plugin-jsdocs/vite.config.unit.ts | 2 +- 8 files changed, 11 insertions(+), 103 deletions(-) delete mode 100644 packages/plugin-jsdocs/src/lib/runner/__snapshots__/doc-processer.unit.test.ts.snap diff --git a/e2e/plugin-doc-coverage-e2e/project.json b/e2e/plugin-doc-coverage-e2e/project.json index 12a10ef13..88d2c308b 100644 --- a/e2e/plugin-doc-coverage-e2e/project.json +++ b/e2e/plugin-doc-coverage-e2e/project.json @@ -1,7 +1,7 @@ { - "name": "plugin-doc-coverage-e2e", + "name": "plugin-jsdocs-e2e", "$schema": "../../node_modules/nx/schemas/project-schema.json", - "sourceRoot": "e2e/plugin-doc-coverage-e2e/src", + "sourceRoot": "e2e/plugin-jsdocs-e2e/src", "projectType": "application", "tags": ["scope:plugin", "type:e2e"], "implicitDependencies": ["cli", "plugin-jsdocs"], @@ -10,13 +10,13 @@ "executor": "@nx/linter:eslint", "outputs": ["{options.outputFile}"], "options": { - "lintFilePatterns": ["e2e/plugin-doc-coverage-e2e/**/*.ts"] + "lintFilePatterns": ["e2e/plugin-jsdocs-e2e/**/*.ts"] } }, "e2e": { "executor": "@nx/vite:test", "options": { - "configFile": "e2e/plugin-doc-coverage-e2e/vite.config.e2e.ts" + "configFile": "e2e/plugin-jsdocs-e2e/vite.config.e2e.ts" } } } diff --git a/e2e/plugin-doc-coverage-e2e/tests/collect.e2e.test.ts b/e2e/plugin-doc-coverage-e2e/tests/collect.e2e.test.ts index 846290009..cbda4bc29 100644 --- a/e2e/plugin-doc-coverage-e2e/tests/collect.e2e.test.ts +++ b/e2e/plugin-doc-coverage-e2e/tests/collect.e2e.test.ts @@ -15,7 +15,7 @@ import { executeProcess, readJsonFile } from '@code-pushup/utils'; describe('PLUGIN collect report with doc-coverage-plugin NPM package', () => { const fixturesDir = path.join( 'e2e', - 'plugin-doc-coverage-e2e', + 'plugin-jsdocs-e2e', 'mocks', 'fixtures', ); diff --git a/e2e/plugin-doc-coverage-e2e/vite.config.e2e.ts b/e2e/plugin-doc-coverage-e2e/vite.config.e2e.ts index 510819007..f592cebec 100644 --- a/e2e/plugin-doc-coverage-e2e/vite.config.e2e.ts +++ b/e2e/plugin-doc-coverage-e2e/vite.config.e2e.ts @@ -3,7 +3,7 @@ import { defineConfig } from 'vite'; import { tsconfigPathAliases } from '../../tools/vitest-tsconfig-path-aliases.js'; export default defineConfig({ - cacheDir: '../../node_modules/.vite/plugin-doc-coverage-e2e', + cacheDir: '../../node_modules/.vite/plugin-jsdocs-e2e', test: { reporters: ['basic'], testTimeout: 120_000, diff --git a/packages/plugin-jsdocs/src/lib/config.ts b/packages/plugin-jsdocs/src/lib/config.ts index 5ab29d3f3..aad427cd1 100644 --- a/packages/plugin-jsdocs/src/lib/config.ts +++ b/packages/plugin-jsdocs/src/lib/config.ts @@ -33,4 +33,4 @@ export const jsDocsPluginConfigSchema = z : target, ); -export type JsDocsPluginConfig = z.infer; +export type JsDocsPluginConfig = z.input; diff --git a/packages/plugin-jsdocs/src/lib/runner/__snapshots__/doc-processer.unit.test.ts.snap b/packages/plugin-jsdocs/src/lib/runner/__snapshots__/doc-processer.unit.test.ts.snap deleted file mode 100644 index 3a9c6a965..000000000 --- a/packages/plugin-jsdocs/src/lib/runner/__snapshots__/doc-processer.unit.test.ts.snap +++ /dev/null @@ -1,92 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`getDocumentationReport > should produce a full report 1`] = ` -{ - "classes": { - "coverage": 33.33, - "issues": [ - { - "file": "test.ts", - "line": 4, - "name": "test", - "type": "classes", - }, - { - "file": "test.ts", - "line": 5, - "name": "test", - "type": "classes", - }, - ], - "nodesCount": 3, - }, - "enums": { - "coverage": 33.33, - "issues": [ - { - "file": "test.ts", - "line": 8, - "name": "test", - "type": "enums", - }, - { - "file": "test.ts", - "line": 9, - "name": "test", - "type": "enums", - }, - ], - "nodesCount": 3, - }, - "functions": { - "coverage": 100, - "issues": [], - "nodesCount": 3, - }, - "interfaces": { - "coverage": 66.67, - "issues": [ - { - "file": "test.ts", - "line": 15, - "name": "test", - "type": "interfaces", - }, - ], - "nodesCount": 3, - }, - "methods": { - "coverage": 100, - "issues": [], - "nodesCount": 0, - }, - "properties": { - "coverage": 100, - "issues": [], - "nodesCount": 0, - }, - "types": { - "coverage": 50, - "issues": [ - { - "file": "test.ts", - "line": 10, - "name": "test", - "type": "types", - }, - { - "file": "test.ts", - "line": 11, - "name": "test", - "type": "types", - }, - ], - "nodesCount": 4, - }, - "variables": { - "coverage": 100, - "issues": [], - "nodesCount": 0, - }, -} -`; diff --git a/packages/plugin-jsdocs/src/lib/runner/doc-processor.ts b/packages/plugin-jsdocs/src/lib/runner/doc-processor.ts index eaae5cd71..2a678f392 100644 --- a/packages/plugin-jsdocs/src/lib/runner/doc-processor.ts +++ b/packages/plugin-jsdocs/src/lib/runner/doc-processor.ts @@ -20,7 +20,7 @@ import { type Node = { getKind: () => SyntaxKind; - getName: () => string; + getName: () => string | undefined; getStartLineNumber: () => number; getJsDocs: () => JSDoc[]; }; @@ -89,7 +89,7 @@ export function getDocumentationReport( const allNodesFromFile = getAllNodesFromASourceFile(sourceFile); const coverageReportOfCurrentFile = getCoverageFromAllNodesOfFile( - allNodesFromFile as Node[], + allNodesFromFile, filePath, ); diff --git a/packages/plugin-jsdocs/vite.config.integration.ts b/packages/plugin-jsdocs/vite.config.integration.ts index d60b962ed..8923fa06f 100644 --- a/packages/plugin-jsdocs/vite.config.integration.ts +++ b/packages/plugin-jsdocs/vite.config.integration.ts @@ -15,7 +15,7 @@ export default defineConfig({ poolOptions: { threads: { singleThread: true } }, coverage: { reporter: ['text', 'lcov'], - reportsDirectory: '../../coverage/plugin-doc-coverage/integration-tests', + reportsDirectory: '../../coverage/plugin-jsdocs/integration-tests', exclude: ['mocks/**', '**/types.ts'], }, environment: 'node', diff --git a/packages/plugin-jsdocs/vite.config.unit.ts b/packages/plugin-jsdocs/vite.config.unit.ts index 5aef1ef13..048851c63 100644 --- a/packages/plugin-jsdocs/vite.config.unit.ts +++ b/packages/plugin-jsdocs/vite.config.unit.ts @@ -15,7 +15,7 @@ export default defineConfig({ poolOptions: { threads: { singleThread: true } }, coverage: { reporter: ['text', 'lcov'], - reportsDirectory: '../../coverage/plugin-doc-coverage/unit-tests', + reportsDirectory: '../../coverage/plugin-jsdocs/unit-tests', exclude: ['mocks/**', '**/types.ts'], }, environment: 'node', From 004cd6d847fb061aa234d94d73d9bc2f3280f21e Mon Sep 17 00:00:00 2001 From: Alejandro <49059458+aramirezj@users.noreply.github.com> Date: Fri, 17 Jan 2025 01:54:51 +0100 Subject: [PATCH 63/66] Update packages/plugin-jsdocs/src/lib/runner/models.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Matฤ›j Chalk <34691111+matejchalk@users.noreply.github.com> --- .../plugin-jsdocs/src/lib/runner/models.ts | 38 +++++++++---------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/packages/plugin-jsdocs/src/lib/runner/models.ts b/packages/plugin-jsdocs/src/lib/runner/models.ts index 8081347cb..aa676679d 100644 --- a/packages/plugin-jsdocs/src/lib/runner/models.ts +++ b/packages/plugin-jsdocs/src/lib/runner/models.ts @@ -1,31 +1,27 @@ import { SyntaxKind } from 'ts-morph'; /** The possible coverage types for documentation analysis */ -export const SINTAX_KIND_LITERAL_STRING = { - CLASSES: 'classes', - METHODS: 'methods', - FUNCTIONS: 'functions', - INTERFACES: 'interfaces', - ENUMS: 'enums', - VARIABLES: 'variables', - PROPERTIES: 'properties', - TYPES: 'types', -} as const; - export type CoverageType = - (typeof SINTAX_KIND_LITERAL_STRING)[keyof typeof SINTAX_KIND_LITERAL_STRING]; + | 'classes' + | 'methods' + | 'functions' + | 'interfaces' + | 'enums' + | 'variables' + | 'properties' + | 'types'; /** Maps the SyntaxKind from the library ts-morph to the coverage type. */ export const SYNTAX_COVERAGE_MAP = new Map([ - [SyntaxKind.ClassDeclaration, SINTAX_KIND_LITERAL_STRING.CLASSES], - [SyntaxKind.MethodDeclaration, SINTAX_KIND_LITERAL_STRING.METHODS], - [SyntaxKind.FunctionDeclaration, SINTAX_KIND_LITERAL_STRING.FUNCTIONS], - [SyntaxKind.InterfaceDeclaration, SINTAX_KIND_LITERAL_STRING.INTERFACES], - [SyntaxKind.EnumDeclaration, SINTAX_KIND_LITERAL_STRING.ENUMS], - [SyntaxKind.VariableDeclaration, SINTAX_KIND_LITERAL_STRING.VARIABLES], - [SyntaxKind.VariableStatement, SINTAX_KIND_LITERAL_STRING.VARIABLES], - [SyntaxKind.PropertyDeclaration, SINTAX_KIND_LITERAL_STRING.PROPERTIES], - [SyntaxKind.TypeAliasDeclaration, SINTAX_KIND_LITERAL_STRING.TYPES], + [SyntaxKind.ClassDeclaration, 'classes'], + [SyntaxKind.MethodDeclaration, 'methods'], + [SyntaxKind.FunctionDeclaration, 'functions'], + [SyntaxKind.InterfaceDeclaration, 'interfaces'], + [SyntaxKind.EnumDeclaration, 'enums'], + [SyntaxKind.VariableDeclaration, 'variables'], + [SyntaxKind.VariableStatement, 'variables'], + [SyntaxKind.PropertyDeclaration, 'properties'], + [SyntaxKind.TypeAliasDeclaration, 'types'], ]); /** The undocumented node is the node that is not documented and has the information for the report. */ From d64735e132b73160ce9bb41a9813a229086844ed Mon Sep 17 00:00:00 2001 From: Alejandro Date: Fri, 17 Jan 2025 02:11:21 +0100 Subject: [PATCH 64/66] chore(plugin-jsdocs): stuff in the pr and more rename --- .../fixtures/angular/code-pushup.config.ts | 4 +-- .../fixtures/react/code-pushup.config.ts | 4 +-- .../tests/collect.e2e.test.ts | 4 +-- packages/plugin-jsdocs/README.md | 30 +++++++++---------- packages/plugin-jsdocs/package.json | 2 +- packages/plugin-jsdocs/src/lib/config.ts | 6 ++++ packages/plugin-jsdocs/src/lib/constants.ts | 2 +- .../plugin-jsdocs/src/lib/jsdocs-plugin.ts | 9 ++---- .../plugin-jsdocs/src/lib/runner/constants.ts | 15 ++++++++++ .../src/lib/runner/doc-processor.ts | 4 +-- .../plugin-jsdocs/src/lib/runner/models.ts | 15 ---------- .../plugin-jsdocs/src/lib/runner/runner.ts | 6 ++-- .../plugin-jsdocs/src/lib/runner/utils.ts | 10 +++---- packages/plugin-jsdocs/src/lib/utils.ts | 6 ++-- tsconfig.base.json | 2 +- 15 files changed, 60 insertions(+), 59 deletions(-) create mode 100644 packages/plugin-jsdocs/src/lib/runner/constants.ts diff --git a/e2e/plugin-doc-coverage-e2e/mocks/fixtures/angular/code-pushup.config.ts b/e2e/plugin-doc-coverage-e2e/mocks/fixtures/angular/code-pushup.config.ts index ba479e9f5..27c389120 100644 --- a/e2e/plugin-doc-coverage-e2e/mocks/fixtures/angular/code-pushup.config.ts +++ b/e2e/plugin-doc-coverage-e2e/mocks/fixtures/angular/code-pushup.config.ts @@ -1,5 +1,5 @@ -import docCoveragePlugin from '@code-pushup/doc-coverage-plugin'; +import jsDocsPlugin from '@code-pushup/jsdocs-plugin'; export default { - plugins: [await docCoveragePlugin({ sourceGlob: ['**/*.ts'] })], + plugins: [jsDocsPlugin(['**/*.ts'])], }; diff --git a/e2e/plugin-doc-coverage-e2e/mocks/fixtures/react/code-pushup.config.ts b/e2e/plugin-doc-coverage-e2e/mocks/fixtures/react/code-pushup.config.ts index ba479e9f5..27c389120 100644 --- a/e2e/plugin-doc-coverage-e2e/mocks/fixtures/react/code-pushup.config.ts +++ b/e2e/plugin-doc-coverage-e2e/mocks/fixtures/react/code-pushup.config.ts @@ -1,5 +1,5 @@ -import docCoveragePlugin from '@code-pushup/doc-coverage-plugin'; +import jsDocsPlugin from '@code-pushup/jsdocs-plugin'; export default { - plugins: [await docCoveragePlugin({ sourceGlob: ['**/*.ts'] })], + plugins: [jsDocsPlugin(['**/*.ts'])], }; diff --git a/e2e/plugin-doc-coverage-e2e/tests/collect.e2e.test.ts b/e2e/plugin-doc-coverage-e2e/tests/collect.e2e.test.ts index cbda4bc29..499c02c20 100644 --- a/e2e/plugin-doc-coverage-e2e/tests/collect.e2e.test.ts +++ b/e2e/plugin-doc-coverage-e2e/tests/collect.e2e.test.ts @@ -12,7 +12,7 @@ import { } from '@code-pushup/test-utils'; import { executeProcess, readJsonFile } from '@code-pushup/utils'; -describe('PLUGIN collect report with doc-coverage-plugin NPM package', () => { +describe('PLUGIN collect report with jsdocs-plugin NPM package', () => { const fixturesDir = path.join( 'e2e', 'plugin-jsdocs-e2e', @@ -47,7 +47,7 @@ describe('PLUGIN collect report with doc-coverage-plugin NPM package', () => { await teardownTestFolder(reactOutputDir); }); - it('should run Doc Coverage plugin for Angular example dir and create report.json', async () => { + it('should run JSDoc plugin for Angular example dir and create report.json', async () => { const { code, stdout } = await executeProcess({ command: 'npx', args: ['@code-pushup/cli', 'collect', '--no-progress'], diff --git a/packages/plugin-jsdocs/README.md b/packages/plugin-jsdocs/README.md index f6a48110e..67617b4d7 100644 --- a/packages/plugin-jsdocs/README.md +++ b/packages/plugin-jsdocs/README.md @@ -1,8 +1,8 @@ # @code-pushup/jsdocs-plugin -[![npm](https://img.shields.io/npm/v/%40code-pushup%2Fdoc-coverage-plugin.svg)](https://www.npmjs.com/package/@code-pushup/jsdocs-plugin) -[![downloads](https://img.shields.io/npm/dm/%40code-pushup%2Fdoc-coverage-plugin)](https://npmtrends.com/@code-pushup/jsdocs-plugin) -[![dependencies](https://img.shields.io/librariesio/release/npm/%40code-pushup%2Fdoc-coverage-plugin)](https://www.npmjs.com/package/@code-pushup/jsdocs-plugin?activeTab=dependencies) +[![npm](https://img.shields.io/npm/v/%40code-pushup%2Fjsdocs-plugin.svg)](https://www.npmjs.com/package/@code-pushup/jsdocs-plugin) +[![downloads](https://img.shields.io/npm/dm/%40code-pushup%2Fjsdocs-plugin)](https://npmtrends.com/@code-pushup/jsdocs-plugin) +[![dependencies](https://img.shields.io/librariesio/release/npm/%40code-pushup%2Fjsdocs-plugin)](https://www.npmjs.com/package/@code-pushup/jsdocs-plugin?activeTab=dependencies) ๐Ÿ“š **Code PushUp plugin for tracking documentation coverage.** ๐Ÿ“ @@ -44,9 +44,7 @@ Measured documentation types are mapped to Code PushUp audits in the following w // ... plugins: [ // ... - jsDocsPlugin({ - patterns: ['**/*.ts'], - }), + jsDocsPlugin(['**/*.ts']), ], }; ``` @@ -65,8 +63,8 @@ Measured documentation types are mapped to Code PushUp audits in the following w refs: [ { type: 'group', - plugin: 'doc-coverage', - slug: 'doc-coverage', + plugin: 'jsdocs', + slug: 'jsdocs', weight: 1, }, // ... @@ -157,8 +155,8 @@ This plugin provides a group for convenient declaration in your config. When def refs: [ { type: 'group', - plugin: 'doc-coverage', - slug: 'doc-coverage', + plugin: 'jsdocs', + slug: 'jsdocs', weight: 1, }, // ... @@ -179,14 +177,14 @@ Each documentation type still has its own audit. So when you want to include a s refs: [ { type: 'audit', - plugin: 'doc-coverage', - slug: 'class-doc-coverage', + plugin: 'jsdocs', + slug: 'class-jsdocs', weight: 2, }, { type: 'audit', - plugin: 'doc-coverage', - slug: 'function-doc-coverage', + plugin: 'jsdocs', + slug: 'function-jsdocs', weight: 1, }, // ... @@ -207,7 +205,7 @@ For instance, this is an example of the plugin output: "packageName": "@code-pushup/jsdocs-plugin", "version": "0.57.0", "title": "Documentation coverage", - "slug": "doc-coverage", + "slug": "jsdocs", "icon": "folder-src", "duration": 920, "date": "2024-12-17T16:45:28.581Z", @@ -228,7 +226,7 @@ For instance, this is an example of the plugin output: "docsUrl": "https://www.npmjs.com/package/@code-pushup/jsdocs-plugin/", "groups": [ { - "slug": "doc-coverage", + "slug": "jsdocs", "refs": [ { "slug": "percentage-coverage", diff --git a/packages/plugin-jsdocs/package.json b/packages/plugin-jsdocs/package.json index 88f29d3e2..2cf5f125c 100644 --- a/packages/plugin-jsdocs/package.json +++ b/packages/plugin-jsdocs/package.json @@ -5,7 +5,7 @@ "license": "MIT", "homepage": "https://github.com/code-pushup/cli/tree/main/packages/plugin-jsdocs#readme", "bugs": { - "url": "https://github.com/code-pushup/cli/issues?q=is%3Aissue%20state%3Aopen%20type%3ABug%20label%3A\"๐Ÿงฉ%20doc-coverage-plugin\"" + "url": "https://github.com/code-pushup/cli/issues?q=is%3Aissue%20state%3Aopen%20type%3ABug%20label%3A\"๐Ÿงฉ%20jsdocs-plugin\"" }, "repository": { "type": "git", diff --git a/packages/plugin-jsdocs/src/lib/config.ts b/packages/plugin-jsdocs/src/lib/config.ts index aad427cd1..2b793591d 100644 --- a/packages/plugin-jsdocs/src/lib/config.ts +++ b/packages/plugin-jsdocs/src/lib/config.ts @@ -33,4 +33,10 @@ export const jsDocsPluginConfigSchema = z : target, ); +/** Type of the config that is passed to the plugin */ export type JsDocsPluginConfig = z.input; + +/** Same as JsDocsPluginConfig but processed so the config is already an object even if it was passed the array of patterns */ +export type JsDocsPluginTransformedConfig = z.infer< + typeof jsDocsPluginConfigSchema +>; diff --git a/packages/plugin-jsdocs/src/lib/constants.ts b/packages/plugin-jsdocs/src/lib/constants.ts index cbd09b671..558ef38de 100644 --- a/packages/plugin-jsdocs/src/lib/constants.ts +++ b/packages/plugin-jsdocs/src/lib/constants.ts @@ -1,7 +1,7 @@ import type { Audit, Group } from '@code-pushup/models'; import type { AuditSlug } from './models.js'; -export const PLUGIN_SLUG = 'doc-coverage'; +export const PLUGIN_SLUG = 'jsdocs'; export const AUDITS_MAP: Record = { 'classes-coverage': { diff --git a/packages/plugin-jsdocs/src/lib/jsdocs-plugin.ts b/packages/plugin-jsdocs/src/lib/jsdocs-plugin.ts index fe2afba03..694d0c135 100644 --- a/packages/plugin-jsdocs/src/lib/jsdocs-plugin.ts +++ b/packages/plugin-jsdocs/src/lib/jsdocs-plugin.ts @@ -7,10 +7,9 @@ import { filterGroupsByOnlyAudits, } from './utils.js'; -export const PLUGIN_TITLE = 'Documentation coverage'; +export const PLUGIN_TITLE = 'JSDoc coverage'; -export const PLUGIN_DESCRIPTION = - 'Official Code PushUp documentation coverage plugin.'; +export const PLUGIN_DESCRIPTION = 'Official Code PushUp JSDoc coverage plugin.'; export const PLUGIN_DOCS_URL = 'https://www.npmjs.com/package/@code-pushup/jsdocs-plugin/'; @@ -25,9 +24,7 @@ export const PLUGIN_DOCS_URL = * // ... core config ... * plugins: [ * // ... other plugins ... - * jsDocsPlugin({ - * patterns: ['src/**/*.{ts,tsx}'] - * }) + * jsDocsPlugin(['**/*.ts']) * ] * } * diff --git a/packages/plugin-jsdocs/src/lib/runner/constants.ts b/packages/plugin-jsdocs/src/lib/runner/constants.ts new file mode 100644 index 000000000..bb0a5da9a --- /dev/null +++ b/packages/plugin-jsdocs/src/lib/runner/constants.ts @@ -0,0 +1,15 @@ +import { SyntaxKind } from 'ts-morph'; +import type { CoverageType } from './models.js'; + +/** Maps the SyntaxKind from the library ts-morph to the coverage type. */ +export const SYNTAX_COVERAGE_MAP = new Map([ + [SyntaxKind.ClassDeclaration, 'classes'], + [SyntaxKind.MethodDeclaration, 'methods'], + [SyntaxKind.FunctionDeclaration, 'functions'], + [SyntaxKind.InterfaceDeclaration, 'interfaces'], + [SyntaxKind.EnumDeclaration, 'enums'], + [SyntaxKind.VariableDeclaration, 'variables'], + [SyntaxKind.VariableStatement, 'variables'], + [SyntaxKind.PropertyDeclaration, 'properties'], + [SyntaxKind.TypeAliasDeclaration, 'types'], +]); diff --git a/packages/plugin-jsdocs/src/lib/runner/doc-processor.ts b/packages/plugin-jsdocs/src/lib/runner/doc-processor.ts index 2a678f392..ba26d14e0 100644 --- a/packages/plugin-jsdocs/src/lib/runner/doc-processor.ts +++ b/packages/plugin-jsdocs/src/lib/runner/doc-processor.ts @@ -7,7 +7,7 @@ import { VariableStatement, } from 'ts-morph'; import { objectFromEntries, objectToEntries } from '@code-pushup/utils'; -import type { JsDocsPluginConfig } from '../config.js'; +import type { JsDocsPluginTransformedConfig } from '../config.js'; import type { DocumentationCoverageReport, DocumentationReport, @@ -55,7 +55,7 @@ export function getVariablesInformation( * @returns Object containing coverage statistics and undocumented items */ export function processJsDocs( - config: JsDocsPluginConfig, + config: JsDocsPluginTransformedConfig, ): DocumentationCoverageReport { const project = new Project(); project.addSourceFilesAtPaths(config.patterns); diff --git a/packages/plugin-jsdocs/src/lib/runner/models.ts b/packages/plugin-jsdocs/src/lib/runner/models.ts index aa676679d..6da638ece 100644 --- a/packages/plugin-jsdocs/src/lib/runner/models.ts +++ b/packages/plugin-jsdocs/src/lib/runner/models.ts @@ -1,5 +1,3 @@ -import { SyntaxKind } from 'ts-morph'; - /** The possible coverage types for documentation analysis */ export type CoverageType = | 'classes' @@ -11,19 +9,6 @@ export type CoverageType = | 'properties' | 'types'; -/** Maps the SyntaxKind from the library ts-morph to the coverage type. */ -export const SYNTAX_COVERAGE_MAP = new Map([ - [SyntaxKind.ClassDeclaration, 'classes'], - [SyntaxKind.MethodDeclaration, 'methods'], - [SyntaxKind.FunctionDeclaration, 'functions'], - [SyntaxKind.InterfaceDeclaration, 'interfaces'], - [SyntaxKind.EnumDeclaration, 'enums'], - [SyntaxKind.VariableDeclaration, 'variables'], - [SyntaxKind.VariableStatement, 'variables'], - [SyntaxKind.PropertyDeclaration, 'properties'], - [SyntaxKind.TypeAliasDeclaration, 'types'], -]); - /** The undocumented node is the node that is not documented and has the information for the report. */ export type UndocumentedNode = { file: string; diff --git a/packages/plugin-jsdocs/src/lib/runner/runner.ts b/packages/plugin-jsdocs/src/lib/runner/runner.ts index 9c51a4aca..811e751be 100644 --- a/packages/plugin-jsdocs/src/lib/runner/runner.ts +++ b/packages/plugin-jsdocs/src/lib/runner/runner.ts @@ -1,11 +1,11 @@ import type { AuditOutputs, RunnerFunction } from '@code-pushup/models'; -import type { JsDocsPluginConfig } from '../config.js'; +import type { JsDocsPluginTransformedConfig } from '../config.js'; import { processJsDocs } from './doc-processor.js'; import type { CoverageType, DocumentationCoverageReport } from './models.js'; import { coverageTypeToAuditSlug } from './utils.js'; export function createRunnerFunction( - config: JsDocsPluginConfig, + config: JsDocsPluginTransformedConfig, ): RunnerFunction { return (): AuditOutputs => { const coverageResult = processJsDocs(config); @@ -21,7 +21,7 @@ export function createRunnerFunction( */ export function trasformCoverageReportToAuditOutputs( coverageResult: DocumentationCoverageReport, - options: Pick, + options: Pick, ): AuditOutputs { return Object.entries(coverageResult) .filter(([type]) => { diff --git a/packages/plugin-jsdocs/src/lib/runner/utils.ts b/packages/plugin-jsdocs/src/lib/runner/utils.ts index d75a20752..7729ab407 100644 --- a/packages/plugin-jsdocs/src/lib/runner/utils.ts +++ b/packages/plugin-jsdocs/src/lib/runner/utils.ts @@ -1,9 +1,9 @@ import { SyntaxKind } from 'ts-morph'; -import { - type CoverageType, - type DocumentationCoverageReport, - type DocumentationReport, - SYNTAX_COVERAGE_MAP, +import { SYNTAX_COVERAGE_MAP } from './constants.js'; +import type { + CoverageType, + DocumentationCoverageReport, + DocumentationReport, } from './models.js'; /** diff --git a/packages/plugin-jsdocs/src/lib/utils.ts b/packages/plugin-jsdocs/src/lib/utils.ts index 5803c8194..ce99d9c86 100644 --- a/packages/plugin-jsdocs/src/lib/utils.ts +++ b/packages/plugin-jsdocs/src/lib/utils.ts @@ -1,5 +1,5 @@ import type { Audit, Group } from '@code-pushup/models'; -import type { JsDocsPluginConfig } from './config.js'; +import type { JsDocsPluginTransformedConfig } from './config.js'; import { AUDITS_MAP } from './constants.js'; /** @@ -10,7 +10,7 @@ import { AUDITS_MAP } from './constants.js'; * @returns The audits. */ export function filterAuditsByPluginConfig( - config: Pick, + config: Pick, ): Audit[] { const { onlyAudits, skipAudits } = config; @@ -38,7 +38,7 @@ export function filterAuditsByPluginConfig( */ export function filterGroupsByOnlyAudits( groups: Group[], - options: Pick, + options: Pick, ): Group[] { const audits = filterAuditsByPluginConfig(options); return groups diff --git a/tsconfig.base.json b/tsconfig.base.json index 66277fc44..0bbce1646 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -28,12 +28,12 @@ "@code-pushup/js-packages-plugin": [ "packages/plugin-js-packages/src/index.ts" ], + "@code-pushup/jsdocs-plugin": ["packages/plugin-jsdocs/src/index.ts"], "@code-pushup/lighthouse-plugin": [ "packages/plugin-lighthouse/src/index.ts" ], "@code-pushup/models": ["packages/models/src/index.ts"], "@code-pushup/nx-plugin": ["packages/nx-plugin/src/index.ts"], - "@code-pushup/plugin-jsdocs": ["packages/plugin-jsdocs/src/index.ts"], "@code-pushup/test-nx-utils": ["testing/test-nx-utils/src/index.ts"], "@code-pushup/test-setup": ["testing/test-setup/src/index.ts"], "@code-pushup/test-utils": ["testing/test-utils/src/index.ts"], From cc808beb2c0d77631cc4404b9192123aec7e9c6b Mon Sep 17 00:00:00 2001 From: Alejandro Date: Fri, 17 Jan 2025 02:18:07 +0100 Subject: [PATCH 65/66] chore(plugin-jsdocs): rename e2e folder --- .../eslint.config.js | 0 .../mocks/fixtures/angular/code-pushup.config.ts | 0 .../mocks/fixtures/angular/src/app.component.css | 0 .../mocks/fixtures/angular/src/app.component.html | 0 .../mocks/fixtures/angular/src/app.component.spec.ts | 0 .../mocks/fixtures/angular/src/app.component.ts | 0 .../mocks/fixtures/angular/src/map-event.function.ts | 0 .../mocks/fixtures/react/code-pushup.config.ts | 0 .../mocks/fixtures/react/component.js | 0 e2e/{plugin-doc-coverage-e2e => plugin-jsdocs-e2e}/project.json | 0 .../tests/collect.e2e.test.ts | 0 e2e/{plugin-doc-coverage-e2e => plugin-jsdocs-e2e}/tsconfig.json | 0 .../tsconfig.test.json | 0 .../vite.config.e2e.ts | 0 14 files changed, 0 insertions(+), 0 deletions(-) rename e2e/{plugin-doc-coverage-e2e => plugin-jsdocs-e2e}/eslint.config.js (100%) rename e2e/{plugin-doc-coverage-e2e => plugin-jsdocs-e2e}/mocks/fixtures/angular/code-pushup.config.ts (100%) rename e2e/{plugin-doc-coverage-e2e => plugin-jsdocs-e2e}/mocks/fixtures/angular/src/app.component.css (100%) rename e2e/{plugin-doc-coverage-e2e => plugin-jsdocs-e2e}/mocks/fixtures/angular/src/app.component.html (100%) rename e2e/{plugin-doc-coverage-e2e => plugin-jsdocs-e2e}/mocks/fixtures/angular/src/app.component.spec.ts (100%) rename e2e/{plugin-doc-coverage-e2e => plugin-jsdocs-e2e}/mocks/fixtures/angular/src/app.component.ts (100%) rename e2e/{plugin-doc-coverage-e2e => plugin-jsdocs-e2e}/mocks/fixtures/angular/src/map-event.function.ts (100%) rename e2e/{plugin-doc-coverage-e2e => plugin-jsdocs-e2e}/mocks/fixtures/react/code-pushup.config.ts (100%) rename e2e/{plugin-doc-coverage-e2e => plugin-jsdocs-e2e}/mocks/fixtures/react/component.js (100%) rename e2e/{plugin-doc-coverage-e2e => plugin-jsdocs-e2e}/project.json (100%) rename e2e/{plugin-doc-coverage-e2e => plugin-jsdocs-e2e}/tests/collect.e2e.test.ts (100%) rename e2e/{plugin-doc-coverage-e2e => plugin-jsdocs-e2e}/tsconfig.json (100%) rename e2e/{plugin-doc-coverage-e2e => plugin-jsdocs-e2e}/tsconfig.test.json (100%) rename e2e/{plugin-doc-coverage-e2e => plugin-jsdocs-e2e}/vite.config.e2e.ts (100%) diff --git a/e2e/plugin-doc-coverage-e2e/eslint.config.js b/e2e/plugin-jsdocs-e2e/eslint.config.js similarity index 100% rename from e2e/plugin-doc-coverage-e2e/eslint.config.js rename to e2e/plugin-jsdocs-e2e/eslint.config.js diff --git a/e2e/plugin-doc-coverage-e2e/mocks/fixtures/angular/code-pushup.config.ts b/e2e/plugin-jsdocs-e2e/mocks/fixtures/angular/code-pushup.config.ts similarity index 100% rename from e2e/plugin-doc-coverage-e2e/mocks/fixtures/angular/code-pushup.config.ts rename to e2e/plugin-jsdocs-e2e/mocks/fixtures/angular/code-pushup.config.ts diff --git a/e2e/plugin-doc-coverage-e2e/mocks/fixtures/angular/src/app.component.css b/e2e/plugin-jsdocs-e2e/mocks/fixtures/angular/src/app.component.css similarity index 100% rename from e2e/plugin-doc-coverage-e2e/mocks/fixtures/angular/src/app.component.css rename to e2e/plugin-jsdocs-e2e/mocks/fixtures/angular/src/app.component.css diff --git a/e2e/plugin-doc-coverage-e2e/mocks/fixtures/angular/src/app.component.html b/e2e/plugin-jsdocs-e2e/mocks/fixtures/angular/src/app.component.html similarity index 100% rename from e2e/plugin-doc-coverage-e2e/mocks/fixtures/angular/src/app.component.html rename to e2e/plugin-jsdocs-e2e/mocks/fixtures/angular/src/app.component.html diff --git a/e2e/plugin-doc-coverage-e2e/mocks/fixtures/angular/src/app.component.spec.ts b/e2e/plugin-jsdocs-e2e/mocks/fixtures/angular/src/app.component.spec.ts similarity index 100% rename from e2e/plugin-doc-coverage-e2e/mocks/fixtures/angular/src/app.component.spec.ts rename to e2e/plugin-jsdocs-e2e/mocks/fixtures/angular/src/app.component.spec.ts diff --git a/e2e/plugin-doc-coverage-e2e/mocks/fixtures/angular/src/app.component.ts b/e2e/plugin-jsdocs-e2e/mocks/fixtures/angular/src/app.component.ts similarity index 100% rename from e2e/plugin-doc-coverage-e2e/mocks/fixtures/angular/src/app.component.ts rename to e2e/plugin-jsdocs-e2e/mocks/fixtures/angular/src/app.component.ts diff --git a/e2e/plugin-doc-coverage-e2e/mocks/fixtures/angular/src/map-event.function.ts b/e2e/plugin-jsdocs-e2e/mocks/fixtures/angular/src/map-event.function.ts similarity index 100% rename from e2e/plugin-doc-coverage-e2e/mocks/fixtures/angular/src/map-event.function.ts rename to e2e/plugin-jsdocs-e2e/mocks/fixtures/angular/src/map-event.function.ts diff --git a/e2e/plugin-doc-coverage-e2e/mocks/fixtures/react/code-pushup.config.ts b/e2e/plugin-jsdocs-e2e/mocks/fixtures/react/code-pushup.config.ts similarity index 100% rename from e2e/plugin-doc-coverage-e2e/mocks/fixtures/react/code-pushup.config.ts rename to e2e/plugin-jsdocs-e2e/mocks/fixtures/react/code-pushup.config.ts diff --git a/e2e/plugin-doc-coverage-e2e/mocks/fixtures/react/component.js b/e2e/plugin-jsdocs-e2e/mocks/fixtures/react/component.js similarity index 100% rename from e2e/plugin-doc-coverage-e2e/mocks/fixtures/react/component.js rename to e2e/plugin-jsdocs-e2e/mocks/fixtures/react/component.js diff --git a/e2e/plugin-doc-coverage-e2e/project.json b/e2e/plugin-jsdocs-e2e/project.json similarity index 100% rename from e2e/plugin-doc-coverage-e2e/project.json rename to e2e/plugin-jsdocs-e2e/project.json diff --git a/e2e/plugin-doc-coverage-e2e/tests/collect.e2e.test.ts b/e2e/plugin-jsdocs-e2e/tests/collect.e2e.test.ts similarity index 100% rename from e2e/plugin-doc-coverage-e2e/tests/collect.e2e.test.ts rename to e2e/plugin-jsdocs-e2e/tests/collect.e2e.test.ts diff --git a/e2e/plugin-doc-coverage-e2e/tsconfig.json b/e2e/plugin-jsdocs-e2e/tsconfig.json similarity index 100% rename from e2e/plugin-doc-coverage-e2e/tsconfig.json rename to e2e/plugin-jsdocs-e2e/tsconfig.json diff --git a/e2e/plugin-doc-coverage-e2e/tsconfig.test.json b/e2e/plugin-jsdocs-e2e/tsconfig.test.json similarity index 100% rename from e2e/plugin-doc-coverage-e2e/tsconfig.test.json rename to e2e/plugin-jsdocs-e2e/tsconfig.test.json diff --git a/e2e/plugin-doc-coverage-e2e/vite.config.e2e.ts b/e2e/plugin-jsdocs-e2e/vite.config.e2e.ts similarity index 100% rename from e2e/plugin-doc-coverage-e2e/vite.config.e2e.ts rename to e2e/plugin-jsdocs-e2e/vite.config.e2e.ts From a383080ff8a8d9b2c01de0ee105f39e017ba08c4 Mon Sep 17 00:00:00 2001 From: Alejandro Date: Fri, 17 Jan 2025 02:30:58 +0100 Subject: [PATCH 66/66] fix(plugin-jsdocs-e2e): fix e2e snapshopt --- .../tests/__snapshots__/report.json | 197 ++++++++++++++++++ .../tests/__snapshots__/report.txt | 31 +++ 2 files changed, 228 insertions(+) create mode 100644 e2e/plugin-jsdocs-e2e/tests/__snapshots__/report.json create mode 100644 e2e/plugin-jsdocs-e2e/tests/__snapshots__/report.txt diff --git a/e2e/plugin-jsdocs-e2e/tests/__snapshots__/report.json b/e2e/plugin-jsdocs-e2e/tests/__snapshots__/report.json new file mode 100644 index 000000000..08d4dc630 --- /dev/null +++ b/e2e/plugin-jsdocs-e2e/tests/__snapshots__/report.json @@ -0,0 +1,197 @@ +{ + "packageName": "@code-pushup/core", + "plugins": [ + { + "title": "JSDoc coverage", + "slug": "jsdocs", + "icon": "folder-docs", + "description": "Official Code PushUp JSDoc coverage plugin.", + "docsUrl": "https://www.npmjs.com/package/@code-pushup/jsdocs-plugin/", + "groups": [ + { + "slug": "documentation-coverage", + "refs": [ + { + "slug": "classes-coverage", + "weight": 2 + }, + { + "slug": "methods-coverage", + "weight": 2 + }, + { + "slug": "functions-coverage", + "weight": 2 + }, + { + "slug": "interfaces-coverage", + "weight": 1 + }, + { + "slug": "variables-coverage", + "weight": 1 + }, + { + "slug": "properties-coverage", + "weight": 1 + }, + { + "slug": "types-coverage", + "weight": 1 + }, + { + "slug": "enums-coverage", + "weight": 1 + } + ], + "title": "Documentation coverage", + "description": "Documentation coverage" + } + ], + "audits": [ + { + "slug": "enums-coverage", + "displayValue": "0 undocumented enums", + "value": 0, + "score": 1, + "details": { + "issues": [] + }, + "title": "Enums coverage", + "description": "Documentation coverage of enums" + }, + { + "slug": "interfaces-coverage", + "displayValue": "0 undocumented interfaces", + "value": 0, + "score": 1, + "details": { + "issues": [] + }, + "title": "Interfaces coverage", + "description": "Documentation coverage of interfaces" + }, + { + "slug": "types-coverage", + "displayValue": "0 undocumented types", + "value": 0, + "score": 1, + "details": { + "issues": [] + }, + "title": "Types coverage", + "description": "Documentation coverage of types" + }, + { + "slug": "functions-coverage", + "displayValue": "2 undocumented functions", + "value": 2, + "score": 0.3333, + "details": { + "issues": [ + { + "message": "Missing functions documentation for notRealisticFunction", + "severity": "warning", + "source": { + "file": "tmp/e2e/plugin-jsdocs-e2e/__test__/angular/src/app.component.spec.ts", + "position": { + "startLine": 1 + } + } + }, + { + "message": "Missing functions documentation for mapEventToCustomEvent", + "severity": "warning", + "source": { + "file": "tmp/e2e/plugin-jsdocs-e2e/__test__/angular/src/map-event.function.ts", + "position": { + "startLine": 3 + } + } + } + ] + }, + "title": "Functions coverage", + "description": "Documentation coverage of functions" + }, + { + "slug": "variables-coverage", + "displayValue": "1 undocumented variables", + "value": 1, + "score": 0, + "details": { + "issues": [ + { + "message": "Missing variables documentation for someVariable", + "severity": "warning", + "source": { + "file": "tmp/e2e/plugin-jsdocs-e2e/__test__/angular/src/map-event.function.ts", + "position": { + "startLine": 1 + } + } + } + ] + }, + "title": "Variables coverage", + "description": "Documentation coverage of variables" + }, + { + "slug": "classes-coverage", + "displayValue": "0 undocumented classes", + "value": 0, + "score": 1, + "details": { + "issues": [] + }, + "title": "Classes coverage", + "description": "Documentation coverage of classes" + }, + { + "slug": "methods-coverage", + "displayValue": "1 undocumented methods", + "value": 1, + "score": 0.5, + "details": { + "issues": [ + { + "message": "Missing methods documentation for sendEvent", + "severity": "warning", + "source": { + "file": "tmp/e2e/plugin-jsdocs-e2e/__test__/angular/src/app.component.ts", + "position": { + "startLine": 15 + } + } + } + ] + }, + "title": "Methods coverage", + "description": "Documentation coverage of methods" + }, + { + "slug": "properties-coverage", + "displayValue": "1 undocumented properties", + "value": 1, + "score": 0, + "details": { + "issues": [ + { + "message": "Missing properties documentation for title", + "severity": "warning", + "source": { + "file": "tmp/e2e/plugin-jsdocs-e2e/__test__/angular/src/app.component.ts", + "position": { + "startLine": 5 + } + } + } + ] + }, + "title": "Properties coverage", + "description": "Documentation coverage of properties" + } + ] + } + ] +} \ No newline at end of file diff --git a/e2e/plugin-jsdocs-e2e/tests/__snapshots__/report.txt b/e2e/plugin-jsdocs-e2e/tests/__snapshots__/report.txt new file mode 100644 index 000000000..a44e639b3 --- /dev/null +++ b/e2e/plugin-jsdocs-e2e/tests/__snapshots__/report.txt @@ -0,0 +1,31 @@ +Code PushUp CLI +[ info ] Run collect... +Code PushUp Report - @code-pushup/core@0.57.0 + + +JSDoc coverage audits + +โ— Properties coverage 1 undocumented + properties +โ— Variables coverage 1 undocumented + variables +โ— Functions coverage 2 undocumented + functions +โ— Methods coverage 1 undocumented + methods +โ— ... 4 audits with perfect scores omitted for brevity ... + +Made with โค by code-pushup.dev + +[ success ] Collecting report successful! +[ info ] ๐Ÿ’ก Configure categories to see the scores in an overview table. See: https://github.com/code-pushup/cli/blob/main/packages/cli/README.md +โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ +โ”‚ โ”‚ +โ”‚ ๐Ÿ’ก Visualize your reports โ”‚ +โ”‚ โ”‚ +โ”‚ โฏ npx code-pushup upload - Run upload to upload the created report to the server โ”‚ +โ”‚ https://github.com/code-pushup/cli/tree/main/packages/cli#upload-command โ”‚ +โ”‚ โฏ npx code-pushup autorun - Run collect & upload โ”‚ +โ”‚ https://github.com/code-pushup/cli/tree/main/packages/cli#autorun-command โ”‚ +โ”‚ โ”‚ +โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ