Full Code of GoogleChromeLabs/ndb for AI

master ade2052ad27f cached
61 files
169.2 KB
43.6k tokens
244 symbols
1 requests
Download .txt
Repository: GoogleChromeLabs/ndb
Branch: master
Commit: ade2052ad27f
Files: 61
Total size: 169.2 KB

Directory structure:
gitextract_lgd8360j/

├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── .npmignore
├── .travis.yml
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── build.js
├── debug.js
├── docs/
│   └── issue_template.md
├── front_end/
│   ├── ndb/
│   │   ├── Connection.js
│   │   ├── FileSystem.js
│   │   ├── InspectorFrontendHostOverrides.js
│   │   ├── NdbMain.js
│   │   └── module.json
│   ├── ndb.html
│   ├── ndb.js
│   ├── ndb.json
│   ├── ndb_sdk/
│   │   ├── NodeRuntime.js
│   │   ├── NodeWorker.js
│   │   └── module.json
│   ├── ndb_ui/
│   │   ├── NodeProcesses.js
│   │   ├── RunConfiguration.js
│   │   ├── Terminal.js
│   │   ├── module.json
│   │   ├── nodeProcesses.css
│   │   └── runConfiguration.css
│   └── xterm/
│       └── module.json
├── lib/
│   ├── backend.js
│   ├── filepath_to_url.js
│   ├── launcher.js
│   ├── preload/
│   │   └── ndb/
│   │       └── preload.js
│   └── process_utility.js
├── ndb.js
├── package.json
├── scripts/
│   └── builder.js
├── services/
│   ├── file_system.js
│   ├── file_system_io.js
│   ├── ndd_service.js
│   ├── search.js
│   └── terminal.js
├── test/
│   ├── assets/
│   │   └── test-project/
│   │       ├── index.js
│   │       ├── index.mjs
│   │       └── package.json
│   ├── basic.spec.js
│   ├── integration.js
│   └── platform.spec.js
├── utils/
│   └── testrunner/
│       ├── Matchers.js
│       ├── Multimap.js
│       ├── README.md
│       ├── Reporter.js
│       ├── TestRunner.js
│       ├── examples/
│       │   ├── fail.js
│       │   ├── hookfail.js
│       │   ├── hooktimeout.js
│       │   ├── timeout.js
│       │   └── unhandledpromiserejection.js
│       └── index.js
└── version.js

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

================================================
FILE: .editorconfig
================================================
root = true

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


================================================
FILE: .eslintignore
================================================
test/test.js
utils/testrunner/


================================================
FILE: .eslintrc.js
================================================
module.exports = {
    "root": true,

    "env": {
        "node": true,
        "es6": true
    },

    "parserOptions": {
        "ecmaVersion": 9
    },

    /**
     * ESLint rules
     *
     * All available rules: http://eslint.org/docs/rules/
     *
     * Rules take the following form:
     *   "rule-name", [severity, { opts }]
     * Severity: 2 == error, 1 == warning, 0 == off.
     */
    "rules": {
        /**
         * Enforced rules
         */


        // syntax preferences
        "quotes": [2, "single", {
            "avoidEscape": true,
            "allowTemplateLiterals": true
        }],
        "semi": 2,
        "no-extra-semi": 2,
        "comma-style": [2, "last"],
        "wrap-iife": [2, "inside"],
        "spaced-comment": [2, "always", {
            "markers": ["*"]
        }],
        "eqeqeq": [2],
        "arrow-body-style": [2, "as-needed"],
        "accessor-pairs": [2, {
            "getWithoutSet": false,
            "setWithoutGet": false
        }],
        "brace-style": [2, "1tbs", {"allowSingleLine": true}],
        "curly": [2, "multi-or-nest", "consistent"],
        "new-parens": 2,
        "func-call-spacing": 2,
        "arrow-parens": [2, "as-needed"],
        "prefer-const": 2,
        "quote-props": [2, "consistent"],

        // anti-patterns
        "no-var": 2,
        "no-with": 2,
        "no-multi-str": 2,
        "no-caller": 2,
        "no-implied-eval": 2,
        "no-labels": 2,
        "no-new-object": 2,
        "no-octal-escape": 2,
        "no-self-compare": 2,
        "no-shadow-restricted-names": 2,
        "no-cond-assign": 2,
        "no-debugger": 2,
        "no-dupe-keys": 2,
        "no-duplicate-case": 2,
        "no-empty-character-class": 2,
        "no-unreachable": 2,
        "no-unsafe-negation": 2,
        "radix": 2,
        "valid-typeof": 2,
        "no-unused-vars": [2, { "args": "none", "vars": "local", "varsIgnorePattern": "(_|load)" }],
        "no-implicit-globals": [2],

        // es2015 features
        "require-yield": 2,
        "template-curly-spacing": [2, "never"],

        // spacing details
        "space-infix-ops": 2,
        "space-in-parens": [2, "never"],
        "space-before-function-paren": [2, "never"],
        "no-whitespace-before-property": 2,
        "keyword-spacing": [2, {
            "overrides": {
                "if": {"after": true},
                "else": {"after": true},
                "for": {"after": true},
                "while": {"after": true},
                "do": {"after": true},
                "switch": {"after": true},
                "return": {"after": true}
            }
        }],
        "arrow-spacing": [2, {
            "after": true,
            "before": true
        }],

        // file whitespace
        "no-multiple-empty-lines": [2, {"max": 2}],
        "no-mixed-spaces-and-tabs": 2,
        "no-trailing-spaces": 2,
        "linebreak-style": [ process.platform === "win32" ? 0 : 2, "unix" ],
        "indent": [2, 2, { "SwitchCase": 1, "CallExpression": {"arguments": 2}, "MemberExpression": 2 }],
        "key-spacing": [2, {
            "beforeColon": false
        }]
    }
};

================================================
FILE: .gitignore
================================================
/.local-frontend/
/node_modules/
.DS_Store
*.swp
*.pyc
.vscode
package-lock.json
yarn.lock


================================================
FILE: .npmignore
================================================
# exclude all tests
test/
test/fs/
utils/testrunner

# repeats from .gitignore
/node_modules/
.DS_Store
*.swp
*.pyc
.vscode
package-lock.json
yarn.lock

# other
.editorconfig
.eslintignore
.eslintrc.js
README.md
ndb-*.tgz


================================================
FILE: .travis.yml
================================================
language: node_js
node_js:
  - "12"
  - "10"


================================================
FILE: CONTRIBUTING.md
================================================
# How to Contribute

First of all, thank you for your interest in ndb!
We'd love to accept your patches and contributions!

## Contributor License Agreement

Contributions to this project must be accompanied by a Contributor License
Agreement. You (or your employer) retain the copyright to your contribution,
this simply gives us permission to use and redistribute your contributions as
part of the project. Head over to <https://cla.developers.google.com/> to see
your current agreements on file or to sign a new one.

You generally only need to submit a CLA once, so if you've already submitted one
(even if it was for a different project), you probably don't need to do it
again.

## Getting setup

1. Clone this repository

```bash
git clone https://github.com/GoogleChromeLabs/ndb
cd ndb
```

2. Install dependencies

```bash
npm install
```

## Code reviews

