[
  {
    "path": ".changeset/cold-wolves-occur.md",
    "content": "---\n\"webpack-dashboard\": patch\n---\n\n'#359 update node version to 18 in github actions workflows and github actions versions'\n"
  },
  {
    "path": ".changeset/config.json",
    "content": "{\n\t\"$schema\": \"https://unpkg.com/@changesets/config@2.3.0/schema.json\",\n\t\"changelog\": [\n\t\t\"@svitejs/changesets-changelog-github-compact\",\n\t\t{\n\t\t\t\"repo\": \"FormidableLabs/webpack-dashboard\"\n\t\t}\n\t],\n\t\"access\": \"public\",\n\t\"baseBranch\": \"master\"\n}"
  },
  {
    "path": ".editorconfig",
    "content": "# editorconfig.org\nroot = true\n\n[*]\nindent_style = space\nindent_size = 2\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = true\ninsert_final_newline = true\nmax_line_length = 100\n\n[*.md]\ntrim_trailing_whitespace = false"
  },
  {
    "path": ".eslintignore",
    "content": "dist-*"
  },
  {
    "path": ".eslintrc.json",
    "content": "{\n  \"extends\": [\"formidable/configurations/es6-node\", \"plugin:prettier/recommended\"],\n  \"rules\": {\n    \"func-style\": \"off\",\n    \"arrow-parens\": [\"error\", \"as-needed\"]\n  }\n}\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\non:\n  push:\n    branches:\n      - master\n  pull_request:\n    branches:\n      - master\n\njobs:\n  build:\n\n    runs-on: ${{ matrix.os }}\n\n    strategy:\n      matrix:\n        os: [ubuntu-latest, windows-latest]\n        node-version: [18.x]\n\n    steps:\n    # Setup\n    - uses: actions/checkout@v4\n    - name: Use Node.js ${{ matrix.node-version }}\n      uses: actions/setup-node@v4\n      with:\n        cache: \"yarn\"\n        node-version: ${{ matrix.node-version }}\n\n    # Installation\n    - run: yarn --version\n    - run: yarn install --frozen-lockfile\n      env:\n        CI: true\n\n    # CI\n    - run: yarn check-ci\n    # Test\n    - run: yarn test\n    # Code coverage\n    - run: yarn codecov\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Release\non:\n  push:\n    branches:\n      - master\njobs:\n  release:\n    name: Release\n    runs-on: ubuntu-latest\n    permissions:\n      contents: write\n      id-token: write\n      issues: write\n      repository-projects: write\n      deployments: write\n      packages: write\n      pull-requests: write\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-node@v4\n        with:\n          cache: \"yarn\"\n          node-version: 18\n      - name: Install dependencies\n        run: yarn install --frozen-lockfile\n\n      - name: Check CI\n        run: yarn check-ci\n      - name: Unit Tests\n        run: yarn test\n\n      - name: PR or Publish\n        id: changesets\n        uses: changesets/action@v1\n        with:\n          version: yarn changeset version\n          publish: yarn changeset publish\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}\n"
  },
  {
    "path": ".gitignore",
    "content": "# dependencies\n/node_modules/\n\n# misc\n.DS_Store\nnpm-debug.log*\n.nyc_output\n.coverage\nyarn-error.log\npackage-lock.json\ndist-*\n.vscode\n.lankrc.js\n"
  },
  {
    "path": ".mocharc.yml",
    "content": "require: \"./test/setup.js\"\nrecursive: true\n"
  },
  {
    "path": ".npmignore",
    "content": "/*\n!/bin\n!/dashboard\n!/plugin\n!/utils\n!LICENSE\n!CHANGELOG.md\n!README.md\n!package.json\n!index.js\n!index.d.ts\n"
  },
  {
    "path": ".nycrc",
    "content": "{\n  \"reporter\": [\n    \"html\",\n    \"lcov\",\n    \"text\"\n  ],\n  \"report-dir\": \"./.coverage\"\n}\n"
  },
  {
    "path": ".prettierignore",
    "content": "examples/**/dist-*/*.js\nexamples/**/dist-*/*.json"
  },
  {
    "path": ".prettierrc",
    "content": "{\n  \"arrowParens\": \"avoid\",\n  \"trailingComma\": \"none\",\n  \"endOfLine\": \"auto\"\n}\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\n## 3.3.8\n\n### Patch Changes\n\n- Adding GitHub release workflow ([#354](https://github.com/FormidableLabs/webpack-dashboard/pull/354))\n\n## 3.3.7\n\n- Bug: Move plugin types and update to webpack v5. [#324](https://github.com/FormidableLabs/webpack-dashboard/issues/324)\n\n## 3.3.6\n\n- Bug: Allow socket messages to be null. [#335](https://github.com/FormidableLabs/webpack-dashboard/issues/335), [#336](https://github.com/FormidableLabs/webpack-dashboard/issues/336)\n\n## [3.3.5] - 2021-07-12\n\n- Chore: Update dependencies. [#333](https://github.com/FormidableLabs/webpack-dashboard/issues/333)\n- Coverage: Add CodeCov stats. [#206](https://github.com/FormidableLabs/webpack-dashboard/issues/206)\n- CI: Update Node matrix to 12/14/16.\n\n## [3.3.4] - 2021-07-12\n\n- Chore: Refactor internal stats consumption to perform `inspectpack` analysis in the main thread, without using `main` streams.\n- Chore: Refactor internal handler in plugin to always be a wrapped function so that we can't accidentally have asynchronous code call the handler function after it is removed / nulled.\n- Bugfix: Add message counting delayed cleanup in plugin to allow messages to drain in Dashboard. Fixes [#294](https://github.com/FormidableLabs/webpack-dashboard/issues/294).\n\n## [3.3.3] - 2021-05-05\n\n- Security: Update `socket.io` version to get rid of vulnerable `xmlhttprequest-ssl` package. Included in: https://github.com/FormidableLabs/webpack-dashboard/pull/325 by @texpert.\n\n## [3.3.2] - 2021-05-05\n\n- Empty publish.\n\n## [3.3.1] - 2021-01-29\n\n- Bugfix: Ensure `Status` is properly updating and reaches completion. Fixes #321\n\n## [3.3.0] - 2021-01-21\n\n- Add `webpack@5` support. Closes #316\n- Bugfix: `webpack@5` warning message conflict. Fixes #314\n- Update various production dependencies.\n\n## [3.2.1] - 2020-08-24\n\n- Add missing dependency on `chalk`. Included in: https://github.com/FormidableLabs/webpack-dashboard/pull/309 by @am-a.\n\n## [3.2.0] - 2019-09-08\n\n- Add left / right navigation keys to assets in Modules and Problems screens. Included in: https://github.com/FormidableLabs/webpack-dashboard/pull/288 by @wapgear.\n\n## [3.1.0] - 2019-08-27\n\n- Add `DashboardPlugin({ includeAssets: [ \"stringPrefix\", /regexObj/ ] })` Webpack plugin filtering option.\n- Add `webpack-dashboard --include-assets stringPrefix1 -a stringPrefix2` CLI filtering option.\n- Change `\"mode\"` SocketIO event to `\"options\"` as it now passes both `minimal` and `includeAssets` from CLI to the Webpack plugin.\n- Fix unit tests that incorrectly relied on `.complete()` for `most` observables.\n- Add additional `examples` fixture for development.\n\n## [3.0.7] - 2019-05-15\n\n### Features\n\n- Very minor path normalization for displaying modules paths on Windows and Prettier fixes for Windows. Included in: https://github.com/FormidableLabs/webpack-dashboard/pull/284 by @ryan-roemer.\n- Add AppVeyor for Windows builds in CI. Included in: https://github.com/FormidableLabs/webpack-dashboard/pull/284 by @ryan-roemer.\n\n### Migration Instructions\n\nNo changes required to start using v3.0.7 🎉.\n\n## [3.0.6] - 2019-05-09\n\n### Features\n\n- Prevent dashboard from spawning its own console for the child process on Windows. Closes #212. Included in: https://github.com/FormidableLabs/webpack-dashboard/pull/284 by @snack-able.\n\n### Migration Instructions\n\nNo changes required to start using v3.0.6 🎉.\n\n## [3.0.5] - 2019-04-24\n\n### Features\n\n- Use `npm-run-all` as task runner for `package.json` scripts. Included in: https://github.com/FormidableLabs/webpack-dashboard/pull/283.\n- Use `test` in lieu of `test-summary` for `nyc` coverage reporting on command line. Included in: https://github.com/FormidableLabs/webpack-dashboard/pull/283.\n\n### Security\n\n- Address `handlebars` security vulnerability. Included in: https://github.com/FormidableLabs/webpack-dashboard/pull/282 by @juliusl.\n- Address additional security vulnerabilities in `js-yaml`. Included in: https://github.com/FormidableLabs/webpack-dashboard/pull/283.\n\n### Migration Instructions\n\nNo changes required to start using v3.0.5 🎉.\n\n## [3.0.4] - 2019-04-24 [DEPRECATED]\n\n`v3.0.4` was an erroneous publish.\n\n## [3.0.3] - 2019-04-18\n\n### Bugs\n\n- **Socket.io disconnects / large stats object size**: Dramatically reduce the size of the webpack stats object being sent from client (webpack plugin) to server (CLI). Add client error/disconnect information for better future debugging. Original issue: https://github.com/FormidableLabs/inspectpack/issues/279 and fix: https://github.com/FormidableLabs/inspectpack/pull/281\n\n### Migration Instructions\n\nNo changes required to start using v3.0.3 🎉.\n\n## [3.0.2] - 2019-03-28\n\n### Features\n\n- Upgrade `inspectpack` dependency to handle `null` chunks. Original issue: https://github.com/FormidableLabs/inspectpack/issues/110 and upstream fix: https://github.com/FormidableLabs/inspectpack/pull/111\n\n### Migration Instructions\n\nNo changes required to start using v3.0.2 🎉.\n\n## [3.0.1] - 2019-03-26\n\n### Features\n\n- Use `process.kill` with `SIGINT` to gracefully exit the dashboard process. Included in: https://github.com/FormidableLabs/webpack-dashboard/pull/277 by @joakimbeng.\n- Update dependencies to address security warnings. Included in: https://github.com/FormidableLabs/webpack-dashboard/pull/275 by @stereobooster.\n\n### Migration Instructions\n\nNo changes required to start using v3.0.1 🎉. We do recommend adopting this patch as soon as possible to get the security upgrades.\n\n## [3.0.0] - 2019-02-14\n\n### Features\n\n- Migrated from using `blessed` to [`neo-blessed`](https://github.com/embark-framework/neo-blessed) as the underlying terminal renderer. `neo-blessed` is a maintained fork of `blessed` and brings in some nice fixes for us. Included in: https://github.com/FormidableLabs/webpack-dashboard/pull/270\n- Added Prettier to the codebase 🎉 Included in: https://github.com/FormidableLabs/webpack-dashboard/pull/270\n\n### Docs\n\n- Added a warning about deprecation of Node 6 support. Included in: https://github.com/FormidableLabs/webpack-dashboard/pull/270\n\n### Migration Instructions\n\nWith this release we are dropping support for Node 6 altogether. `neo-blessed` requires Node [>= 8.0.0](https://github.com/embark-framework/neo-blessed/blob/master/package.json#L38), meaning all users of the dashboard will need to run it using Node 8 or above. Previous versions of `webpack-dashboard` are still compatible with Node 6.\n\n## [2.1.0] - 2019-01-29\n\n### Features\n\n- Added a few example setups to make the local development experience with `webpack-dashboard` a lot easier. Users can now clone the repo, `yarn`, and `yarn dev` to get running. Included in: https://github.com/FormidableLabs/webpack-dashboard/pull/267\n- Migrated to `inspectpack@4`. Included in: https://github.com/FormidableLabs/webpack-dashboard/pull/263\n- Added TypeScript defitions. Included in: https://github.com/FormidableLabs/webpack-dashboard/pull/269\n\n### Tests\n\n- Added regression tests to fix an unknown import issue for our `format-*` utils. Included in: https://github.com/FormidableLabs/webpack-dashboard/pull/263\n- Added tests for all `Dashboard` methods. Included in: https://github.com/FormidableLabs/webpack-dashboard/pull/263\n\n### Docs\n\n- Added a Local Development section to the README to make it easier to contribute to `webpack-dashboard`. Included in: https://github.com/FormidableLabs/webpack-dashboard/pull/267\n\n### Migration Instructions\n\nNo changes required to start using v2.1.0 🎉\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributing to Webpack-Dashboard\n\n## Contributor Covenant Code of Conduct\n\n### Our Pledge\n\nIn the interest of fostering an open and welcoming environment, we as\ncontributors and maintainers pledge to making participation in our project and\nour community a harassment-free experience for everyone, regardless of age, body\nsize, disability, ethnicity, gender identity and expression, level of experience,\nnationality, personal appearance, race, religion, or sexual identity and\norientation.\n\n### Our Standards\n\nExamples of behavior that contributes to creating a positive environment\ninclude:\n\n- Using welcoming and inclusive language\n- Being respectful of differing viewpoints and experiences\n- Gracefully accepting constructive criticism\n- Focusing on what is best for the community\n- Showing empathy towards other community members\n\nExamples of unacceptable behavior by participants include:\n\n- The use of sexualized language or imagery and unwelcome sexual attention or\n  advances\n- Trolling, insulting/derogatory comments, and personal or political attacks\n- Public or private harassment\n- Publishing others' private information, such as a physical or electronic\n  address, without explicit permission\n- Other conduct which could reasonably be considered inappropriate in a\n  professional setting\n\n### Our Responsibilities\n\nProject maintainers are responsible for clarifying the standards of acceptable\nbehavior and are expected to take appropriate and fair corrective action in\nresponse to any instances of unacceptable behavior.\n\nProject maintainers have the right and responsibility to remove, edit, or\nreject comments, commits, code, wiki edits, issues, and other contributions\nthat are not aligned to this Code of Conduct, or to ban temporarily or\npermanently any contributor for other behaviors that they deem inappropriate,\nthreatening, offensive, or harmful.\n\n### Scope\n\nThis Code of Conduct applies both within project spaces and in public spaces\nwhen an individual is representing the project or its community. Examples of\nrepresenting a project or community include using an official project e-mail\naddress, posting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event. Representation of a project may be\nfurther defined and clarified by project maintainers.\n\n### Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported by contacting the project team at lauren.eastridge@formidable.com. All\ncomplaints will be reviewed and investigated and will result in a response that\nis deemed necessary and appropriate to the circumstances. The project team is\nobligated to maintain confidentiality with regard to the reporter of an incident.\nFurther details of specific enforcement policies may be posted separately.\n\nProject maintainers who do not follow or enforce the Code of Conduct in good\nfaith may face temporary or permanent repercussions as determined by other\nmembers of the project's leadership.\n\n### Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,\navailable at [http://contributor-covenant.org/version/1/4][version]\n\n[homepage]: http://contributor-covenant.org\n[version]: http://contributor-covenant.org/version/1/4/\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "## Development\n\n### Installing dependencies\n\n```sh\nyarn install\n```\n\n### Testing\n\nYou will find tests for files colocated with `*.test.ts` suffixes. Whenever making any changes, ensure that all existing tests pass by running `yarn test`.\n\nIf you are adding a new feature or some extra functionality, you should also make sure to accompany those changes with appropriate tests.\n\n### Linting and Formatting\n\nBefore committing any changes, be sure to do `yarn lint`; this will lint all relevant files using [ESLint](http://eslint.org/) and report on any changes that you need to make.\n\n### Before submitting a PR...\n\nThanks for taking the time to help us make webpack-dashboard even better! Before you go ahead and submit a PR, make sure that you have done the following:\n\n- Run the tests using `yarn test`\n- Run lint and flow using `yarn lint`\n- Run `yarn changeset`\n\n### Using changesets\n\nOur official release path is to use automation to perform the actual publishing of our packages. The steps are to:\n\n1. A human developer adds a changeset. Ideally this is as a part of a PR that will have a version impact on a package.\n2. On merge of a PR our automation system opens a \"Version Packages\" PR.\n3. On merging the \"Version Packages\" PR, the automation system publishes the packages.\n\nHere are more details:\n\n### Add a changeset\n\nWhen you would like to add a changeset (which creates a file indicating the type of change), in your branch/PR issue this command:\n\n```sh\n$ yarn changeset\n```\n\nto produce an interactive menu. Navigate the packages with arrow keys and hit `<space>` to select 1+ packages. Hit `<return>` when done. Select semver versions for packages and add appropriate messages. From there, you'll be prompted to enter a summary of the change. Some tips for this summary:\n\n1. Aim for a single line, 1+ sentences as appropriate.\n2. Include issue links in GH format (e.g. `#123`).\n3. You don't need to reference the current pull request or whatnot, as that will be added later automatically.\n\nAfter this, you'll see a new uncommitted file in `.changesets` like:\n\n```sh\n$ git status\n# ....\nUntracked files:\n  (use \"git add <file>...\" to include in what will be committed)\n\t.changeset/flimsy-pandas-marry.md\n```\n\nReview the file, make any necessary adjustments, and commit it to source. When we eventually do a package release, the changeset notes and version will be incorporated!\n\n### Creating versions\n\nOn a merge of a feature PR, the changesets GitHub action will open a new PR titled `\"Version Packages\"`. This PR is automatically kept up to date with additional PRs with changesets. So, if you're not ready to publish yet, just keep merging feature PRs and then merge the version packages PR later.\n\n### Publishing packages\n\nOn the merge of a version packages PR, the changesets GitHub action will publish the packages to npm.\n"
  },
  {
    "path": "ISSUE_TEMPLATE.md",
    "content": "#### Please provide a description and details of the bug / issue below:\n\n---\n\n#### If the issue is visual, please provide screenshots here\n\n---\n\n#### Steps to reproduce the problem\n\n---\n\n#### Please provide a gist of relevant files\n\n1. package.json (specifically the script you are using to start the dashboard)\n2. webpack.config.js\n3. index.js (Your express based dev server, if applicable)\n\n---\n\n#### More Details\n\n- What operating system are you on?\n- What terminal application are you using?\n- What version of webpack-dashboard are you using?\n- What is the output of running `echo $TERM`?\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2016-present, Formidable Labs. All rights reserved.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software is furnished to do so,\nsubject 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, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE."
  },
  {
    "path": "README.md",
    "content": "[![Webpack Dashboard — Formidable, We build the modern web](https://raw.githubusercontent.com/FormidableLabs/webpack-dashboard/master/webpack-dashboard-Hero.png)](https://formidable.com/open-source/)\n\n[![npm version][npm_img]][npm_site]\n[![Actions Status][actions_img]][actions_site]\n[![Coverage Status][cov_img]][cov_site]\n[![Maintenance Status][maintenance-image]](#maintenance-status)\n\nA CLI dashboard for your webpack dev server\n\n### What's this all about?\n\nWhen using webpack, especially for a dev server, you are probably used to seeing something like this:\n\n![https://i.imgur.com/p1uAqkD.png](https://i.imgur.com/p1uAqkD.png)\n\nThat's cool, but it's mostly noise and scrolly and not super helpful. This plugin changes that. Now when you run your dev server, you basically work at NASA:\n\n![https://i.imgur.com/qL6dXJd.png](https://i.imgur.com/qL6dXJd.png)\n\n### Install\n\n```sh\n$ npm install --save-dev webpack-dashboard\n# ... or ...\n$ yarn add --dev webpack-dashboard\n```\n\n> ℹ️ **Note**: You can alternatively globally install the dashboard (e.g. `npm install -g webpack-dashboard`) for use with any project and everything should work the same.\n\n### Use\n\n**`webpack-dashboard@^3.0.0` requires Node 8 or above.** Previous versions support down to Node 6.\n\nFirst, import the plugin and add it to your webpack config:\n\n```js\n// Import the plugin:\nconst DashboardPlugin = require(\"webpack-dashboard/plugin\");\n\n// Add it to your webpack configuration plugins.\nmodule.exports = {\n  // ...\n  plugins: [new DashboardPlugin()];\n  // ...\n};\n```\n\nThen, modify your dev server start script previously looked like:\n\n```js\n\"scripts\": {\n    \"dev\": \"node index.js\", # OR\n    \"dev\": \"webpack-dev-server\", # OR\n    \"dev\": \"webpack\",\n}\n```\n\nYou would change that to:\n\n```js\n\"scripts\": {\n    \"dev\": \"webpack-dashboard -- node index.js\", # OR\n    \"dev\": \"webpack-dashboard -- webpack-dev-server\", # OR\n    \"dev\": \"webpack-dashboard -- webpack\",\n}\n```\n\nNow you can just run your start script like normal, except now, you are awesome. Not that you weren't before. I'm just saying. More so.\n\n#### Customizations\n\nMore configuration customization examples can be found in our [getting started](./docs/getting-started.md) guide.\n\nFor example, if you want to use a custom port of `webpack-dashboard` to communicate between its plugin and CLI tool, you would first set the number in the options object in webpack configuration:\n\n```js\nplugins: [new DashboardPlugin({ port: 3001 })];\n```\n\nThen, you would pass it along to the CLI to match:\n\n```sh\n$ webpack-dashboard --port 3001 -- webpack\n```\n\n> ⚠️ **Warning**: When choosing a custom port, you need to find one that is **not** already in use. You should not choose one that is being used by `webpack-dev-server` / `devServer` or any other process. Instead, pick one that is **only** for `webpack-dashboard` and pair that up in the plugin configuration and CLI port flag.\n\n### Run it\n\nFinally, start your server using whatever command you have set up. Either you have `npm run dev` or `npm start` pointed at `node devServer.js` or something along those lines.\n\nThen, sit back and pretend you're an astronaut.\n\n### Supported Operating Systems and Terminals\n\n**macOS →**\nWebpack Dashboard works in Terminal, iTerm 2, and Hyper. For mouse events, like scrolling, in Terminal you will need to ensure _View → Enable Mouse Reporting_ is enabled. This is supported in macOS El Capitan, Sierra, and High Sierra. In iTerm 2, to select full rows of text hold the <kbd>⌥ Opt</kbd> key. To select a block of text hold the <kbd>⌥ Opt</kbd> + <kbd>⌘ Cmd</kbd> key combination.\n\n**Windows 10 →** Webpack Dashboard works in Command Prompt, PowerShell, and Linux Subsystem for Windows. Mouse events are not supported at this time, as discussed further in the documentation of the underlying terminal library we use [Blessed](https://github.com/chjj/blessed#windows-compatibility). The main log can be scrolled using the <kbd>↑</kbd>, <kbd>↓</kbd>, <kbd>Page Up</kbd>, and <kbd>Page Down</kbd> keys.\n\n**Linux →** Webpack Dashboard has been verified in the built-in terminal app for Debian-based Linux distributions such as Ubuntu or Mint. Mouse events and scrolling are supported automatically. To highlight or select lines hold the <kbd>⇧ Shift</kbd> key.\n\n### API\n\n#### webpack-dashboard (CLI)\n\n##### Options\n\n- `-c, --color [color]` - Custom ANSI color for your dashboard\n- `-m, --minimal` - Runs the dashboard in minimal mode\n- `-t, --title [title]` - Set title of terminal window\n- `-p, --port [port]` - Custom port for socket communication server\n- `-a, --include-assets [string prefix]` - Limit display to asset names matching string prefix (option can be repeated and is concatenated to `new DashboardPlugin({ includeAssets })` options array)\n\n##### Arguments\n\n`[command]` - The command you want to run, i.e. `webpack-dashboard -- node index.js`\n\n#### Webpack plugin\n\n#### Options\n\n- `host` - Custom host for connection the socket client\n- `port` - Custom port for connecting the socket client\n- `includeAssets` - Limit display to asset names matching string prefix or regex (`Array<String | RegExp>`)\n- `handler` - Plugin handler method, i.e. `dashboard.setData`\n\n_Note: you can also just pass a function in as an argument, which then becomes the handler, i.e. `new DashboardPlugin(dashboard.setData)`_\n\n### Local Development\n\nWe've standardized our local development process for `webpack-dashboard` on using `yarn`. We recommend using `yarn 1.10.x+`, as these versions include the `integrity` checksum. The checksum helps to verify the integrity of an installed package before its code is executed. 🚀\n\nTo run this repo locally against our provided examples, take the usual steps.\n\n```sh\nyarn\nyarn dev\n```\n\nWe re-use a small handful of the fixtures from [`inspectpack`](https://github.com/FormidableLabs/inspectpack) so that you can work locally on the dashboard while simulating common `node_modules` dependency issues you might face in the wild. These live in `/examples`.\n\nTo change the example you're working against, simply alter the `EXAMPLE` env variable in the `dev` script in `package.json` to match the scenario you want to run in `/examples`. For example, if you want to run the `tree-shaking` example, change the `dev` script from this:\n\n```sh\n$ cross-env EXAMPLE=duplicates-esm \\\n  node bin/webpack-dashboard.js -- \\\n  webpack-cli --config examples/config/webpack.config.js --watch\n```\n\nto this:\n\n```sh\n$ cross-env EXAMPLE=tree-shaking WEBPACK_MODE=production \\\n  node bin/webpack-dashboard.js -- \\\n  webpack-cli --config examples/config/webpack.config.js --watch\n```\n\nThen just run `yarn dev` to get up and running. PRs are very much appreciated!\n\n## Contributing\n\nPlease see our [contributing guide](CONTRIBUTING.MD).\n\n#### Credits\n\nModule output deeply inspired by: [https://github.com/robertknight/webpack-bundle-size-analyzer](https://github.com/robertknight/webpack-bundle-size-analyzer)\n\nError output deeply inspired by: [https://github.com/facebookincubator/create-react-app](https://github.com/facebookincubator/create-react-app)\n\n#### Maintenance Status\n\n**Active:** Formidable is actively working on this project, and we expect to continue for work for the foreseeable future. Bug reports, feature requests and pull requests are welcome.\n\n[maintenance-image]: https://img.shields.io/badge/maintenance-active-green.svg?color=brightgreen&style=flat\n[npm_img]: https://img.shields.io/npm/v/webpack-dashboard.svg?style=flat\n[npm_site]: https://www.npmjs.com/package/webpack-dashboard\n[actions_img]: https://github.com/FormidableLabs/webpack-dashboard/workflows/CI/badge.svg\n[actions_site]: https://github.com/FormidableLabs/webpack-dashboard/actions\n[cov_img]: https://codecov.io/gh/FormidableLabs/webpack-dashboard/branch/master/graph/badge.svg\n[cov_site]: https://codecov.io/gh/FormidableLabs/webpack-dashboard\n"
  },
  {
    "path": "bin/webpack-dashboard.js",
    "content": "#!/usr/bin/env node\n\n\"use strict\";\n\nconst commander = require(\"commander\");\nconst spawn = require(\"cross-spawn\");\nconst Dashboard = require(\"../dashboard/index\");\nconst io = require(\"socket.io\");\n\nconst DEFAULT_PORT = 9838;\n\nconst pkg = require(\"../package.json\");\n\nconst collect = (val, prev) => prev.concat([val]);\n\n// Wrap up side effects in a script.\n// eslint-disable-next-line max-statements, complexity\nconst main = opts => {\n  opts = opts || {};\n  const argv = typeof opts.argv === \"undefined\" ? process.argv : opts.argv;\n  const isWindows = process.platform === \"win32\";\n\n  const program = new commander.Command(\"webpack-dashboard\")\n    .version(pkg.version)\n    .option(\"-c, --color [color]\", \"Dashboard color\")\n    .option(\"-m, --minimal\", \"Minimal mode\")\n    .option(\"-t, --title [title]\", \"Terminal window title\")\n    .option(\"-p, --port [port]\", \"Socket listener port\")\n    .option(\"-a, --include-assets [string prefix]\", \"Asset names to limit to\", collect, [])\n    .usage(\"[options] -- [script] [arguments]\")\n    .parse(argv);\n\n  const cliOpts = program.opts();\n  const cliArgs = program.args;\n\n  let logFromChild = true;\n  let child;\n\n  if (!cliArgs.length) {\n    logFromChild = false;\n  }\n\n  if (logFromChild) {\n    const command = cliArgs[0];\n    const args = cliArgs.slice(1);\n    const env = process.env;\n\n    env.FORCE_COLOR = true;\n\n    child = spawn(command, args, {\n      env,\n      stdio: [null, null, null, null],\n      detached: !isWindows\n    });\n  }\n\n  const dashboard = new Dashboard({\n    color: cliOpts.color || \"green\",\n    minimal: cliOpts.minimal || false,\n    title: cliOpts.title || null\n  });\n\n  const port = parseInt(cliOpts.port || DEFAULT_PORT, 10);\n  const server = opts.server || io(port);\n\n  server.on(\"error\", err => {\n    // eslint-disable-next-line no-console\n    console.log(err);\n  });\n\n  if (logFromChild) {\n    server.on(\"connection\", socket => {\n      socket.emit(\"options\", {\n        minimal: cliOpts.minimal || false,\n        includeAssets: cliOpts.includeAssets || []\n      });\n\n      socket.on(\"message\", (message, ack) => {\n        // Note: `message` may be null.\n        // https://github.com/FormidableLabs/webpack-dashboard/issues/335\n        if (message && message.type !== \"log\") {\n          dashboard.setData(message, ack);\n        }\n      });\n    });\n\n    child.stdout.on(\"data\", data => {\n      dashboard.setData([\n        {\n          type: \"log\",\n          value: data.toString(\"utf8\")\n        }\n      ]);\n    });\n\n    child.stderr.on(\"data\", data => {\n      dashboard.setData([\n        {\n          type: \"log\",\n          value: data.toString(\"utf8\")\n        }\n      ]);\n    });\n\n    process.on(\"exit\", () => {\n      process.kill(isWindows ? child.pid : -child.pid);\n    });\n  } else {\n    server.on(\"connection\", socket => {\n      socket.on(\"message\", (message, ack) => {\n        dashboard.setData(message, ack);\n      });\n    });\n  }\n};\n\nif (require.main === module) {\n  main();\n}\n\nmodule.exports = main;\n"
  },
  {
    "path": "dashboard/index.js",
    "content": "\"use strict\";\n\nconst chalk = require(\"chalk\");\nconst blessed = require(\"neo-blessed\");\n\nconst { formatOutput } = require(\"../utils/format-output\");\nconst { formatModules } = require(\"../utils/format-modules\");\nconst { formatAssets } = require(\"../utils/format-assets\");\nconst { formatProblems } = require(\"../utils/format-problems\");\nconst { deserializeError } = require(\"../utils/error-serialization\");\n\nconst PERCENT_MULTIPLIER = 100;\n\nconst DEFAULT_SCROLL_OPTIONS = {\n  scrollable: true,\n  input: true,\n  alwaysScroll: true,\n  scrollbar: {\n    ch: \" \",\n    inverse: true\n  },\n  keys: true,\n  vi: true,\n  mouse: true\n};\n\nclass Dashboard {\n  // eslint-disable-next-line max-statements\n  constructor(options) {\n    // Options, params\n    options = options || {};\n    const title = options.title || \"webpack-dashboard\";\n\n    this.color = options.color || \"green\";\n    this.minimal = options.minimal || false;\n    this.stats = null;\n\n    // Data binding, lookup tables.\n    this.actionForMessageType = {\n      progress: this.setProgress.bind(this),\n      operations: this.setOperations.bind(this),\n      status: this.setStatus.bind(this),\n      stats: this.setStats.bind(this),\n      log: this.setLog.bind(this),\n      clear: this.clear.bind(this),\n      sizes: _data => {\n        if (this.minimal) {\n          return;\n        }\n        if (_data.value instanceof Error) {\n          this.setSizesError(_data.value);\n        } else {\n          this.setSizes(_data);\n        }\n      },\n      problems: _data => {\n        if (this.minimal) {\n          return;\n        }\n        if (_data.value instanceof Error) {\n          this.setProblemsError(_data.value);\n        } else {\n          this.setProblems(_data);\n        }\n      }\n    };\n\n    // Start UI stuff.\n    this.screen = blessed.screen({\n      title,\n      smartCSR: true,\n      dockBorders: false,\n      fullUnicode: true,\n      autoPadding: true\n    });\n\n    this.layoutLog();\n    this.layoutStatus();\n\n    if (!this.minimal) {\n      this.layoutModules();\n      this.layoutAssets();\n      this.layoutProblems();\n    }\n\n    this.screen.key([\"escape\", \"q\", \"C-c\"], () => {\n      process.kill(process.pid, \"SIGINT\");\n    });\n\n    this.screen.render();\n  }\n\n  setData(dataArray, ack) {\n    dataArray\n      .map(data =>\n        data.error\n          ? Object.assign({}, data, {\n              value: deserializeError(data.value)\n            })\n          : data\n      )\n      .forEach(data => {\n        this.actionForMessageType[data.type](data);\n      });\n\n    this.screen.render();\n\n    // Send ack back if requested.\n    if (ack) {\n      ack();\n    }\n  }\n\n  setProgress(data) {\n    const percent = parseInt(data.value * PERCENT_MULTIPLIER, 10);\n    const formattedPercent = `${percent.toString()}%`;\n\n    if (!percent) {\n      this.progressbar.setProgress(percent);\n    }\n\n    if (this.minimal) {\n      this.progress.setContent(formattedPercent);\n    } else {\n      this.progressbar.setContent(formattedPercent);\n      this.progressbar.setProgress(percent);\n    }\n  }\n\n  setOperations(data) {\n    this.operations.setContent(data.value);\n  }\n\n  setStatus(data) {\n    let content;\n\n    switch (data.value) {\n      case \"Success\":\n        content = `{green-fg}{bold}${data.value}{/}`;\n        break;\n      case \"Failed\":\n        content = `{red-fg}{bold}${data.value}{/}`;\n        break;\n      case \"Error\":\n        content = `{red-fg}{bold}${data.value}{/}`;\n        break;\n      default:\n        content = `{bold}${data.value}{/}`;\n    }\n    this.status.setContent(content);\n  }\n\n  setStats(data) {\n    const stats = {\n      hasErrors: () => data.value.errors,\n      hasWarnings: () => data.value.warnings,\n      toJson: () => data.value.data\n    };\n\n    // Save for later when merging inspectpack sizes into the asset list\n    this.stats = stats;\n\n    if (stats.hasErrors()) {\n      this.status.setContent(\"{red-fg}{bold}Failed{/}\");\n    }\n\n    this.logText.log(formatOutput(stats));\n\n    if (!this.minimal) {\n      this.modulesMenu.setLabel(chalk.yellow(\"Modules (loading...)\"));\n      this.assets.setLabel(chalk.yellow(\"Assets (loading...)\"));\n      this.problemsMenu.setLabel(chalk.yellow(\"Problems (loading...)\"));\n    }\n  }\n\n  setSizes(data) {\n    const { assets } = data.value;\n\n    // Start with top-level assets.\n    this.assets.setLabel(\"Assets\");\n    this.assetTable.setData(formatAssets(assets));\n\n    // Then split modules across assets.\n    const previousSelection = this.modulesMenu.selected;\n    const modulesItems = Object.keys(assets).reduce(\n      (memo, name) =>\n        Object.assign({}, memo, {\n          [name]: () => {\n            this.moduleTable.setData(formatModules(assets[name].files));\n            this.screen.render();\n          }\n        }),\n      {}\n    );\n\n    this.modulesMenu.setLabel(\"Modules\");\n    this.modulesMenu.setItems(modulesItems);\n    this.modulesMenu.selectTab(previousSelection);\n\n    // Final render.\n    this.screen.render();\n  }\n\n  setSizesError(err) {\n    this.modulesMenu.setLabel(chalk.red(\"Modules (error)\"));\n    this.assets.setLabel(chalk.red(\"Assets (error)\"));\n    this.logText.log(chalk.red(\"Could not load module/asset sizes.\"));\n    this.logText.log(chalk.red(err));\n  }\n\n  setProblems(data) {\n    const { duplicates, versions } = data.value;\n\n    // Separate across assets.\n    // Use duplicates as the \"canary\" to get asset names.\n    const assetNames = Object.keys(duplicates.assets);\n\n    const previousSelection = this.problemsMenu.selected;\n    const problemsItems = assetNames.reduce(\n      (memo, name) =>\n        Object.assign({}, memo, {\n          [name]: () => {\n            this.problems.setContent(\n              formatProblems({\n                duplicates: duplicates.assets[name],\n                versions: versions.assets[name]\n              })\n            );\n            this.screen.render();\n          }\n        }),\n      {}\n    );\n\n    this.problemsMenu.setLabel(\"Problems\");\n    this.problemsMenu.setItems(problemsItems);\n    this.problemsMenu.selectTab(previousSelection);\n\n    this.screen.render();\n  }\n\n  setProblemsError(err) {\n    this.problemsMenu.setLabel(chalk.red(\"Problems (error)\"));\n    this.logText.log(chalk.red(\"Could not analyze bundle problems.\"));\n    this.logText.log(chalk.red(err.stack));\n  }\n\n  setLog(data) {\n    if (this.stats && this.stats.hasErrors()) {\n      return;\n    }\n    this.logText.log(data.value.replace(/[{}]/g, \"\"));\n  }\n\n  clear() {\n    this.logText.setContent(\"\");\n  }\n\n  layoutLog() {\n    this.log = blessed.box({\n      label: \"Log\",\n      padding: 1,\n      width: this.minimal ? \"100%\" : \"75%\",\n      height: this.minimal ? \"70%\" : \"36%\",\n      left: \"0%\",\n      top: \"0%\",\n      border: {\n        type: \"line\"\n      },\n      style: {\n        fg: -1,\n        border: {\n          fg: this.color\n        }\n      }\n    });\n\n    this.logText = blessed.log(\n      Object.assign({}, DEFAULT_SCROLL_OPTIONS, {\n        parent: this.log,\n        tags: true,\n        width: \"100%-5\"\n      })\n    );\n\n    this.screen.append(this.log);\n    this.mapNavigationKeysToScrollLog();\n  }\n\n  mapNavigationKeysToScrollLog() {\n    this.screen.key([\"pageup\"], () => {\n      this.logText.setScrollPerc(0);\n      this.logText.screen.render();\n    });\n    this.screen.key([\"pagedown\"], () => {\n      // eslint-disable-next-line no-magic-numbers\n      this.logText.setScrollPerc(100);\n      this.logText.screen.render();\n    });\n    this.screen.key([\"up\"], () => {\n      this.logText.scroll(-1);\n      this.logText.screen.render();\n    });\n    this.screen.key([\"down\"], () => {\n      this.logText.scroll(1);\n      this.logText.screen.render();\n    });\n    this.screen.key([\"left\"], () => {\n      const currentIndex = this.modulesMenu.selected;\n      this.modulesMenu.selectTab(currentIndex - 1);\n      this.problemsMenu.selectTab(currentIndex - 1);\n      this.problemsMenu.screen.render();\n      this.modulesMenu.screen.render();\n    });\n    this.screen.key([\"right\"], () => {\n      const currentIndex = this.modulesMenu.selected;\n      this.modulesMenu.selectTab(currentIndex + 1);\n      this.problemsMenu.selectTab(currentIndex + 1);\n      this.problemsMenu.screen.render();\n      this.modulesMenu.screen.render();\n    });\n  }\n\n  layoutModules() {\n    this.modulesMenu = blessed.listbar({\n      label: \"Modules\",\n      mouse: true,\n      tags: true,\n      width: \"50%\",\n      height: \"66%\",\n      left: \"0%\",\n      top: \"36%\",\n      border: {\n        type: \"line\"\n      },\n      padding: 1,\n      style: {\n        fg: -1,\n        border: {\n          fg: this.color\n        },\n        prefix: {\n          fg: -1\n        },\n        item: {\n          fg: \"white\"\n        },\n        selected: {\n          fg: \"black\",\n          bg: this.color\n        }\n      },\n      autoCommandKeys: true\n    });\n\n    this.moduleTable = blessed.table(\n      Object.assign({}, DEFAULT_SCROLL_OPTIONS, {\n        parent: this.modulesMenu,\n        height: \"100%\",\n        width: \"100%-5\",\n        padding: {\n          top: 2,\n          right: 1,\n          left: 1\n        },\n        align: \"left\",\n        data: [[\"Name\", \"Size\", \"Percent\"]],\n        tags: true\n      })\n    );\n\n    this.screen.append(this.modulesMenu);\n  }\n\n  layoutAssets() {\n    this.assets = blessed.box({\n      label: \"Assets\",\n      tags: true,\n      padding: 1,\n      width: \"50%\",\n      height: \"28%\",\n      left: \"50%\",\n      top: \"36%\",\n      border: {\n        type: \"line\"\n      },\n      style: {\n        fg: -1,\n        border: {\n          fg: this.color\n        }\n      }\n    });\n\n    this.assetTable = blessed.table(\n      Object.assign({}, DEFAULT_SCROLL_OPTIONS, {\n        parent: this.assets,\n        height: \"100%\",\n        width: \"100%-5\",\n        align: \"left\",\n        padding: 1,\n        data: [[\"Name\", \"Size\"]]\n      })\n    );\n\n    this.screen.append(this.assets);\n  }\n\n  layoutProblems() {\n    this.problemsMenu = blessed.listbar({\n      label: \"Problems\",\n      mouse: true,\n      width: \"50%\",\n      height: \"38%\",\n      left: \"50%\",\n      top: \"63%\",\n      border: {\n        type: \"line\"\n      },\n      padding: {\n        top: 1\n      },\n      style: {\n        border: {\n          fg: this.color\n        },\n        prefix: {\n          fg: -1\n        },\n        item: {\n          fg: \"white\"\n        },\n        selected: {\n          fg: \"black\",\n          bg: this.color\n        }\n      },\n      autoCommandKeys: true\n    });\n\n    this.problems = blessed.box(\n      Object.assign({}, DEFAULT_SCROLL_OPTIONS, {\n        parent: this.problemsMenu,\n        padding: 1,\n        border: {\n          fg: -1\n        },\n        style: {\n          fg: -1,\n          border: {\n            fg: this.color\n          }\n        },\n        tags: true\n      })\n    );\n\n    this.screen.append(this.problemsMenu);\n  }\n\n  // eslint-disable-next-line complexity\n  layoutStatus() {\n    this.wrapper = blessed.layout({\n      width: this.minimal ? \"100%\" : \"25%\",\n      height: this.minimal ? \"30%\" : \"36%\",\n      top: this.minimal ? \"70%\" : \"0%\",\n      left: this.minimal ? \"0%\" : \"75%\",\n      layout: \"grid\"\n    });\n\n    this.status = blessed.box({\n      parent: this.wrapper,\n      label: \"Status\",\n      tags: true,\n      padding: {\n        left: 1\n      },\n      width: this.minimal ? \"34%-1\" : \"100%\",\n      height: this.minimal ? \"100%\" : \"34%\",\n      valign: \"middle\",\n      border: {\n        type: \"line\"\n      },\n      style: {\n        fg: -1,\n        border: {\n          fg: this.color\n        }\n      }\n    });\n\n    this.operations = blessed.box({\n      parent: this.wrapper,\n      label: \"Operation\",\n      tags: true,\n      padding: {\n        left: 1\n      },\n      width: this.minimal ? \"34%-1\" : \"100%\",\n      height: this.minimal ? \"100%\" : \"34%\",\n      valign: \"middle\",\n      border: {\n        type: \"line\"\n      },\n      style: {\n        fg: -1,\n        border: {\n          fg: this.color\n        }\n      }\n    });\n\n    this.progress = blessed.box({\n      parent: this.wrapper,\n      label: \"Progress\",\n      tags: true,\n      padding: this.minimal\n        ? {\n            left: 1\n          }\n        : 1,\n      width: this.minimal ? \"33%\" : \"100%\",\n      height: this.minimal ? \"100%\" : \"34%\",\n      valign: \"middle\",\n      border: {\n        type: \"line\"\n      },\n      style: {\n        fg: -1,\n        border: {\n          fg: this.color\n        }\n      }\n    });\n\n    this.progressbar = new blessed.ProgressBar({\n      parent: this.progress,\n      height: 1,\n      width: \"90%\",\n      top: \"center\",\n      left: \"center\",\n      hidden: this.minimal,\n      orientation: \"horizontal\",\n      style: {\n        bar: {\n          bg: this.color\n        }\n      }\n    });\n\n    this.screen.append(this.wrapper);\n  }\n}\n\nmodule.exports = Dashboard;\n"
  },
  {
    "path": "docs/getting-started.md",
    "content": "# Getting Started with Webpack-Dashboard\n\n## Install\n\n```sh\n$ npm install --save-dev webpack-dashboard\n# ... or ...\n$ yarn add --dev webpack-dashboard\n```\n\n## Use\n\n***OS X Terminal.app users:*** Make sure that **View → Allow Mouse Reporting** is enabled, otherwise scrolling through logs and modules won't work. If your version of Terminal.app doesn't have this feature, you may want to check out an alternative such as [iTerm2](https://www.iterm2.com/index.html).\n\nFirst, import the plugin and add it to your webpack config:\n\n```js\n// Import the plugin:\nconst DashboardPlugin = require(\"webpack-dashboard/plugin\");\n\n// Add it to your webpack configuration plugins.\nmodule.exports = {\n  // ...\n  plugins: [new DashboardPlugin({\n    /* options */\n  })];\n  // ...\n};\n```\n\nBecause sockets use a port, the constructor now supports passing an options object that can include a custom port (if the default is giving you problems). If using a custom port, the port number must be included in the options object here, as well as passed using the -p flag in the call to webpack-dashboard. See how below:\n\n```js\nplugins: [\n  new DashboardPlugin({ port: 3001 })\n]\n```\n\nThe next step, is to call webpack-dashboard from your `package.json`. So if your dev server start script previously looked like:\n\n```js\n\"scripts\": {\n  \"dev\": \"node index.js\"\n}\n```\n\nYou would change that to:\n\n```js\n\"scripts\": {\n  \"dev\": \"webpack-dashboard -- node index.js\"\n}\n```\n\nIf you are using the webpack-dev-server script, you can do something like:\n\n```js\n\"scripts\": {\n  \"dev\": \"webpack-dashboard -- webpack-dev-server --config ./webpack.dev.js\"\n}\n```\n\nAgain, the new version uses sockets, so if you want to use a custom port you must use the `-p` option to pass that:\n\n```js\n\"scripts\": {\n  \"dev\": \"webpack-dashboard -p 3001 -- node index.js\"\n}\n```\nYou can also pass a supported ANSI color using the `-c` flag to custom colorize your dashboard:\n\n```js\n\"scripts\": {\n  \"dev\": \"webpack-dashboard -c magenta -- node index.js\"\n}\n```\nNow you can just run your start script like normal, except now, you are awesome. Not that you weren't before. I'm just saying. More so.\n\n## Other usage\n\nWe previously provided detailed guides for integration with `webpack-dev-server` and `express`, but as both of those projects now can be entirely configuration file based, we recommend just following the [webpack development server guide](https://webpack.js.org/guides/development/) to integrate with your appropriate development server setup of choice.\n"
  },
  {
    "path": "examples/.eslintrc.json",
    "content": "{\n  \"parserOptions\": {\n    \"sourceType\": \"module\"\n  }\n}"
  },
  {
    "path": "examples/config/webpack.config.js",
    "content": "const { resolve } = require(\"path\");\nconst { StatsWriterPlugin } = require(\"webpack-stats-plugin\");\nconst { DuplicatesPlugin } = require(\"inspectpack/plugin\");\nconst Dashboard = require(\"../../plugin\");\nconst webpackPkg = require(\"webpack/package.json\");\nconst webpackVers = webpackPkg.version.split(\".\")[0];\n\n// Specify the directory of the example we're working with\nconst cwd = `${process.cwd()}/examples/${process.env.EXAMPLE}`;\nif (!process.env.EXAMPLE) {\n  throw new Error(\"EXAMPLE is required\");\n}\n\nconst mode = process.env.WEBPACK_MODE || \"development\";\n\nmodule.exports = {\n  mode,\n  devtool: false,\n  context: resolve(cwd),\n  entry: {\n    bundle: \"./src/index.js\",\n    // Hard-code path to the \"hello world\" no-dep entry for 2+ asset testing\n    hello: \"../simple/src/index.js\"\n  },\n  output: {\n    path: resolve(cwd, `dist-${mode}-${webpackVers}`),\n    pathinfo: true,\n    filename: \"[name].js\"\n  },\n  plugins: [\n    new StatsWriterPlugin({\n      fields: [\"assets\", \"modules\"],\n      stats: {\n        source: true // Needed for webpack5+\n      }\n    }),\n    new DuplicatesPlugin({\n      verbose: true,\n      emitErrors: false\n    }),\n    new Dashboard({\n      // Optionally filter which assets to report on by string prefix or regex.\n      // includeAssets: [\"bundle\", /bund/]\n    })\n  ]\n};\n"
  },
  {
    "path": "examples/config/webpack.config.ts",
    "content": "/**\n * No-op build with TS config to see if webpack-cli bombs out.\n */\n\nimport DashboardPlugin from '../../plugin';\n\nimport * as path from 'path';\nimport * as webpack from 'webpack';\nconst webpackVers = webpack.version;\n\nconst cwd = `${process.cwd()}/examples/${process.env.EXAMPLE}`;\nif (!process.env.EXAMPLE) {\n  throw new Error(\"EXAMPLE is required\");\n}\n\nconst mode = process.env.WEBPACK_MODE || \"development\";\n\nconst config: webpack.Configuration = {\n  mode: 'development',\n  entry: {\n    bundle: \"./src/index.js\"\n  },\n  context: path.resolve(cwd),\n  output: {\n    path: path.resolve(cwd, `dist-ts-${mode}-${webpackVers}`),\n    pathinfo: true,\n    filename: \"[name].js\"\n  },\n  devtool: false,\n  plugins: [\n    new DashboardPlugin()\n  ]\n};\n\nexport default config;\n"
  },
  {
    "path": "examples/duplicates-esm/package.json",
    "content": "{\n  \"name\": \"duplicates-esm\",\n  \"version\": \"1.2.3\",\n  \"description\": \"DUMMY APP\",\n  \"main\": \"src/index.js\",\n  \"dependencies\": {\n    \"foo\": \"^1.0.0\",\n    \"uses-foo\": \"^1.0.9\"\n  }\n}\n"
  },
  {
    "path": "examples/duplicates-esm/src/index.js",
    "content": "/* eslint-disable no-console*/\n\nimport { foo } from \"foo\";\nimport { usesFoo } from \"uses-foo\";\n\nconsole.log(\"foo\", foo());\nconsole.log(\"usesFoo\", usesFoo());\n"
  },
  {
    "path": "examples/simple/package.json",
    "content": "{\n  \"name\": \"simple\",\n  \"version\": \"1.2.3\",\n  \"description\": \"DUMMY APP\",\n  \"main\": \"src/index.js\"\n}\n"
  },
  {
    "path": "examples/simple/src/index.js",
    "content": "/* eslint-disable no-console*/\n\nconst hello = () => \"hello world\";\n\nconsole.log(hello());\n"
  },
  {
    "path": "examples/tree-shaking/package.json",
    "content": "{\n  \"name\": \"tree-shaking\",\n  \"version\": \"1.2.3\",\n  \"description\": \"DUMMY APP\",\n  \"main\": \"src/index.js\",\n  \"dependencies\": {\n    \"foo\": \"^1.0.0\",\n    \"uses-foo\": \"^1.0.9\"\n  }\n}\n"
  },
  {
    "path": "examples/tree-shaking/src/index.js",
    "content": "/* eslint-disable no-console*/\n\nimport { red } from \"foo\";\nimport { usesRed } from \"uses-foo\";\n\nconsole.log(\"red\", red());\nconsole.log(\"usesRed\", usesRed());\n"
  },
  {
    "path": "index.js",
    "content": "\"use strict\";\n\nconst dashboard = require(\"./dashboard/index\");\n\nmodule.exports = dashboard;\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"webpack-dashboard\",\n  \"version\": \"3.3.8\",\n  \"description\": \"a CLI dashboard for webpack dev server\",\n  \"bin\": \"bin/webpack-dashboard.js\",\n  \"main\": \"index.js\",\n  \"engines\": {\n    \"node\": \">=8.0.0\"\n  },\n  \"types\": \"index.d.ts\",\n  \"scripts\": {\n    \"test\": \"mocha \\\"test/**/*.spec.js\\\"\",\n    \"test-cov\": \"nyc mocha \\\"test/**/*.spec.js\\\"\",\n    \"lint\": \"eslint .\",\n    \"check\": \"run-s format-check lint test check-ts\",\n    \"check-ci\": \"run-s format-check lint test-cov check-ts\",\n    \"check-ts\": \"tsc plugin/index.d.ts examples/config/webpack.config.ts --noEmit\",\n    \"dev\": \"cross-env EXAMPLE=duplicates-esm node bin/webpack-dashboard.js -- webpack-cli --config examples/config/webpack.config.js --watch\",\n    \"dev-ts\": \"cross-env EXAMPLE=duplicates-esm node bin/webpack-dashboard.js -- webpack-cli --config examples/config/webpack.config.ts --watch\",\n    \"format\": \"prettier --write \\\"./{bin,examples,plugin,test,utils}/**/*.js\\\"\",\n    \"format-check\": \"prettier --list-different \\\"./{bin,examples,plugin,test,utils}/**/*.js\\\"\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/FormidableLabs/webpack-dashboard.git\"\n  },\n  \"keywords\": [\n    \"webpack\",\n    \"cli\",\n    \"plugin\",\n    \"dashboard\"\n  ],\n  \"author\": \"Ken Wheeler\",\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/FormidableLabs/webpack-dashboard/issues\"\n  },\n  \"homepage\": \"https://github.com/FormidableLabs/webpack-dashboard\",\n  \"peerDependencies\": {\n    \"webpack\": \"*\"\n  },\n  \"dependencies\": {\n    \"@changesets/cli\": \"^2.26.1\",\n    \"chalk\": \"^4.1.1\",\n    \"commander\": \"^8.0.0\",\n    \"cross-spawn\": \"^7.0.3\",\n    \"filesize\": \"^7.0.0\",\n    \"handlebars\": \"^4.1.2\",\n    \"inspectpack\": \"^4.7.1\",\n    \"neo-blessed\": \"^0.2.0\",\n    \"socket.io\": \"^4.1.3\",\n    \"socket.io-client\": \"^4.1.3\"\n  },\n  \"devDependencies\": {\n    \"@svitejs/changesets-changelog-github-compact\": \"^0.1.1\",\n    \"@types/node\": \"^22.1.0\",\n    \"babel-eslint\": \"^10.1.0\",\n    \"chai\": \"^4.3.4\",\n    \"codecov\": \"^3.8.3\",\n    \"cross-env\": \"^7.0.3\",\n    \"eslint\": \"^7.30.0\",\n    \"eslint-config-formidable\": \"^4.0.0\",\n    \"eslint-config-prettier\": \"^8.3.0\",\n    \"eslint-plugin-filenames\": \"^1.1.0\",\n    \"eslint-plugin-import\": \"^2.23.4\",\n    \"eslint-plugin-prettier\": \"^3.4.0\",\n    \"eslint-plugin-promise\": \"^5.1.0\",\n    \"mocha\": \"^9.0.2\",\n    \"npm-run-all\": \"^4.1.5\",\n    \"nyc\": \"^15.1.0\",\n    \"prettier\": \"^2.3.2\",\n    \"sinon\": \"^11.1.1\",\n    \"sinon-chai\": \"^3.7.0\",\n    \"ts-node\": \"^10.4.0\",\n    \"typescript\": \"^4.3.5\",\n    \"webpack\": \"^5.44.0\",\n    \"webpack-cli\": \"^4.7.2\",\n    \"webpack-stats-plugin\": \"^1.0.3\"\n  },\n  \"publishConfig\": {\n    \"provenance\": true\n  }\n}"
  },
  {
    "path": "plugin/index.d.ts",
    "content": "interface IMessage {\n  type: string;\n  value: string | number | { [key: string]: any }\n  error?: boolean;\n}\n\ninterface IDashboardOptions {\n  port?: number;\n  host?: string;\n  handler?: (dataArray: IMessage[]) => void;\n}\n\ninterface ICompiler {\n  hooks?: any;\n  plugin?: (name: string, callback: () => void) => void;\n}\n\nexport default class DashboardPlugin {\n  constructor(options?: IDashboardOptions);\n  apply(compiler: ICompiler): void;\n}\n"
  },
  {
    "path": "plugin/index.js",
    "content": "/* eslint-disable max-params, max-statements */\n\n\"use strict\";\n\nconst webpack = require(\"webpack\");\nconst io = require(\"socket.io-client\");\nconst inspectpack = require(\"inspectpack\");\n\nconst serializer = require(\"../utils/error-serialization\");\n\nconst DEFAULT_PORT = 9838;\nconst DEFAULT_HOST = \"127.0.0.1\";\nconst ONE_SECOND = 1000;\nconst INSPECTPACK_PROBLEM_ACTIONS = [\"duplicates\", \"versions\"];\nconst INSPECTPACK_PROBLEM_TYPE = \"problems\";\nconst CLEANUP_MAX_NUM_TRIES = 3; // Try 3 times to close before giving up.\nconst CLEANUP_RETRY_DELAY_MS = 100; // Delay before a retry.\n\nfunction noop() {}\n\nfunction getTimeMessage(timer) {\n  let time = Date.now() - timer;\n\n  if (time >= ONE_SECOND) {\n    time /= ONE_SECOND;\n    time = Math.round(time);\n    time += \"s\";\n  } else {\n    time += \"ms\";\n  }\n\n  return ` (${time})`;\n}\n\n// Naive camel-casing.\nconst camel = str => str.replace(/-([a-z])/, group => group[1].toUpperCase());\n\n// Normalize webpack3 vs. 4 API differences.\nfunction _webpackHook(hookType, compiler, event, callback) {\n  if (compiler.hooks) {\n    hookType = hookType || \"tap\";\n    compiler.hooks[camel(event)][hookType](\"webpack-dashboard\", callback);\n  } else {\n    compiler.plugin(event, callback);\n  }\n}\n\nconst webpackHook = _webpackHook.bind(null, \"tap\");\nconst webpackAsyncHook = _webpackHook.bind(null, \"tapAsync\");\n\nclass DashboardPlugin {\n  constructor(options) {\n    if (typeof options === \"function\") {\n      this._handler = options;\n    } else {\n      options = options || {};\n      this.host = options.host || DEFAULT_HOST;\n      this.port = options.port || DEFAULT_PORT;\n      this.includeAssets = options.includeAssets || [];\n      this._handler = options.handler || null;\n    }\n\n    this.watching = false;\n    this.openMessages = 0;\n  }\n\n  handler(...args) {\n    if (this._handler) {\n      this._handler(...args);\n    }\n  }\n\n  cleanup(numTried = 0) {\n    if (!this._cleanedUp && !this.watching && this.socket) {\n      // Clear handler so we don't emit any more messages.\n      this._handler = null;\n\n      // Check if we have unhandled dashboard messages.\n      if (this.openMessages > 0 && numTried < CLEANUP_MAX_NUM_TRIES) {\n        // Wait a small interval and try again, up to a maximum.\n        setTimeout(() => this.cleanup(numTried++), CLEANUP_RETRY_DELAY_MS);\n        return;\n      }\n\n      // Close!\n      this._cleanedUp = true;\n      this.socket.close();\n    }\n  }\n\n  apply(compiler) {\n    // Reached compile \"done\" state.\n    let reachedDone = false;\n    // Compile has finished in \"done\", \"error\", \"failed\" states.\n    let finished = false;\n    let timer;\n\n    if (!this._handler) {\n      this._handler = noop;\n      const port = this.port;\n      const host = this.host;\n      this.socket = io(`http://${host}:${port}`);\n      this.socket.on(\"connect\", () => {\n        // Manually track messages we send to the dashboard and decrement later.\n        const socketMsg = this.socket.emit.bind(this.socket, \"message\");\n        const ack = () => {\n          this.openMessages--;\n        };\n        this._handler = (...args) => {\n          this.openMessages++;\n          socketMsg(...args, ack);\n        };\n      });\n      this.socket.once(\"options\", args => {\n        this.minimal = args.minimal;\n        this.includeAssets = this.includeAssets.concat(args.includeAssets || []);\n      });\n      this.socket.on(\"error\", err => {\n        // eslint-disable-next-line no-console\n        console.log(err);\n      });\n      this.socket.on(\"disconnect\", () => {\n        if (!reachedDone) {\n          // eslint-disable-next-line no-console\n          console.log(\"Socket.io disconnected before completing build lifecycle.\");\n        }\n      });\n    }\n\n    new webpack.ProgressPlugin((percent, msg) => {\n      // Skip reporting once finished.\n      if (finished) {\n        return;\n      }\n\n      this.handler([\n        {\n          type: \"status\",\n          value: \"Compiling\"\n        },\n        {\n          type: \"progress\",\n          value: percent\n        },\n        {\n          type: \"operations\",\n          value: msg + getTimeMessage(timer)\n        }\n      ]);\n    }).apply(compiler);\n\n    webpackAsyncHook(compiler, \"watch-run\", (c, done) => {\n      this.watching = true;\n      done();\n    });\n\n    webpackAsyncHook(compiler, \"run\", (c, done) => {\n      this.watching = false;\n      done();\n    });\n\n    webpackHook(compiler, \"compile\", () => {\n      timer = Date.now();\n      finished = false;\n      this.handler([\n        {\n          type: \"status\",\n          value: \"Compiling\"\n        }\n      ]);\n    });\n\n    webpackHook(compiler, \"invalid\", () => {\n      finished = true;\n      this.handler([\n        {\n          type: \"status\",\n          value: \"Invalidated\"\n        },\n        {\n          type: \"progress\",\n          value: 0\n        },\n        {\n          type: \"operations\",\n          value: \"idle\"\n        },\n        {\n          type: \"clear\"\n        }\n      ]);\n    });\n\n    webpackHook(compiler, \"failed\", () => {\n      finished = true;\n      this.handler([\n        {\n          type: \"status\",\n          value: \"Failed\"\n        },\n        {\n          type: \"operations\",\n          value: `idle${getTimeMessage(timer)}`\n        }\n      ]);\n    });\n\n    webpackAsyncHook(compiler, \"done\", (stats, done) => {\n      const { errors, options } = stats.compilation;\n      const statsOptions = (options.devServer && options.devServer.stats) ||\n        options.stats || { colors: true };\n      const status = errors.length ? \"Error\" : \"Success\";\n\n      // We only need errors/warnings for stats information for finishing up.\n      // This allows us to avoid sending a full stats object to the CLI which\n      // can cause socket.io client disconnects for large objects.\n      // See: https://github.com/FormidableLabs/webpack-dashboard/issues/279\n      const statsJsonOptions = {\n        all: false,\n        errors: true,\n        warnings: true\n      };\n\n      reachedDone = true;\n      finished = true;\n      this.handler([\n        {\n          type: \"status\",\n          value: status\n        },\n        {\n          type: \"progress\",\n          value: 1\n        },\n        {\n          type: \"operations\",\n          value: `idle${getTimeMessage(timer)}`\n        },\n        {\n          type: \"stats\",\n          value: {\n            errors: stats.hasErrors(),\n            warnings: stats.hasWarnings(),\n            data: stats.toJson(statsJsonOptions)\n          }\n        },\n        {\n          type: \"log\",\n          value: stats.toString(statsOptions)\n        }\n      ]);\n\n      // Skip metrics in minimal mode.\n      const getMetrics = () => (this.minimal ? Promise.resolve() : this.getMetrics(stats));\n\n      // eslint-disable-next-line promise/catch-or-return\n      getMetrics()\n        .then(datas => this.handler(datas))\n        .catch(err => {\n          console.log(\"Error from inspectpack:\", err); // eslint-disable-line no-console\n        })\n        // eslint-disable-next-line promise/always-return\n        .then(() => {\n          this.cleanup();\n          done(); // eslint-disable-line promise/no-callback-in-promise\n        });\n    });\n  }\n\n  getMetrics(statsObj) {\n    // Get the **full** stats object here for `inspectpack` analysis.\n    const stats = statsObj.toJson({\n      source: true // Needed for webpack5+\n    });\n\n    // Truncate off non-included assets.\n    const { includeAssets } = this;\n    if (includeAssets.length) {\n      stats.assets = stats.assets.filter(({ name }) =>\n        includeAssets.some(pattern => {\n          if (typeof pattern === \"string\") {\n            return name.startsWith(pattern);\n          } else if (pattern instanceof RegExp) {\n            return pattern.test(name);\n          }\n\n          // Pass through bad options..\n          return false;\n        })\n      );\n    }\n\n    // Late destructure so that we can stub.\n    const { actions } = inspectpack;\n    const { serializeError } = serializer;\n\n    const getSizes = () =>\n      actions(\"sizes\", { stats })\n        .then(instance => instance.getData())\n        .then(data => ({\n          type: \"sizes\",\n          value: data\n        }))\n        .catch(err => ({\n          type: \"sizes\",\n          error: true,\n          value: serializeError(err)\n        }));\n\n    const getProblems = () =>\n      Promise.all(\n        INSPECTPACK_PROBLEM_ACTIONS.map(action =>\n          actions(action, { stats }).then(instance => instance.getData())\n        )\n      )\n        .then(datas => ({\n          type: INSPECTPACK_PROBLEM_TYPE,\n          value: INSPECTPACK_PROBLEM_ACTIONS.reduce(\n            (memo, action, i) =>\n              Object.assign({}, memo, {\n                [action]: datas[i]\n              }),\n            {}\n          )\n        }))\n        .catch(err => ({\n          type: INSPECTPACK_PROBLEM_TYPE,\n          error: true,\n          value: serializeError(err)\n        }));\n\n    return Promise.all([getSizes(), getProblems()]);\n  }\n}\n\nmodule.exports = DashboardPlugin;\n"
  },
  {
    "path": "test/.eslintrc.json",
    "content": "{\n  \"extends\": [\"formidable/configurations/es6-node-test\", \"plugin:prettier/recommended\"]\n}\n"
  },
  {
    "path": "test/base.spec.js",
    "content": "\"use strict\";\n\n/**\n * Base server unit test initialization / global before/after's.\n *\n * This file should be `require`'ed by all other test files.\n *\n * **Note**: Because there is a global sandbox server unit tests should always\n * be run in a separate process from other types of tests.\n */\nconst sinon = require(\"sinon\");\n\nconst blessed = require(\"neo-blessed\");\n\nconst base = (module.exports = {\n  sandbox: null\n});\n\nbeforeEach(() => {\n  base.sandbox = sinon.createSandbox({\n    useFakeTimers: true\n  });\n\n  // Stub out **all** of blessed so we don't end up in a terminal.\n  // Blessed is a `typeof` function, so manually iterate key.s\n  Object.keys(blessed)\n    .filter(key => typeof blessed[key] === \"function\")\n    .forEach(key => {\n      base.sandbox.stub(blessed, key);\n    });\n\n  // Some manual hacking.\n  blessed.screen.returns({\n    append: sinon.spy(),\n    key: sinon.spy(),\n    render: sinon.spy()\n  });\n\n  blessed.listbar.returns({\n    selected: \"selected\",\n    setLabel: sinon.spy(),\n    setProblems: sinon.spy(),\n    selectTab: sinon.spy(),\n    setItems: sinon.spy()\n  });\n\n  blessed.box.returns({\n    setContent: sinon.spy(),\n    setLabel: sinon.spy()\n  });\n\n  blessed.log.returns({\n    log: sinon.spy()\n  });\n\n  blessed.table.returns({\n    setData: sinon.spy()\n  });\n\n  blessed.ProgressBar.returns({\n    setContent: sinon.spy(),\n    setProgress: sinon.spy()\n  });\n});\n\nafterEach(() => {\n  base.sandbox.restore();\n});\n"
  },
  {
    "path": "test/bin/webpack-dashboard.spec.js",
    "content": "\"use strict\";\n\nconst base = require(\"../base.spec\");\n\nconst cli = require(\"../../bin/webpack-dashboard\");\n\ndescribe(\"bin/webpack-dashboard\", () => {\n  it(\"can invoke the dashboard cli\", () => {\n    expect(() =>\n      cli({\n        argv: [],\n        server: {\n          on: base.sandbox.spy()\n        }\n      })\n    ).to.not.throw();\n  });\n});\n"
  },
  {
    "path": "test/dashboard/index.spec.js",
    "content": "\"use strict\";\n\nconst chalk = require(\"chalk\");\nconst blessed = require(\"neo-blessed\");\n\nconst base = require(\"../base.spec\");\nconst Dashboard = require(\"../../dashboard\");\n\nconst mockSetItems = () => {\n  // Override ListBar fakes from what we do in `base.spec.js`.\n  // Note that these are **already** stubbed. We're not monkey-patching blessed.\n  blessed.listbar.returns({\n    selected: \"selected\",\n    setLabel: base.sandbox.spy(),\n    selectTab: base.sandbox.spy(),\n    setItems: base.sandbox.stub().callsFake(obj => {\n      // Naively simulate what setItems would do calling each object key.\n      Object.keys(obj).forEach(key => obj[key]());\n    })\n  });\n};\n\ndescribe(\"dashboard\", () => {\n  const options = {\n    color: \"red\",\n    minimal: true,\n    title: \"my-title\"\n  };\n\n  it(\"can create a new no option dashboard\", () => {\n    const dashboard = new Dashboard();\n    expect(dashboard).to.be.ok;\n    expect(dashboard.color).to.equal(\"green\");\n    expect(dashboard.minimal).to.be.false;\n    expect(dashboard.stats).to.be.null;\n  });\n\n  it(\"can create a new with options dashboard\", () => {\n    const dashboardWithOptions = new Dashboard(options);\n    expect(dashboardWithOptions).to.be.ok;\n    expect(dashboardWithOptions.color).to.equal(\"red\");\n    expect(dashboardWithOptions.minimal).to.be.true;\n  });\n\n  describe(\"set* methods\", () => {\n    let dashboard;\n\n    beforeEach(() => {\n      dashboard = new Dashboard();\n    });\n\n    describe(\"setData\", () => {\n      const dataArray = [\n        {\n          type: \"progress\",\n          value: 0.57\n        },\n        {\n          type: \"operations\",\n          value: \"IDLE\"\n        }\n      ];\n\n      it(\"can setData\", () => {\n        expect(() => dashboard.setData(dataArray)).to.not.throw;\n      });\n    });\n\n    describe(\"setOperations\", () => {\n      const data = {\n        value: \"IDLE\"\n      };\n\n      it(\"can setOperations\", () => {\n        expect(() => dashboard.setOperations(data)).to.not.throw;\n\n        dashboard.setOperations(data);\n        expect(dashboard.operations.setContent).to.have.been.calledWith(data.value);\n      });\n    });\n\n    describe(\"setStatus\", () => {\n      const data = {\n        value: \"Success\"\n      };\n\n      it(\"can setStatus\", () => {\n        expect(() => dashboard.setStatus(data)).to.not.throw;\n\n        dashboard.setStatus(data);\n        expect(dashboard.status.setContent).to.have.been.calledWith(\n          `{green-fg}{bold}${data.value}{/}`\n        );\n      });\n\n      it(\"should display a failed status on build failure\", () => {\n        data.value = \"Failed\";\n        expect(() => dashboard.setStatus(data)).to.not.throw;\n\n        dashboard.setStatus(data);\n        expect(dashboard.status.setContent).to.have.been.calledWith(\n          `{red-fg}{bold}${data.value}{/}`\n        );\n      });\n\n      it(\"should display any other status string without coloring\", () => {\n        data.value = \"Unknown\";\n        expect(() => dashboard.setStatus(data)).to.not.throw;\n\n        dashboard.setStatus(data);\n        expect(dashboard.status.setContent).to.have.been.calledWith(`{bold}${data.value}{/}`);\n      });\n    });\n\n    describe(\"setProgress\", () => {\n      const data = {\n        value: 0.57\n      };\n\n      it(\"can setProgress\", () => {\n        expect(() => dashboard.setProgress(data)).to.not.throw;\n\n        dashboard.setProgress(data);\n        expect(dashboard.progressbar.setProgress).to.have.been.calledOnce;\n        expect(dashboard.progressbar.setContent).to.have.been.called;\n      });\n\n      it(`should call progressbar.setProgress twice if not in minimal mode\n      and percent is falsy`, () => {\n        data.value = null;\n        expect(() => dashboard.setProgress(data)).to.not.throw;\n\n        dashboard.setProgress(data);\n        expect(dashboard.progressbar.setProgress).to.have.been.calledTwice;\n      });\n    });\n\n    describe(\"setStats\", () => {\n      const data = {\n        value: {\n          errors: null,\n          data: {\n            errors: [],\n            warnings: []\n          }\n        }\n      };\n\n      it(\"can setStats\", () => {\n        expect(() => dashboard.setStats(data)).not.to.throw;\n\n        dashboard.setStats(data);\n        expect(dashboard.logText.log).to.have.been.called;\n        expect(dashboard.modulesMenu.setLabel).to.have.been.calledWith(\n          chalk.yellow(\"Modules (loading...)\")\n        );\n        expect(dashboard.assets.setLabel).to.have.been.calledWith(\n          chalk.yellow(\"Assets (loading...)\")\n        );\n        expect(dashboard.problemsMenu.setLabel).to.have.been.calledWith(\n          chalk.yellow(\"Problems (loading...)\")\n        );\n      });\n\n      it(\"should display stats errors if present\", () => {\n        data.value.errors = [\"error\"];\n        expect(() => dashboard.setStats(data)).not.to.throw;\n\n        dashboard.setStats(data);\n        expect(dashboard.status.setContent).to.have.been.calledWith(\"{red-fg}{bold}Failed{/}\");\n      });\n    });\n\n    describe(\"setSizes\", () => {\n      const data = {\n        value: {\n          assets: {\n            foo: {\n              meta: {\n                full: 456\n              },\n              files: [\n                {\n                  size: {\n                    full: 123\n                  },\n                  fileName: \"test.js\",\n                  baseName: \"/home/bar/test.js\"\n                }\n              ]\n            },\n            bar: {\n              meta: {\n                full: 123\n              },\n              files: []\n            }\n          }\n        }\n      };\n\n      it(\"can setSizes\", () => {\n        const formattedData = [\n          [\"Name\", \"Size\"],\n          [\"foo\", \"456 B\"],\n          [\"bar\", \"123 B\"],\n          [\"Total\", \"579 B\"]\n        ];\n\n        expect(() => dashboard.setSizes(data)).to.not.throw;\n\n        dashboard.setSizes(data);\n        expect(dashboard.assets.setLabel).to.have.been.calledWith(\"Assets\");\n        expect(dashboard.assetTable.setData).to.have.been.calledWith(formattedData);\n        expect(dashboard.modulesMenu.setLabel).to.have.been.calledWith(\"Modules\");\n        expect(dashboard.modulesMenu.setItems).to.have.been.called;\n        expect(dashboard.modulesMenu.selectTab).to.have.been.calledWith(\n          dashboard.modulesMenu.selected\n        );\n        expect(dashboard.screen.render).to.have.been.called;\n      });\n\n      it(\"should call formatModules\", () => {\n        // Mock out the call to setItems to force call of formatModules.\n        mockSetItems();\n        // Discard generic dashboard, create a new one with adjusted mocks.\n        dashboard = new Dashboard();\n        expect(() => dashboard.setSizes(data)).to.not.throw;\n      });\n    });\n\n    describe(\"setSizesError\", () => {\n      const err = \"error\";\n\n      it(\"can setSizesError\", () => {\n        expect(() => dashboard.setSizesError(err)).to.not.throw;\n\n        dashboard.setSizesError(err);\n        expect(dashboard.modulesMenu.setLabel).to.have.been.calledWith(\n          chalk.red(\"Modules (error)\")\n        );\n        expect(dashboard.assets.setLabel).to.have.been.calledWith(chalk.red(\"Assets (error)\"));\n        expect(dashboard.logText.log).to.have.been.calledWith(\n          chalk.red(\"Could not load module/asset sizes.\")\n        );\n        expect(dashboard.logText.log).to.have.been.calledWith(chalk.red(err));\n      });\n    });\n\n    describe(\"setProblems\", () => {\n      const data = {\n        value: {\n          duplicates: {\n            assets: {\n              foo: \"foo\",\n              bar: \"bar\"\n            }\n          },\n          versions: {\n            assets: {\n              foo: \"1.2.3\",\n              bar: \"3.2.1\"\n            }\n          }\n        }\n      };\n\n      it(\"can setProblems\", () => {\n        expect(() => dashboard.setProblems(data)).to.not.throw;\n\n        dashboard.setProblems(data);\n        expect(dashboard.problemsMenu.setLabel).to.have.been.calledWith(\"Problems\");\n        expect(dashboard.problemsMenu.setItems).to.have.been.called;\n        expect(dashboard.problemsMenu.selectTab).to.have.been.calledWith(\n          dashboard.problemsMenu.selected\n        );\n        expect(dashboard.screen.render).to.have.been.called;\n      });\n\n      it(\"should call formatProblems\", () => {\n        // Mock out the call to setItems to force call of formatProblems.\n        mockSetItems();\n        // Discard generic dashboard, create a new one with adjusted mocks.\n\n        dashboard = new Dashboard();\n        expect(() => dashboard.setProblems(data)).to.not.throw;\n      });\n    });\n\n    describe(\"setProblemsError\", () => {\n      const err = { stack: \"stack\" };\n\n      it(\"can setProblemsError\", () => {\n        expect(() => dashboard.setProblemsError(err)).to.not.throw;\n\n        dashboard.setProblemsError(err);\n        expect(dashboard.problemsMenu.setLabel).to.have.been.calledWith(\n          chalk.red(\"Problems (error)\")\n        );\n        expect(dashboard.logText.log).to.have.been.calledWith(\n          chalk.red(\"Could not analyze bundle problems.\")\n        );\n        expect(dashboard.logText.log).to.have.been.calledWith(chalk.red(err.stack));\n      });\n    });\n\n    describe(\"setLog\", () => {\n      const data = { value: \"[{ log: 'log' }]\" };\n\n      it(\"can setLog\", () => {\n        expect(() => dashboard.setLog(data)).not.to.throw;\n\n        dashboard.setLog(data);\n        expect(dashboard.logText.log).to.have.been.calledWith(\"[ log: 'log' ]\");\n      });\n\n      it(\"should return early if the stats object has errors\", () => {\n        dashboard.stats = {};\n        dashboard.stats.hasErrors = () => true;\n        expect(dashboard.setLog(data)).to.be.undefined;\n\n        dashboard.setLog(data);\n        expect(dashboard.logText.log).to.not.have.been.called;\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "test/plugin/index.spec.js",
    "content": "\"use strict\";\n\nconst inspectpackActions = require(\"inspectpack/lib/actions\");\n\nconst base = require(\"../base.spec\");\nconst Plugin = require(\"../../plugin\");\nconst errorSerializer = require(\"../../utils/error-serialization\");\n\ndescribe(\"plugin\", () => {\n  const options = {\n    port: 3000,\n    host: \"111.0.2.3\"\n  };\n\n  it(\"can create a new no option plugin\", () => {\n    const plugin = new Plugin();\n    expect(plugin).to.be.ok;\n    expect(plugin.host).to.equal(\"127.0.0.1\");\n    // eslint-disable-next-line no-magic-numbers\n    expect(plugin.port).to.equal(9838);\n    expect(plugin._handler).to.be.null;\n    expect(plugin.watching).to.be.false;\n  });\n\n  it(\"can create a new with options dashboard\", () => {\n    const pluginWithOptions = new Plugin(options);\n    expect(pluginWithOptions.host).to.equal(\"111.0.2.3\");\n    // eslint-disable-next-line no-magic-numbers\n    expect(pluginWithOptions.port).to.equal(3000);\n  });\n\n  describe(\"plugin methods\", () => {\n    let stats;\n    let toJson;\n    let compilation;\n    let compiler;\n    let plugin;\n\n    beforeEach(() => {\n      stats = {\n        modules: [],\n        assets: []\n      };\n      toJson = base.sandbox.stub().callsFake(() => stats);\n      compilation = {\n        errors: [],\n        warnings: [],\n        getStats: () => ({ toJson }),\n        tap: base.sandbox.stub(),\n        tapAsync: base.sandbox.stub() // this is us in webpack-dashboard\n      };\n      compiler = {\n        // mock out webpack4 compiler, since that's what we have in devDeps\n        hooks: {\n          compilation,\n          emit: {\n            intercept: base.sandbox.stub()\n          },\n          watchRun: {\n            tapAsync: base.sandbox.stub()\n          },\n          run: {\n            tapAsync: base.sandbox.stub()\n          },\n          compile: {\n            tap: base.sandbox.stub()\n          },\n          failed: {\n            tap: base.sandbox.stub()\n          },\n          invalid: {\n            tap: base.sandbox.stub()\n          },\n          done: {\n            tap: base.sandbox.stub()\n          }\n        }\n      };\n\n      plugin = new Plugin();\n    });\n\n    it(\"can do a basic compilation\", () => {\n      expect(() => plugin.apply(compiler)).to.not.throw;\n\n      // after instantiation, test that we can hit getMetrics\n      expect(() => plugin.getMetrics({ toJson })).to.not.throw;\n    });\n\n    it(\"can do a basic getMetrics\", () => {\n      const actions = base.sandbox.spy(inspectpackActions, \"actions\");\n\n      return (\n        plugin\n          .getMetrics({ toJson })\n          // eslint-disable-next-line promise/always-return\n          .then(() => {\n            expect(actions).to.have.been.calledThrice;\n          })\n      );\n    });\n\n    it(\"filters assets for includeAssets\", () => {\n      const actions = base.sandbox.spy(inspectpackActions, \"actions\");\n\n      stats = {\n        assets: [\n          {\n            name: \"one.js\",\n            modules: []\n          },\n          {\n            name: \"two.js\",\n            modules: []\n          },\n          {\n            name: \"three.js\",\n            modules: []\n          }\n        ]\n      };\n\n      plugin = new Plugin({\n        includeAssets: [\n          \"one\", // string prefix\n          /tw/ // regex match\n        ]\n      });\n\n      return (\n        plugin\n          .getMetrics({ toJson })\n          // eslint-disable-next-line promise/always-return\n          .then(() => {\n            expect(actions).to.have.been.calledWith(\"sizes\", {\n              stats: {\n                assets: [\n                  { modules: [], name: \"one.js\" },\n                  { modules: [], name: \"two.js\" }\n                ]\n              }\n            });\n          })\n      );\n    });\n\n    it(\"should serialize errors when encountered\", () => {\n      const actions = base.sandbox.stub(inspectpackActions, \"actions\").rejects();\n      const serializeError = base.sandbox.spy(errorSerializer, \"serializeError\");\n\n      return (\n        plugin\n          .getMetrics({ toJson })\n          // eslint-disable-next-line promise/always-return\n          .then(() => {\n            // All three actions called.\n            expect(actions).to.have.been.calledThrice;\n            // ... but since two are in Promise.all only get one rejection.\n            expect(serializeError).to.have.been.calledTwice;\n          })\n      );\n    });\n  });\n});\n"
  },
  {
    "path": "test/setup.js",
    "content": "\"use strict\";\n\nconst chai = require(\"chai\");\nconst sinonChai = require(\"sinon-chai\");\n\n// Add chai plugins.\nchai.use(sinonChai);\n\n// Add test lib globals.\nglobal.expect = chai.expect;\n"
  },
  {
    "path": "test/utils/format-assets.spec.js",
    "content": "\"use strict\";\n\nconst { _getAssetSize, _getTotalSize, _printAssets } = require(\"../../utils/format-assets\");\n\ndescribe(\"format-assets\", () => {\n  describe(\"#_getAssetSize\", () => {\n    context(\"when asset size is present\", () => {\n      it(\"returns a readable file size as string\", () => {\n        const asset = {\n          size: 500\n        };\n        expect(_getAssetSize(asset)).to.equal(\"500 B\");\n      });\n    });\n\n    context(\"when no asset size is present\", () => {\n      it(\"returns zero in a readable file size as string\", () => {\n        const asset = {\n          size: undefined\n        };\n        expect(_getAssetSize(asset)).to.equal(\"0 B\");\n      });\n    });\n  });\n\n  describe(\"#_getTotalSize\", () => {\n    it(\"returns a readable file size of all assets as a string\", () => {\n      const assets = [{ size: 500 }, { size: undefined }, { size: 1000 }];\n      expect(_getTotalSize(assets)).to.equal(\"1.46 KB\");\n    });\n  });\n\n  describe(\"#_printAssets\", () => {\n    it(\"returns a nested array of assets information\", () => {\n      const assetList = [\n        {\n          name: \"assets1\",\n          size: 500\n        },\n        {\n          name: \"assets2\",\n          size: 0\n        },\n        {\n          name: \"assets2\",\n          size: 500\n        }\n      ];\n\n      const output = [\n        [\"Name\", \"Size\"],\n        [\"assets1\", \"500 B\"],\n        [\"assets2\", \"0 B\"],\n        [\"assets2\", \"500 B\"],\n        [\"Total\", \"1000 B\"]\n      ];\n      expect(_printAssets(assetList)).eql(output);\n    });\n  });\n});\n"
  },
  {
    "path": "test/utils/format-modules.spec.js",
    "content": "\"use strict\";\n\nconst { normalize, sep } = require(\"path\");\nconst { _formatFileName, _formatPercentage } = require(\"../../utils/format-modules\");\n\ndescribe(\"format-modules\", () => {\n  describe(\"#_formatFileName\", () => {\n    it(\"returns a blessed green colored file name\", () => {\n      const mod = {\n        fileName: normalize(\"foo/bar/test.js\")\n      };\n      expect(_formatFileName(mod)).to.equal(`{green-fg}.${sep}foo${sep}bar${sep}test.js{/}`);\n    });\n\n    context(\"when there is a baseName\", () => {\n      it(\"returns a blessed yellow colored file name\", () => {\n        const mod = {\n          fileName: \"test.js\",\n          baseName: normalize(\"/home/bar/test.js\")\n        };\n        expect(_formatFileName(mod)).to.equal(\"{yellow-fg}test.js{/}\");\n      });\n    });\n\n    context(\"when node_modules is present in fileName\", () => {\n      it(\"returns a blessed yellow colored file name\", () => {\n        const mod = {\n          fileName: normalize(\"/node_modules/@foo/test.js\"),\n          baseName: normalize(\"/home/bar/node_modules/@foo/test.js\")\n        };\n        expect(_formatFileName(mod)).to.equal(\n          `~${sep}{yellow-fg}@foo{/}${sep}{yellow-fg}test.js{/}`\n        );\n      });\n    });\n  });\n\n  describe(\"#_formatPercentage\", () => {\n    it(\"returns a precentage as a string\", () => {\n      // eslint-disable-next-line no-magic-numbers\n      expect(_formatPercentage(30, 15)).to.equal(\"200%\");\n    });\n  });\n});\n"
  },
  {
    "path": "test/utils/format-output.spec.js",
    "content": "\"use strict\";\n\nconst { _isLikelyASyntaxError, _lineJoin, _formatMessage } = require(\"../../utils/format-output\");\n\ndescribe(\"format-output\", () => {\n  describe(\"#_isLikelyASyntaxError\", () => {\n    context(\"when message is a syntax error\", () => {\n      it(\"returns true\", () => {\n        const message = \"Syntax error: missing ; before statement\";\n        expect(_isLikelyASyntaxError(message)).to.be.true;\n      });\n    });\n\n    context(\"when message is a type error\", () => {\n      it(\"returns false\", () => {\n        const message = \"Type error: null has no properties\";\n        expect(_isLikelyASyntaxError(message)).to.be.false;\n      });\n    });\n  });\n\n  describe(\"#_formatMessage\", () => {\n    it(\"returns a readable user friendly message\", () => {\n      const message1 = \"Module build failed: SyntaxError: missing ; before statement\";\n      const message2 = \"/Module not found: Error: Cannot resolve 'file' or 'directory'/\";\n      expect(_formatMessage(message1)).to.equal(\"Syntax error: missing ; before statement\");\n      expect(_formatMessage(message2)).to.equal(\"/Module not found:/\");\n    });\n  });\n\n  describe(\"#_lineJoin\", () => {\n    it(\"returns the elements of an array on a newline as a string\", () => {\n      const array = [\"word\", \"word2\", \"word3\"];\n      const output = \"word\\nword2\\nword3\";\n      expect(_lineJoin(array)).to.equal(output);\n    });\n  });\n});\n"
  },
  {
    "path": "test/utils/format-versions.spec.js",
    "content": "\"use strict\";\n\nconst formatVersions = require(\"../../utils/format-versions\");\n\ndescribe(\"format-versions\", () => {\n  describe(\"when package are present\", () => {\n    const data = {\n      packages: {\n        foo: {\n          \"1.1.1\": [\n            {\n              skews: {\n                parts: [\n                  { name: \"foo-dep\", range: \"^1.0.0\" },\n                  { name: \"bar\", range: \"^3.0.2\" }\n                ]\n              }\n            }\n          ]\n        }\n      }\n    };\n\n    it(\"should return a handlebar compile template\", () => {\n      const result =\n        // eslint-disable-next-line max-len\n        \"{yellow-fg}{underline}Version skews{/}\\n\\n{yellow-fg}{bold}foo{/}\\n  {green-fg}1.1.1{/}\\n    {cyan-fg}foo-dep{/}@^1.0.0 -> {cyan-fg}bar{/}@^3.0.2\\n\";\n      expect(formatVersions(data)).to.equal(result);\n    });\n  });\n\n  describe(\"when packages are not present\", () => {\n    it(\"should return an empty string\", () => {\n      const data = {\n        packages: []\n      };\n      expect(formatVersions(data)).to.equal(\"\");\n    });\n  });\n});\n"
  },
  {
    "path": "utils/error-serialization.js",
    "content": "\"use strict\";\n\nconst serializeError = err => ({\n  code: err.code,\n  message: err.message,\n  stack: err.stack\n});\n\nconst deserializeError = serializedError => {\n  const err = new Error();\n  err.code = serializedError.code;\n  err.message = serializedError.message;\n  err.stack = serializedError.stack;\n  return err;\n};\n\nmodule.exports = {\n  serializeError,\n  deserializeError\n};\n"
  },
  {
    "path": "utils/format-assets.js",
    "content": "\"use strict\";\n\n/**\n * Assets are the full emitted bundles.\n */\nconst filesize = require(\"filesize\");\n\nfunction _getAssetSize(asset) {\n  return filesize(asset.size || 0);\n}\n\nfunction _getTotalSize(assetsList) {\n  return filesize(assetsList.reduce((total, asset) => total + (asset.size || 0), 0));\n}\n\nfunction _printAssets(assetsList) {\n  return [[\"Name\", \"Size\"]]\n    .concat(assetsList.map(asset => [asset.name, _getAssetSize(asset)]))\n    .concat([[\"Total\", _getTotalSize(assetsList)]]);\n}\n\nfunction formatAssets(assets) {\n  // Convert to list.\n  const assetsList = Object.keys(assets).map(name => ({\n    name,\n    size: assets[name].meta.full\n  }));\n\n  return _printAssets(assetsList);\n}\n\nmodule.exports = { formatAssets, _getAssetSize, _getTotalSize, _printAssets };\n"
  },
  {
    "path": "utils/format-duplicates.js",
    "content": "\"use strict\";\n\n/**\n * Problem: Duplicate files (same path name) in a bundle.\n */\nconst filesize = require(\"filesize\");\nconst Handlebars = require(\"handlebars\");\n\nHandlebars.registerHelper(\"filesize\", function (options) {\n  // eslint-disable-next-line no-invalid-this\n  return filesize(options.fn(this));\n});\n\n/* eslint-disable max-len*/\nconst template = Handlebars.compile(\n  `{yellow-fg}{underline}Duplicate files{/}\n\n{{#each files}}\n- {green-fg}{{@key}}{/}\n  (files: {{meta.extraFiles.num}}, sources: {{meta.extraSources.num}}, bytes: {{#filesize}}{{meta.extraSources.bytes}}{{/filesize}})\n{{/each}}\n\nExtra duplicate files (unique): {{meta.extraFiles.num}}\nExtra duplicate sources (non-unique): {{meta.extraSources.num}}\nWasted duplicate bytes (non-unique): {{#filesize}}{{meta.extraSources.bytes}}{{/filesize}}\n`\n);\n/* eslint-enable max-len*/\n\nfunction formatDuplicates(duplicates) {\n  const haveDups = !!Object.keys((duplicates || {}).files || {}).length;\n  return haveDups ? template(duplicates) : \"\";\n}\n\nmodule.exports = formatDuplicates;\n"
  },
  {
    "path": "utils/format-modules.js",
    "content": "\"use strict\";\n\n/**\n * Modules are the individual files within an asset.\n */\nconst { relative, resolve, sep } = require(\"path\");\nconst filesize = require(\"filesize\");\n\nconst PERCENT_MULTIPLIER = 100;\nconst PERCENT_PRECISION = 3;\n\n// Convert to:\n// - existing source file name\n// - the path leading up to **just** the package (not including subpath).\nfunction _formatFileName(mod) {\n  const { fileName, baseName } = mod;\n\n  // Source file.\n  if (!baseName) {\n    return `{green-fg}.${sep}${relative(process.cwd(), resolve(fileName))}{/}`;\n  }\n\n  // Package\n  let parts = fileName.split(sep);\n  // Remove starting path.\n  const firstNmIdx = parts.indexOf(\"node_modules\");\n  parts = parts.slice(firstNmIdx);\n\n  // Remove trailing path after package.\n  const lastNmIdx = parts.lastIndexOf(\"node_modules\");\n  const isScoped = (parts[lastNmIdx + 1] || \"\").startsWith(\"@\");\n  parts = parts.slice(0, lastNmIdx + (isScoped ? 3 : 2)); // eslint-disable-line no-magic-numbers\n\n  return parts.map(part => (part === \"node_modules\" ? \"~\" : `{yellow-fg}${part}{/}`)).join(sep);\n}\n\nfunction _formatPercentage(modSize, assetSize) {\n  const percentage = ((modSize / assetSize) * PERCENT_MULTIPLIER).toPrecision(PERCENT_PRECISION);\n\n  return `${percentage}%`;\n}\n\nfunction formatModules(mods) {\n  // We _could_ use the `asset.meta.full` from inspectpack, but that is for\n  // the entire module with boilerplate included. We instead do a percentage\n  // of the files we're counting here.\n  const assetSize = mods.reduce((count, mod) => count + mod.size.full, 0);\n\n  // First, process the modules into a map to normalize file paths.\n  const modsMap = mods.reduce((memo, mod) => {\n    // File name collapses to packages for dependencies.\n    // Aggregate into object.\n    const fileName = _formatFileName(mod);\n\n    // Add in information.\n    memo[fileName] = memo[fileName] || {\n      fileName,\n      num: 0,\n      size: 0\n    };\n    memo[fileName].num += 1;\n    memo[fileName].size += mod.size.full;\n\n    return memo;\n  }, {});\n\n  return [].concat(\n    [[\"Name\", \"Size\", \"Percent\"]],\n    Object.keys(modsMap)\n      .map(fileName => modsMap[fileName])\n      .sort((a, b) => a.size < b.size) // sort largest first\n      .map(mod => [\n        `${mod.fileName} ${mod.num > 1 ? `(${mod.num})` : \"\"}`,\n        filesize(mod.size),\n        _formatPercentage(mod.size, assetSize)\n      ])\n  );\n}\n\nmodule.exports = {\n  formatModules,\n  _formatFileName,\n  _formatPercentage\n};\n"
  },
  {
    "path": "utils/format-output.js",
    "content": "\"use strict\";\n\nconst friendlySyntaxErrorLabel = \"Syntax error:\";\n\nfunction _isLikelyASyntaxError(message) {\n  return message.indexOf(friendlySyntaxErrorLabel) !== -1;\n}\n\nfunction _formatMessage(message = \"\") {\n  // Handle legacy and modern webpack shapes.\n  message = typeof message.message !== \"undefined\" ? message.message : message;\n\n  return message\n    .replace(\"Module build failed: SyntaxError:\", friendlySyntaxErrorLabel)\n    .replace(/Module not found: Error: Cannot resolve 'file' or 'directory'/, \"Module not found:\")\n    .replace(/^\\s*at\\s.*:\\d+:\\d+[\\s\\)]*\\n/gm, \"\")\n    .replace(\"./~/css-loader!./~/postcss-loader!\", \"\");\n}\n\nfunction _lineJoin(arr) {\n  return arr.join(\"\\n\");\n}\n\n// eslint-disable-next-line max-statements\nfunction formatOutput(stats) {\n  const output = [];\n  const hasErrors = stats.hasErrors();\n  const hasWarnings = stats.hasWarnings();\n\n  const json = stats.toJson({\n    source: true // Needed for webpack5+\n  });\n  let formattedErrors = json.errors.map(message => `Error in ${_formatMessage(message)}`);\n  const formattedWarnings = json.warnings.map(message => `Warning in ${_formatMessage(message)}`);\n\n  if (hasErrors) {\n    output.push(\"{red-fg}Failed to compile.{/}\");\n    output.push(\"\");\n    if (formattedErrors.some(_isLikelyASyntaxError)) {\n      formattedErrors = formattedErrors.filter(_isLikelyASyntaxError);\n    }\n    formattedErrors.forEach(message => {\n      output.push(message);\n      output.push(\"\");\n    });\n    return _lineJoin(output);\n  }\n\n  if (hasWarnings) {\n    output.push(\"{yellow-fg}Compiled with warnings.{/yellow-fg}\");\n    output.push(\"\");\n    formattedWarnings.forEach(message => {\n      output.push(message);\n      output.push(\"\");\n    });\n\n    return _lineJoin(output);\n  }\n\n  output.push(\"{green-fg}Compiled successfully!{/}\");\n  output.push(\"\");\n\n  return _lineJoin(output);\n}\n\nmodule.exports = { formatOutput, _formatMessage, _isLikelyASyntaxError, _lineJoin };\n"
  },
  {
    "path": "utils/format-problems.js",
    "content": "\"use strict\";\n\nconst formatDuplicates = require(\"./format-duplicates\");\nconst formatVersions = require(\"./format-versions\");\n\nfunction formatProblems(data) {\n  const duplicates = formatDuplicates(data.duplicates);\n  const versions = formatVersions(data.versions);\n\n  if (!duplicates && !versions) {\n    return \"{green-fg}No problems detected!{/}\";\n  }\n  if (duplicates && !versions) {\n    return `{green-fg}No version skews!{/}\\n\\n${duplicates}`;\n  }\n  if (!duplicates && versions) {\n    return `{green-fg}No duplicate files!{/}\\n\\n${versions}`;\n  }\n\n  return `${duplicates}\\n${versions}`;\n}\n\nmodule.exports = { formatProblems };\n"
  },
  {
    "path": "utils/format-versions.js",
    "content": "\"use strict\";\n\nconst Handlebars = require(\"handlebars\");\n\n// From inspectpack.\nconst pkgNamePath = pkgParts =>\n  pkgParts.reduce((m, part) => `${m}${m ? \" -> \" : \"\"}{cyan-fg}${part.name}{/}@${part.range}`, \"\");\n\nHandlebars.registerHelper(\"skew\", function (options) {\n  // eslint-disable-next-line no-invalid-this\n  return pkgNamePath(options.fn(this));\n});\n\nconst template = Handlebars.compile(\n  `{yellow-fg}{underline}Version skews{/}\n\n{{#each packages}}\n{yellow-fg}{bold}{{@key}}{/}\n  {{#each this}}\n  {green-fg}{{@key}}{/}\n    {{#each this}}\n      {{#each skews}}\n    {{#skew}}{{{this}}}{{/skew}}\n      {{/each}}\n    {{/each}}\n  {{/each}}\n{{/each}}\n`\n);\n\nfunction formatVersions(versions) {\n  const haveSkews = !!Object.keys((versions || {}).packages || {}).length;\n  return haveSkews ? template(versions) : \"\";\n}\n\nmodule.exports = formatVersions;\n"
  }
]