master 9333a27e4b8f cached
52 files
86.7 KB
22.9k tokens
58 symbols
1 requests
Download .txt
Repository: FormidableLabs/webpack-dashboard
Branch: master
Commit: 9333a27e4b8f
Files: 52
Total size: 86.7 KB

Directory structure:
gitextract_wps1zf6f/

├── .changeset/
│   ├── cold-wolves-occur.md
│   └── config.json
├── .editorconfig
├── .eslintignore
├── .eslintrc.json
├── .github/
│   └── workflows/
│       ├── ci.yml
│       └── release.yml
├── .gitignore
├── .mocharc.yml
├── .npmignore
├── .nycrc
├── .prettierignore
├── .prettierrc
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── ISSUE_TEMPLATE.md
├── LICENSE
├── README.md
├── bin/
│   └── webpack-dashboard.js
├── dashboard/
│   └── index.js
├── docs/
│   └── getting-started.md
├── examples/
│   ├── .eslintrc.json
│   ├── config/
│   │   ├── webpack.config.js
│   │   └── webpack.config.ts
│   ├── duplicates-esm/
│   │   ├── package.json
│   │   └── src/
│   │       └── index.js
│   ├── simple/
│   │   ├── package.json
│   │   └── src/
│   │       └── index.js
│   └── tree-shaking/
│       ├── package.json
│       └── src/
│           └── index.js
├── index.js
├── package.json
├── plugin/
│   ├── index.d.ts
│   └── index.js
├── test/
│   ├── .eslintrc.json
│   ├── base.spec.js
│   ├── bin/
│   │   └── webpack-dashboard.spec.js
│   ├── dashboard/
│   │   └── index.spec.js
│   ├── plugin/
│   │   └── index.spec.js
│   ├── setup.js
│   └── utils/
│       ├── format-assets.spec.js
│       ├── format-modules.spec.js
│       ├── format-output.spec.js
│       └── format-versions.spec.js
└── utils/
    ├── error-serialization.js
    ├── format-assets.js
    ├── format-duplicates.js
    ├── format-modules.js
    ├── format-output.js
    ├── format-problems.js
    └── format-versions.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .changeset/cold-wolves-occur.md
================================================
---
"webpack-dashboard": patch
---

'#359 update node version to 18 in github actions workflows and github actions versions'


================================================
FILE: .changeset/config.json
================================================
{
	"$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json",
	"changelog": [
		"@svitejs/changesets-changelog-github-compact",
		{
			"repo": "FormidableLabs/webpack-dashboard"
		}
	],
	"access": "public",
	"baseBranch": "master"
}

================================================
FILE: .editorconfig
================================================
# editorconfig.org
root = true

[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
max_line_length = 100

[*.md]
trim_trailing_whitespace = false

================================================
FILE: .eslintignore
================================================
dist-*

================================================
FILE: .eslintrc.json
================================================
{
  "extends": ["formidable/configurations/es6-node", "plugin:prettier/recommended"],
  "rules": {
    "func-style": "off",
    "arrow-parens": ["error", "as-needed"]
  }
}


================================================
FILE: .github/workflows/ci.yml
================================================
name: CI

on:
  push:
    branches:
      - master
  pull_request:
    branches:
      - master

jobs:
  build:

    runs-on: ${{ matrix.os }}

    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest]
        node-version: [18.x]

    steps:
    # Setup
    - uses: actions/checkout@v4
    - name: Use Node.js ${{ matrix.node-version }}
      uses: actions/setup-node@v4
      with:
        cache: "yarn"
        node-version: ${{ matrix.node-version }}

    # Installation
    - run: yarn --version
    - run: yarn install --frozen-lockfile
      env:
        CI: true

    # CI
    - run: yarn check-ci
    # Test
    - run: yarn test
    # Code coverage
    - run: yarn codecov


================================================
FILE: .github/workflows/release.yml
================================================
name: Release
on:
  push:
    branches:
      - master
jobs:
  release:
    name: Release
    runs-on: ubuntu-latest
    permissions:
      contents: write
      id-token: write
      issues: write
      repository-projects: write
      deployments: write
      packages: write
      pull-requests: write
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          cache: "yarn"
          node-version: 18
      - name: Install dependencies
        run: yarn install --frozen-lockfile

      - name: Check CI
        run: yarn check-ci
      - name: Unit Tests
        run: yarn test

      - name: PR or Publish
        id: changesets
        uses: changesets/action@v1
        with:
          version: yarn changeset version
          publish: yarn changeset publish
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}


================================================
FILE: .gitignore
================================================
# dependencies
/node_modules/

# misc
.DS_Store
npm-debug.log*
.nyc_output
.coverage
yarn-error.log
package-lock.json
dist-*
.vscode
.lankrc.js


================================================
FILE: .mocharc.yml
================================================
require: "./test/setup.js"
recursive: true


