[
  {
    "path": ".editorconfig",
    "content": "[*.{js,jsx,ts,tsx,vue}]\nindent_style = space\nindent_size = 4\nend_of_line = lf\ntrim_trailing_whitespace = true\ninsert_final_newline = true\nmax_line_length = 100\n"
  },
  {
    "path": ".eslintrc",
    "content": "{\n  \"root\": true,\n  \"parser\": \"@typescript-eslint/parser\",\n  \"plugins\": [\n    \"@typescript-eslint\"\n  ],\n  \"ignorePatterns\": [\"dist/**/*.js\", \"bin/**/*.js\"],\n  \"extends\": [\n    \"eslint:recommended\",\n    \"plugin:@typescript-eslint/eslint-recommended\",\n    \"plugin:@typescript-eslint/recommended\"\n  ],\n  \"rules\": {\n    \"semi\": [\"error\", \"always\"],\n    \"max-len\": [\"error\", 120]\n  }\n}\n"
  },
  {
    "path": ".github/workflows/node.js.yml",
    "content": "name: Push (lint + unit tests)\n\non: [push]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n\n    strategy:\n      matrix:\n        node-version: [16.x]\n\n    steps:\n      - uses: actions/checkout@v3\n      - name: Use Node.js ${{ matrix.node-version }}\n        uses: actions/setup-node@v3\n        with:\n          node-version: ${{ matrix.node-version }}\n      - name: npm install, build, and test\n        run: |\n          npm install\n          npm run build\n          npm run lint\n          npm run test\n        env:\n          CI: true"
  },
  {
    "path": ".gitignore",
    "content": "node_modules\n\n# Editor directories and files\n.idea\n.vscode\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": ".nvmrc",
    "content": "18.6\n"
  },
  {
    "path": "LICENSE.txt",
    "content": "MIT License\n\nCopyright (c) 2021-2022 Martina Scharrer\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE."
  },
  {
    "path": "README.md",
    "content": "# vuensight 👀\nVisualize Vue.js **component relationships** and **communication channels**, i.e. props, events and slots. This tool operates on the \ncommand line and is made for developers. The aim of vuensight is to provide visual insight into the components of a \nVue.js project and to support developers before and during refactoring, e.g. by visually analyzing which prop is used \nin which parent component or by highlighting unused components or channels.\n\nAn example visualization of vuensight itself:\n![demo image of vuensight](docs/vuensight-demo.png)\n\nThis tool is built on top of the two awesome packages:\n- [dependency-cruiser](https://github.com/sverweij/dependency-cruiser) for building the dependency tree \n- [vue-docgen-api](https://github.com/vue-styleguidist/vue-styleguidist/tree/dev/packages/vue-docgen-api) for parsing the Vue files\n\n## Getting started 🚀\n### Install\nFirst, install the cli package either locally in the project you want to visualize:\n```\nnpm i -D @vuensight/cli\n```\n\nOr globally on your machine if you intend to visualize multiple projects:\n```\nnpm i -g @vuensight/cli\n```\n\n### Run\nThen run the tool in your project folder:\n```\nvuensight\n```\n\n#### Options\n- `--dir` or `-d` (optional): Specify the directory that should be parsed relative from your current working directory, default is `src`\n- `--port` or `-p` (optional): Start the application in a different port, default is 4444\n- `--webpack-config` or `-wpc` (optional): Specify the path to your webpack-config (from your current working directory). This is particularly important if you use aliases.\n- `--ts-config` or `-tsc` (optional): Specify the path to your TypeScript config file (from your current working directory).\n\nAn example usage:\n```\nvuensight --dir resources/js --port 9999 --webpack-config ./webpack-config.json --ts-config ./tsconfig.json\n```\n\n## Licencse\n[MIT](LICENSE.txt)\n\n\n## Development\n### Requirements\n- `npm version >= 7` (the project is a monorepo and uses npm workspaces which require at least npm version 7)  \n\n### Installing dependencies\n- `npm i` (in root directory) to install all dependencies of all packages\n- `npm i <package-name>` to add a global dependency for all packages \n- `npm i <package-name> --workspace @vuensight/<vuensight-package-name>` to add a new dependency to a specific package\n\n### Build packages\n- `npm run build` in root folder (to build all packages at the same time)\n- `npm run build` in each package\n\n#### or use a watcher\n- `npm run build:watch` in every package separately \n\n### Link locally\nFor testing vuensight locally in a Vue project run `npm link` in the cli package and `npm link @vuensight/cli` in the \nproject you want to test it on. Make sure you use a correct node version and ran `npm i` in the root directory \nbeforehand as this links the packages together internally.\n\n### Unit tests\n- `npm run test` in root (to run tests for all packages)\n- `npm run test` in each package\n\n### Publish\n- `npm publish` in each package\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"vuensight\",\n  \"description\": \"`vuensight` is a cli tool for parsing and visualizing Vue.js projects. The ultimate goal is to visualize the components communication e.g. their props, events and slots in an interactive web app. **This project is currently a work in progress!**\",\n  \"scripts\": {\n    \"build\": \"npm run build --workspaces\",\n    \"lint\": \"npm run lint --workspaces\",\n    \"test\": \"npm run test --workspaces\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/martinascharrer/vuensight.git\"\n  },\n  \"author\": \"Martina Scharrer\",\n  \"license\": \"(MIT)\",\n  \"bugs\": {\n    \"url\": \"https://github.com/martinascharrer/vuensight/issues\"\n  },\n  \"homepage\": \"https://github.com/martinascharrer/vuensight#readme\",\n  \"workspaces\": [\n    \"./packages/types\",\n    \"./packages/parser\",\n    \"./packages/app\",\n    \"./packages/cli\"\n  ],\n  \"devDependencies\": {\n    \"@types/node\": \"^17.0.22\",\n    \"@typescript-eslint/eslint-plugin\": \"^5.16.0\",\n    \"@typescript-eslint/parser\": \"^5.16.0\",\n    \"eslint\": \"^8.11.0\",\n    \"jest\": \"^27.5.1\",\n    \"jest-cli\": \"^27.5.1\",\n    \"ts-jest\": \"^27.1.3\",\n    \"typescript\": \"^4.6.2\"\n  }\n}\n"
  },
  {
    "path": "packages/app/.browserslistrc",
    "content": "> 1%\nlast 2 versions\nnot dead\n"
  },
  {
    "path": "packages/app/.editorconfig",
    "content": "[*.{js,jsx,ts,tsx,vue}]\nindent_style = space\nindent_size = 2\nend_of_line = lf\ntrim_trailing_whitespace = true\ninsert_final_newline = true\nmax_line_length = 120\n"
  },
  {
    "path": "packages/app/.eslintrc.js",
    "content": "module.exports = {\n  root: true,\n  env: {\n    node: true,\n  },\n  extends: [\n    'plugin:vue/vue3-essential',\n    '@vue/airbnb',\n    '@vue/typescript/recommended',\n  ],\n  parserOptions: {\n    ecmaVersion: 2020,\n  },\n  rules: {\n    'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',\n    'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',\n    'max-len': ['error', 120],\n    'import/no-unresolved': 'warn',\n    'import/extensions': 'warn',\n  },\n  overrides: [\n    {\n      files: [\n        '**/__tests__/*.{j,t}s?(x)',\n        '**/tests/unit/**/*.spec.{j,t}s?(x)',\n      ],\n      env: {\n        jest: true,\n      },\n    },\n  ],\n};\n"
  },
  {
    "path": "packages/app/.gitignore",
    "content": ".DS_Store\nnode_modules\n\n# local env files\n.env.local\n.env.*.local\n\n# Log files\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\n\n# Editor directories and files\n.idea\n.vscode\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n\n/dist\ntsconfig.tsbuildinfo"
  },
  {
    "path": "packages/app/.npmignore",
    "content": "*\n\n!dist/app/**/*\ndist/app/**/*.js.map\n!dist/server/*.js\n!dist/server/*.d.ts\n!package.json\n!readme.md"
  },
  {
    "path": "packages/app/LICENSE.txt",
    "content": "MIT License\n\nCopyright (c) 2021-2022 Martina Scharrer\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE."
  },
  {
    "path": "packages/app/README.md",
    "content": "# @vuensight/app\n> ⚠️ **General information about usage and setup of `vuensight` can be found [here](https://github.com/martinascharrer/vuensight)**  \n\nThe `app` package visualizes the parsed Vue project as force-directed graph. \nThe visualization is built with [d3.js](https://d3js.org/) and [webCola](https://github.com/tgdwyer/WebCola).\n\n## Project setup\n```\nnpm install\n```\n\n### Compiles and hot-reloads for development\n```\nnpm run serve\n```\n\n### Compiles and minifies for production\n```\nnpm run build\n```\n\n### Run your unit tests\n```\nnpm run test:unit\n```\n\n### Lints and fixes files\n```\nnpm run lint\n```\n\n### Customize configuration\nSee [Configuration Reference](https://cli.vuejs.org/config/).\n\n## Licencse\n[MIT](LICENSE.txt)"
  },
  {
    "path": "packages/app/babel.config.js",
    "content": "module.exports = {\n  presets: [\n    '@vue/cli-plugin-babel/preset',\n  ],\n};\n"
  },
  {
    "path": "packages/app/jest.config.js",
    "content": "module.exports = {\n  preset: '@vue/cli-plugin-unit-jest/presets/typescript-and-babel',\n  transform: {\n    '^.+\\\\.vue$': '@vue/vue3-jest',\n  },\n  testMatch: ['**/__tests__/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)'],\n  moduleFileExtensions: [\n    'js',\n    'ts',\n  ],\n};\n"
  },
  {
    "path": "packages/app/package.json",
    "content": "{\n  \"name\": \"@vuensight/app\",\n  \"version\": \"0.3.3\",\n  \"main\": \"dist/server/index.js\",\n  \"description\": \"The front-end of @vuensight that visualizes Vue.js projects.\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/martinascharrer/vuensight.git\"\n  },\n  \"author\": \"Martina Scharrer\",\n  \"license\": \"(MIT)\",\n  \"bugs\": {\n    \"url\": \"https://github.com/martinascharrer/vuensight/issues\"\n  },\n  \"homepage\": \"https://github.com/martinascharrer/vuensight#readme\",\n  \"scripts\": {\n    \"serve\": \"vue-cli-service serve\",\n    \"build\": \"rm -rf dist/ && echo 'Building app...' && vue-cli-service build --dest dist/app && echo 'Building server...' && cd server && tsc -p tsconfig.pkg.json\",\n    \"build:watch\": \"vue-cli-service build --watch\",\n    \"test\": \"vue-cli-service test:unit\",\n    \"lint\": \"vue-cli-service lint\",\n    \"prepublish\": \"npm run build\"\n  },\n  \"dependencies\": {\n    \"@vuensight/parser\": \"^0.1.4\",\n    \"@vuensight/types\": \"^0.1.0\",\n    \"@vueuse/core\": \"^8.3.1\",\n    \"connect-history-api-fallback\": \"^1.6.0\",\n    \"core-js\": \"^3.21.1\",\n    \"d3\": \"^5.16.0\",\n    \"express\": \"^4.17.3\",\n    \"vue\": \"^3.2.31\",\n    \"vue-router\": \"^4.0.14\",\n    \"webcola\": \"^3.4.0\"\n  },\n  \"devDependencies\": {\n    \"@types/connect-history-api-fallback\": \"^1.3.5\",\n    \"@types/d3\": \"^7.1.0\",\n    \"@types/express\": \"^4.17.13\",\n    \"@vue/cli-plugin-babel\": \"^5.0.4\",\n    \"@vue/cli-plugin-eslint\": \"^5.0.4\",\n    \"@vue/cli-plugin-router\": \"^5.0.4\",\n    \"@vue/cli-plugin-typescript\": \"^5.0.4\",\n    \"@vue/cli-plugin-unit-jest\": \"^5.0.4\",\n    \"@vue/cli-service\": \"^5.0.4\",\n    \"@vue/compiler-sfc\": \"^3.2.31\",\n    \"@vue/eslint-config-airbnb\": \"^6.0.0\",\n    \"@vue/eslint-config-typescript\": \"^10.0.0\",\n    \"@vue/test-utils\": \"^2.0.0-rc.17\",\n    \"@vue/vue3-jest\": \"^27.0.0\",\n    \"eslint-plugin-import\": \"^2.25.4\",\n    \"eslint-plugin-vue\": \"^8.5.0\",\n    \"sass\": \"^1.49.9\",\n    \"sass-loader\": \"^12.6.0\"\n  }\n}\n"
  },
  {
    "path": "packages/app/public/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n    <meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n    <link rel=\"icon\" href=\"<%= BASE_URL %>favicon.ico\">\n    <title><%= htmlWebpackPlugin.options.title %></title>\n  </head>\n  <body>\n    <noscript>\n      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>\n    </noscript>\n    <div id=\"app\"></div>\n    <!-- built files will be auto injected -->\n  </body>\n</html>\n"
  },
  {
    "path": "packages/app/server/index.ts",
    "content": "import history from 'connect-history-api-fallback';\nimport express from 'express';\nimport { join } from 'path';\n\nimport { parse } from '@vuensight/parser';\n\nexport default async (directory: string, port?: number, webpackConfigPath?: string, tsConfigPath?: string) => {\n  const app = express();\n\n  const localPort = port || 4444;\n\n  app.get('/parse-result', async (request, response) => {\n    const parseResult = await parse(directory, 'vue', webpackConfigPath, tsConfigPath);\n    response.json(parseResult);\n  });\n\n  app.use(history());\n\n  app.use(express.static(join(__dirname, '../app')));\n\n  app.listen((+localPort), () => {\n    console.log(`👀 vuensight: http://localhost:${localPort}`);\n  });\n};\n"
  },
  {
    "path": "packages/app/server/tsconfig.pkg.json",
    "content": "{\n  \"extends\": \"../../../tsconfig.build.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../dist/server\"\n  },\n  \"include\": [\n    \"./**/*.ts\"\n  ]\n}\n"
  },
  {
    "path": "packages/app/src/App.vue",
    "content": "<template>\n  <router-view/>\n</template>\n\n<style lang=\"scss\">\n@import url('https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,400;0,700;1,400&display=swap');\n\n@import 'assets/css/border-radius.css';\n@import 'assets/css/box-shadow.css';\n@import 'assets/css/color.css';\n@import 'assets/css/font.css';\n@import 'assets/css/icon.css';\n@import 'assets/css/spacing.css';\n\n*, *::before, *::after {\n    box-sizing: border-box;\n}\n\n* {\n    margin: 0;\n}\n\nhtml, body {\n    height: 100%;\n}\n\ninput, button, textarea, select {\n    font: inherit;\n    border: none;\n    background: transparent;\n    text-align: inherit;\n    padding: 0;\n\n    &:hover {\n        border: none;\n        outline: none;\n    }\n}\n\nbutton {\n    cursor: pointer;\n}\n\n.input {\n    display: flex;\n    align-items: center;\n    gap: var(--spacing--s);\n    background: white;\n    border: none;\n    border-radius: var(--border-radius--s);\n    box-shadow: var(--box-shadow--s);\n    padding: var(--spacing--xs) var(--spacing--m);\n    min-width: var(--spacing--2xl);\n    height: var(--spacing--3xl);\n\n    &:hover {\n        outline: 2px solid var(--yellow-30);\n    }\n}\n\np, h1, h2, h3, h4, h5, h6 {\n    overflow-wrap: break-word;\n}\n\n#root, #__next {\n    isolation: isolate;\n}\n\nbody {\n    font-size: var(--font-size--m);\n    font-family: NotoSans, Arial, Helvetica, sans-serif;\n    -webkit-font-smoothing: antialiased;\n    -moz-osx-font-smoothing: grayscale;\n    color: var(--navy-90);\n    line-height: 1.5;\n}\n</style>\n"
  },
  {
    "path": "packages/app/src/assets/css/border-radius.css",
    "content": ":root {\n    --border-radius--xs: 2px;\n    --border-radius--s: 5px;\n    --border-radius--m: 10px;\n    --border-radius--l: 15px;\n    --border-radius--xl: 20px;\n    --border-radius--2xl: 25px;\n}"
  },
  {
    "path": "packages/app/src/assets/css/box-shadow.css",
    "content": ":root {\n    --box-shadow--xs: 0px 0px 2px rgba(0,0,0,0.2);\n    --box-shadow--s: 0px 0px 4px rgba(0,0,0,0.23);\n    --box-shadow--m: 0px 0px 8px rgba(0,0,0,0.3);\n}"
  },
  {
    "path": "packages/app/src/assets/css/color.css",
    "content": ":root {\n    --mint-grey: #CBE5DE;\n    --mint-10: #CDF6EB;\n    --mint-30: #A0E2CF;\n    --mint-50: #61c799;\n    --mint-70: #369D6F;\n\n    --red-grey: #E9D0C9;\n    --red-10: #F6DCD4;\n    --red-30: #FFB7A1;\n    --red-50: #F57A5F;\n    --red-70: #C54C31;\n\n    --purple-grey: #c7b1c9;\n    --purple-30: #E9BCEF;\n    --purple-50: #C37ECD;\n    --purple-70: #854D8D;\n\n    --yellow-30: #F6D697;\n    --yellow-50: #EDAB2C;\n\n    --grey-5: #FaFaFa;\n    --grey-10: #F2F2F2;\n    --grey-15: #d7d7d7;\n    --grey-20: #CECECE;\n    --grey-30: #9a9a9a;\n    --grey-50: #7c7c7c;\n\n    --navy-50: #7A8497;\n    --navy-90: #212c41;\n}"
  },
  {
    "path": "packages/app/src/assets/css/font.css",
    "content": ":root {\n    font-size: 16px;\n\n    --font-size--3xs: 0.35em;\n    --font-size--2xs: 0.5em;\n    --font-size--xs: 0.75em;\n    --font-size--s: 0.875em;\n    --font-size--m: 1em;\n    --font-size--l: 1.125em;\n    --font-size--xl: 1.25em;\n    --font-size--2xl: 1.5em;\n    --font-size--3xl: 2em;\n    --font-size--4xl: 2.75em;\n\n}\n"
  },
  {
    "path": "packages/app/src/assets/css/icon.css",
    "content": ":root {\n    --icon--xs: 0.75rem;\n    --icon--s: 1rem;\n    --icon--m: 1.25rem;\n    --icon--l: 1.5rem;\n    --icon--xl: 1.75rem;\n    --icon--2xl: 2rem;\n}"
  },
  {
    "path": "packages/app/src/assets/css/spacing.css",
    "content": ":root {\n    --spacing--2xs: 0.125rem;\n    --spacing--xs: 0.25rem;\n    --spacing--s: 0.5rem;\n    --spacing--m: 0.75rem;\n    --spacing--l: 1rem;\n    --spacing--xl: 1.5rem;\n    --spacing--2xl: 2rem;\n    --spacing--3xl: 2.75rem;\n    --spacing--4xl: 4rem;\n    --spacing--5xl: 6rem;\n}"
  },
  {
    "path": "packages/app/src/components/CardCommunicationChannel.test.ts",
    "content": "// eslint-disable-next-line import/no-extraneous-dependencies\nimport { mount, MountingOptions } from '@vue/test-utils';\nimport CardCommunicationChannel from '@/components/CardCommunicationChannel.vue';\n\ndescribe('CardCommunicationChannel.vue', () => {\n  it('renders the card with correct name and counter', () => {\n    const wrapper = mount(CardCommunicationChannel, {\n      props: {\n        channel: {\n          name: 'foo prop',\n        },\n        dependents: [\n          {\n            name: 'Bar',\n            fullPath: 'foo/bar.vue',\n          },\n          {\n            name: 'Test',\n            fullPath: 'foo/test.vue',\n          },\n        ],\n      },\n    } as MountingOptions<any>); // eslint-disable-line @typescript-eslint/no-explicit-any\n    expect(wrapper.find('[data-qa=\"name\"]').text()).toBe('foo prop');\n    expect(wrapper.find('[data-qa=\"counter\"]').text()).toBe('2');\n  });\n});\n"
  },
  {
    "path": "packages/app/src/components/CardCommunicationChannel.vue",
    "content": "<template>\n    <base-card\n        class=\"cardCommunicationChannel\"\n        :class=\"{\n            [`cardCommunicationChannel--${color}`]: color,\n            'cardCommunicationChannel--selected': isSelected,\n            'cardCommunicationChannel--disabled': dependents.length === 0\n        }\"\n        :disabled=\"dependents.length === 0\"\n    >\n        <template #header>\n            <div class=\"cardCommunicationChannel__header\">\n                <base-check-icon\n                    :color=\"color\"\n                    :is-checked=\"isSelected\"\n                    :is-disabled=\"dependents.length === 0\"\n                />\n                <p data-qa=\"name\">{{ channel.name }}</p>\n                <base-badge data-qa=\"counter\">{{ dependents.length }}</base-badge>\n                <base-badge :color=\"`light-${color}`\" v-if=\"channel.mixin\">mixin</base-badge>\n            </div>\n        </template>\n        <template\n            v-if=\"channel.type || channel.default || channel.required || channel.mixin\"\n            #body\n        >\n            <template v-if=\"channel.type\">\n                type: {{ channel.type.name }}\n            </template>\n            <template v-if=\"channel.default\">\n                <base-delimiter :color=\"color\" /> default: {{ channel.default }}\n            </template>\n            <template v-if=\"channel.required\">\n                <base-delimiter :color=\"color\" /> required: {{ channel.required }}\n            </template>\n            <template v-if=\"channel.mixin\">\n                <base-delimiter :color=\"color\" /> mixin: {{ channel.mixin.name }}\n            </template>\n        </template>\n        <template v-if=\"dependents.length > 0\" #footer>\n            used in:\n            <base-list\n                v-if=\"dependents.length > 0\" :color=\"color\"\n                :items=\"dependents.map(dep => dep.name)\"\n            />\n        </template>\n    </base-card>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent, PropType } from 'vue';\n\nimport BaseBadge from '@/components/base/BaseBadge.vue';\nimport BaseCard from '@/components/base/BaseCard.vue';\nimport BaseCheckIcon from '@/components/base/BaseCheckIcon.vue';\nimport BaseDelimiter from '@/components/base/BaseDelimiter.vue';\nimport BaseList from '@/components/base/BaseList.vue';\n\nimport { Prop, Dependent } from '@vuensight/types';\nimport { Color } from '@/types/color';\n\nexport default defineComponent({\n  name: 'CardCommunicationChannel',\n  props: {\n    channel: {\n      type: Object as PropType<Prop>,\n      required: true,\n    },\n    dependents: {\n      type: Array as PropType<Array<Dependent>>,\n      required: true,\n    },\n    color: {\n      type: String as PropType<Color>,\n      default: 'mint',\n    },\n    isSelected: {\n      type: Boolean,\n      default: false,\n    },\n  },\n  components: {\n    BaseBadge,\n    BaseCard,\n    BaseCheckIcon,\n    BaseDelimiter,\n    BaseList,\n  },\n});\n</script>\n\n<style lang=\"scss\">\n.cardCommunicationChannel {\n    cursor: pointer;\n\n    &--mint.cardCommunicationChannel--selected {\n        outline: 2px solid var(--mint-50);\n    }\n\n    &--red.cardCommunicationChannel--selected {\n        outline: 2px solid var(--red-50);\n    }\n\n    &--purple.cardCommunicationChannel--selected {\n        outline: 2px solid var(--purple-50);\n    }\n\n    &:hover {\n        box-shadow: var(--box-shadow--m);\n    }\n\n    &--disabled {\n        color: var(--grey-30);\n        cursor: default;\n        box-shadow: var(--box-shadow--xs);\n\n        &:hover {\n            box-shadow: var(--box-shadow--xs);\n        }\n    }\n\n    &__header {\n        display: flex;\n        gap: var(--spacing--m);\n        align-items: center;\n    }\n}\n</style>\n"
  },
  {
    "path": "packages/app/src/components/ForceGraph.vue",
    "content": "<template>\n  <svg ref=\"graphRef\" class=\"forceGraph\"></svg>\n</template>\n\n<script>\nimport {\n  defineComponent, ref, onMounted, watch, computed,\n} from 'vue';\nimport * as d3 from 'd3';\nimport * as cola from 'webcola';\n\nimport nodeSizeAttributeType from '@/types/nodeSizeAttributeType';\n\nconst SMALL_CIRCLE_RADIUS = 2;\nconst DISTANCE_BETWEEN_SMALL_CIRCLES = 5;\nconst NODE_SIZE_NO_FILTER = 15;\n\nexport default defineComponent({\n  name: 'ForceGraph',\n  props: {\n    data: {\n      type: Object,\n      required: true,\n    },\n    selectedChannelType: {\n      type: String,\n      default: null,\n    },\n    selectedChannel: {\n      type: Object,\n      default: null,\n    },\n    nodeSizeAttribute: {\n      type: String,\n      default: 'props',\n    },\n    searchString: {\n      type: String,\n      default: '',\n    },\n    width: {\n      type: Number,\n      default: 500,\n    },\n    height: {\n      type: Number,\n      default: 500,\n    },\n  },\n  setup(props, { emit }) {\n    const graphRef = ref(null);\n    const selectedNode = ref('');\n    const transitionTime = null;\n\n    const nodes = props.data.nodes.map((d) => ({ ...d, id: d.fullPath }));\n    const indices = new Map(nodes.map((d) => [d.fullPath, d]));\n    const links = props.data.links.map((d) => Object.assign(Object.create(d), {\n      source: indices.get(d.source),\n      target: indices.get(d.target),\n    }));\n\n    const nodeSizeAttributeScale = computed(() => {\n      if (props.nodeSizeAttribute === nodeSizeAttributeType.NONE) return (d) => d;\n      const attributeCounts = props.nodeSizeAttribute === nodeSizeAttributeType.CHANNELS\n        ? nodes.map((node) => node.props.length + node.events.length + node.slots.length)\n        : nodes.map((node) => node[props.nodeSizeAttribute].length);\n      return d3.scaleLinear(d3.extent(attributeCounts), [8, 30]);\n    });\n\n    const calculateNodeSize = (d) => {\n      if (props.nodeSizeAttribute === nodeSizeAttributeType.NONE) return NODE_SIZE_NO_FILTER;\n\n      const value = props.nodeSizeAttribute === nodeSizeAttributeType.CHANNELS\n        ? d.props.length + d.events.length + d.slots.length : d[props.nodeSizeAttribute].length;\n      return nodeSizeAttributeScale.value(value);\n    };\n\n    const updateNodeSize = () => {\n      d3.selectAll('.node__circle')\n        .transition(transitionTime)\n        .attr('r', (d) => calculateNodeSize(d));\n    };\n\n    const getCirclePositionFactor = (index, nodeSize) => (index / nodeSize)\n          * (SMALL_CIRCLE_RADIUS / 2) * DISTANCE_BETWEEN_SMALL_CIRCLES;\n\n    const updateCommunicationChannelPositions = () => {\n      const nodeSize = d3.local();\n      d3.selectAll('.node--selectedDependent')\n        .each((d, i, currentNodes) => nodeSize.set(currentNodes[i], calculateNodeSize(d)))\n        .selectAll('.node__channel')\n        .transition(transitionTime)\n        .attr('cx', (d, i, currentNodes) => {\n          const channelCircleRadius = nodeSize.get(currentNodes[i]) + 3;\n          return channelCircleRadius * Math.cos(getCirclePositionFactor(i, channelCircleRadius) - Math.PI * 0.5);\n        })\n        .attr('cy', (d, i, currentNodes) => {\n          const channelCircleRadius = nodeSize.get(currentNodes[i]) + 3;\n          return channelCircleRadius * Math.sin(getCirclePositionFactor(i, channelCircleRadius) - Math.PI * 0.5);\n        });\n    };\n\n    const updateArrowTips = () => {\n      const LINK_ARROW_SIZE = 4;\n      d3.selectAll('.arrow')\n        .transition(transitionTime)\n        .attr('refX', (d) => {\n          const targetNode = nodes.find((node) => node.id === d.target.id);\n          return calculateNodeSize(targetNode) + LINK_ARROW_SIZE;\n        });\n    };\n\n    const drawCommunicationChannelCircles = () => {\n      d3.selectAll('.node--selectedDependent')\n        .append('g')\n        .selectAll('.node__channel')\n        .data((data) => {\n          const dependent = selectedNode?.value.dependents.find((dep) => dep.fullPath === data.fullPath);\n          return dependent ? dependent[`used${props.selectedChannelType}`] : [];\n        })\n        .enter()\n        .append('circle')\n        .attr('r', SMALL_CIRCLE_RADIUS)\n        .attr('class', `node__channel node__channel--${props.selectedChannelType.toLowerCase()}`);\n\n      updateCommunicationChannelPositions();\n    };\n\n    const resetChannelSelection = () => {\n      d3.selectAll('.node--usesProps').classed('node--usesProps', false);\n      d3.selectAll('.node--usesEvents').classed('node--usesEvents', false);\n      d3.selectAll('.node--usesSlots').classed('node--usesSlots', false);\n      d3.selectAll('.node__channel--selected').classed('node__channel--selected', false);\n    };\n\n    const removeNodeChannels = () => {\n      d3.selectAll('.node__channel').remove();\n      d3.selectAll('.node__channel--props').remove();\n      d3.selectAll('.node__channel--slots').remove();\n      d3.selectAll('.node__channel--events').remove();\n    };\n\n    const resetNodeSelection = () => {\n      emit('unselected');\n      d3.selectAll('.node--selected').classed('node--selected', false);\n      d3.selectAll('.node--selectedDependent').classed('node--selectedDependent', false);\n      d3.selectAll('.link--selected').classed('link--selected', false);\n      d3.selectAll('.arrow--selected').classed('arrow--selected', false);\n      d3.selectAll('.node--greyedOut').classed('node--greyedOut', false);\n      d3.selectAll('.link--greyedOut').classed('link--greyedOut', false);\n      removeNodeChannels();\n      resetChannelSelection();\n      d3.selectAll('.node').raise();\n    };\n\n    onMounted(() => {\n      const svg = d3.select(graphRef.value)\n        .attr('viewBox', [0, 0, props.width, props.height])\n        .attr('class', 'svg')\n        .on('click', () => {\n          resetNodeSelection();\n        });\n\n      svg.transition().duration(500);\n\n      const g = svg.append('g');\n\n      const layout = cola.d3adaptor(d3)\n        .size([props.width, props.height])\n        .nodes(nodes)\n        .links(links)\n        .avoidOverlaps(true)\n        .symmetricDiffLinkLengths(20)\n        .start(10, 15, 20);\n\n      const LINK_ARROW_SIZE = 4;\n      svg.append('defs').selectAll('.arrow')\n        .data(links)\n        .enter()\n        .append('marker')\n        .attr('class', 'arrow')\n        .attr('id', (d) => `arrow-${d.source.index}-${d.target.index}`)\n        .attr('viewBox', `0 -${LINK_ARROW_SIZE / 2} ${LINK_ARROW_SIZE} ${LINK_ARROW_SIZE}`)\n        .attr('refX', (d) => {\n          const targetNode = nodes.find((node) => node.id === d.target.id);\n          return calculateNodeSize(targetNode) + LINK_ARROW_SIZE;\n        })\n        .attr('refY', 0)\n        .attr('markerWidth', LINK_ARROW_SIZE)\n        .attr('markerHeight', LINK_ARROW_SIZE)\n        .attr('orient', 'auto')\n        .append('path')\n        .attr('d', `M1,-${(LINK_ARROW_SIZE / 2) - 1}\n          L${LINK_ARROW_SIZE - 1},0\n          L1,${(LINK_ARROW_SIZE / 2) - 1}\n          L1,-${(LINK_ARROW_SIZE / 2) - 1} Z`)\n        .style('opacity', '1');\n\n      const link = g.selectAll('line')\n        .data(links)\n        .enter()\n        .append('line')\n        .attr('id', (d) => `link-${d.source.index}-${d.target.index}`)\n        .attr('class', 'link')\n        .attr('marker-end', (d) => `url(#arrow-${d.source.index}-${d.target.index})`);\n\n      const node = g.selectAll('g')\n        .data(nodes)\n        .enter()\n        .append('g')\n        .attr('id', (d) => `node-${d.index}`)\n        .attr('class', 'node')\n        .call(layout.drag)\n      // eslint-disable-next-line func-names\n        .on('click', function (data) {\n          d3.event.stopPropagation();\n          resetNodeSelection();\n\n          selectedNode.value = data;\n\n          // highlight selected node + dependents\n          const dependents = links.filter((l) => l.target.index === data.index);\n          dependents.forEach((dependent) => {\n            d3.select(`#link-${dependent.source.index}-${dependent.target.index}`)\n              .classed('link--selected', true)\n              .raise();\n            d3.select(`#arrow-${dependent.source.index}-${dependent.target.index}`)\n              .classed('arrow--selected', true)\n              .raise();\n            d3.select(`#node-${dependent.source.index}`).classed('node--selectedDependent', true);\n          });\n          d3.selectAll('.node--selectedDependent').raise();\n          d3.select(this).classed('node--selected', true).raise();\n          drawCommunicationChannelCircles();\n\n          // grey out everything else\n          d3.selectAll('.node:not(.node--selected, .node--selectedDependent)').classed('node--greyedOut', true);\n          d3.selectAll('.link:not(.link--selected').classed('link--greyedOut', true);\n\n          emit('selected', data);\n        });\n\n      node.append('circle')\n        .attr('class', 'node__circle');\n\n      updateNodeSize();\n\n      const label = node.append('g')\n        .attr('dy', 2)\n        .attr('dx', 0)\n        .attr('class', 'node__label');\n\n      label.append('text')\n        .attr('dy', 2)\n        .attr('dx', 0)\n        .attr('class', 'node__labelText')\n        .text((d) => d.name);\n\n      label.insert('rect', '.node__labelText')\n        .attr('fill', 'white')\n      // eslint-disable-next-line func-names\n        .attr('width', function () {\n          const bbox = d3.select(this.parentNode).select('.node__labelText').node().getBBox();\n          return bbox.width + 2;\n        })\n      // eslint-disable-next-line func-names\n        .attr('height', function () {\n          const bbox = d3.select(this.parentNode).select('.node__labelText').node().getBBox();\n          return bbox.height;\n        })\n      // eslint-disable-next-line func-names\n        .attr('x', function () {\n          const bbox = d3.select(this.parentNode).select('.node__labelText').node().getBBox();\n          return bbox.x - 1;\n        })\n      // eslint-disable-next-line func-names\n        .attr('y', function () {\n          const bbox = d3.select(this.parentNode).select('.node__labelText').node().getBBox();\n          return bbox.y;\n        })\n        .attr('class', 'node__labelBackground');\n\n      layout.on('tick', () => {\n        link\n          .attr('x1', (d) => d.source.x)\n          .attr('y1', (d) => d.source.y)\n          .attr('x2', (d) => d.target.x)\n          .attr('y2', (d) => d.target.y);\n\n        node.attr('transform', (d) => `translate(${d.x},${d.y})`);\n      });\n\n      function zoomed() {\n        g.attr('transform', d3.event.transform);\n      }\n\n      const zoom = d3.zoom()\n        .scaleExtent([0.4, 8])\n        .on('zoom', zoomed);\n\n      svg.call(zoom);\n    });\n\n    const highlightChannelUsage = (channel) => {\n      d3.selectAll('.node--selectedDependent')\n        .classed(`node--uses${props.selectedChannelType}`, (d) => {\n          const dependent = selectedNode.value.dependents.find((dep) => dep.fullPath === d.fullPath);\n          const channels = selectedNode.value[props.selectedChannelType.toLowerCase()];\n          return dependent[`used${props.selectedChannelType}`].some((prop) => channels[prop].name === channel.name);\n        });\n\n      d3.selectAll(`.node--selectedDependent .node__channel--${props.selectedChannelType.toLowerCase()}`)\n        .classed(\n          'node__channel--selected',\n          (d) => selectedNode.value[props.selectedChannelType.toLowerCase()][d].name === channel.name,\n        );\n    };\n\n    watch(() => props.nodeSizeAttribute, () => {\n      updateNodeSize();\n      updateCommunicationChannelPositions();\n      updateArrowTips();\n    });\n\n    watch(() => props.searchString, () => {\n      d3.selectAll('.node')\n        .classed('node--searchMatches', (d) => props.searchString.length > 0\n          && d.name.toLowerCase().includes(props.searchString.toLowerCase()));\n    });\n\n    watch(() => props.selectedChannel, () => {\n      resetChannelSelection();\n      if (props.selectedChannel) highlightChannelUsage(props.selectedChannel);\n    });\n\n    watch(() => props.selectedChannelType, () => {\n      removeNodeChannels();\n      resetChannelSelection();\n      drawCommunicationChannelCircles();\n    });\n\n    return {\n      graphRef,\n    };\n  },\n});\n</script>\n\n<style lang=\"scss\">\n.svg {\n    height: 100%;\n    width: 100%;\n    cursor: grab;\n}\n\n.node {\n    text-anchor: middle;\n\n    &__circle {\n        fill: white;\n        stroke: var(--grey-20);\n    }\n\n    &__label {\n        cursor: pointer;\n    }\n\n    &__labelText {\n        font-size: var(--font-size--3xs);\n        fill: var(--navy-90);\n    }\n\n    &__labelBackground {\n        fill: white;\n        rx: var(--border-radius--xs);\n    }\n\n    &__channel {\n\n        &--props {\n            fill: var(--mint-grey);\n            stroke: var(--mint-50);\n        }\n\n        &--events {\n            fill: var(--red-grey);\n            stroke: var(--red-50);\n        }\n\n        &--slots {\n            fill: var(--purple-grey);\n            stroke: var(--purple-50);\n        }\n    }\n\n    &__circle,\n    &__channel {\n        stroke-width: 1px;\n        cursor: pointer;\n    }\n\n    &__circle,\n    &__channel,\n    &__labelText,\n    &__labelBackground {\n        transition: fill 200ms;\n    }\n\n    &--searchMatches {\n        .node__labelBackground {\n            fill: var(--yellow-30);\n        }\n    }\n\n    &--selected {\n        .node__circle,\n        .node__labelBackground {\n            fill: var(--yellow-30);\n        }\n\n        .node__circle {\n            stroke: var(--yellow-50);\n        }\n    }\n\n    &--selectedDependent {\n        .node__circle {\n            stroke: var(--grey-50);\n        }\n    }\n\n    &--usesProps {\n        .node__circle,\n        .node__labelBackground {\n            fill: var(--mint-30);\n        }\n\n        .node__circle {\n            stroke: var(--mint-50);\n        }\n\n        .node__channel--selected {\n            fill: var(--mint-50);\n            stroke: var(--mint-70);\n        }\n    }\n\n    &--usesSlots {\n        .node__circle,\n        .node__labelBackground {\n            fill: var(--purple-30);\n        }\n\n        .node__circle {\n            stroke: var(--purple-50);\n        }\n\n        .node__channel--selected {\n            fill: var(--purple-50);\n            stroke: var(--purple-70);\n        }\n    }\n\n    &--usesEvents {\n        .node__circle,\n        .node__labelBackground {\n            fill: var(--red-30);\n        }\n\n        .node__circle {\n            stroke: var(--red-50);\n        }\n\n        .node__channel--selected {\n            fill: var(--red-50);\n            stroke: var(--red-70);\n        }\n    }\n\n    &--greyedOut {\n        .node__circle {\n            stroke: none;\n            fill: var(--grey-5);\n        }\n\n        .node__labelBackground {\n            fill: var(--grey-5);\n        }\n\n        .node__labelText {\n            fill: var(--grey-20);\n        }\n    }\n}\n\n.link {\n    stroke: var(--grey-20);\n\n    &--selected {\n        stroke: var(--grey-50);\n    }\n\n    &--greyedOut {\n       stroke: var(--grey-15);\n    }\n}\n\n.arrow {\n    stroke: var(--grey-20);\n    fill: var(--grey-20);\n\n    &--selected {\n        stroke: var(--grey-50);\n        fill: var(--grey-50);\n    }\n}\n</style>\n"
  },
  {
    "path": "packages/app/src/components/MenuCommunication.vue",
    "content": "<template>\n    <div class=\"menuCommunication\">\n            <base-dropdown>\n                <template #trigger=\"{ isOpen }\">\n                    <base-icon icon-name=\"node size filter\">\n                        <icon-filter />\n                    </base-icon>\n                    {{ nodeSizeFilterLocal.label }}\n                    <base-arrow-icon :is-flipped=\"isOpen\" />\n                </template>\n                <div class=\"menuCommunication__filterForm\">\n                    <h4>Component size</h4>\n                    <base-radio-button-group\n                        v-model=\"nodeSizeFilterLocal\"\n                        :options=\"nodeSizeFilterOptions\"\n                        name=\"nodeSizeFilter\"\n                    />\n                </div>\n            </base-dropdown>\n        <label class=\"input\" for=\"search\">\n            <base-icon icon-name=\"search\">\n                <icon-search />\n            </base-icon>\n            <input\n                id=\"search\"\n                :value=\"search\"\n                class=\"menuCommunication__search\"\n                placeholder=\"Search for a component\"\n                @input=\"$emit('update:search', $event.target.value)\"\n            />\n            <base-icon-button\n                icon-name=\"cross\"\n                size=\"xs\"\n                @click=\"$emit('update:search', '')\"\n            >\n                    <icon-cross />\n            </base-icon-button>\n        </label>\n    </div>\n</template>\n\n<script lang=\"ts\">\nimport {\n  defineComponent, ref, watch,\n} from 'vue';\n\nimport BaseArrowIcon from '@/components/base/BaseArrowIcon.vue';\nimport BaseDropdown from '@/components/base/BaseDropdown.vue';\nimport BaseIcon from '@/components/base/BaseIcon.vue';\nimport BaseIconButton from '@/components/base/BaseIconButton.vue';\nimport BaseRadioButtonGroup from '@/components/base/BaseRadioButtonGroup.vue';\nimport IconSearch from '@/components/icons/IconSearch.vue';\nimport IconCross from '@/components/icons/IconCross.vue';\nimport IconFilter from '@/components/icons/IconFilter.vue';\n\nimport nodeSizeAttributeType from '@/types/nodeSizeAttributeType';\n\nconst nodeSizeFilterOptions = [\n  {\n    label: 'Props',\n    value: nodeSizeAttributeType.PROP,\n  },\n  {\n    label: 'Events',\n    value: nodeSizeAttributeType.EVENT,\n  },\n  {\n    label: 'Slots',\n    value: nodeSizeAttributeType.SLOT,\n  },\n  {\n    label: 'Props, Events & Slots',\n    value: nodeSizeAttributeType.CHANNELS,\n  },\n  {\n    label: 'Dependencies',\n    value: nodeSizeAttributeType.DEPENDENCIES,\n  },\n  {\n    label: 'Dependents',\n    value: nodeSizeAttributeType.DEPENDENTS,\n  },\n  {\n    label: 'No filter',\n    value: nodeSizeAttributeType.NONE,\n  },\n];\n\nexport default defineComponent({\n  components: {\n    BaseArrowIcon,\n    BaseIcon,\n    BaseIconButton,\n    BaseDropdown,\n    BaseRadioButtonGroup,\n    IconCross,\n    IconFilter,\n    IconSearch,\n  },\n  props: {\n    nodeSizeFilter: {\n      type: String,\n      required: true,\n    },\n    search: {\n      type: String,\n      default: '',\n    },\n  },\n  setup(props, { emit }) {\n    const nodeSizeFilterLocal = ref(nodeSizeFilterOptions.find((option) => option.value === props.nodeSizeFilter));\n    watch(nodeSizeFilterLocal, () => {\n      emit('update:nodeSizeFilter', nodeSizeFilterLocal?.value?.value);\n    });\n\n    return {\n      nodeSizeAttributeType,\n      nodeSizeFilterLocal,\n      nodeSizeFilterOptions,\n    };\n  },\n});\n</script>\n<style lang=\"scss\">\n.menuCommunication {\n    padding: var(--spacing--m);\n    display: flex;\n    gap: var(--spacing--m);\n\n    &__filterForm {\n        display: flex;\n        flex-direction: column;\n        gap: var(--spacing--m);\n    }\n\n    &__search {\n        min-width: 15rem;\n    }\n}\n</style>\n"
  },
  {
    "path": "packages/app/src/components/SidebarCommunication.vue",
    "content": "<template>\n  <div class=\"sidebarCommunication\">\n      <h2>{{ component.name }}</h2>\n      <p>{{ component.fullPath }}</p>\n      <base-sub-nav\n          :items=\"[\n              {\n                  to: '/',\n                  name: 'Props',\n                  color: 'mint',\n                  counter: component.props.length,\n                  disabled: component.props.length > 0\n              },\n              {\n                  to: '/events',\n                  name: 'Events',\n                  color: 'red',\n                  counter: component.events.length,\n                  disabled: component.events.length > 0\n              },\n              {\n                  to: '/slots',\n                  name: 'Slots',\n                  color: 'purple',\n                  counter: component.slots.length,\n                  disabled: component.slots.length > 0\n              }\n          ]\"\n      />\n      <router-view\n          :component=\"component\"\n          @channelSelected=\"selectChannel\"\n      />\n  </div>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent, ref, PropType } from 'vue';\n\nimport BaseSubNav from '@/components/base/BaseSubNav.vue';\n\nimport { Prop, VueComponent } from '@vuensight/types';\n\nexport default defineComponent({\n  components: {\n    BaseSubNav,\n  },\n  props: {\n    component: {\n      type: Object as PropType<VueComponent>,\n      required: true,\n    },\n  },\n  setup(props, { emit }) {\n    const selectedChannel = ref<Prop | null>(null);\n    const selectChannel = (channel: Prop) => {\n      selectedChannel.value = channel;\n      emit('channelSelected', selectedChannel.value);\n    };\n    return {\n      selectChannel,\n      selectedChannel,\n    };\n  },\n});\n</script>\n\n<style lang=\"scss\">\n.sidebarCommunication {\n    display: flex;\n    flex-direction: column;\n    gap: var(--spacing--l);\n    height: 100%;\n}\n</style>\n"
  },
  {
    "path": "packages/app/src/components/SidebarCommunicationEventsTab.vue",
    "content": "<template>\n  <div class=\"sidebarCommunicationPropsTab\">\n      <card-communication-channel\n          v-for=\"event in eventsWithDependents\"\n          :key=\"event.name\"\n          :channel=\"event\"\n          :dependents=\"event.dependents\"\n          :is-selected=\"event.dependents.length > 0 && selectedChannel && selectedChannel.name === event.name\"\n          color=\"red\"\n          @click=\"event.dependents.length > 0 && selectChannel(event)\"\n      />\n  </div>\n</template>\n\n<script lang=\"ts\">\nimport {\n  computed, defineComponent, ref, PropType,\n} from 'vue';\n\nimport CardCommunicationChannel from '@/components/CardCommunicationChannel.vue';\n\nimport { Event, VueComponent } from '@vuensight/types';\n\nexport default defineComponent({\n  components: {\n    CardCommunicationChannel,\n  },\n  props: {\n    component: {\n      type: Object as PropType<VueComponent>,\n      required: true,\n    },\n  },\n  setup(props, { emit }) {\n    const selectedChannel = ref<Event | null>(null);\n    const selectChannel = (channel: Event) => {\n      console.log('event', selectedChannel.value?.name, channel.name);\n      selectedChannel.value = selectedChannel.value?.name !== channel.name ? channel : null;\n      emit('channelSelected', selectedChannel.value);\n    };\n\n    const eventsWithDependents = computed(() => props.component.events.map((event, index) => ({\n      ...event,\n      dependents: props.component.dependents.filter((dependent) => dependent.usedEvents.includes(index)),\n    })));\n\n    return {\n      eventsWithDependents,\n      selectChannel,\n      selectedChannel,\n    };\n  },\n});\n</script>\n\n<style lang=\"scss\">\n.sidebarCommunicationPropsTab {\n    display: flex;\n    flex-direction: column;\n    gap: var(--spacing--l);\n}\n</style>\n"
  },
  {
    "path": "packages/app/src/components/SidebarCommunicationPropsTab.vue",
    "content": "<template>\n  <div class=\"sidebarCommunicationPropsTab\">\n      <card-communication-channel\n          v-for=\"prop in propsWithDependents\"\n          :key=\"prop.name\"\n          :channel=\"prop\"\n          :dependents=\"prop.dependents\"\n          :is-selected=\"prop.dependents.length > 0 && selectedChannel && selectedChannel.name === prop.name\"\n          color=\"mint\"\n          @click=\"prop.dependents.length > 0 && selectChannel(prop)\"\n      />\n  </div>\n</template>\n\n<script lang=\"ts\">\nimport {\n  computed, defineComponent, ref, PropType,\n} from 'vue';\n\nimport CardCommunicationChannel from '@/components/CardCommunicationChannel.vue';\n\nimport { Prop, VueComponent } from '@vuensight/types';\n\nexport default defineComponent({\n  components: {\n    CardCommunicationChannel,\n  },\n  props: {\n    component: {\n      type: Object as PropType<VueComponent>,\n      required: true,\n    },\n  },\n  setup(props, { emit }) {\n    const selectedChannel = ref<Prop | null>(null);\n    const selectChannel = (channel: Prop) => {\n      selectedChannel.value = selectedChannel.value?.name !== channel.name ? channel : null;\n      emit('channelSelected', selectedChannel.value);\n    };\n\n    const propsWithDependents = computed(() => props.component.props.map((prop, index) => ({\n      ...prop,\n      dependents: props.component.dependents.filter((dependent) => dependent.usedProps.includes(index)),\n    })));\n\n    return {\n      propsWithDependents,\n      selectChannel,\n      selectedChannel,\n    };\n  },\n});\n</script>\n\n<style lang=\"scss\">\n.sidebarCommunicationPropsTab {\n    display: flex;\n    flex-direction: column;\n    gap: var(--spacing--l);\n}\n</style>\n"
  },
  {
    "path": "packages/app/src/components/SidebarCommunicationSlotsTab.vue",
    "content": "<template>\n  <div class=\"sidebarCommunicationSlotsTab\">\n      <card-communication-channel\n          v-for=\"slot in slotsWithDependents\"\n          :key=\"slot.name\"\n          :channel=\"slot\"\n          :dependents=\"slot.dependents\"\n          :is-selected=\"slot.dependents.length > 0 && selectedChannel && selectedChannel.name === slot.name\"\n          color=\"purple\"\n          @click=\"slot.dependents.length > 0 && selectChannel(slot)\"\n      />\n  </div>\n</template>\n\n<script lang=\"ts\">\nimport {\n  computed, defineComponent, ref, PropType,\n} from 'vue';\n\nimport CardCommunicationChannel from '@/components/CardCommunicationChannel.vue';\n\nimport { Slot, VueComponent } from '@vuensight/types';\n\nexport default defineComponent({\n  components: {\n    CardCommunicationChannel,\n  },\n  props: {\n    component: {\n      type: Object as PropType<VueComponent>,\n      required: true,\n    },\n  },\n  setup(props, { emit }) {\n    const selectedChannel = ref<Slot | null>(null);\n    const selectChannel = (channel: Slot) => {\n      selectedChannel.value = selectedChannel.value?.name !== channel.name ? channel : null;\n      emit('channelSelected', selectedChannel.value);\n    };\n\n    const slotsWithDependents = computed(() => props.component.slots.map((slot, index) => ({\n      ...slot,\n      dependents: props.component.dependents.filter((dependent) => dependent.usedSlots.includes(index)),\n    })));\n\n    return {\n      slotsWithDependents,\n      selectChannel,\n      selectedChannel,\n    };\n  },\n});\n</script>\n\n<style lang=\"scss\">\n.sidebarCommunicationSlotsTab {\n    display: flex;\n    flex-direction: column;\n    gap: var(--spacing--l);\n}\n</style>\n"
  },
  {
    "path": "packages/app/src/components/base/BaseArrowIcon.vue",
    "content": "<template>\n    <svg\n        :class=\"{ 'baseArrowIcon--flipped': isFlipped }\"\n        class=\"baseArrowIcon\"\n        viewBox=\"0 0 15 16\"\n        xmlns=\"http://www.w3.org/2000/svg\"\n    >\n        <path\n            class=\"baseArrowIcon__arrow\"\n            d=\"M7.5 12.58a.66.66 0 0 1-.46-.19L1.18 6.53c-.25-.25-.25-.67 0-.92s.67-.25.92\n                0l5.4 5.4 5.4-5.4c.25-.25.67-.25.92 0s.25.67 0 .92l-5.86 5.86a.66.66 0 0 1-.46.19z\"\n        />\n    </svg>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent } from 'vue';\n\nexport default defineComponent({\n  name: 'BaseArrowIcon',\n  props: {\n    isFlipped: {\n      type: Boolean,\n      default: false,\n    },\n  },\n});\n</script>\n\n<style lang=\"scss\">\n.baseArrowIcon {\n    display: flex;\n    height: 1.75rem;\n    width: 1.75rem;\n    padding: var(--spacing--s);\n    cursor: pointer;\n    transform: rotate(0deg);\n    background: transparent;\n    transition: background, transform 200ms ease;\n\n    &:hover,\n    &:focus {\n        background: var(--grey-10);\n        border-radius: var(--border-radius--2xl);\n    }\n\n    &__arrow {\n        fill: var(--grey-50);\n        stroke: var(--grey-50);\n    }\n\n    &--flipped {\n        transform: rotate(180deg);\n    }\n}\n</style>\n"
  },
  {
    "path": "packages/app/src/components/base/BaseBadge.vue",
    "content": "<template>\n    <span\n        class=\"baseBadge\"\n        :class=\"`baseBadge--${color} ${isRound ? 'baseBade--round' : ''}`\"\n    >\n        <slot />\n    </span>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent, PropType } from 'vue';\n\nimport { Color } from '@/types/color';\n\nexport default defineComponent({\n  name: 'BaseBadge',\n  props: {\n    color: {\n      type: String as PropType<Color>,\n    },\n    isRound: {\n      type: Boolean,\n      default: true,\n    },\n  },\n});\n</script>\n\n<style lang=\"scss\">\n.baseBadge {\n    background: var(--grey-10);\n    color: var(--grey-50);\n    font-size: var(--font-size--s);\n    line-height: var(--spacing--l);\n    padding: var(--spacing--2xs) var(--spacing--s);\n    border-radius: var(--border-radius--s);\n\n    &--round {\n        border-radius: var(--border-radius--l);\n    }\n\n    &--mint {\n        background: var(--mint-50);\n        color: white;\n    }\n\n    &--red {\n        background: var(--red-50);\n        color: white;\n    }\n\n    &--purple {\n        background: var(--purple-50);\n        color: white;\n    }\n\n    &--light-mint {\n        background: var(--mint-10);\n        color: var(--mint-70);\n    }\n\n    &--light-red {\n        background: var(--red-10);\n        color: var(--red-70);\n    }\n}\n</style>\n"
  },
  {
    "path": "packages/app/src/components/base/BaseCard.vue",
    "content": "<template>\n    <button class=\"baseCard\">\n        <span class=\"baseCard__header\" :class=\"{'baseCard__header--expanded': isExpanded && $slots.body}\">\n            <slot name=\"header\"/>\n            <button\n                v-if=\"$slots.body || $slots.footer\"\n                @click.stop=\"isExpanded = !isExpanded\"\n            >\n                <base-arrow-icon\n                    :is-flipped=\"isExpanded\"\n                />\n            </button>\n        </span>\n        <transition>\n            <span v-if=\"isExpanded && $slots.body\" class=\"baseCard__body\">\n                <slot name=\"body\" />\n            </span>\n        </transition>\n        <transition>\n            <span v-if=\"isExpanded && $slots.footer\" class=\"baseCard__footer\">\n                <slot name=\"footer\" />\n            </span>\n        </transition>\n    </button>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent, ref } from 'vue';\n\nimport BaseArrowIcon from '@/components/base/BaseArrowIcon.vue';\n\nexport default defineComponent({\n  name: 'BaseCard',\n  components: {\n    BaseArrowIcon,\n  },\n  setup() {\n    const isExpanded = ref(false);\n    return {\n      isExpanded,\n    };\n  },\n});\n</script>\n\n<style lang=\"scss\">\n.baseCard {\n    background: white;\n    border-radius: var(--border-radius--m);\n    box-shadow: var(--box-shadow--s);\n    display: flex;\n    flex-direction: column;\n\n    &__header {\n        display: flex;\n        justify-content: space-between;\n        width: 100%;\n        padding: var(--spacing--m) var(--spacing--l);\n\n        &--expanded {\n            border-bottom: 1px solid var(--grey-10);\n        }\n    }\n\n    &__body,\n    &__footer {\n        padding: var(--spacing--m) var(--spacing--l);\n        font-size: var(--font-size--m);\n    }\n\n    &__footer {\n        border-top: 1px solid var(--grey-10);\n        width: 100%;\n    }\n\n    .v-enter-active,\n    .v-leave-active {\n        transition: opacity 0.2s ease;\n    }\n\n    .v-enter-from,\n    .v-leave-to {\n        opacity: 0;\n    }\n}\n\n</style>\n"
  },
  {
    "path": "packages/app/src/components/base/BaseCheckIcon.vue",
    "content": "<template>\n    <svg\n        class=\"baseCheckIcon\"\n        :class=\"{\n            [`baseCheckIcon--${color}`]: color,\n            'baseCheckIcon--selected': isChecked,\n            'baseCheckIcon--disabled': isDisabled\n        }\"\n        xmlns=\"http://www.w3.org/2000/svg\"\n        viewBox=\"0 0 12.19 18\"\n    >\n        <path\n            class=\"baseCheckIcon__check\"\n            d=\"M4.7 12.83a1 1 0 0 1-.71-.3L1.3 9.81c-.39-.4-.38-1.03.01-1.42.39-.38 1.03-.38\n            1.41.01l1.97 1.99 4.77-4.92a.987.987 0 0 1 1.41-.02c.4.38.41 1.02.02 1.41l-5.48\n            5.65c-.18.2-.48.29-.71.32z\"\n        />\n    </svg>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent, PropType } from 'vue';\n\nimport { Color } from '@/types/color';\n\nexport default defineComponent({\n  props: {\n    isChecked: {\n      type: Boolean,\n      default: false,\n    },\n    color: {\n      type: String as PropType<Color>,\n      default: 'mint',\n    },\n    isDisabled: {\n      type: Boolean,\n      default: false,\n    },\n  },\n});\n</script>\n\n<style lang=\"scss\">\n.baseCheckIcon {\n    height: 1.5rem;\n    width: 1.5rem;\n    border-radius: var(--border-radius--l);\n    background: var(--grey-10);\n\n    &__check {\n        fill: var(--grey-50);\n    }\n\n    &--disabled .baseCheckIcon__check {\n        fill: var(--grey-20);\n    }\n\n    &--selected {\n        .baseCheckIcon__check {\n            fill: white;\n        }\n    }\n\n    &--mint.baseCheckIcon--selected {\n        background: var(--mint-50);\n    }\n\n    &--red.baseCheckIcon--selected {\n        background: var(--red-50);\n    }\n\n    &--purple.baseCheckIcon--selected {\n        background: var(--purple-50);\n    }\n}\n</style>\n"
  },
  {
    "path": "packages/app/src/components/base/BaseDelimiter.vue",
    "content": "<template>\n    <span\n        :class=\"{\n            [`baseDelimiter--${color}`]: color,\n        }\"\n        class=\"baseDelimiter\"\n    >\n       &bull;\n    </span>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent, PropType } from 'vue';\n\nimport { Color } from '@/types/color';\n\nexport default defineComponent({\n  name: 'BaseDelimiter',\n  props: {\n    color: {\n      type: String as PropType<Color>,\n    },\n  },\n});\n</script>\n\n<style lang=\"scss\">\n.baseDelimiter {\n    font-weight: bold;\n    color: var(--navy-90);\n\n    &--mint {\n        color: var(--mint-50);\n    }\n\n    &--red {\n        color: var(--red-50);\n    }\n\n    &--purple {\n        color: var(--purple-50);\n    }\n}\n</style>\n"
  },
  {
    "path": "packages/app/src/components/base/BaseDropdown.vue",
    "content": "<template>\n    <div\n        ref=\"dropdown\"\n        class=\"baseDropdown\"\n        :class=\"{'c-appDropdown--open': isOpen}\"\n    >\n        <button class=\"input\" @click=\"toggle\">\n            <slot :isOpen=\"isOpen\" name=\"trigger\"/>\n        </button>\n        <div\n            v-if=\"isOpen\"\n            class=\"baseDropdown__content\"\n        >\n            <slot :close=\"close\"/>\n        </div>\n    </div>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent, ref } from 'vue';\n\nimport { onClickOutside } from '@vueuse/core';\n\nexport default defineComponent({\n  setup() {\n    const isOpen = ref<boolean>(false);\n\n    const toggle = () => {\n      isOpen.value = !isOpen.value;\n    };\n\n    const close = () => {\n      isOpen.value = false;\n    };\n\n    const dropdown = ref(null);\n    onClickOutside(dropdown, () => close());\n\n    return {\n      close,\n      isOpen,\n      toggle,\n      dropdown,\n    };\n  },\n});\n</script>\n<style lang=\"scss\">\n.baseDropdown {\n    display: flex;\n    flex-direction: column;\n    align-items: start;\n    gap: var(--spacing--m);\n\n    &__content {\n        padding: var(--spacing--m);\n        position: absolute;\n        top: calc(var(--spacing--3xl) + var(--spacing--m));\n        margin-top: var(--spacing--m);\n        background: white;\n        border: none;\n        border-radius: var(--border-radius--s);\n        box-shadow: var(--box-shadow--s);\n    }\n}\n</style>\n"
  },
  {
    "path": "packages/app/src/components/base/BaseIcon.vue",
    "content": "<template>\n    <svg :aria-labelledby=\"iconName\"\n         :class=\"`baseIcon baseIcon--${size}`\"\n         role=\"presentation\"\n         viewBox=\"0 0 18 18\"\n         xmlns=\"http://www.w3.org/2000/svg\"\n    >\n        <title :id=\"iconName\" lang=\"en\">{{iconName}} icon</title>\n        <g :fill=\"`var(--${iconColor})`\">\n            <slot />\n        </g>\n    </svg>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent } from 'vue';\n\nexport default defineComponent({\n  props: {\n    iconName: {\n      type: String,\n      default: 'box',\n    },\n    size: {\n      type: String,\n      default: 'm',\n    },\n    iconColor: {\n      type: String,\n      default: 'grey-30',\n    },\n  },\n});\n</script>\n\n<style lang=\"scss\" scoped>\n.baseIcon {\n    display: inline-block;\n    vertical-align: baseline;\n\n    &--xs {\n        width: var(--icon--xs);\n        height: var(--icon--xs);\n    }\n\n    &--s {\n        width: var(--icon--s);\n        height: var(--icon--s);\n    }\n\n    &--m {\n        width: var(--icon--m);\n        height: var(--icon--m);\n    }\n\n    &--l {\n        width: var(--icon--l);\n        height: var(--icon--l);\n    }\n\n    &--xl {\n        width: var(--icon--xl);\n        height: var(--icon--xl);\n    }\n\n    &--2xl {\n        width: var(--icon--2xl);\n        height: var(--icon--2xl);\n    }\n}\n</style>\n"
  },
  {
    "path": "packages/app/src/components/base/BaseIconButton.vue",
    "content": "<template>\n    <button class=\"baseIconButton\">\n        <base-icon :icon-color=\"iconColor\" :icon-name=\"iconName\" :size=\"size\">\n            <slot />\n        </base-icon>\n    </button>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent } from 'vue';\n\nimport BaseIcon from '@/components/base/BaseIcon.vue';\n\nexport default defineComponent({\n  props: {\n    iconName: {\n      type: String,\n      default: 'box',\n    },\n    size: {\n      type: String,\n      default: 'm',\n    },\n    iconColor: {\n      type: String,\n      default: 'grey-30',\n    },\n  },\n  components: {\n    BaseIcon,\n  },\n});\n</script>\n\n<style lang=\"scss\" scoped>\n.baseIconButton {\n    display: flex;\n    justify-content: center;\n    padding: var(--spacing--s);\n}\n</style>\n"
  },
  {
    "path": "packages/app/src/components/base/BaseList.vue",
    "content": "<template>\n    <ul\n        :class=\"{\n            [`baseList--${color}`]: color,\n        }\"\n        class=\"baseList\"\n    >\n        <li\n            v-for=\"item in items\"\n            :key=\"item\"\n            class=\"baseList__item\"\n        >\n            <span class=\"baseList__itemText\">{{ item }}</span>\n        </li>\n    </ul>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent, PropType } from 'vue';\n\nimport { Color } from '@/types/color';\n\nexport default defineComponent({\n  name: 'BaseDelimiter',\n  props: {\n    items: {\n      type: Array,\n      default: () => [],\n    },\n    color: {\n      type: String as PropType<Color>,\n    },\n  },\n});\n</script>\n\n<style lang=\"scss\">\n.baseList {\n    list-style: none; /* Remove default bullets */\n    padding-inline-start: var(--spacing--xl);\n\n    &__item {\n        display: flex;\n    }\n\n    &__item::before {\n        content: \"\\2022\";\n        font-weight: bold;\n        color: var(--navy-90);\n        display: inline-block;\n        width: 1em;\n        margin-left: -1em;\n    }\n\n    &--mint .baseList__item::before {\n        color: var(--mint-50);\n    }\n\n    &--red .baseList__item::before {\n        color: var(--red-50);\n    }\n\n    &--purple .baseList__item::before {\n        color: var(--purple-50);\n    }\n\n    &__itemText {\n        overflow: hidden;\n        white-space: nowrap;\n        text-overflow: ellipsis;\n    }\n}\n</style>\n"
  },
  {
    "path": "packages/app/src/components/base/BaseLoadingSpinner.vue",
    "content": "<template>\n    <div class=\"flower-spinner\">\n        <div class=\"dots-container\">\n            <div class=\"bigger-dot\">\n                <div class=\"smaller-dot\"></div>\n            </div>\n        </div>\n    </div>\n</template>\n\n<style>\n.flower-spinner,  .flower-spinner * {\n    box-sizing: border-box;\n}\n\n.flower-spinner {\n    height: 70px;\n    width: 70px;\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n    justify-content: center;\n    transform: scale(0.8);\n}\n\n.flower-spinner .dots-container {\n    height: calc(70px / 7);\n    width: calc(70px / 7);\n}\n\n.flower-spinner .smaller-dot {\n    background: var(--yellow-50);\n    height: 100%;\n    width: 100%;\n    border-radius: 50%;\n    animation: flower-spinner-smaller-dot-animation 2.5s 0s infinite both;\n\n}\n\n.flower-spinner .bigger-dot {\n    background: var(--yellow-50);\n    height: 100%;\n    width: 100%;\n    padding: 10%;\n    border-radius: 50%;\n    animation: flower-spinner-bigger-dot-animation 2.5s 0s infinite both;\n}\n\n@keyframes flower-spinner-bigger-dot-animation {\n    0%, 100% {\n        box-shadow: var(--yellow-50) 0px 0px 0px,\n        var(--yellow-50) 0px 0px 0px,\n        var(--yellow-50) 0px 0px 0px,\n        var(--yellow-50) 0px 0px 0px,\n        var(--yellow-50) 0px 0px 0px,\n        var(--yellow-50) 0px 0px 0px,\n        var(--yellow-50) 0px 0px 0px,\n        var(--yellow-50) 0px 0px 0px;\n    }\n\n    50% {\n        transform: rotate(180deg);\n    }\n\n    25%, 75% {\n        box-shadow: var(--yellow-50) 26px 0px 0px,\n        var(--yellow-50) -26px 0px 0px,\n        var(--yellow-50) 0px 26px 0px,\n        var(--yellow-50) 0px -26px 0px,\n        var(--yellow-50) 19px -19px 0px,\n        var(--yellow-50) 19px 19px 0px,\n        var(--yellow-50) -19px -19px 0px,\n        var(--yellow-50) -19px 19px 0px;\n    }\n\n    100% {\n        transform: rotate(360deg);\n        box-shadow: var(--yellow-50) 0px 0px 0px,\n        var(--yellow-50) 0px 0px 0px,\n        var(--yellow-50) 0px 0px 0px,\n        var(--yellow-50) 0px 0px 0px,\n        var(--yellow-50) 0px 0px 0px,\n        var(--yellow-50) 0px 0px 0px,\n        var(--yellow-50) 0px 0px 0px,\n        var(--yellow-50) 0px 0px 0px;\n    }\n}\n\n@keyframes flower-spinner-smaller-dot-animation {\n    0%, 100% {\n        box-shadow: var(--yellow-50) 0px 0px 0px,\n        var(--yellow-50) 0px 0px 0px,\n        var(--yellow-50) 0px 0px 0px,\n        var(--yellow-50) 0px 0px 0px,\n        var(--yellow-50) 0px 0px 0px,\n        var(--yellow-50) 0px 0px 0px,\n        var(--yellow-50) 0px 0px 0px,\n        var(--yellow-50) 0px 0px 0px;\n    }\n\n    25%, 75% {\n        box-shadow: var(--yellow-50) 14px 0px 0px,\n        var(--yellow-50) -14px 0px 0px,\n        var(--yellow-50) 0px 14px 0px,\n        var(--yellow-50) 0px -14px 0px,\n        var(--yellow-50) 10px -10px 0px,\n        var(--yellow-50) 10px 10px 0px,\n        var(--yellow-50) -10px -10px 0px,\n        var(--yellow-50) -10px 10px 0px;\n    }\n\n    100% {\n        box-shadow: var(--yellow-50) 0px 0px 0px,\n        var(--yellow-50) 0px 0px 0px,\n        var(--yellow-50) 0px 0px 0px,\n        var(--yellow-50) 0px 0px 0px,\n        var(--yellow-50) 0px 0px 0px,\n        var(--yellow-50) 0px 0px 0px,\n        var(--yellow-50) 0px 0px 0px,\n        var(--yellow-50) 0px 0px 0px;\n    }\n}\n</style>\n"
  },
  {
    "path": "packages/app/src/components/base/BaseRadioButtonGroup.vue",
    "content": "<template>\n    <form class=\"baseRadioButtonGroup\">\n        <label\n            v-for=\"option in options\"\n            :key=\"option.value\"\n            :for=\"option.value\"\n            class=\"baseRadioButtonGroup__label\"\n        >\n            <input\n                type=\"radio\"\n                :name=\"name\"\n                :checked=\"modelValue.value === option.value\"\n                :value=\"option.value\"\n                :id=\"option.value\"\n                class=\"baseRadioButtonGroup__input\"\n                @change=\"$emit('update:modelValue', option)\"\n            />\n            {{ option.label }}\n        </label>\n    </form>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent, PropType } from 'vue';\n\ntype RadioButtonItem = {\n    label: string;\n    value: string;\n}\n\nexport default defineComponent({\n  props: {\n    modelValue: {\n      type: Object as PropType<RadioButtonItem>,\n      required: true,\n    },\n    options: {\n      type: Array as PropType<RadioButtonItem[]>,\n      required: true,\n    },\n    name: {\n      type: String,\n      default: 'radio-button',\n    },\n  },\n});\n</script>\n<style lang=\"scss\">\n.baseRadioButtonGroup {\n    display: flex;\n    flex-direction: column;\n    align-items: start;\n    gap: var(--spacing--m);\n    cursor: pointer;\n\n    &__input {\n        accent-color: var(--mint-70);\n    }\n\n    &__label {\n        width: 100%;\n        cursor: pointer;\n    }\n}\n</style>\n"
  },
  {
    "path": "packages/app/src/components/base/BaseSubNav.vue",
    "content": "<template>\n    <nav\n        class=\"baseSubNav\"\n    >\n        <router-link\n            v-for=\"item in items\"\n            :key=\"item.name\"\n            :to=\"item.to\"\n            :disabled=\"item.disabled\"\n            :class=\"`baseSubNav__item baseSubNav__item--${item.color}`\"\n        >\n            <span class=\"baseSubNav__itemText\">\n                <base-badge\n                    v-if=\"item.counter !== undefined\"\n                    is-round\n                    :color=\"item.color\"\n                >\n                    {{ item.counter }}\n                </base-badge>\n                {{ item.name }}\n            </span>\n        </router-link>\n    </nav>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent, PropType } from 'vue';\n\nimport BaseBadge from '@/components/base/BaseBadge.vue';\n\ntype SubNavItem = {\n    to: string,\n    name: string,\n    counter?: number,\n    disabled?: boolean,\n}\n\nexport default defineComponent({\n  components: {\n    BaseBadge,\n  },\n  props: {\n    items: {\n      type: Object as () => PropType<SubNavItem[]>,\n      required: true,\n    },\n  },\n});\n</script>\n\n<style lang=\"scss\">\n.baseSubNav {\n    display: flex;\n    gap: var(--spacing--s);\n    text-decoration: none;\n    border-bottom: 1px solid var(--grey-10);\n\n    &__item {\n        display: flex;\n        gap: var(--spacing--xs);\n        color: var(--navy-90);\n        text-decoration: none;\n        transition: border-color, background-color, font-weight 200ms ease;\n        border-bottom: 3px solid transparent;\n\n        &:hover {\n            .baseSubNav__itemText {\n                background: var(--grey-10);\n                border-radius: var(--border-radius--s);\n            }\n        }\n\n        &--mint.router-link-exact-active {\n            border-color: var(--mint-50);\n        }\n\n        &--red.router-link-exact-active {\n            border-color: var(--red-50);\n        }\n\n        &--purple.router-link-exact-active {\n            border-color: var(--purple-50);\n        }\n\n        &:active {\n            color: var(--navy-90);\n        }\n    }\n\n    &__itemText {\n        padding: var(--spacing--m) var(--spacing--s);\n    }\n}\n</style>\n"
  },
  {
    "path": "packages/app/src/components/icons/IconCross.vue",
    "content": "<template>\n    <!-- eslint-disable-next-line max-len -->\n    <path d=\"M 3.601562 1.949219 L 9 7.347656 L 14.398438 1.964844 C 14.613281 1.746094 14.910156 1.625 15.21875 1.636719 C 15.832031 1.675781 16.324219 2.167969 16.363281 2.78125 C 16.367188 3.078125 16.25 3.359375 16.035156 3.566406 L 10.621094 9 L 16.035156 14.433594 C 16.25 14.640625 16.367188 14.921875 16.363281 15.21875 C 16.324219 15.832031 15.832031 16.324219 15.21875 16.363281 C 14.910156 16.375 14.613281 16.253906 14.398438 16.035156 L 9 10.652344 L 3.617188 16.035156 C 3.402344 16.253906 3.105469 16.375 2.796875 16.363281 C 2.171875 16.332031 1.667969 15.828125 1.636719 15.203125 C 1.632812 14.90625 1.75 14.621094 1.964844 14.417969 L 7.378906 9 L 1.949219 3.566406 C 1.742188 3.359375 1.628906 3.074219 1.636719 2.78125 C 1.675781 2.167969 2.167969 1.675781 2.78125 1.636719 C 3.085938 1.621094 3.382812 1.734375 3.601562 1.949219 Z M 3.601562 1.949219 \"/>\n</template>\n"
  },
  {
    "path": "packages/app/src/components/icons/IconFilter.vue",
    "content": "<template>\n    <!-- eslint-disable max-len -->\n    <path d=\"M 2.46875 16.507812 C 2.46875 16.828125 2.722656 17.078125 3.042969 17.078125 C 3.363281 17.078125 3.613281 16.828125 3.613281 16.507812 L 3.613281 5.175781 C 4.535156 4.925781 5.21875 4.089844 5.21875 3.082031 C 5.21875 1.898438 4.253906 0.921875 3.054688 0.921875 C 1.855469 0.921875 0.90625 1.898438 0.90625 3.082031 C 0.90625 4.074219 1.578125 4.910156 2.484375 5.164062 L 2.484375 16.507812 Z M 2.023438 3.082031 C 2.023438 2.527344 2.484375 2.066406 3.042969 2.066406 C 3.601562 2.066406 4.058594 2.527344 4.058594 3.082031 C 4.058594 3.640625 3.601562 4.101562 3.042969 4.101562 C 2.484375 4.101562 2.023438 3.640625 2.023438 3.082031 Z M 2.023438 3.082031 \"/>\n    <path d=\"M 8.984375 0.921875 C 8.664062 0.921875 8.414062 1.171875 8.414062 1.492188 L 8.414062 7.382812 C 7.507812 7.632812 6.835938 8.46875 6.835938 9.460938 C 6.835938 10.449219 7.507812 11.289062 8.414062 11.539062 L 8.414062 16.519531 C 8.414062 16.84375 8.664062 17.09375 8.984375 17.09375 C 9.308594 17.09375 9.558594 16.84375 9.558594 16.519531 L 9.558594 11.539062 C 10.480469 11.289062 11.164062 10.449219 11.164062 9.445312 C 11.164062 8.441406 10.480469 7.605469 9.558594 7.351562 L 9.558594 1.492188 C 9.558594 1.171875 9.308594 0.921875 8.984375 0.921875 Z M 10.03125 9.460938 C 10.03125 10.019531 9.570312 10.480469 9.015625 10.480469 C 8.457031 10.480469 7.996094 10.019531 7.996094 9.460938 C 7.996094 8.902344 8.457031 8.441406 9.015625 8.441406 C 9.570312 8.441406 10.03125 8.886719 10.03125 9.460938 Z M 10.03125 9.460938 \"/>\n    <path d=\"M 15.503906 12.835938 L 15.503906 1.492188 C 15.503906 1.171875 15.25 0.921875 14.929688 0.921875 C 14.609375 0.921875 14.359375 1.171875 14.359375 1.492188 L 14.359375 12.835938 C 13.4375 13.089844 12.765625 13.925781 12.765625 14.917969 C 12.765625 16.101562 13.730469 17.078125 14.929688 17.078125 C 16.128906 17.078125 17.09375 16.117188 17.09375 14.917969 C 17.105469 13.925781 16.421875 13.089844 15.503906 12.835938 Z M 14.945312 15.933594 C 14.386719 15.933594 13.925781 15.472656 13.925781 14.917969 C 13.925781 14.359375 14.386719 13.898438 14.945312 13.898438 C 15.503906 13.898438 15.960938 14.359375 15.960938 14.917969 C 15.960938 15.472656 15.503906 15.933594 14.945312 15.933594 Z M 14.945312 15.933594 \"/>\n    <!-- eslint-enable max-len -->\n</template>\n"
  },
  {
    "path": "packages/app/src/components/icons/IconSearch.vue",
    "content": "<template>\n    <!-- eslint-disable-next-line max-len -->\n    <path d=\"M 7.199219 13.492188 C 8.734375 13.492188 10.128906 12.949219 11.21875 12.027344 L 16.101562 16.910156 C 16.214844 17.023438 16.351562 17.078125 16.507812 17.078125 C 16.660156 17.078125 16.800781 17.023438 16.910156 16.910156 C 17.136719 16.6875 17.136719 16.324219 16.910156 16.101562 L 12.027344 11.21875 C 12.933594 10.128906 13.492188 8.722656 13.492188 7.199219 C 13.492188 3.726562 10.675781 0.90625 7.199219 0.90625 C 3.738281 0.90625 0.90625 3.738281 0.90625 7.199219 C 0.90625 10.675781 3.738281 13.492188 7.199219 13.492188 Z M 7.199219 2.050781 C 10.046875 2.050781 12.347656 4.367188 12.347656 7.199219 C 12.347656 10.046875 10.046875 12.347656 7.199219 12.347656 C 4.351562 12.347656 2.050781 10.03125 2.050781 7.199219 C 2.050781 4.367188 4.367188 2.050781 7.199219 2.050781 Z M 7.199219 2.050781 \"/>\n</template>\n"
  },
  {
    "path": "packages/app/src/components/icons/IconSelect.vue",
    "content": "<template>\n    <!-- eslint-disable-next-line max-len -->\n    <path d=\"M 8.773438 13.5 C 7.574219 13.4375 6.5625 12.976562 5.738281 12.113281 C 4.914062 11.25 4.5 10.210938 4.5 9 C 4.5 7.75 4.9375 6.6875 5.8125 5.8125 C 6.6875 4.9375 7.75 4.5 9 4.5 C 10.210938 4.5 11.25 4.914062 12.113281 5.738281 C 12.976562 6.5625 13.4375 7.582031 13.5 8.792969 L 12.320312 8.417969 C 12.179688 7.617188 11.804688 6.953125 11.195312 6.421875 C 10.582031 5.890625 9.851562 5.625 9 5.625 C 8.0625 5.625 7.265625 5.953125 6.609375 6.609375 C 5.953125 7.265625 5.625 8.0625 5.625 9 C 5.625 9.835938 5.890625 10.566406 6.421875 11.183594 C 6.953125 11.804688 7.617188 12.179688 8.417969 12.320312 Z M 9 16.5 C 7.960938 16.5 6.988281 16.304688 6.074219 15.910156 C 5.164062 15.515625 4.367188 14.980469 3.695312 14.304688 C 3.019531 13.632812 2.484375 12.835938 2.089844 11.925781 C 1.695312 11.011719 1.5 10.039062 1.5 9 C 1.5 7.960938 1.695312 6.988281 2.089844 6.074219 C 2.484375 5.164062 3.019531 4.367188 3.695312 3.695312 C 4.367188 3.019531 5.164062 2.484375 6.074219 2.089844 C 6.988281 1.695312 7.960938 1.5 9 1.5 C 10.039062 1.5 11.011719 1.695312 11.925781 2.089844 C 12.835938 2.484375 13.632812 3.019531 14.304688 3.695312 C 14.980469 4.367188 15.515625 5.164062 15.910156 6.074219 C 16.304688 6.988281 16.5 7.960938 16.5 9 C 16.5 9.113281 16.496094 9.226562 16.492188 9.335938 C 16.484375 9.449219 16.476562 9.5625 16.460938 9.675781 L 15.375 9.335938 L 15.375 9 C 15.375 7.226562 14.757812 5.71875 13.519531 4.480469 C 12.28125 3.242188 10.773438 2.625 9 2.625 C 7.226562 2.625 5.71875 3.242188 4.480469 4.480469 C 3.242188 5.71875 2.625 7.226562 2.625 9 C 2.625 10.773438 3.242188 12.28125 4.480469 13.519531 C 5.71875 14.757812 7.226562 15.375 9 15.375 L 9.335938 15.375 L 9.675781 16.460938 C 9.5625 16.476562 9.449219 16.484375 9.335938 16.492188 C 9.226562 16.496094 9.113281 16.5 9 16.5 Z M 15.394531 16.875 L 12.1875 13.667969 L 11.25 16.5 L 9 9 L 16.5 11.25 L 13.667969 12.1875 L 16.875 15.394531 Z M 15.394531 16.875 \"/>\n</template>\n"
  },
  {
    "path": "packages/app/src/components/layout/LayoutSplitView.vue",
    "content": "<template>\n    <div class=\"layoutSplitView\">\n        <main class=\"layoutSplitView__main\">\n            <slot />\n        </main>\n        <aside class=\"layoutSplitView__aside\">\n            <slot name=\"aside\" />\n        </aside>\n    </div>\n</template>\n\n<script>\nimport { defineComponent } from 'vue';\n\nexport default defineComponent({\n  name: 'LayoutSplitView',\n});\n</script>\n\n<style lang=\"scss\">\n.layoutSplitView {\n    display: flex;\n    flex-direction: row;\n    height: 100vh;\n\n    &__main {\n        width: 75vw;\n        overflow-y: hidden;\n        background-color: var(--grey-10);\n    }\n\n    &__aside {\n        width: 25vw;\n        background: white;\n        padding: var(--spacing--l) var(--spacing--xl);\n        box-shadow: var(--box-shadow--s);\n        overflow-y: scroll;\n    }\n}\n</style>\n"
  },
  {
    "path": "packages/app/src/composables/fetch.ts",
    "content": "import { ref, readonly } from 'vue';\n\nexport const useFetch = (fetcher: () => Promise<any>) => { // eslint-disable-line @typescript-eslint/no-explicit-any\n  const data = ref(null);\n  const isLoading = ref(false);\n  const isError = ref(false);\n\n  const get = async () => {\n    isLoading.value = true;\n    data.value = null;\n    isError.value = false;\n    try {\n      data.value = await fetcher();\n    } catch (e) {\n      isError.value = true;\n      console.error(e);\n    }\n    isLoading.value = false;\n  };\n\n  return {\n    data: readonly(data),\n    isLoading: readonly(isLoading),\n    isError: readonly(isError),\n    get,\n  };\n};\n\nexport default {\n  useFetch,\n};\n"
  },
  {
    "path": "packages/app/src/main.ts",
    "content": "import { createApp } from 'vue';\nimport App from './App.vue';\nimport router from './router';\n\ncreateApp(App).use(router).mount('#app');\n"
  },
  {
    "path": "packages/app/src/router/index.ts",
    "content": "import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';\nimport PageCommunication from '../views/PageCommunication.vue';\nimport SidebarCommunicationEventsTab from '../components/SidebarCommunicationEventsTab.vue';\nimport SidebarCommunicationPropsTab from '../components/SidebarCommunicationPropsTab.vue';\nimport SidebarCommunicationSlotsTab from '../components/SidebarCommunicationSlotsTab.vue';\n\nconst routes: Array<RouteRecordRaw> = [\n  {\n    path: '/',\n    name: 'page-communication',\n    component: PageCommunication,\n    children: [\n      {\n        path: '',\n        name: 'Props',\n        component: SidebarCommunicationPropsTab,\n      },\n      {\n        path: 'events',\n        name: 'Events',\n        component: SidebarCommunicationEventsTab,\n      },\n      {\n        path: 'slots',\n        name: 'Slots',\n        component: SidebarCommunicationSlotsTab,\n      },\n    ],\n  },\n];\n\nconst router = createRouter({\n  history: createWebHistory(process.env.BASE_URL),\n  routes,\n});\n\nexport default router;\n"
  },
  {
    "path": "packages/app/src/services/parser.ts",
    "content": "import { VueComponent } from '@vuensight/types';\n\nexport const get = ():Promise<VueComponent[]> => fetch('/parse-result')\n  .then((result) => result.json());\n\nexport default {\n  get,\n};\n"
  },
  {
    "path": "packages/app/src/shims-vue.d.ts",
    "content": "/* eslint-disable */\ndeclare module '*.vue' {\n  import type { DefineComponent } from 'vue'\n  const component: DefineComponent<{}, {}, any>\n  export default component\n}"
  },
  {
    "path": "packages/app/src/types/color.ts",
    "content": "export type Color = 'mint' | 'red' | 'purple' | 'light-mint' | 'light-red' | 'light-purple';\n"
  },
  {
    "path": "packages/app/src/types/force.ts",
    "content": "import { VueComponent } from '@vuensight/types';\n\nexport type Link = {\n    source: string,\n    target: string,\n}\n\nexport type ForceLayout = {\n    nodes: VueComponent[],\n    links: Link[],\n}\n"
  },
  {
    "path": "packages/app/src/types/nodeSizeAttributeType.ts",
    "content": "const nodeSizeAttributeType = {\n  PROP: 'props',\n  EVENT: 'events',\n  SLOT: 'slots',\n  CHANNELS: 'channels',\n  DEPENDENTS: 'dependents',\n  DEPENDENCIES: 'dependencies',\n  NONE: 'none',\n};\nObject.freeze(nodeSizeAttributeType);\n\nexport default nodeSizeAttributeType;\n"
  },
  {
    "path": "packages/app/src/views/PageCommunication.vue",
    "content": "<template>\n    <layout-split-view>\n        <div\n            v-if=\"isLoading\"\n            class=\"pageCommunication__loading\"\n        >\n            <base-loading-spinner />\n            <p>... analyzing your components</p>\n        </div>\n        <div\n            v-else-if=\"isError\"\n            class=\"pageCommunication__error\"\n        >\n            <h2>Oh no!</h2>\n            <p>Something went wrong while parsing your project.</p>\n        </div>\n        <div\n            v-else-if=\"forceGraphData && forceGraphData.nodes.length === 0\"\n            class=\"pageCommunication__empty\"\n        >\n            <h2>No components found.</h2>\n            <p>Sorry, we could not find any Vue components. Did you specify the correct folder?</p>\n        </div>\n        <template\n            v-else-if=\"forceGraphData && forceGraphData.nodes.length > 0\"\n        >\n            <menu-communication\n                v-model:node-size-filter=\"nodeSizeFilter\"\n                v-model:search=\"componentSearch\"\n                class=\"pageCommunication__menu\"\n            />\n            <force-graph\n                :selected-channel=\"selectedChannel\"\n                :selected-channel-type=\"selectedChannelType\"\n                :data=\"forceGraphData\"\n                :node-size-attribute=\"nodeSizeFilter\"\n                :search-string=\"componentSearch\"\n                @selected=\"selectedComponent = $event\"\n                @unselected=\"selectedComponent = null\"\n            />\n        </template>\n        <template #aside>\n            <sidebar-communication\n                v-if=\"selectedComponent\"\n                :component=\"selectedComponent\"\n                @channelSelected=\"selectedChannel = $event\"\n            />\n            <div v-else class=\"pageCommunication__noSelection\">\n                <base-icon\n                    icon-color=\"yellow-50\"\n                    icon-name=\"select\"\n                    size=\"2xl\"\n                >\n                    <icon-select/>\n                </base-icon>\n                <p>Select one of the components to get details about its props, events and slots.</p>\n            </div>\n        </template>\n    </layout-split-view>\n</template>\n\n<script lang=\"ts\">\nimport {\n  defineComponent,\n  ComputedRef,\n  computed,\n  ref,\n} from 'vue';\nimport { useRoute } from 'vue-router';\n\nimport * as parserService from '@/services/parser';\n\nimport BaseIcon from '@/components/base/BaseIcon.vue';\nimport BaseLoadingSpinner from '@/components/base/BaseLoadingSpinner.vue';\nimport ForceGraph from '@/components/ForceGraph.vue';\nimport IconSelect from '@/components/icons/IconSelect.vue';\nimport LayoutSplitView from '@/components/layout/LayoutSplitView.vue';\nimport MenuCommunication from '@/components/MenuCommunication.vue';\nimport SidebarCommunication from '@/components/SidebarCommunication.vue';\n\nimport { useFetch } from '@/composables/fetch';\n\nimport {\n  VueComponent, Dependency, Prop,\n} from '@vuensight/types';\nimport {\n  ForceLayout, Link,\n} from '@/types/force';\n\nexport default defineComponent({\n  name: 'PageCommunication',\n  components: {\n    BaseIcon,\n    BaseLoadingSpinner,\n    ForceGraph,\n    IconSelect,\n    LayoutSplitView,\n    MenuCommunication,\n    SidebarCommunication,\n  },\n  setup() {\n    const {\n      data,\n      get: getParserData,\n      isLoading,\n      isError,\n    } = useFetch(parserService.get);\n    getParserData();\n\n    const selectedComponent = ref<VueComponent | null>(null);\n    const selectedChannel = ref<Prop | null>(null);\n\n    const route = useRoute();\n    const selectedChannelType = computed<string>(\n      () => (route && typeof route.name === 'string' ? route.name : 'Props'),\n    );\n\n    const nodeSizeFilter = ref<string>('props');\n    const componentSearch = ref<string>('');\n\n    const formatDataForForceLayout = (originalData: VueComponent[]) => {\n      const nodes: VueComponent[] = [];\n      const links: Link[] = [];\n      originalData.forEach((component: VueComponent) => {\n        nodes.push(component);\n        component.dependencies.forEach(\n          (dependency: Dependency) => links.push({\n            source: component.fullPath,\n            target: dependency.fullPath,\n          }),\n        );\n      });\n      return { nodes, links };\n    };\n\n    const forceGraphData: ComputedRef<ForceLayout | null> = computed(() => {\n      if (data && data.value !== null) {\n        return formatDataForForceLayout(data.value as unknown as VueComponent[]);\n      }\n      return null;\n    });\n\n    return {\n      componentSearch,\n      data,\n      forceGraphData,\n      isLoading,\n      isError,\n      nodeSizeFilter,\n      selectedChannel,\n      selectedChannelType,\n      selectedComponent,\n    };\n  },\n});\n</script>\n<style lang=\"scss\">\n.pageCommunication {\n    &__menu {\n        position: fixed;\n        top: 0;\n        left: 0;\n    }\n\n    &__loading,\n    &__error,\n    &__empty,\n    &__noSelection {\n        display: flex;\n        flex-direction: column;\n        align-items: center;\n        justify-content: center;\n        margin-top: calc(var(--spacing--3xl) * -1);\n        height: 100%;\n        color: var(--grey-50);\n    }\n\n    &__noSelection {\n        width: 75%;\n        margin-left: auto;\n        margin-right: auto;\n        text-align: center;\n        gap: var(--spacing--s);\n    }\n}\n</style>\n"
  },
  {
    "path": "packages/app/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"esnext\",\n    \"module\": \"esnext\",\n    \"strict\": true,\n    \"jsx\": \"preserve\",\n    \"importHelpers\": true,\n    \"moduleResolution\": \"node\",\n    \"skipLibCheck\": true,\n    \"esModuleInterop\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"sourceMap\": true,\n    \"composite\": true,\n    \"baseUrl\": \"./\",\n    \"types\": [\n      \"webpack-env\",\n      \"jest\"\n    ],\n    \"paths\": {\n      \"@/*\": [\n        \"src/*\"\n      ]\n    },\n    \"lib\": [\n      \"esnext\",\n      \"dom\",\n      \"dom.iterable\",\n      \"scripthost\"\n    ]\n  },\n  \"include\": [\n    \"src/**/*.ts\",\n    \"src/**/*.tsx\",\n    \"src/**/*.vue\",\n    \"tests/**/*.ts\",\n    \"tests/**/*.tsx\"\n  ],\n  \"exclude\": [\n    \"node_modules\",\n    \"server/**/*\"\n  ]\n}\n"
  },
  {
    "path": "packages/cli/.eslintrc",
    "content": "{\n  \"ignorePatterns\": [\"bin/**/*.js\"]\n}\n"
  },
  {
    "path": "packages/cli/.gitignore",
    "content": "/dist\n"
  },
  {
    "path": "packages/cli/.npmignore",
    "content": "*\n!dist/*\n!package.json\n!readme.md"
  },
  {
    "path": "packages/cli/LICENSE.txt",
    "content": "MIT License\n\nCopyright (c) 2021-2022 Martina Scharrer\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE."
  },
  {
    "path": "packages/cli/README.md",
    "content": "# vuensight 👀\nVisualize Vue.js **component relationships** and **communication channels**, i.e. props, events and slots. This tool operates on the\ncommand line and is made for developers. The aim of vuensight is to provide visual insight into the components of a\nVue.js project and to support developers before and during refactoring, e.g. by visually analyzing which prop is used\nin which parent component or by highlighting unused components or channels.\n\nAn example visualization of vuensight itself:\n![demo image of vuensight](docs/vuensight-demo.png)\n\nThis tool is built on top of the two awesome packages:\n- [dependency-cruiser](https://github.com/sverweij/dependency-cruiser) for building the dependency tree\n- [vue-docgen-api](https://github.com/vue-styleguidist/vue-styleguidist/tree/dev/packages/vue-docgen-api) for parsing the Vue files\n\n## Getting started 🚀\n### Install\nFirst, install the cli package either locally in the project you want to visualize:\n```\nnpm i -D @vuensight/cli\n```\n\nOr globally on your machine if you plan to visualize multiple projects:\n```\nnpm i -g @vuensight/cli\n```\n\n### Run\nThen run the tool in your project folder:\n```\nvuensight\n```\n\n#### Options\n- `--dir` or `-d` (optional): Specify the directory that should be parsed relative from your current working directory, default is `src`\n- `--port` or `-p` (optional): Start the application in a different port, default is 4444\n- `--webpack-config` or `-wpc` (optional): Specify the path to your webpack-config (from your current working directory). This is particularly important if you use aliases.\n- `--ts-config` or `-tsc` (optional): Specify the path to your TypeScript config file (from your current working directory).\n\nAn example usage:\n```\nvuensight --dir resources/js --port 9999 --webpack-config ./webpack-config.json --ts-config ./tsconfig.json\n```\n\n## Licencse\n[MIT](LICENSE.txt)\n"
  },
  {
    "path": "packages/cli/package.json",
    "content": "{\n  \"name\": \"@vuensight/cli\",\n  \"version\": \"0.1.5\",\n  \"description\": \"Command line interface of @vuensight.\",\n  \"main\": \"bin/index.js\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/martinascharrer/vuensight.git\"\n  },\n  \"scripts\": {\n    \"build\": \"rm -rf dist/* && tsc -p tsconfig.pkg.json\",\n    \"build:watch\": \"tsc -w -p tsconfig.pkg.json\",\n    \"lint\": \"eslint  --ext ts\",\n    \"test\": \"echo 'no tests yet in cli'\",\n    \"prepublish\": \"npm run build\"\n  },\n  \"bin\": {\n    \"vuensight\": \"dist/index.js\"\n  },\n  \"keywords\": [\n    \"cli\",\n    \"node.js\"\n  ],\n  \"author\": \"Martina Scharrer\",\n  \"license\": \"(MIT)\",\n  \"bugs\": {\n    \"url\": \"https://github.com/martinascharrer/vuensight/issues\"\n  },\n  \"homepage\": \"https://github.com/martinascharrer/vuensight#readme\",\n  \"dependencies\": {\n    \"@vuensight/app\": \"^0.3.1\",\n    \"commander\": \"^9.1.0\"\n  }\n}\n"
  },
  {
    "path": "packages/cli/src/index.ts",
    "content": "#!/usr/bin/env node\n\nimport { program } from 'commander';\nimport startServer from '@vuensight/app';\n\nprogram\n    .description('Vue Component Insight CLI')\n    .option('-d, --dir [dir]', 'specify the directory that should be analyzed (default is src)', 'src')\n    .option('-p, --port [port]', 'start the application in a different port (default is 4444)')\n    .option('-wpc, --webpack-config [webpackConfig]', 'path to webpack config file')\n    .option('-tsc, --ts-config [tsConfig]', 'path to TypeScript config file')\n    .parse();\n\nconst dir = program.opts().dir;\nconst port = program.opts().port;\nconst webpackConfig = program.opts().webpackConfig;\nconst tsConfig = program.opts().tsConfig;\n\nconst init = async () => {\n  try {\n      await startServer(dir, port, webpackConfig, tsConfig);\n  } catch (e) {\n    console.error('Something went wrong parsing the project', e);\n  }\n};\n\ninit();\n\n"
  },
  {
    "path": "packages/cli/tsconfig.pkg.json",
    "content": "{\n  \"extends\": \"../../tsconfig.build.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./dist/\"\n  },\n  \"include\": [\n    \"./src/*.ts\"\n  ],\n  \"paths\": {\n    \"@/*\": [\n      \"src/*\"\n    ]\n  }\n}\n"
  },
  {
    "path": "packages/parser/.eslintrc",
    "content": "{\n  \"ignorePatterns\": [\"test/project/*\"]\n}\n"
  },
  {
    "path": "packages/parser/.gitignore",
    "content": "/dist\n/coverage\n"
  },
  {
    "path": "packages/parser/.npmignore",
    "content": "*\n!dist/**/*.js\ndist/tsconfig.pkg.tsbuildinfo\n!package.json\n!readme.md"
  },
  {
    "path": "packages/parser/LICENSE.txt",
    "content": "MIT License\n\nCopyright (c) 2021-2022 Martina Scharrer\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE."
  },
  {
    "path": "packages/parser/README.md",
    "content": "# @vuensight/parser\n> ⚠️ **General information about usage and setup of `vuensight` can be found [here](https://github.com/martinascharrer/vuensight)**\n\nThe `parser` extracts information about component dependencies as well as their used props/events/slots of a\ngiven Vue.js project.\n\n## Development\nCompile typescript files to `dist` folder\n```\nnpm run build\n```\n\nRun eslint\n```\nnpm run lint\n```\n\nRun unit tests + coverage\n```\nnpm test\n```\n\nRun unit test watcher\n```\nnpm run test:watch\n```\n\n## Licencse\n[MIT](LICENSE.txt)"
  },
  {
    "path": "packages/parser/package.json",
    "content": "{\n  \"name\": \"@vuensight/parser\",\n  \"version\": \"0.1.8\",\n  \"main\": \"dist/index.js\",\n  \"description\": \"This parser extracts information regarding the dependencies and their used props, events and slots of a given Vue.js project.\",\n  \"scripts\": {\n    \"build\": \"rm -rf dist/* && tsc -p tsconfig.pkg.json\",\n    \"build:watch\": \"tsc -w -p tsconfig.pkg.json\",\n    \"test\": \"jest --coverage\",\n    \"test:watch\": \"jest --watch\",\n    \"lint\": \"eslint --ext ts\",\n    \"prepublish\": \"npm run build\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/martinascharrer/vuensight.git\"\n  },\n  \"keywords\": [\n    \"parser\",\n    \"vue\"\n  ],\n  \"author\": \"Martina Scharrer\",\n  \"license\": \"(MIT)\",\n  \"bugs\": {\n    \"url\": \"https://github.com/martinascharrer/vuensight/issues\"\n  },\n  \"homepage\": \"https://github.com/martinascharrer/vuensight#readme\",\n  \"jest\": {\n    \"preset\": \"ts-jest\",\n    \"testEnvironment\": \"node\",\n    \"moduleNameMapper\": {\n      \"dependency-cruiser/config-utl/extract-ts-config\": \"dependency-cruiser/src/config-utl/extract-ts-config\",\n      \"dependency-cruiser/config-utl/extract-webpack-resolve-config\": \"dependency-cruiser/src/config-utl/extract-webpack-resolve-config\"\n    }\n  },\n  \"dependencies\": {\n    \"dependency-cruiser\": \"^12.10.0\",\n    \"fs\": \"^0.0.1-security\",\n    \"jsdom\": \"^21.1.0\",\n    \"vue-docgen-api\": \"^4.60.0\"\n  },\n  \"devDependencies\": {\n    \"@types/jsdom\": \"^21.1.0\",\n    \"@vuensight/types\": \"^0.1.0\"\n  }\n}\n"
  },
  {
    "path": "packages/parser/src/index.ts",
    "content": "import { VueComponent } from  '@vuensight/types';\n\nimport { findDependencies } from './vue/dependencies';\nimport { analyzeComponents, analyzeCommunicationChannelUsage } from './vue/analyzer';\n\nexport const parse = async (\n    directory: string,\n    fileType = 'vue',\n    webpackConfigPath?: string,\n    tsConfigPath?: string\n): Promise<VueComponent[]> => {\n  const modules = findDependencies(directory, fileType, webpackConfigPath, tsConfigPath);\n  if (!modules) return new Array<VueComponent>();\n\n  const components: VueComponent[] = await analyzeComponents(modules);\n  return analyzeCommunicationChannelUsage(components);\n};\n"
  },
  {
    "path": "packages/parser/src/utils/files.test.ts",
    "content": "import { getFileNameFromPath } from './files';\n\ndescribe('files', () => {\n  describe('getFileNameFromPath', () => {\n    it('should extract the filename from the path', () => {\n      expect(getFileNameFromPath('src/test/asdf/TestFile.vue')).toBe('TestFile.vue');\n    });\n\n    it('should extract the filename from the path', () => {\n      expect(getFileNameFromPath('src\\\\test\\\\asdf\\\\TestFile.vue')).toBe('TestFile.vue');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/parser/src/utils/files.ts",
    "content": "export const getFileNameFromPath = (path: string): string => {\n  const lastDirectoryIndex = path.lastIndexOf('\\\\') !== -1 ? path.lastIndexOf('\\\\') : path.lastIndexOf('/');\n  return path.substring(lastDirectoryIndex + 1, path.length);\n};\n"
  },
  {
    "path": "packages/parser/src/utils/kababize.ts",
    "content": "export const kebabize = (str: string):string => str.split('')\n  .map((letter, idx) => (letter.toUpperCase() === letter\n    ? `${idx !== 0 ? '-' : ''}${letter.toLowerCase()}`\n    : letter)).join('');\n\nexport default {\n  kebabize,\n};\n"
  },
  {
    "path": "packages/parser/src/utils/kebabize.test.ts",
    "content": "import { kebabize } from './kababize';\n\ndescribe('kebabize', () => {\n  it('should convert the string to kebab-style', () => {\n    expect(kebabize('FooBar')).toBe('foo-bar');\n  });\n});\n"
  },
  {
    "path": "packages/parser/src/utils/vue.test.ts",
    "content": "import {\n  extractScriptContent,\n  findTemplate,\n  getComponentImportName,\n  getTemplateContent\n} from './vue';\n\nconst TEST_FILE = '<template>Hello</template><script>const foo = 3;</script><style></style>';\nconst TEST_FILE_WITHOUT_TEMPLATE = '<script>const foo = 3;</script><style></style>';\n\nconst makeTestFileWithScript = (script: string) => `\n<template><div>Test File</div></template>\n<script>${script}</script>\n<style>div { color: red; }</style>\n`;\n\ndescribe('vue', () => {\n  describe('findTemplate', () => {\n    it('should find the template string in the file', () => {\n      expect(findTemplate(TEST_FILE)).toBe('<template>Hello</template>');\n    });\n\n    it('should not find the template string in the file when there is none', () => {\n      expect(findTemplate(TEST_FILE_WITHOUT_TEMPLATE)).toBe(null);\n    });\n  });\n\n  describe('getTemplateContent', () => {\n    it('should return the content of the template without the template tag', () => {\n      expect(getTemplateContent(TEST_FILE)).toBe('Hello');\n    });\n\n    it('should return null when there is no template', () => {\n      expect(getTemplateContent(TEST_FILE_WITHOUT_TEMPLATE)).toBe(null);\n    });\n  });\n\n  describe('extractScriptContent', () => {\n    it('should extract the script content from the given file', () => {\n      const script = `const test = 3`;\n      const testFile = makeTestFileWithScript(script);\n\n      expect(extractScriptContent(testFile)).toBe(script);\n    });\n  });\n\n  describe('getComponentImportName', () => {\n    it('should find the import name of a component in a js script', () => {\n      const script = `import Button from '@/components/base/BaseButton';`;\n      const testFile = makeTestFileWithScript(script);\n\n      expect(getComponentImportName(testFile, 'BaseButton')).toBe('Button');\n    });\n\n    it('should not find the import name of a component if it is not imported', () => {\n      const script = `import Test from '@/components/base/Test';`;\n      const testFile = makeTestFileWithScript(script);\n\n      expect(getComponentImportName(testFile, 'BaseButton')).toBe(null);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/parser/src/utils/vue.ts",
    "content": "export const findTemplate = (fileContent: string): string | null => {\n  const templateBody = fileContent.match(/(?<template><template>[\\s\\S]*<\\/template>)/u);\n  return templateBody?.groups?.template || null;\n};\n\nexport const getTemplateContent = (fileContent: string): string | null => {\n  const template = findTemplate(fileContent);\n  // remove <template> and </template> tags at the beginning and the end\n  return template ? template.substring(10, template.length - 11) : null;\n};\n\nexport const extractScriptContent = (fileContent: string): string => {\n  const scriptStartString = '<script>';\n  const scriptStart = fileContent.search(scriptStartString);\n  const scriptEnd = fileContent.search('</script>');\n  return fileContent.slice(scriptStart + scriptStartString.length, scriptEnd);\n};\n\nexport const getComponentImportName = (fileContent: string, fileName: string) => {\n  const script = extractScriptContent(fileContent);\n  let name: string | null = null;\n  script.split(/\\r?\\n/).forEach((line: string) =>  {\n    if (line.includes('import') && line.includes(`/${fileName}`)) name = line.split(' ')[1];\n  });\n  return name;\n};\n"
  },
  {
    "path": "packages/parser/src/vue/analyzer.ts",
    "content": "import { readFileSync } from 'fs';\nimport { normalize } from 'path';\nimport { IModule } from 'dependency-cruiser';\n\nimport { VueComponent } from '@vuensight/types';\n\nimport { getFileNameFromPath } from '../utils/files';\nimport { formatDependencies } from './dependencies';\nimport { parseComponentFile, getDependentWithUsedChannelsAnalysis } from './communication-channels';\n\nexport const findComponentData = (components: VueComponent[], fullPath: string)\n  : VueComponent | undefined => components.find((component) => component.fullPath === fullPath);\n\nexport const findComponentDataByString = (components: VueComponent[], string: string)\n    : VueComponent | undefined => components.find((component) => component.fullPath.includes(string));\n\nexport const analyzeComponents = async (modules: IModule[]): Promise<VueComponent[]> => {\n  return await Promise.all(modules.map(async (module) => {\n    const fullPath = normalize(module.source);\n    const fileName = getFileNameFromPath(fullPath);\n    const [name, fileType] = fileName.split('.');\n\n    let fileContent = '';\n    try {\n      fileContent = readFileSync(fullPath, {encoding: 'utf-8'});\n    } catch (e) {\n      console.error(e);\n    }\n    const dependencies = formatDependencies(module.dependencies);\n    const parsedComponentData = fileType === 'vue' ? await parseComponentFile(fullPath) : null;\n\n    return {\n      name: parsedComponentData?.name && parsedComponentData?.name !== name ?  parsedComponentData?.name : name,\n      fullPath,\n      fileContent,\n      fileName,\n      fileType,\n      props: parsedComponentData?.props ?? [],\n      events: parsedComponentData?.events ?? [],\n      slots: parsedComponentData?.slots ?? [],\n      dependencies,\n      dependents: module.dependents.map(dependent => ({\n        fullPath: dependent,\n        name: '',\n        usedProps: [],\n        usedSlots: [],\n        usedEvents: [],\n      })),\n    };\n  }));\n};\n\nexport const analyzeCommunicationChannelUsage = (components: VueComponent[]): VueComponent[] => {\n  return components.map((component) => {\n    const dependents = component.dependents.map((dependent) => {\n      const dependentData = findComponentData(components, normalize(dependent.fullPath));\n      if (dependentData && dependentData.fileType === 'vue') {\n          return getDependentWithUsedChannelsAnalysis(dependentData, component);\n      }\n      return dependent;\n    });\n    return {\n      ...component,\n      dependents,\n    };\n  });\n};\n"
  },
  {
    "path": "packages/parser/src/vue/communication-channels.test.ts",
    "content": "import { JSDOM } from 'jsdom';\n\nimport {\n    findDependencyInstancesInTemplate,\n    isPropUsed,\n    isEventUsed,\n    isSlotUsed,\n    getUsedChannels\n} from './communication-channels';\n\nconst createComponent = (template: string) => {\n    const fragment = JSDOM.fragment(template);\n    return fragment.querySelector('ComponentName');\n};\n\ndescribe('parser', () => {\n    describe('findDependencyInstancesInTemplate', () => {\n        it('should find a component usage in camel case', function () {\n            const template = '<div><TestComponent>test</TestComponent></div>';\n            expect(findDependencyInstancesInTemplate(template, 'TestComponent')).toHaveLength(1);\n        });\n\n        it('should find a component usage in kebab case', function () {\n            const template = '<div><test-component>test</test-component></div>';\n            expect(findDependencyInstancesInTemplate(template, 'TestComponent')).toHaveLength(1);\n        });\n\n        it('should find multiple usages of a component', function () {\n            const template = '<div><test-component>test</test-component><TestComponent>test</TestComponent></div>';\n            expect(findDependencyInstancesInTemplate(template, 'TestComponent')).toHaveLength(2);\n        });\n\n        it('should find a component usage inside of a template tag', function () {\n            const template = `\n                <template>\n                    <template v-if=\"true\"><test-component>test</test-component></template>\n                </template>\n            `;\n            expect(findDependencyInstancesInTemplate(template, 'TestComponent')).toHaveLength(1);\n        });\n    });\n    \n    describe('isPropUsed', () => {\n        it('should find the prop in kebab syntax', () => {\n            const prop = { name: 'TestProp', type: { name: 'String' }, default: 'Test', required: false };\n            const element = createComponent(`<ComponentName test-prop=\"foo\"/>`);\n\n            const isUsed = element ? isPropUsed(element, prop) : false;\n            expect(isUsed).toBe(true);\n        });\n\n        it('should find the prop in kebab syntax when it is bound to data', () => {\n            const prop = { name: 'TestProp', type: { name: 'String' }, default: 'Test', required: false };\n            const element = createComponent(`<ComponentName :test-prop=\"foo\"/>`);\n\n            const isUsed = element ? isPropUsed(element, prop) : false;\n            expect(isUsed).toBe(true);\n        });\n\n        it('should find the prop in camel-case syntax', () => {\n            const prop = { name: 'TestProp', type: { name: 'String' }, default: 'Test', required: false };\n            const element = createComponent(`<ComponentName TestProp=\"foo\"/>`);\n\n            const isUsed = element ? isPropUsed(element, prop) : false;\n            expect(isUsed).toBe(true);\n        });\n\n        it('should find the prop in camel-case syntax when it is bound to data', () => {\n            const prop = { name: 'TestProp', type: { name: 'String' }, default: 'Test', required: false };\n            const element = createComponent(`<ComponentName :TestProp=\"foo\"/>`);\n\n            const isUsed = element ? isPropUsed(element, prop) : false;\n            expect(isUsed).toBe(true);\n        });\n    });\n\n    describe('isEventUsed', () => {\n        it('should find the event when the `@` syntax is used', () => {\n            const event = { name: 'test-event', isSync: false };\n            const element = createComponent(`<ComponentName @test-event=\"foo\"/>`);\n\n            const isUsed = element ? isEventUsed(element, event) : false;\n            expect(isUsed).toBe(true);\n        });\n\n        it('should find the event when the `v-on:` syntax is used', () => {\n            const event = { name: 'test-event', isSync: false };\n            const element = createComponent(`<ComponentName v-on:test-event=\"foo\"/>`);\n\n            const isUsed = element ? isEventUsed(element, event) : false;\n            expect(isUsed).toBe(true);\n        });\n\n        it('should not find the event when it is not used', () => {\n            const event = { name: 'test-event', isSync: false };\n            const element = createComponent(`<ComponentName v-on:other-event=\"foo\"/>`);\n\n            const isUsed = element ? isEventUsed(element, event) : false;\n            expect(isUsed).toBe(false);\n        });\n    });\n\n    describe('isSlotUsed', () => {\n        it('should find the slot when the `#` syntax is used', () => {\n            const slot = { name: 'header' };\n            const element = createComponent(\n                `<ComponentName @test-event=\"foo\"><template #header>Headline</template></ComponentName>`\n            );\n\n            const isUsed = element ? isSlotUsed(element, slot) : false;\n            expect(isUsed).toBe(true);\n        });\n\n        it('should find the slot when the `v-slot` syntax is used', () => {\n            const slot = { name: 'header' };\n            const element = createComponent(\n                `<ComponentName @test-event=\"foo\"><template v-slot:header>Headline</template></ComponentName>`\n            );\n\n            const isUsed = element ? isSlotUsed(element, slot) : false;\n            expect(isUsed).toBe(true);\n        });\n\n        it('should not find the slot when it is not used', () => {\n            const slot = { name: 'header' };\n            const element = createComponent(\n                `<ComponentName @test-event=\"foo\">Headline</ComponentName>`\n            );\n\n            const isUsed = element ? isSlotUsed(element, slot) : true;\n            expect(isUsed).toBe(false);\n        });\n    });\n\n    describe('getUsedChannel', () => {\n        it('should return the indices of the used events when they are used once', () => {\n            const events = [{ name: 'test-event', isSync: false }, { name: 'test-event2' }, ];\n            const element = createComponent(`<ComponentName @test-event=\"foo\"/>`);\n\n            const usedEvents = element ? getUsedChannels([element], events, isEventUsed) : false;\n            expect(usedEvents).toStrictEqual([0]);\n        });\n\n        it('should return the index of the used events once when they are used multiple times', () => {\n            const events = [{ name: 'test-event', isSync: false }, { name: 'TestEventTwo' }, ];\n            const element = createComponent(\n        `<ComponentName @TestEventTwo=\"foo\"/>\n                 <ComponentName @TestEventTwo=\"foo\"/>\n                 <ComponentName @TestEventTwo=\"foo\"/>`\n            );\n            const usedEvents = element ? getUsedChannels([element], events, isEventUsed) : false;\n            expect(usedEvents).toStrictEqual([1]);\n        });\n\n        it('should return an empty array when the event is not used', () => {\n            const events = [{ name: 'test-event', isSync: false }, { name: 'TestEventTwo' }, ];\n            const element = createComponent(`<ComponentName />`);\n            const usedEvents = element ? getUsedChannels([element], events, isEventUsed) : false;\n            expect(usedEvents).toStrictEqual([]);\n        });\n    });\n});\n"
  },
  {
    "path": "packages/parser/src/vue/communication-channels.ts",
    "content": "import { parse, PropDescriptor } from 'vue-docgen-api';\nimport { JSDOM } from 'jsdom';\nimport { Dependent, Event, Prop, Slot, VueComponent } from  '@vuensight/types';\n\nimport { getComponentImportName } from '../utils/vue';\nimport { getTemplateContent } from '../utils/vue';\nimport { kebabize } from '../utils/kababize';\n\nexport const findDependencyInstancesInTemplate = (template: string, name: string): Element[] => {\n  const templateWithoutTemplateTags = template.replace(/template/g, 'temp-tag');\n  const fragment = JSDOM.fragment(templateWithoutTemplateTags);\n  const dependencyUsagesCamelCase = Array.from(fragment.querySelectorAll(name));\n  const dependencyUsagesKebabCase = Array.from(fragment.querySelectorAll(kebabize(name)));\n  return [...dependencyUsagesCamelCase, ...dependencyUsagesKebabCase];\n};\n\nexport const parseComponentFile = async (filePath: string): Promise<Partial<VueComponent> | null> => {\n try {\n    const { displayName: name, props, events, slots } = await parse(filePath);\n    return { name, props: props && formatProps(props), events, slots };\n  } catch (e) {\n    console.error(`Something went wrong while parsing the component: ${filePath}`, e);\n  }\n  return null;\n};\n\nconst formatProps = (props: PropDescriptor[]):Prop[] => {\n  return props.map((prop) => ({\n    ...prop,\n    default: prop?.defaultValue?.value,\n  }));\n};\n\nexport const isPropUsed = (template: Element, prop: Prop): boolean => {\n  const propFormats = [prop.name, `:${prop.name}`, `:${kebabize(prop.name)}`, kebabize(prop.name)];\n  let isUsed = false;\n  propFormats.forEach((format) => {\n    if (!isUsed) isUsed = Boolean(template.attributes.getNamedItem(format));\n  });\n  return isUsed;\n};\n\nexport const isEventUsed = (template: Element, event: Event): boolean => {\n  const eventFormat = [`@${event.name}`, `v-on:${event.name}`];\n  let isUsed = false;\n  eventFormat.forEach((format) => (isUsed = isUsed || Boolean(template.attributes.getNamedItem(format))));\n  return isUsed;\n};\n\n\nexport const isSlotUsed = (template: Element, slot: Slot): boolean => {\n  const slotFormat = [`#${slot.name}`, `v-slot:${slot.name}`];\n  let isUsed = false;\n  slotFormat.forEach((format) => (isUsed = isUsed || Boolean(template.innerHTML.includes(format))));\n  return isUsed;\n};\n\nexport const getUsedChannels = <Channel>(\n    dependencyInstances: Element[],\n    channels: Channel[],\n    validator: (instance: Element, channel: Channel) => boolean\n): number[] => {\n  const usedChannels = new Set<number>();\n  channels.forEach((channel, index) => {\n    dependencyInstances.forEach((dependencyUsage) => validator(dependencyUsage, channel) && usedChannels.add(index));\n  });\n  return [...usedChannels];\n};\n\nexport const getDependentWithUsedChannelsAnalysis = (\n    { fullPath: dependentFullPath, name: dependentName, fileContent: dependentFilecontent }: VueComponent,\n    { name, props, events, slots }: VueComponent\n): Dependent => {\n  const template = getTemplateContent(dependentFilecontent);\n  if (!template) return {\n    fullPath: dependentFullPath,\n    name: dependentName,\n    usedProps: [],\n    usedEvents: [],\n    usedSlots: []\n  };\n  let dependencyInstances = findDependencyInstancesInTemplate(template, name);\n  if (dependencyInstances.length === 0)  {\n    const importName = getComponentImportName(dependentFilecontent, name);\n    dependencyInstances = importName ? findDependencyInstancesInTemplate(template, importName) : dependencyInstances;\n  }\n  return {\n    fullPath: dependentFullPath,\n    name: dependentName,\n    usedProps: dependencyInstances ? getUsedChannels(dependencyInstances, props, isPropUsed) : [],\n    usedEvents: dependencyInstances ? getUsedChannels(dependencyInstances, events, isEventUsed) : [],\n    usedSlots: dependencyInstances ?  getUsedChannels(dependencyInstances, slots, isSlotUsed) : []\n  };\n};\n"
  },
  {
    "path": "packages/parser/src/vue/dependencies.ts",
    "content": "import { normalize } from 'path';\nimport {\n  cruise, IDependency, IReporterOutput, IModule\n} from 'dependency-cruiser';\n\n// extract features of dependency-cruiser are still experimental and therefore not exported by default.\n// See: https://github.com/sverweij/dependency-cruiser/blob/develop/doc/api.md#utility-functions\n// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-ignore\nimport extractTSConfig from 'dependency-cruiser/config-utl/extract-ts-config';\n// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-ignore\nimport extractWebpackResolveConfig from 'dependency-cruiser/config-utl/extract-webpack-resolve-config';\n\nimport { Dependency } from  '@vuensight/types';\n\nexport const findDependencies = (\n    directory = 'src',\n    fileType?: string,\n    webpackConfigPath?: string,\n    tsConfigPath?: string\n):IModule[] | null => {\n  let cruiseResult: IReporterOutput | null = null;\n  const webpackResolveConfig = webpackConfigPath ? extractWebpackResolveConfig(webpackConfigPath) : null;\n  const tsConfig = tsConfigPath ? extractTSConfig(tsConfigPath) : null;\n  try {\n    cruiseResult = cruise(\n        [directory],\n        {\n          includeOnly: `^.*.(${fileType})$`,\n          exclude: ['node_modules'],\n          doNotFollow: {\n             path: 'node_modules',\n             dependencyTypes: [\n                'npm',\n                'npm-dev',\n                'npm-optional',\n                'npm-peer',\n                'npm-bundled',\n                'npm-no-pkg',\n             ],\n          },\n          forceDeriveDependents: true,\n        },\n        webpackResolveConfig,\n        tsConfig\n    );\n  } catch (error) {\n    console.error('Something went wrong cruising the project ', error);\n  }\n  if (cruiseResult && typeof cruiseResult?.output !== 'string') return cruiseResult?.output?.modules;\n  return null;\n};\n\nexport const formatDependencies = (dependencies: IDependency[]): Dependency[] => {\n  return dependencies.map((dependency) => ({\n      fullPath: normalize(dependency.resolved),\n  }));\n};\n"
  },
  {
    "path": "packages/parser/test/parsing-process.test.ts",
    "content": "import { parse } from '../src';\nimport { normalize } from 'path';\n\ndescribe('parse', () => {\n    it('should return data about dependencies and communication channels when parsing a Vue project', async () => {\n        const expectedParseResult = [{\n            name: 'Child',\n            fullPath: normalize('test/project/Child.vue'),\n            fileName: 'Child.vue',\n            fileType: 'vue',\n            fileContent: expect.any(String),\n            events: [{ name: 'selected' }],\n            props: [{\n                name: 'title',\n                type: {\n                    name: 'string'\n                },\n                default: \"'Hello'\",\n                defaultValue: {\n                    func: false,\n                    value: \"'Hello'\"\n                }\n            }],\n            slots: [{ name: 'header' }],\n            dependencies: [],\n            dependents: [{\n                fullPath: normalize('test/project/Parent.vue'),\n                name: \"Parent\",\n                usedProps: [0],\n                usedEvents: [0],\n                usedSlots: [0],\n            }],\n        }, {\n            name: 'Parent',\n            fullPath: normalize('test/project/Parent.vue'),\n            fileName: 'Parent.vue',\n            fileType: 'vue',\n            events: [],\n            props: [],\n            slots: [],\n            fileContent: expect.any(String),\n            dependencies: [{\n                fullPath: normalize('test/project/Child.vue'),\n\n            }],\n            dependents: [],\n        }];\n        const parseResult = await parse('test/project');\n\n        expect(parseResult).toEqual(expectedParseResult);\n    });\n});\n"
  },
  {
    "path": "packages/parser/test/project/Child.vue",
    "content": "<template>\n    <div>\n        <slot name=\"header\" />\n        Test {{ title }}\n        <button @click=\"$emit('selected')\">Select</button>\n    </div>\n</template>\n\n<script>\nexport default {\n    name: 'Child',\n    props: {\n        title: {\n            default: 'Hello',\n            type: String\n        }\n    }\n};\n</script>\n\n<style scoped>\n\n</style>"
  },
  {
    "path": "packages/parser/test/project/Parent.vue",
    "content": "<template>\n    <div>\n        <Child title=\"test\" @selected=\"select\">\n            <template #header>Important Information</template>\n        </Child>\n    </div>\n</template>\n\n<script>\nimport Child from './Child';\nexport default {\n    name: 'Parent',\n    components: { Child },\n    methods: {\n        select: () => {},\n    }\n};\n</script>\n\n<style scoped>\n\n</style>"
  },
  {
    "path": "packages/parser/tsconfig.pkg.json",
    "content": "{\n  \"extends\": \"../../tsconfig.build.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./dist/\"\n  },\n  \"include\": [\n    \"./src/*.ts\"\n  ]\n}\n"
  },
  {
    "path": "packages/types/index.d.ts",
    "content": "export interface Mixin {\n  name: string\n  path: string\n}\n\nexport type Prop = {\n  name: string,\n  type?: { name: string; func?: boolean | undefined; } | undefined,\n  required?: boolean,\n  default?: string,\n  mixin?: Mixin,\n}\n\nexport type Event = {\n  name: string,\n  isSync?: boolean,\n  mixin?: Mixin,\n}\n\nexport type Slot = {\n  name: string,\n}\n\nexport type Dependency = {\n  fullPath: string,\n}\n\nexport type Dependent = {\n  fullPath: string,\n  name: string,\n  usedProps: number[], // indexOf used prop\n  usedEvents: number[], // indexOf used event\n  usedSlots: number[], // indexOf used event\n}\n\nexport type VueComponent = {\n  name: string,\n  fullPath: string,\n  fileName: string,\n  fileType: string,\n  fileContent: string,\n  props: Prop[],\n  events: Event[],\n  slots: Slot[],\n  dependencies: Dependency[],\n  dependents: Dependent[],\n}\n"
  },
  {
    "path": "packages/types/package.json",
    "content": "{\n  \"name\": \"@vuensight/types\",\n  \"version\": \"0.1.0\",\n  \"description\": \"The shared types package for @vuensight.\",\n  \"main\": \"index.d.ts\",\n  \"scripts\": {\n    \"build\": \"echo 'no build for types package'\",\n    \"test\": \"echo 'no tests for types package'\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/martinascharrer/vuensight.git\"\n  },\n  \"keywords\": [\n    \"Vue.js\",\n    \"types\",\n    \"typescript\"\n  ],\n  \"author\": \"Martina Scharrer\",\n  \"license\": \"(MIT)\",\n  \"bugs\": {\n    \"url\": \"https://github.com/martinascharrer/vuensight/issues\"\n  },\n  \"homepage\": \"https://github.com/martinascharrer/vuensight#readme\"\n}\n"
  },
  {
    "path": "tsconfig.build.json",
    "content": "{\n  \"exclude\": [\n    \"**/*.test.ts\",\n    \"**/*.stub.ts\",\n    \"node_modules\",\n    \"dist\"\n  ],\n  \"compilerOptions\": {\n    \"incremental\": true,\n    \"target\": \"es2019\",\n    \"module\": \"commonjs\",\n    \"declaration\": true,\n    \"sourceMap\": true,\n    \"strict\": true,\n    \"moduleResolution\": \"node\",\n    \"esModuleInterop\": true,\n    \"skipLibCheck\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"paths\": {\n      \"@vuensight/*\": [\"./packages/*/\"]\n    }\n  },\n}"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"extends\": \"./tsconfig.build.json\",\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\"\n  },\n  \"include\": [\n    \"./packages/*/**.ts\"\n  ]\n}"
  }
]