All submissions, including submissions by project members, require review. We
use GitHub pull requests for this purpose. Consult
[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
information on using pull requests.

## Code Style

- Coding style is fully defined in [.eslintrc](https://github.com/GoogleChrome/puppeteer/blob/master/.eslintrc.js)
- Code should be annotated with [closure annotations](https://github.com/google/closure-compiler/wiki/Annotating-JavaScript-for-the-Closure-Compiler).
- Comments should be generally avoided. If the code would not be understood without comments, consider re-writing the code to make it self-explanatory.

To run code linter, use:

```bash
npm run lint
```

## Commit Messages

Commit messages should follow the Semantic Commit Messages format:

```
label(namespace): title

description

footer
```

1. *label* is one of the following:
    - `fix` - ndb bug fixes.
    - `feat` - ndb features.
    - `docs` - changes to docs, e.g. `docs(api.md): ..` to change documentation.
    - `test` - changes to ndb tests infrastructure.
    - `style` - ndb code style: spaces/alignment/wrapping etc.
    - `chore` - build-related work.
2. *namespace* is put in parenthesis after label and is **optional**.
3. *title* is a brief summary of changes.
4. *description* is **optional**, new-line separated from title and is in present tense.
5. *footer* is **optional**, new-line separated from *description* and contains "fixes" / "references" attribution to github issues.

Example:

```
fix(NddService): fix NddService.attach method

This patch fixes NddService.attach so that it works with Node 12.

Fixes #123, Fixes #234
```

## Adding New Dependencies

For all dependencies (both installation and development):
- **Do not add** a dependency if the desired functionality is easily implementable.
- If adding a dependency, it should be well-maintained and trustworthy.

A barrier for introducing new installation dependencies is especially high:
- **Do not add** installation dependency unless it's critical to project success.

## Writing Tests

- Every ndb service feature should be accompanied by a test.
- Tests should be *hermetic*. Tests should not depend on external services.
- Tests should work on all three platforms: Mac, Linux and Win.

ndb tests are located in [test/test.js](https://github.com/GoogleChromeLabs/ndb/blob/master/test/test.js)
and are written with a [mocha](https://mochajs.org/) framework.

- To run all tests:

```bash
npm run unit
```

- To run a specific test, substitute the `it` with `fit` (mnemonic rule: '*focus it*'):

```js
  ...
  // Using "fit" to run specific test
  fit('should work', async function({service}) {
    const response = await service.method();
    expect(response).toBe(true);
  })
```

- To disable a specific test, substitute the `it` with `xit` (mnemonic rule: '*cross it*'):

```js
  ...
  // Using "xit" to skip specific test
  xit('should work', async function({service}) {
    const response = await service.method();
    expect(response).toBe(true);
  })
```

## Developing ndb hints

- Environment variable NDB_DEBUG_FRONTEND=1 forces ndb to fetch
frontend from front_end folder and chrome-devtools-frontend
package.

```bash
NDB_DEBUG_FRONTEND=1 ndb .
```

- To debug ndb by itself or any ndb service you can use ndb.
```bash
NDB_DEBUG_FRONTEND=1 ndb ndb index.js
```

- To debug running Chrome DevTools frontend you can open DevTools,
use Ctrl+Shift+I on Linux or View > Developer > Developer Tools menu
item on Mac OS.

## [For Project Maintainers] Releasing to NPM

Releasing to NPM consists of 3 phases:
1. Source Code: mark a release.
    1. Bump `package.json` version following the SEMVER rules and send a PR titled `'chore: mark version vXXX.YYY.ZZZ'`.
    2. Make sure the PR passes **all checks**.
    3. Merge the PR.
    4. Once merged, publish release notes using the "create new tag" option.
        - **NOTE**: tag names are prefixed with `'v'`, e.g. for version `1.4.0` tag is `v1.4.0`.
2. Publish to NPM.
    1. On your local machine, pull from [upstream](https://github.com/GoogleChromeLabs/ndb) and make sure the last commit is the one just merged.
    2. Run `git status` and make sure there are no untracked files.
        - **WHY**: this is to avoid bundling unnecessary files to NPM package
    3. Run [`pkgfiles`](https://www.npmjs.com/package/pkgfiles) to make sure you don't publish anything unnecessary.
    4. Run `npm publish`.
3. Source Code: mark post-release.
    1. Bump `package.json` version to `-post` version and send a PR titled `'chore: bump version to vXXX.YYY.ZZZ-post'`
        - **NOTE**: no other commits should be landed in-between release commit and bump commit.



================================================
FILE: LICENSE
================================================

                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright 2018 Google Inc.

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

================================================
FILE: README.md
================================================
# ndb

<!-- [START badges] -->
[![Build Status](https://img.shields.io/travis/com/GoogleChromeLabs/ndb/master.svg)](https://travis-ci.com/GoogleChromeLabs/ndb)
[![NPM ndb package](https://img.shields.io/npm/v/ndb.svg)](https://npmjs.org/package/ndb)
<!-- [END badges] -->

<img src="https://raw.githubusercontent.com/ChromeDevTools/devtools-logo/master/192.png" height="200" align="right">

> ndb is an improved debugging experience for Node.js, enabled by Chrome DevTools

## Installation

Compatibility: ndb requires Node >=8.0.0. It works best with Node >=10.

Installation: ndb depends on [Puppeteer](https://github.com/GoogleChrome/puppeteer) which downloads a recent version of Chromium (~170MB Mac, ~280MB Linux, ~280MB Win).

```bash
# global install with npm:
npm install -g ndb


# alternatively, with yarn:
yarn global add ndb
```

Global installation may fail with different permission errors, you can find help in this [thread](https://github.com/GoogleChromeLabs/ndb/issues/20).

Windows users: Installation may fail on Windows during compilation the native dependencies. The following command may help: `npm install -g windows-build-tools`

### Local install

If you want ndb available from an npm script (eg. `npm run debug` runs `ndb index.js`), you can install it as a development dependency:

```bash
# local install with npm:
npm install --save-dev ndb


# alternatively, with yarn:
yarn add ndb --dev
```

You can then [set up an npm script](https://docs.npmjs.com/misc/scripts#examples). In this case, ndb will not be available in your system path.


## Getting Started

You can start debugging your Node.js application using one of the following ways:

- Use `ndb` instead of the `node` command

```bash
ndb server.js

# Alternatively, you can prepend `ndb`
ndb node server.js
```

- Prepend `ndb` in front of any other binary

```bash
# If you use some other binary, just prepend `ndb`
## npm run unit
ndb npm run unit

# Debug any globally installed package
## mocha
ndb mocha

# To use a local binary, use `npx` and prepend before it
ndb npx mocha
```

- Launch `ndb` as a standalone application 
   - Then, debug any npm script from your `package.json`, e.g. unit tests

```bash
# cd to your project folder (with a package.json)
ndb .
# In Sources panel > "NPM Scripts" sidebar, click the selected "Run" button 
```

- Use `Ctrl`/`Cmd` + `R` to restart last run
- Run any node command from within ndb's integrated terminal and ndb will connect automatically
- Run any open script source by using 'Run this script' context menu item, ndb will connect automatically as well

- Use `--prof` flag to profile your app, `Ctrl`/`Cmd` + `R` restarts profiling
```bash
ndb --prof npm run unit
```

## What can I do?

`ndb` has some powerful features exclusively for Node.js:
1. Child processes are detected and attached to.
1. You can place breakpoints before the modules are required.
1. You can edit your files within the UI. On Ctrl-S/Cmd-S, DevTools will [save the changes to disk](https://developers.google.com/web/tools/chrome-devtools/workspaces/).
1. By default, ndb [blackboxes](https://developers.google.com/web/tools/chrome-devtools/javascript/reference#blackbox) all scripts outside current working directory to improve focus. This includes node internal libraries (like `_stream_wrap.js`, `async_hooks.js`, `fs.js`) This behaviour may be changed by "Blackbox anything outside working dir" setting. 

In addition, you can use all the DevTools functionality that you've used in [typical Node debugging](https://medium.com/@paul_irish/debugging-node-js-nightlies-with-chrome-devtools-7c4a1b95ae27):
- breakpoint debugging, async stacks (AKA long stack traces), [async stepping](https://developers.google.com/web/updates/2018/01/devtools#async), etc...
- console (top-level await, object inspection, advanced filtering)
- [eager evaluation](https://developers.google.com/web/updates/2018/05/devtools#eagerevaluation) in console (requires Node >= 10)
- JS sampling profiler
- memory profiler

### Screenshot
![image](https://user-images.githubusercontent.com/39191/43023843-14a085a6-8c21-11e8-85b7-b9fd3405938a.png)


## Contributing

Check out [contributing guide](https://github.com/GoogleChromeLabs/ndb/blob/master/CONTRIBUTING.md) to get an overview of ndb development.

#### Thanks to the 'OG' `ndb`

In early 2011, [@smtlaissezfaire](https://github.com/smtlaissezfaire) released the first serious debugger for Node.js, under the `ndb` package name. It's still preserved at [github.com/smtlaissezfaire/ndb](https://github.com/smtlaissezfaire/ndb#readme). We thank Scott for generously donating the package name.


================================================
FILE: build.js
================================================
const path = require('path');

const Terser = require('terser');
const rimraf = require('rimraf');

const { buildApp } = require('./scripts/builder.js');

const DEVTOOLS_DIR = path.dirname(
    require.resolve('chrome-devtools-frontend/front_end/shell.json'));

(async function main() {
  const outFolder = path.join(__dirname, '.local-frontend');
  await new Promise(resolve => rimraf(outFolder, resolve));

  return buildApp(
      ['ndb', 'heap_snapshot_worker', 'formatter_worker'], [
        path.join(__dirname, 'front_end'),
        DEVTOOLS_DIR,
        path.join(__dirname, 'node_modules'),
      ], outFolder,
      minifyJS);
})();

function minifyJS(code) {
  return Terser.minify(code, {
    mangle: true,
    ecma: 8,
    compress: false
  }).code;
}


================================================
FILE: debug.js
================================================
#!/usr/bin/env node
process.env.NDB_DEBUG_FRONTEND = 1;
require('./ndb.js');


================================================
FILE: docs/issue_template.md
================================================
<!--
For issues, feature requests, or setup troubles with ndb, file an issue right here!
-->

### Steps to reproduce

**Tell us about your environment:**

* ndb version:
* Platform / OS version:
* Node.js version:

**What steps will reproduce the problem?**

_Please include code that reproduces the issue._

1.
2.
3.

**What is the expected result?**


**What happens instead?**


================================================
FILE: front_end/ndb/Connection.js
================================================
Ndb.Connection = class {
  constructor(channel) {
    this._onMessage = null;
    this._onDisconnect = null;
    this._channel = channel;
  }

  static async create(channel) {
    const connection = new Ndb.Connection(channel);
    await channel.listen(rpc.handle(connection));
    return connection;
  }

  /**
   * @param {function((!Object|string))} onMessage
   */
  setOnMessage(onMessage) {
    this._onMessage = onMessage;
  }

  /**
   * @param {function(string)} onDisconnect
   */
  setOnDisconnect(onDisconnect) {
    this._onDisconnect = onDisconnect;
  }

  /**
   * @param {string} message
   */
  sendRawMessage(message) {
    this._channel.send(message);
  }

  /**
   * @return {!Promise}
   */
  disconnect() {
    this._channel.close();
  }

  /**
   * @param {message}
   */
  dispatchMessage(message) {
    if (this._onMessage)
      this._onMessage(message);
  }
};


================================================
FILE: front_end/ndb/FileSystem.js
================================================
/**
 * @license Copyright 2018 Google Inc. All Rights Reserved.
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
 */

Ndb.FileSystem = class extends Persistence.PlatformFileSystem {
  constructor(fsService, fsIOService, searchService, manager, rootURL) {
    super(rootURL, '');
    this._fsService = fsService;
    this._fsIOService = fsIOService;
    this._searchService = searchService;
    this._rootURL = rootURL;
    this._manager = manager;

    /** @type {!Array<string>} */
    this._initialFilePaths = [];
  }

  static async create(manager, rootURL) {
    const searchClient = new Ndb.FileSystem.SearchClient();
    const [fsService, fsIOService, searchService] = await Promise.all([
      Ndb.backend.createService('file_system.js'),
      Ndb.backend.createService('file_system_io.js'),
      Ndb.backend.createService('search.js', rpc.handle(searchClient))]);

    // TODO: fix PlatformFileSystem upstream, entire search / indexing pipeline should go
    // through the platform filesystem. This should make searchClient also go away.
    InspectorFrontendHost.stopIndexing = searchService.stopIndexing.bind(searchService);

    const fs = new Ndb.FileSystem(fsService, fsIOService, searchService, manager, rootURL);
    await fs._initFilePaths();
    return fs;
  }

  /**
   * @override
   * @return {string}
   */
  embedderPath() {
    throw new Error('Not implemented');
  }

  /**
   * @override
   * @return {!Promise}
   */
  async _initFilePaths() {
    await this._fsService.startWatcher(this._rootURL, this._excludePattern(), rpc.handle(this));
  }

  forceFileLoad(scriptName) {
    return this._fsService.forceFileLoad(scriptName);
  }

  /**
   * @override
   * @return {!Array<string>}
   */
  initialFilePaths() {
    return this._initialFilePaths;
  }

  /**
   * @override
   * @return {!Array<string>}
   */
  initialGitFolders() {
    return [];
  }

  /**
   * @override
   * @param {string} path
   * @return {!Promise<?{modificationTime: !Date, size: number}>}
   */
  getMetadata(path) {
    // This method should never be called as long as we are matching using file urls.
    throw new Error('not implemented');
  }

  /**
   * @override
   * @param {string} path
   * @return {!Promise<?Blob>}
   */
  requestFileBlob(path) {
    throw new Error('not implemented');
  }

  /**
   * @override
   * @param {string} path
   * @param {function(?string,boolean)} callback
   */
  async requestFileContent(path, callback) {
    const result = await this._fsIOService.readFile(this._rootURL + path, 'base64');
    if (this.contentType(path) === Common.resourceTypes.Image) {
      callback(result, true);
    } else {
      const content = await(await fetch(`data:application/octet-stream;base64,${result}`)).text();
      callback(content, false);
    }
  }

  /**
   * @override
   * @param {string} path
   * @param {string} content
   * @param {boolean} isBase64
   */
  async setFileContent(path, content, isBase64) {
    await this._fsIOService.writeFile(this._rootURL + path, isBase64 ? content : content.toBase64(), 'base64');
  }

  /**
   * @override
   * @param {string} path
   * @param {?string} name
   * @return {!Promise<?string>}
   */
  async createFile(path, name) {
    const result = await this._fsIOService.createFile(this._rootURL + (path.length === 0 || path.startsWith('/') ? '' : '/') + path);
    return result.substr(this._rootURL.length + 1);
  }

  /**
   * @override
   * @param {string} path
   * @return {!Promise<boolean>}
   */
  async deleteFile(path) {
    return await this._fsIOService.deleteFile(this._rootURL + path);
  }

  /**
   * @override
   * @param {string} path
   * @param {string} newName
   * @param {function(boolean, string=)} callback
   */
  async renameFile(path, newName, callback) {
    const result = await this._fsIOService.renameFile(this._rootURL + path, newName);
    callback(result, result ? newName : null);
  }

  /**
   * @override
   * @param {string} path
   * @return {!Common.ResourceType}
   */
  contentType(path) {
    const extension = Common.ParsedURL.extractExtension(path);
    if (Persistence.IsolatedFileSystem._styleSheetExtensions.has(extension))
      return Common.resourceTypes.Stylesheet;
    if (Persistence.IsolatedFileSystem._documentExtensions.has(extension))
      return Common.resourceTypes.Document;
    if (Persistence.IsolatedFileSystem.ImageExtensions.has(extension))
      return Common.resourceTypes.Image;
    if (Persistence.IsolatedFileSystem._scriptExtensions.has(extension))
      return Common.resourceTypes.Script;
    return Persistence.IsolatedFileSystem.BinaryExtensions.has(extension) ? Common.resourceTypes.Other :
      Common.resourceTypes.Document;
  }

  /**
   * @override
   * @param {string} path
   * @return {string}
   */
  mimeFromPath(path) {
    return Common.ResourceType.mimeFromURL(path) || 'text/plain';
  }

  /**
   * @override
   * @param {string} path
   * @return {boolean}
   */
  canExcludeFolder(path) {
    return !!path ;
  }

  /**
   * @override
   * @param {string} url
   * @return {string}
   */
  tooltipForURL(url) {
    const path = Common.ParsedURL.urlToPlatformPath(url, Host.isWin()).trimMiddle(150);
    return ls`Linked to ${path}`;
  }

  /**
   * @override
   * @param {string} query
   * @param {!Common.Progress} progress
   * @return {!Promise<!Array<string>>}
   */
  searchInPath(query, progress) {
    return new Promise(resolve => {
      const requestId = this._manager.registerCallback(innerCallback);
      this._searchService.searchInPath(requestId, this._rootURL, query);

      /**
       * @param {!Array<string>} files
       */
      function innerCallback(files) {
        resolve(files);
        progress.worked(1);
      }
    });
  }

  /**
   * @param {string} name
   */
  filesChanged(events) {
    for (const event of events) {
      const paths = new Multimap();
      paths.set(this._rootURL, event.name);
      const emptyMap = new Multimap();
      Persistence.isolatedFileSystemManager.dispatchEventToListeners(Persistence.IsolatedFileSystemManager.Events.FileSystemFilesChanged, {
        changed: event.type === 'change' ? paths : emptyMap,
        added: event.type === 'add' ? paths : emptyMap,
        removed: event.type === 'unlink' ? paths : emptyMap
      });
    }
  }

  /**
   * @override
   * @param {!Common.Progress} progress
   */
  indexContent(progress) {
    progress.setTotalWork(1);
    const requestId = this._manager.registerProgress(progress);
    this._searchService.indexPath(requestId, this._rootURL, this._excludePattern());
  }

  /**
   * @override
   * @return {boolean}
   */
  supportsAutomapping() {
    return true;
  }

  /**
   * @return {string}
   */
  _excludePattern() {
    return this._manager.workspaceFolderExcludePatternSetting().get();
  }
};

Ndb.FileSystem.SearchClient = class {
  /**
   * @param {number} requestId
   * @param {string} fileSystemPath
   * @param {number} totalWork
   */
  indexingTotalWorkCalculated(requestId, fileSystemPath, totalWork) {
    this._callFrontend(() => InspectorFrontendAPI.indexingTotalWorkCalculated(requestId, fileSystemPath, totalWork));
  }

  /**
   * @param {number} requestId
   * @param {string} fileSystemPath
   * @param {number} worked
   */
  indexingWorked(requestId, fileSystemPath, worked) {
    this._callFrontend(() => InspectorFrontendAPI.indexingWorked(requestId, fileSystemPath, worked));
  }

  /**
   * @param {number} requestId
   * @param {string} fileSystemPath
   */
  indexingDone(requestId, fileSystemPath) {
    this._callFrontend(_ => InspectorFrontendAPI.indexingDone(requestId, fileSystemPath));
  }

  /**
   * @param {number} requestId
   * @param {string} fileSystemPath
   * @param {!Array.<string>} files
   */
  searchCompleted(requestId, fileSystemPath, files) {
    this._callFrontend(_ => InspectorFrontendAPI.searchCompleted(requestId, fileSystemPath, files));
  }

  _callFrontend(f) {
    if (Runtime.queryParam('debugFrontend'))
      setTimeout(f, 0);
    else
      f();
  }
};


================================================
FILE: front_end/ndb/InspectorFrontendHostOverrides.js
================================================
/**
 * @license Copyright 2018 Google Inc. All Rights Reserved.
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
 */

(function(){
  InspectorFrontendHost.getPreferences = async function(callback) {
    [Ndb.backend] = await carlo.loadParams();
    const prefs = {
      '__bundled__uiTheme': '"dark"'
    };
    for (let i = 0; i < window.localStorage.length; i++) {
      const key = window.localStorage.key(i);
      prefs[key] = window.localStorage.getItem(key);
    }
    callback(prefs);
  };

  InspectorFrontendHost.isHostedMode = () => false;
  InspectorFrontendHost.copyText = text => navigator.clipboard.writeText(text);
  InspectorFrontendHost.openInNewTab = url => Ndb.backend.openInNewTab(url);
  InspectorFrontendHost.bringToFront = () => Ndb.backend.bringToFront();
  InspectorFrontendHost.loadNetworkResource = async(url, headers, streamId, callback) => {
    const text = await Ndb.backend.loadNetworkResource(url, headers);
    if (text) {
      Host.ResourceLoader.streamWrite(streamId, text);
      callback({statusCode: 200});
    } else {
      callback({statusCode: 404});
    }
  };
})();


================================================
FILE: front_end/ndb/NdbMain.js
================================================
/**
 * @license Copyright 2018 Google Inc. All Rights Reserved.
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
 */

Ndb.npmExecPath = function() {
  if (!Ndb._npmExecPathPromise)
    Ndb._npmExecPathPromise = Ndb.backend.which('npm').then(result => result.resolvedPath);
  return Ndb._npmExecPathPromise;
};

/**
 * @implements {Common.Runnable}
 */
Ndb.NdbMain = class extends Common.Object {
  /**
   * @override
   */
  async run() {
    InspectorFrontendAPI.setUseSoftMenu(true);
    document.title = 'ndb';
    Common.moduleSetting('blackboxInternalScripts').addChangeListener(Ndb.NdbMain._calculateBlackboxState);
    Ndb.NdbMain._calculateBlackboxState();

    const setting = Persistence.isolatedFileSystemManager.workspaceFolderExcludePatternSetting();
    setting.set(Ndb.NdbMain._defaultExcludePattern().join('|'));
    Ndb.nodeProcessManager = await Ndb.NodeProcessManager.create(SDK.targetManager);

    Ndb.processInfo = await Ndb.backend.processInfo();
    await Ndb.nodeProcessManager.addFileSystem(Ndb.processInfo.cwd);

    // TODO(ak239): we do not want to create this model for workers, so we need a way to add custom capabilities.
    SDK.SDKModel.register(NdbSdk.NodeWorkerModel, SDK.Target.Capability.JS, true);
    SDK.SDKModel.register(NdbSdk.NodeRuntimeModel, SDK.Target.Capability.JS, true);

    await new Promise(resolve => SDK.initMainConnection(resolve));
    SDK.targetManager.createTarget('<root>', ls`Root`, SDK.Target.Type.Browser, null);
    if (Common.moduleSetting('autoStartMain').get()) {
      const main = await Ndb.mainConfiguration();
      if (main) {
        if (main.prof)
          await Ndb.nodeProcessManager.profile(main.execPath, main.args);
        else
          Ndb.nodeProcessManager.debug(main.execPath, main.args);
      }
    }
    Ndb.nodeProcessManager.startRepl();
  }

  static _defaultExcludePattern() {
    const defaultCommonExcludedFolders = [
      '/bower_components/', '/\\.devtools', '/\\.git/', '/\\.sass-cache/', '/\\.hg/', '/\\.idea/',
      '/\\.svn/', '/\\.cache/', '/\\.project/'
    ];
    const defaultWinExcludedFolders = ['/Thumbs.db$', '/ehthumbs.db$', '/Desktop.ini$', '/\\$RECYCLE.BIN/'];
    const defaultMacExcludedFolders = [
      '/\\.DS_Store$', '/\\.Trashes$', '/\\.Spotlight-V100$', '/\\.AppleDouble$', '/\\.LSOverride$', '/Icon$',
      '/\\._.*$'
    ];
    const defaultLinuxExcludedFolders = ['/.*~$'];
    let defaultExcludedFolders = defaultCommonExcludedFolders;
    if (Host.isWin())
      defaultExcludedFolders = defaultExcludedFolders.concat(defaultWinExcludedFolders);
    else if (Host.isMac())
      defaultExcludedFolders = defaultExcludedFolders.concat(defaultMacExcludedFolders);
    else
      defaultExcludedFolders = defaultExcludedFolders.concat(defaultLinuxExcludedFolders);
    return defaultExcludedFolders;
  }

  static _calculateBlackboxState() {
    const blackboxInternalScripts = Common.moduleSetting('blackboxInternalScripts').get();
    const PATTERN = '^internal[\\/].*|bin/npm-cli\.js$|bin/yarn\.js$';
    const regexPatterns = Common.moduleSetting('skipStackFramesPattern').getAsArray()
        .filter(({pattern}) => pattern !== PATTERN && pattern !== '^internal/.*');
    if (blackboxInternalScripts)
      regexPatterns.push({pattern: PATTERN });
    Common.moduleSetting('skipStackFramesPattern').setAsArray(regexPatterns);
  }
};

Ndb.mainConfiguration = async() => {
  const info = Ndb.processInfo;
  const cmd = info.argv.slice(2);
  if (cmd.length === 0 || cmd[0] === '.')
    return null;
  let execPath;
  let args;
  let prof = false;
  if (cmd[0] === '--prof') {
    prof = true;
    cmd.shift();
  }
  if (cmd[0].endsWith('.js')
    || cmd[0].endsWith('.mjs')
    || cmd[0].startsWith('-')) {
    execPath = await Ndb.processInfo.nodeExecPath;
    args = cmd;
  } else {
    execPath = cmd[0];
    args = cmd.slice(1);
  }
  if (execPath === 'npm')
    execPath = await Ndb.npmExecPath();
  else if (execPath === 'node')
    execPath = await Ndb.processInfo.nodeExecPath;
  return {
    name: 'main',
    command: cmd.join(' '),
    execPath,
    args,
    prof
  };
};

/**
 * @implements {UI.ContextMenu.Provider}
 * @unrestricted
 */
Ndb.ContextMenuProvider = class {
  /**
   * @override
   * @param {!Event} event
   * @param {!UI.ContextMenu} contextMenu
   * @param {!Object} object
   */
  appendApplicableItems(event, contextMenu, object) {
    if (!(object instanceof Workspace.UISourceCode))
      return;
    const url = object.url();
    if (!url.startsWith('file://') || (!url.endsWith('.js') && !url.endsWith('.mjs')))
      return;
    contextMenu.debugSection().appendItem(ls`Run this script`, async() => {
      const platformPath = await Ndb.backend.fileURLToPath(url);
      const args = url.endsWith('.mjs') ? ['--experimental-modules', platformPath] : [platformPath];
      Ndb.nodeProcessManager.debug(Ndb.processInfo.nodeExecPath, args);
    });
  }
};

Ndb._connectionSymbol = Symbol('connection');

Ndb.NodeProcessManager = class extends Common.Object {
  constructor(targetManager) {
    super();
    this._service = null;
    this._lastDebugId = 0;
    this._lastStarted = null;
    this._targetManager = targetManager;
    this._cwds = new Map();
    this._finishProfiling = null;
    this._cpuProfiles = [];
    this._targetManager.addModelListener(
        SDK.RuntimeModel, SDK.RuntimeModel.Events.ExecutionContextDestroyed, this._onExecutionContextDestroyed, this);
    this._targetManager.addModelListener(
        NdbSdk.NodeRuntimeModel, NdbSdk.NodeRuntimeModel.Events.WaitingForDisconnect, this._onWaitingForDisconnect, this);
  }

  static async create(targetManager) {
    const manager = new Ndb.NodeProcessManager(targetManager);
    manager._service = await Ndb.backend.createService('ndd_service.js', rpc.handle(manager));
    return manager;
  }

  env() {
    return this._service.env();
  }

  /**
   * @param {string} cwd
   * @param {string=} mainFileName
   * @return {!Promise}
   */
  async addFileSystem(cwd, mainFileName) {
    let promise = this._cwds.get(cwd);
    if (!promise) {
      async function innerAdd() {
        const fileSystemManager = Persistence.isolatedFileSystemManager;
        const fs = await Ndb.FileSystem.create(fileSystemManager, cwd);
        fileSystemManager.addPlatformFileSystem(cwd, fs);
        return fs;
      }
      promise = innerAdd();
      this._cwds.set(cwd, promise);
    }
    if (mainFileName)
      await (await promise).forceFileLoad(mainFileName);
    await promise;
  }

  async detected(info, channel) {
    const connection = await Ndb.Connection.create(channel);
    const target = this._targetManager.createTarget(
        info.id, userFriendlyName(info), SDK.Target.Type.Node,
        this._targetManager.targetById(info.ppid) || this._targetManager.mainTarget(), undefined, false, connection);
    target[NdbSdk.connectionSymbol] = connection;
    await this.addFileSystem(info.cwd, info.scriptName);
    if (info.scriptName) {
      const scriptURL = info.scriptName;
      const uiSourceCode = Workspace.workspace.uiSourceCodeForURL(scriptURL);
      const isBlackboxed = Bindings.blackboxManager.isBlackboxedURL(scriptURL, false);
      if (isBlackboxed)
        return connection.disconnect();
      if (uiSourceCode) {
        if (Common.moduleSetting('pauseAtStart').get() && !isBlackboxed)
          Bindings.breakpointManager.setBreakpoint(uiSourceCode, 0, 0, '', true);
        else
          Common.Revealer.reveal(uiSourceCode);
      }
    }
    if (info.data === this._profilingNddData)
      this._profiling.add(target.id());

    function userFriendlyName(info) {
      if (info.data === 'ndb/repl')
        return 'repl';
      return info.argv.map(arg => {
        const index1 = arg.lastIndexOf('/');
        const index2 = arg.lastIndexOf('\\');
        if (index1 === -1 && index2 === -1)
          return arg;
        return arg.slice(Math.max(index1, index2) + 1);
      }).join(' ');
    }
  }

  disconnected(sessionId) {
    const target = this._targetManager.targetById(sessionId);
    if (target) {
      this._targetManager.removeTarget(target);
      target.dispose();
    }
  }

  async terminalData(stream, data) {
    const content = await(await fetch(`data:application/octet-stream;base64,${data}`)).text();
    if (content.startsWith('Debugger listening on') || content.startsWith('Debugger attached.') || content.startsWith('Waiting for the debugger to disconnect...'))
      return;
    await Ndb.backend.writeTerminalData(stream, data);
    this.dispatchEventToListeners(Ndb.NodeProcessManager.Events.TerminalData, content);
  }

  async _onExecutionContextDestroyed(event) {
    const executionContext = event.data;
    if (!executionContext.isDefault)
      return;
    return this._onWaitingForDisconnect({data: executionContext.target()});
  }

  async _onWaitingForDisconnect(event) {
    const target = event.data;
    if (target.name() === 'repl')
      this.startRepl();
    if (this._profiling && (this._profiling.has(target.id()) || this._profiling.has(target.parentTarget().id()))) {
      this._cpuProfiles.push({
        profile: await target.model(SDK.CPUProfilerModel).stopRecording(),
        name: target.name(),
        id: target.id()
      });
      this._profiling.delete(target.id());
      if (this._profiling.size === 0)
        this._finishProfiling();
    }
    const connection = target[NdbSdk.connectionSymbol];
    if (connection)
      await connection.disconnect();
  }

  async startRepl() {
    const code = btoa(`console.log('Welcome to the ndb %cR%cE%cP%cL%c!',
      'color:#8bc34a', 'color:#ffc107', 'color:#ff5722', 'color:#2196f3', 'color:inherit');
      process.title = 'ndb/repl';
      process.on('uncaughtException', console.error);
      setInterval(_ => 0, 2147483647)//# sourceURL=repl.js`);
    const args = ['-e', `eval(Buffer.from('${code}', 'base64').toString())`];
    const options = { ignoreOutput: true, data: 'ndb/repl' };
    const node = Ndb.processInfo.nodeExecPath;
    return this.debug(node, args, options);
  }

  async debug(execPath, args, options) {
    options = options || {};
    const debugId = options.data || String(++this._lastDebugId);
    if (!options.data)
      this._lastStarted = {execPath, args, debugId, isProfiling: !!this._finishProfiling};

    return this._service.debug(
        execPath, args, {
          ...options,
          data: debugId,
          cwd: Ndb.processInfo.cwd
        });
  }

  async profile(execPath, args, options) {
    // TODO(ak239): move it out here.
    await UI.viewManager.showView('timeline');
    const action = UI.actionRegistry.action('timeline.toggle-recording');
    await action.execute();
    this._profilingNddData = String(++this._lastDebugId);
    this._profiling = new Set();
    this.debug(execPath, args, { data: this._profilingNddData });
    await new Promise(resolve => this._finishProfiling = resolve);
    this._profilingNddData = '';
    await Promise.all(SDK.targetManager.models(SDK.CPUProfilerModel).map(profiler => profiler.stopRecording()));
    const controller = Timeline.TimelinePanel.instance()._controller;
    const mainProfile = this._cpuProfiles.find(data => !data.id.includes('#'));
    controller.traceEventsCollected([{
      cat: SDK.TracingModel.DevToolsMetadataEventCategory,
      name: TimelineModel.TimelineModel.DevToolsMetadataEvent.TracingStartedInPage,
      ph: 'M', pid: 1, tid: mainProfile.id, ts: 0,
      args: {data: {sessionId: 1}}
    }]);
    for (const {profile, name, id} of this._cpuProfiles) {
      controller.traceEventsCollected([{
        cat: SDK.TracingModel.DevToolsMetadataEventCategory,
        name: TimelineModel.TimelineModel.DevToolsMetadataEvent.TracingSessionIdForWorker,
        ph: 'M', pid: 1, tid: id, ts: 0,
        args: {data: {sessionId: 1, workerThreadId: id, workerId: id, url: name}}
      }]);
      controller.traceEventsCollected(TimelineModel.TimelineJSProfileProcessor.buildTraceProfileFromCpuProfile(
          profile, id, false, TimelineModel.TimelineModel.WorkerThreadName));
    }
    this._cpuProfiles = [];
    await action.execute();
  }

  async kill(target) {
    return target.runtimeAgent().invoke_evaluate({
      expression: 'process.exit(-1)'
    });
  }

  async restartLast() {
    if (!this._lastStarted)
      return;
    await Promise.all(SDK.targetManager.targets()
        .filter(target => target.id() !== '<root>')
        .map(target => target.runtimeAgent().invoke_evaluate({
          expression: `'${this._lastStarted.debugId}' === process.env.NDD_DATA && process.exit(-1)`
        })));
    const {execPath, args, isProfiling} = this._lastStarted;
    if (!isProfiling)
      await this.debug(execPath, args);
    else
      await this.profile(execPath, args);
  }
};

Ndb.NodeProcessManager.Events = {
  TerminalData: Symbol('terminalData')
};

/**
 * @implements {UI.ActionDelegate}
 * @unrestricted
 */
Ndb.RestartActionDelegate = class {
  /**
   * @override
   * @param {!UI.Context} context
   * @param {string} actionId
   * @return {boolean}
   */
  handleAction(context, actionId) {
    switch (actionId) {
      case 'ndb.restart':
        Ndb.nodeProcessManager.restartLast();
        return true;
    }
    return false;
  }
};

// Temporary hack until frontend with fix is rolled.
// fix: TBA.
SDK.Target.prototype.decorateLabel = function(label) {
  return this.name();
};

// Front-end does not respect modern toggle semantics, patch it.
const originalToggle = DOMTokenList.prototype.toggle;
DOMTokenList.prototype.toggle = function(token, force) {
  if (arguments.length === 1)
    force = !this.contains(token);
  return originalToggle.call(this, token, !!force);
};

/**
 * @param {string} sourceMapURL
 * @param {string} compiledURL
 * @return {!Promise<?SDK.TextSourceMap>}
 * @this {SDK.TextSourceMap}
 */
SDK.TextSourceMap.load = async function(sourceMapURL, compiledURL) {
  const {payload, error} = await Ndb.backend.loadSourceMap(sourceMapURL, compiledURL);
  if (error || !payload)
    return null;

  let textSourceMap;
  try {
    textSourceMap = new SDK.TextSourceMap(compiledURL, sourceMapURL, payload);
  } catch (e) {
    Common.console.warn('DevTools failed to parse SourceMap: ' + sourceMapURL);
    return null;
  }

  const modulePrefix = await Ndb.backend.getNodeScriptPrefix();
  for (const uiSourceCode of Workspace.workspace.uiSourceCodes()) {
    if (uiSourceCode.url() === compiledURL && uiSourceCode.project().type() === Workspace.projectTypes.Network) {
      const content = await uiSourceCode.requestContent();
      if (content.startsWith(modulePrefix)) {
        for (const mapping of textSourceMap.mappings()) {
          if (!mapping.lineNumber)
            mapping.columnNumber += modulePrefix.length;
        }
        break;
      }
    }
  }

  return textSourceMap;
};


================================================
FILE: front_end/ndb/module.json
================================================
{
    "extensions": [
        {
            "type": "early-initialization",
            "className": "Ndb.NdbMain"
        },
        {
            "type": "setting",
            "category": "Debugger",
            "title": "Set breakpoint at first line",
            "settingName": "pauseAtStart",
            "settingType": "boolean",
            "defaultValue": false
        },
        {
            "type": "setting",
            "category": "Debugger",
            "title": "Autostart main",
            "settingName": "autoStartMain",
            "settingType": "boolean",
            "defaultValue": true
        },
        {
            "type": "setting",
            "category": "Debugger",
            "title": "Blackbox internal/* Node.js scripts",
            "settingName": "blackboxInternalScripts",
            "settingType": "boolean",
            "defaultValue": true
        },
        {
            "type": "action",
            "actionId": "ndb.restart",
            "className": "Ndb.RestartActionDelegate",
            "title": "Restart last run configuration",
            "bindings": [
                {
                    "platform": "windows,linux",
                    "shortcut": "F5 Ctrl+R"
                },
                {
                    "platform": "mac",
                    "shortcut": "Meta+R"
                }
            ]
        },
        {
            "type": "@UI.ContextMenu.Provider",
            "contextTypes": [
                "Workspace.UISourceCode"
            ],
            "className": "Ndb.ContextMenuProvider"
        }
    ],
    "dependencies": ["common", "sdk", "ndb_sdk", "bindings", "persistence", "components"],
    "scripts": [
        "InspectorFrontendHostOverrides.js",
        "Connection.js",
        "FileSystem.js",
        "NdbMain.js"
    ]
}


================================================
FILE: front_end/ndb.html
================================================
<!doctype html>
<html lang="en">
<head>
    <link rel="icon" type="image/png" href="favicon.png">
    <meta http-equiv="content-type" content="text/html; charset=utf-8">
    <meta name="referrer" content="no-referrer">
    <script type="text/javascript" src="Runtime.js"></script>
    <script type="text/javascript" src="ndb.js"></script>
</head>
<body class="undocked" id="-blink-dev-tools"></body>
</html>


================================================
FILE: front_end/ndb.js
================================================
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

Runtime.startApplication('ndb');


================================================
FILE: front_end/ndb.json
================================================
{
  "modules" : [
     { "name": "ndb_sdk", "type": "autostart" },
     { "name": "ndb", "type": "autostart" },
     { "name": "layer_viewer" },
     { "name": "timeline_model" },
     { "name": "timeline" },
     { "name": "product_registry" },
     { "name": "mobile_throttling" },
     { "name": "ndb_ui" },
     { "name": "xterm" }
 ],
  "extends": "shell",
  "has_html": true
}


================================================
FILE: front_end/ndb_sdk/NodeRuntime.js
================================================
Protocol.inspectorBackend.registerCommand('NodeRuntime.notifyWhenWaitingForDisconnect', [{'name': 'enabled', 'type': 'boolean', 'optional': false}], [], false);
Protocol.inspectorBackend.registerEvent('NodeRuntime.waitingForDisconnect', []);

NdbSdk.NodeRuntimeModel = class extends SDK.SDKModel {
  /**
   * @param {!SDK.Target} target
   */
  constructor(target) {
    super(target);

    this._agent = target.nodeRuntimeAgent();
    this.target().registerNodeRuntimeDispatcher(new NdbSdk.NodeRuntimeDispatcher(this));
    this._agent.notifyWhenWaitingForDisconnect(true);
  }

  /**
   * @param {string} sessionId
   * @param {!Object} workerInfo
   * @param {boolean} waitingForDebugger
   */
  _waitingForDisconnect() {
    this.dispatchEventToListeners(NdbSdk.NodeRuntimeModel.Events.WaitingForDisconnect, this.target());
  }
};

/** @enum {symbol} */
NdbSdk.NodeRuntimeModel.Events = {
  WaitingForDisconnect: Symbol('WaitingForDisconnect')
};

NdbSdk.NodeRuntimeDispatcher = class {
  constructor(nodeRuntimeModel) {
    this._nodeRuntimeModel = nodeRuntimeModel;
  }

  waitingForDisconnect() {
    this._nodeRuntimeModel._waitingForDisconnect();
  }
};


================================================
FILE: front_end/ndb_sdk/NodeWorker.js
================================================
Protocol.inspectorBackend.registerCommand('NodeWorker.enable', [{'name': 'waitForDebuggerOnStart', 'type': 'boolean', 'optional': false}], [], false);
Protocol.inspectorBackend.registerCommand('NodeWorker.disable', [], [], false);
Protocol.inspectorBackend.registerCommand('NodeWorker.sendMessageToWorker', [{'name': 'message', 'type': 'string', 'optional': false}, {'name': 'sessionId', 'type': 'string', 'optional': false}], [], false);
Protocol.inspectorBackend.registerCommand('NodeWorker.detach', [{'name': 'sessionId', 'type': 'string', 'optional': false}], [], false);
Protocol.inspectorBackend.registerEvent('NodeWorker.attachedToWorker', ['sessionId', 'workerInfo', 'waitingForDebugger']);
Protocol.inspectorBackend.registerEvent('NodeWorker.detachedFromWorker', ['sessionId']);
Protocol.inspectorBackend.registerEvent('NodeWorker.receivedMessageFromWorker', ['sessionId', 'message']);

NdbSdk.connectionSymbol = Symbol('connection');

NdbSdk.NodeWorkerModel = class extends SDK.SDKModel {
  /**
   * @param {!SDK.Target} target
   */
  constructor(target) {
    super(target);

    this._sessions = new Map();
    this._targets = new Map();
    this._agent = target.nodeWorkerAgent();
    this.target().registerNodeWorkerDispatcher(new NdbSdk.NodeWorkerDispatcher(this));
    this._agent.invoke_enable({waitForDebuggerOnStart: true});
  }

  /**
   * @param {string} message
   * @param {string} sessionId
   * @return {!Promise}
   */
  sendMessageToWorker(message, sessionId) {
    return this._agent.sendMessageToWorker(message, sessionId);
  }

  /**
   * @param {string} sessionId
   * @return {!Promise}
   */
  detach(sessionId) {
    return this._agent.detach(sessionId);
  }

  /**
   * @override
   */
  dispose() {
    this._sessions.clear();
    for (const target of this._targets.values()) {
      SDK.targetManager.removeTarget(target);
      target.dispose();
    }
    this._targets.clear();
  }

  /**
   * @param {string} sessionId
   * @param {!Object} workerInfo
   * @param {boolean} waitingForDebugger
   */
  _attachedToWorker(sessionId, workerInfo, waitingForDebugger) {
    const id = this.target().id() + '#' + workerInfo.workerId;
    const connection = new NdbSdk.NodeWorkerConnection(sessionId, this);
    this._sessions.set(sessionId, connection);
    const target = SDK.targetManager.createTarget(
        id, workerInfo.title, SDK.Target.Type.Node, this.target(),
        undefined, false, connection);
    target[NdbSdk.connectionSymbol] = connection;
    this._targets.set(sessionId, target);
    target.runtimeAgent().runIfWaitingForDebugger();
  }

  /**
   * @param {string} sessionId
   */
  _detachedFromWorker(sessionId) {
    const session = this._sessions.get(sessionId);
    if (session) {
      this._sessions.delete(sessionId);
      const target = this._targets.get(sessionId);
      if (target) {
        SDK.targetManager.removeTarget(target);
        target.dispose();
        this._targets.delete(sessionId);
      }
    }
  }

  /**
   * @param {string} sessionId
   * @param {string} message
   */
  _receivedMessageFromWorker(sessionId, message) {
    const session = this._sessions.get(sessionId);
    if (session)
      session.receivedMessageFromWorker(message);
  }
};

NdbSdk.NodeWorkerConnection = class {
  constructor(sessionId, nodeWorkerModel) {
    this._onMessage = null;
    this._onDisconnect = null;
    this._sessionId = sessionId;
    this._nodeWorkerModel = nodeWorkerModel;
  }

  /**
   * @param {function((!Object|string))} onMessage
   */
  setOnMessage(onMessage) {
    this._onMessage = onMessage;
  }

  /**
   * @param {function(string)} onDisconnect
   */
  setOnDisconnect(onDisconnect) {
    this._onDisconnect = onDisconnect;
  }

  /**
   * @param {string} message
   */
  sendRawMessage(message) {
    this._nodeWorkerModel.sendMessageToWorker(message, this._sessionId);
  }

  /**
   * @return {!Promise}
   */
  disconnect() {
    return this._nodeWorkerModel.detach(this._sessionId);
  }

  /**
   * @param {string} message
   */
  receivedMessageFromWorker(message) {
    if (this._onMessage)
      this._onMessage(message);
  }

  detachedFromWorker() {
    if (this._onDisconnect)
      this._onDisconnect();
  }
};

NdbSdk.NodeWorkerDispatcher = class {
  /**
   * @param {!NdbSdk.NodeWorkerModel}
   */
  constructor(nodeWorkerModel) {
    this._nodeWorkerModel = nodeWorkerModel;
  }

  /**
   * @param {string} sessionId
   * @param {!Object} workerInfo
   * @param {boolean} waitingForDebugger
   */
  attachedToWorker(sessionId, workerInfo, waitingForDebugger) {
    this._nodeWorkerModel._attachedToWorker(sessionId, workerInfo, waitingForDebugger);
  }

  /**
   * @param {string} sessionId
   */
  detachedFromWorker(sessionId) {
    this._nodeWorkerModel._detachedFromWorker(sessionId);
  }

  /**
   * @param {string} sessionId
   * @param {string} message
   */
  receivedMessageFromWorker(sessionId, message) {
    this._nodeWorkerModel._receivedMessageFromWorker(sessionId, message);
  }
};


================================================
FILE: front_end/ndb_sdk/module.json
================================================
{
    "dependencies": ["sdk"],
    "scripts": [
        "NodeRuntime.js",
        "NodeWorker.js"
    ]
}


================================================
FILE: front_end/ndb_ui/NodeProcesses.js
================================================
/**
 * @license Copyright 2018 Google Inc. All Rights Reserved.
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
 */

Ndb.NodeProcesses = class extends UI.VBox {
  constructor() {
    super(true);
    this.registerRequiredCSS('ndb_ui/nodeProcesses.css');

    const toolbar = new UI.Toolbar('process-toolbar', this.contentElement);
    this._pauseAtStartCheckbox = new UI.ToolbarSettingCheckbox(
        Common.moduleSetting('pauseAtStart'));
    this._pauseAtStartCheckbox.element.id = 'pause-at-start-checkbox';
    toolbar.appendToolbarItem(this._pauseAtStartCheckbox);

    this._emptyElement = this.contentElement.createChild('div', 'gray-info-message');
    this._emptyElement.id = 'no-running-nodes-msg';
    this._emptyElement.textContent = Common.UIString('No running nodes');

    this._treeOutline = new UI.TreeOutlineInShadow();
    this._treeOutline.registerRequiredCSS('ndb_ui/nodeProcesses.css');
    this.contentElement.appendChild(this._treeOutline.element);
    this._treeOutline.element.classList.add('hidden');

    this._targetToUI = new Map();
    SDK.targetManager.observeTargets(this);
  }

  /**
   * @override
   * @param {!SDK.Target} target
   */
  targetAdded(target) {
    if (target.id() === '<root>')
      return;
    if (target.name() === 'repl')
      return;
    const f = UI.Fragment.build`
      <div class=process-item>
        <div class=process-title>${target.name()}</div>
        <div $=state class=process-item-state></div>
      </div>
      <div class='controls-container fill'>
        <div class=controls-gradient></div>
        <div $=controls-buttons class=controls-buttons></div>
      </div>
    `;
    const debuggerModel = target.model(SDK.DebuggerModel);
    debuggerModel.addEventListener(SDK.DebuggerModel.Events.DebuggerPaused, () => {
      f.$('state').textContent = 'paused';
    });
    debuggerModel.addEventListener(SDK.DebuggerModel.Events.DebuggerResumed, () => {
      f.$('state').textContent = 'attached';
    });
    f.$('state').textContent = debuggerModel.isPaused() ? 'paused' : 'attached';

    const buttons = f.$('controls-buttons');
    const toolbar = new UI.Toolbar('', buttons);
    const button = new UI.ToolbarButton(Common.UIString('Kill'), 'largeicon-terminate-execution');
    button.addEventListener(UI.ToolbarButton.Events.Click, _ => Ndb.nodeProcessManager.kill(target));
    toolbar.appendToolbarItem(button);

    const treeElement = new UI.TreeElement(f.element());
    treeElement.onselect = _ => {
      if (UI.context.flavor(SDK.Target) !== target)
        UI.context.setFlavor(SDK.Target, target);
    };

    const parentTarget = target.parentTarget();
    let parentTreeElement = this._treeOutline.rootElement();
    if (parentTarget) {
      const parentUI = this._targetToUI.get(parentTarget);
      if (parentUI)
        parentTreeElement = parentUI.treeElement;
    }
    parentTreeElement.appendChild(treeElement);
    parentTreeElement.expand();

    if (!this._targetToUI.size) {
      this._emptyElement.classList.add('hidden');
      this._treeOutline.element.classList.remove('hidden');
    }
    this._targetToUI.set(target, {treeElement, f});
  }

  /**
   * @override
   * @param {!SDK.Target} target
   */
  targetRemoved(target) {
    const ui = this._targetToUI.get(target);
    if (ui) {
      const parentTreeElement = ui.treeElement.parent;
      for (const child of ui.treeElement.children().slice()) {
        ui.treeElement.removeChild(child);
        parentTreeElement.appendChild(child);
      }
      parentTreeElement.removeChild(ui.treeElement);
      this._targetToUI.delete(target);
    }
    if (!this._targetToUI.size) {
      this._emptyElement.classList.remove('hidden');
      this._treeOutline.element.classList.add('hidden');
    }
  }

  _targetFlavorChanged({data: target}) {
    const treeElement = this._targetToUI.get(target);
    if (treeElement)
      treeElement.select();
  }
};


================================================
FILE: front_end/ndb_ui/RunConfiguration.js
================================================
/**
 * @license Copyright 2018 Google Inc. All Rights Reserved.
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
 */

Ndb.RunConfiguration = class extends UI.VBox {
  constructor() {
    super(true);
    this.registerRequiredCSS('ndb_ui/runConfiguration.css');
    this._items = new UI.ListModel();
    this._list = new UI.ListControl(this._items, this, UI.ListMode.NonViewport);
    this.contentElement.appendChild(this._list.element);
    this.update();
  }

  async update() {
    const configurations = [];
    const main = await Ndb.mainConfiguration();
    if (main)
      configurations.push(main);
    const pkg = await Ndb.backend.pkg();
    if (pkg) {
      const scripts = pkg.scripts || {};
      this._items.replaceAll(configurations.concat(Object.keys(scripts).map(name => ({
        name,
        command: scripts[name],
        args: ['run', name]
      }))));
    }
  }

  /**
   * @override
   * @param {!SDK.DebuggerModel} debuggerModel
   * @return {!Element}
   */
  createElementForItem(item) {
    const f = UI.Fragment.build`
    <div class=list-item>
      <div class=configuration-item>
        <div>${item.name}</div>
        <div class=configuration-command>${item.command}</div>
      </div>
      <div class='controls-container fill'>
        <div class=controls-gradient></div>
        <div $=controls-buttons class=controls-buttons></div>
      </div>
    </div>`;
    const buttons = f.$('controls-buttons');
    const toolbar = new UI.Toolbar('', buttons);
    const runButton = new UI.ToolbarButton(Common.UIString('Run'), 'largeicon-play');
    runButton.addEventListener(UI.ToolbarButton.Events.Click, this._runConfig.bind(this, item.execPath, item.args));
    toolbar.appendToolbarItem(runButton);
    const profileButton = new UI.ToolbarButton(Common.UIString('Start recording..'), 'largeicon-start-recording');
    profileButton.addEventListener(UI.ToolbarButton.Events.Click, this._profileConfig.bind(this, item.execPath, item.args));
    toolbar.appendToolbarItem(profileButton);
    return f.element();
  }

  async _runConfig(execPath, args) {
    await Ndb.nodeProcessManager.debug(execPath || await Ndb.npmExecPath(), args);
  }

  async _profileConfig(execPath, args) {
    await Ndb.nodeProcessManager.profile(execPath || await Ndb.npmExecPath(), args);
  }

  /**
   * @override
   * @param {!SDK.DebuggerModel} debuggerModel
   * @return {number}
   */
  heightForItem(debuggerModel) {
    return 12;
  }

  /**
   * @override
   * @param {!SDK.DebuggerModel} debuggerModel
   * @return {boolean}
   */
  isItemSelectable(debuggerModel) {
    return false;
  }

  /**
   * @override
   * @param {?Profiler.IsolateSelector.ListItem} from
   * @param {?Profiler.IsolateSelector.ListItem} to
   * @param {?Element} fromElement
   * @param {?Element} toElement
   */
  selectedItemChanged(from, to, fromElement, toElement) {}
};


================================================
FILE: front_end/ndb_ui/Terminal.js
================================================
/**
 * @license Copyright 2018 Google Inc. All Rights Reserved.
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
 */

Terminal.applyAddon(fit);

Ndb.Terminal = class extends UI.VBox {
  constructor() {
    super(true);
    this._init = false;
    this.registerRequiredCSS('xterm/dist/xterm.css');
    this.element.addEventListener('contextmenu', this._onContextMenu.bind(this));
    this._terminal = Ndb.Terminal._createTerminal();
    this._terminal.on('resize', this._sendResize.bind(this));
    this._terminal.on('data', this._sendData.bind(this));
    Ndb.nodeProcessManager.addEventListener(Ndb.NodeProcessManager.Events.TerminalData, this._terminalData, this);
  }

  static _createTerminal() {
    const terminal = new Terminal();
    let fontFamily;
    let fontSize = 11;
    if (Host.isMac()) {
      fontFamily = 'Menlo, monospace';
    } else if (Host.isWin()) {
      fontFamily = 'Consolas, Lucida Console, Courier New, monospace';
      fontSize = 12;
    } else {
      fontFamily = 'dejavu sans mono, monospace';
    }
    terminal.setOption('fontFamily', fontFamily);
    terminal.setOption('fontSize', fontSize);
    terminal.setOption('cursorStyle', 'bar');
    terminal.setOption('convertEol', true);
    return terminal;
  }

  async _restartService() {
    if (this._backend)
      this._backend.dispose();
    const env = await Ndb.nodeProcessManager.env();
    this._anotherTerminalHint(env);
    this._backend = await Ndb.backend.createService(
        'terminal.js',
        rpc.handle(this),
        env,
        this._terminal.cols,
        this._terminal.rows);
  }

  _anotherTerminalHint(env) {
    this._terminal.writeln('# Want to use your own terminal? Copy paste following lines..');
    this._terminal.writeln('');
    this._terminal.writeln(Object.keys(env).map(k => `export ${k}='${env[k]}'`).join('\n'));
    this._terminal.writeln('');
    this._terminal.writeln('# ..and after you can run any node program (e.g., npm run unit), ndb will detect it.');
    this._terminal.writeln('');
  }

  /**
   * @param {!Event} event
   */
  _onContextMenu(event) {
    const selection = this._terminal ? this._terminal.getSelection() : null;
    const contextMenu = new UI.ContextMenu(event);
    const copyItem = contextMenu.defaultSection().appendItem(ls`Copy`, () => navigator.clipboard.writeText(selection));
    copyItem.setEnabled(!!selection);
    contextMenu.defaultSection().appendItem(ls`Paste`, async() => {
      if (this._backend)
        this._backend.write(await navigator.clipboard.readText());
    });
    contextMenu.show();
  }

  /**
   * @param {string} error
   */
  async initFailed(error) {
    this._terminal.write('# Builtin terminal is unvailable: ' + error.replace(/\n/g, '\n#'));
    this._terminal.writeln('');
  }

  /**
   * @param {string} data
   */
  dataAdded(data) {
    if (data.startsWith('Debugger listening on') || data.startsWith('Debugger attached.') || data.startsWith('Waiting for the debugger to disconnect...'))
      return;
    this._terminal.write(data);
  }

  closed() {
    this._restartService();
  }

  /**
   * @param {!{cols: number, rows: number}} size
   */
  _sendResize(size) {
    if (this._backend)
      this._backend.resize(size.cols, size.rows);
  }

  /**
   * @param {string} data
   */
  _sendData(data) {
    if (this._backend)
      this._backend.write(data);
  }

  onResize() {
    this._terminal.fit();
  }

  _terminalData(event) {
    this._terminal.write(event.data);
  }

  wasShown() {
    if (this._init)
      return;
    this._init = true;
    this._terminal.open(this.contentElement);
    this._restartService();
  }
};


================================================
FILE: front_end/ndb_ui/module.json
================================================
{
  "extensions": [
      {
          "type": "view",
          "category": "NDB",
          "id": "ndb.runView",
          "title": "NPM Scripts",
          "persistence": "permanent",
          "location": "run-view-sidebar",
          "className": "Ndb.RunConfiguration"
      },
      {
          "type": "view",
          "location": "drawer-view",
          "id": "ndb.terminal",
          "title": "Terminal",
          "persistence": "permanent",
          "order": 1,
          "className": "Ndb.Terminal"
      },
      {
          "type": "view",
          "id": "ndb.processes-view",
          "title": "Node processes",
          "persistence": "permanent",
          "className": "Ndb.NodeProcesses",
          "location": "sources.sidebar-top"
      }
  ],
  "dependencies": ["ui", "sources", "timeline", "ndb", "xterm"],
  "scripts": [
      "RunConfiguration.js",
      "NodeProcesses.js",
      "Terminal.js"
  ],
  "resources": [
      "runConfiguration.css",
      "nodeProcesses.css"
  ]
}


================================================
FILE: front_end/ndb_ui/nodeProcesses.css
================================================
/**
 * @license Copyright 2018 Google Inc. All Rights Reserved.
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
 */

.process-toolbar {
  background-color: var(--toolbar-bg-color);
  border-bottom: var(--divider-border);
}

.tree-outline li {
  min-height: 20px;
}

.tree-outline li::before {
  display: none;
}

.process-item {
  width: 100%;
  display: flex;
  flex-wrap: wrap;
}

.process-item-state {
  color: #888;
  margin-left: auto;
  padding: 0 10px 0 10px;
}

.controls-container {
  display: flex;
  flex-direction: row;
  justify-content: flex-end;
  align-items: stretch;
  pointer-events: none;
}

.controls-gradient {
    flex: 0 1 50px;
}

li:hover .controls-gradient {
  background-image: linear-gradient(90deg, transparent, hsl(0, 0%, 96%));
}

.controls-buttons {
  flex: none;
  display: flex;
  flex-direction: row;
  align-items: center;
  pointer-events: auto;
  visibility: hidden;
}

li:hover .controls-buttons {
  background-color: hsl(0, 0%, 96%);
  visibility: visible;
}


================================================
FILE: front_end/ndb_ui/runConfiguration.css
================================================
/**
 * @license Copyright 2018 Google Inc. All Rights Reserved.
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
 */

.list-item {
  flex: none;
  min-height: 30px;
  display: flex;
  align-items: center;
  position: relative;
  overflow: hidden;
  padding-bottom: 3px;
}

.list-item:hover {
  background: hsl(0, 0%, 96%);
}

.list-item {
  border-top: 1px solid #efefef;
}

.configuration-item {
  padding: 3px 5px 3px 5px;
  height: 30px;
  align-items: center;
  position: relative;
  flex: auto 1 0;
  width: 100%;
}

.configuration-command {
  color: #888;
  margin-left: auto;
  padding: 0 10px 0 10px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.controls-container {
  display: flex;
  flex-direction: row;
  justify-content: flex-end;
  align-items: stretch;
  pointer-events: none;
}

.controls-gradient {
    flex: 0 1 50px;
}

.list-item:hover .controls-gradient {
  background-image: linear-gradient(90deg, transparent, hsl(0, 0%, 96%));
}

.controls-buttons {
  flex: none;
  display: flex;
  flex-direction: row;
  align-items: center;
  pointer-events: auto;
  visibility: hidden;
}

.list-item:hover .controls-buttons {
  background-color: hsl(0, 0%, 96%);
  visibility: visible;
}

================================================
FILE: front_end/xterm/module.json
================================================
{
  "scripts": [
      "dist/xterm.js",
      "dist/addons/fit/fit.js"
  ],
  "resources": [
      "dist/xterm.css"
  ]
}


================================================
FILE: lib/backend.js
================================================
/**
 * @license Copyright 2018 Google Inc. All Rights Reserved.
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
 */

const { rpc_process } = require('carlo/rpc');
const fs = require('fs');
const path = require('path');
const readline = require('readline');
const util = require('util');
const { URL } = require('url');
const { Readable } = require('stream');
const opn = require('opn');
const querystring = require('querystring');
const which = require('which');

const fsReadFile = util.promisify(fs.readFile);
const { fileURLToPath } = require('./filepath_to_url.js');

const MODULE_WRAP_PREFIX = (() => {
  const wrapped = require('module').wrap('☃');
  return wrapped.substring(0, wrapped.indexOf('☃'));
})();

class Backend {
  constructor(window) {
    this._window = window;
    this._info = JSON.parse(Buffer.from(window.paramsForReuse().data, 'base64').toString('utf8'));
    this._handles = [];
    this._window.on('close', () => this._handles.splice(0).forEach(handle => handle.dispose()));
  }

  async createService(name, ...args) {
    const fileName = path.join(__dirname, '..', 'services', name);
    const handle = await rpc_process.spawn(fileName, ...args);
    this._handles.push(handle);
    return handle;
  }

  bringToFront() {
    return this._window.bringToFront();
  }

  /**
   * @param {text} url
   */
  openInNewTab(url) {
    opn(url);
  }

  pkg() {
    // TODO(ak239spb): implement it as decorations over package.json file.
    try {
      return require(path.join(fileURLToPath(this._info.cwd), 'package.json'));
    } catch (e) {
      return null;
    }
  }

  async loadSourceMap(sourceMapURL, compiledURL) {
    try {
      let payload;
      if (sourceMapURL.startsWith('data:')) {
        const [metadata, ...other] = sourceMapURL.split(',');
        const urlPayload = other.join(',');
        const isBase64 = metadata.endsWith(';base64');
        payload = JSON.parse(Buffer.from(isBase64 ? urlPayload : querystring.unescape(urlPayload), isBase64 ? 'base64' : 'utf8').toString('utf8'));
      } else {
        const fileURL = new URL(sourceMapURL);
        const content = await fsReadFile(fileURL, 'utf8');
        payload = JSON.parse(content);
      }
      await removeSourceContentIfMatch(sourceMapURL, compiledURL, payload);
      return {payload};
    } catch (e) {
      return {error: e.stack};
    }
  }

  getNodeScriptPrefix() {
    return MODULE_WRAP_PREFIX;
  }

  which(command) {
    return new Promise(resolve => which(command, (error, resolvedPath) => resolve({
      resolvedPath: resolvedPath,
      error: error ? error.message : null
    })));
  }

  processInfo() {
    return this._info;
  }

  writeTerminalData(stream, data) {
    const buffer = Buffer.from(data, 'base64');
    if (stream === 'stderr')
      process.stderr.write(buffer);
    else if (stream === 'stdout')
      process.stdout.write(buffer);
  }

  fileURLToPath(url) {
    return fileURLToPath(url);
  }

  async loadNetworkResource(url, headers) {
    try {
      if (url.startsWith('file://')) {
        const fileURL = new URL(url);
        return await fsReadFile(fileURL, 'utf8');
      } else {
        return null;
      }
    } catch (e) {
      return null;
    }
  }
}

class StringStream extends Readable {
  constructor(str) {
    super();
    this._str = str;
    this._ended = false;
  }

  _read() {
    if (this._ended)
      return;
    this._ended = true;
    process.nextTick(_ => {
      this.push(Buffer.from(this._str, 'utf8'));
      this.push(null);
    });
  }
}

async function removeSourceContentIfMatch(sourceMapURL, compiledURL, payload) {
  const {sourcesContent, sources} = payload;
  if (!sourcesContent || !sources)
    return;
  for (let i = 0; i < sources.length; ++i) {
    if (!sources[i] || !sourcesContent[i]) continue;
    let url = sources[i];
    if (!path.isAbsolute(url))
      url = path.join(path.dirname(compiledURL), url);
    if (!fs.existsSync(url))
      continue;
    const sourceContentStream = new StringStream(sourcesContent[i]);
    const sourceContentLines = await readLines(sourceContentStream);
    const fileStream = fs.createReadStream(url);
    const fileStreamLines = await readLines(fileStream);
    if (sourceContentLines.length === fileStreamLines.length) {
      let equal = true;
      for (let i = 0; i < sourceContentLines.length; ++i) {
        if (sourceContentLines[i] !== fileStreamLines[i]) {
          equal = false;
          break;
        }
      }
      if (equal)
        sourcesContent[i] = undefined;
    }
  }
}

async function readLines(stream) {
  const rl = readline.createInterface({
    input: stream,
    crlfDelay: Infinity
  });
  return new Promise(resolve => {
    stream.once('error', _ => resolve(null));
    const lines = [];
    rl.on('line', line => lines.push(line));
    rl.on('close', _ => resolve(lines));
  });
}

module.exports = { Backend };


================================================
FILE: lib/filepath_to_url.js
================================================
const url = require('url');

if (url.pathToFileURL) {
  module.exports = {
    pathToFileURL: url.pathToFileURL,
    fileURLToPath: url.fileURLToPath
  };
} else {
  // Node 8 does not have nice url methods.
  // Polyfill should match DevTools frontend behavior,
  // otherwise breakpoints will not work.
  function pathToFileURL(fileSystemPath) {
    fileSystemPath = fileSystemPath.replace(/\\/g, '/');
    if (!fileSystemPath.startsWith('file://')) {
      if (fileSystemPath.startsWith('/'))
        fileSystemPath = 'file://' + fileSystemPath;
      else
        fileSystemPath = 'file:///' + fileSystemPath;
    }
    return fileSystemPath;
  }
  /**
   * @param {string} fileURL
   * @return {string}
   */
  function fileURLToPath(fileURL) {
    if (process.platform === 'win32')
      return fileURL.substr('file:///'.length).replace(/\//g, '\\');
    return fileURL.substr('file://'.length);
  }

  module.exports = {
    fileURLToPath,
    pathToFileURL,
  };
}


================================================
FILE: lib/launcher.js
================================================
/**
 * @license Copyright 2018 Google Inc. All Rights Reserved.
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
 */

const path = require('path');
const carlo = require('carlo');
const { rpc, rpc_process } = require('carlo/rpc');
const { pathToFileURL } = require('./filepath_to_url.js');
const { Backend } = require('./backend.js');

process.on('unhandledRejection', error => {
  if (error.message.includes('Protocol error') && error.message.includes('Target closed'))
    process.exit(1);
  console.log('unhandledRejection', error.stack || error.message);
});

async function launch() {
  let app;
  const carloArgs = process.env.NDB_CARLO_ARGS ? JSON.parse(process.env.NDB_CARLO_ARGS) : {};
  try {
    app = await carlo.launch({
      bgcolor: '#242424',
      channel: ['chromium'],
      paramsForReuse: {
        data: Buffer.from(JSON.stringify({
          cwd: pathToFileURL(process.cwd()).toString(),
          argv: process.argv,
          nodeExecPath: process.execPath
        })).toString('base64')
      },
      ...carloArgs
    });
  } catch (e) {
    if (e.message !== 'Could not start the browser or the browser was already running with the given profile.')
      throw e;
    process.exit(0);
  }

  process.title = 'ndb/main';
  const appName = 'ndb';
  const debugFrontend = !!process.env.NDB_DEBUG_FRONTEND;

  app.setIcon(path.join(__dirname, '..', 'front_end', 'favicon.png'));
  const overridesFolder = debugFrontend
    ? path.dirname(require.resolve(`../front_end/${appName}.json`))
    : path.join(__dirname, '..', '.local-frontend');
  app.serveFolder(overridesFolder);
  if (debugFrontend) {
    try {
      app.serveFolder(path.join(__dirname, '..', 'node_modules'));
      app.serveFolder(path.dirname(require.resolve(`chrome-devtools-frontend/front_end/ndb_app.json`)));
    } catch (e) {
      console.log('To use NDB_DEBUG_FRONTEND=1 you should run npm install from ndb folder first');
      process.exit(1);
    }
  }
  app.on('exit', () => setTimeout(() => process.exit(0), 0));
  app.on('window', load);
  load(app.mainWindow());

  async function load(window) {
    const params = [
      ['experiments', true],
      ['debugFrontend', debugFrontend],
      ['sources.hide_add_folder', true],
      ['sources.hide_thread_sidebar', true],
      ['timelineTracingJSProfileDisabled', true],
      ['panel', 'sources']];
    const paramString = params.reduce((acc, p) => acc += `${p[0]}=${p[1]}&`, '');
    window.load(`${appName}.html?${paramString}`, rpc.handle(new Backend(window)));
  }
}

module.exports = {launch};


================================================
FILE: lib/preload/ndb/preload.js
================================================
/**
 * @license Copyright 2018 Google Inc. All Rights Reserved.
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
 */

(function() {
  if (!process.env.NDD_IPC)
    return;
  if (!process.env.NDD_PUBLISH_DATA) {
    try {
      if (!require('worker_threads').isMainThread)
        return;
    } catch (e) {
      // node 8 does not support workers
    }
    const { pathToFileURL } = require('../../filepath_to_url.js');
    let scriptName = '';
    try {
      scriptName = pathToFileURL(require.resolve(process.argv[1])).toString();
    } catch (e) {
      // preload can get scriptName iff node starts with script as first argument,
      // we should be ready for exception in other cases, e.g., node -e '...'
    }
    const ppid = process.env.NDD_PPID;
    process.env.NDD_PPID = process.pid;
    if (!process.env.NDD_DATA)
      process.env.NDD_DATA = process.pid + '_ndbId';
    process.versions['ndb'] = '1.1.5';
    const inspector = require('inspector');
    inspector.open(0, undefined, false);
    const info = {
      cwd: pathToFileURL(process.cwd()),
      argv: process.argv.concat(process.execArgv),
      data: process.env.NDD_DATA,
      ppid: ppid,
      id: String(process.pid),
      inspectorUrl: inspector.url(),
      scriptName: scriptName
    };
    const {execFileSync} = require('child_process');
    execFileSync(process.execPath, [__filename], {
      env: {
        NDD_IPC: process.env.NDD_IPC,
        NDD_PUBLISH_DATA: JSON.stringify(info)
      }
    });
  } else {
    const net = require('net');
    const TIMEOUT = 30000;
    const socket = net.createConnection(process.env.NDD_IPC, () => {
      socket.write(process.env.NDD_PUBLISH_DATA);
      const timeoutId = setTimeout(() => socket.destroy(), TIMEOUT);
      socket.on('data', () => {
        clearTimeout(timeoutId);
        socket.destroy();
      });
    });
    socket.on('error', err => {
      process.stderr.write('\u001b[31mndb is not found:\u001b[0m\n');
      process.stderr.write('please restart it and update env variables or unset NDD_IPC and NODE_OPTIONS.\n');
      process.exit(0);
    });
  }
})();
// eslint-disable-next-line spaced-comment
//# sourceURL=internal/preload.js


================================================
FILE: lib/process_utility.js
================================================
/**
 * @license Copyright 2018 Google Inc. All Rights Reserved.
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
 */

module.exports = prepareProcess;

function prepareProcess(name, disposeCallback) {
  process.title = 'ndb/' + name;
  function silentRpcErrors(error) {
    if (!process.connected && error.code === 'ERR_IPC_CHANNEL_CLOSED')
      return;
    throw error;
  }
  process.on('uncaughtException', silentRpcErrors);
  process.on('unhandledRejection', silentRpcErrors);
  // dispose when child process is disconnected
  process.on('disconnect', () => disposeCallback());
}


================================================
FILE: ndb.js
================================================
#!/usr/bin/env node
/**
 * @license Copyright 2018 Google Inc. All Rights Reserved.
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
 */

require('update-notifier')({pkg: require('./package.json')}).notify({isGlobal: true});

if (process.argv.length > 2 && (process.argv[2] === '-v' || process.argv[2] === '--version')) {
  console.log(`v${require('./package.json').version}`);
  process.exit(0);
}

if (process.argv.length > 2 && process.argv[2] === '--help') {
  console.log('Usage:');
  console.log('');
  console.log('Use ndb instead of node command:');
  console.log('\tndb server.js');
  console.log('\tndb node server.js');
  console.log('');
  console.log('Prepend ndb in front of any other binary:');
  console.log('\tndb npm run unit');
  console.log('\tndb mocha');
  console.log('\tndb npx mocha');
  console.log('');
  console.log('Launch ndb as a standalone application:');
  console.log('\tndb .');
  console.log('');
  console.log('More information is available at https://github.com/GoogleChromeLabs/ndb#readme');
  process.exit(0);
}

const {launch} = require('./lib/launcher.js');
launch();


================================================
FILE: package.json
================================================
{
  "name": "ndb",
  "version": "1.1.5",
  "description": "Chrome DevTools for Node.js",
  "main": "ndb.js",
  "repository": "github:GoogleChromeLabs/ndb",
  "homepage": "https://github.com/GoogleChromeLabs/ndb#readme",
  "engines": {
    "node": ">=8.0.0"
  },
  "bin": {
    "ndb": "./ndb.js"
  },
  "scripts": {
    "lint": "./node_modules/.bin/eslint .",
    "prepare": "node build.js",
    "test": "npm run lint",
    "version": "node version.js && git add ./lib/preload/ndb/preload.js"
  },
  "author": "The Chromium Authors",
  "license": "Apache-2.0",
  "dependencies": {
    "carlo": "^0.9.46",
    "chokidar": "^3.0.2",
    "debug": "^4.1.1",
    "isbinaryfile": "^3.0.3",
    "mime": "^2.4.4",
    "opn": "^5.5.0",
    "update-notifier": "^2.5.0",
    "which": "^1.3.1",
    "ws": "^6.2.1",
    "xterm": "^3.14.5"
  },
  "optionalDependencies": {
    "node-pty": "^0.9.0-beta18"
  },
  "devDependencies": {
    "chrome-devtools-frontend": "1.0.672485",
    "eslint": "^5.16.0",
    "mocha": "^5.2.0",
    "rimraf": "^2.6.3",
    "terser": "^3.17.0"
  }
}


================================================
FILE: scripts/builder.js
================================================
const fs = {
  ...require('fs'),
  ...require('fs').promises
};
const os = require('os');
const path = require('path');

/**
 * @typedef {Object} AppDescriptor
 * @property {string} name
 * @property {!Array<!Object>} modules
 * @property {boolean} hasHtml
 * @property {?string} extends
 */

/**
 * @typedef {Object} RawModuleDescriptor
 * @property {!Array} dependencies
 * @property {!Array} scripts
 * @property {!Array} resources
 * @property {!Array} extensions
 * @property {string} experiment
 */

/**
 * @typedef {Object} ModuleDescriptor
 * @property {string} content
 * @property {!Array=} extensions
 * @property {!Array=} dependencies
 * @property {string=} experiment
 */

/**
 * @param {!Array<string>} appNames
 * @param {!Array<string>} pathFolders
 * @return {!Promise<!Map<string, !AppDescriptor>>}
 */
async function loadAppDescriptors(appNames, pathFolders) {
  const descriptors = new Map();
  const descriptorQueue = appNames.slice(0);
  while (descriptorQueue.length) {
    const name = descriptorQueue.shift();
    if (descriptors.has(name))
      continue;
    const source = await loadSource(pathFolders, name + '.json');
    const content = JSON.parse(source);
    const descriptor = {
      name: name,
      modules: content.modules || [],
      hasHtml: content.has_html || false,
    };
    if (content.extends) {
      descriptor.extends = content.extends;
      descriptorQueue.push(descriptor.extends);
    }
    descriptors.set(name, descriptor);
  }
  return descriptors;
}

/**
 * @param {string} moduleName
 * @return {string}
 */
function moduleNamespace(moduleName) {
  const specialCaseNameSpaces = {
    'sdk': 'SDK',
    'js_sdk': 'JSSDK',
    'browser_sdk': 'BrowserSDK',
    'ui': 'UI',
    'object_ui': 'ObjectUI',
    'perf_ui': 'PerfUI',
    'har_importer': 'HARImporter',
    'sdk_test_runner': 'SDKTestRunner',
    'cpu_profiler_test_runner': 'CPUProfilerTestRunner'
  };
  return moduleName in specialCaseNameSpaces
    ? specialCaseNameSpaces[moduleName]
    : moduleName.split('_').map(name => name.charAt(0).toUpperCase() + name.substr(1)).join('');
}

/**
 * @param {!Map<string, !AppDescriptor>} appDescriptors
 * @param {!Array<string>} pathFolders
 * @return {!Promise<!Map<string, !ModuleDescriptor>>}
 */
async function loadModules(appDescriptors, pathFolders, customLoadModuleSource) {
  const modules = new Map();
  appDescriptors.forEach(descriptor => descriptor.modules.forEach(module => modules.set(module.name, null)));
  await Promise.all(Array.from(modules).map(async([moduleName, module]) => {
    modules.set(moduleName, await loadModule(pathFolders, moduleName, customLoadModuleSource));
  }));
  return modules;
}

/**
 * @param {!Array<string>} pathFolders
 * @param {string} moduleName
 * @param {!function(!Object):!Promise<string>} customLoadModuleSource
 */
async function loadModule(pathFolders, moduleName, customLoadModuleSource) {
  const { descriptor: rawDescriptor, paths } = await loadRawModule(pathFolders, moduleName, 'module.json');

  let scriptContent = await customLoadModuleSource(rawDescriptor, paths);
  const promises = [];
  if (scriptContent === null) {
    scriptContent = '';
    promises.push(...(rawDescriptor.scripts || []).map(name => loadSource(pathFolders, moduleName, name)));
  }
  promises.push(...(rawDescriptor.resources || []).map(name => loadResource(pathFolders, moduleName, name)));
  scriptContent += (await Promise.all(promises)).join('\n');

  const namespace = moduleNamespace(moduleName);
  const content = `self['${namespace}'] = self['${namespace}'] || {};\n${scriptContent}\n`;

  const descriptor = { content };
  if (rawDescriptor.extensions)
    descriptor.extensions = rawDescriptor.extensions;
  if (rawDescriptor.dependencies)
    descriptor.dependencies = rawDescriptor.dependencies;
  if (rawDescriptor.experiment)
    descriptor.experiment = rawDescriptor.experiment;
  return descriptor;

  /**
   * @param {!Array<string>} pathFolders
   * @param {string} moduleName
   * @param {string} name
   */
  async function loadResource(pathFolders, moduleName, name) {
    const resource = await loadSource(pathFolders, moduleName, name);
    const content = resource.replace(/\\/g, '\\\\').replace(/\n/g, '\\n').replace(/'/g, '\\\'');
    return `Runtime.cachedResources['${moduleName}/${name}'] = '${content}';`;
  }
}

/**
 * @param {!Map<string, !ModuleDescriptor>} moduleDescriptors
 * @return {!Promise<!Array<!{content: !Buffer, name: string}>>}
 */
async function loadImages(moduleDescriptors, pathFolders) {
  const images = [];
  const re = /Images\/[\w.-]+/g;
  for (const [, module] of moduleDescriptors) {
    const m = module.content.match(re);
    if (m)
      images.push(...m);
  }
  return Promise.all(Array.from(new Set(images)).map(async image => ({
    content: await fs.readFile(lookupFile(pathFolders, image)[0]),
    name: image
  })));
}

/**
 * @params {!Array<string>} pathFolders
 * @param {!Array<string>} fileNameParts
 * @return {!Array<string>}
 */
function lookupFile(pathFolders, ...fileNameParts) {
  const paths = [];
  for (const pathFolder of pathFolders) {
    const absoluteFileName = path.join(pathFolder, ...fileNameParts);
    if (fs.existsSync(absoluteFileName))
      paths.push(absoluteFileName);
  }
  if (paths.length === 0)
    console.error(`File ${fileNameParts.join(path.sep)} not found in ${pathFolders}`);
  return paths;
}

/**
 * @params {!Array<string>} pathFolders
 * @param {!Array<string>} fileNameParts
 * @return {!Promise<string>}
 */
async function loadSource(pathFolders, ...fileNameParts) {
  const paths = lookupFile(pathFolders, ...fileNameParts).reverse();
  return (await Promise.all(paths.map(name => fs.readFile(name, 'utf8')))).join('\n');
}

/**
 * @params {!Array<string>} pathFolders
 * @param {!Array<string>} fileNameParts
 * @return {!Promise<!{descriptor: !RawModuleDescriptor, paths: !Array<string>}>}
 */
async function loadRawModule(pathFolders, ...fileNameParts) {
  const paths = lookupFile(pathFolders, ...fileNameParts);
  const sources = await Promise.all(paths.map(name => fs.readFile(name, 'utf8')));
  if (paths.length > 1)
    console.error('Module ' + fileNameParts[0] + ' overriden');
  const descriptors = sources.map(data => JSON.parse(data)).reverse();
  const descriptor = {
    dependencies: [],
    scripts: [],
    resources: [],
    extensions: [],
    experiment: '',
    ...descriptors[0]
  };
  return { descriptor, paths: [path[0]] };
}

/**
 * @param {!Array<string>} appNames
 * @param {!Array<string>} pathFolders
 * @param {string} outFolder
 * @param {function(string):string=} minifyJS
 * @return {!Promise}
 */
async function buildApp(appNames, pathFolders, outFolder, minifyJS = code => code, customLoadModuleSource = descriptor => Promise.resolve(null)) {
  const descriptors = await loadAppDescriptors(appNames, pathFolders);
  const modules = await loadModules(descriptors, pathFolders, customLoadModuleSource);
  const fetchedImages = await loadImages(modules, pathFolders);
  const runtime = await loadSource(pathFolders, 'Runtime.js');

  const builtApps = [];
  const notAutoStartModules = new Set();
  for (const appName of appNames) {
    const appDescriptor = { modules: [], hasHtml: false };
    let current = descriptors.get(appName);
    while (current) {
      appDescriptor.modules.push(...current.modules);
      appDescriptor.hasHtml = appDescriptor.hasHtml || current.hasHtml;
      current = current.extends ? descriptors.get(current.extends) : null;
    }

    const moduleDescriptors = appDescriptor.modules.map(module => {
      const moduleName = module.name;
      const moduleDescriptor = modules.get(module.name);
      const descriptor = { name: moduleName, remote: false };
      if (module.type !== 'autostart')
        descriptor.scripts = [`${moduleName}_module.js`];
      if (moduleDescriptor.extensions)
        descriptor.extensions = moduleDescriptor.extensions;
      if (moduleDescriptor.dependencies)
        descriptor.dependencies = moduleDescriptor.dependencies;
      if (moduleDescriptor.experiment)
        descriptor.experiment = moduleDescriptor.experiment;
      return descriptor;
    });

    const autoStartModulesByName = new Map();
    appDescriptor.modules.map(module => {
      if (module.type === 'autostart')
        autoStartModulesByName.set(module.name, module);
      else
        notAutoStartModules.add(module.name);
    });

    const appScript = await loadSource(pathFolders, appName + '.js');

    let scriptContent = '';
    scriptContent += '/* Runtime.js */\n' + runtime + '\n';
    scriptContent += `allDescriptors.push(...${JSON.stringify(moduleDescriptors)});\n`;
    scriptContent += `applicationDescriptor = ${JSON.stringify(appDescriptor)};\n`;
    scriptContent += appScript;
    const visitedModule = new Set();
    for (const [, module] of autoStartModulesByName)
      scriptContent += writeModule(modules, module, autoStartModulesByName, visitedModule);

    let htmlContent = '';
    if (appDescriptor.hasHtml) {
      const content = await loadSource(pathFolders, appName + '.html');
      htmlContent = content.replace(/<script.*?src="Runtime.js"><\/script>/, '<!-- <script src="Runtime.js"></script> -->');
    }

    builtApps.push({
      scriptContent,
      htmlContent,
      name: appName
    });

    function writeModule(modules, module, autoStartModulesByName, visitedModule) {
      if (visitedModule.has(module.name))
        return '';
      visitedModule.add(module.name);
      const builtModule = modules.get(module.name);
      let content = '';
      for (const dep of builtModule.dependencies) {
        const depModule = autoStartModulesByName.get(dep);
        if (!depModule)
          console.error(`Autostart module ${module.name} depends on not autostart module ${dep}`);
        content += writeModule(modules, depModule, autoStartModulesByName, visitedModule);
      }
      return content + builtModule.content;
    }
  }

  const favicon = await fs.readFile(lookupFile(pathFolders, 'favicon.png')[0]);

  const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'devtools-frontend-'));
  const promises = [];
  for (const app of builtApps) {
    promises.push(fs.writeFile(path.join(tmpDir, app.name + '.js'), await minifyJS(app.scriptContent)));
    if (app.htmlContent)
      promises.push(fs.writeFile(path.join(tmpDir, app.name + '.html'), app.htmlContent));
  }

  promises.push(...Array.from(notAutoStartModules).map(async moduleName => {
    await fs.mkdir(path.join(tmpDir, moduleName));
    return fs.writeFile(path.join(tmpDir, moduleName, moduleName + '_module.js'), await minifyJS(modules.get(moduleName).content));
  }));

  const createImageFolder = fs.mkdir(path.join(tmpDir, 'Images'));
  promises.push(...fetchedImages.map(async image => {
    await createImageFolder;
    if (image.content)
      return fs.writeFile(path.join(tmpDir, 'Images', image.name.substr('Images/'.length)), image.content);
  }));
  if (favicon)
    promises.push(fs.writeFile(path.join(tmpDir, 'favicon.png'), favicon));
  await Promise.all(promises);

  await fs.rename(tmpDir, outFolder);
}

module.exports = { buildApp };


================================================
FILE: services/file_system.js
================================================
const fs = require('fs');

const { rpc, rpc_process } = require('carlo/rpc');
const chokidar = require('chokidar');
const {pathToFileURL, fileURLToPath} = require('../lib/filepath_to_url.js');

class FileSystemHandler {
  constructor() {
    require('../lib/process_utility.js')('file_system', () => this.dispose());
    this._watcher = null;
    this._embedderPath = '';
    this._client = null;
  }

  startWatcher(embedderPath, exludePattern, client, mainFileName) {
    this._embedderPath = fileURLToPath(embedderPath);
    this._client = client;
    this._watcher = chokidar.watch([this._embedderPath], {
      ignored: new RegExp(exludePattern),
      awaitWriteFinish: true,
      ignorePermissionErrors: true
    });
    const events = [];
    this._watcher.on('all', (event, name) => {
      if (event === 'add' || event === 'change' || event === 'unlink') {
        if (!events.length)
          setTimeout(() => client.filesChanged(events.splice(0)), 100);
        events.push({
          type: event,
          name: pathToFileURL(name).toString()
        });
      }
    });
    this._watcher.on('error', console.error);
  }

  forceFileLoad(fileNameURL) {
    const fileName = fileURLToPath(fileNameURL);
    if (fileName.startsWith(this._embedderPath) && fs.existsSync(fileName))
      this._client.filesChanged([{type: 'add', name: fileNameURL}]);
  }

  dispose() {
    this._watcher.close();
    Promise.resolve().then(() => process.exit(0));
  }
}

rpc_process.init(args => rpc.handle(new FileSystemHandler()));


================================================
FILE: services/file_system_io.js
================================================
const { rpc, rpc_process } = require('carlo/rpc');
const fs = require('fs');
const { URL } = require('url');

class FileSystemIO {
  constructor() {
    require('../lib/process_utility.js')('file_system_io', () => this.dispose());
  }

  /**
   * @param {string} fileURL
   * @param {string} encoding
   */
  readFile(fileURL, encoding) {
    return fs.readFileSync(new URL(fileURL), encoding);
  }

  /**
   * @param {string} fileURL
   * @param {string} content
   * @param {string} encoding
   */
  writeFile(fileURL, content, encoding) {
    if (encoding === 'base64')
      content = Buffer.from(content, 'base64');
    fs.writeFileSync(new URL(fileURL), content, {encoding: encoding});
  }

  /**
   * @param {string} folderURL
   */
  createFile(folderURL) {
    let name = 'NewFile';
    let counter = 1;
    while (fs.existsSync(new URL(folderURL + '/' + name))) {
      name = 'NewFile' + counter;
      ++counter;
    }
    fs.writeFileSync(new URL(folderURL + '/' + name), '');
    return folderURL + '/' + name;
  }

  /**
   * @param {string} fileURL
   */
  deleteFile(fileURL) {
    try {
      fs.unlinkSync(new URL(fileURL));
      return true;
    } catch (e) {
      return false;
    }
  }

  /**
   * @param {string} fileURL
   * @param {string} newName
   */
  renameFile(fileURL, newName) {
    const newURL = new URL(fileURL.substr(0, fileURL.lastIndexOf('/') + 1) + newName);
    try {
      if (fs.existsSync(newURL)) return false;
      fs.renameSync(new URL(fileURL), newURL);
      return true;
    } catch (e) {
      return false;
    }
  }

  dispose() {
    Promise.resolve().then(() => process.exit(0));
  }
}

rpc_process.init(args => rpc.handle(new FileSystemIO()));


================================================
FILE: services/ndd_service.js
================================================
/**
 * @license Copyright 2018 Google Inc. All Rights Reserved.
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
 */

const { spawn } = require('child_process');
const os = require('os');
const path = require('path');
const net = require('net');
const { fileURLToPath } = require('../lib/filepath_to_url.js');

const protocolDebug = require('debug')('ndd_service:protocol');
const caughtErrorDebug = require('debug', 'ndd_service:caught');
const { rpc, rpc_process } = require('carlo/rpc');
const WebSocket = require('ws');

function silentRpcErrors(error) {
  if (!process.connected && error.code === 'ERR_IPC_CHANNEL_CLOSED')
    return;
  throw error;
}

process.on('uncaughtException', silentRpcErrors);
process.on('unhandledRejection', silentRpcErrors);

const DebugState = {
  WS_OPEN: 1,
  WS_ERROR: 2,
  WS_CLOSE: 3,
  PROCESS_DISCONNECT: 4
};

const CALL_EXIT_MESSAGE = JSON.stringify({
  id: -1,
  method: 'Runtime.evaluate',
  params: { expression: 'process.exit(-1)' }
});

class Channel {
  /**
   * @param {!WebSocket} ws
   */
  constructor(ws) {
    this._ws = ws;
    this._handler = null;
    this._messageListener = this._messageReceived.bind(this);
    this._ws.on('message', this._messageListener);
  }

  /**
   * @param {string} message
   */
  send(message) {
    if (this._ws.readyState === WebSocket.OPEN) {
      protocolDebug('>', message);
      this._ws.send(message);
    }
  }

  close() {
    this._ws.close();
  }

  /**
   * @param {!Object}
   */
  listen(handler) {
    this._handler = handler;
  }

  dispose() {
    this._ws.removeListener('message', this._messageListener);
  }

  /**
   * @param {string} message
   */
  _messageReceived(message) {
    if (this._handler) {
      protocolDebug('<', message);
      this._handler.dispatchMessage(message);
    }
  }
}

class NddService {
  constructor(frontend) {
    process.title = 'ndb/ndd_service';
    this._disconnectPromise = new Promise(resolve => process.once('disconnect', () => resolve(DebugState.PROCESS_DISCONNECT)));
    this._connected = new Set();
    this._frontend = frontend;

    const pipePrefix = process.platform === 'win32' ? '\\\\.\\pipe\\' : os.tmpdir();
    const pipeName = `node-ndb.${process.pid}.sock`;
    this._pipe = path.join(pipePrefix, pipeName);
    const server = net.createServer(socket => {
      socket.on('data', async d => {
        const runSession = await this._startSession(JSON.parse(d), frontend);
        socket.write('run');
        runSession();
      });
      socket.on('error', e => caughtErrorDebug(e));
    }).listen(this._pipe);
    server.unref();
  }

  dispose() {
    process.disconnect();
  }

  async _startSession(info, frontend) {
    const ws = new WebSocket(info.inspectorUrl);
    const openPromise = new Promise(resolve => ws.once('open', () => resolve(DebugState.WS_OPEN)));
    const errorPromise = new Promise(resolve => ws.once('error', () => resolve(DebugState.WS_ERROR)));
    const closePromise = new Promise(resolve => ws.once('close', () => resolve(DebugState.WS_CLOSE)));
    let state = await Promise.race([openPromise, errorPromise, closePromise, this._disconnectPromise]);
    if (state === DebugState.WS_OPEN) {
      this._connected.add(info.id);
      const channel = new Channel(ws);
      state = await Promise.race([frontend.detected(info, rpc.handle(channel)), this._disconnectPromise]);
      return async() => {
        if (state !== DebugState.PROCESS_DISCONNECT)
          state = await Promise.race([closePromise, errorPromise, this._disconnectPromise]);
        channel.dispose();
        this._connected.delete(info.id);
        if (state !== DebugState.PROCESS_DISCONNECT)
          frontend.disconnected(info.id);
        else
          ws.send(CALL_EXIT_MESSAGE, () => ws.close());
      };
    } else {
      return async function() {};
    }
  }

  env() {
    return {
      NODE_OPTIONS: `--require ndb/preload.js`,
      NODE_PATH: `${process.env.NODE_PATH || ''}${path.delimiter}${path.join(__dirname, '..', 'lib', 'preload')}`,
      NDD_IPC: this._pipe
    };
  }

  async debug(execPath, args, options) {
    const env = this.env();
    if (options.data)
      env.NDD_DATA = options.data;
    const p = spawn(execPath, args, {
      cwd: options.cwd ? fileURLToPath(options.cwd) : undefined,
      env: { ...process.env, ...env },
      stdio: options.ignoreOutput ? 'ignore' : ['inherit', 'pipe', 'pipe'],
      windowsHide: true
    });
    if (!options.ignoreOutput) {
      p.stderr.on('data', data => {
        if (process.connected)
          this._frontend.terminalData('stderr', data.toString('base64'));
      });
      p.stdout.on('data', data => {
        if (process.connected)
          this._frontend.terminalData('stdout', data.toString('base64'));
      });
    }
    const finishPromise = new Promise(resolve => {
      p.once('exit', resolve);
      p.once('error', resolve);
    });
    const result = await Promise.race([finishPromise, this._disconnectPromise]);
    if (result === DebugState.PROCESS_DISCONNECT && !this._connected.has(p.pid)) {
      // The frontend can start the process but disconnects before it is
      // finished if it is blackboxed (e.g., npm process); in this case, we need
      // to kill this process here.
      p.kill();
    }
  }
}

rpc_process.init(frontend => rpc.handle(new NddService(frontend)));


================================================
FILE: services/search.js
================================================
/**
 * @license Copyright 2018 Google Inc. All Rights Reserved.
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
 */

const { rpc, rpc_process } = require('carlo/rpc');
const fs = require('fs');
const path = require('path');

const { fileURLToPath, pathToFileURL } = require('../lib/filepath_to_url.js');

const isbinaryfile = require('isbinaryfile');

// TODO(ak239): track changed files.

class SearchBackend {
  constructor(frontend) {
    require('../lib/process_utility.js')('search', () => this.dispose());
    this._frontend = frontend;
    this._activeIndexing = new Set();
    this._index = new Map();
    this._filesQueue = new Set();

    this._lastFileNameIndex = 0;
    this._indexToFileName = new Map();
    this._fileNameToIndex = new Map();
  }

  /**
   * @param {number} requestId
   * @param {string} fileSystemPath
   */
  async indexPath(requestId, fileSystemPath, excludedPattern) {
    fileSystemPath = fileURLToPath(fileSystemPath);
    const excludeRegex = new RegExp(excludedPattern);
    if (this._index.has(fileSystemPath)) {
      this._indexChangedFiles(requestId, fileSystemPath);
      return;
    }
    this._activeIndexing.add(requestId);
    const index = new Map();
    const directories = [fileSystemPath];
    const allFiles = [];
    while (directories.length) {
      if (!this._activeIndexing.has(requestId))
        return;
      const directory = directories.shift();
      await new Promise(done => fs.readdir(directory, 'utf8', async(err, files) => {
        if (err) {
          done();
          return;
        }
        files = files.filter(file => !file.startsWith('.'));
        files = files.map(file => path.join(directory, file));
        await Promise.all(files.map(file => new Promise(done => fs.stat(file, (err, stats) => {
          if (err) {
            done();
            return;
          }
          const relativeName = path.relative(fileSystemPath, file);
          const testName = `/${relativeName}${stats.isDirectory() ? '/' : ''}`.replace(/\\/g, '/');
          if (excludeRegex && excludeRegex.test(testName)) {
            done();
            return;
          }
          if (stats.isDirectory())
            directories.push(file);
          if (stats.isFile())
            allFiles.push(file);
          done();
        }))));
        done();
      }));
    }

    const textFiles = [];
    for (const file of allFiles) {
      if (file.endsWith('.js') || file.endsWith('.json'))
        textFiles.push(file);
      else if (!await new Promise(resolve => isbinaryfile(file, (err, isBinary) => resolve(err || isBinary))))
        textFiles.push(file);
    }
    this._frontend.indexingTotalWorkCalculated(requestId, fileSystemPath, textFiles.length);
    for (const fileName of textFiles) {
      if (!this._activeIndexing.has(requestId))
        return;
      await this._indexFile(fileName, index);
      this._frontend.indexingWorked(requestId, fileSystemPath, 1);
    }
    this._index.set(fileSystemPath, index);
    this._frontend.indexingDone(requestId, fileSystemPath);
    this._activeIndexing.delete(requestId);
  }

  /**
   * @param {string} fileName
   * @param {!Map<string,!Set<string>>} index
   * @return {!Promise}
   */
  _indexFile(fileName, index) {
    const stream = fs.createReadStream(fileName, {encoding: 'utf8'});
    return new Promise(done => {
      let prev = '';
      const trigrams = new Set();
      stream.on('error', finished.bind(this));
      stream.on('data', chunk => {
        chunk = prev + chunk;
        chunk = chunk.toLowerCase();
        while (chunk.length > 3) {
          trigrams.add(chunk.substring(0, 3));
          chunk = chunk.substring(1);
        }
        prev = chunk;
      });
      stream.on('end', finished.bind(this));

      function finished() {
        let fileNameIndex;
        if (this._fileNameToIndex.has(fileName)) {
          fileNameIndex = this._fileNameToIndex.get(fileName);
        } else {
          fileNameIndex = ++this._lastFileNameIndex;
          this._indexToFileName.set(fileNameIndex, fileName);
          this._fileNameToIndex.set(fileName, fileNameIndex);
        }
        for (const trigram of trigrams) {
          let values = index.get(trigram);
          if (!values) {
            values = new Set();
            index.set(trigram, values);
          }
          values.add(fileNameIndex);
        }
        done();
      }
    }).then(() => stream.close());
  }

  /**
   * @param {number} requestId
   * @param {string} fileSystemPath
   */
  async _indexChangedFiles(requestId, fileSystemPath) {
    if (!this._filesQueue.size) {
      this._frontend.indexingDone(requestId);
      return;
    }
    this._activeIndexing.add(requestId);

    const allFiles = Array.from(this._filesQueue);
    this._filesQueue.clear();

    const textFiles = [];
    for (const file of allFiles) {
      if (file.endsWith('.js') || file.endsWith('.json'))
        textFiles.push(file);
      else if (!await new Promise(resolve => isbinaryfile(file, (err, isBinary) => resolve(err || isBinary))))
        textFiles.push(file);
    }

    this._frontend.indexingTotalWorkCalculated(requestId, textFiles.length);
    const index = this._index.get(fileSystemPath);
    while (textFiles.length) {
      if (!this._activeIndexing.has(requestId)) {
        for (const fileName of textFiles)
          this._filesQueue.add(fileName);
        return;
      }
      const fileName = textFiles.shift();
      await this._indexFile(fileName, index);
      this._frontend.indexingWorked(requestId, 1);
    }
    this._activeIndexing.delete(requestId);
    this._frontend.indexingDone(requestId);
  }

  /**
   * @param {number} requestId
   */
  stopIndexing(requestId) {
    this._activeIndexing.delete(requestId);
  }

  /**
   * @param {number} requestId
   * @param {string} fileSystemPath
   * @param {string} query
   */
  searchInPath(requestId, fileSystemPath, query) {
    fileSystemPath = fileURLToPath(fileSystemPath);
    const index = this._index.get(fileSystemPath);
    let result = [];
    query = query.toLowerCase();
    if (index && query.length === 0) {
      result = Array.from(new Set(index.values()));
    } else if (index && query.length > 2) {
      let resultSet = index.get(query.substring(0, 3)) || new Set();
      for (let i = 1; i < query.length - 2; ++i) {
        const trigram = query.substring(i, i + 3);
        const current = index.get(trigram) || new Set();
        const nextCurrent = new Set();
        for (const file of current) {
          if (resultSet.has(file))
            nextCurrent.add(file);
        }
        resultSet = nextCurrent;
      }
      result = Array.from(resultSet);
    }
    result = result.map(index => this._indexToFileName.get(index));
    result = result.map(result => pathToFileURL(result).toString());
    this._frontend.searchCompleted(requestId, fileSystemPath, result);
  }

  dispose() {
    Promise.resolve().then(() => process.exit(0));
  }
}

rpc_process.init(frontend => rpc.handle(new SearchBackend(frontend)));


================================================
FILE: services/terminal.js
================================================
/**
 * @license Copyright 2018 Google Inc. All Rights Reserved.
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
 */

const fs = require('fs');
const { rpc, rpc_process } = require('carlo/rpc');

class Terminal {
  constructor(frontend, pty, env, cols, rows) {
    require('../lib/process_utility.js')('terminal', () => this.dispose());
    let shell = process.env.SHELL;
    if (!shell || !fs.existsSync(shell))
      shell = process.platform === 'win32' ? 'cmd.exe' : 'bash';
    this._term = pty.spawn(shell, [], {
      name: 'xterm-color',
      cols: cols,
      rows: rows,
      cwd: process.cwd(),
      env: {
        ...process.env,
        ...env
      }
    });
    this._term.on('data', data => frontend.dataAdded(data));
    this._term.on('close', () => frontend.closed());
  }

  dispose() {
    Promise.resolve().then(() => process.exit(0));
  }

  resize(cols, rows) {
    this._term.resize(cols, rows);
  }

  write(data) {
    this._term.write(data);
  }
}

async function init(frontend, env, cols, rows) {
  try {
    const pty = require('node-pty');
    return rpc.handle(new Terminal(frontend, pty, env, cols, rows));
  } catch (e) {
    await frontend.initFailed(e.stack);
    setImmediate(() => process.exit(0));
    return null;
  }
}

rpc_process.init(init);


================================================
FILE: test/assets/test-project/index.js
================================================
/**
 * @license Copyright 2018 Google Inc. All Rights Reserved.
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
 */

console.log(42);


================================================
FILE: test/assets/test-project/index.mjs
================================================
/**
 * @license Copyright 2018 Google Inc. All Rights Reserved.
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
 */

  console.log(239);


================================================
FILE: test/assets/test-project/package.json
================================================
{
  "name": "test-project",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "run": "node index.js",
    "test": "echo \"Error: no test specified\" && exit 1",
    "run-module": "node --experimental-modules index.mjs",
    "run-module-without-flag": "node index.mjs",
    "atexit": "node -e \"process.once('exit', _ => console.log(42))\""
  },
  "keywords": []
}


================================================
FILE: test/basic.spec.js
================================================
/**
 * @license Copyright 2018 Google Inc. All Rights Reserved.
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
 */

const assert = require('assert');
const fs = require('fs');
const os = require('os');
const path = require('path');
const removeFolder = require('rimraf');
const util = require('util');

const {launch} = require('../lib/launcher.js');
const {ReleaseBuilder} = require('../scripts/build_release_application.js');

const fsMkdtemp = util.promisify(fs.mkdtemp);

module.exports.addTests = function({testRunner}) {
  // eslint-disable-next-line
  const {beforeAll, afterAll} = testRunner;
  // eslint-disable-next-line
  const {it, fit, xit} = testRunner;
  xit('run configuration', async function() {
    const configDir = await fsMkdtemp(path.join(os.tmpdir(), 'ndb-test-'));
    const frontend = await launch({
      configDir: configDir,
      argv: ['.'],
      cwd: path.join(__dirname, 'assets', 'test-project'),
      debugFrontend: false,
      doNotCopyPreferences: true
    });

    const configItem = await frontend.waitForSelector('body /deep/ .list-item');
    configItem.hover();
    const runButton = await frontend.waitForSelector('body /deep/ .list-item /deep/ [aria-label=Run]', {
      visible: true
    });
    runButton.click();
    const consoleMessage = await frontend.waitForSelector('body /deep/ .console-message-wrapper:nth-child(3) .console-message-text');
    assert.equal('42', await frontend.evaluate(x => x.innerText, consoleMessage));

    await frontend.close();
    await util.promisify(removeFolder)(configDir);
  });

  xit('run, pause at start, kill', async function() {
    const configDir = await fsMkdtemp(path.join(os.tmpdir(), 'ndb-test-'));
    const frontend = await launch({
      configDir: configDir,
      argv: ['.'],
      cwd: path.join(__dirname, 'assets', 'test-project'),
      debugFrontend: false,
      doNotCopyPreferences: true
    });

    const [pauseAtStartCheckbox, configItem] = await Promise.all([
      frontend.waitForSelector('body /deep/ #pause-at-start-checkbox'),
      frontend.waitForSelector('body /deep/ .list-item')
    ]);
    await pauseAtStartCheckbox.click();
    configItem.hover();
    const runButton = await frontend.waitForSelector('body /deep/ .list-item /deep/ [aria-label=Run]', {
      visible: true
    });
    runButton.click();
    const executionLine = await frontend.waitForSelector('.cm-execution-line .CodeMirror-line');
    const executionLineText = await frontend.evaluate(x => x.innerText, executionLine);
    assert.equal(executionLineText, 'console.log(42);');

    const processItem = await frontend.waitForSelector('body /deep/ li.selected');
    processItem.hover();

    const killButton = await frontend.waitForSelector('body /deep/ li.selected /deep/ [aria-label=Kill]');
    killButton.click();
    await frontend.waitForSelector('body /deep/ #no-running-nodes-msg', {
      visible: true
    });

    await frontend.close();
    await util.promisify(removeFolder)(configDir);
  });

  xit('terminal', async function() {
    const configDir = await fsMkdtemp(path.join(os.tmpdir(), 'ndb-test-'));
    const frontend = await launch({
      configDir: configDir,
      argv: ['.'],
      cwd: path.join(__dirname, 'assets', 'test-project'),
      debugFrontend: false,
      doNotCopyPreferences: true
    });

    const [pauseAtStartCheckbox, terminalTab, resumeButton, consoleTab] = await Promise.all([
      frontend.waitForSelector('body /deep/ #pause-at-start-checkbox'),
      frontend.waitForSelector('body /deep/ #tab-ndb\\.terminal'),
      frontend.waitForSelector('body /deep/ [aria-label="Pause script execution"]'),
      frontend.waitForSelector('body /deep/ #tab-console-view')
    ]);
    await pauseAtStartCheckbox.click();
    terminalTab.click();
    const terminal = await frontend.waitForSelector('body /deep/ .xterm-cursor-layer', {
      visible: true
    });
    await frontend.click('body /deep/ .xterm-cursor-layer');
    await frontend.type('body /deep/ .xterm-cursor-layer', 'node -e "console.log(42)"');
    await terminal.press('Enter');

    const executionLine = await frontend.waitForSelector('.cm-execution-line .CodeMirror-line');
    const executionLineText = await frontend.evaluate(x => x.innerText, executionLine);
    assert.equal(executionLineText, 'console.log(42);');

    resumeButton.click();

    await frontend.waitForSelector('body /deep/ #no-running-nodes-msg', {
      visible: true
    });

    consoleTab.click();
    const consoleMessage = await frontend.waitForSelector('body /deep/ .console-message-wrapper:nth-child(2) .console-message-text');
    assert.equal('42', await frontend.evaluate(x => x.innerText, consoleMessage));

    await frontend.close();
    await util.promisify(removeFolder)(configDir);
  });

  xit('terminal exit', async function() {
    const configDir = await fsMkdtemp(path.join(os.tmpdir(), 'ndb-test-'));
    const frontend = await launch({
      configDir: configDir,
      argv: ['.'],
      cwd: path.join(__dirname, 'assets', 'test-project'),
      debugFrontend: false,
      doNotCopyPreferences: true
    });

    const [terminalTab, consoleTab] = await Promise.all([
      frontend.waitForSelector('body /deep/ #tab-ndb\\.terminal'),
      frontend.waitForSelector('body /deep/ #tab-console-view'),
    ]);
    terminalTab.click();
    const terminal = await frontend.waitForSelector('body /deep/ .xterm-cursor-layer', {
      visible: true
    });
    await frontend.click('body /deep/ .xterm-cursor-layer');
    await frontend.type('body /deep/ .xterm-cursor-layer', 'exit');
    await terminal.press('Enter');
    // we need better way to wait until terminal reconnected.
    await new Promise(resolve => setTimeout(resolve, 300));
    await frontend.type('body /deep/ .xterm-cursor-layer', 'node -e "console.log(42)"');
    await terminal.press('Enter');

    consoleTab.click();
    const consoleMessage = await frontend.waitForSelector('body /deep/ .console-message-wrapper:nth-child(2) .console-message-text');
    assert.equal('42', await frontend.evaluate(x => x.innerText, consoleMessage));

    await frontend.close();
    await util.promisify(removeFolder)(configDir);
  });

  xit('repl and uncaught error', async function() {
    const configDir = await fsMkdtemp(path.join(os.tmpdir(), 'ndb-test-'));
    const frontend = await launch({
      configDir: configDir,
      argv: ['.'],
      cwd: path.join(__dirname, 'assets', 'test-project'),
      debugFrontend: false,
      doNotCopyPreferences: true
    });
    const consolePrompt = await frontend.waitForSelector('body /deep/ #console-prompt');
    await frontend.type('body /deep/ #console-prompt', 'require("child_process").spawn("!@#$%")');
    await consolePrompt.press('Enter');
    await frontend.type('body /deep/ #console-prompt', 'console.log(42)');
    consolePrompt.press('Enter');
    const consoleMessage = await frontend.waitForSelector('body /deep/ .console-message-wrapper:nth-child(6) .console-message-text');
    assert.equal('42', await frontend.evaluate(x => x.innerText, consoleMessage));
    await frontend.close();
    await util.promisify(removeFolder)(configDir);
  });

  beforeAll(async function(state) {
    const DEVTOOLS_DIR = path.dirname(
        require.resolve('chrome-devtools-frontend/front_end/shell.json'));
    const frontendFolder = await fsMkdtemp(path.join(os.tmpdir(), 'ndb-test-frontend-'));
    await new ReleaseBuilder([
      path.join(__dirname, '..', 'front_end'),
      DEVTOOLS_DIR,
      path.join(__dirname, '..'),
      path.join(__dirname, '..', '..', '..')
    ], frontendFolder).buildApp('integration_test_runner');
    state.frontendFolder = frontendFolder;
  });

  afterAll(async function(state) {
    return util.promisify(removeFolder)(state.frontendFolder);
  });

  it('breakpoint inside .mjs file', async function(state) {
    const configDir = await fsMkdtemp(path.join(os.tmpdir(), 'ndb-test-'));
    const frontend = await launch({
      configDir: configDir,
      argv: ['.'],
      cwd: path.join(__dirname, 'assets', 'test-project'),
      debugFrontend: false,
      doNotCopyPreferences: true,
      appName: 'integration_test_runner',
      releaseFrontendFolder: state.frontendFolder,
      doNotProcessExit: true
    });
    await setupHelpers(frontend);
    await frontend.showScriptSource('index.mjs');
    await frontend.setBreakpoint(6, '');
    await frontend.waitForConfigurations();

    {
      frontend.runConfiguration('run-module');
      const {frames: [{location}]} = await frontend.waitUntilPaused();
      assert.equal(6, location.lineNumber);
      assert.equal(2, location.columnNumber);
      await frontend.resumeExecution();
    }

    {
      frontend.runConfiguration('run-module-without-flag');
      const {frames: [{location}]} = await frontend.waitUntilPaused();
      assert.equal(6, location.lineNumber);
      assert.equal(2, location.columnNumber);
      await frontend.resumeExecution();
    }

    await frontend.close();
    await util.promisify(removeFolder)(configDir);
  });

  it('Stay attached', async function(state) {
    const configDir = await fsMkdtemp(path.join(os.tmpdir(), 'ndb-test-'));
    const frontend = await launch({
      configDir: configDir,
      argv: ['.'],
      cwd: path.join(__dirname, 'assets', 'test-project'),
      debugFrontend: false,
      doNotCopyPreferences: true,
      appName: 'integration_test_runner',
      releaseFrontendFolder: state.frontendFolder,
      doNotProcessExit: true
    });
    await setupHelpers(frontend);
    await frontend.setSetting('waitAtEnd', true);
    frontend.runConfiguration('atexit');
    await frontend.waitForConsoleMessage('42');
    const processes = await frontend.nodeProcess();
    processes.sort();
    assert.equal(`node -e process.once('exit', _ => console.log(42))`, processes[0]);
    assert.equal(`node npm run atexit`, processes[1]);
    const targetDestroyed = frontend.waitTargetDestroyed(2);
    await frontend.killProcess(`node -e process.once('exit', _ => console.log(42))`);
    await targetDestroyed;
    assert.deepStrictEqual([], await frontend.nodeProcess());
    await frontend.close();
    await util.promisify(removeFolder)(configDir);
  });
};

// eslint-disable-next-line
function sleep() {
  return new Promise(resolve => setTimeout(resolve, 2147483647));
}

async function setupHelpers(frontend) {
  await frontend.evaluate(() => self.runtime.loadModulePromise('sources_test_runner'));
  await frontend.evaluate(_ => SourcesTestRunner.startDebuggerTest());
  frontend.waitForConfigurations = function() {
    return this.waitForSelector('body /deep/ div.configuration-item');
  };

  frontend.showScriptSource = function(name) {
    return this.evaluate(name => SourcesTestRunner.showScriptSourcePromise(name), name);
  };

  frontend.setBreakpoint = function(line, condition) {
    return this.evaluate((line, condition) => {
      const sourcesView = Sources.SourcesPanel.instance().sourcesView();
      const frame = sourcesView.currentSourceFrame();
      SourcesTestRunner.setBreakpoint(frame, line, condition, true);
    }, line, condition);
  };

  frontend.runConfiguration = async function(name) {
    const handle = await this.evaluateHandle(name => {
      const items = runtime.sharedInstance(Ndb.RunConfiguration).contentElement.querySelectorAll('div.list-item');
      return Array.from(items).find(e => e.innerText.split('\n')[0] === name);
    }, name);
    const element = handle.asElement();
    await element.hover();
    const runButton = await element.$('div.controls-buttons');
    await runButton.click();
  };

  frontend.waitUntilPaused = function() {
    return this.evaluate(_ => new Promise(resolve => {
      SourcesTestRunner.waitUntilPaused(frames => resolve({frames: frames.map(frame => frame._payload)}));
    }));
  };

  frontend.resumeExecution = function() {
    return this.evaluate(_ => new Promise(resolve => SourcesTestRunner.resumeExecution(resolve)));
  };

  frontend.setSetting = function(name, value) {
    return this.evaluate((name, value) => Common.moduleSetting(name).set(value), name, value);
  };

  frontend.waitForConsoleMessage = function(text) {
    return this.evaluate(text =>
      new Promise(resolve => TestRunner.addSniffer(SDK.ConsoleModel.prototype, 'addMessage', msg => {
        console.log(msg.messageText);
        if (msg.messageText === text)
          resolve();
      }, true))
    , text);
  };

  frontend.nodeProcess = function() {
    return this.evaluate(_ => {
      const titles = self.runtime.sharedInstance(Ndb.NodeProcesses).contentElement.querySelectorAll('div /deep/ .process-title');
      return Array.from(titles).map(el => el.innerText);
    });
  };

  frontend.killProcess = async function(name) {
    const handle = await this.evaluateHandle(name => {
      const titles = self.runtime.sharedInstance(Ndb.NodeProcesses).contentElement.querySelectorAll('div /deep/ li');
      return Array.from(titles).find(e => e.innerText.split('\n')[0] === name);
    }, name);
    const element = handle.asElement();
    await element.hover();
    const btn = await element.$('div.controls-buttons');
    await btn.click();
  };

  frontend.waitTargetDestroyed = async function(num) {
    return this.evaluate(num =>
      new Promise(resolve =>  Ndb.NodeProcessManager.instance().then(manager => {
        manager.addEventListener(Ndb.NodeProcessManager.Events.Finished, _ => !--num && resolve());
      }))
    , num);
  };
}


================================================
FILE: test/integration.js
================================================
/**
 * @license Copyright 2018 Google Inc. All Rights Reserved.
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
 */

const {TestRunner, Reporter} = require('../utils/testrunner/');
let parallel = 1;
if (process.env.NDB_PARALLEL_TESTS)
  parallel = parseInt(process.env.NDB_PARALLEL_TESTS.trim(), 10);
const timeout = 10000;
const testRunner = new TestRunner({timeout, parallel});

require('./basic.spec.js').addTests({testRunner});

new Reporter(testRunner);
testRunner.run();


================================================
FILE: test/platform.spec.js
================================================
/**
 * @license Copyright 2018 Google Inc. All Rights Reserved.
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
 */

const {TestRunner, Reporter, Matchers} = require('../utils/testrunner/');
let parallel = 1;
if (process.env.NDB_PARALLEL_TESTS)
  parallel = parseInt(process.env.NDB_PARALLEL_TESTS.trim(), 10);
const timeout = 10000;
const testRunner = new TestRunner({timeout, parallel});
const {expect} = new Matchers();
addTests(testRunner);
new Reporter(testRunner);
testRunner.run();

const { execFile } = require('child_process');

// Tests for specific Node platform features.
function addTests(testRunner) {
  // eslint-disable-next-line
  const {beforeAll, afterAll} = testRunner;
  // eslint-disable-next-line
  const {it, fit, xit} = testRunner;

  xit('--title flag (fails on Node v8.x)', async function() {
    const result = await new Promise(resolve => execFile(
        process.execPath, ['--title=abc', '-p', 'process.title'], (error, stdout, stderr) => {
          resolve(stdout + stderr);
        }));
    expect(result).toBe('abc\n');
  });
}


================================================
FILE: utils/testrunner/Matchers.js
================================================
/**
 * Copyright 2017 Google Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

module.exports = class Matchers {
  constructor(customMatchers = {}) {
    this._matchers = {};
    Object.assign(this._matchers, DefaultMatchers);
    Object.assign(this._matchers, customMatchers);
    this.expect = this.expect.bind(this);
  }

  addMatcher(name, matcher) {
    this._matchers[name] = matcher;
  }

  expect(value) {
    return new Expect(value, this._matchers);
  }
};

class Expect {
  constructor(value, matchers) {
    this.not = {};
    this.not.not = this;
    for (const matcherName of Object.keys(matchers)) {
      const matcher = matchers[matcherName];
      this[matcherName] = applyMatcher.bind(null, matcherName, matcher, false /* inverse */, value);
      this.not[matcherName] = applyMatcher.bind(null, matcherName, matcher, true /* inverse */, value);
    }

    function applyMatcher(matcherName, matcher, inverse, value, ...args) {
      const result = matcher.call(null, value, ...args);
      const message = `expect.${inverse ? 'not.' : ''}${matcherName} failed` + (result.message ? `: ${result.message}` : '');
      if (result.pass === inverse)
        throw new Error(message);
    }
  }
}

const DefaultMatchers = {
  toBe: function(value, other, message) {
    message = message || `${value} == ${other}`;
    return { pass: value === other, message };
  },

  toBeFalsy: function(value, message) {
    message = message || `${value}`;
    return { pass: !value, message };
  },

  toBeTruthy: function(value, message) {
    message = message || `${value}`;
    return { pass: !!value, message };
  },

  toBeGreaterThan: function(value, other, message) {
    message = message || `${value} > ${other}`;
    return { pass: value > other, message };
  },

  toBeGreaterThanOrEqual: function(value, other, message) {
    message = message || `${value} >= ${other}`;
    return { pass: value >= other, message };
  },

  toBeLessThan: function(value, other, message) {
    message = message || `${value} < ${other}`;
    return { pass: value < other, message };
  },

  toBeLessThanOrEqual: function(value, other, message) {
    message = message || `${value} <= ${other}`;
    return { pass: value <= other, message };
  },

  toBeNull: function(value, message) {
    message = message || `${value} == null`;
    return { pass: value === null, message };
  },

  toContain: function(value, other, message) {
    message = message || `${value} ⊇ ${other}`;
    return { pass: value.includes(other), message };
  },

  toEqual: function(value, other, message) {
    const valueJson = stringify(value);
    const otherJson = stringify(other);
    message = message || `${valueJson} ≈ ${otherJson}`;
    return { pass: valueJson === otherJson, message };
  },

  toBeCloseTo: function(value, other, precision, message) {
    return {
      pass: Math.abs(value - other) < Math.pow(10, -precision),
      message
    };
  }
};

function stringify(value) {
  function stabilize(key, object) {
    if (typeof object !== 'object' || object === undefined || object === null)
      return object;
    const result = {};
    for (const key of Object.keys(object).sort())
      result[key] = object[key];
    return result;
  }

  return JSON.stringify(stabilize(null, value), stabilize);
}


================================================
FILE: utils/testrunner/Multimap.js
================================================
/**
 * Copyright 2017 Google Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

class Multimap {
  constructor() {
    this._map = new Map();
  }

  set(key, value) {
    let set = this._map.get(key);
    if (!set) {
      set = new Set();
      this._map.set(key, set);
    }
    set.add(value);
  }

  get(key) {
    let result = this._map.get(key);
    if (!result)
      result = new Set();
    return result;
  }

  has(key) {
    return this._map.has(key);
  }

  hasValue(key, value) {
    const set = this._map.get(key);
    if (!set)
      return false;
    return set.has(value);
  }

  /**
   * @return {number}
   */
  get size() {
    return this._map.size;
  }

  delete(key, value) {
    const values = this.get(key);
    const result = values.delete(value);
    if (!values.size)
      this._map.delete(key);
    return result;
  }

  deleteAll(key) {
    this._map.delete(key);
  }

  firstValue(key) {
    const set = this._map.get(key);
    if (!set)
      return null;
    return set.values().next().value;
  }

  firstKey() {
    return this._map.keys().next().value;
  }

  valuesArray() {
    const result = [];
    for (const key of this._map.keys())
      result.push(...Array.from(this._map.get(key).values()));
    return result;
  }

  keysArray() {
    return Array.from(this._map.keys());
  }

  clear() {
    this._map.clear();
  }
}

module.exports = Multimap;


================================================
FILE: utils/testrunner/README.md
================================================
# TestRunner

- testrunner is a library: no additional binary required; tests are `node.js` scripts
- parallel wrt IO operations
- supports async/await
- modular
- well-isolated state per execution thread

Example

```js
const {TestRunner, Reporter, Matchers} = require('../utils/testrunner');

// Runner holds and runs all the tests
const runner = new TestRunner({
  parallel: 2, // run 2 parallel threads
  timeout: 1000, // setup timeout of 1 second per test
});
// Simple expect-like matchers
const {expect} = new Matchers();

// Extract jasmine-like DSL into the global namespace
const {describe, xdescribe, fdescribe} = runner;
const {it, fit, xit} = runner;
const {beforeAll, beforeEach, afterAll, afterEach} = runner;

beforeAll(state => {
  state.parallelIndex; // either 0 or 1 in this example, depending on the executing thread
  state.foo = 'bar'; // set state for every test
});

describe('math', () => {
  it('to be sane', async (state, test) => {
    state.parallelIndex; // Very first test will always be ran by the 0's thread
    state.foo; // this will be 'bar'
    expect(2 + 2).toBe(4);
  });
});

// Reporter subscribes to TestRunner events and displays information in terminal
const reporter = new Reporter(runner);

// Run all tests.
runner.run();
```


================================================
FILE: utils/testrunner/Reporter.js
================================================
/**
 * Copyright 2017 Google Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

const RED_COLOR = '\x1b[31m';
const GREEN_COLOR = '\x1b[32m';
const YELLOW_COLOR = '\x1b[33m';
const RESET_COLOR = '\x1b[0m';

class Reporter {
  constructor(runner) {
    this._runner = runner;
    runner.on('started', this._onStarted.bind(this));
    runner.on('terminated', this._onTerminated.bind(this));
    runner.on('finished', this._onFinished.bind(this));
    runner.on('teststarted', this._onTestStarted.bind(this));
    runner.on('testfinished', this._onTestFinished.bind(this));
  }

  _onStarted() {
    this._timestamp = Date.now();
    console.log(`Running ${YELLOW_COLOR}${this._runner.parallel()}${RESET_COLOR} worker(s):\n`);
  }

  _onTerminated(message, error) {
    this._printTestResults();
    console.log(`${RED_COLOR}## TERMINATED ##${RESET_COLOR}`);
    console.log('Message:');
    console.log(`  ${RED_COLOR}${message}${RESET_COLOR}`);
    if (error && error.stack) {
      console.log('Stack:');
      console.log(error.stack.split('\n').map(line => '  ' + line).join('\n'));
    }
    process.exit(2);
  }

  _onFinished() {
    this._printTestResults();
    const failedTests = this._runner.failedTests();
    process.exit(failedTests.length > 0 ? 1 : 0);
  }

  _printTestResults() {
    // 2 newlines after completing all tests.
    console.log('\n');

    const failedTests = this._runner.failedTests();
    if (failedTests.length > 0) {
      console.log('\nFailures:');
      for (let i = 0; i < failedTests.length; ++i) {
        const test = failedTests[i];
        console.log(`${i + 1}) ${test.fullName} (${formatLocation(test)})`);
        if (test.result === 'timedout') {
          console.log('  Message:');
          console.log(`    ${YELLOW_COLOR}Timeout Exceeded ${this._runner.timeout()}ms${RESET_COLOR}`);
        } else {
          console.log('  Message:');
          console.log(`    ${RED_COLOR}${test.error.message || test.error}${RESET_COLOR}`);
          console.log('  Stack:');
          if (test.error.stack) {
            const stack = test.error.stack.split('\n').map(line => '    ' + line);
            let i = 0;
            while (i < stack.length && !stack[i].includes(__dirname))
              ++i;
            while (i < stack.length && stack[i].includes(__dirname))
              ++i;
            if (i < stack.length) {
              const indent = stack[i].match(/^\s*/)[0];
              stack[i] = stack[i].substring(0, indent.length - 3) + YELLOW_COLOR + '⇨ ' + RESET_COLOR +  stack[i].substring(indent.length - 1);
            }
            console.log(stack.join('\n'));
          }
        }
        if (test.output) {
          console.log('  Output:');
          console.log(test.output.split('\n').map(line => '    ' + line).join('\n'));
        }
        console.log('');
      }
    }

    const tests = this._runner.tests();
    const skippedTests = tests.filter(test => test.result === 'skipped');
    if (skippedTests.length > 0) {
      console.log('\nSkipped:');
      for (let i = 0; i < skippedTests.length; ++i) {
        const test = skippedTests[i];
        console.log(`${i + 1}) ${test.fullName}`);
        console.log(`  ${YELLOW_COLOR}Temporary disabled with xit${RESET_COLOR} ${formatLocation(test)}\n`);
      }
    }

    const executedTests = tests.filter(test => test.result);
    console.log(`\nRan ${executedTests.length} of ${tests.length} test(s)`);
    const milliseconds = Date.now() - this._timestamp;
    const seconds = milliseconds / 1000;
    console.log(`Finished in ${YELLOW_COLOR}${seconds}${RESET_COLOR} seconds`);

    function formatLocation(test) {
      const location = test.location;
      if (!location)
        return '';
      return `${location.fileName}:${location.lineNumber}:${location.columnNumber}`;
    }
  }

  _onTestStarted() {
  }

  _onTestFinished(test) {
    if (test.result === 'ok')
      process.stdout.write(`${GREEN_COLOR}.${RESET_COLOR}`);
    else if (test.result === 'skipped')
      process.stdout.write(`${YELLOW_COLOR}*${RESET_COLOR}`);
    else if (test.result === 'failed')
      process.stdout.write(`${RED_COLOR}F${RESET_COLOR}`);
    else if (test.result === 'timedout')
      process.stdout.write(`${RED_COLOR}T${RESET_COLOR}`);
  }
}

module.exports = Reporter;


================================================
FILE: utils/testrunner/TestRunner.js
================================================
/**
 * Copyright 2017 Google Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

const path = require('path');
const EventEmitter = require('events');
const Multimap = require('./Multimap');

const TimeoutError = new Error('Timeout');
const TerminatedError = new Error('Terminated');

const MAJOR_NODEJS_VERSION = parseInt(process.version.substring(1).split('.')[0], 10);

class UserCallback {
  constructor(callback, timeout) {
    this._callback = callback;
    this._terminatePromise = new Promise(resolve => {
      this._terminateCallback = resolve;
    });

    this.timeout = timeout;
    this.location = this._getLocation();
  }

  async run(...args) {
    const timeoutPromise = new Promise(resolve => {
      setTimeout(resolve.bind(null, TimeoutError), this.timeout);
    });
    try {
      return await Promise.race([
        Promise.resolve().then(this._callback.bind(null, ...args)).then(() => null).catch(e => e),
        timeoutPromise,
        this._terminatePromise
      ]);
    } catch (e) {
      return e;
    }
  }

  _getLocation() {
    const error = new Error();
    const stackFrames = error.stack.split('\n').slice(1);
    // Find first stackframe that doesn't point to this file.
    for (let frame of stackFrames) {
      frame = frame.trim();
      if (!frame.startsWith('at '))
        return null;
      if (frame.endsWith(')')) {
        const from = frame.indexOf('(');
        frame = frame.substring(from + 1, frame.length - 1);
      } else {
        frame = frame.substring('at '.length + 1);
      }

      const match = frame.match(/^(.*):(\d+):(\d+)$/);
      if (!match)
        return null;
      const filePath = match[1];
      const lineNumber = match[2];
      const columnNumber = match[3];
      if (filePath === __filename)
        continue;
      const fileName = filePath.split(path.sep).pop();
      return { fileName, filePath, lineNumber, columnNumber };
    }
    return null;
  }

  terminate() {
    this._terminateCallback(TerminatedError);
  }
}

const TestMode = {
  Run: 'run',
  Skip: 'skip',
  Focus: 'focus'
};

const TestResult = {
  Ok: 'ok',
  Skipped: 'skipped', // User skipped the test
  Failed: 'failed', // Exception happened during running
  TimedOut: 'timedout', // Timeout Exceeded while running
};

class Test {
  constructor(suite, name, callback, declaredMode, timeout) {
    this.suite = suite;
    this.name = name;
    this.fullName = (suite.fullName + ' ' + name).trim();
    this.declaredMode = declaredMode;
    this._userCallback = new UserCallback(callback, timeout);
    this.location = this._userCallback.location;

    // Test results
    this.result = null;
    this.error = null;
  }
}

class Suite {
  constructor(parentSuite, name, declaredMode) {
    this.parentSuite = parentSuite;
    this.name = name;
    this.fullName = (parentSuite ? parentSuite.fullName + ' ' + name : name).trim();
    this.declaredMode = declaredMode;
    /** @type {!Array<(!Test|!Suite)>} */
    this.children = [];

    this.beforeAll = null;
    this.beforeEach = null;
    this.afterAll = null;
    this.afterEach = null;
  }
}

class TestPass {
  constructor(runner, rootSuite, tests, parallel) {
    this._runner = runner;
    this._parallel = parallel;
    this._runningUserCallbacks = new Multimap();

    this._rootSuite = rootSuite;
    this._workerDistribution = new Multimap();

    let workerId = 0;
    for (const test of tests) {
      // Reset results for tests that will be run.
      test.result = null;
      test.error = null;
      this._workerDistribution.set(test, workerId);
      for (let suite = test.suite; suite; suite = suite.parentSuite)
        this._workerDistribution.set(suite, workerId);
      // Do not shard skipped tests across workers.
      if (test.declaredMode !== TestMode.Skip)
        workerId = (workerId + 1) % parallel;
    }

    this._termination = null;
  }

  async run() {
    const terminations = [
      createTermination.call(this, 'SIGINT', 'SIGINT received'),
      createTermination.call(this, 'SIGHUP', 'SIGHUP received'),
      createTermination.call(this, 'SIGTERM', 'SIGTERM received'),
      createTermination.call(this, 'unhandledRejection', 'UNHANDLED PROMISE REJECTION'),
    ];
    for (const termination of terminations)
      process.on(termination.event, termination.handler);

    const workerPromises = [];
    for (let i = 0; i < this._parallel; ++i)
      workerPromises.push(this._runSuite(i, [this._rootSuite], {parallelIndex: i}));
    await Promise.all(workerPromises);

    for (const termination of terminations)
      process.removeListener(termination.event, termination.handler);
    return this._termination;

    function createTermination(event, message) {
      return {
        event,
        message,
        handler: error => this._terminate(message, error)
      };
    }
  }

  async _runSuite(workerId, suitesStack, state) {
    if (this._termination)
      return;
    const currentSuite = suitesStack[suitesStack.length - 1];
    if (!this._workerDistribution.hasValue(currentSuite, workerId))
      return;
    await this._runHook(workerId, currentSuite, 'beforeAll', state);
    for (const child of currentSuite.children) {
      if (!this._workerDistribution.hasValue(child, workerId))
        continue;
      if (child instanceof Test) {
        for (let i = 0; i < suitesStack.length; i++)
          await this._runHook(workerId, suitesStack[i], 'beforeEach', state, child);
        await this._runTest(workerId, child, state);
        for (let i = suitesStack.length - 1; i >= 0; i--)
          await this._runHook(workerId, suitesStack[i], 'afterEach', state, child);
      } else {
        suitesStack.push(child);
        await this._runSuite(workerId, suitesStack, state);
        suitesStack.pop();
      }
    }
    await this._runHook(workerId, currentSuite, 'afterAll', state);
  }

  async _runTest(workerId, test, state) {
    if (this._termination)
      return;
    this._runner._willStartTest(test);
    if (test.declaredMode === TestMode.Skip) {
      test.result = TestResult.Skipped;
      this._runner._didFinishTest(test);
      return;
    }
    this._runningUserCallbacks.set(workerId, test._userCallback);
    const error = await test._userCallback.run(state, test);
    this._runningUserCallbacks.delete(workerId, test._userCallback);
    if (this._termination)
      return;
    test.error = error;
    if (!error)
      test.result = TestResult.Ok;
    else if (test.error === TimeoutError)
      test.result = TestResult.TimedOut;
    else
      test.result = TestResult.Failed;
    this._runner._didFinishTest(test);
  }

  async _runHook(workerId, suite, hookName, ...args) {
    if (this._termination)
      return;
    const hook = suite[hookName];
    if (!hook)
      return;
    this._runningUserCallbacks.set(workerId, hook);
    const error = await hook.run(...args);
    this._runningUserCallbacks.delete(workerId, hook);
    if (error === TimeoutError) {
      const location = `${hook.location.fileName}:${hook.location.lineNumber}:${hook.location.columnNumber}`;
      const message = `${location} - Timeout Exceeded ${hook.timeout}ms while running "${hookName}" in suite "${suite.fullName}"`;
      this._terminate(message, null);
    } else if (error) {
      const location = `${hook.location.fileName}:${hook.location.lineNumber}:${hook.location.columnNumber}`;
      const message = `${location} - FAILED while running "${hookName}" in suite "${suite.fullName}"`;
      this._terminate(message, error);
    }
  }

  _terminate(message, error) {
    if (this._termination)
      return;
    this._termination = {message, error};
    for (const userCallback of this._runningUserCallbacks.valuesArray())
      userCallback.terminate();
  }
}

class TestRunner extends EventEmitter {
  constructor(options = {}) {
    super();
    this._rootSuite = new Suite(null, '', TestMode.Run);
    this._currentSuite = this._rootSuite;
    this._tests = [];
    // Default timeout is 10 seconds.
    this._timeout = options.timeout === 0 ? 2147483647 : options.timeout || 10 * 1000;
    this._parallel = options.parallel || 1;
    this._retryFailures = !!options.retryFailures;

    this._hasFocusedTestsOrSuites = false;

    if (MAJOR_NODEJS_VERSION >= 8) {
      const inspector = require('inspector');
      if (inspector.url()) {
        console.log('TestRunner detected inspector; overriding certain properties to be debugger-friendly');
        console.log('  - timeout = 0 (Infinite)');
        this._timeout = 2147483647;
        this._parallel = 1;
      }
    }

    // bind methods so that they can be used as a DSL.
    this.describe = this._addSuite.bind(this, TestMode.Run);
    this.fdescribe = this._addSuite.bind(this, TestMode.Focus);
    this.xdescribe = this._addSuite.bind(this, TestMode.Skip);
    this.it = this._addTest.bind(this, TestMode.Run);
    this.fit = this._addTest.bind(this, TestMode.Focus);
    this.xit = this._addTest.bind(this, TestMode.Skip);
    this.beforeAll = this._addHook.bind(this, 'beforeAll');
    this.beforeEach = this._addHook.bind(this, 'beforeEach');
    this.afterAll = this._addHook.bind(this, 'afterAll');
    this.afterEach = this._addHook.bind(this, 'afterEach');
  }

  _addTest(mode, name, callback) {
    let suite = this._currentSuite;
    let isSkipped = suite.declaredMode === TestMode.Skip;
    while ((suite = suite.parentSuite))
      isSkipped |= suite.declaredMode === TestMode.Skip;
    const test = new Test(this._currentSuite, name, callback, isSkipped ? TestMode.Skip : mode, this._timeout);
    this._currentSuite.children.push(test);
    this._tests.push(test);
    this._hasFocusedTestsOrSuites = this._hasFocusedTestsOrSuites || mode === TestMode.Focus;
  }

  _addSuite(mode, name, callback) {
    const oldSuite = this._currentSuite;
    const suite = new Suite(this._currentSuite, name, mode);
    this._currentSuite.children.push(suite);
    this._currentSuite = suite;
    callback();
    this._currentSuite = oldSuite;
    this._hasFocusedTestsOrSuites = this._hasFocusedTestsOrSuites || mode === TestMode.Focus;
  }

  _addHook(hookName, callback) {
    assert(this._currentSuite[hookName] === null, `Only one ${hookName} hook available per suite`);
    const hook = new UserCallback(callback, this._timeout);
    this._currentSuite[hookName] = hook;
  }

  async run() {
    this.emit(TestRunner.Events.Started);
    const pass = new TestPass(this, this._rootSuite, this._runnableTests(), this._parallel);
    const termination = await pass.run();
    if (termination)
      this.emit(TestRunner.Events.Terminated, termination.message, termination.error);
    else
      this.emit(TestRunner.Events.Finished);
  }

  timeout() {
    return this._timeout;
  }

  _runnableTests() {
    if (!this._hasFocusedTestsOrSuites)
      return this._tests;

    const tests = [];
    const blacklistSuites = new Set();
    // First pass: pick "fit" and blacklist parent suites
    for (const test of this._tests) {
      if (test.declaredMode !== TestMode.Focus)
        continue;
      tests.push(test);
      for (let suite = test.suite; suite; suite = suite.parentSuite)
        blacklistSuites.add(suite);
    }
    // Second pass: pick all tests that belong to non-blacklisted "fdescribe"
    for (const test of this._tests) {
      let insideFocusedSuite = false;
      for (let suite = test.suite; suite; suite = suite.parentSuite) {
        if (!blacklistSuites.has(suite) && suite.declaredMode === TestMode.Focus) {
          insideFocusedSuite = true;
          break;
        }
      }
      if (insideFocusedSuite)
        tests.push(test);
    }
    return tests;
  }

  hasFocusedTestsOrSuites() {
    return this._hasFocusedTestsOrSuites;
  }

  tests() {
    return this._tests.slice();
  }

  failedTests() {
    return this._tests.filter(test => test.result === 'failed' || test.result === 'timedout');
  }

  parallel() {
    return this._parallel;
  }

  _willStartTest(test) {
    this.emit('teststarted', test);
  }

  _didFinishTest(test) {
    this.emit('testfinished', test);
  }
}

/**
 * @param {*} value
 * @param {string=} message
 */
function assert(value, message) {
  if (!value)
    throw new Error(message);
}

TestRunner.Events = {
  Started: 'started',
  TestStarted: 'teststarted',
  TestFinished: 'testfinished',
  Terminated: 'terminated',
  Finished: 'finished',
};

module.exports = TestRunner;


================================================
FILE: utils/testrunner/examples/fail.js
================================================
/**
 * Copyright 2017 Google Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

const {TestRunner, Reporter, Matchers} = require('..');

const runner = new TestRunner();
const reporter = new Reporter(runner);
const {expect} = new Matchers();

const {describe, xdescribe, fdescribe} = runner;
const {it, fit, xit} = runner;
const {beforeAll, beforeEach, afterAll, afterEach} = runner;

describe('testsuite', () => {
  it('toBe', async (state) => {
    expect(2 + 2).toBe(5);
  });
  it('toBeFalsy', async (state) => {
    expect(true).toBeFalsy();
  });
  it('toBeTruthy', async (state) => {
    expect(false).toBeTruthy();
  });
  it('toBeGreaterThan', async (state) => {
    expect(2).toBeGreaterThan(3);
  });
  it('toBeNull', async (state) => {
    expect(2).toBeNull();
  });
  it('toContain', async (state) => {
    expect('asdf').toContain('e');
  });
  it('not.toContain', async (state) => {
    expect('asdf').not.toContain('a');
  });
  it('toEqual', async (state) => {
    expect([1,2,3]).toEqual([1,2,3,4]);
  });
});

runner.run();


================================================
FILE: utils/testrunner/examples/hookfail.js
================================================
/**
 * Copyright 2017 Google Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

const {TestRunner, Reporter, Matchers} = require('..');

const runner = new TestRunner();
const reporter = new Reporter(runner);
const {expect} = new Matchers();

const {describe, xdescribe, fdescribe} = runner;
const {it, fit, xit} = runner;
const {beforeAll, beforeEach, afterAll, afterEach} = runner;

describe('testsuite', () => {
  beforeAll(() => {
    expect(false).toBeTruthy();
  });
  it('test', async () => {
  });
});

runner.run();


================================================
FILE: utils/testrunner/examples/hooktimeout.js
================================================
/**
 * Copyright 2017 Google Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

const {TestRunner, Reporter, Matchers} = require('..');

const runner = new TestRunner({ timeout: 100 });
const reporter = new Reporter(runner);
const {expect} = new Matchers();

const {describe, xdescribe, fdescribe} = runner;
const {it, fit, xit} = runner;
const {beforeAll, beforeEach, afterAll, afterEach} = runner;

describe('testsuite', () => {
  beforeAll(async () => {
    await new Promise(() => {});
  });
  it('something', async (state) => {
  });
});

runner.run();


================================================
FILE: utils/testrunner/examples/timeout.js
================================================
/**
 * Copyright 2017 Google Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

const {TestRunner, Reporter} = require('..');

const runner = new TestRunner({ timeout: 100 });
const reporter = new Reporter(runner);

const {describe, xdescribe, fdescribe} = runner;
const {it, fit, xit} = runner;
const {beforeAll, beforeEach, afterAll, afterEach} = runner;

describe('testsuite', () => {
  it('timeout', async (state) => {
    await new Promise(() => {});
  });
});

runner.run();


================================================
FILE: utils/testrunner/examples/unhandledpromiserejection.js
================================================
/**
 * Copyright 2017 Google Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

const {TestRunner, Reporter} = require('..');

const runner = new TestRunner();
const reporter = new Reporter(runner);

const {describe, xdescribe, fdescribe} = runner;
const {it, fit, xit} = runner;
const {beforeAll, beforeEach, afterAll, afterEach} = runner;

describe('testsuite', () => {
  it('failure', async (state) => {
    Promise.reject(new Error('fail!'));
  });
  it('slow', async () => {
    await new Promise(x => setTimeout(x, 1000));
  });
});

runner.run();


================================================
FILE: utils/testrunner/index.js
================================================
/**
 * Copyright 2017 Google Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

const TestRunner = require('./TestRunner');
const Reporter = require('./Reporter');
const Matchers = require('./Matchers');

module.exports = { TestRunner, Reporter, Matchers };


================================================
FILE: version.js
================================================
const fs = require('fs');

(function main() {
  const version = require('./package.json').version;
  let preload = fs.readFileSync('./lib/preload/ndb/preload.js', 'utf8');
  preload = preload.replace(/process\.versions\['ndb'\] = '[\d+\.]+';/, `process.versions['ndb'] = '${version}';`);
  fs.writeFileSync('./lib/preload/ndb/preload.js', preload, 'utf8');
})();
Download .txt
gitextract_lgd8360j/

├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── .npmignore
├── .travis.yml
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── build.js
├── debug.js
├── docs/
│   └── issue_template.md
├── front_end/
│   ├── ndb/
│   │   ├── Connection.js
│   │   ├── FileSystem.js
│   │   ├── InspectorFrontendHostOverrides.js
│   │   ├── NdbMain.js
│   │   └── module.json
│   ├── ndb.html
│   ├── ndb.js
│   ├── ndb.json
│   ├── ndb_sdk/
│   │   ├── NodeRuntime.js
│   │   ├── NodeWorker.js
│   │   └── module.json
│   ├── ndb_ui/
│   │   ├── NodeProcesses.js
│   │   ├── RunConfiguration.js
│   │   ├── Terminal.js
│   │   ├── module.json
│   │   ├── nodeProcesses.css
│   │   └── runConfiguration.css
│   └── xterm/
│       └── module.json
├── lib/
│   ├── backend.js
│   ├── filepath_to_url.js
│   ├── launcher.js
│   ├── preload/
│   │   └── ndb/
│   │       └── preload.js
│   └── process_utility.js
├── ndb.js
├── package.json
├── scripts/
│   └── builder.js
├── services/
│   ├── file_system.js
│   ├── file_system_io.js
│   ├── ndd_service.js
│   ├── search.js
│   └── terminal.js
├── test/
│   ├── assets/
│   │   └── test-project/
│   │       ├── index.js
│   │       ├── index.mjs
│   │       └── package.json
│   ├── basic.spec.js
│   ├── integration.js
│   └── platform.spec.js
├── utils/
│   └── testrunner/
│       ├── Matchers.js
│       ├── Multimap.js
│       ├── README.md
│       ├── Reporter.js
│       ├── TestRunner.js
│       ├── examples/
│       │   ├── fail.js
│       │   ├── hookfail.js
│       │   ├── hooktimeout.js
│       │   ├── timeout.js
│       │   └── unhandledpromiserejection.js
│       └── index.js
└── version.js
Download .txt
SYMBOL INDEX (244 symbols across 25 files)

FILE: build.js
  constant DEVTOOLS_DIR (line 8) | const DEVTOOLS_DIR = path.dirname(
  function minifyJS (line 24) | function minifyJS(code) {

FILE: front_end/ndb/Connection.js
  method constructor (line 2) | constructor(channel) {
  method create (line 8) | static async create(channel) {
  method setOnMessage (line 17) | setOnMessage(onMessage) {
  method setOnDisconnect (line 24) | setOnDisconnect(onDisconnect) {
  method sendRawMessage (line 31) | sendRawMessage(message) {
  method disconnect (line 38) | disconnect() {
  method dispatchMessage (line 45) | dispatchMessage(message) {

FILE: front_end/ndb/FileSystem.js
  method constructor (line 8) | constructor(fsService, fsIOService, searchService, manager, rootURL) {
  method create (line 20) | static async create(manager, rootURL) {
  method embedderPath (line 40) | embedderPath() {
  method _initFilePaths (line 48) | async _initFilePaths() {
  method forceFileLoad (line 52) | forceFileLoad(scriptName) {
  method initialFilePaths (line 60) | initialFilePaths() {
  method initialGitFolders (line 68) | initialGitFolders() {
  method getMetadata (line 77) | getMetadata(path) {
  method requestFileBlob (line 87) | requestFileBlob(path) {
  method requestFileContent (line 96) | async requestFileContent(path, callback) {
  method setFileContent (line 112) | async setFileContent(path, content, isBase64) {
  method createFile (line 122) | async createFile(path, name) {
  method deleteFile (line 132) | async deleteFile(path) {
  method renameFile (line 142) | async renameFile(path, newName, callback) {
  method contentType (line 152) | contentType(path) {
  method mimeFromPath (line 171) | mimeFromPath(path) {
  method canExcludeFolder (line 180) | canExcludeFolder(path) {
  method tooltipForURL (line 189) | tooltipForURL(url) {
  method searchInPath (line 200) | searchInPath(query, progress) {
  method filesChanged (line 218) | filesChanged(events) {
  method indexContent (line 235) | indexContent(progress) {
  method supportsAutomapping (line 245) | supportsAutomapping() {
  method _excludePattern (line 252) | _excludePattern() {
  method indexingTotalWorkCalculated (line 263) | indexingTotalWorkCalculated(requestId, fileSystemPath, totalWork) {
  method indexingWorked (line 272) | indexingWorked(requestId, fileSystemPath, worked) {
  method indexingDone (line 280) | indexingDone(requestId, fileSystemPath) {
  method searchCompleted (line 289) | searchCompleted(requestId, fileSystemPath, files) {
  method _callFrontend (line 293) | _callFrontend(f) {

FILE: front_end/ndb/NdbMain.js
  method run (line 20) | async run() {
  method _defaultExcludePattern (line 51) | static _defaultExcludePattern() {
  method _calculateBlackboxState (line 72) | static _calculateBlackboxState() {
  method appendApplicableItems (line 128) | appendApplicableItems(event, contextMenu, object) {
  method constructor (line 145) | constructor(targetManager) {
  method create (line 160) | static async create(targetManager) {
  method env (line 166) | env() {
  method addFileSystem (line 175) | async addFileSystem(cwd, mainFileName) {
  method detected (line 192) | async detected(info, channel) {
  method disconnected (line 228) | disconnected(sessionId) {
  method terminalData (line 236) | async terminalData(stream, data) {
  method _onExecutionContextDestroyed (line 244) | async _onExecutionContextDestroyed(event) {
  method _onWaitingForDisconnect (line 251) | async _onWaitingForDisconnect(event) {
  method startRepl (line 270) | async startRepl() {
  method debug (line 282) | async debug(execPath, args, options) {
  method profile (line 296) | async profile(execPath, args, options) {
  method kill (line 329) | async kill(target) {
  method restartLast (line 335) | async restartLast() {
  method handleAction (line 366) | handleAction(context, actionId) {

FILE: front_end/ndb_sdk/NodeRuntime.js
  method constructor (line 8) | constructor(target) {
  method _waitingForDisconnect (line 21) | _waitingForDisconnect() {
  method constructor (line 32) | constructor(nodeRuntimeModel) {
  method waitingForDisconnect (line 36) | waitingForDisconnect() {

FILE: front_end/ndb_sdk/NodeWorker.js
  method constructor (line 15) | constructor(target) {
  method sendMessageToWorker (line 30) | sendMessageToWorker(message, sessionId) {
  method detach (line 38) | detach(sessionId) {
  method dispose (line 45) | dispose() {
  method _attachedToWorker (line 59) | _attachedToWorker(sessionId, workerInfo, waitingForDebugger) {
  method _detachedFromWorker (line 74) | _detachedFromWorker(sessionId) {
  method _receivedMessageFromWorker (line 91) | _receivedMessageFromWorker(sessionId, message) {
  method constructor (line 99) | constructor(sessionId, nodeWorkerModel) {
  method setOnMessage (line 109) | setOnMessage(onMessage) {
  method setOnDisconnect (line 116) | setOnDisconnect(onDisconnect) {
  method sendRawMessage (line 123) | sendRawMessage(message) {
  method disconnect (line 130) | disconnect() {
  method receivedMessageFromWorker (line 137) | receivedMessageFromWorker(message) {
  method detachedFromWorker (line 142) | detachedFromWorker() {
  method constructor (line 152) | constructor(nodeWorkerModel) {
  method attachedToWorker (line 161) | attachedToWorker(sessionId, workerInfo, waitingForDebugger) {
  method detachedFromWorker (line 168) | detachedFromWorker(sessionId) {
  method receivedMessageFromWorker (line 176) | receivedMessageFromWorker(sessionId, message) {

FILE: front_end/ndb_ui/NodeProcesses.js
  method constructor (line 8) | constructor() {
  method targetAdded (line 35) | targetAdded(target) {
  method targetRemoved (line 92) | targetRemoved(target) {
  method _targetFlavorChanged (line 109) | _targetFlavorChanged({data: target}) {

FILE: front_end/ndb_ui/RunConfiguration.js
  method constructor (line 8) | constructor() {
  method update (line 17) | async update() {
  method createElementForItem (line 38) | createElementForItem(item) {
  method _runConfig (line 61) | async _runConfig(execPath, args) {
  method _profileConfig (line 65) | async _profileConfig(execPath, args) {
  method heightForItem (line 74) | heightForItem(debuggerModel) {
  method isItemSelectable (line 83) | isItemSelectable(debuggerModel) {
  method selectedItemChanged (line 94) | selectedItemChanged(from, to, fromElement, toElement) {}

FILE: front_end/ndb_ui/Terminal.js
  method constructor (line 10) | constructor() {
  method _createTerminal (line 21) | static _createTerminal() {
  method _restartService (line 40) | async _restartService() {
  method _anotherTerminalHint (line 53) | _anotherTerminalHint(env) {
  method _onContextMenu (line 65) | _onContextMenu(event) {
  method initFailed (line 80) | async initFailed(error) {
  method dataAdded (line 88) | dataAdded(data) {
  method closed (line 94) | closed() {
  method _sendResize (line 101) | _sendResize(size) {
  method _sendData (line 109) | _sendData(data) {
  method onResize (line 114) | onResize() {
  method _terminalData (line 118) | _terminalData(event) {
  method wasShown (line 122) | wasShown() {

FILE: lib/backend.js
  constant MODULE_WRAP_PREFIX (line 21) | const MODULE_WRAP_PREFIX = (() => {
  class Backend (line 26) | class Backend {
    method constructor (line 27) | constructor(window) {
    method createService (line 34) | async createService(name, ...args) {
    method bringToFront (line 41) | bringToFront() {
    method openInNewTab (line 48) | openInNewTab(url) {
    method pkg (line 52) | pkg() {
    method loadSourceMap (line 61) | async loadSourceMap(sourceMapURL, compiledURL) {
    method getNodeScriptPrefix (line 81) | getNodeScriptPrefix() {
    method which (line 85) | which(command) {
    method processInfo (line 92) | processInfo() {
    method writeTerminalData (line 96) | writeTerminalData(stream, data) {
    method fileURLToPath (line 104) | fileURLToPath(url) {
    method loadNetworkResource (line 108) | async loadNetworkResource(url, headers) {
  class StringStream (line 122) | class StringStream extends Readable {
    method constructor (line 123) | constructor(str) {
    method _read (line 129) | _read() {
  function removeSourceContentIfMatch (line 140) | async function removeSourceContentIfMatch(sourceMapURL, compiledURL, pay...
  function readLines (line 169) | async function readLines(stream) {

FILE: lib/filepath_to_url.js
  function pathToFileURL (line 12) | function pathToFileURL(fileSystemPath) {
  function fileURLToPath (line 26) | function fileURLToPath(fileURL) {

FILE: lib/launcher.js
  function launch (line 19) | async function launch() {

FILE: lib/process_utility.js
  function prepareProcess (line 9) | function prepareProcess(name, disposeCallback) {

FILE: scripts/builder.js
  function loadAppDescriptors (line 38) | async function loadAppDescriptors(appNames, pathFolders) {
  function moduleNamespace (line 65) | function moduleNamespace(moduleName) {
  function loadModules (line 87) | async function loadModules(appDescriptors, pathFolders, customLoadModule...
  function loadModule (line 101) | async function loadModule(pathFolders, moduleName, customLoadModuleSourc...
  function loadImages (line 141) | async function loadImages(moduleDescriptors, pathFolders) {
  function lookupFile (line 160) | function lookupFile(pathFolders, ...fileNameParts) {
  function loadSource (line 177) | async function loadSource(pathFolders, ...fileNameParts) {
  function loadRawModule (line 187) | async function loadRawModule(pathFolders, ...fileNameParts) {
  function buildApp (line 211) | async function buildApp(appNames, pathFolders, outFolder, minifyJS = cod...

FILE: services/file_system.js
  class FileSystemHandler (line 7) | class FileSystemHandler {
    method constructor (line 8) | constructor() {
    method startWatcher (line 15) | startWatcher(embedderPath, exludePattern, client, mainFileName) {
    method forceFileLoad (line 37) | forceFileLoad(fileNameURL) {
    method dispose (line 43) | dispose() {

FILE: services/file_system_io.js
  class FileSystemIO (line 5) | class FileSystemIO {
    method constructor (line 6) | constructor() {
    method readFile (line 14) | readFile(fileURL, encoding) {
    method writeFile (line 23) | writeFile(fileURL, content, encoding) {
    method createFile (line 32) | createFile(folderURL) {
    method deleteFile (line 46) | deleteFile(fileURL) {
    method renameFile (line 59) | renameFile(fileURL, newName) {
    method dispose (line 70) | dispose() {

FILE: services/ndd_service.js
  function silentRpcErrors (line 18) | function silentRpcErrors(error) {
  constant CALL_EXIT_MESSAGE (line 34) | const CALL_EXIT_MESSAGE = JSON.stringify({
  class Channel (line 40) | class Channel {
    method constructor (line 44) | constructor(ws) {
    method send (line 54) | send(message) {
    method close (line 61) | close() {
    method listen (line 68) | listen(handler) {
    method dispose (line 72) | dispose() {
    method _messageReceived (line 79) | _messageReceived(message) {
  class NddService (line 87) | class NddService {
    method constructor (line 88) | constructor(frontend) {
    method dispose (line 108) | dispose() {
    method _startSession (line 112) | async _startSession(info, frontend) {
    method env (line 137) | env() {
    method debug (line 145) | async debug(execPath, args, options) {

FILE: services/search.js
  class SearchBackend (line 17) | class SearchBackend {
    method constructor (line 18) | constructor(frontend) {
    method indexPath (line 34) | async indexPath(requestId, fileSystemPath, excludedPattern) {
    method _indexFile (line 101) | _indexFile(fileName, index) {
    method _indexChangedFiles (line 144) | async _indexChangedFiles(requestId, fileSystemPath) {
    method stopIndexing (line 181) | stopIndexing(requestId) {
    method searchInPath (line 190) | searchInPath(requestId, fileSystemPath, query) {
    method dispose (line 216) | dispose() {

FILE: services/terminal.js
  class Terminal (line 10) | class Terminal {
    method constructor (line 11) | constructor(frontend, pty, env, cols, rows) {
    method dispose (line 30) | dispose() {
    method resize (line 34) | resize(cols, rows) {
    method write (line 38) | write(data) {
  function init (line 43) | async function init(frontend, env, cols, rows) {

FILE: test/basic.spec.js
  function sleep (line 265) | function sleep() {
  function setupHelpers (line 269) | async function setupHelpers(frontend) {

FILE: test/platform.spec.js
  function addTests (line 21) | function addTests(testRunner) {

FILE: utils/testrunner/Matchers.js
  method constructor (line 18) | constructor(customMatchers = {}) {
  method addMatcher (line 25) | addMatcher(name, matcher) {
  method expect (line 29) | expect(value) {
  class Expect (line 34) | class Expect {
    method constructor (line 35) | constructor(value, matchers) {
  function stringify (line 114) | function stringify(value) {

FILE: utils/testrunner/Multimap.js
  class Multimap (line 17) | class Multimap {
    method constructor (line 18) | constructor() {
    method set (line 22) | set(key, value) {
    method get (line 31) | get(key) {
    method has (line 38) | has(key) {
    method hasValue (line 42) | hasValue(key, value) {
    method size (line 52) | get size() {
    method delete (line 56) | delete(key, value) {
    method deleteAll (line 64) | deleteAll(key) {
    method firstValue (line 68) | firstValue(key) {
    method firstKey (line 75) | firstKey() {
    method valuesArray (line 79) | valuesArray() {
    method keysArray (line 86) | keysArray() {
    method clear (line 90) | clear() {

FILE: utils/testrunner/Reporter.js
  constant RED_COLOR (line 17) | const RED_COLOR = '\x1b[31m';
  constant GREEN_COLOR (line 18) | const GREEN_COLOR = '\x1b[32m';
  constant YELLOW_COLOR (line 19) | const YELLOW_COLOR = '\x1b[33m';
  constant RESET_COLOR (line 20) | const RESET_COLOR = '\x1b[0m';
  class Reporter (line 22) | class Reporter {
    method constructor (line 23) | constructor(runner) {
    method _onStarted (line 32) | _onStarted() {
    method _onTerminated (line 37) | _onTerminated(message, error) {
    method _onFinished (line 49) | _onFinished() {
    method _printTestResults (line 55) | _printTestResults() {
    method _onTestStarted (line 119) | _onTestStarted() {
    method _onTestFinished (line 122) | _onTestFinished(test) {

FILE: utils/testrunner/TestRunner.js
  constant MAJOR_NODEJS_VERSION (line 24) | const MAJOR_NODEJS_VERSION = parseInt(process.version.substring(1).split...
  class UserCallback (line 26) | class UserCallback {
    method constructor (line 27) | constructor(callback, timeout) {
    method run (line 37) | async run(...args) {
    method _getLocation (line 52) | _getLocation() {
    method terminate (line 81) | terminate() {
  class Test (line 99) | class Test {
    method constructor (line 100) | constructor(suite, name, callback, declaredMode, timeout) {
  class Suite (line 114) | class Suite {
    method constructor (line 115) | constructor(parentSuite, name, declaredMode) {
  class TestPass (line 130) | class TestPass {
    method constructor (line 131) | constructor(runner, rootSuite, tests, parallel) {
    method run (line 155) | async run() {
    method _runSuite (line 183) | async _runSuite(workerId, suitesStack, state) {
    method _runTest (line 208) | async _runTest(workerId, test, state) {
    method _runHook (line 232) | async _runHook(workerId, suite, hookName, ...args) {
    method _terminate (line 252) | _terminate(message, error) {
  class TestRunner (line 261) | class TestRunner extends EventEmitter {
    method constructor (line 262) | constructor(options = {}) {
    method _addTest (line 297) | _addTest(mode, name, callback) {
    method _addSuite (line 308) | _addSuite(mode, name, callback) {
    method _addHook (line 318) | _addHook(hookName, callback) {
    method run (line 324) | async run() {
    method timeout (line 334) | timeout() {
    method _runnableTests (line 338) | _runnableTests() {
    method hasFocusedTestsOrSuites (line 367) | hasFocusedTestsOrSuites() {
    method tests (line 371) | tests() {
    method failedTests (line 375) | failedTests() {
    method parallel (line 379) | parallel() {
    method _willStartTest (line 383) | _willStartTest(test) {
    method _didFinishTest (line 387) | _didFinishTest(test) {
  function assert (line 396) | function assert(value, message) {
Condensed preview — 61 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (183K chars).
[
  {
    "path": ".editorconfig",
    "chars": 147,
    "preview": "root = true\n\n[*]\nindent_style = space\nindent_size = 2\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = true\ni"
  },
  {
    "path": ".eslintignore",
    "chars": 31,
    "preview": "test/test.js\nutils/testrunner/\n"
  },
  {
    "path": ".eslintrc.js",
    "chars": 3174,
    "preview": "module.exports = {\n    \"root\": true,\n\n    \"env\": {\n        \"node\": true,\n        \"es6\": true\n    },\n\n    \"parserOptions\""
  },
  {
    "path": ".gitignore",
    "chars": 91,
    "preview": "/.local-frontend/\n/node_modules/\n.DS_Store\n*.swp\n*.pyc\n.vscode\npackage-lock.json\nyarn.lock\n"
  },
  {
    "path": ".npmignore",
    "chars": 222,
    "preview": "# exclude all tests\ntest/\ntest/fs/\nutils/testrunner\n\n# repeats from .gitignore\n/node_modules/\n.DS_Store\n*.swp\n*.pyc\n.vsc"
  },
  {
    "path": ".travis.yml",
    "chars": 45,
    "preview": "language: node_js\nnode_js:\n  - \"12\"\n  - \"10\"\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 5604,
    "preview": "# How to Contribute\n\nFirst of all, thank you for your interest in ndb!\nWe'd love to accept your patches and contribution"
  },
  {
    "path": "LICENSE",
    "chars": 11341,
    "preview": "\n                                 Apache License\n                           Version 2.0, January 2004\n                  "
  },
  {
    "path": "README.md",
    "chars": 4643,
    "preview": "# ndb\n\n<!-- [START badges] -->\n[![Build Status](https://img.shields.io/travis/com/GoogleChromeLabs/ndb/master.svg)](http"
  },
  {
    "path": "build.js",
    "chars": 765,
    "preview": "const path = require('path');\n\nconst Terser = require('terser');\nconst rimraf = require('rimraf');\n\nconst { buildApp } ="
  },
  {
    "path": "debug.js",
    "chars": 77,
    "preview": "#!/usr/bin/env node\nprocess.env.NDB_DEBUG_FRONTEND = 1;\nrequire('./ndb.js');\n"
  },
  {
    "path": "docs/issue_template.md",
    "chars": 380,
    "preview": "<!--\nFor issues, feature requests, or setup troubles with ndb, file an issue right here!\n-->\n\n### Steps to reproduce\n\n**"
  },
  {
    "path": "front_end/ndb/Connection.js",
    "chars": 888,
    "preview": "Ndb.Connection = class {\n  constructor(channel) {\n    this._onMessage = null;\n    this._onDisconnect = null;\n    this._c"
  },
  {
    "path": "front_end/ndb/FileSystem.js",
    "chars": 8504,
    "preview": "/**\n * @license Copyright 2018 Google Inc. All Rights Reserved.\n * Licensed under the Apache License, Version 2.0 (the \""
  },
  {
    "path": "front_end/ndb/InspectorFrontendHostOverrides.js",
    "chars": 1589,
    "preview": "/**\n * @license Copyright 2018 Google Inc. All Rights Reserved.\n * Licensed under the Apache License, Version 2.0 (the \""
  },
  {
    "path": "front_end/ndb/NdbMain.js",
    "chars": 15320,
    "preview": "/**\n * @license Copyright 2018 Google Inc. All Rights Reserved.\n * Licensed under the Apache License, Version 2.0 (the \""
  },
  {
    "path": "front_end/ndb/module.json",
    "chars": 1826,
    "preview": "{\n    \"extensions\": [\n        {\n            \"type\": \"early-initialization\",\n            \"className\": \"Ndb.NdbMain\"\n     "
  },
  {
    "path": "front_end/ndb.html",
    "chars": 408,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n    <link rel=\"icon\" type=\"image/png\" href=\"favicon.png\">\n    <meta http-equiv=\""
  },
  {
    "path": "front_end/ndb.js",
    "chars": 199,
    "preview": "// Copyright 2018 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style licen"
  },
  {
    "path": "front_end/ndb.json",
    "chars": 383,
    "preview": "{\n  \"modules\" : [\n     { \"name\": \"ndb_sdk\", \"type\": \"autostart\" },\n     { \"name\": \"ndb\", \"type\": \"autostart\" },\n     { \""
  },
  {
    "path": "front_end/ndb_sdk/NodeRuntime.js",
    "chars": 1163,
    "preview": "Protocol.inspectorBackend.registerCommand('NodeRuntime.notifyWhenWaitingForDisconnect', [{'name': 'enabled', 'type': 'bo"
  },
  {
    "path": "front_end/ndb_sdk/NodeWorker.js",
    "chars": 5006,
    "preview": "Protocol.inspectorBackend.registerCommand('NodeWorker.enable', [{'name': 'waitForDebuggerOnStart', 'type': 'boolean', 'o"
  },
  {
    "path": "front_end/ndb_sdk/module.json",
    "chars": 106,
    "preview": "{\n    \"dependencies\": [\"sdk\"],\n    \"scripts\": [\n        \"NodeRuntime.js\",\n        \"NodeWorker.js\"\n    ]\n}\n"
  },
  {
    "path": "front_end/ndb_ui/NodeProcesses.js",
    "chars": 4396,
    "preview": "/**\n * @license Copyright 2018 Google Inc. All Rights Reserved.\n * Licensed under the Apache License, Version 2.0 (the \""
  },
  {
    "path": "front_end/ndb_ui/RunConfiguration.js",
    "chars": 3350,
    "preview": "/**\n * @license Copyright 2018 Google Inc. All Rights Reserved.\n * Licensed under the Apache License, Version 2.0 (the \""
  },
  {
    "path": "front_end/ndb_ui/Terminal.js",
    "chars": 4108,
    "preview": "/**\n * @license Copyright 2018 Google Inc. All Rights Reserved.\n * Licensed under the Apache License, Version 2.0 (the \""
  },
  {
    "path": "front_end/ndb_ui/module.json",
    "chars": 1011,
    "preview": "{\n  \"extensions\": [\n      {\n          \"type\": \"view\",\n          \"category\": \"NDB\",\n          \"id\": \"ndb.runView\",\n      "
  },
  {
    "path": "front_end/ndb_ui/nodeProcesses.css",
    "chars": 1476,
    "preview": "/**\n * @license Copyright 2018 Google Inc. All Rights Reserved.\n * Licensed under the Apache License, Version 2.0 (the \""
  },
  {
    "path": "front_end/ndb_ui/runConfiguration.css",
    "chars": 1702,
    "preview": "/**\n * @license Copyright 2018 Google Inc. All Rights Reserved.\n * Licensed under the Apache License, Version 2.0 (the \""
  },
  {
    "path": "front_end/xterm/module.json",
    "chars": 122,
    "preview": "{\n  \"scripts\": [\n      \"dist/xterm.js\",\n      \"dist/addons/fit/fit.js\"\n  ],\n  \"resources\": [\n      \"dist/xterm.css\"\n  ]\n"
  },
  {
    "path": "lib/backend.js",
    "chars": 5362,
    "preview": "/**\n * @license Copyright 2018 Google Inc. All Rights Reserved.\n * Licensed under the Apache License, Version 2.0 (the \""
  },
  {
    "path": "lib/filepath_to_url.js",
    "chars": 973,
    "preview": "const url = require('url');\n\nif (url.pathToFileURL) {\n  module.exports = {\n    pathToFileURL: url.pathToFileURL,\n    fil"
  },
  {
    "path": "lib/launcher.js",
    "chars": 3033,
    "preview": "/**\n * @license Copyright 2018 Google Inc. All Rights Reserved.\n * Licensed under the Apache License, Version 2.0 (the \""
  },
  {
    "path": "lib/preload/ndb/preload.js",
    "chars": 2664,
    "preview": "/**\n * @license Copyright 2018 Google Inc. All Rights Reserved.\n * Licensed under the Apache License, Version 2.0 (the \""
  },
  {
    "path": "lib/process_utility.js",
    "chars": 1059,
    "preview": "/**\n * @license Copyright 2018 Google Inc. All Rights Reserved.\n * Licensed under the Apache License, Version 2.0 (the \""
  },
  {
    "path": "ndb.js",
    "chars": 1585,
    "preview": "#!/usr/bin/env node\n/**\n * @license Copyright 2018 Google Inc. All Rights Reserved.\n * Licensed under the Apache License"
  },
  {
    "path": "package.json",
    "chars": 1066,
    "preview": "{\n  \"name\": \"ndb\",\n  \"version\": \"1.1.5\",\n  \"description\": \"Chrome DevTools for Node.js\",\n  \"main\": \"ndb.js\",\n  \"reposito"
  },
  {
    "path": "scripts/builder.js",
    "chars": 11169,
    "preview": "const fs = {\n  ...require('fs'),\n  ...require('fs').promises\n};\nconst os = require('os');\nconst path = require('path');\n"
  },
  {
    "path": "services/file_system.js",
    "chars": 1531,
    "preview": "const fs = require('fs');\n\nconst { rpc, rpc_process } = require('carlo/rpc');\nconst chokidar = require('chokidar');\ncons"
  },
  {
    "path": "services/file_system_io.js",
    "chars": 1704,
    "preview": "const { rpc, rpc_process } = require('carlo/rpc');\nconst fs = require('fs');\nconst { URL } = require('url');\n\nclass File"
  },
  {
    "path": "services/ndd_service.js",
    "chars": 5828,
    "preview": "/**\n * @license Copyright 2018 Google Inc. All Rights Reserved.\n * Licensed under the Apache License, Version 2.0 (the \""
  },
  {
    "path": "services/search.js",
    "chars": 7519,
    "preview": "/**\n * @license Copyright 2018 Google Inc. All Rights Reserved.\n * Licensed under the Apache License, Version 2.0 (the \""
  },
  {
    "path": "services/terminal.js",
    "chars": 1761,
    "preview": "/**\n * @license Copyright 2018 Google Inc. All Rights Reserved.\n * Licensed under the Apache License, Version 2.0 (the \""
  },
  {
    "path": "test/assets/test-project/index.js",
    "chars": 610,
    "preview": "/**\n * @license Copyright 2018 Google Inc. All Rights Reserved.\n * Licensed under the Apache License, Version 2.0 (the \""
  },
  {
    "path": "test/assets/test-project/index.mjs",
    "chars": 613,
    "preview": "/**\n * @license Copyright 2018 Google Inc. All Rights Reserved.\n * Licensed under the Apache License, Version 2.0 (the \""
  },
  {
    "path": "test/assets/test-project/package.json",
    "chars": 398,
    "preview": "{\n  \"name\": \"test-project\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"run\": \""
  },
  {
    "path": "test/basic.spec.js",
    "chars": 13980,
    "preview": "/**\n * @license Copyright 2018 Google Inc. All Rights Reserved.\n * Licensed under the Apache License, Version 2.0 (the \""
  },
  {
    "path": "test/integration.js",
    "chars": 953,
    "preview": "/**\n * @license Copyright 2018 Google Inc. All Rights Reserved.\n * Licensed under the Apache License, Version 2.0 (the \""
  },
  {
    "path": "test/platform.spec.js",
    "chars": 1540,
    "preview": "/**\n * @license Copyright 2018 Google Inc. All Rights Reserved.\n * Licensed under the Apache License, Version 2.0 (the \""
  },
  {
    "path": "utils/testrunner/Matchers.js",
    "chars": 3839,
    "preview": "/**\n * Copyright 2017 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licens"
  },
  {
    "path": "utils/testrunner/Multimap.js",
    "chars": 1931,
    "preview": "/**\n * Copyright 2017 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licens"
  },
  {
    "path": "utils/testrunner/README.md",
    "chars": 1275,
    "preview": "# TestRunner\n\n- testrunner is a library: no additional binary required; tests are `node.js` scripts\n- parallel wrt IO op"
  },
  {
    "path": "utils/testrunner/Reporter.js",
    "chars": 4837,
    "preview": "/**\n * Copyright 2017 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licens"
  },
  {
    "path": "utils/testrunner/TestRunner.js",
    "chars": 12955,
    "preview": "/**\n * Copyright 2017 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licens"
  },
  {
    "path": "utils/testrunner/examples/fail.js",
    "chars": 1582,
    "preview": "/**\n * Copyright 2017 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licens"
  },
  {
    "path": "utils/testrunner/examples/hookfail.js",
    "chars": 1063,
    "preview": "/**\n * Copyright 2017 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licens"
  },
  {
    "path": "utils/testrunner/examples/hooktimeout.js",
    "chars": 1096,
    "preview": "/**\n * Copyright 2017 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licens"
  },
  {
    "path": "utils/testrunner/examples/timeout.js",
    "chars": 1019,
    "preview": "/**\n * Copyright 2017 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licens"
  },
  {
    "path": "utils/testrunner/examples/unhandledpromiserejection.js",
    "chars": 1092,
    "preview": "/**\n * Copyright 2017 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licens"
  },
  {
    "path": "utils/testrunner/index.js",
    "chars": 796,
    "preview": "/**\n * Copyright 2017 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licens"
  },
  {
    "path": "version.js",
    "chars": 363,
    "preview": "const fs = require('fs');\n\n(function main() {\n  const version = require('./package.json').version;\n  let preload = fs.re"
  }
]

About this extraction

This page contains the full source code of the GoogleChromeLabs/ndb GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 61 files (169.2 KB), approximately 43.6k tokens, and a symbol index with 244 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!