================================================
FILE: .npmignore
================================================
/*
!/bin
!/dashboard
!/plugin
!/utils
!LICENSE
!CHANGELOG.md
!README.md
!package.json
!index.js
!index.d.ts


================================================
FILE: .nycrc
================================================
{
  "reporter": [
    "html",
    "lcov",
    "text"
  ],
  "report-dir": "./.coverage"
}


================================================
FILE: .prettierignore
================================================
examples/**/dist-*/*.js
examples/**/dist-*/*.json

================================================
FILE: .prettierrc
================================================
{
  "arrowParens": "avoid",
  "trailingComma": "none",
  "endOfLine": "auto"
}


================================================
FILE: CHANGELOG.md
================================================
# Changelog

## 3.3.8

### Patch Changes

- Adding GitHub release workflow ([#354](https://github.com/FormidableLabs/webpack-dashboard/pull/354))

## 3.3.7

- Bug: Move plugin types and update to webpack v5. [#324](https://github.com/FormidableLabs/webpack-dashboard/issues/324)

## 3.3.6

- 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)

## [3.3.5] - 2021-07-12

- Chore: Update dependencies. [#333](https://github.com/FormidableLabs/webpack-dashboard/issues/333)
- Coverage: Add CodeCov stats. [#206](https://github.com/FormidableLabs/webpack-dashboard/issues/206)
- CI: Update Node matrix to 12/14/16.

## [3.3.4] - 2021-07-12

- Chore: Refactor internal stats consumption to perform `inspectpack` analysis in the main thread, without using `main` streams.
- 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.
- 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).

## [3.3.3] - 2021-05-05

- 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.

## [3.3.2] - 2021-05-05

- Empty publish.

## [3.3.1] - 2021-01-29

- Bugfix: Ensure `Status` is properly updating and reaches completion. Fixes #321

## [3.3.0] - 2021-01-21

- Add `webpack@5` support. Closes #316
- Bugfix: `webpack@5` warning message conflict. Fixes #314
- Update various production dependencies.

## [3.2.1] - 2020-08-24

- Add missing dependency on `chalk`. Included in: https://github.com/FormidableLabs/webpack-dashboard/pull/309 by @am-a.

## [3.2.0] - 2019-09-08

- Add left / right navigation keys to assets in Modules and Problems screens. Included in: https://github.com/FormidableLabs/webpack-dashboard/pull/288 by @wapgear.

## [3.1.0] - 2019-08-27

- Add `DashboardPlugin({ includeAssets: [ "stringPrefix", /regexObj/ ] })` Webpack plugin filtering option.
- Add `webpack-dashboard --include-assets stringPrefix1 -a stringPrefix2` CLI filtering option.
- Change `"mode"` SocketIO event to `"options"` as it now passes both `minimal` and `includeAssets` from CLI to the Webpack plugin.
- Fix unit tests that incorrectly relied on `.complete()` for `most` observables.
- Add additional `examples` fixture for development.

## [3.0.7] - 2019-05-15

### Features

- 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.
- Add AppVeyor for Windows builds in CI. Included in: https://github.com/FormidableLabs/webpack-dashboard/pull/284 by @ryan-roemer.

### Migration Instructions

No changes required to start using v3.0.7 🎉.

## [3.0.6] - 2019-05-09

### Features

- 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.

### Migration Instructions

No changes required to start using v3.0.6 🎉.

## [3.0.5] - 2019-04-24

### Features

- Use `npm-run-all` as task runner for `package.json` scripts. Included in: https://github.com/FormidableLabs/webpack-dashboard/pull/283.
- Use `test` in lieu of `test-summary` for `nyc` coverage reporting on command line. Included in: https://github.com/FormidableLabs/webpack-dashboard/pull/283.

### Security

- Address `handlebars` security vulnerability. Included in: https://github.com/FormidableLabs/webpack-dashboard/pull/282 by @juliusl.
- Address additional security vulnerabilities in `js-yaml`. Included in: https://github.com/FormidableLabs/webpack-dashboard/pull/283.

### Migration Instructions

No changes required to start using v3.0.5 🎉.

## [3.0.4] - 2019-04-24 [DEPRECATED]

`v3.0.4` was an erroneous publish.

## [3.0.3] - 2019-04-18

### Bugs

- **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

### Migration Instructions

No changes required to start using v3.0.3 🎉.

## [3.0.2] - 2019-03-28

### Features

- 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

### Migration Instructions

No changes required to start using v3.0.2 🎉.

## [3.0.1] - 2019-03-26

### Features

- Use `process.kill` with `SIGINT` to gracefully exit the dashboard process. Included in: https://github.com/FormidableLabs/webpack-dashboard/pull/277 by @joakimbeng.
- Update dependencies to address security warnings. Included in: https://github.com/FormidableLabs/webpack-dashboard/pull/275 by @stereobooster.

### Migration Instructions

No changes required to start using v3.0.1 🎉. We do recommend adopting this patch as soon as possible to get the security upgrades.

## [3.0.0] - 2019-02-14

### Features

- 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
- Added Prettier to the codebase 🎉 Included in: https://github.com/FormidableLabs/webpack-dashboard/pull/270

### Docs

- Added a warning about deprecation of Node 6 support. Included in: https://github.com/FormidableLabs/webpack-dashboard/pull/270

### Migration Instructions

With 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.

## [2.1.0] - 2019-01-29

### Features

- 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
- Migrated to `inspectpack@4`. Included in: https://github.com/FormidableLabs/webpack-dashboard/pull/263
- Added TypeScript defitions. Included in: https://github.com/FormidableLabs/webpack-dashboard/pull/269

### Tests

- Added regression tests to fix an unknown import issue for our `format-*` utils. Included in: https://github.com/FormidableLabs/webpack-dashboard/pull/263
- Added tests for all `Dashboard` methods. Included in: https://github.com/FormidableLabs/webpack-dashboard/pull/263

### Docs

- 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

### Migration Instructions

No changes required to start using v2.1.0 🎉


================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributing to Webpack-Dashboard

## Contributor Covenant Code of Conduct

### Our Pledge

In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of experience,
nationality, personal appearance, race, religion, or sexual identity and
orientation.

### Our Standards

Examples of behavior that contributes to creating a positive environment
include:

- Using welcoming and inclusive language
- Being respectful of differing viewpoints and experiences
- Gracefully accepting constructive criticism
- Focusing on what is best for the community
- Showing empathy towards other community members

Examples of unacceptable behavior by participants include:

- The use of sexualized language or imagery and unwelcome sexual attention or
  advances
- Trolling, insulting/derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or electronic
  address, without explicit permission
- Other conduct which could reasonably be considered inappropriate in a
  professional setting

### Our Responsibilities

Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.

Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.

### Scope

This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.

### Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at lauren.eastridge@formidable.com. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.

Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.

### Attribution

This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at [http://contributor-covenant.org/version/1/4][version]

[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/


================================================
FILE: CONTRIBUTING.md
================================================
## Development

### Installing dependencies

```sh
yarn install
```

### Testing

You will find tests for files colocated with `*.test.ts` suffixes. Whenever making any changes, ensure that all existing tests pass by running `yarn test`.

If you are adding a new feature or some extra functionality, you should also make sure to accompany those changes with appropriate tests.

### Linting and Formatting

Before 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.

### Before submitting a PR...

Thanks 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:

- Run the tests using `yarn test`
- Run lint and flow using `yarn lint`
- Run `yarn changeset`

### Using changesets

Our official release path is to use automation to perform the actual publishing of our packages. The steps are to:

1. A human developer adds a changeset. Ideally this is as a part of a PR that will have a version impact on a package.
2. On merge of a PR our automation system opens a "Version Packages" PR.
3. On merging the "Version Packages" PR, the automation system publishes the packages.

Here are more details:

### Add a changeset

When you would like to add a changeset (which creates a file indicating the type of change), in your branch/PR issue this command:

```sh
$ yarn changeset
```

to 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:

1. Aim for a single line, 1+ sentences as appropriate.
2. Include issue links in GH format (e.g. `#123`).
3. You don't need to reference the current pull request or whatnot, as that will be added later automatically.

After this, you'll see a new uncommitted file in `.changesets` like:

```sh
$ git status
# ....
Untracked files:
  (use "git add <file>..." to include in what will be committed)
	.changeset/flimsy-pandas-marry.md
```

Review 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!

### Creating versions

On 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.

### Publishing packages

On the merge of a version packages PR, the changesets GitHub action will publish the packages to npm.


================================================
FILE: ISSUE_TEMPLATE.md
================================================
#### Please provide a description and details of the bug / issue below:

---

#### If the issue is visual, please provide screenshots here

---

#### Steps to reproduce the problem

---

#### Please provide a gist of relevant files

1. package.json (specifically the script you are using to start the dashboard)
2. webpack.config.js
3. index.js (Your express based dev server, if applicable)

---

#### More Details

- What operating system are you on?
- What terminal application are you using?
- What version of webpack-dashboard are you using?
- What is the output of running `echo $TERM`?


================================================
FILE: LICENSE
================================================
The MIT License (MIT)

Copyright (c) 2016-present, Formidable Labs. All rights reserved.

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

================================================
FILE: README.md
================================================
[![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/)

[![npm version][npm_img]][npm_site]
[![Actions Status][actions_img]][actions_site]
[![Coverage Status][cov_img]][cov_site]
[![Maintenance Status][maintenance-image]](#maintenance-status)

A CLI dashboard for your webpack dev server

### What's this all about?

When using webpack, especially for a dev server, you are probably used to seeing something like this:

![https://i.imgur.com/p1uAqkD.png](https://i.imgur.com/p1uAqkD.png)

That'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:

![https://i.imgur.com/qL6dXJd.png](https://i.imgur.com/qL6dXJd.png)

### Install

```sh
$ npm install --save-dev webpack-dashboard
# ... or ...
$ yarn add --dev webpack-dashboard
```

> ℹ️ **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.

### Use

**`webpack-dashboard@^3.0.0` requires Node 8 or above.** Previous versions support down to Node 6.

First, import the plugin and add it to your webpack config:

```js
// Import the plugin:
const DashboardPlugin = require("webpack-dashboard/plugin");

// Add it to your webpack configuration plugins.
module.exports = {
  // ...
  plugins: [new DashboardPlugin()];
  // ...
};
```

Then, modify your dev server start script previously looked like:

```js
"scripts": {
    "dev": "node index.js", # OR
    "dev": "webpack-dev-server", # OR
    "dev": "webpack",
}
```

You would change that to:

```js
"scripts": {
    "dev": "webpack-dashboard -- node index.js", # OR
    "dev": "webpack-dashboard -- webpack-dev-server", # OR
    "dev": "webpack-dashboard -- webpack",
}
```

Now 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.

#### Customizations

More configuration customization examples can be found in our [getting started](./docs/getting-started.md) guide.

For 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:

```js
plugins: [new DashboardPlugin({ port: 3001 })];
```

Then, you would pass it along to the CLI to match:

```sh
$ webpack-dashboard --port 3001 -- webpack
```

> ⚠️ **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.

### Run it

Finally, 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.

Then, sit back and pretend you're an astronaut.

### Supported Operating Systems and Terminals

**macOS →**
Webpack 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.

**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.

**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.

### API

#### webpack-dashboard (CLI)

##### Options

- `-c, --color [color]` - Custom ANSI color for your dashboard
- `-m, --minimal` - Runs the dashboard in minimal mode
- `-t, --title [title]` - Set title of terminal window
- `-p, --port [port]` - Custom port for socket communication server
- `-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)

##### Arguments

`[command]` - The command you want to run, i.e. `webpack-dashboard -- node index.js`

#### Webpack plugin

#### Options

- `host` - Custom host for connection the socket client
- `port` - Custom port for connecting the socket client
- `includeAssets` - Limit display to asset names matching string prefix or regex (`Array<String | RegExp>`)
- `handler` - Plugin handler method, i.e. `dashboard.setData`

_Note: you can also just pass a function in as an argument, which then becomes the handler, i.e. `new DashboardPlugin(dashboard.setData)`_

### Local Development

We'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. 🚀

To run this repo locally against our provided examples, take the usual steps.

```sh
yarn
yarn dev
```

We 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`.

To 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:

```sh
$ cross-env EXAMPLE=duplicates-esm \
  node bin/webpack-dashboard.js -- \
  webpack-cli --config examples/config/webpack.config.js --watch
```

to this:

```sh
$ cross-env EXAMPLE=tree-shaking WEBPACK_MODE=production \
  node bin/webpack-dashboard.js -- \
  webpack-cli --config examples/config/webpack.config.js --watch
```

Then just run `yarn dev` to get up and running. PRs are very much appreciated!

## Contributing

Please see our [contributing guide](CONTRIBUTING.MD).

#### Credits

Module output deeply inspired by: [https://github.com/robertknight/webpack-bundle-size-analyzer](https://github.com/robertknight/webpack-bundle-size-analyzer)

Error output deeply inspired by: [https://github.com/facebookincubator/create-react-app](https://github.com/facebookincubator/create-react-app)

#### Maintenance Status

**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.

[maintenance-image]: https://img.shields.io/badge/maintenance-active-green.svg?color=brightgreen&style=flat
[npm_img]: https://img.shields.io/npm/v/webpack-dashboard.svg?style=flat
[npm_site]: https://www.npmjs.com/package/webpack-dashboard
[actions_img]: https://github.com/FormidableLabs/webpack-dashboard/workflows/CI/badge.svg
[actions_site]: https://github.com/FormidableLabs/webpack-dashboard/actions
[cov_img]: https://codecov.io/gh/FormidableLabs/webpack-dashboard/branch/master/graph/badge.svg
[cov_site]: https://codecov.io/gh/FormidableLabs/webpack-dashboard


================================================
FILE: bin/webpack-dashboard.js
================================================
#!/usr/bin/env node

"use strict";

const commander = require("commander");
const spawn = require("cross-spawn");
const Dashboard = require("../dashboard/index");
const io = require("socket.io");

const DEFAULT_PORT = 9838;

const pkg = require("../package.json");

const collect = (val, prev) => prev.concat([val]);

// Wrap up side effects in a script.
// eslint-disable-next-line max-statements, complexity
const main = opts => {
  opts = opts || {};
  const argv = typeof opts.argv === "undefined" ? process.argv : opts.argv;
  const isWindows = process.platform === "win32";

  const program = new commander.Command("webpack-dashboard")
    .version(pkg.version)
    .option("-c, --color [color]", "Dashboard color")
    .option("-m, --minimal", "Minimal mode")
    .option("-t, --title [title]", "Terminal window title")
    .option("-p, --port [port]", "Socket listener port")
    .option("-a, --include-assets [string prefix]", "Asset names to limit to", collect, [])
    .usage("[options] -- [script] [arguments]")
    .parse(argv);

  const cliOpts = program.opts();
  const cliArgs = program.args;

  let logFromChild = true;
  let child;

  if (!cliArgs.length) {
    logFromChild = false;
  }

  if (logFromChild) {
    const command = cliArgs[0];
    const args = cliArgs.slice(1);
    const env = process.env;

    env.FORCE_COLOR = true;

    child = spawn(command, args, {
      env,
      stdio: [null, null, null, null],
      detached: !isWindows
    });
  }

  const dashboard = new Dashboard({
    color: cliOpts.color || "green",
    minimal: cliOpts.minimal || false,
    title: cliOpts.title || null
  });

  const port = parseInt(cliOpts.port || DEFAULT_PORT, 10);
  const server = opts.server || io(port);

  server.on("error", err => {
    // eslint-disable-next-line no-console
    console.log(err);
  });

  if (logFromChild) {
    server.on("connection", socket => {
      socket.emit("options", {
        minimal: cliOpts.minimal || false,
        includeAssets: cliOpts.includeAssets || []
      });

      socket.on("message", (message, ack) => {
        // Note: `message` may be null.
        // https://github.com/FormidableLabs/webpack-dashboard/issues/335
        if (message && message.type !== "log") {
          dashboard.setData(message, ack);
        }
      });
    });

    child.stdout.on("data", data => {
      dashboard.setData([
        {
          type: "log",
          value: data.toString("utf8")
        }
      ]);
    });

    child.stderr.on("data", data => {
      dashboard.setData([
        {
          type: "log",
          value: data.toString("utf8")
        }
      ]);
    });

    process.on("exit", () => {
      process.kill(isWindows ? child.pid : -child.pid);
    });
  } else {
    server.on("connection", socket => {
      socket.on("message", (message, ack) => {
        dashboard.setData(message, ack);
      });
    });
  }
};

if (require.main === module) {
  main();
}

module.exports = main;


================================================
FILE: dashboard/index.js
================================================
"use strict";

const chalk = require("chalk");
const blessed = require("neo-blessed");

const { formatOutput } = require("../utils/format-output");
const { formatModules } = require("../utils/format-modules");
const { formatAssets } = require("../utils/format-assets");
const { formatProblems } = require("../utils/format-problems");
const { deserializeError } = require("../utils/error-serialization");

const PERCENT_MULTIPLIER = 100;

const DEFAULT_SCROLL_OPTIONS = {
  scrollable: true,
  input: true,
  alwaysScroll: true,
  scrollbar: {
    ch: " ",
    inverse: true
  },
  keys: true,
  vi: true,
  mouse: true
};

class Dashboard {
  // eslint-disable-next-line max-statements
  constructor(options) {
    // Options, params
    options = options || {};
    const title = options.title || "webpack-dashboard";

    this.color = options.color || "green";
    this.minimal = options.minimal || false;
    this.stats = null;

    // Data binding, lookup tables.
    this.actionForMessageType = {
      progress: this.setProgress.bind(this),
      operations: this.setOperations.bind(this),
      status: this.setStatus.bind(this),
      stats: this.setStats.bind(this),
      log: this.setLog.bind(this),
      clear: this.clear.bind(this),
      sizes: _data => {
        if (this.minimal) {
          return;
        }
        if (_data.value instanceof Error) {
          this.setSizesError(_data.value);
        } else {
          this.setSizes(_data);
        }
      },
      problems: _data => {
        if (this.minimal) {
          return;
        }
        if (_data.value instanceof Error) {
          this.setProblemsError(_data.value);
        } else {
          this.setProblems(_data);
        }
      }
    };

    // Start UI stuff.
    this.screen = blessed.screen({
      title,
      smartCSR: true,
      dockBorders: false,
      fullUnicode: true,
      autoPadding: true
    });

    this.layoutLog();
    this.layoutStatus();

    if (!this.minimal) {
      this.layoutModules();
      this.layoutAssets();
      this.layoutProblems();
    }

    this.screen.key(["escape", "q", "C-c"], () => {
      process.kill(process.pid, "SIGINT");
    });

    this.screen.render();
  }

  setData(dataArray, ack) {
    dataArray
      .map(data =>
        data.error
          ? Object.assign({}, data, {
              value: deserializeError(data.value)
            })
          : data
      )
      .forEach(data => {
        this.actionForMessageType[data.type](data);
      });

    this.screen.render();

    // Send ack back if requested.
    if (ack) {
      ack();
    }
  }

  setProgress(data) {
    const percent = parseInt(data.value * PERCENT_MULTIPLIER, 10);
    const formattedPercent = `${percent.toString()}%`;

    if (!percent) {
      this.progressbar.setProgress(percent);
    }

    if (this.minimal) {
      this.progress.setContent(formattedPercent);
    } else {
      this.progressbar.setContent(formattedPercent);
      this.progressbar.setProgress(percent);
    }
  }

  setOperations(data) {
    this.operations.setContent(data.value);
  }

  setStatus(data) {
    let content;

    switch (data.value) {
      case "Success":
        content = `{green-fg}{bold}${data.value}{/}`;
        break;
      case "Failed":
        content = `{red-fg}{bold}${data.value}{/}`;
        break;
      case "Error":
        content = `{red-fg}{bold}${data.value}{/}`;
        break;
      default:
        content = `{bold}${data.value}{/}`;
    }
    this.status.setContent(content);
  }

  setStats(data) {
    const stats = {
      hasErrors: () => data.value.errors,
      hasWarnings: () => data.value.warnings,
      toJson: () => data.value.data
    };

    // Save for later when merging inspectpack sizes into the asset list
    this.stats = stats;

    if (stats.hasErrors()) {
      this.status.setContent("{red-fg}{bold}Failed{/}");
    }

    this.logText.log(formatOutput(stats));

    if (!this.minimal) {
      this.modulesMenu.setLabel(chalk.yellow("Modules (loading...)"));
      this.assets.setLabel(chalk.yellow("Assets (loading...)"));
      this.problemsMenu.setLabel(chalk.yellow("Problems (loading...)"));
    }
  }

  setSizes(data) {
    const { assets } = data.value;

    // Start with top-level assets.
    this.assets.setLabel("Assets");
    this.assetTable.setData(formatAssets(assets));

    // Then split modules across assets.
    const previousSelection = this.modulesMenu.selected;
    const modulesItems = Object.keys(assets).reduce(
      (memo, name) =>
        Object.assign({}, memo, {
          [name]: () => {
            this.moduleTable.setData(formatModules(assets[name].files));
            this.screen.render();
          }
        }),
      {}
    );

    this.modulesMenu.setLabel("Modules");
    this.modulesMenu.setItems(modulesItems);
    this.modulesMenu.selectTab(previousSelection);

    // Final render.
    this.screen.render();
  }

  setSizesError(err) {
    this.modulesMenu.setLabel(chalk.red("Modules (error)"));
    this.assets.setLabel(chalk.red("Assets (error)"));
    this.logText.log(chalk.red("Could not load module/asset sizes."));
    this.logText.log(chalk.red(err));
  }

  setProblems(data) {
    const { duplicates, versions } = data.value;

    // Separate across assets.
    // Use duplicates as the "canary" to get asset names.
    const assetNames = Object.keys(duplicates.assets);

    const previousSelection = this.problemsMenu.selected;
    const problemsItems = assetNames.reduce(
      (memo, name) =>
        Object.assign({}, memo, {
          [name]: () => {
            this.problems.setContent(
              formatProblems({
                duplicates: duplicates.assets[name],
                versions: versions.assets[name]
              })
            );
            this.screen.render();
          }
        }),
      {}
    );

    this.problemsMenu.setLabel("Problems");
    this.problemsMenu.setItems(problemsItems);
    this.problemsMenu.selectTab(previousSelection);

    this.screen.render();
  }

  setProblemsError(err) {
    this.problemsMenu.setLabel(chalk.red("Problems (error)"));
    this.logText.log(chalk.red("Could not analyze bundle problems."));
    this.logText.log(chalk.red(err.stack));
  }

  setLog(data) {
    if (this.stats && this.stats.hasErrors()) {
      return;
    }
    this.logText.log(data.value.replace(/[{}]/g, ""));
  }

  clear() {
    this.logText.setContent("");
  }

  layoutLog() {
    this.log = blessed.box({
      label: "Log",
      padding: 1,
      width: this.minimal ? "100%" : "75%",
      height: this.minimal ? "70%" : "36%",
      left: "0%",
      top: "0%",
      border: {
        type: "line"
      },
      style: {
        fg: -1,
        border: {
          fg: this.color
        }
      }
    });

    this.logText = blessed.log(
      Object.assign({}, DEFAULT_SCROLL_OPTIONS, {
        parent: this.log,
        tags: true,
        width: "100%-5"
      })
    );

    this.screen.append(this.log);
    this.mapNavigationKeysToScrollLog();
  }

  mapNavigationKeysToScrollLog() {
    this.screen.key(["pageup"], () => {
      this.logText.setScrollPerc(0);
      this.logText.screen.render();
    });
    this.screen.key(["pagedown"], () => {
      // eslint-disable-next-line no-magic-numbers
      this.logText.setScrollPerc(100);
      this.logText.screen.render();
    });
    this.screen.key(["up"], () => {
      this.logText.scroll(-1);
      this.logText.screen.render();
    });
    this.screen.key(["down"], () => {
      this.logText.scroll(1);
      this.logText.screen.render();
    });
    this.screen.key(["left"], () => {
      const currentIndex = this.modulesMenu.selected;
      this.modulesMenu.selectTab(currentIndex - 1);
      this.problemsMenu.selectTab(currentIndex - 1);
      this.problemsMenu.screen.render();
      this.modulesMenu.screen.render();
    });
    this.screen.key(["right"], () => {
      const currentIndex = this.modulesMenu.selected;
      this.modulesMenu.selectTab(currentIndex + 1);
      this.problemsMenu.selectTab(currentIndex + 1);
      this.problemsMenu.screen.render();
      this.modulesMenu.screen.render();
    });
  }

  layoutModules() {
    this.modulesMenu = blessed.listbar({
      label: "Modules",
      mouse: true,
      tags: true,
      width: "50%",
      height: "66%",
      left: "0%",
      top: "36%",
      border: {
        type: "line"
      },
      padding: 1,
      style: {
        fg: -1,
        border: {
          fg: this.color
        },
        prefix: {
          fg: -1
        },
        item: {
          fg: "white"
        },
        selected: {
          fg: "black",
          bg: this.color
        }
      },
      autoCommandKeys: true
    });

    this.moduleTable = blessed.table(
      Object.assign({}, DEFAULT_SCROLL_OPTIONS, {
        parent: this.modulesMenu,
        height: "100%",
        width: "100%-5",
        padding: {
          top: 2,
          right: 1,
          left: 1
        },
        align: "left",
        data: [["Name", "Size", "Percent"]],
        tags: true
      })
    );

    this.screen.append(this.modulesMenu);
  }

  layoutAssets() {
    this.assets = blessed.box({
      label: "Assets",
      tags: true,
      padding: 1,
      width: "50%",
      height: "28%",
      left: "50%",
      top: "36%",
      border: {
        type: "line"
      },
      style: {
        fg: -1,
        border: {
          fg: this.color
        }
      }
    });

    this.assetTable = blessed.table(
      Object.assign({}, DEFAULT_SCROLL_OPTIONS, {
        parent: this.assets,
        height: "100%",
        width: "100%-5",
        align: "left",
        padding: 1,
        data: [["Name", "Size"]]
      })
    );

    this.screen.append(this.assets);
  }

  layoutProblems() {
    this.problemsMenu = blessed.listbar({
      label: "Problems",
      mouse: true,
      width: "50%",
      height: "38%",
      left: "50%",
      top: "63%",
      border: {
        type: "line"
      },
      padding: {
        top: 1
      },
      style: {
        border: {
          fg: this.color
        },
        prefix: {
          fg: -1
        },
        item: {
          fg: "white"
        },
        selected: {
          fg: "black",
          bg: this.color
        }
      },
      autoCommandKeys: true
    });

    this.problems = blessed.box(
      Object.assign({}, DEFAULT_SCROLL_OPTIONS, {
        parent: this.problemsMenu,
        padding: 1,
        border: {
          fg: -1
        },
        style: {
          fg: -1,
          border: {
            fg: this.color
          }
        },
        tags: true
      })
    );

    this.screen.append(this.problemsMenu);
  }

  // eslint-disable-next-line complexity
  layoutStatus() {
    this.wrapper = blessed.layout({
      width: this.minimal ? "100%" : "25%",
      height: this.minimal ? "30%" : "36%",
      top: this.minimal ? "70%" : "0%",
      left: this.minimal ? "0%" : "75%",
      layout: "grid"
    });

    this.status = blessed.box({
      parent: this.wrapper,
      label: "Status",
      tags: true,
      padding: {
        left: 1
      },
      width: this.minimal ? "34%-1" : "100%",
      height: this.minimal ? "100%" : "34%",
      valign: "middle",
      border: {
        type: "line"
      },
      style: {
        fg: -1,
        border: {
          fg: this.color
        }
      }
    });

    this.operations = blessed.box({
      parent: this.wrapper,
      label: "Operation",
      tags: true,
      padding: {
        left: 1
      },
      width: this.minimal ? "34%-1" : "100%",
      height: this.minimal ? "100%" : "34%",
      valign: "middle",
      border: {
        type: "line"
      },
      style: {
        fg: -1,
        border: {
          fg: this.color
        }
      }
    });

    this.progress = blessed.box({
      parent: this.wrapper,
      label: "Progress",
      tags: true,
      padding: this.minimal
        ? {
            left: 1
          }
        : 1,
      width: this.minimal ? "33%" : "100%",
      height: this.minimal ? "100%" : "34%",
      valign: "middle",
      border: {
        type: "line"
      },
      style: {
        fg: -1,
        border: {
          fg: this.color
        }
      }
    });

    this.progressbar = new blessed.ProgressBar({
      parent: this.progress,
      height: 1,
      width: "90%",
      top: "center",
      left: "center",
      hidden: this.minimal,
      orientation: "horizontal",
      style: {
        bar: {
          bg: this.color
        }
      }
    });

    this.screen.append(this.wrapper);
  }
}

module.exports = Dashboard;


================================================
FILE: docs/getting-started.md
================================================
# Getting Started with Webpack-Dashboard

## Install

```sh
$ npm install --save-dev webpack-dashboard
# ... or ...
$ yarn add --dev webpack-dashboard
```

## Use

***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).

First, import the plugin and add it to your webpack config:

```js
// Import the plugin:
const DashboardPlugin = require("webpack-dashboard/plugin");

// Add it to your webpack configuration plugins.
module.exports = {
  // ...
  plugins: [new DashboardPlugin({
    /* options */
  })];
  // ...
};
```

Because 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:

```js
plugins: [
  new DashboardPlugin({ port: 3001 })
]
```

The next step, is to call webpack-dashboard from your `package.json`. So if your dev server start script previously looked like:

```js
"scripts": {
  "dev": "node index.js"
}
```

You would change that to:

```js
"scripts": {
  "dev": "webpack-dashboard -- node index.js"
}
```

If you are using the webpack-dev-server script, you can do something like:

```js
"scripts": {
  "dev": "webpack-dashboard -- webpack-dev-server --config ./webpack.dev.js"
}
```

Again, the new version uses sockets, so if you want to use a custom port you must use the `-p` option to pass that:

```js
"scripts": {
  "dev": "webpack-dashboard -p 3001 -- node index.js"
}
```
You can also pass a supported ANSI color using the `-c` flag to custom colorize your dashboard:

```js
"scripts": {
  "dev": "webpack-dashboard -c magenta -- node index.js"
}
```
Now 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.

## Other usage

We 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.


================================================
FILE: examples/.eslintrc.json
================================================
{
  "parserOptions": {
    "sourceType": "module"
  }
}

================================================
FILE: examples/config/webpack.config.js
================================================
const { resolve } = require("path");
const { StatsWriterPlugin } = require("webpack-stats-plugin");
const { DuplicatesPlugin } = require("inspectpack/plugin");
const Dashboard = require("../../plugin");
const webpackPkg = require("webpack/package.json");
const webpackVers = webpackPkg.version.split(".")[0];

// Specify the directory of the example we're working with
const cwd = `${process.cwd()}/examples/${process.env.EXAMPLE}`;
if (!process.env.EXAMPLE) {
  throw new Error("EXAMPLE is required");
}

const mode = process.env.WEBPACK_MODE || "development";

module.exports = {
  mode,
  devtool: false,
  context: resolve(cwd),
  entry: {
    bundle: "./src/index.js",
    // Hard-code path to the "hello world" no-dep entry for 2+ asset testing
    hello: "../simple/src/index.js"
  },
  output: {
    path: resolve(cwd, `dist-${mode}-${webpackVers}`),
    pathinfo: true,
    filename: "[name].js"
  },
  plugins: [
    new StatsWriterPlugin({
      fields: ["assets", "modules"],
      stats: {
        source: true // Needed for webpack5+
      }
    }),
    new DuplicatesPlugin({
      verbose: true,
      emitErrors: false
    }),
    new Dashboard({
      // Optionally filter which assets to report on by string prefix or regex.
      // includeAssets: ["bundle", /bund/]
    })
  ]
};


================================================
FILE: examples/config/webpack.config.ts
================================================
/**
 * No-op build with TS config to see if webpack-cli bombs out.
 */

import DashboardPlugin from '../../plugin';

import * as path from 'path';
import * as webpack from 'webpack';
const webpackVers = webpack.version;

const cwd = `${process.cwd()}/examples/${process.env.EXAMPLE}`;
if (!process.env.EXAMPLE) {
  throw new Error("EXAMPLE is required");
}

const mode = process.env.WEBPACK_MODE || "development";

const config: webpack.Configuration = {
  mode: 'development',
  entry: {
    bundle: "./src/index.js"
  },
  context: path.resolve(cwd),
  output: {
    path: path.resolve(cwd, `dist-ts-${mode}-${webpackVers}`),
    pathinfo: true,
    filename: "[name].js"
  },
  devtool: false,
  plugins: [
    new DashboardPlugin()
  ]
};

export default config;


================================================
FILE: examples/duplicates-esm/package.json
================================================
{
  "name": "duplicates-esm",
  "version": "1.2.3",
  "description": "DUMMY APP",
  "main": "src/index.js",
  "dependencies": {
    "foo": "^1.0.0",
    "uses-foo": "^1.0.9"
  }
}


================================================
FILE: examples/duplicates-esm/src/index.js
================================================
/* eslint-disable no-console*/

import { foo } from "foo";
import { usesFoo } from "uses-foo";

console.log("foo", foo());
console.log("usesFoo", usesFoo());


================================================
FILE: examples/simple/package.json
================================================
{
  "name": "simple",
  "version": "1.2.3",
  "description": "DUMMY APP",
  "main": "src/index.js"
}


================================================
FILE: examples/simple/src/index.js
================================================
/* eslint-disable no-console*/

const hello = () => "hello world";

console.log(hello());


================================================
FILE: examples/tree-shaking/package.json
================================================
{
  "name": "tree-shaking",
  "version": "1.2.3",
  "description": "DUMMY APP",
  "main": "src/index.js",
  "dependencies": {
    "foo": "^1.0.0",
    "uses-foo": "^1.0.9"
  }
}


================================================
FILE: examples/tree-shaking/src/index.js
================================================
/* eslint-disable no-console*/

import { red } from "foo";
import { usesRed } from "uses-foo";

console.log("red", red());
console.log("usesRed", usesRed());


================================================
FILE: index.js
================================================
"use strict";

const dashboard = require("./dashboard/index");

module.exports = dashboard;


================================================
FILE: package.json
================================================
{
  "name": "webpack-dashboard",
  "version": "3.3.8",
  "description": "a CLI dashboard for webpack dev server",
  "bin": "bin/webpack-dashboard.js",
  "main": "index.js",
  "engines": {
    "node": ">=8.0.0"
  },
  "types": "index.d.ts",
  "scripts": {
    "test": "mocha \"test/**/*.spec.js\"",
    "test-cov": "nyc mocha \"test/**/*.spec.js\"",
    "lint": "eslint .",
    "check": "run-s format-check lint test check-ts",
    "check-ci": "run-s format-check lint test-cov check-ts",
    "check-ts": "tsc plugin/index.d.ts examples/config/webpack.config.ts --noEmit",
    "dev": "cross-env EXAMPLE=duplicates-esm node bin/webpack-dashboard.js -- webpack-cli --config examples/config/webpack.config.js --watch",
    "dev-ts": "cross-env EXAMPLE=duplicates-esm node bin/webpack-dashboard.js -- webpack-cli --config examples/config/webpack.config.ts --watch",
    "format": "prettier --write \"./{bin,examples,plugin,test,utils}/**/*.js\"",
    "format-check": "prettier --list-different \"./{bin,examples,plugin,test,utils}/**/*.js\""
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/FormidableLabs/webpack-dashboard.git"
  },
  "keywords": [
    "webpack",
    "cli",
    "plugin",
    "dashboard"
  ],
  "author": "Ken Wheeler",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/FormidableLabs/webpack-dashboard/issues"
  },
  "homepage": "https://github.com/FormidableLabs/webpack-dashboard",
  "peerDependencies": {
    "webpack": "*"
  },
  "dependencies": {
    "@changesets/cli": "^2.26.1",
    "chalk": "^4.1.1",
    "commander": "^8.0.0",
    "cross-spawn": "^7.0.3",
    "filesize": "^7.0.0",
    "handlebars": "^4.1.2",
    "inspectpack": "^4.7.1",
    "neo-blessed": "^0.2.0",
    "socket.io": "^4.1.3",
    "socket.io-client": "^4.1.3"
  },
  "devDependencies": {
    "@svitejs/changesets-changelog-github-compact": "^0.1.1",
    "@types/node": "^22.1.0",
    "babel-eslint": "^10.1.0",
    "chai": "^4.3.4",
    "codecov": "^3.8.3",
    "cross-env": "^7.0.3",
    "eslint": "^7.30.0",
    "eslint-config-formidable": "^4.0.0",
    "eslint-config-prettier": "^8.3.0",
    "eslint-plugin-filenames": "^1.1.0",
    "eslint-plugin-import": "^2.23.4",
    "eslint-plugin-prettier": "^3.4.0",
    "eslint-plugin-promise": "^5.1.0",
    "mocha": "^9.0.2",
    "npm-run-all": "^4.1.5",
    "nyc": "^15.1.0",
    "prettier": "^2.3.2",
    "sinon": "^11.1.1",
    "sinon-chai": "^3.7.0",
    "ts-node": "^10.4.0",
    "typescript": "^4.3.5",
    "webpack": "^5.44.0",
    "webpack-cli": "^4.7.2",
    "webpack-stats-plugin": "^1.0.3"
  },
  "publishConfig": {
    "provenance": true
  }
}

================================================
FILE: plugin/index.d.ts
================================================
interface IMessage {
  type: string;
  value: string | number | { [key: string]: any }
  error?: boolean;
}

interface IDashboardOptions {
  port?: number;
  host?: string;
  handler?: (dataArray: IMessage[]) => void;
}

interface ICompiler {
  hooks?: any;
  plugin?: (name: string, callback: () => void) => void;
}

export default class DashboardPlugin {
  constructor(options?: IDashboardOptions);
  apply(compiler: ICompiler): void;
}


================================================
FILE: plugin/index.js
================================================
/* eslint-disable max-params, max-statements */

"use strict";

const webpack = require("webpack");
const io = require("socket.io-client");
const inspectpack = require("inspectpack");

const serializer = require("../utils/error-serialization");

const DEFAULT_PORT = 9838;
const DEFAULT_HOST = "127.0.0.1";
const ONE_SECOND = 1000;
const INSPECTPACK_PROBLEM_ACTIONS = ["duplicates", "versions"];
const INSPECTPACK_PROBLEM_TYPE = "problems";
const CLEANUP_MAX_NUM_TRIES = 3; // Try 3 times to close before giving up.
const CLEANUP_RETRY_DELAY_MS = 100; // Delay before a retry.

function noop() {}

function getTimeMessage(timer) {
  let time = Date.now() - timer;

  if (time >= ONE_SECOND) {
    time /= ONE_SECOND;
    time = Math.round(time);
    time += "s";
  } else {
    time += "ms";
  }

  return ` (${time})`;
}

// Naive camel-casing.
const camel = str => str.replace(/-([a-z])/, group => group[1].toUpperCase());

// Normalize webpack3 vs. 4 API differences.
function _webpackHook(hookType, compiler, event, callback) {
  if (compiler.hooks) {
    hookType = hookType || "tap";
    compiler.hooks[camel(event)][hookType]("webpack-dashboard", callback);
  } else {
    compiler.plugin(event, callback);
  }
}

const webpackHook = _webpackHook.bind(null, "tap");
const webpackAsyncHook = _webpackHook.bind(null, "tapAsync");

class DashboardPlugin {
  constructor(options) {
    if (typeof options === "function") {
      this._handler = options;
    } else {
      options = options || {};
      this.host = options.host || DEFAULT_HOST;
      this.port = options.port || DEFAULT_PORT;
      this.includeAssets = options.includeAssets || [];
      this._handler = options.handler || null;
    }

    this.watching = false;
    this.openMessages = 0;
  }

  handler(...args) {
    if (this._handler) {
      this._handler(...args);
    }
  }

  cleanup(numTried = 0) {
    if (!this._cleanedUp && !this.watching && this.socket) {
      // Clear handler so we don't emit any more messages.
      this._handler = null;

      // Check if we have unhandled dashboard messages.
      if (this.openMessages > 0 && numTried < CLEANUP_MAX_NUM_TRIES) {
        // Wait a small interval and try again, up to a maximum.
        setTimeout(() => this.cleanup(numTried++), CLEANUP_RETRY_DELAY_MS);
        return;
      }

      // Close!
      this._cleanedUp = true;
      this.socket.close();
    }
  }

  apply(compiler) {
    // Reached compile "done" state.
    let reachedDone = false;
    // Compile has finished in "done", "error", "failed" states.
    let finished = false;
    let timer;

    if (!this._handler) {
      this._handler = noop;
      const port = this.port;
      const host = this.host;
      this.socket = io(`http://${host}:${port}`);
      this.socket.on("connect", () => {
        // Manually track messages we send to the dashboard and decrement later.
        const socketMsg = this.socket.emit.bind(this.socket, "message");
        const ack = () => {
          this.openMessages--;
        };
        this._handler = (...args) => {
          this.openMessages++;
          socketMsg(...args, ack);
        };
      });
      this.socket.once("options", args => {
        this.minimal = args.minimal;
        this.includeAssets = this.includeAssets.concat(args.includeAssets || []);
      });
      this.socket.on("error", err => {
        // eslint-disable-next-line no-console
        console.log(err);
      });
      this.socket.on("disconnect", () => {
        if (!reachedDone) {
          // eslint-disable-next-line no-console
          console.log("Socket.io disconnected before completing build lifecycle.");
        }
      });
    }

    new webpack.ProgressPlugin((percent, msg) => {
      // Skip reporting once finished.
      if (finished) {
        return;
      }

      this.handler([
        {
          type: "status",
          value: "Compiling"
        },
        {
          type: "progress",
          value: percent
        },
        {
          type: "operations",
          value: msg + getTimeMessage(timer)
        }
      ]);
    }).apply(compiler);

    webpackAsyncHook(compiler, "watch-run", (c, done) => {
      this.watching = true;
      done();
    });

    webpackAsyncHook(compiler, "run", (c, done) => {
      this.watching = false;
      done();
    });

    webpackHook(compiler, "compile", () => {
      timer = Date.now();
      finished = false;
      this.handler([
        {
          type: "status",
          value: "Compiling"
        }
      ]);
    });

    webpackHook(compiler, "invalid", () => {
      finished = true;
      this.handler([
        {
          type: "status",
          value: "Invalidated"
        },
        {
          type: "progress",
          value: 0
        },
        {
          type: "operations",
          value: "idle"
        },
        {
          type: "clear"
        }
      ]);
    });

    webpackHook(compiler, "failed", () => {
      finished = true;
      this.handler([
        {
          type: "status",
          value: "Failed"
        },
        {
          type: "operations",
          value: `idle${getTimeMessage(timer)}`
        }
      ]);
    });

    webpackAsyncHook(compiler, "done", (stats, done) => {
      const { errors, options } = stats.compilation;
      const statsOptions = (options.devServer && options.devServer.stats) ||
        options.stats || { colors: true };
      const status = errors.length ? "Error" : "Success";

      // We only need errors/warnings for stats information for finishing up.
      // This allows us to avoid sending a full stats object to the CLI which
      // can cause socket.io client disconnects for large objects.
      // See: https://github.com/FormidableLabs/webpack-dashboard/issues/279
      const statsJsonOptions = {
        all: false,
        errors: true,
        warnings: true
      };

      reachedDone = true;
      finished = true;
      this.handler([
        {
          type: "status",
          value: status
        },
        {
          type: "progress",
          value: 1
        },
        {
          type: "operations",
          value: `idle${getTimeMessage(timer)}`
        },
        {
          type: "stats",
          value: {
            errors: stats.hasErrors(),
            warnings: stats.hasWarnings(),
            data: stats.toJson(statsJsonOptions)
          }
        },
        {
          type: "log",
          value: stats.toString(statsOptions)
        }
      ]);

      // Skip metrics in minimal mode.
      const getMetrics = () => (this.minimal ? Promise.resolve() : this.getMetrics(stats));

      // eslint-disable-next-line promise/catch-or-return
      getMetrics()
        .then(datas => this.handler(datas))
        .catch(err => {
          console.log("Error from inspectpack:", err); // eslint-disable-line no-console
        })
        // eslint-disable-next-line promise/always-return
        .then(() => {
          this.cleanup();
          done(); // eslint-disable-line promise/no-callback-in-promise
        });
    });
  }

  getMetrics(statsObj) {
    // Get the **full** stats object here for `inspectpack` analysis.
    const stats = statsObj.toJson({
      source: true // Needed for webpack5+
    });

    // Truncate off non-included assets.
    const { includeAssets } = this;
    if (includeAssets.length) {
      stats.assets = stats.assets.filter(({ name }) =>
        includeAssets.some(pattern => {
          if (typeof pattern === "string") {
            return name.startsWith(pattern);
          } else if (pattern instanceof RegExp) {
            return pattern.test(name);
          }

          // Pass through bad options..
          return false;
        })
      );
    }

    // Late destructure so that we can stub.
    const { actions } = inspectpack;
    const { serializeError } = serializer;

    const getSizes = () =>
      actions("sizes", { stats })
        .then(instance => instance.getData())
        .then(data => ({
          type: "sizes",
          value: data
        }))
        .catch(err => ({
          type: "sizes",
          error: true,
          value: serializeError(err)
        }));

    const getProblems = () =>
      Promise.all(
        INSPECTPACK_PROBLEM_ACTIONS.map(action =>
          actions(action, { stats }).then(instance => instance.getData())
        )
      )
        .then(datas => ({
          type: INSPECTPACK_PROBLEM_TYPE,
          value: INSPECTPACK_PROBLEM_ACTIONS.reduce(
            (memo, action, i) =>
              Object.assign({}, memo, {
                [action]: datas[i]
              }),
            {}
          )
        }))
        .catch(err => ({
          type: INSPECTPACK_PROBLEM_TYPE,
          error: true,
          value: serializeError(err)
        }));

    return Promise.all([getSizes(), getProblems()]);
  }
}

module.exports = DashboardPlugin;


================================================
FILE: test/.eslintrc.json
================================================
{
  "extends": ["formidable/configurations/es6-node-test", "plugin:prettier/recommended"]
}


================================================
FILE: test/base.spec.js
================================================
"use strict";

/**
 * Base server unit test initialization / global before/after's.
 *
 * This file should be `require`'ed by all other test files.
 *
 * **Note**: Because there is a global sandbox server unit tests should always
 * be run in a separate process from other types of tests.
 */
const sinon = require("sinon");

const blessed = require("neo-blessed");

const base = (module.exports = {
  sandbox: null
});

beforeEach(() => {
  base.sandbox = sinon.createSandbox({
    useFakeTimers: true
  });

  // Stub out **all** of blessed so we don't end up in a terminal.
  // Blessed is a `typeof` function, so manually iterate key.s
  Object.keys(blessed)
    .filter(key => typeof blessed[key] === "function")
    .forEach(key => {
      base.sandbox.stub(blessed, key);
    });

  // Some manual hacking.
  blessed.screen.returns({
    append: sinon.spy(),
    key: sinon.spy(),
    render: sinon.spy()
  });

  blessed.listbar.returns({
    selected: "selected",
    setLabel: sinon.spy(),
    setProblems: sinon.spy(),
    selectTab: sinon.spy(),
    setItems: sinon.spy()
  });

  blessed.box.returns({
    setContent: sinon.spy(),
    setLabel: sinon.spy()
  });

  blessed.log.returns({
    log: sinon.spy()
  });

  blessed.table.returns({
    setData: sinon.spy()
  });

  blessed.ProgressBar.returns({
    setContent: sinon.spy(),
    setProgress: sinon.spy()
  });
});

afterEach(() => {
  base.sandbox.restore();
});


================================================
FILE: test/bin/webpack-dashboard.spec.js
================================================
"use strict";

const base = require("../base.spec");

const cli = require("../../bin/webpack-dashboard");

describe("bin/webpack-dashboard", () => {
  it("can invoke the dashboard cli", () => {
    expect(() =>
      cli({
        argv: [],
        server: {
          on: base.sandbox.spy()
        }
      })
    ).to.not.throw();
  });
});


================================================
FILE: test/dashboard/index.spec.js
================================================
"use strict";

const chalk = require("chalk");
const blessed = require("neo-blessed");

const base = require("../base.spec");
const Dashboard = require("../../dashboard");

const mockSetItems = () => {
  // Override ListBar fakes from what we do in `base.spec.js`.
  // Note that these are **already** stubbed. We're not monkey-patching blessed.
  blessed.listbar.returns({
    selected: "selected",
    setLabel: base.sandbox.spy(),
    selectTab: base.sandbox.spy(),
    setItems: base.sandbox.stub().callsFake(obj => {
      // Naively simulate what setItems would do calling each object key.
      Object.keys(obj).forEach(key => obj[key]());
    })
  });
};

describe("dashboard", () => {
  const options = {
    color: "red",
    minimal: true,
    title: "my-title"
  };

  it("can create a new no option dashboard", () => {
    const dashboard = new Dashboard();
    expect(dashboard).to.be.ok;
    expect(dashboard.color).to.equal("green");
    expect(dashboard.minimal).to.be.false;
    expect(dashboard.stats).to.be.null;
  });

  it("can create a new with options dashboard", () => {
    const dashboardWithOptions = new Dashboard(options);
    expect(dashboardWithOptions).to.be.ok;
    expect(dashboardWithOptions.color).to.equal("red");
    expect(dashboardWithOptions.minimal).to.be.true;
  });

  describe("set* methods", () => {
    let dashboard;

    beforeEach(() => {
      dashboard = new Dashboard();
    });

    describe("setData", () => {
      const dataArray = [
        {
          type: "progress",
          value: 0.57
        },
        {
          type: "operations",
          value: "IDLE"
        }
      ];

      it("can setData", () => {
        expect(() => dashboard.setData(dataArray)).to.not.throw;
      });
    });

    describe("setOperations", () => {
      const data = {
        value: "IDLE"
      };

      it("can setOperations", () => {
        expect(() => dashboard.setOperations(data)).to.not.throw;

        dashboard.setOperations(data);
        expect(dashboard.operations.setContent).to.have.been.calledWith(data.value);
      });
    });

    describe("setStatus", () => {
      const data = {
        value: "Success"
      };

      it("can setStatus", () => {
        expect(() => dashboard.setStatus(data)).to.not.throw;

        dashboard.setStatus(data);
        expect(dashboard.status.setContent).to.have.been.calledWith(
          `{green-fg}{bold}${data.value}{/}`
        );
      });

      it("should display a failed status on build failure", () => {
        data.value = "Failed";
        expect(() => dashboard.setStatus(data)).to.not.throw;

        dashboard.setStatus(data);
        expect(dashboard.status.setContent).to.have.been.calledWith(
          `{red-fg}{bold}${data.value}{/}`
        );
      });

      it("should display any other status string without coloring", () => {
        data.value = "Unknown";
        expect(() => dashboard.setStatus(data)).to.not.throw;

        dashboard.setStatus(data);
        expect(dashboard.status.setContent).to.have.been.calledWith(`{bold}${data.value}{/}`);
      });
    });

    describe("setProgress", () => {
      const data = {
        value: 0.57
      };

      it("can setProgress", () => {
        expect(() => dashboard.setProgress(data)).to.not.throw;

        dashboard.setProgress(data);
        expect(dashboard.progressbar.setProgress).to.have.been.calledOnce;
        expect(dashboard.progressbar.setContent).to.have.been.called;
      });

      it(`should call progressbar.setProgress twice if not in minimal mode
      and percent is falsy`, () => {
        data.value = null;
        expect(() => dashboard.setProgress(data)).to.not.throw;

        dashboard.setProgress(data);
        expect(dashboard.progressbar.setProgress).to.have.been.calledTwice;
      });
    });

    describe("setStats", () => {
      const data = {
        value: {
          errors: null,
          data: {
            errors: [],
            warnings: []
          }
        }
      };

      it("can setStats", () => {
        expect(() => dashboard.setStats(data)).not.to.throw;

        dashboard.setStats(data);
        expect(dashboard.logText.log).to.have.been.called;
        expect(dashboard.modulesMenu.setLabel).to.have.been.calledWith(
          chalk.yellow("Modules (loading...)")
        );
        expect(dashboard.assets.setLabel).to.have.been.calledWith(
          chalk.yellow("Assets (loading...)")
        );
        expect(dashboard.problemsMenu.setLabel).to.have.been.calledWith(
          chalk.yellow("Problems (loading...)")
        );
      });

      it("should display stats errors if present", () => {
        data.value.errors = ["error"];
        expect(() => dashboard.setStats(data)).not.to.throw;

        dashboard.setStats(data);
        expect(dashboard.status.setContent).to.have.been.calledWith("{red-fg}{bold}Failed{/}");
      });
    });

    describe("setSizes", () => {
      const data = {
        value: {
          assets: {
            foo: {
              meta: {
                full: 456
              },
              files: [
                {
                  size: {
                    full: 123
                  },
                  fileName: "test.js",
                  baseName: "/home/bar/test.js"
                }
              ]
            },
            bar: {
              meta: {
                full: 123
              },
              files: []
            }
          }
        }
      };

      it("can setSizes", () => {
        const formattedData = [
          ["Name", "Size"],
          ["foo", "456 B"],
          ["bar", "123 B"],
          ["Total", "579 B"]
        ];

        expect(() => dashboard.setSizes(data)).to.not.throw;

        dashboard.setSizes(data);
        expect(dashboard.assets.setLabel).to.have.been.calledWith("Assets");
        expect(dashboard.assetTable.setData).to.have.been.calledWith(formattedData);
        expect(dashboard.modulesMenu.setLabel).to.have.been.calledWith("Modules");
        expect(dashboard.modulesMenu.setItems).to.have.been.called;
        expect(dashboard.modulesMenu.selectTab).to.have.been.calledWith(
          dashboard.modulesMenu.selected
        );
        expect(dashboard.screen.render).to.have.been.called;
      });

      it("should call formatModules", () => {
        // Mock out the call to setItems to force call of formatModules.
        mockSetItems();
        // Discard generic dashboard, create a new one with adjusted mocks.
        dashboard = new Dashboard();
        expect(() => dashboard.setSizes(data)).to.not.throw;
      });
    });

    describe("setSizesError", () => {
      const err = "error";

      it("can setSizesError", () => {
        expect(() => dashboard.setSizesError(err)).to.not.throw;

        dashboard.setSizesError(err);
        expect(dashboard.modulesMenu.setLabel).to.have.been.calledWith(
          chalk.red("Modules (error)")
        );
        expect(dashboard.assets.setLabel).to.have.been.calledWith(chalk.red("Assets (error)"));
        expect(dashboard.logText.log).to.have.been.calledWith(
          chalk.red("Could not load module/asset sizes.")
        );
        expect(dashboard.logText.log).to.have.been.calledWith(chalk.red(err));
      });
    });

    describe("setProblems", () => {
      const data = {
        value: {
          duplicates: {
            assets: {
              foo: "foo",
              bar: "bar"
            }
          },
          versions: {
            assets: {
              foo: "1.2.3",
              bar: "3.2.1"
            }
          }
        }
      };

      it("can setProblems", () => {
        expect(() => dashboard.setProblems(data)).to.not.throw;

        dashboard.setProblems(data);
        expect(dashboard.problemsMenu.setLabel).to.have.been.calledWith("Problems");
        expect(dashboard.problemsMenu.setItems).to.have.been.called;
        expect(dashboard.problemsMenu.selectTab).to.have.been.calledWith(
          dashboard.problemsMenu.selected
        );
        expect(dashboard.screen.render).to.have.been.called;
      });

      it("should call formatProblems", () => {
        // Mock out the call to setItems to force call of formatProblems.
        mockSetItems();
        // Discard generic dashboard, create a new one with adjusted mocks.

        dashboard = new Dashboard();
        expect(() => dashboard.setProblems(data)).to.not.throw;
      });
    });

    describe("setProblemsError", () => {
      const err = { stack: "stack" };

      it("can setProblemsError", () => {
        expect(() => dashboard.setProblemsError(err)).to.not.throw;

        dashboard.setProblemsError(err);
        expect(dashboard.problemsMenu.setLabel).to.have.been.calledWith(
          chalk.red("Problems (error)")
        );
        expect(dashboard.logText.log).to.have.been.calledWith(
          chalk.red("Could not analyze bundle problems.")
        );
        expect(dashboard.logText.log).to.have.been.calledWith(chalk.red(err.stack));
      });
    });

    describe("setLog", () => {
      const data = { value: "[{ log: 'log' }]" };

      it("can setLog", () => {
        expect(() => dashboard.setLog(data)).not.to.throw;

        dashboard.setLog(data);
        expect(dashboard.logText.log).to.have.been.calledWith("[ log: 'log' ]");
      });

      it("should return early if the stats object has errors", () => {
        dashboard.stats = {};
        dashboard.stats.hasErrors = () => true;
        expect(dashboard.setLog(data)).to.be.undefined;

        dashboard.setLog(data);
        expect(dashboard.logText.log).to.not.have.been.called;
      });
    });
  });
});


================================================
FILE: test/plugin/index.spec.js
================================================
"use strict";

const inspectpackActions = require("inspectpack/lib/actions");

const base = require("../base.spec");
const Plugin = require("../../plugin");
const errorSerializer = require("../../utils/error-serialization");

describe("plugin", () => {
  const options = {
    port: 3000,
    host: "111.0.2.3"
  };

  it("can create a new no option plugin", () => {
    const plugin = new Plugin();
    expect(plugin).to.be.ok;
    expect(plugin.host).to.equal("127.0.0.1");
    // eslint-disable-next-line no-magic-numbers
    expect(plugin.port).to.equal(9838);
    expect(plugin._handler).to.be.null;
    expect(plugin.watching).to.be.false;
  });

  it("can create a new with options dashboard", () => {
    const pluginWithOptions = new Plugin(options);
    expect(pluginWithOptions.host).to.equal("111.0.2.3");
    // eslint-disable-next-line no-magic-numbers
    expect(pluginWithOptions.port).to.equal(3000);
  });

  describe("plugin methods", () => {
    let stats;
    let toJson;
    let compilation;
    let compiler;
    let plugin;

    beforeEach(() => {
      stats = {
        modules: [],
        assets: []
      };
      toJson = base.sandbox.stub().callsFake(() => stats);
      compilation = {
        errors: [],
        warnings: [],
        getStats: () => ({ toJson }),
        tap: base.sandbox.stub(),
        tapAsync: base.sandbox.stub() // this is us in webpack-dashboard
      };
      compiler = {
        // mock out webpack4 compiler, since that's what we have in devDeps
        hooks: {
          compilation,
          emit: {
            intercept: base.sandbox.stub()
          },
          watchRun: {
            tapAsync: base.sandbox.stub()
          },
          run: {
            tapAsync: base.sandbox.stub()
          },
          compile: {
            tap: base.sandbox.stub()
          },
          failed: {
            tap: base.sandbox.stub()
          },
          invalid: {
            tap: base.sandbox.stub()
          },
          done: {
            tap: base.sandbox.stub()
          }
        }
      };

      plugin = new Plugin();
    });

    it("can do a basic compilation", () => {
      expect(() => plugin.apply(compiler)).to.not.throw;

      // after instantiation, test that we can hit getMetrics
      expect(() => plugin.getMetrics({ toJson })).to.not.throw;
    });

    it("can do a basic getMetrics", () => {
      const actions = base.sandbox.spy(inspectpackActions, "actions");

      return (
        plugin
          .getMetrics({ toJson })
          // eslint-disable-next-line promise/always-return
          .then(() => {
            expect(actions).to.have.been.calledThrice;
          })
      );
    });

    it("filters assets for includeAssets", () => {
      const actions = base.sandbox.spy(inspectpackActions, "actions");

      stats = {
        assets: [
          {
            name: "one.js",
            modules: []
          },
          {
            name: "two.js",
            modules: []
          },
          {
            name: "three.js",
            modules: []
          }
        ]
      };

      plugin = new Plugin({
        includeAssets: [
          "one", // string prefix
          /tw/ // regex match
        ]
      });

      return (
        plugin
          .getMetrics({ toJson })
          // eslint-disable-next-line promise/always-return
          .then(() => {
            expect(actions).to.have.been.calledWith("sizes", {
              stats: {
                assets: [
                  { modules: [], name: "one.js" },
                  { modules: [], name: "two.js" }
                ]
              }
            });
          })
      );
    });

    it("should serialize errors when encountered", () => {
      const actions = base.sandbox.stub(inspectpackActions, "actions").rejects();
      const serializeError = base.sandbox.spy(errorSerializer, "serializeError");

      return (
        plugin
          .getMetrics({ toJson })
          // eslint-disable-next-line promise/always-return
          .then(() => {
            // All three actions called.
            expect(actions).to.have.been.calledThrice;
            // ... but since two are in Promise.all only get one rejection.
            expect(serializeError).to.have.been.calledTwice;
          })
      );
    });
  });
});


================================================
FILE: test/setup.js
================================================
"use strict";

const chai = require("chai");
const sinonChai = require("sinon-chai");

// Add chai plugins.
chai.use(sinonChai);

// Add test lib globals.
global.expect = chai.expect;


================================================
FILE: test/utils/format-assets.spec.js
================================================
"use strict";

const { _getAssetSize, _getTotalSize, _printAssets } = require("../../utils/format-assets");

describe("format-assets", () => {
  describe("#_getAssetSize", () => {
    context("when asset size is present", () => {
      it("returns a readable file size as string", () => {
        const asset = {
          size: 500
        };
        expect(_getAssetSize(asset)).to.equal("500 B");
      });
    });

    context("when no asset size is present", () => {
      it("returns zero in a readable file size as string", () => {
        const asset = {
          size: undefined
        };
        expect(_getAssetSize(asset)).to.equal("0 B");
      });
    });
  });

  describe("#_getTotalSize", () => {
    it("returns a readable file size of all assets as a string", () => {
      const assets = [{ size: 500 }, { size: undefined }, { size: 1000 }];
      expect(_getTotalSize(assets)).to.equal("1.46 KB");
    });
  });

  describe("#_printAssets", () => {
    it("returns a nested array of assets information", () => {
      const assetList = [
        {
          name: "assets1",
          size: 500
        },
        {
          name: "assets2",
          size: 0
        },
        {
          name: "assets2",
          size: 500
        }
      ];

      const output = [
        ["Name", "Size"],
        ["assets1", "500 B"],
        ["assets2", "0 B"],
        ["assets2", "500 B"],
        ["Total", "1000 B"]
      ];
      expect(_printAssets(assetList)).eql(output);
    });
  });
});


================================================
FILE: test/utils/format-modules.spec.js
================================================
"use strict";

const { normalize, sep } = require("path");
const { _formatFileName, _formatPercentage } = require("../../utils/format-modules");

describe("format-modules", () => {
  describe("#_formatFileName", () => {
    it("returns a blessed green colored file name", () => {
      const mod = {
        fileName: normalize("foo/bar/test.js")
      };
      expect(_formatFileName(mod)).to.equal(`{green-fg}.${sep}foo${sep}bar${sep}test.js{/}`);
    });

    context("when there is a baseName", () => {
      it("returns a blessed yellow colored file name", () => {
        const mod = {
          fileName: "test.js",
          baseName: normalize("/home/bar/test.js")
        };
        expect(_formatFileName(mod)).to.equal("{yellow-fg}test.js{/}");
      });
    });

    context("when node_modules is present in fileName", () => {
      it("returns a blessed yellow colored file name", () => {
        const mod = {
          fileName: normalize("/node_modules/@foo/test.js"),
          baseName: normalize("/home/bar/node_modules/@foo/test.js")
        };
        expect(_formatFileName(mod)).to.equal(
          `~${sep}{yellow-fg}@foo{/}${sep}{yellow-fg}test.js{/}`
        );
      });
    });
  });

  describe("#_formatPercentage", () => {
    it("returns a precentage as a string", () => {
      // eslint-disable-next-line no-magic-numbers
      expect(_formatPercentage(30, 15)).to.equal("200%");
    });
  });
});


================================================
FILE: test/utils/format-output.spec.js
================================================
"use strict";

const { _isLikelyASyntaxError, _lineJoin, _formatMessage } = require("../../utils/format-output");

describe("format-output", () => {
  describe("#_isLikelyASyntaxError", () => {
    context("when message is a syntax error", () => {
      it("returns true", () => {
        const message = "Syntax error: missing ; before statement";
        expect(_isLikelyASyntaxError(message)).to.be.true;
      });
    });

    context("when message is a type error", () => {
      it("returns false", () => {
        const message = "Type error: null has no properties";
        expect(_isLikelyASyntaxError(message)).to.be.false;
      });
    });
  });

  describe("#_formatMessage", () => {
    it("returns a readable user friendly message", () => {
      const message1 = "Module build failed: SyntaxError: missing ; before statement";
      const message2 = "/Module not found: Error: Cannot resolve 'file' or 'directory'/";
      expect(_formatMessage(message1)).to.equal("Syntax error: missing ; before statement");
      expect(_formatMessage(message2)).to.equal("/Module not found:/");
    });
  });

  describe("#_lineJoin", () => {
    it("returns the elements of an array on a newline as a string", () => {
      const array = ["word", "word2", "word3"];
      const output = "word\nword2\nword3";
      expect(_lineJoin(array)).to.equal(output);
    });
  });
});


================================================
FILE: test/utils/format-versions.spec.js
================================================
"use strict";

const formatVersions = require("../../utils/format-versions");

describe("format-versions", () => {
  describe("when package are present", () => {
    const data = {
      packages: {
        foo: {
          "1.1.1": [
            {
              skews: {
                parts: [
                  { name: "foo-dep", range: "^1.0.0" },
                  { name: "bar", range: "^3.0.2" }
                ]
              }
            }
          ]
        }
      }
    };

    it("should return a handlebar compile template", () => {
      const result =
        // eslint-disable-next-line max-len
        "{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";
      expect(formatVersions(data)).to.equal(result);
    });
  });

  describe("when packages are not present", () => {
    it("should return an empty string", () => {
      const data = {
        packages: []
      };
      expect(formatVersions(data)).to.equal("");
    });
  });
});


================================================
FILE: utils/error-serialization.js
================================================
"use strict";

const serializeError = err => ({
  code: err.code,
  message: err.message,
  stack: err.stack
});

const deserializeError = serializedError => {
  const err = new Error();
  err.code = serializedError.code;
  err.message = serializedError.message;
  err.stack = serializedError.stack;
  return err;
};

module.exports = {
  serializeError,
  deserializeError
};


================================================
FILE: utils/format-assets.js
================================================
"use strict";

/**
 * Assets are the full emitted bundles.
 */
const filesize = require("filesize");

function _getAssetSize(asset) {
  return filesize(asset.size || 0);
}

function _getTotalSize(assetsList) {
  return filesize(assetsList.reduce((total, asset) => total + (asset.size || 0), 0));
}

function _printAssets(assetsList) {
  return [["Name", "Size"]]
    .concat(assetsList.map(asset => [asset.name, _getAssetSize(asset)]))
    .concat([["Total", _getTotalSize(assetsList)]]);
}

function formatAssets(assets) {
  // Convert to list.
  const assetsList = Object.keys(assets).map(name => ({
    name,
    size: assets[name].meta.full
  }));

  return _printAssets(assetsList);
}

module.exports = { formatAssets, _getAssetSize, _getTotalSize, _printAssets };


================================================
FILE: utils/format-duplicates.js
================================================
"use strict";

/**
 * Problem: Duplicate files (same path name) in a bundle.
 */
const filesize = require("filesize");
const Handlebars = require("handlebars");

Handlebars.registerHelper("filesize", function (options) {
  // eslint-disable-next-line no-invalid-this
  return filesize(options.fn(this));
});

/* eslint-disable max-len*/
const template = Handlebars.compile(
  `{yellow-fg}{underline}Duplicate files{/}

{{#each files}}
- {green-fg}{{@key}}{/}
  (files: {{meta.extraFiles.num}}, sources: {{meta.extraSources.num}}, bytes: {{#filesize}}{{meta.extraSources.bytes}}{{/filesize}})
{{/each}}

Extra duplicate files (unique): {{meta.extraFiles.num}}
Extra duplicate sources (non-unique): {{meta.extraSources.num}}
Wasted duplicate bytes (non-unique): {{#filesize}}{{meta.extraSources.bytes}}{{/filesize}}
`
);
/* eslint-enable max-len*/

function formatDuplicates(duplicates) {
  const haveDups = !!Object.keys((duplicates || {}).files || {}).length;
  return haveDups ? template(duplicates) : "";
}

module.exports = formatDuplicates;


================================================
FILE: utils/format-modules.js
================================================
"use strict";

/**
 * Modules are the individual files within an asset.
 */
const { relative, resolve, sep } = require("path");
const filesize = require("filesize");

const PERCENT_MULTIPLIER = 100;
const PERCENT_PRECISION = 3;

// Convert to:
// - existing source file name
// - the path leading up to **just** the package (not including subpath).
function _formatFileName(mod) {
  const { fileName, baseName } = mod;

  // Source file.
  if (!baseName) {
    return `{green-fg}.${sep}${relative(process.cwd(), resolve(fileName))}{/}`;
  }

  // Package
  let parts = fileName.split(sep);
  // Remove starting path.
  const firstNmIdx = parts.indexOf("node_modules");
  parts = parts.slice(firstNmIdx);

  // Remove trailing path after package.
  const lastNmIdx = parts.lastIndexOf("node_modules");
  const isScoped = (parts[lastNmIdx + 1] || "").startsWith("@");
  parts = parts.slice(0, lastNmIdx + (isScoped ? 3 : 2)); // eslint-disable-line no-magic-numbers

  return parts.map(part => (part === "node_modules" ? "~" : `{yellow-fg}${part}{/}`)).join(sep);
}

function _formatPercentage(modSize, assetSize) {
  const percentage = ((modSize / assetSize) * PERCENT_MULTIPLIER).toPrecision(PERCENT_PRECISION);

  return `${percentage}%`;
}

function formatModules(mods) {
  // We _could_ use the `asset.meta.full` from inspectpack, but that is for
  // the entire module with boilerplate included. We instead do a percentage
  // of the files we're counting here.
  const assetSize = mods.reduce((count, mod) => count + mod.size.full, 0);

  // First, process the modules into a map to normalize file paths.
  const modsMap = mods.reduce((memo, mod) => {
    // File name collapses to packages for dependencies.
    // Aggregate into object.
    const fileName = _formatFileName(mod);

    // Add in information.
    memo[fileName] = memo[fileName] || {
      fileName,
      num: 0,
      size: 0
    };
    memo[fileName].num += 1;
    memo[fileName].size += mod.size.full;

    return memo;
  }, {});

  return [].concat(
    [["Name", "Size", "Percent"]],
    Object.keys(modsMap)
      .map(fileName => modsMap[fileName])
      .sort((a, b) => a.size < b.size) // sort largest first
      .map(mod => [
        `${mod.fileName} ${mod.num > 1 ? `(${mod.num})` : ""}`,
        filesize(mod.size),
        _formatPercentage(mod.size, assetSize)
      ])
  );
}

module.exports = {
  formatModules,
  _formatFileName,
  _formatPercentage
};


================================================
FILE: utils/format-output.js
================================================
"use strict";

const friendlySyntaxErrorLabel = "Syntax error:";

function _isLikelyASyntaxError(message) {
  return message.indexOf(friendlySyntaxErrorLabel) !== -1;
}

function _formatMessage(message = "") {
  // Handle legacy and modern webpack shapes.
  message = typeof message.message !== "undefined" ? message.message : message;

  return message
    .replace("Module build failed: SyntaxError:", friendlySyntaxErrorLabel)
    .replace(/Module not found: Error: Cannot resolve 'file' or 'directory'/, "Module not found:")
    .replace(/^\s*at\s.*:\d+:\d+[\s\)]*\n/gm, "")
    .replace("./~/css-loader!./~/postcss-loader!", "");
}

function _lineJoin(arr) {
  return arr.join("\n");
}

// eslint-disable-next-line max-statements
function formatOutput(stats) {
  const output = [];
  const hasErrors = stats.hasErrors();
  const hasWarnings = stats.hasWarnings();

  const json = stats.toJson({
    source: true // Needed for webpack5+
  });
  let formattedErrors = json.errors.map(message => `Error in ${_formatMessage(message)}`);
  const formattedWarnings = json.warnings.map(message => `Warning in ${_formatMessage(message)}`);

  if (hasErrors) {
    output.push("{red-fg}Failed to compile.{/}");
    output.push("");
    if (formattedErrors.some(_isLikelyASyntaxError)) {
      formattedErrors = formattedErrors.filter(_isLikelyASyntaxError);
    }
    formattedErrors.forEach(message => {
      output.push(message);
      output.push("");
    });
    return _lineJoin(output);
  }

  if (hasWarnings) {
    output.push("{yellow-fg}Compiled with warnings.{/yellow-fg}");
    output.push("");
    formattedWarnings.forEach(message => {
      output.push(message);
      output.push("");
    });

    return _lineJoin(output);
  }

  output.push("{green-fg}Compiled successfully!{/}");
  output.push("");

  return _lineJoin(output);
}

module.exports = { formatOutput, _formatMessage, _isLikelyASyntaxError, _lineJoin };


================================================
FILE: utils/format-problems.js
================================================
"use strict";

const formatDuplicates = require("./format-duplicates");
const formatVersions = require("./format-versions");

function formatProblems(data) {
  const duplicates = formatDuplicates(data.duplicates);
  const versions = formatVersions(data.versions);

  if (!duplicates && !versions) {
    return "{green-fg}No problems detected!{/}";
  }
  if (duplicates && !versions) {
    return `{green-fg}No version skews!{/}\n\n${duplicates}`;
  }
  if (!duplicates && versions) {
    return `{green-fg}No duplicate files!{/}\n\n${versions}`;
  }

  return `${duplicates}\n${versions}`;
}

module.exports = { formatProblems };


================================================
FILE: utils/format-versions.js
================================================
"use strict";

const Handlebars = require("handlebars");

// From inspectpack.
const pkgNamePath = pkgParts =>
  pkgParts.reduce((m, part) => `${m}${m ? " -> " : ""}{cyan-fg}${part.name}{/}@${part.range}`, "");

Handlebars.registerHelper("skew", function (options) {
  // eslint-disable-next-line no-invalid-this
  return pkgNamePath(options.fn(this));
});

const template = Handlebars.compile(
  `{yellow-fg}{underline}Version skews{/}

{{#each packages}}
{yellow-fg}{bold}{{@key}}{/}
  {{#each this}}
  {green-fg}{{@key}}{/}
    {{#each this}}
      {{#each skews}}
    {{#skew}}{{{this}}}{{/skew}}
      {{/each}}
    {{/each}}
  {{/each}}
{{/each}}
`
);

function formatVersions(versions) {
  const haveSkews = !!Object.keys((versions || {}).packages || {}).length;
  return haveSkews ? template(versions) : "";
}

module.exports = formatVersions;
Download .txt
gitextract_wps1zf6f/

├── .changeset/
│   ├── cold-wolves-occur.md
│   └── config.json
├── .editorconfig
├── .eslintignore
├── .eslintrc.json
├── .github/
│   └── workflows/
│       ├── ci.yml
│       └── release.yml
├── .gitignore
├── .mocharc.yml
├── .npmignore
├── .nycrc
├── .prettierignore
├── .prettierrc
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── ISSUE_TEMPLATE.md
├── LICENSE
├── README.md
├── bin/
│   └── webpack-dashboard.js
├── dashboard/
│   └── index.js
├── docs/
│   └── getting-started.md
├── examples/
│   ├── .eslintrc.json
│   ├── config/
│   │   ├── webpack.config.js
│   │   └── webpack.config.ts
│   ├── duplicates-esm/
│   │   ├── package.json
│   │   └── src/
│   │       └── index.js
│   ├── simple/
│   │   ├── package.json
│   │   └── src/
│   │       └── index.js
│   └── tree-shaking/
│       ├── package.json
│       └── src/
│           └── index.js
├── index.js
├── package.json
├── plugin/
│   ├── index.d.ts
│   └── index.js
├── test/
│   ├── .eslintrc.json
│   ├── base.spec.js
│   ├── bin/
│   │   └── webpack-dashboard.spec.js
│   ├── dashboard/
│   │   └── index.spec.js
│   ├── plugin/
│   │   └── index.spec.js
│   ├── setup.js
│   └── utils/
│       ├── format-assets.spec.js
│       ├── format-modules.spec.js
│       ├── format-output.spec.js
│       └── format-versions.spec.js
└── utils/
    ├── error-serialization.js
    ├── format-assets.js
    ├── format-duplicates.js
    ├── format-modules.js
    ├── format-output.js
    ├── format-problems.js
    └── format-versions.js
Download .txt
SYMBOL INDEX (58 symbols across 10 files)

FILE: bin/webpack-dashboard.js
  constant DEFAULT_PORT (line 10) | const DEFAULT_PORT = 9838;

FILE: dashboard/index.js
  constant PERCENT_MULTIPLIER (line 12) | const PERCENT_MULTIPLIER = 100;
  constant DEFAULT_SCROLL_OPTIONS (line 14) | const DEFAULT_SCROLL_OPTIONS = {
  class Dashboard (line 27) | class Dashboard {
    method constructor (line 29) | constructor(options) {
    method setData (line 93) | setData(dataArray, ack) {
    method setProgress (line 114) | setProgress(data) {
    method setOperations (line 130) | setOperations(data) {
    method setStatus (line 134) | setStatus(data) {
    method setStats (line 153) | setStats(data) {
    method setSizes (line 176) | setSizes(data) {
    method setSizesError (line 204) | setSizesError(err) {
    method setProblems (line 211) | setProblems(data) {
    method setProblemsError (line 242) | setProblemsError(err) {
    method setLog (line 248) | setLog(data) {
    method clear (line 255) | clear() {
    method layoutLog (line 259) | layoutLog() {
    method mapNavigationKeysToScrollLog (line 290) | mapNavigationKeysToScrollLog() {
    method layoutModules (line 324) | layoutModules() {
    method layoutAssets (line 375) | layoutAssets() {
    method layoutProblems (line 409) | layoutProblems() {
    method layoutStatus (line 462) | layoutStatus() {

FILE: plugin/index.d.ts
  type IMessage (line 1) | interface IMessage {
  type IDashboardOptions (line 7) | interface IDashboardOptions {
  type ICompiler (line 13) | interface ICompiler {
  class DashboardPlugin (line 18) | class DashboardPlugin {

FILE: plugin/index.js
  constant DEFAULT_PORT (line 11) | const DEFAULT_PORT = 9838;
  constant DEFAULT_HOST (line 12) | const DEFAULT_HOST = "127.0.0.1";
  constant ONE_SECOND (line 13) | const ONE_SECOND = 1000;
  constant INSPECTPACK_PROBLEM_ACTIONS (line 14) | const INSPECTPACK_PROBLEM_ACTIONS = ["duplicates", "versions"];
  constant INSPECTPACK_PROBLEM_TYPE (line 15) | const INSPECTPACK_PROBLEM_TYPE = "problems";
  constant CLEANUP_MAX_NUM_TRIES (line 16) | const CLEANUP_MAX_NUM_TRIES = 3;
  constant CLEANUP_RETRY_DELAY_MS (line 17) | const CLEANUP_RETRY_DELAY_MS = 100;
  function noop (line 19) | function noop() {}
  function getTimeMessage (line 21) | function getTimeMessage(timer) {
  function _webpackHook (line 39) | function _webpackHook(hookType, compiler, event, callback) {
  class DashboardPlugin (line 51) | class DashboardPlugin {
    method constructor (line 52) | constructor(options) {
    method handler (line 67) | handler(...args) {
    method cleanup (line 73) | cleanup(numTried = 0) {
    method apply (line 91) | apply(compiler) {
    method getMetrics (line 270) | getMetrics(statsObj) {

FILE: utils/format-assets.js
  function _getAssetSize (line 8) | function _getAssetSize(asset) {
  function _getTotalSize (line 12) | function _getTotalSize(assetsList) {
  function _printAssets (line 16) | function _printAssets(assetsList) {
  function formatAssets (line 22) | function formatAssets(assets) {

FILE: utils/format-duplicates.js
  function formatDuplicates (line 30) | function formatDuplicates(duplicates) {

FILE: utils/format-modules.js
  constant PERCENT_MULTIPLIER (line 9) | const PERCENT_MULTIPLIER = 100;
  constant PERCENT_PRECISION (line 10) | const PERCENT_PRECISION = 3;
  function _formatFileName (line 15) | function _formatFileName(mod) {
  function _formatPercentage (line 37) | function _formatPercentage(modSize, assetSize) {
  function formatModules (line 43) | function formatModules(mods) {

FILE: utils/format-output.js
  function _isLikelyASyntaxError (line 5) | function _isLikelyASyntaxError(message) {
  function _formatMessage (line 9) | function _formatMessage(message = "") {
  function _lineJoin (line 20) | function _lineJoin(arr) {
  function formatOutput (line 25) | function formatOutput(stats) {

FILE: utils/format-problems.js
  function formatProblems (line 6) | function formatProblems(data) {

FILE: utils/format-versions.js
  function formatVersions (line 31) | function formatVersions(versions) {
Condensed preview — 52 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (96K chars).
[
  {
    "path": ".changeset/cold-wolves-occur.md",
    "chars": 125,
    "preview": "---\n\"webpack-dashboard\": patch\n---\n\n'#359 update node version to 18 in github actions workflows and github actions versi"
  },
  {
    "path": ".changeset/config.json",
    "chars": 242,
    "preview": "{\n\t\"$schema\": \"https://unpkg.com/@changesets/config@2.3.0/schema.json\",\n\t\"changelog\": [\n\t\t\"@svitejs/changesets-changelog"
  },
  {
    "path": ".editorconfig",
    "chars": 228,
    "preview": "# editorconfig.org\nroot = true\n\n[*]\nindent_style = space\nindent_size = 2\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_"
  },
  {
    "path": ".eslintignore",
    "chars": 6,
    "preview": "dist-*"
  },
  {
    "path": ".eslintrc.json",
    "chars": 173,
    "preview": "{\n  \"extends\": [\"formidable/configurations/es6-node\", \"plugin:prettier/recommended\"],\n  \"rules\": {\n    \"func-style\": \"of"
  },
  {
    "path": ".github/workflows/ci.yml",
    "chars": 696,
    "preview": "name: CI\n\non:\n  push:\n    branches:\n      - master\n  pull_request:\n    branches:\n      - master\n\njobs:\n  build:\n\n    run"
  },
  {
    "path": ".github/workflows/release.yml",
    "chars": 925,
    "preview": "name: Release\non:\n  push:\n    branches:\n      - master\njobs:\n  release:\n    name: Release\n    runs-on: ubuntu-latest\n   "
  },
  {
    "path": ".gitignore",
    "chars": 144,
    "preview": "# dependencies\n/node_modules/\n\n# misc\n.DS_Store\nnpm-debug.log*\n.nyc_output\n.coverage\nyarn-error.log\npackage-lock.json\ndi"
  },
  {
    "path": ".mocharc.yml",
    "chars": 43,
    "preview": "require: \"./test/setup.js\"\nrecursive: true\n"
  },
  {
    "path": ".npmignore",
    "chars": 108,
    "preview": "/*\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",
    "chars": 90,
    "preview": "{\n  \"reporter\": [\n    \"html\",\n    \"lcov\",\n    \"text\"\n  ],\n  \"report-dir\": \"./.coverage\"\n}\n"
  },
  {
    "path": ".prettierignore",
    "chars": 49,
    "preview": "examples/**/dist-*/*.js\nexamples/**/dist-*/*.json"
  },
  {
    "path": ".prettierrc",
    "chars": 79,
    "preview": "{\n  \"arrowParens\": \"avoid\",\n  \"trailingComma\": \"none\",\n  \"endOfLine\": \"auto\"\n}\n"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 7447,
    "preview": "# Changelog\n\n## 3.3.8\n\n### Patch Changes\n\n- Adding GitHub release workflow ([#354](https://github.com/FormidableLabs/web"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "chars": 3285,
    "preview": "# Contributing to Webpack-Dashboard\n\n## Contributor Covenant Code of Conduct\n\n### Our Pledge\n\nIn the interest of fosteri"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 2842,
    "preview": "## Development\n\n### Installing dependencies\n\n```sh\nyarn install\n```\n\n### Testing\n\nYou will find tests for files colocate"
  },
  {
    "path": "ISSUE_TEMPLATE.md",
    "chars": 593,
    "preview": "#### Please provide a description and details of the bug / issue below:\n\n---\n\n#### If the issue is visual, please provid"
  },
  {
    "path": "LICENSE",
    "chars": 1112,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2016-present, Formidable Labs. All rights reserved.\n\nPermission is hereby granted, "
  },
  {
    "path": "README.md",
    "chars": 7850,
    "preview": "[![Webpack Dashboard — Formidable, We build the modern web](https://raw.githubusercontent.com/FormidableLabs/webpack-das"
  },
  {
    "path": "bin/webpack-dashboard.js",
    "chars": 2973,
    "preview": "#!/usr/bin/env node\n\n\"use strict\";\n\nconst commander = require(\"commander\");\nconst spawn = require(\"cross-spawn\");\nconst "
  },
  {
    "path": "dashboard/index.js",
    "chars": 12656,
    "preview": "\"use strict\";\n\nconst chalk = require(\"chalk\");\nconst blessed = require(\"neo-blessed\");\n\nconst { formatOutput } = require"
  },
  {
    "path": "docs/getting-started.md",
    "chars": 2499,
    "preview": "# Getting Started with Webpack-Dashboard\n\n## Install\n\n```sh\n$ npm install --save-dev webpack-dashboard\n# ... or ...\n$ ya"
  },
  {
    "path": "examples/.eslintrc.json",
    "chars": 55,
    "preview": "{\n  \"parserOptions\": {\n    \"sourceType\": \"module\"\n  }\n}"
  },
  {
    "path": "examples/config/webpack.config.js",
    "chars": 1301,
    "preview": "const { resolve } = require(\"path\");\nconst { StatsWriterPlugin } = require(\"webpack-stats-plugin\");\nconst { DuplicatesPl"
  },
  {
    "path": "examples/config/webpack.config.ts",
    "chars": 767,
    "preview": "/**\n * No-op build with TS config to see if webpack-cli bombs out.\n */\n\nimport DashboardPlugin from '../../plugin';\n\nimp"
  },
  {
    "path": "examples/duplicates-esm/package.json",
    "chars": 180,
    "preview": "{\n  \"name\": \"duplicates-esm\",\n  \"version\": \"1.2.3\",\n  \"description\": \"DUMMY APP\",\n  \"main\": \"src/index.js\",\n  \"dependenc"
  },
  {
    "path": "examples/duplicates-esm/src/index.js",
    "chars": 158,
    "preview": "/* eslint-disable no-console*/\n\nimport { foo } from \"foo\";\nimport { usesFoo } from \"uses-foo\";\n\nconsole.log(\"foo\", foo()"
  },
  {
    "path": "examples/simple/package.json",
    "chars": 101,
    "preview": "{\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",
    "chars": 90,
    "preview": "/* eslint-disable no-console*/\n\nconst hello = () => \"hello world\";\n\nconsole.log(hello());\n"
  },
  {
    "path": "examples/tree-shaking/package.json",
    "chars": 178,
    "preview": "{\n  \"name\": \"tree-shaking\",\n  \"version\": \"1.2.3\",\n  \"description\": \"DUMMY APP\",\n  \"main\": \"src/index.js\",\n  \"dependencie"
  },
  {
    "path": "examples/tree-shaking/src/index.js",
    "chars": 158,
    "preview": "/* eslint-disable no-console*/\n\nimport { red } from \"foo\";\nimport { usesRed } from \"uses-foo\";\n\nconsole.log(\"red\", red()"
  },
  {
    "path": "index.js",
    "chars": 92,
    "preview": "\"use strict\";\n\nconst dashboard = require(\"./dashboard/index\");\n\nmodule.exports = dashboard;\n"
  },
  {
    "path": "package.json",
    "chars": 2635,
    "preview": "{\n  \"name\": \"webpack-dashboard\",\n  \"version\": \"3.3.8\",\n  \"description\": \"a CLI dashboard for webpack dev server\",\n  \"bin"
  },
  {
    "path": "plugin/index.d.ts",
    "chars": 439,
    "preview": "interface IMessage {\n  type: string;\n  value: string | number | { [key: string]: any }\n  error?: boolean;\n}\n\ninterface I"
  },
  {
    "path": "plugin/index.js",
    "chars": 8909,
    "preview": "/* eslint-disable max-params, max-statements */\n\n\"use strict\";\n\nconst webpack = require(\"webpack\");\nconst io = require(\""
  },
  {
    "path": "test/.eslintrc.json",
    "chars": 92,
    "preview": "{\n  \"extends\": [\"formidable/configurations/es6-node-test\", \"plugin:prettier/recommended\"]\n}\n"
  },
  {
    "path": "test/base.spec.js",
    "chars": 1436,
    "preview": "\"use strict\";\n\n/**\n * Base server unit test initialization / global before/after's.\n *\n * This file should be `require`'"
  },
  {
    "path": "test/bin/webpack-dashboard.spec.js",
    "chars": 343,
    "preview": "\"use strict\";\n\nconst base = require(\"../base.spec\");\n\nconst cli = require(\"../../bin/webpack-dashboard\");\n\ndescribe(\"bin"
  },
  {
    "path": "test/dashboard/index.spec.js",
    "chars": 9680,
    "preview": "\"use strict\";\n\nconst chalk = require(\"chalk\");\nconst blessed = require(\"neo-blessed\");\n\nconst base = require(\"../base.sp"
  },
  {
    "path": "test/plugin/index.spec.js",
    "chars": 4331,
    "preview": "\"use strict\";\n\nconst inspectpackActions = require(\"inspectpack/lib/actions\");\n\nconst base = require(\"../base.spec\");\ncon"
  },
  {
    "path": "test/setup.js",
    "chars": 184,
    "preview": "\"use strict\";\n\nconst chai = require(\"chai\");\nconst sinonChai = require(\"sinon-chai\");\n\n// Add chai plugins.\nchai.use(sin"
  },
  {
    "path": "test/utils/format-assets.spec.js",
    "chars": 1515,
    "preview": "\"use strict\";\n\nconst { _getAssetSize, _getTotalSize, _printAssets } = require(\"../../utils/format-assets\");\n\ndescribe(\"f"
  },
  {
    "path": "test/utils/format-modules.spec.js",
    "chars": 1433,
    "preview": "\"use strict\";\n\nconst { normalize, sep } = require(\"path\");\nconst { _formatFileName, _formatPercentage } = require(\"../.."
  },
  {
    "path": "test/utils/format-output.spec.js",
    "chars": 1381,
    "preview": "\"use strict\";\n\nconst { _isLikelyASyntaxError, _lineJoin, _formatMessage } = require(\"../../utils/format-output\");\n\ndescr"
  },
  {
    "path": "test/utils/format-versions.spec.js",
    "chars": 1061,
    "preview": "\"use strict\";\n\nconst formatVersions = require(\"../../utils/format-versions\");\n\ndescribe(\"format-versions\", () => {\n  des"
  },
  {
    "path": "utils/error-serialization.js",
    "chars": 377,
    "preview": "\"use strict\";\n\nconst serializeError = err => ({\n  code: err.code,\n  message: err.message,\n  stack: err.stack\n});\n\nconst "
  },
  {
    "path": "utils/format-assets.js",
    "chars": 770,
    "preview": "\"use strict\";\n\n/**\n * Assets are the full emitted bundles.\n */\nconst filesize = require(\"filesize\");\n\nfunction _getAsset"
  },
  {
    "path": "utils/format-duplicates.js",
    "chars": 1045,
    "preview": "\"use strict\";\n\n/**\n * Problem: Duplicate files (same path name) in a bundle.\n */\nconst filesize = require(\"filesize\");\nc"
  },
  {
    "path": "utils/format-modules.js",
    "chars": 2444,
    "preview": "\"use strict\";\n\n/**\n * Modules are the individual files within an asset.\n */\nconst { relative, resolve, sep } = require(\""
  },
  {
    "path": "utils/format-output.js",
    "chars": 1932,
    "preview": "\"use strict\";\n\nconst friendlySyntaxErrorLabel = \"Syntax error:\";\n\nfunction _isLikelyASyntaxError(message) {\n  return mes"
  },
  {
    "path": "utils/format-problems.js",
    "chars": 630,
    "preview": "\"use strict\";\n\nconst formatDuplicates = require(\"./format-duplicates\");\nconst formatVersions = require(\"./format-version"
  },
  {
    "path": "utils/format-versions.js",
    "chars": 852,
    "preview": "\"use strict\";\n\nconst Handlebars = require(\"handlebars\");\n\n// From inspectpack.\nconst pkgNamePath = pkgParts =>\n  pkgPart"
  }
]

About this extraction

This page contains the full source code of the FormidableLabs/webpack-dashboard GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 52 files (86.7 KB), approximately 22.9k tokens, and a symbol index with 58 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!