[
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\nindent_style = space\nindent_size = 2\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = true\ninsert_final_newline = true\n"
  },
  {
    "path": ".eslintignore",
    "content": "test/test.js\nutils/testrunner/\n"
  },
  {
    "path": ".eslintrc.js",
    "content": "module.exports = {\n    \"root\": true,\n\n    \"env\": {\n        \"node\": true,\n        \"es6\": true\n    },\n\n    \"parserOptions\": {\n        \"ecmaVersion\": 9\n    },\n\n    /**\n     * ESLint rules\n     *\n     * All available rules: http://eslint.org/docs/rules/\n     *\n     * Rules take the following form:\n     *   \"rule-name\", [severity, { opts }]\n     * Severity: 2 == error, 1 == warning, 0 == off.\n     */\n    \"rules\": {\n        /**\n         * Enforced rules\n         */\n\n\n        // syntax preferences\n        \"quotes\": [2, \"single\", {\n            \"avoidEscape\": true,\n            \"allowTemplateLiterals\": true\n        }],\n        \"semi\": 2,\n        \"no-extra-semi\": 2,\n        \"comma-style\": [2, \"last\"],\n        \"wrap-iife\": [2, \"inside\"],\n        \"spaced-comment\": [2, \"always\", {\n            \"markers\": [\"*\"]\n        }],\n        \"eqeqeq\": [2],\n        \"arrow-body-style\": [2, \"as-needed\"],\n        \"accessor-pairs\": [2, {\n            \"getWithoutSet\": false,\n            \"setWithoutGet\": false\n        }],\n        \"brace-style\": [2, \"1tbs\", {\"allowSingleLine\": true}],\n        \"curly\": [2, \"multi-or-nest\", \"consistent\"],\n        \"new-parens\": 2,\n        \"func-call-spacing\": 2,\n        \"arrow-parens\": [2, \"as-needed\"],\n        \"prefer-const\": 2,\n        \"quote-props\": [2, \"consistent\"],\n\n        // anti-patterns\n        \"no-var\": 2,\n        \"no-with\": 2,\n        \"no-multi-str\": 2,\n        \"no-caller\": 2,\n        \"no-implied-eval\": 2,\n        \"no-labels\": 2,\n        \"no-new-object\": 2,\n        \"no-octal-escape\": 2,\n        \"no-self-compare\": 2,\n        \"no-shadow-restricted-names\": 2,\n        \"no-cond-assign\": 2,\n        \"no-debugger\": 2,\n        \"no-dupe-keys\": 2,\n        \"no-duplicate-case\": 2,\n        \"no-empty-character-class\": 2,\n        \"no-unreachable\": 2,\n        \"no-unsafe-negation\": 2,\n        \"radix\": 2,\n        \"valid-typeof\": 2,\n        \"no-unused-vars\": [2, { \"args\": \"none\", \"vars\": \"local\", \"varsIgnorePattern\": \"(_|load)\" }],\n        \"no-implicit-globals\": [2],\n\n        // es2015 features\n        \"require-yield\": 2,\n        \"template-curly-spacing\": [2, \"never\"],\n\n        // spacing details\n        \"space-infix-ops\": 2,\n        \"space-in-parens\": [2, \"never\"],\n        \"space-before-function-paren\": [2, \"never\"],\n        \"no-whitespace-before-property\": 2,\n        \"keyword-spacing\": [2, {\n            \"overrides\": {\n                \"if\": {\"after\": true},\n                \"else\": {\"after\": true},\n                \"for\": {\"after\": true},\n                \"while\": {\"after\": true},\n                \"do\": {\"after\": true},\n                \"switch\": {\"after\": true},\n                \"return\": {\"after\": true}\n            }\n        }],\n        \"arrow-spacing\": [2, {\n            \"after\": true,\n            \"before\": true\n        }],\n\n        // file whitespace\n        \"no-multiple-empty-lines\": [2, {\"max\": 2}],\n        \"no-mixed-spaces-and-tabs\": 2,\n        \"no-trailing-spaces\": 2,\n        \"linebreak-style\": [ process.platform === \"win32\" ? 0 : 2, \"unix\" ],\n        \"indent\": [2, 2, { \"SwitchCase\": 1, \"CallExpression\": {\"arguments\": 2}, \"MemberExpression\": 2 }],\n        \"key-spacing\": [2, {\n            \"beforeColon\": false\n        }]\n    }\n};"
  },
  {
    "path": ".gitignore",
    "content": "/.local-frontend/\n/node_modules/\n.DS_Store\n*.swp\n*.pyc\n.vscode\npackage-lock.json\nyarn.lock\n"
  },
  {
    "path": ".npmignore",
    "content": "# exclude all tests\ntest/\ntest/fs/\nutils/testrunner\n\n# repeats from .gitignore\n/node_modules/\n.DS_Store\n*.swp\n*.pyc\n.vscode\npackage-lock.json\nyarn.lock\n\n# other\n.editorconfig\n.eslintignore\n.eslintrc.js\nREADME.md\nndb-*.tgz\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: node_js\nnode_js:\n  - \"12\"\n  - \"10\"\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# How to Contribute\n\nFirst of all, thank you for your interest in ndb!\nWe'd love to accept your patches and contributions!\n\n## Contributor License Agreement\n\nContributions to this project must be accompanied by a Contributor License\nAgreement. You (or your employer) retain the copyright to your contribution,\nthis simply gives us permission to use and redistribute your contributions as\npart of the project. Head over to <https://cla.developers.google.com/> to see\nyour current agreements on file or to sign a new one.\n\nYou generally only need to submit a CLA once, so if you've already submitted one\n(even if it was for a different project), you probably don't need to do it\nagain.\n\n## Getting setup\n\n1. Clone this repository\n\n```bash\ngit clone https://github.com/GoogleChromeLabs/ndb\ncd ndb\n```\n\n2. Install dependencies\n\n```bash\nnpm install\n```\n\n## Code reviews\n\nAll submissions, including submissions by project members, require review. We\nuse GitHub pull requests for this purpose. Consult\n[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more\ninformation on using pull requests.\n\n## Code Style\n\n- Coding style is fully defined in [.eslintrc](https://github.com/GoogleChrome/puppeteer/blob/master/.eslintrc.js)\n- Code should be annotated with [closure annotations](https://github.com/google/closure-compiler/wiki/Annotating-JavaScript-for-the-Closure-Compiler).\n- Comments should be generally avoided. If the code would not be understood without comments, consider re-writing the code to make it self-explanatory.\n\nTo run code linter, use:\n\n```bash\nnpm run lint\n```\n\n## Commit Messages\n\nCommit messages should follow the Semantic Commit Messages format:\n\n```\nlabel(namespace): title\n\ndescription\n\nfooter\n```\n\n1. *label* is one of the following:\n    - `fix` - ndb bug fixes.\n    - `feat` - ndb features.\n    - `docs` - changes to docs, e.g. `docs(api.md): ..` to change documentation.\n    - `test` - changes to ndb tests infrastructure.\n    - `style` - ndb code style: spaces/alignment/wrapping etc.\n    - `chore` - build-related work.\n2. *namespace* is put in parenthesis after label and is **optional**.\n3. *title* is a brief summary of changes.\n4. *description* is **optional**, new-line separated from title and is in present tense.\n5. *footer* is **optional**, new-line separated from *description* and contains \"fixes\" / \"references\" attribution to github issues.\n\nExample:\n\n```\nfix(NddService): fix NddService.attach method\n\nThis patch fixes NddService.attach so that it works with Node 12.\n\nFixes #123, Fixes #234\n```\n\n## Adding New Dependencies\n\nFor all dependencies (both installation and development):\n- **Do not add** a dependency if the desired functionality is easily implementable.\n- If adding a dependency, it should be well-maintained and trustworthy.\n\nA barrier for introducing new installation dependencies is especially high:\n- **Do not add** installation dependency unless it's critical to project success.\n\n## Writing Tests\n\n- Every ndb service feature should be accompanied by a test.\n- Tests should be *hermetic*. Tests should not depend on external services.\n- Tests should work on all three platforms: Mac, Linux and Win.\n\nndb tests are located in [test/test.js](https://github.com/GoogleChromeLabs/ndb/blob/master/test/test.js)\nand are written with a [mocha](https://mochajs.org/) framework.\n\n- To run all tests:\n\n```bash\nnpm run unit\n```\n\n- To run a specific test, substitute the `it` with `fit` (mnemonic rule: '*focus it*'):\n\n```js\n  ...\n  // Using \"fit\" to run specific test\n  fit('should work', async function({service}) {\n    const response = await service.method();\n    expect(response).toBe(true);\n  })\n```\n\n- To disable a specific test, substitute the `it` with `xit` (mnemonic rule: '*cross it*'):\n\n```js\n  ...\n  // Using \"xit\" to skip specific test\n  xit('should work', async function({service}) {\n    const response = await service.method();\n    expect(response).toBe(true);\n  })\n```\n\n## Developing ndb hints\n\n- Environment variable NDB_DEBUG_FRONTEND=1 forces ndb to fetch\nfrontend from front_end folder and chrome-devtools-frontend\npackage.\n\n```bash\nNDB_DEBUG_FRONTEND=1 ndb .\n```\n\n- To debug ndb by itself or any ndb service you can use ndb.\n```bash\nNDB_DEBUG_FRONTEND=1 ndb ndb index.js\n```\n\n- To debug running Chrome DevTools frontend you can open DevTools,\nuse Ctrl+Shift+I on Linux or View > Developer > Developer Tools menu\nitem on Mac OS.\n\n## [For Project Maintainers] Releasing to NPM\n\nReleasing to NPM consists of 3 phases:\n1. Source Code: mark a release.\n    1. Bump `package.json` version following the SEMVER rules and send a PR titled `'chore: mark version vXXX.YYY.ZZZ'`.\n    2. Make sure the PR passes **all checks**.\n    3. Merge the PR.\n    4. Once merged, publish release notes using the \"create new tag\" option.\n        - **NOTE**: tag names are prefixed with `'v'`, e.g. for version `1.4.0` tag is `v1.4.0`.\n2. Publish to NPM.\n    1. On your local machine, pull from [upstream](https://github.com/GoogleChromeLabs/ndb) and make sure the last commit is the one just merged.\n    2. Run `git status` and make sure there are no untracked files.\n        - **WHY**: this is to avoid bundling unnecessary files to NPM package\n    3. Run [`pkgfiles`](https://www.npmjs.com/package/pkgfiles) to make sure you don't publish anything unnecessary.\n    4. Run `npm publish`.\n3. Source Code: mark post-release.\n    1. Bump `package.json` version to `-post` version and send a PR titled `'chore: bump version to vXXX.YYY.ZZZ-post'`\n        - **NOTE**: no other commits should be landed in-between release commit and bump commit.\n\n"
  },
  {
    "path": "LICENSE",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright 2018 Google Inc.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License."
  },
  {
    "path": "README.md",
    "content": "# ndb\n\n<!-- [START badges] -->\n[![Build Status](https://img.shields.io/travis/com/GoogleChromeLabs/ndb/master.svg)](https://travis-ci.com/GoogleChromeLabs/ndb)\n[![NPM ndb package](https://img.shields.io/npm/v/ndb.svg)](https://npmjs.org/package/ndb)\n<!-- [END badges] -->\n\n<img src=\"https://raw.githubusercontent.com/ChromeDevTools/devtools-logo/master/192.png\" height=\"200\" align=\"right\">\n\n> ndb is an improved debugging experience for Node.js, enabled by Chrome DevTools\n\n## Installation\n\nCompatibility: ndb requires Node >=8.0.0. It works best with Node >=10.\n\nInstallation: ndb depends on [Puppeteer](https://github.com/GoogleChrome/puppeteer) which downloads a recent version of Chromium (~170MB Mac, ~280MB Linux, ~280MB Win).\n\n```bash\n# global install with npm:\nnpm install -g ndb\n\n\n# alternatively, with yarn:\nyarn global add ndb\n```\n\nGlobal installation may fail with different permission errors, you can find help in this [thread](https://github.com/GoogleChromeLabs/ndb/issues/20).\n\nWindows users: Installation may fail on Windows during compilation the native dependencies. The following command may help: `npm install -g windows-build-tools`\n\n### Local install\n\nIf you want ndb available from an npm script (eg. `npm run debug` runs `ndb index.js`), you can install it as a development dependency:\n\n```bash\n# local install with npm:\nnpm install --save-dev ndb\n\n\n# alternatively, with yarn:\nyarn add ndb --dev\n```\n\nYou 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.\n\n\n## Getting Started\n\nYou can start debugging your Node.js application using one of the following ways:\n\n- Use `ndb` instead of the `node` command\n\n```bash\nndb server.js\n\n# Alternatively, you can prepend `ndb`\nndb node server.js\n```\n\n- Prepend `ndb` in front of any other binary\n\n```bash\n# If you use some other binary, just prepend `ndb`\n## npm run unit\nndb npm run unit\n\n# Debug any globally installed package\n## mocha\nndb mocha\n\n# To use a local binary, use `npx` and prepend before it\nndb npx mocha\n```\n\n- Launch `ndb` as a standalone application \n   - Then, debug any npm script from your `package.json`, e.g. unit tests\n\n```bash\n# cd to your project folder (with a package.json)\nndb .\n# In Sources panel > \"NPM Scripts\" sidebar, click the selected \"Run\" button \n```\n\n- Use `Ctrl`/`Cmd` + `R` to restart last run\n- Run any node command from within ndb's integrated terminal and ndb will connect automatically\n- Run any open script source by using 'Run this script' context menu item, ndb will connect automatically as well\n\n- Use `--prof` flag to profile your app, `Ctrl`/`Cmd` + `R` restarts profiling\n```bash\nndb --prof npm run unit\n```\n\n## What can I do?\n\n`ndb` has some powerful features exclusively for Node.js:\n1. Child processes are detected and attached to.\n1. You can place breakpoints before the modules are required.\n1. 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/).\n1. 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. \n\nIn 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):\n- breakpoint debugging, async stacks (AKA long stack traces), [async stepping](https://developers.google.com/web/updates/2018/01/devtools#async), etc...\n- console (top-level await, object inspection, advanced filtering)\n- [eager evaluation](https://developers.google.com/web/updates/2018/05/devtools#eagerevaluation) in console (requires Node >= 10)\n- JS sampling profiler\n- memory profiler\n\n### Screenshot\n![image](https://user-images.githubusercontent.com/39191/43023843-14a085a6-8c21-11e8-85b7-b9fd3405938a.png)\n\n\n## Contributing\n\nCheck out [contributing guide](https://github.com/GoogleChromeLabs/ndb/blob/master/CONTRIBUTING.md) to get an overview of ndb development.\n\n#### Thanks to the 'OG' `ndb`\n\nIn 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.\n"
  },
  {
    "path": "build.js",
    "content": "const path = require('path');\n\nconst Terser = require('terser');\nconst rimraf = require('rimraf');\n\nconst { buildApp } = require('./scripts/builder.js');\n\nconst DEVTOOLS_DIR = path.dirname(\n    require.resolve('chrome-devtools-frontend/front_end/shell.json'));\n\n(async function main() {\n  const outFolder = path.join(__dirname, '.local-frontend');\n  await new Promise(resolve => rimraf(outFolder, resolve));\n\n  return buildApp(\n      ['ndb', 'heap_snapshot_worker', 'formatter_worker'], [\n        path.join(__dirname, 'front_end'),\n        DEVTOOLS_DIR,\n        path.join(__dirname, 'node_modules'),\n      ], outFolder,\n      minifyJS);\n})();\n\nfunction minifyJS(code) {\n  return Terser.minify(code, {\n    mangle: true,\n    ecma: 8,\n    compress: false\n  }).code;\n}\n"
  },
  {
    "path": "debug.js",
    "content": "#!/usr/bin/env node\nprocess.env.NDB_DEBUG_FRONTEND = 1;\nrequire('./ndb.js');\n"
  },
  {
    "path": "docs/issue_template.md",
    "content": "<!--\nFor issues, feature requests, or setup troubles with ndb, file an issue right here!\n-->\n\n### Steps to reproduce\n\n**Tell us about your environment:**\n\n* ndb version:\n* Platform / OS version:\n* Node.js version:\n\n**What steps will reproduce the problem?**\n\n_Please include code that reproduces the issue._\n\n1.\n2.\n3.\n\n**What is the expected result?**\n\n\n**What happens instead?**\n"
  },
  {
    "path": "front_end/ndb/Connection.js",
    "content": "Ndb.Connection = class {\n  constructor(channel) {\n    this._onMessage = null;\n    this._onDisconnect = null;\n    this._channel = channel;\n  }\n\n  static async create(channel) {\n    const connection = new Ndb.Connection(channel);\n    await channel.listen(rpc.handle(connection));\n    return connection;\n  }\n\n  /**\n   * @param {function((!Object|string))} onMessage\n   */\n  setOnMessage(onMessage) {\n    this._onMessage = onMessage;\n  }\n\n  /**\n   * @param {function(string)} onDisconnect\n   */\n  setOnDisconnect(onDisconnect) {\n    this._onDisconnect = onDisconnect;\n  }\n\n  /**\n   * @param {string} message\n   */\n  sendRawMessage(message) {\n    this._channel.send(message);\n  }\n\n  /**\n   * @return {!Promise}\n   */\n  disconnect() {\n    this._channel.close();\n  }\n\n  /**\n   * @param {message}\n   */\n  dispatchMessage(message) {\n    if (this._onMessage)\n      this._onMessage(message);\n  }\n};\n"
  },
  {
    "path": "front_end/ndb/FileSystem.js",
    "content": "/**\n * @license Copyright 2018 Google Inc. All Rights Reserved.\n * 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\n * 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.\n */\n\nNdb.FileSystem = class extends Persistence.PlatformFileSystem {\n  constructor(fsService, fsIOService, searchService, manager, rootURL) {\n    super(rootURL, '');\n    this._fsService = fsService;\n    this._fsIOService = fsIOService;\n    this._searchService = searchService;\n    this._rootURL = rootURL;\n    this._manager = manager;\n\n    /** @type {!Array<string>} */\n    this._initialFilePaths = [];\n  }\n\n  static async create(manager, rootURL) {\n    const searchClient = new Ndb.FileSystem.SearchClient();\n    const [fsService, fsIOService, searchService] = await Promise.all([\n      Ndb.backend.createService('file_system.js'),\n      Ndb.backend.createService('file_system_io.js'),\n      Ndb.backend.createService('search.js', rpc.handle(searchClient))]);\n\n    // TODO: fix PlatformFileSystem upstream, entire search / indexing pipeline should go\n    // through the platform filesystem. This should make searchClient also go away.\n    InspectorFrontendHost.stopIndexing = searchService.stopIndexing.bind(searchService);\n\n    const fs = new Ndb.FileSystem(fsService, fsIOService, searchService, manager, rootURL);\n    await fs._initFilePaths();\n    return fs;\n  }\n\n  /**\n   * @override\n   * @return {string}\n   */\n  embedderPath() {\n    throw new Error('Not implemented');\n  }\n\n  /**\n   * @override\n   * @return {!Promise}\n   */\n  async _initFilePaths() {\n    await this._fsService.startWatcher(this._rootURL, this._excludePattern(), rpc.handle(this));\n  }\n\n  forceFileLoad(scriptName) {\n    return this._fsService.forceFileLoad(scriptName);\n  }\n\n  /**\n   * @override\n   * @return {!Array<string>}\n   */\n  initialFilePaths() {\n    return this._initialFilePaths;\n  }\n\n  /**\n   * @override\n   * @return {!Array<string>}\n   */\n  initialGitFolders() {\n    return [];\n  }\n\n  /**\n   * @override\n   * @param {string} path\n   * @return {!Promise<?{modificationTime: !Date, size: number}>}\n   */\n  getMetadata(path) {\n    // This method should never be called as long as we are matching using file urls.\n    throw new Error('not implemented');\n  }\n\n  /**\n   * @override\n   * @param {string} path\n   * @return {!Promise<?Blob>}\n   */\n  requestFileBlob(path) {\n    throw new Error('not implemented');\n  }\n\n  /**\n   * @override\n   * @param {string} path\n   * @param {function(?string,boolean)} callback\n   */\n  async requestFileContent(path, callback) {\n    const result = await this._fsIOService.readFile(this._rootURL + path, 'base64');\n    if (this.contentType(path) === Common.resourceTypes.Image) {\n      callback(result, true);\n    } else {\n      const content = await(await fetch(`data:application/octet-stream;base64,${result}`)).text();\n      callback(content, false);\n    }\n  }\n\n  /**\n   * @override\n   * @param {string} path\n   * @param {string} content\n   * @param {boolean} isBase64\n   */\n  async setFileContent(path, content, isBase64) {\n    await this._fsIOService.writeFile(this._rootURL + path, isBase64 ? content : content.toBase64(), 'base64');\n  }\n\n  /**\n   * @override\n   * @param {string} path\n   * @param {?string} name\n   * @return {!Promise<?string>}\n   */\n  async createFile(path, name) {\n    const result = await this._fsIOService.createFile(this._rootURL + (path.length === 0 || path.startsWith('/') ? '' : '/') + path);\n    return result.substr(this._rootURL.length + 1);\n  }\n\n  /**\n   * @override\n   * @param {string} path\n   * @return {!Promise<boolean>}\n   */\n  async deleteFile(path) {\n    return await this._fsIOService.deleteFile(this._rootURL + path);\n  }\n\n  /**\n   * @override\n   * @param {string} path\n   * @param {string} newName\n   * @param {function(boolean, string=)} callback\n   */\n  async renameFile(path, newName, callback) {\n    const result = await this._fsIOService.renameFile(this._rootURL + path, newName);\n    callback(result, result ? newName : null);\n  }\n\n  /**\n   * @override\n   * @param {string} path\n   * @return {!Common.ResourceType}\n   */\n  contentType(path) {\n    const extension = Common.ParsedURL.extractExtension(path);\n    if (Persistence.IsolatedFileSystem._styleSheetExtensions.has(extension))\n      return Common.resourceTypes.Stylesheet;\n    if (Persistence.IsolatedFileSystem._documentExtensions.has(extension))\n      return Common.resourceTypes.Document;\n    if (Persistence.IsolatedFileSystem.ImageExtensions.has(extension))\n      return Common.resourceTypes.Image;\n    if (Persistence.IsolatedFileSystem._scriptExtensions.has(extension))\n      return Common.resourceTypes.Script;\n    return Persistence.IsolatedFileSystem.BinaryExtensions.has(extension) ? Common.resourceTypes.Other :\n      Common.resourceTypes.Document;\n  }\n\n  /**\n   * @override\n   * @param {string} path\n   * @return {string}\n   */\n  mimeFromPath(path) {\n    return Common.ResourceType.mimeFromURL(path) || 'text/plain';\n  }\n\n  /**\n   * @override\n   * @param {string} path\n   * @return {boolean}\n   */\n  canExcludeFolder(path) {\n    return !!path ;\n  }\n\n  /**\n   * @override\n   * @param {string} url\n   * @return {string}\n   */\n  tooltipForURL(url) {\n    const path = Common.ParsedURL.urlToPlatformPath(url, Host.isWin()).trimMiddle(150);\n    return ls`Linked to ${path}`;\n  }\n\n  /**\n   * @override\n   * @param {string} query\n   * @param {!Common.Progress} progress\n   * @return {!Promise<!Array<string>>}\n   */\n  searchInPath(query, progress) {\n    return new Promise(resolve => {\n      const requestId = this._manager.registerCallback(innerCallback);\n      this._searchService.searchInPath(requestId, this._rootURL, query);\n\n      /**\n       * @param {!Array<string>} files\n       */\n      function innerCallback(files) {\n        resolve(files);\n        progress.worked(1);\n      }\n    });\n  }\n\n  /**\n   * @param {string} name\n   */\n  filesChanged(events) {\n    for (const event of events) {\n      const paths = new Multimap();\n      paths.set(this._rootURL, event.name);\n      const emptyMap = new Multimap();\n      Persistence.isolatedFileSystemManager.dispatchEventToListeners(Persistence.IsolatedFileSystemManager.Events.FileSystemFilesChanged, {\n        changed: event.type === 'change' ? paths : emptyMap,\n        added: event.type === 'add' ? paths : emptyMap,\n        removed: event.type === 'unlink' ? paths : emptyMap\n      });\n    }\n  }\n\n  /**\n   * @override\n   * @param {!Common.Progress} progress\n   */\n  indexContent(progress) {\n    progress.setTotalWork(1);\n    const requestId = this._manager.registerProgress(progress);\n    this._searchService.indexPath(requestId, this._rootURL, this._excludePattern());\n  }\n\n  /**\n   * @override\n   * @return {boolean}\n   */\n  supportsAutomapping() {\n    return true;\n  }\n\n  /**\n   * @return {string}\n   */\n  _excludePattern() {\n    return this._manager.workspaceFolderExcludePatternSetting().get();\n  }\n};\n\nNdb.FileSystem.SearchClient = class {\n  /**\n   * @param {number} requestId\n   * @param {string} fileSystemPath\n   * @param {number} totalWork\n   */\n  indexingTotalWorkCalculated(requestId, fileSystemPath, totalWork) {\n    this._callFrontend(() => InspectorFrontendAPI.indexingTotalWorkCalculated(requestId, fileSystemPath, totalWork));\n  }\n\n  /**\n   * @param {number} requestId\n   * @param {string} fileSystemPath\n   * @param {number} worked\n   */\n  indexingWorked(requestId, fileSystemPath, worked) {\n    this._callFrontend(() => InspectorFrontendAPI.indexingWorked(requestId, fileSystemPath, worked));\n  }\n\n  /**\n   * @param {number} requestId\n   * @param {string} fileSystemPath\n   */\n  indexingDone(requestId, fileSystemPath) {\n    this._callFrontend(_ => InspectorFrontendAPI.indexingDone(requestId, fileSystemPath));\n  }\n\n  /**\n   * @param {number} requestId\n   * @param {string} fileSystemPath\n   * @param {!Array.<string>} files\n   */\n  searchCompleted(requestId, fileSystemPath, files) {\n    this._callFrontend(_ => InspectorFrontendAPI.searchCompleted(requestId, fileSystemPath, files));\n  }\n\n  _callFrontend(f) {\n    if (Runtime.queryParam('debugFrontend'))\n      setTimeout(f, 0);\n    else\n      f();\n  }\n};\n"
  },
  {
    "path": "front_end/ndb/InspectorFrontendHostOverrides.js",
    "content": "/**\n * @license Copyright 2018 Google Inc. All Rights Reserved.\n * 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\n * 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.\n */\n\n(function(){\n  InspectorFrontendHost.getPreferences = async function(callback) {\n    [Ndb.backend] = await carlo.loadParams();\n    const prefs = {\n      '__bundled__uiTheme': '\"dark\"'\n    };\n    for (let i = 0; i < window.localStorage.length; i++) {\n      const key = window.localStorage.key(i);\n      prefs[key] = window.localStorage.getItem(key);\n    }\n    callback(prefs);\n  };\n\n  InspectorFrontendHost.isHostedMode = () => false;\n  InspectorFrontendHost.copyText = text => navigator.clipboard.writeText(text);\n  InspectorFrontendHost.openInNewTab = url => Ndb.backend.openInNewTab(url);\n  InspectorFrontendHost.bringToFront = () => Ndb.backend.bringToFront();\n  InspectorFrontendHost.loadNetworkResource = async(url, headers, streamId, callback) => {\n    const text = await Ndb.backend.loadNetworkResource(url, headers);\n    if (text) {\n      Host.ResourceLoader.streamWrite(streamId, text);\n      callback({statusCode: 200});\n    } else {\n      callback({statusCode: 404});\n    }\n  };\n})();\n"
  },
  {
    "path": "front_end/ndb/NdbMain.js",
    "content": "/**\n * @license Copyright 2018 Google Inc. All Rights Reserved.\n * 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\n * 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.\n */\n\nNdb.npmExecPath = function() {\n  if (!Ndb._npmExecPathPromise)\n    Ndb._npmExecPathPromise = Ndb.backend.which('npm').then(result => result.resolvedPath);\n  return Ndb._npmExecPathPromise;\n};\n\n/**\n * @implements {Common.Runnable}\n */\nNdb.NdbMain = class extends Common.Object {\n  /**\n   * @override\n   */\n  async run() {\n    InspectorFrontendAPI.setUseSoftMenu(true);\n    document.title = 'ndb';\n    Common.moduleSetting('blackboxInternalScripts').addChangeListener(Ndb.NdbMain._calculateBlackboxState);\n    Ndb.NdbMain._calculateBlackboxState();\n\n    const setting = Persistence.isolatedFileSystemManager.workspaceFolderExcludePatternSetting();\n    setting.set(Ndb.NdbMain._defaultExcludePattern().join('|'));\n    Ndb.nodeProcessManager = await Ndb.NodeProcessManager.create(SDK.targetManager);\n\n    Ndb.processInfo = await Ndb.backend.processInfo();\n    await Ndb.nodeProcessManager.addFileSystem(Ndb.processInfo.cwd);\n\n    // TODO(ak239): we do not want to create this model for workers, so we need a way to add custom capabilities.\n    SDK.SDKModel.register(NdbSdk.NodeWorkerModel, SDK.Target.Capability.JS, true);\n    SDK.SDKModel.register(NdbSdk.NodeRuntimeModel, SDK.Target.Capability.JS, true);\n\n    await new Promise(resolve => SDK.initMainConnection(resolve));\n    SDK.targetManager.createTarget('<root>', ls`Root`, SDK.Target.Type.Browser, null);\n    if (Common.moduleSetting('autoStartMain').get()) {\n      const main = await Ndb.mainConfiguration();\n      if (main) {\n        if (main.prof)\n          await Ndb.nodeProcessManager.profile(main.execPath, main.args);\n        else\n          Ndb.nodeProcessManager.debug(main.execPath, main.args);\n      }\n    }\n    Ndb.nodeProcessManager.startRepl();\n  }\n\n  static _defaultExcludePattern() {\n    const defaultCommonExcludedFolders = [\n      '/bower_components/', '/\\\\.devtools', '/\\\\.git/', '/\\\\.sass-cache/', '/\\\\.hg/', '/\\\\.idea/',\n      '/\\\\.svn/', '/\\\\.cache/', '/\\\\.project/'\n    ];\n    const defaultWinExcludedFolders = ['/Thumbs.db$', '/ehthumbs.db$', '/Desktop.ini$', '/\\\\$RECYCLE.BIN/'];\n    const defaultMacExcludedFolders = [\n      '/\\\\.DS_Store$', '/\\\\.Trashes$', '/\\\\.Spotlight-V100$', '/\\\\.AppleDouble$', '/\\\\.LSOverride$', '/Icon$',\n      '/\\\\._.*$'\n    ];\n    const defaultLinuxExcludedFolders = ['/.*~$'];\n    let defaultExcludedFolders = defaultCommonExcludedFolders;\n    if (Host.isWin())\n      defaultExcludedFolders = defaultExcludedFolders.concat(defaultWinExcludedFolders);\n    else if (Host.isMac())\n      defaultExcludedFolders = defaultExcludedFolders.concat(defaultMacExcludedFolders);\n    else\n      defaultExcludedFolders = defaultExcludedFolders.concat(defaultLinuxExcludedFolders);\n    return defaultExcludedFolders;\n  }\n\n  static _calculateBlackboxState() {\n    const blackboxInternalScripts = Common.moduleSetting('blackboxInternalScripts').get();\n    const PATTERN = '^internal[\\\\/].*|bin/npm-cli\\.js$|bin/yarn\\.js$';\n    const regexPatterns = Common.moduleSetting('skipStackFramesPattern').getAsArray()\n        .filter(({pattern}) => pattern !== PATTERN && pattern !== '^internal/.*');\n    if (blackboxInternalScripts)\n      regexPatterns.push({pattern: PATTERN });\n    Common.moduleSetting('skipStackFramesPattern').setAsArray(regexPatterns);\n  }\n};\n\nNdb.mainConfiguration = async() => {\n  const info = Ndb.processInfo;\n  const cmd = info.argv.slice(2);\n  if (cmd.length === 0 || cmd[0] === '.')\n    return null;\n  let execPath;\n  let args;\n  let prof = false;\n  if (cmd[0] === '--prof') {\n    prof = true;\n    cmd.shift();\n  }\n  if (cmd[0].endsWith('.js')\n    || cmd[0].endsWith('.mjs')\n    || cmd[0].startsWith('-')) {\n    execPath = await Ndb.processInfo.nodeExecPath;\n    args = cmd;\n  } else {\n    execPath = cmd[0];\n    args = cmd.slice(1);\n  }\n  if (execPath === 'npm')\n    execPath = await Ndb.npmExecPath();\n  else if (execPath === 'node')\n    execPath = await Ndb.processInfo.nodeExecPath;\n  return {\n    name: 'main',\n    command: cmd.join(' '),\n    execPath,\n    args,\n    prof\n  };\n};\n\n/**\n * @implements {UI.ContextMenu.Provider}\n * @unrestricted\n */\nNdb.ContextMenuProvider = class {\n  /**\n   * @override\n   * @param {!Event} event\n   * @param {!UI.ContextMenu} contextMenu\n   * @param {!Object} object\n   */\n  appendApplicableItems(event, contextMenu, object) {\n    if (!(object instanceof Workspace.UISourceCode))\n      return;\n    const url = object.url();\n    if (!url.startsWith('file://') || (!url.endsWith('.js') && !url.endsWith('.mjs')))\n      return;\n    contextMenu.debugSection().appendItem(ls`Run this script`, async() => {\n      const platformPath = await Ndb.backend.fileURLToPath(url);\n      const args = url.endsWith('.mjs') ? ['--experimental-modules', platformPath] : [platformPath];\n      Ndb.nodeProcessManager.debug(Ndb.processInfo.nodeExecPath, args);\n    });\n  }\n};\n\nNdb._connectionSymbol = Symbol('connection');\n\nNdb.NodeProcessManager = class extends Common.Object {\n  constructor(targetManager) {\n    super();\n    this._service = null;\n    this._lastDebugId = 0;\n    this._lastStarted = null;\n    this._targetManager = targetManager;\n    this._cwds = new Map();\n    this._finishProfiling = null;\n    this._cpuProfiles = [];\n    this._targetManager.addModelListener(\n        SDK.RuntimeModel, SDK.RuntimeModel.Events.ExecutionContextDestroyed, this._onExecutionContextDestroyed, this);\n    this._targetManager.addModelListener(\n        NdbSdk.NodeRuntimeModel, NdbSdk.NodeRuntimeModel.Events.WaitingForDisconnect, this._onWaitingForDisconnect, this);\n  }\n\n  static async create(targetManager) {\n    const manager = new Ndb.NodeProcessManager(targetManager);\n    manager._service = await Ndb.backend.createService('ndd_service.js', rpc.handle(manager));\n    return manager;\n  }\n\n  env() {\n    return this._service.env();\n  }\n\n  /**\n   * @param {string} cwd\n   * @param {string=} mainFileName\n   * @return {!Promise}\n   */\n  async addFileSystem(cwd, mainFileName) {\n    let promise = this._cwds.get(cwd);\n    if (!promise) {\n      async function innerAdd() {\n        const fileSystemManager = Persistence.isolatedFileSystemManager;\n        const fs = await Ndb.FileSystem.create(fileSystemManager, cwd);\n        fileSystemManager.addPlatformFileSystem(cwd, fs);\n        return fs;\n      }\n      promise = innerAdd();\n      this._cwds.set(cwd, promise);\n    }\n    if (mainFileName)\n      await (await promise).forceFileLoad(mainFileName);\n    await promise;\n  }\n\n  async detected(info, channel) {\n    const connection = await Ndb.Connection.create(channel);\n    const target = this._targetManager.createTarget(\n        info.id, userFriendlyName(info), SDK.Target.Type.Node,\n        this._targetManager.targetById(info.ppid) || this._targetManager.mainTarget(), undefined, false, connection);\n    target[NdbSdk.connectionSymbol] = connection;\n    await this.addFileSystem(info.cwd, info.scriptName);\n    if (info.scriptName) {\n      const scriptURL = info.scriptName;\n      const uiSourceCode = Workspace.workspace.uiSourceCodeForURL(scriptURL);\n      const isBlackboxed = Bindings.blackboxManager.isBlackboxedURL(scriptURL, false);\n      if (isBlackboxed)\n        return connection.disconnect();\n      if (uiSourceCode) {\n        if (Common.moduleSetting('pauseAtStart').get() && !isBlackboxed)\n          Bindings.breakpointManager.setBreakpoint(uiSourceCode, 0, 0, '', true);\n        else\n          Common.Revealer.reveal(uiSourceCode);\n      }\n    }\n    if (info.data === this._profilingNddData)\n      this._profiling.add(target.id());\n\n    function userFriendlyName(info) {\n      if (info.data === 'ndb/repl')\n        return 'repl';\n      return info.argv.map(arg => {\n        const index1 = arg.lastIndexOf('/');\n        const index2 = arg.lastIndexOf('\\\\');\n        if (index1 === -1 && index2 === -1)\n          return arg;\n        return arg.slice(Math.max(index1, index2) + 1);\n      }).join(' ');\n    }\n  }\n\n  disconnected(sessionId) {\n    const target = this._targetManager.targetById(sessionId);\n    if (target) {\n      this._targetManager.removeTarget(target);\n      target.dispose();\n    }\n  }\n\n  async terminalData(stream, data) {\n    const content = await(await fetch(`data:application/octet-stream;base64,${data}`)).text();\n    if (content.startsWith('Debugger listening on') || content.startsWith('Debugger attached.') || content.startsWith('Waiting for the debugger to disconnect...'))\n      return;\n    await Ndb.backend.writeTerminalData(stream, data);\n    this.dispatchEventToListeners(Ndb.NodeProcessManager.Events.TerminalData, content);\n  }\n\n  async _onExecutionContextDestroyed(event) {\n    const executionContext = event.data;\n    if (!executionContext.isDefault)\n      return;\n    return this._onWaitingForDisconnect({data: executionContext.target()});\n  }\n\n  async _onWaitingForDisconnect(event) {\n    const target = event.data;\n    if (target.name() === 'repl')\n      this.startRepl();\n    if (this._profiling && (this._profiling.has(target.id()) || this._profiling.has(target.parentTarget().id()))) {\n      this._cpuProfiles.push({\n        profile: await target.model(SDK.CPUProfilerModel).stopRecording(),\n        name: target.name(),\n        id: target.id()\n      });\n      this._profiling.delete(target.id());\n      if (this._profiling.size === 0)\n        this._finishProfiling();\n    }\n    const connection = target[NdbSdk.connectionSymbol];\n    if (connection)\n      await connection.disconnect();\n  }\n\n  async startRepl() {\n    const code = btoa(`console.log('Welcome to the ndb %cR%cE%cP%cL%c!',\n      'color:#8bc34a', 'color:#ffc107', 'color:#ff5722', 'color:#2196f3', 'color:inherit');\n      process.title = 'ndb/repl';\n      process.on('uncaughtException', console.error);\n      setInterval(_ => 0, 2147483647)//# sourceURL=repl.js`);\n    const args = ['-e', `eval(Buffer.from('${code}', 'base64').toString())`];\n    const options = { ignoreOutput: true, data: 'ndb/repl' };\n    const node = Ndb.processInfo.nodeExecPath;\n    return this.debug(node, args, options);\n  }\n\n  async debug(execPath, args, options) {\n    options = options || {};\n    const debugId = options.data || String(++this._lastDebugId);\n    if (!options.data)\n      this._lastStarted = {execPath, args, debugId, isProfiling: !!this._finishProfiling};\n\n    return this._service.debug(\n        execPath, args, {\n          ...options,\n          data: debugId,\n          cwd: Ndb.processInfo.cwd\n        });\n  }\n\n  async profile(execPath, args, options) {\n    // TODO(ak239): move it out here.\n    await UI.viewManager.showView('timeline');\n    const action = UI.actionRegistry.action('timeline.toggle-recording');\n    await action.execute();\n    this._profilingNddData = String(++this._lastDebugId);\n    this._profiling = new Set();\n    this.debug(execPath, args, { data: this._profilingNddData });\n    await new Promise(resolve => this._finishProfiling = resolve);\n    this._profilingNddData = '';\n    await Promise.all(SDK.targetManager.models(SDK.CPUProfilerModel).map(profiler => profiler.stopRecording()));\n    const controller = Timeline.TimelinePanel.instance()._controller;\n    const mainProfile = this._cpuProfiles.find(data => !data.id.includes('#'));\n    controller.traceEventsCollected([{\n      cat: SDK.TracingModel.DevToolsMetadataEventCategory,\n      name: TimelineModel.TimelineModel.DevToolsMetadataEvent.TracingStartedInPage,\n      ph: 'M', pid: 1, tid: mainProfile.id, ts: 0,\n      args: {data: {sessionId: 1}}\n    }]);\n    for (const {profile, name, id} of this._cpuProfiles) {\n      controller.traceEventsCollected([{\n        cat: SDK.TracingModel.DevToolsMetadataEventCategory,\n        name: TimelineModel.TimelineModel.DevToolsMetadataEvent.TracingSessionIdForWorker,\n        ph: 'M', pid: 1, tid: id, ts: 0,\n        args: {data: {sessionId: 1, workerThreadId: id, workerId: id, url: name}}\n      }]);\n      controller.traceEventsCollected(TimelineModel.TimelineJSProfileProcessor.buildTraceProfileFromCpuProfile(\n          profile, id, false, TimelineModel.TimelineModel.WorkerThreadName));\n    }\n    this._cpuProfiles = [];\n    await action.execute();\n  }\n\n  async kill(target) {\n    return target.runtimeAgent().invoke_evaluate({\n      expression: 'process.exit(-1)'\n    });\n  }\n\n  async restartLast() {\n    if (!this._lastStarted)\n      return;\n    await Promise.all(SDK.targetManager.targets()\n        .filter(target => target.id() !== '<root>')\n        .map(target => target.runtimeAgent().invoke_evaluate({\n          expression: `'${this._lastStarted.debugId}' === process.env.NDD_DATA && process.exit(-1)`\n        })));\n    const {execPath, args, isProfiling} = this._lastStarted;\n    if (!isProfiling)\n      await this.debug(execPath, args);\n    else\n      await this.profile(execPath, args);\n  }\n};\n\nNdb.NodeProcessManager.Events = {\n  TerminalData: Symbol('terminalData')\n};\n\n/**\n * @implements {UI.ActionDelegate}\n * @unrestricted\n */\nNdb.RestartActionDelegate = class {\n  /**\n   * @override\n   * @param {!UI.Context} context\n   * @param {string} actionId\n   * @return {boolean}\n   */\n  handleAction(context, actionId) {\n    switch (actionId) {\n      case 'ndb.restart':\n        Ndb.nodeProcessManager.restartLast();\n        return true;\n    }\n    return false;\n  }\n};\n\n// Temporary hack until frontend with fix is rolled.\n// fix: TBA.\nSDK.Target.prototype.decorateLabel = function(label) {\n  return this.name();\n};\n\n// Front-end does not respect modern toggle semantics, patch it.\nconst originalToggle = DOMTokenList.prototype.toggle;\nDOMTokenList.prototype.toggle = function(token, force) {\n  if (arguments.length === 1)\n    force = !this.contains(token);\n  return originalToggle.call(this, token, !!force);\n};\n\n/**\n * @param {string} sourceMapURL\n * @param {string} compiledURL\n * @return {!Promise<?SDK.TextSourceMap>}\n * @this {SDK.TextSourceMap}\n */\nSDK.TextSourceMap.load = async function(sourceMapURL, compiledURL) {\n  const {payload, error} = await Ndb.backend.loadSourceMap(sourceMapURL, compiledURL);\n  if (error || !payload)\n    return null;\n\n  let textSourceMap;\n  try {\n    textSourceMap = new SDK.TextSourceMap(compiledURL, sourceMapURL, payload);\n  } catch (e) {\n    Common.console.warn('DevTools failed to parse SourceMap: ' + sourceMapURL);\n    return null;\n  }\n\n  const modulePrefix = await Ndb.backend.getNodeScriptPrefix();\n  for (const uiSourceCode of Workspace.workspace.uiSourceCodes()) {\n    if (uiSourceCode.url() === compiledURL && uiSourceCode.project().type() === Workspace.projectTypes.Network) {\n      const content = await uiSourceCode.requestContent();\n      if (content.startsWith(modulePrefix)) {\n        for (const mapping of textSourceMap.mappings()) {\n          if (!mapping.lineNumber)\n            mapping.columnNumber += modulePrefix.length;\n        }\n        break;\n      }\n    }\n  }\n\n  return textSourceMap;\n};\n"
  },
  {
    "path": "front_end/ndb/module.json",
    "content": "{\n    \"extensions\": [\n        {\n            \"type\": \"early-initialization\",\n            \"className\": \"Ndb.NdbMain\"\n        },\n        {\n            \"type\": \"setting\",\n            \"category\": \"Debugger\",\n            \"title\": \"Set breakpoint at first line\",\n            \"settingName\": \"pauseAtStart\",\n            \"settingType\": \"boolean\",\n            \"defaultValue\": false\n        },\n        {\n            \"type\": \"setting\",\n            \"category\": \"Debugger\",\n            \"title\": \"Autostart main\",\n            \"settingName\": \"autoStartMain\",\n            \"settingType\": \"boolean\",\n            \"defaultValue\": true\n        },\n        {\n            \"type\": \"setting\",\n            \"category\": \"Debugger\",\n            \"title\": \"Blackbox internal/* Node.js scripts\",\n            \"settingName\": \"blackboxInternalScripts\",\n            \"settingType\": \"boolean\",\n            \"defaultValue\": true\n        },\n        {\n            \"type\": \"action\",\n            \"actionId\": \"ndb.restart\",\n            \"className\": \"Ndb.RestartActionDelegate\",\n            \"title\": \"Restart last run configuration\",\n            \"bindings\": [\n                {\n                    \"platform\": \"windows,linux\",\n                    \"shortcut\": \"F5 Ctrl+R\"\n                },\n                {\n                    \"platform\": \"mac\",\n                    \"shortcut\": \"Meta+R\"\n                }\n            ]\n        },\n        {\n            \"type\": \"@UI.ContextMenu.Provider\",\n            \"contextTypes\": [\n                \"Workspace.UISourceCode\"\n            ],\n            \"className\": \"Ndb.ContextMenuProvider\"\n        }\n    ],\n    \"dependencies\": [\"common\", \"sdk\", \"ndb_sdk\", \"bindings\", \"persistence\", \"components\"],\n    \"scripts\": [\n        \"InspectorFrontendHostOverrides.js\",\n        \"Connection.js\",\n        \"FileSystem.js\",\n        \"NdbMain.js\"\n    ]\n}\n"
  },
  {
    "path": "front_end/ndb.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n<head>\n    <link rel=\"icon\" type=\"image/png\" href=\"favicon.png\">\n    <meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\">\n    <meta name=\"referrer\" content=\"no-referrer\">\n    <script type=\"text/javascript\" src=\"Runtime.js\"></script>\n    <script type=\"text/javascript\" src=\"ndb.js\"></script>\n</head>\n<body class=\"undocked\" id=\"-blink-dev-tools\"></body>\n</html>\n"
  },
  {
    "path": "front_end/ndb.js",
    "content": "// Copyright 2018 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\nRuntime.startApplication('ndb');\n"
  },
  {
    "path": "front_end/ndb.json",
    "content": "{\n  \"modules\" : [\n     { \"name\": \"ndb_sdk\", \"type\": \"autostart\" },\n     { \"name\": \"ndb\", \"type\": \"autostart\" },\n     { \"name\": \"layer_viewer\" },\n     { \"name\": \"timeline_model\" },\n     { \"name\": \"timeline\" },\n     { \"name\": \"product_registry\" },\n     { \"name\": \"mobile_throttling\" },\n     { \"name\": \"ndb_ui\" },\n     { \"name\": \"xterm\" }\n ],\n  \"extends\": \"shell\",\n  \"has_html\": true\n}\n"
  },
  {
    "path": "front_end/ndb_sdk/NodeRuntime.js",
    "content": "Protocol.inspectorBackend.registerCommand('NodeRuntime.notifyWhenWaitingForDisconnect', [{'name': 'enabled', 'type': 'boolean', 'optional': false}], [], false);\nProtocol.inspectorBackend.registerEvent('NodeRuntime.waitingForDisconnect', []);\n\nNdbSdk.NodeRuntimeModel = class extends SDK.SDKModel {\n  /**\n   * @param {!SDK.Target} target\n   */\n  constructor(target) {\n    super(target);\n\n    this._agent = target.nodeRuntimeAgent();\n    this.target().registerNodeRuntimeDispatcher(new NdbSdk.NodeRuntimeDispatcher(this));\n    this._agent.notifyWhenWaitingForDisconnect(true);\n  }\n\n  /**\n   * @param {string} sessionId\n   * @param {!Object} workerInfo\n   * @param {boolean} waitingForDebugger\n   */\n  _waitingForDisconnect() {\n    this.dispatchEventToListeners(NdbSdk.NodeRuntimeModel.Events.WaitingForDisconnect, this.target());\n  }\n};\n\n/** @enum {symbol} */\nNdbSdk.NodeRuntimeModel.Events = {\n  WaitingForDisconnect: Symbol('WaitingForDisconnect')\n};\n\nNdbSdk.NodeRuntimeDispatcher = class {\n  constructor(nodeRuntimeModel) {\n    this._nodeRuntimeModel = nodeRuntimeModel;\n  }\n\n  waitingForDisconnect() {\n    this._nodeRuntimeModel._waitingForDisconnect();\n  }\n};\n"
  },
  {
    "path": "front_end/ndb_sdk/NodeWorker.js",
    "content": "Protocol.inspectorBackend.registerCommand('NodeWorker.enable', [{'name': 'waitForDebuggerOnStart', 'type': 'boolean', 'optional': false}], [], false);\nProtocol.inspectorBackend.registerCommand('NodeWorker.disable', [], [], false);\nProtocol.inspectorBackend.registerCommand('NodeWorker.sendMessageToWorker', [{'name': 'message', 'type': 'string', 'optional': false}, {'name': 'sessionId', 'type': 'string', 'optional': false}], [], false);\nProtocol.inspectorBackend.registerCommand('NodeWorker.detach', [{'name': 'sessionId', 'type': 'string', 'optional': false}], [], false);\nProtocol.inspectorBackend.registerEvent('NodeWorker.attachedToWorker', ['sessionId', 'workerInfo', 'waitingForDebugger']);\nProtocol.inspectorBackend.registerEvent('NodeWorker.detachedFromWorker', ['sessionId']);\nProtocol.inspectorBackend.registerEvent('NodeWorker.receivedMessageFromWorker', ['sessionId', 'message']);\n\nNdbSdk.connectionSymbol = Symbol('connection');\n\nNdbSdk.NodeWorkerModel = class extends SDK.SDKModel {\n  /**\n   * @param {!SDK.Target} target\n   */\n  constructor(target) {\n    super(target);\n\n    this._sessions = new Map();\n    this._targets = new Map();\n    this._agent = target.nodeWorkerAgent();\n    this.target().registerNodeWorkerDispatcher(new NdbSdk.NodeWorkerDispatcher(this));\n    this._agent.invoke_enable({waitForDebuggerOnStart: true});\n  }\n\n  /**\n   * @param {string} message\n   * @param {string} sessionId\n   * @return {!Promise}\n   */\n  sendMessageToWorker(message, sessionId) {\n    return this._agent.sendMessageToWorker(message, sessionId);\n  }\n\n  /**\n   * @param {string} sessionId\n   * @return {!Promise}\n   */\n  detach(sessionId) {\n    return this._agent.detach(sessionId);\n  }\n\n  /**\n   * @override\n   */\n  dispose() {\n    this._sessions.clear();\n    for (const target of this._targets.values()) {\n      SDK.targetManager.removeTarget(target);\n      target.dispose();\n    }\n    this._targets.clear();\n  }\n\n  /**\n   * @param {string} sessionId\n   * @param {!Object} workerInfo\n   * @param {boolean} waitingForDebugger\n   */\n  _attachedToWorker(sessionId, workerInfo, waitingForDebugger) {\n    const id = this.target().id() + '#' + workerInfo.workerId;\n    const connection = new NdbSdk.NodeWorkerConnection(sessionId, this);\n    this._sessions.set(sessionId, connection);\n    const target = SDK.targetManager.createTarget(\n        id, workerInfo.title, SDK.Target.Type.Node, this.target(),\n        undefined, false, connection);\n    target[NdbSdk.connectionSymbol] = connection;\n    this._targets.set(sessionId, target);\n    target.runtimeAgent().runIfWaitingForDebugger();\n  }\n\n  /**\n   * @param {string} sessionId\n   */\n  _detachedFromWorker(sessionId) {\n    const session = this._sessions.get(sessionId);\n    if (session) {\n      this._sessions.delete(sessionId);\n      const target = this._targets.get(sessionId);\n      if (target) {\n        SDK.targetManager.removeTarget(target);\n        target.dispose();\n        this._targets.delete(sessionId);\n      }\n    }\n  }\n\n  /**\n   * @param {string} sessionId\n   * @param {string} message\n   */\n  _receivedMessageFromWorker(sessionId, message) {\n    const session = this._sessions.get(sessionId);\n    if (session)\n      session.receivedMessageFromWorker(message);\n  }\n};\n\nNdbSdk.NodeWorkerConnection = class {\n  constructor(sessionId, nodeWorkerModel) {\n    this._onMessage = null;\n    this._onDisconnect = null;\n    this._sessionId = sessionId;\n    this._nodeWorkerModel = nodeWorkerModel;\n  }\n\n  /**\n   * @param {function((!Object|string))} onMessage\n   */\n  setOnMessage(onMessage) {\n    this._onMessage = onMessage;\n  }\n\n  /**\n   * @param {function(string)} onDisconnect\n   */\n  setOnDisconnect(onDisconnect) {\n    this._onDisconnect = onDisconnect;\n  }\n\n  /**\n   * @param {string} message\n   */\n  sendRawMessage(message) {\n    this._nodeWorkerModel.sendMessageToWorker(message, this._sessionId);\n  }\n\n  /**\n   * @return {!Promise}\n   */\n  disconnect() {\n    return this._nodeWorkerModel.detach(this._sessionId);\n  }\n\n  /**\n   * @param {string} message\n   */\n  receivedMessageFromWorker(message) {\n    if (this._onMessage)\n      this._onMessage(message);\n  }\n\n  detachedFromWorker() {\n    if (this._onDisconnect)\n      this._onDisconnect();\n  }\n};\n\nNdbSdk.NodeWorkerDispatcher = class {\n  /**\n   * @param {!NdbSdk.NodeWorkerModel}\n   */\n  constructor(nodeWorkerModel) {\n    this._nodeWorkerModel = nodeWorkerModel;\n  }\n\n  /**\n   * @param {string} sessionId\n   * @param {!Object} workerInfo\n   * @param {boolean} waitingForDebugger\n   */\n  attachedToWorker(sessionId, workerInfo, waitingForDebugger) {\n    this._nodeWorkerModel._attachedToWorker(sessionId, workerInfo, waitingForDebugger);\n  }\n\n  /**\n   * @param {string} sessionId\n   */\n  detachedFromWorker(sessionId) {\n    this._nodeWorkerModel._detachedFromWorker(sessionId);\n  }\n\n  /**\n   * @param {string} sessionId\n   * @param {string} message\n   */\n  receivedMessageFromWorker(sessionId, message) {\n    this._nodeWorkerModel._receivedMessageFromWorker(sessionId, message);\n  }\n};\n"
  },
  {
    "path": "front_end/ndb_sdk/module.json",
    "content": "{\n    \"dependencies\": [\"sdk\"],\n    \"scripts\": [\n        \"NodeRuntime.js\",\n        \"NodeWorker.js\"\n    ]\n}\n"
  },
  {
    "path": "front_end/ndb_ui/NodeProcesses.js",
    "content": "/**\n * @license Copyright 2018 Google Inc. All Rights Reserved.\n * 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\n * 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.\n */\n\nNdb.NodeProcesses = class extends UI.VBox {\n  constructor() {\n    super(true);\n    this.registerRequiredCSS('ndb_ui/nodeProcesses.css');\n\n    const toolbar = new UI.Toolbar('process-toolbar', this.contentElement);\n    this._pauseAtStartCheckbox = new UI.ToolbarSettingCheckbox(\n        Common.moduleSetting('pauseAtStart'));\n    this._pauseAtStartCheckbox.element.id = 'pause-at-start-checkbox';\n    toolbar.appendToolbarItem(this._pauseAtStartCheckbox);\n\n    this._emptyElement = this.contentElement.createChild('div', 'gray-info-message');\n    this._emptyElement.id = 'no-running-nodes-msg';\n    this._emptyElement.textContent = Common.UIString('No running nodes');\n\n    this._treeOutline = new UI.TreeOutlineInShadow();\n    this._treeOutline.registerRequiredCSS('ndb_ui/nodeProcesses.css');\n    this.contentElement.appendChild(this._treeOutline.element);\n    this._treeOutline.element.classList.add('hidden');\n\n    this._targetToUI = new Map();\n    SDK.targetManager.observeTargets(this);\n  }\n\n  /**\n   * @override\n   * @param {!SDK.Target} target\n   */\n  targetAdded(target) {\n    if (target.id() === '<root>')\n      return;\n    if (target.name() === 'repl')\n      return;\n    const f = UI.Fragment.build`\n      <div class=process-item>\n        <div class=process-title>${target.name()}</div>\n        <div $=state class=process-item-state></div>\n      </div>\n      <div class='controls-container fill'>\n        <div class=controls-gradient></div>\n        <div $=controls-buttons class=controls-buttons></div>\n      </div>\n    `;\n    const debuggerModel = target.model(SDK.DebuggerModel);\n    debuggerModel.addEventListener(SDK.DebuggerModel.Events.DebuggerPaused, () => {\n      f.$('state').textContent = 'paused';\n    });\n    debuggerModel.addEventListener(SDK.DebuggerModel.Events.DebuggerResumed, () => {\n      f.$('state').textContent = 'attached';\n    });\n    f.$('state').textContent = debuggerModel.isPaused() ? 'paused' : 'attached';\n\n    const buttons = f.$('controls-buttons');\n    const toolbar = new UI.Toolbar('', buttons);\n    const button = new UI.ToolbarButton(Common.UIString('Kill'), 'largeicon-terminate-execution');\n    button.addEventListener(UI.ToolbarButton.Events.Click, _ => Ndb.nodeProcessManager.kill(target));\n    toolbar.appendToolbarItem(button);\n\n    const treeElement = new UI.TreeElement(f.element());\n    treeElement.onselect = _ => {\n      if (UI.context.flavor(SDK.Target) !== target)\n        UI.context.setFlavor(SDK.Target, target);\n    };\n\n    const parentTarget = target.parentTarget();\n    let parentTreeElement = this._treeOutline.rootElement();\n    if (parentTarget) {\n      const parentUI = this._targetToUI.get(parentTarget);\n      if (parentUI)\n        parentTreeElement = parentUI.treeElement;\n    }\n    parentTreeElement.appendChild(treeElement);\n    parentTreeElement.expand();\n\n    if (!this._targetToUI.size) {\n      this._emptyElement.classList.add('hidden');\n      this._treeOutline.element.classList.remove('hidden');\n    }\n    this._targetToUI.set(target, {treeElement, f});\n  }\n\n  /**\n   * @override\n   * @param {!SDK.Target} target\n   */\n  targetRemoved(target) {\n    const ui = this._targetToUI.get(target);\n    if (ui) {\n      const parentTreeElement = ui.treeElement.parent;\n      for (const child of ui.treeElement.children().slice()) {\n        ui.treeElement.removeChild(child);\n        parentTreeElement.appendChild(child);\n      }\n      parentTreeElement.removeChild(ui.treeElement);\n      this._targetToUI.delete(target);\n    }\n    if (!this._targetToUI.size) {\n      this._emptyElement.classList.remove('hidden');\n      this._treeOutline.element.classList.add('hidden');\n    }\n  }\n\n  _targetFlavorChanged({data: target}) {\n    const treeElement = this._targetToUI.get(target);\n    if (treeElement)\n      treeElement.select();\n  }\n};\n"
  },
  {
    "path": "front_end/ndb_ui/RunConfiguration.js",
    "content": "/**\n * @license Copyright 2018 Google Inc. All Rights Reserved.\n * 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\n * 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.\n */\n\nNdb.RunConfiguration = class extends UI.VBox {\n  constructor() {\n    super(true);\n    this.registerRequiredCSS('ndb_ui/runConfiguration.css');\n    this._items = new UI.ListModel();\n    this._list = new UI.ListControl(this._items, this, UI.ListMode.NonViewport);\n    this.contentElement.appendChild(this._list.element);\n    this.update();\n  }\n\n  async update() {\n    const configurations = [];\n    const main = await Ndb.mainConfiguration();\n    if (main)\n      configurations.push(main);\n    const pkg = await Ndb.backend.pkg();\n    if (pkg) {\n      const scripts = pkg.scripts || {};\n      this._items.replaceAll(configurations.concat(Object.keys(scripts).map(name => ({\n        name,\n        command: scripts[name],\n        args: ['run', name]\n      }))));\n    }\n  }\n\n  /**\n   * @override\n   * @param {!SDK.DebuggerModel} debuggerModel\n   * @return {!Element}\n   */\n  createElementForItem(item) {\n    const f = UI.Fragment.build`\n    <div class=list-item>\n      <div class=configuration-item>\n        <div>${item.name}</div>\n        <div class=configuration-command>${item.command}</div>\n      </div>\n      <div class='controls-container fill'>\n        <div class=controls-gradient></div>\n        <div $=controls-buttons class=controls-buttons></div>\n      </div>\n    </div>`;\n    const buttons = f.$('controls-buttons');\n    const toolbar = new UI.Toolbar('', buttons);\n    const runButton = new UI.ToolbarButton(Common.UIString('Run'), 'largeicon-play');\n    runButton.addEventListener(UI.ToolbarButton.Events.Click, this._runConfig.bind(this, item.execPath, item.args));\n    toolbar.appendToolbarItem(runButton);\n    const profileButton = new UI.ToolbarButton(Common.UIString('Start recording..'), 'largeicon-start-recording');\n    profileButton.addEventListener(UI.ToolbarButton.Events.Click, this._profileConfig.bind(this, item.execPath, item.args));\n    toolbar.appendToolbarItem(profileButton);\n    return f.element();\n  }\n\n  async _runConfig(execPath, args) {\n    await Ndb.nodeProcessManager.debug(execPath || await Ndb.npmExecPath(), args);\n  }\n\n  async _profileConfig(execPath, args) {\n    await Ndb.nodeProcessManager.profile(execPath || await Ndb.npmExecPath(), args);\n  }\n\n  /**\n   * @override\n   * @param {!SDK.DebuggerModel} debuggerModel\n   * @return {number}\n   */\n  heightForItem(debuggerModel) {\n    return 12;\n  }\n\n  /**\n   * @override\n   * @param {!SDK.DebuggerModel} debuggerModel\n   * @return {boolean}\n   */\n  isItemSelectable(debuggerModel) {\n    return false;\n  }\n\n  /**\n   * @override\n   * @param {?Profiler.IsolateSelector.ListItem} from\n   * @param {?Profiler.IsolateSelector.ListItem} to\n   * @param {?Element} fromElement\n   * @param {?Element} toElement\n   */\n  selectedItemChanged(from, to, fromElement, toElement) {}\n};\n"
  },
  {
    "path": "front_end/ndb_ui/Terminal.js",
    "content": "/**\n * @license Copyright 2018 Google Inc. All Rights Reserved.\n * 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\n * 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.\n */\n\nTerminal.applyAddon(fit);\n\nNdb.Terminal = class extends UI.VBox {\n  constructor() {\n    super(true);\n    this._init = false;\n    this.registerRequiredCSS('xterm/dist/xterm.css');\n    this.element.addEventListener('contextmenu', this._onContextMenu.bind(this));\n    this._terminal = Ndb.Terminal._createTerminal();\n    this._terminal.on('resize', this._sendResize.bind(this));\n    this._terminal.on('data', this._sendData.bind(this));\n    Ndb.nodeProcessManager.addEventListener(Ndb.NodeProcessManager.Events.TerminalData, this._terminalData, this);\n  }\n\n  static _createTerminal() {\n    const terminal = new Terminal();\n    let fontFamily;\n    let fontSize = 11;\n    if (Host.isMac()) {\n      fontFamily = 'Menlo, monospace';\n    } else if (Host.isWin()) {\n      fontFamily = 'Consolas, Lucida Console, Courier New, monospace';\n      fontSize = 12;\n    } else {\n      fontFamily = 'dejavu sans mono, monospace';\n    }\n    terminal.setOption('fontFamily', fontFamily);\n    terminal.setOption('fontSize', fontSize);\n    terminal.setOption('cursorStyle', 'bar');\n    terminal.setOption('convertEol', true);\n    return terminal;\n  }\n\n  async _restartService() {\n    if (this._backend)\n      this._backend.dispose();\n    const env = await Ndb.nodeProcessManager.env();\n    this._anotherTerminalHint(env);\n    this._backend = await Ndb.backend.createService(\n        'terminal.js',\n        rpc.handle(this),\n        env,\n        this._terminal.cols,\n        this._terminal.rows);\n  }\n\n  _anotherTerminalHint(env) {\n    this._terminal.writeln('# Want to use your own terminal? Copy paste following lines..');\n    this._terminal.writeln('');\n    this._terminal.writeln(Object.keys(env).map(k => `export ${k}='${env[k]}'`).join('\\n'));\n    this._terminal.writeln('');\n    this._terminal.writeln('# ..and after you can run any node program (e.g., npm run unit), ndb will detect it.');\n    this._terminal.writeln('');\n  }\n\n  /**\n   * @param {!Event} event\n   */\n  _onContextMenu(event) {\n    const selection = this._terminal ? this._terminal.getSelection() : null;\n    const contextMenu = new UI.ContextMenu(event);\n    const copyItem = contextMenu.defaultSection().appendItem(ls`Copy`, () => navigator.clipboard.writeText(selection));\n    copyItem.setEnabled(!!selection);\n    contextMenu.defaultSection().appendItem(ls`Paste`, async() => {\n      if (this._backend)\n        this._backend.write(await navigator.clipboard.readText());\n    });\n    contextMenu.show();\n  }\n\n  /**\n   * @param {string} error\n   */\n  async initFailed(error) {\n    this._terminal.write('# Builtin terminal is unvailable: ' + error.replace(/\\n/g, '\\n#'));\n    this._terminal.writeln('');\n  }\n\n  /**\n   * @param {string} data\n   */\n  dataAdded(data) {\n    if (data.startsWith('Debugger listening on') || data.startsWith('Debugger attached.') || data.startsWith('Waiting for the debugger to disconnect...'))\n      return;\n    this._terminal.write(data);\n  }\n\n  closed() {\n    this._restartService();\n  }\n\n  /**\n   * @param {!{cols: number, rows: number}} size\n   */\n  _sendResize(size) {\n    if (this._backend)\n      this._backend.resize(size.cols, size.rows);\n  }\n\n  /**\n   * @param {string} data\n   */\n  _sendData(data) {\n    if (this._backend)\n      this._backend.write(data);\n  }\n\n  onResize() {\n    this._terminal.fit();\n  }\n\n  _terminalData(event) {\n    this._terminal.write(event.data);\n  }\n\n  wasShown() {\n    if (this._init)\n      return;\n    this._init = true;\n    this._terminal.open(this.contentElement);\n    this._restartService();\n  }\n};\n"
  },
  {
    "path": "front_end/ndb_ui/module.json",
    "content": "{\n  \"extensions\": [\n      {\n          \"type\": \"view\",\n          \"category\": \"NDB\",\n          \"id\": \"ndb.runView\",\n          \"title\": \"NPM Scripts\",\n          \"persistence\": \"permanent\",\n          \"location\": \"run-view-sidebar\",\n          \"className\": \"Ndb.RunConfiguration\"\n      },\n      {\n          \"type\": \"view\",\n          \"location\": \"drawer-view\",\n          \"id\": \"ndb.terminal\",\n          \"title\": \"Terminal\",\n          \"persistence\": \"permanent\",\n          \"order\": 1,\n          \"className\": \"Ndb.Terminal\"\n      },\n      {\n          \"type\": \"view\",\n          \"id\": \"ndb.processes-view\",\n          \"title\": \"Node processes\",\n          \"persistence\": \"permanent\",\n          \"className\": \"Ndb.NodeProcesses\",\n          \"location\": \"sources.sidebar-top\"\n      }\n  ],\n  \"dependencies\": [\"ui\", \"sources\", \"timeline\", \"ndb\", \"xterm\"],\n  \"scripts\": [\n      \"RunConfiguration.js\",\n      \"NodeProcesses.js\",\n      \"Terminal.js\"\n  ],\n  \"resources\": [\n      \"runConfiguration.css\",\n      \"nodeProcesses.css\"\n  ]\n}\n"
  },
  {
    "path": "front_end/ndb_ui/nodeProcesses.css",
    "content": "/**\n * @license Copyright 2018 Google Inc. All Rights Reserved.\n * 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\n * 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.\n */\n\n.process-toolbar {\n  background-color: var(--toolbar-bg-color);\n  border-bottom: var(--divider-border);\n}\n\n.tree-outline li {\n  min-height: 20px;\n}\n\n.tree-outline li::before {\n  display: none;\n}\n\n.process-item {\n  width: 100%;\n  display: flex;\n  flex-wrap: wrap;\n}\n\n.process-item-state {\n  color: #888;\n  margin-left: auto;\n  padding: 0 10px 0 10px;\n}\n\n.controls-container {\n  display: flex;\n  flex-direction: row;\n  justify-content: flex-end;\n  align-items: stretch;\n  pointer-events: none;\n}\n\n.controls-gradient {\n    flex: 0 1 50px;\n}\n\nli:hover .controls-gradient {\n  background-image: linear-gradient(90deg, transparent, hsl(0, 0%, 96%));\n}\n\n.controls-buttons {\n  flex: none;\n  display: flex;\n  flex-direction: row;\n  align-items: center;\n  pointer-events: auto;\n  visibility: hidden;\n}\n\nli:hover .controls-buttons {\n  background-color: hsl(0, 0%, 96%);\n  visibility: visible;\n}\n"
  },
  {
    "path": "front_end/ndb_ui/runConfiguration.css",
    "content": "/**\n * @license Copyright 2018 Google Inc. All Rights Reserved.\n * 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\n * 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.\n */\n\n.list-item {\n  flex: none;\n  min-height: 30px;\n  display: flex;\n  align-items: center;\n  position: relative;\n  overflow: hidden;\n  padding-bottom: 3px;\n}\n\n.list-item:hover {\n  background: hsl(0, 0%, 96%);\n}\n\n.list-item {\n  border-top: 1px solid #efefef;\n}\n\n.configuration-item {\n  padding: 3px 5px 3px 5px;\n  height: 30px;\n  align-items: center;\n  position: relative;\n  flex: auto 1 0;\n  width: 100%;\n}\n\n.configuration-command {\n  color: #888;\n  margin-left: auto;\n  padding: 0 10px 0 10px;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n}\n\n.controls-container {\n  display: flex;\n  flex-direction: row;\n  justify-content: flex-end;\n  align-items: stretch;\n  pointer-events: none;\n}\n\n.controls-gradient {\n    flex: 0 1 50px;\n}\n\n.list-item:hover .controls-gradient {\n  background-image: linear-gradient(90deg, transparent, hsl(0, 0%, 96%));\n}\n\n.controls-buttons {\n  flex: none;\n  display: flex;\n  flex-direction: row;\n  align-items: center;\n  pointer-events: auto;\n  visibility: hidden;\n}\n\n.list-item:hover .controls-buttons {\n  background-color: hsl(0, 0%, 96%);\n  visibility: visible;\n}"
  },
  {
    "path": "front_end/xterm/module.json",
    "content": "{\n  \"scripts\": [\n      \"dist/xterm.js\",\n      \"dist/addons/fit/fit.js\"\n  ],\n  \"resources\": [\n      \"dist/xterm.css\"\n  ]\n}\n"
  },
  {
    "path": "lib/backend.js",
    "content": "/**\n * @license Copyright 2018 Google Inc. All Rights Reserved.\n * 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\n * 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.\n */\n\nconst { rpc_process } = require('carlo/rpc');\nconst fs = require('fs');\nconst path = require('path');\nconst readline = require('readline');\nconst util = require('util');\nconst { URL } = require('url');\nconst { Readable } = require('stream');\nconst opn = require('opn');\nconst querystring = require('querystring');\nconst which = require('which');\n\nconst fsReadFile = util.promisify(fs.readFile);\nconst { fileURLToPath } = require('./filepath_to_url.js');\n\nconst MODULE_WRAP_PREFIX = (() => {\n  const wrapped = require('module').wrap('☃');\n  return wrapped.substring(0, wrapped.indexOf('☃'));\n})();\n\nclass Backend {\n  constructor(window) {\n    this._window = window;\n    this._info = JSON.parse(Buffer.from(window.paramsForReuse().data, 'base64').toString('utf8'));\n    this._handles = [];\n    this._window.on('close', () => this._handles.splice(0).forEach(handle => handle.dispose()));\n  }\n\n  async createService(name, ...args) {\n    const fileName = path.join(__dirname, '..', 'services', name);\n    const handle = await rpc_process.spawn(fileName, ...args);\n    this._handles.push(handle);\n    return handle;\n  }\n\n  bringToFront() {\n    return this._window.bringToFront();\n  }\n\n  /**\n   * @param {text} url\n   */\n  openInNewTab(url) {\n    opn(url);\n  }\n\n  pkg() {\n    // TODO(ak239spb): implement it as decorations over package.json file.\n    try {\n      return require(path.join(fileURLToPath(this._info.cwd), 'package.json'));\n    } catch (e) {\n      return null;\n    }\n  }\n\n  async loadSourceMap(sourceMapURL, compiledURL) {\n    try {\n      let payload;\n      if (sourceMapURL.startsWith('data:')) {\n        const [metadata, ...other] = sourceMapURL.split(',');\n        const urlPayload = other.join(',');\n        const isBase64 = metadata.endsWith(';base64');\n        payload = JSON.parse(Buffer.from(isBase64 ? urlPayload : querystring.unescape(urlPayload), isBase64 ? 'base64' : 'utf8').toString('utf8'));\n      } else {\n        const fileURL = new URL(sourceMapURL);\n        const content = await fsReadFile(fileURL, 'utf8');\n        payload = JSON.parse(content);\n      }\n      await removeSourceContentIfMatch(sourceMapURL, compiledURL, payload);\n      return {payload};\n    } catch (e) {\n      return {error: e.stack};\n    }\n  }\n\n  getNodeScriptPrefix() {\n    return MODULE_WRAP_PREFIX;\n  }\n\n  which(command) {\n    return new Promise(resolve => which(command, (error, resolvedPath) => resolve({\n      resolvedPath: resolvedPath,\n      error: error ? error.message : null\n    })));\n  }\n\n  processInfo() {\n    return this._info;\n  }\n\n  writeTerminalData(stream, data) {\n    const buffer = Buffer.from(data, 'base64');\n    if (stream === 'stderr')\n      process.stderr.write(buffer);\n    else if (stream === 'stdout')\n      process.stdout.write(buffer);\n  }\n\n  fileURLToPath(url) {\n    return fileURLToPath(url);\n  }\n\n  async loadNetworkResource(url, headers) {\n    try {\n      if (url.startsWith('file://')) {\n        const fileURL = new URL(url);\n        return await fsReadFile(fileURL, 'utf8');\n      } else {\n        return null;\n      }\n    } catch (e) {\n      return null;\n    }\n  }\n}\n\nclass StringStream extends Readable {\n  constructor(str) {\n    super();\n    this._str = str;\n    this._ended = false;\n  }\n\n  _read() {\n    if (this._ended)\n      return;\n    this._ended = true;\n    process.nextTick(_ => {\n      this.push(Buffer.from(this._str, 'utf8'));\n      this.push(null);\n    });\n  }\n}\n\nasync function removeSourceContentIfMatch(sourceMapURL, compiledURL, payload) {\n  const {sourcesContent, sources} = payload;\n  if (!sourcesContent || !sources)\n    return;\n  for (let i = 0; i < sources.length; ++i) {\n    if (!sources[i] || !sourcesContent[i]) continue;\n    let url = sources[i];\n    if (!path.isAbsolute(url))\n      url = path.join(path.dirname(compiledURL), url);\n    if (!fs.existsSync(url))\n      continue;\n    const sourceContentStream = new StringStream(sourcesContent[i]);\n    const sourceContentLines = await readLines(sourceContentStream);\n    const fileStream = fs.createReadStream(url);\n    const fileStreamLines = await readLines(fileStream);\n    if (sourceContentLines.length === fileStreamLines.length) {\n      let equal = true;\n      for (let i = 0; i < sourceContentLines.length; ++i) {\n        if (sourceContentLines[i] !== fileStreamLines[i]) {\n          equal = false;\n          break;\n        }\n      }\n      if (equal)\n        sourcesContent[i] = undefined;\n    }\n  }\n}\n\nasync function readLines(stream) {\n  const rl = readline.createInterface({\n    input: stream,\n    crlfDelay: Infinity\n  });\n  return new Promise(resolve => {\n    stream.once('error', _ => resolve(null));\n    const lines = [];\n    rl.on('line', line => lines.push(line));\n    rl.on('close', _ => resolve(lines));\n  });\n}\n\nmodule.exports = { Backend };\n"
  },
  {
    "path": "lib/filepath_to_url.js",
    "content": "const url = require('url');\n\nif (url.pathToFileURL) {\n  module.exports = {\n    pathToFileURL: url.pathToFileURL,\n    fileURLToPath: url.fileURLToPath\n  };\n} else {\n  // Node 8 does not have nice url methods.\n  // Polyfill should match DevTools frontend behavior,\n  // otherwise breakpoints will not work.\n  function pathToFileURL(fileSystemPath) {\n    fileSystemPath = fileSystemPath.replace(/\\\\/g, '/');\n    if (!fileSystemPath.startsWith('file://')) {\n      if (fileSystemPath.startsWith('/'))\n        fileSystemPath = 'file://' + fileSystemPath;\n      else\n        fileSystemPath = 'file:///' + fileSystemPath;\n    }\n    return fileSystemPath;\n  }\n  /**\n   * @param {string} fileURL\n   * @return {string}\n   */\n  function fileURLToPath(fileURL) {\n    if (process.platform === 'win32')\n      return fileURL.substr('file:///'.length).replace(/\\//g, '\\\\');\n    return fileURL.substr('file://'.length);\n  }\n\n  module.exports = {\n    fileURLToPath,\n    pathToFileURL,\n  };\n}\n"
  },
  {
    "path": "lib/launcher.js",
    "content": "/**\n * @license Copyright 2018 Google Inc. All Rights Reserved.\n * 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\n * 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.\n */\n\nconst path = require('path');\nconst carlo = require('carlo');\nconst { rpc, rpc_process } = require('carlo/rpc');\nconst { pathToFileURL } = require('./filepath_to_url.js');\nconst { Backend } = require('./backend.js');\n\nprocess.on('unhandledRejection', error => {\n  if (error.message.includes('Protocol error') && error.message.includes('Target closed'))\n    process.exit(1);\n  console.log('unhandledRejection', error.stack || error.message);\n});\n\nasync function launch() {\n  let app;\n  const carloArgs = process.env.NDB_CARLO_ARGS ? JSON.parse(process.env.NDB_CARLO_ARGS) : {};\n  try {\n    app = await carlo.launch({\n      bgcolor: '#242424',\n      channel: ['chromium'],\n      paramsForReuse: {\n        data: Buffer.from(JSON.stringify({\n          cwd: pathToFileURL(process.cwd()).toString(),\n          argv: process.argv,\n          nodeExecPath: process.execPath\n        })).toString('base64')\n      },\n      ...carloArgs\n    });\n  } catch (e) {\n    if (e.message !== 'Could not start the browser or the browser was already running with the given profile.')\n      throw e;\n    process.exit(0);\n  }\n\n  process.title = 'ndb/main';\n  const appName = 'ndb';\n  const debugFrontend = !!process.env.NDB_DEBUG_FRONTEND;\n\n  app.setIcon(path.join(__dirname, '..', 'front_end', 'favicon.png'));\n  const overridesFolder = debugFrontend\n    ? path.dirname(require.resolve(`../front_end/${appName}.json`))\n    : path.join(__dirname, '..', '.local-frontend');\n  app.serveFolder(overridesFolder);\n  if (debugFrontend) {\n    try {\n      app.serveFolder(path.join(__dirname, '..', 'node_modules'));\n      app.serveFolder(path.dirname(require.resolve(`chrome-devtools-frontend/front_end/ndb_app.json`)));\n    } catch (e) {\n      console.log('To use NDB_DEBUG_FRONTEND=1 you should run npm install from ndb folder first');\n      process.exit(1);\n    }\n  }\n  app.on('exit', () => setTimeout(() => process.exit(0), 0));\n  app.on('window', load);\n  load(app.mainWindow());\n\n  async function load(window) {\n    const params = [\n      ['experiments', true],\n      ['debugFrontend', debugFrontend],\n      ['sources.hide_add_folder', true],\n      ['sources.hide_thread_sidebar', true],\n      ['timelineTracingJSProfileDisabled', true],\n      ['panel', 'sources']];\n    const paramString = params.reduce((acc, p) => acc += `${p[0]}=${p[1]}&`, '');\n    window.load(`${appName}.html?${paramString}`, rpc.handle(new Backend(window)));\n  }\n}\n\nmodule.exports = {launch};\n"
  },
  {
    "path": "lib/preload/ndb/preload.js",
    "content": "/**\n * @license Copyright 2018 Google Inc. All Rights Reserved.\n * 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\n * 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.\n */\n\n(function() {\n  if (!process.env.NDD_IPC)\n    return;\n  if (!process.env.NDD_PUBLISH_DATA) {\n    try {\n      if (!require('worker_threads').isMainThread)\n        return;\n    } catch (e) {\n      // node 8 does not support workers\n    }\n    const { pathToFileURL } = require('../../filepath_to_url.js');\n    let scriptName = '';\n    try {\n      scriptName = pathToFileURL(require.resolve(process.argv[1])).toString();\n    } catch (e) {\n      // preload can get scriptName iff node starts with script as first argument,\n      // we should be ready for exception in other cases, e.g., node -e '...'\n    }\n    const ppid = process.env.NDD_PPID;\n    process.env.NDD_PPID = process.pid;\n    if (!process.env.NDD_DATA)\n      process.env.NDD_DATA = process.pid + '_ndbId';\n    process.versions['ndb'] = '1.1.5';\n    const inspector = require('inspector');\n    inspector.open(0, undefined, false);\n    const info = {\n      cwd: pathToFileURL(process.cwd()),\n      argv: process.argv.concat(process.execArgv),\n      data: process.env.NDD_DATA,\n      ppid: ppid,\n      id: String(process.pid),\n      inspectorUrl: inspector.url(),\n      scriptName: scriptName\n    };\n    const {execFileSync} = require('child_process');\n    execFileSync(process.execPath, [__filename], {\n      env: {\n        NDD_IPC: process.env.NDD_IPC,\n        NDD_PUBLISH_DATA: JSON.stringify(info)\n      }\n    });\n  } else {\n    const net = require('net');\n    const TIMEOUT = 30000;\n    const socket = net.createConnection(process.env.NDD_IPC, () => {\n      socket.write(process.env.NDD_PUBLISH_DATA);\n      const timeoutId = setTimeout(() => socket.destroy(), TIMEOUT);\n      socket.on('data', () => {\n        clearTimeout(timeoutId);\n        socket.destroy();\n      });\n    });\n    socket.on('error', err => {\n      process.stderr.write('\\u001b[31mndb is not found:\\u001b[0m\\n');\n      process.stderr.write('please restart it and update env variables or unset NDD_IPC and NODE_OPTIONS.\\n');\n      process.exit(0);\n    });\n  }\n})();\n// eslint-disable-next-line spaced-comment\n//# sourceURL=internal/preload.js\n"
  },
  {
    "path": "lib/process_utility.js",
    "content": "/**\n * @license Copyright 2018 Google Inc. All Rights Reserved.\n * 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\n * 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.\n */\n\nmodule.exports = prepareProcess;\n\nfunction prepareProcess(name, disposeCallback) {\n  process.title = 'ndb/' + name;\n  function silentRpcErrors(error) {\n    if (!process.connected && error.code === 'ERR_IPC_CHANNEL_CLOSED')\n      return;\n    throw error;\n  }\n  process.on('uncaughtException', silentRpcErrors);\n  process.on('unhandledRejection', silentRpcErrors);\n  // dispose when child process is disconnected\n  process.on('disconnect', () => disposeCallback());\n}\n"
  },
  {
    "path": "ndb.js",
    "content": "#!/usr/bin/env node\n/**\n * @license Copyright 2018 Google Inc. All Rights Reserved.\n * 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\n * 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.\n */\n\nrequire('update-notifier')({pkg: require('./package.json')}).notify({isGlobal: true});\n\nif (process.argv.length > 2 && (process.argv[2] === '-v' || process.argv[2] === '--version')) {\n  console.log(`v${require('./package.json').version}`);\n  process.exit(0);\n}\n\nif (process.argv.length > 2 && process.argv[2] === '--help') {\n  console.log('Usage:');\n  console.log('');\n  console.log('Use ndb instead of node command:');\n  console.log('\\tndb server.js');\n  console.log('\\tndb node server.js');\n  console.log('');\n  console.log('Prepend ndb in front of any other binary:');\n  console.log('\\tndb npm run unit');\n  console.log('\\tndb mocha');\n  console.log('\\tndb npx mocha');\n  console.log('');\n  console.log('Launch ndb as a standalone application:');\n  console.log('\\tndb .');\n  console.log('');\n  console.log('More information is available at https://github.com/GoogleChromeLabs/ndb#readme');\n  process.exit(0);\n}\n\nconst {launch} = require('./lib/launcher.js');\nlaunch();\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"ndb\",\n  \"version\": \"1.1.5\",\n  \"description\": \"Chrome DevTools for Node.js\",\n  \"main\": \"ndb.js\",\n  \"repository\": \"github:GoogleChromeLabs/ndb\",\n  \"homepage\": \"https://github.com/GoogleChromeLabs/ndb#readme\",\n  \"engines\": {\n    \"node\": \">=8.0.0\"\n  },\n  \"bin\": {\n    \"ndb\": \"./ndb.js\"\n  },\n  \"scripts\": {\n    \"lint\": \"./node_modules/.bin/eslint .\",\n    \"prepare\": \"node build.js\",\n    \"test\": \"npm run lint\",\n    \"version\": \"node version.js && git add ./lib/preload/ndb/preload.js\"\n  },\n  \"author\": \"The Chromium Authors\",\n  \"license\": \"Apache-2.0\",\n  \"dependencies\": {\n    \"carlo\": \"^0.9.46\",\n    \"chokidar\": \"^3.0.2\",\n    \"debug\": \"^4.1.1\",\n    \"isbinaryfile\": \"^3.0.3\",\n    \"mime\": \"^2.4.4\",\n    \"opn\": \"^5.5.0\",\n    \"update-notifier\": \"^2.5.0\",\n    \"which\": \"^1.3.1\",\n    \"ws\": \"^6.2.1\",\n    \"xterm\": \"^3.14.5\"\n  },\n  \"optionalDependencies\": {\n    \"node-pty\": \"^0.9.0-beta18\"\n  },\n  \"devDependencies\": {\n    \"chrome-devtools-frontend\": \"1.0.672485\",\n    \"eslint\": \"^5.16.0\",\n    \"mocha\": \"^5.2.0\",\n    \"rimraf\": \"^2.6.3\",\n    \"terser\": \"^3.17.0\"\n  }\n}\n"
  },
  {
    "path": "scripts/builder.js",
    "content": "const fs = {\n  ...require('fs'),\n  ...require('fs').promises\n};\nconst os = require('os');\nconst path = require('path');\n\n/**\n * @typedef {Object} AppDescriptor\n * @property {string} name\n * @property {!Array<!Object>} modules\n * @property {boolean} hasHtml\n * @property {?string} extends\n */\n\n/**\n * @typedef {Object} RawModuleDescriptor\n * @property {!Array} dependencies\n * @property {!Array} scripts\n * @property {!Array} resources\n * @property {!Array} extensions\n * @property {string} experiment\n */\n\n/**\n * @typedef {Object} ModuleDescriptor\n * @property {string} content\n * @property {!Array=} extensions\n * @property {!Array=} dependencies\n * @property {string=} experiment\n */\n\n/**\n * @param {!Array<string>} appNames\n * @param {!Array<string>} pathFolders\n * @return {!Promise<!Map<string, !AppDescriptor>>}\n */\nasync function loadAppDescriptors(appNames, pathFolders) {\n  const descriptors = new Map();\n  const descriptorQueue = appNames.slice(0);\n  while (descriptorQueue.length) {\n    const name = descriptorQueue.shift();\n    if (descriptors.has(name))\n      continue;\n    const source = await loadSource(pathFolders, name + '.json');\n    const content = JSON.parse(source);\n    const descriptor = {\n      name: name,\n      modules: content.modules || [],\n      hasHtml: content.has_html || false,\n    };\n    if (content.extends) {\n      descriptor.extends = content.extends;\n      descriptorQueue.push(descriptor.extends);\n    }\n    descriptors.set(name, descriptor);\n  }\n  return descriptors;\n}\n\n/**\n * @param {string} moduleName\n * @return {string}\n */\nfunction moduleNamespace(moduleName) {\n  const specialCaseNameSpaces = {\n    'sdk': 'SDK',\n    'js_sdk': 'JSSDK',\n    'browser_sdk': 'BrowserSDK',\n    'ui': 'UI',\n    'object_ui': 'ObjectUI',\n    'perf_ui': 'PerfUI',\n    'har_importer': 'HARImporter',\n    'sdk_test_runner': 'SDKTestRunner',\n    'cpu_profiler_test_runner': 'CPUProfilerTestRunner'\n  };\n  return moduleName in specialCaseNameSpaces\n    ? specialCaseNameSpaces[moduleName]\n    : moduleName.split('_').map(name => name.charAt(0).toUpperCase() + name.substr(1)).join('');\n}\n\n/**\n * @param {!Map<string, !AppDescriptor>} appDescriptors\n * @param {!Array<string>} pathFolders\n * @return {!Promise<!Map<string, !ModuleDescriptor>>}\n */\nasync function loadModules(appDescriptors, pathFolders, customLoadModuleSource) {\n  const modules = new Map();\n  appDescriptors.forEach(descriptor => descriptor.modules.forEach(module => modules.set(module.name, null)));\n  await Promise.all(Array.from(modules).map(async([moduleName, module]) => {\n    modules.set(moduleName, await loadModule(pathFolders, moduleName, customLoadModuleSource));\n  }));\n  return modules;\n}\n\n/**\n * @param {!Array<string>} pathFolders\n * @param {string} moduleName\n * @param {!function(!Object):!Promise<string>} customLoadModuleSource\n */\nasync function loadModule(pathFolders, moduleName, customLoadModuleSource) {\n  const { descriptor: rawDescriptor, paths } = await loadRawModule(pathFolders, moduleName, 'module.json');\n\n  let scriptContent = await customLoadModuleSource(rawDescriptor, paths);\n  const promises = [];\n  if (scriptContent === null) {\n    scriptContent = '';\n    promises.push(...(rawDescriptor.scripts || []).map(name => loadSource(pathFolders, moduleName, name)));\n  }\n  promises.push(...(rawDescriptor.resources || []).map(name => loadResource(pathFolders, moduleName, name)));\n  scriptContent += (await Promise.all(promises)).join('\\n');\n\n  const namespace = moduleNamespace(moduleName);\n  const content = `self['${namespace}'] = self['${namespace}'] || {};\\n${scriptContent}\\n`;\n\n  const descriptor = { content };\n  if (rawDescriptor.extensions)\n    descriptor.extensions = rawDescriptor.extensions;\n  if (rawDescriptor.dependencies)\n    descriptor.dependencies = rawDescriptor.dependencies;\n  if (rawDescriptor.experiment)\n    descriptor.experiment = rawDescriptor.experiment;\n  return descriptor;\n\n  /**\n   * @param {!Array<string>} pathFolders\n   * @param {string} moduleName\n   * @param {string} name\n   */\n  async function loadResource(pathFolders, moduleName, name) {\n    const resource = await loadSource(pathFolders, moduleName, name);\n    const content = resource.replace(/\\\\/g, '\\\\\\\\').replace(/\\n/g, '\\\\n').replace(/'/g, '\\\\\\'');\n    return `Runtime.cachedResources['${moduleName}/${name}'] = '${content}';`;\n  }\n}\n\n/**\n * @param {!Map<string, !ModuleDescriptor>} moduleDescriptors\n * @return {!Promise<!Array<!{content: !Buffer, name: string}>>}\n */\nasync function loadImages(moduleDescriptors, pathFolders) {\n  const images = [];\n  const re = /Images\\/[\\w.-]+/g;\n  for (const [, module] of moduleDescriptors) {\n    const m = module.content.match(re);\n    if (m)\n      images.push(...m);\n  }\n  return Promise.all(Array.from(new Set(images)).map(async image => ({\n    content: await fs.readFile(lookupFile(pathFolders, image)[0]),\n    name: image\n  })));\n}\n\n/**\n * @params {!Array<string>} pathFolders\n * @param {!Array<string>} fileNameParts\n * @return {!Array<string>}\n */\nfunction lookupFile(pathFolders, ...fileNameParts) {\n  const paths = [];\n  for (const pathFolder of pathFolders) {\n    const absoluteFileName = path.join(pathFolder, ...fileNameParts);\n    if (fs.existsSync(absoluteFileName))\n      paths.push(absoluteFileName);\n  }\n  if (paths.length === 0)\n    console.error(`File ${fileNameParts.join(path.sep)} not found in ${pathFolders}`);\n  return paths;\n}\n\n/**\n * @params {!Array<string>} pathFolders\n * @param {!Array<string>} fileNameParts\n * @return {!Promise<string>}\n */\nasync function loadSource(pathFolders, ...fileNameParts) {\n  const paths = lookupFile(pathFolders, ...fileNameParts).reverse();\n  return (await Promise.all(paths.map(name => fs.readFile(name, 'utf8')))).join('\\n');\n}\n\n/**\n * @params {!Array<string>} pathFolders\n * @param {!Array<string>} fileNameParts\n * @return {!Promise<!{descriptor: !RawModuleDescriptor, paths: !Array<string>}>}\n */\nasync function loadRawModule(pathFolders, ...fileNameParts) {\n  const paths = lookupFile(pathFolders, ...fileNameParts);\n  const sources = await Promise.all(paths.map(name => fs.readFile(name, 'utf8')));\n  if (paths.length > 1)\n    console.error('Module ' + fileNameParts[0] + ' overriden');\n  const descriptors = sources.map(data => JSON.parse(data)).reverse();\n  const descriptor = {\n    dependencies: [],\n    scripts: [],\n    resources: [],\n    extensions: [],\n    experiment: '',\n    ...descriptors[0]\n  };\n  return { descriptor, paths: [path[0]] };\n}\n\n/**\n * @param {!Array<string>} appNames\n * @param {!Array<string>} pathFolders\n * @param {string} outFolder\n * @param {function(string):string=} minifyJS\n * @return {!Promise}\n */\nasync function buildApp(appNames, pathFolders, outFolder, minifyJS = code => code, customLoadModuleSource = descriptor => Promise.resolve(null)) {\n  const descriptors = await loadAppDescriptors(appNames, pathFolders);\n  const modules = await loadModules(descriptors, pathFolders, customLoadModuleSource);\n  const fetchedImages = await loadImages(modules, pathFolders);\n  const runtime = await loadSource(pathFolders, 'Runtime.js');\n\n  const builtApps = [];\n  const notAutoStartModules = new Set();\n  for (const appName of appNames) {\n    const appDescriptor = { modules: [], hasHtml: false };\n    let current = descriptors.get(appName);\n    while (current) {\n      appDescriptor.modules.push(...current.modules);\n      appDescriptor.hasHtml = appDescriptor.hasHtml || current.hasHtml;\n      current = current.extends ? descriptors.get(current.extends) : null;\n    }\n\n    const moduleDescriptors = appDescriptor.modules.map(module => {\n      const moduleName = module.name;\n      const moduleDescriptor = modules.get(module.name);\n      const descriptor = { name: moduleName, remote: false };\n      if (module.type !== 'autostart')\n        descriptor.scripts = [`${moduleName}_module.js`];\n      if (moduleDescriptor.extensions)\n        descriptor.extensions = moduleDescriptor.extensions;\n      if (moduleDescriptor.dependencies)\n        descriptor.dependencies = moduleDescriptor.dependencies;\n      if (moduleDescriptor.experiment)\n        descriptor.experiment = moduleDescriptor.experiment;\n      return descriptor;\n    });\n\n    const autoStartModulesByName = new Map();\n    appDescriptor.modules.map(module => {\n      if (module.type === 'autostart')\n        autoStartModulesByName.set(module.name, module);\n      else\n        notAutoStartModules.add(module.name);\n    });\n\n    const appScript = await loadSource(pathFolders, appName + '.js');\n\n    let scriptContent = '';\n    scriptContent += '/* Runtime.js */\\n' + runtime + '\\n';\n    scriptContent += `allDescriptors.push(...${JSON.stringify(moduleDescriptors)});\\n`;\n    scriptContent += `applicationDescriptor = ${JSON.stringify(appDescriptor)};\\n`;\n    scriptContent += appScript;\n    const visitedModule = new Set();\n    for (const [, module] of autoStartModulesByName)\n      scriptContent += writeModule(modules, module, autoStartModulesByName, visitedModule);\n\n    let htmlContent = '';\n    if (appDescriptor.hasHtml) {\n      const content = await loadSource(pathFolders, appName + '.html');\n      htmlContent = content.replace(/<script.*?src=\"Runtime.js\"><\\/script>/, '<!-- <script src=\"Runtime.js\"></script> -->');\n    }\n\n    builtApps.push({\n      scriptContent,\n      htmlContent,\n      name: appName\n    });\n\n    function writeModule(modules, module, autoStartModulesByName, visitedModule) {\n      if (visitedModule.has(module.name))\n        return '';\n      visitedModule.add(module.name);\n      const builtModule = modules.get(module.name);\n      let content = '';\n      for (const dep of builtModule.dependencies) {\n        const depModule = autoStartModulesByName.get(dep);\n        if (!depModule)\n          console.error(`Autostart module ${module.name} depends on not autostart module ${dep}`);\n        content += writeModule(modules, depModule, autoStartModulesByName, visitedModule);\n      }\n      return content + builtModule.content;\n    }\n  }\n\n  const favicon = await fs.readFile(lookupFile(pathFolders, 'favicon.png')[0]);\n\n  const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'devtools-frontend-'));\n  const promises = [];\n  for (const app of builtApps) {\n    promises.push(fs.writeFile(path.join(tmpDir, app.name + '.js'), await minifyJS(app.scriptContent)));\n    if (app.htmlContent)\n      promises.push(fs.writeFile(path.join(tmpDir, app.name + '.html'), app.htmlContent));\n  }\n\n  promises.push(...Array.from(notAutoStartModules).map(async moduleName => {\n    await fs.mkdir(path.join(tmpDir, moduleName));\n    return fs.writeFile(path.join(tmpDir, moduleName, moduleName + '_module.js'), await minifyJS(modules.get(moduleName).content));\n  }));\n\n  const createImageFolder = fs.mkdir(path.join(tmpDir, 'Images'));\n  promises.push(...fetchedImages.map(async image => {\n    await createImageFolder;\n    if (image.content)\n      return fs.writeFile(path.join(tmpDir, 'Images', image.name.substr('Images/'.length)), image.content);\n  }));\n  if (favicon)\n    promises.push(fs.writeFile(path.join(tmpDir, 'favicon.png'), favicon));\n  await Promise.all(promises);\n\n  await fs.rename(tmpDir, outFolder);\n}\n\nmodule.exports = { buildApp };\n"
  },
  {
    "path": "services/file_system.js",
    "content": "const fs = require('fs');\n\nconst { rpc, rpc_process } = require('carlo/rpc');\nconst chokidar = require('chokidar');\nconst {pathToFileURL, fileURLToPath} = require('../lib/filepath_to_url.js');\n\nclass FileSystemHandler {\n  constructor() {\n    require('../lib/process_utility.js')('file_system', () => this.dispose());\n    this._watcher = null;\n    this._embedderPath = '';\n    this._client = null;\n  }\n\n  startWatcher(embedderPath, exludePattern, client, mainFileName) {\n    this._embedderPath = fileURLToPath(embedderPath);\n    this._client = client;\n    this._watcher = chokidar.watch([this._embedderPath], {\n      ignored: new RegExp(exludePattern),\n      awaitWriteFinish: true,\n      ignorePermissionErrors: true\n    });\n    const events = [];\n    this._watcher.on('all', (event, name) => {\n      if (event === 'add' || event === 'change' || event === 'unlink') {\n        if (!events.length)\n          setTimeout(() => client.filesChanged(events.splice(0)), 100);\n        events.push({\n          type: event,\n          name: pathToFileURL(name).toString()\n        });\n      }\n    });\n    this._watcher.on('error', console.error);\n  }\n\n  forceFileLoad(fileNameURL) {\n    const fileName = fileURLToPath(fileNameURL);\n    if (fileName.startsWith(this._embedderPath) && fs.existsSync(fileName))\n      this._client.filesChanged([{type: 'add', name: fileNameURL}]);\n  }\n\n  dispose() {\n    this._watcher.close();\n    Promise.resolve().then(() => process.exit(0));\n  }\n}\n\nrpc_process.init(args => rpc.handle(new FileSystemHandler()));\n"
  },
  {
    "path": "services/file_system_io.js",
    "content": "const { rpc, rpc_process } = require('carlo/rpc');\nconst fs = require('fs');\nconst { URL } = require('url');\n\nclass FileSystemIO {\n  constructor() {\n    require('../lib/process_utility.js')('file_system_io', () => this.dispose());\n  }\n\n  /**\n   * @param {string} fileURL\n   * @param {string} encoding\n   */\n  readFile(fileURL, encoding) {\n    return fs.readFileSync(new URL(fileURL), encoding);\n  }\n\n  /**\n   * @param {string} fileURL\n   * @param {string} content\n   * @param {string} encoding\n   */\n  writeFile(fileURL, content, encoding) {\n    if (encoding === 'base64')\n      content = Buffer.from(content, 'base64');\n    fs.writeFileSync(new URL(fileURL), content, {encoding: encoding});\n  }\n\n  /**\n   * @param {string} folderURL\n   */\n  createFile(folderURL) {\n    let name = 'NewFile';\n    let counter = 1;\n    while (fs.existsSync(new URL(folderURL + '/' + name))) {\n      name = 'NewFile' + counter;\n      ++counter;\n    }\n    fs.writeFileSync(new URL(folderURL + '/' + name), '');\n    return folderURL + '/' + name;\n  }\n\n  /**\n   * @param {string} fileURL\n   */\n  deleteFile(fileURL) {\n    try {\n      fs.unlinkSync(new URL(fileURL));\n      return true;\n    } catch (e) {\n      return false;\n    }\n  }\n\n  /**\n   * @param {string} fileURL\n   * @param {string} newName\n   */\n  renameFile(fileURL, newName) {\n    const newURL = new URL(fileURL.substr(0, fileURL.lastIndexOf('/') + 1) + newName);\n    try {\n      if (fs.existsSync(newURL)) return false;\n      fs.renameSync(new URL(fileURL), newURL);\n      return true;\n    } catch (e) {\n      return false;\n    }\n  }\n\n  dispose() {\n    Promise.resolve().then(() => process.exit(0));\n  }\n}\n\nrpc_process.init(args => rpc.handle(new FileSystemIO()));\n"
  },
  {
    "path": "services/ndd_service.js",
    "content": "/**\n * @license Copyright 2018 Google Inc. All Rights Reserved.\n * 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\n * 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.\n */\n\nconst { spawn } = require('child_process');\nconst os = require('os');\nconst path = require('path');\nconst net = require('net');\nconst { fileURLToPath } = require('../lib/filepath_to_url.js');\n\nconst protocolDebug = require('debug')('ndd_service:protocol');\nconst caughtErrorDebug = require('debug', 'ndd_service:caught');\nconst { rpc, rpc_process } = require('carlo/rpc');\nconst WebSocket = require('ws');\n\nfunction silentRpcErrors(error) {\n  if (!process.connected && error.code === 'ERR_IPC_CHANNEL_CLOSED')\n    return;\n  throw error;\n}\n\nprocess.on('uncaughtException', silentRpcErrors);\nprocess.on('unhandledRejection', silentRpcErrors);\n\nconst DebugState = {\n  WS_OPEN: 1,\n  WS_ERROR: 2,\n  WS_CLOSE: 3,\n  PROCESS_DISCONNECT: 4\n};\n\nconst CALL_EXIT_MESSAGE = JSON.stringify({\n  id: -1,\n  method: 'Runtime.evaluate',\n  params: { expression: 'process.exit(-1)' }\n});\n\nclass Channel {\n  /**\n   * @param {!WebSocket} ws\n   */\n  constructor(ws) {\n    this._ws = ws;\n    this._handler = null;\n    this._messageListener = this._messageReceived.bind(this);\n    this._ws.on('message', this._messageListener);\n  }\n\n  /**\n   * @param {string} message\n   */\n  send(message) {\n    if (this._ws.readyState === WebSocket.OPEN) {\n      protocolDebug('>', message);\n      this._ws.send(message);\n    }\n  }\n\n  close() {\n    this._ws.close();\n  }\n\n  /**\n   * @param {!Object}\n   */\n  listen(handler) {\n    this._handler = handler;\n  }\n\n  dispose() {\n    this._ws.removeListener('message', this._messageListener);\n  }\n\n  /**\n   * @param {string} message\n   */\n  _messageReceived(message) {\n    if (this._handler) {\n      protocolDebug('<', message);\n      this._handler.dispatchMessage(message);\n    }\n  }\n}\n\nclass NddService {\n  constructor(frontend) {\n    process.title = 'ndb/ndd_service';\n    this._disconnectPromise = new Promise(resolve => process.once('disconnect', () => resolve(DebugState.PROCESS_DISCONNECT)));\n    this._connected = new Set();\n    this._frontend = frontend;\n\n    const pipePrefix = process.platform === 'win32' ? '\\\\\\\\.\\\\pipe\\\\' : os.tmpdir();\n    const pipeName = `node-ndb.${process.pid}.sock`;\n    this._pipe = path.join(pipePrefix, pipeName);\n    const server = net.createServer(socket => {\n      socket.on('data', async d => {\n        const runSession = await this._startSession(JSON.parse(d), frontend);\n        socket.write('run');\n        runSession();\n      });\n      socket.on('error', e => caughtErrorDebug(e));\n    }).listen(this._pipe);\n    server.unref();\n  }\n\n  dispose() {\n    process.disconnect();\n  }\n\n  async _startSession(info, frontend) {\n    const ws = new WebSocket(info.inspectorUrl);\n    const openPromise = new Promise(resolve => ws.once('open', () => resolve(DebugState.WS_OPEN)));\n    const errorPromise = new Promise(resolve => ws.once('error', () => resolve(DebugState.WS_ERROR)));\n    const closePromise = new Promise(resolve => ws.once('close', () => resolve(DebugState.WS_CLOSE)));\n    let state = await Promise.race([openPromise, errorPromise, closePromise, this._disconnectPromise]);\n    if (state === DebugState.WS_OPEN) {\n      this._connected.add(info.id);\n      const channel = new Channel(ws);\n      state = await Promise.race([frontend.detected(info, rpc.handle(channel)), this._disconnectPromise]);\n      return async() => {\n        if (state !== DebugState.PROCESS_DISCONNECT)\n          state = await Promise.race([closePromise, errorPromise, this._disconnectPromise]);\n        channel.dispose();\n        this._connected.delete(info.id);\n        if (state !== DebugState.PROCESS_DISCONNECT)\n          frontend.disconnected(info.id);\n        else\n          ws.send(CALL_EXIT_MESSAGE, () => ws.close());\n      };\n    } else {\n      return async function() {};\n    }\n  }\n\n  env() {\n    return {\n      NODE_OPTIONS: `--require ndb/preload.js`,\n      NODE_PATH: `${process.env.NODE_PATH || ''}${path.delimiter}${path.join(__dirname, '..', 'lib', 'preload')}`,\n      NDD_IPC: this._pipe\n    };\n  }\n\n  async debug(execPath, args, options) {\n    const env = this.env();\n    if (options.data)\n      env.NDD_DATA = options.data;\n    const p = spawn(execPath, args, {\n      cwd: options.cwd ? fileURLToPath(options.cwd) : undefined,\n      env: { ...process.env, ...env },\n      stdio: options.ignoreOutput ? 'ignore' : ['inherit', 'pipe', 'pipe'],\n      windowsHide: true\n    });\n    if (!options.ignoreOutput) {\n      p.stderr.on('data', data => {\n        if (process.connected)\n          this._frontend.terminalData('stderr', data.toString('base64'));\n      });\n      p.stdout.on('data', data => {\n        if (process.connected)\n          this._frontend.terminalData('stdout', data.toString('base64'));\n      });\n    }\n    const finishPromise = new Promise(resolve => {\n      p.once('exit', resolve);\n      p.once('error', resolve);\n    });\n    const result = await Promise.race([finishPromise, this._disconnectPromise]);\n    if (result === DebugState.PROCESS_DISCONNECT && !this._connected.has(p.pid)) {\n      // The frontend can start the process but disconnects before it is\n      // finished if it is blackboxed (e.g., npm process); in this case, we need\n      // to kill this process here.\n      p.kill();\n    }\n  }\n}\n\nrpc_process.init(frontend => rpc.handle(new NddService(frontend)));\n"
  },
  {
    "path": "services/search.js",
    "content": "/**\n * @license Copyright 2018 Google Inc. All Rights Reserved.\n * 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\n * 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.\n */\n\nconst { rpc, rpc_process } = require('carlo/rpc');\nconst fs = require('fs');\nconst path = require('path');\n\nconst { fileURLToPath, pathToFileURL } = require('../lib/filepath_to_url.js');\n\nconst isbinaryfile = require('isbinaryfile');\n\n// TODO(ak239): track changed files.\n\nclass SearchBackend {\n  constructor(frontend) {\n    require('../lib/process_utility.js')('search', () => this.dispose());\n    this._frontend = frontend;\n    this._activeIndexing = new Set();\n    this._index = new Map();\n    this._filesQueue = new Set();\n\n    this._lastFileNameIndex = 0;\n    this._indexToFileName = new Map();\n    this._fileNameToIndex = new Map();\n  }\n\n  /**\n   * @param {number} requestId\n   * @param {string} fileSystemPath\n   */\n  async indexPath(requestId, fileSystemPath, excludedPattern) {\n    fileSystemPath = fileURLToPath(fileSystemPath);\n    const excludeRegex = new RegExp(excludedPattern);\n    if (this._index.has(fileSystemPath)) {\n      this._indexChangedFiles(requestId, fileSystemPath);\n      return;\n    }\n    this._activeIndexing.add(requestId);\n    const index = new Map();\n    const directories = [fileSystemPath];\n    const allFiles = [];\n    while (directories.length) {\n      if (!this._activeIndexing.has(requestId))\n        return;\n      const directory = directories.shift();\n      await new Promise(done => fs.readdir(directory, 'utf8', async(err, files) => {\n        if (err) {\n          done();\n          return;\n        }\n        files = files.filter(file => !file.startsWith('.'));\n        files = files.map(file => path.join(directory, file));\n        await Promise.all(files.map(file => new Promise(done => fs.stat(file, (err, stats) => {\n          if (err) {\n            done();\n            return;\n          }\n          const relativeName = path.relative(fileSystemPath, file);\n          const testName = `/${relativeName}${stats.isDirectory() ? '/' : ''}`.replace(/\\\\/g, '/');\n          if (excludeRegex && excludeRegex.test(testName)) {\n            done();\n            return;\n          }\n          if (stats.isDirectory())\n            directories.push(file);\n          if (stats.isFile())\n            allFiles.push(file);\n          done();\n        }))));\n        done();\n      }));\n    }\n\n    const textFiles = [];\n    for (const file of allFiles) {\n      if (file.endsWith('.js') || file.endsWith('.json'))\n        textFiles.push(file);\n      else if (!await new Promise(resolve => isbinaryfile(file, (err, isBinary) => resolve(err || isBinary))))\n        textFiles.push(file);\n    }\n    this._frontend.indexingTotalWorkCalculated(requestId, fileSystemPath, textFiles.length);\n    for (const fileName of textFiles) {\n      if (!this._activeIndexing.has(requestId))\n        return;\n      await this._indexFile(fileName, index);\n      this._frontend.indexingWorked(requestId, fileSystemPath, 1);\n    }\n    this._index.set(fileSystemPath, index);\n    this._frontend.indexingDone(requestId, fileSystemPath);\n    this._activeIndexing.delete(requestId);\n  }\n\n  /**\n   * @param {string} fileName\n   * @param {!Map<string,!Set<string>>} index\n   * @return {!Promise}\n   */\n  _indexFile(fileName, index) {\n    const stream = fs.createReadStream(fileName, {encoding: 'utf8'});\n    return new Promise(done => {\n      let prev = '';\n      const trigrams = new Set();\n      stream.on('error', finished.bind(this));\n      stream.on('data', chunk => {\n        chunk = prev + chunk;\n        chunk = chunk.toLowerCase();\n        while (chunk.length > 3) {\n          trigrams.add(chunk.substring(0, 3));\n          chunk = chunk.substring(1);\n        }\n        prev = chunk;\n      });\n      stream.on('end', finished.bind(this));\n\n      function finished() {\n        let fileNameIndex;\n        if (this._fileNameToIndex.has(fileName)) {\n          fileNameIndex = this._fileNameToIndex.get(fileName);\n        } else {\n          fileNameIndex = ++this._lastFileNameIndex;\n          this._indexToFileName.set(fileNameIndex, fileName);\n          this._fileNameToIndex.set(fileName, fileNameIndex);\n        }\n        for (const trigram of trigrams) {\n          let values = index.get(trigram);\n          if (!values) {\n            values = new Set();\n            index.set(trigram, values);\n          }\n          values.add(fileNameIndex);\n        }\n        done();\n      }\n    }).then(() => stream.close());\n  }\n\n  /**\n   * @param {number} requestId\n   * @param {string} fileSystemPath\n   */\n  async _indexChangedFiles(requestId, fileSystemPath) {\n    if (!this._filesQueue.size) {\n      this._frontend.indexingDone(requestId);\n      return;\n    }\n    this._activeIndexing.add(requestId);\n\n    const allFiles = Array.from(this._filesQueue);\n    this._filesQueue.clear();\n\n    const textFiles = [];\n    for (const file of allFiles) {\n      if (file.endsWith('.js') || file.endsWith('.json'))\n        textFiles.push(file);\n      else if (!await new Promise(resolve => isbinaryfile(file, (err, isBinary) => resolve(err || isBinary))))\n        textFiles.push(file);\n    }\n\n    this._frontend.indexingTotalWorkCalculated(requestId, textFiles.length);\n    const index = this._index.get(fileSystemPath);\n    while (textFiles.length) {\n      if (!this._activeIndexing.has(requestId)) {\n        for (const fileName of textFiles)\n          this._filesQueue.add(fileName);\n        return;\n      }\n      const fileName = textFiles.shift();\n      await this._indexFile(fileName, index);\n      this._frontend.indexingWorked(requestId, 1);\n    }\n    this._activeIndexing.delete(requestId);\n    this._frontend.indexingDone(requestId);\n  }\n\n  /**\n   * @param {number} requestId\n   */\n  stopIndexing(requestId) {\n    this._activeIndexing.delete(requestId);\n  }\n\n  /**\n   * @param {number} requestId\n   * @param {string} fileSystemPath\n   * @param {string} query\n   */\n  searchInPath(requestId, fileSystemPath, query) {\n    fileSystemPath = fileURLToPath(fileSystemPath);\n    const index = this._index.get(fileSystemPath);\n    let result = [];\n    query = query.toLowerCase();\n    if (index && query.length === 0) {\n      result = Array.from(new Set(index.values()));\n    } else if (index && query.length > 2) {\n      let resultSet = index.get(query.substring(0, 3)) || new Set();\n      for (let i = 1; i < query.length - 2; ++i) {\n        const trigram = query.substring(i, i + 3);\n        const current = index.get(trigram) || new Set();\n        const nextCurrent = new Set();\n        for (const file of current) {\n          if (resultSet.has(file))\n            nextCurrent.add(file);\n        }\n        resultSet = nextCurrent;\n      }\n      result = Array.from(resultSet);\n    }\n    result = result.map(index => this._indexToFileName.get(index));\n    result = result.map(result => pathToFileURL(result).toString());\n    this._frontend.searchCompleted(requestId, fileSystemPath, result);\n  }\n\n  dispose() {\n    Promise.resolve().then(() => process.exit(0));\n  }\n}\n\nrpc_process.init(frontend => rpc.handle(new SearchBackend(frontend)));\n"
  },
  {
    "path": "services/terminal.js",
    "content": "/**\n * @license Copyright 2018 Google Inc. All Rights Reserved.\n * 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\n * 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.\n */\n\nconst fs = require('fs');\nconst { rpc, rpc_process } = require('carlo/rpc');\n\nclass Terminal {\n  constructor(frontend, pty, env, cols, rows) {\n    require('../lib/process_utility.js')('terminal', () => this.dispose());\n    let shell = process.env.SHELL;\n    if (!shell || !fs.existsSync(shell))\n      shell = process.platform === 'win32' ? 'cmd.exe' : 'bash';\n    this._term = pty.spawn(shell, [], {\n      name: 'xterm-color',\n      cols: cols,\n      rows: rows,\n      cwd: process.cwd(),\n      env: {\n        ...process.env,\n        ...env\n      }\n    });\n    this._term.on('data', data => frontend.dataAdded(data));\n    this._term.on('close', () => frontend.closed());\n  }\n\n  dispose() {\n    Promise.resolve().then(() => process.exit(0));\n  }\n\n  resize(cols, rows) {\n    this._term.resize(cols, rows);\n  }\n\n  write(data) {\n    this._term.write(data);\n  }\n}\n\nasync function init(frontend, env, cols, rows) {\n  try {\n    const pty = require('node-pty');\n    return rpc.handle(new Terminal(frontend, pty, env, cols, rows));\n  } catch (e) {\n    await frontend.initFailed(e.stack);\n    setImmediate(() => process.exit(0));\n    return null;\n  }\n}\n\nrpc_process.init(init);\n"
  },
  {
    "path": "test/assets/test-project/index.js",
    "content": "/**\n * @license Copyright 2018 Google Inc. All Rights Reserved.\n * 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\n * 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.\n */\n\nconsole.log(42);\n"
  },
  {
    "path": "test/assets/test-project/index.mjs",
    "content": "/**\n * @license Copyright 2018 Google Inc. All Rights Reserved.\n * 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\n * 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.\n */\n\n  console.log(239);\n"
  },
  {
    "path": "test/assets/test-project/package.json",
    "content": "{\n  \"name\": \"test-project\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"run\": \"node index.js\",\n    \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\",\n    \"run-module\": \"node --experimental-modules index.mjs\",\n    \"run-module-without-flag\": \"node index.mjs\",\n    \"atexit\": \"node -e \\\"process.once('exit', _ => console.log(42))\\\"\"\n  },\n  \"keywords\": []\n}\n"
  },
  {
    "path": "test/basic.spec.js",
    "content": "/**\n * @license Copyright 2018 Google Inc. All Rights Reserved.\n * 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\n * 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.\n */\n\nconst assert = require('assert');\nconst fs = require('fs');\nconst os = require('os');\nconst path = require('path');\nconst removeFolder = require('rimraf');\nconst util = require('util');\n\nconst {launch} = require('../lib/launcher.js');\nconst {ReleaseBuilder} = require('../scripts/build_release_application.js');\n\nconst fsMkdtemp = util.promisify(fs.mkdtemp);\n\nmodule.exports.addTests = function({testRunner}) {\n  // eslint-disable-next-line\n  const {beforeAll, afterAll} = testRunner;\n  // eslint-disable-next-line\n  const {it, fit, xit} = testRunner;\n  xit('run configuration', async function() {\n    const configDir = await fsMkdtemp(path.join(os.tmpdir(), 'ndb-test-'));\n    const frontend = await launch({\n      configDir: configDir,\n      argv: ['.'],\n      cwd: path.join(__dirname, 'assets', 'test-project'),\n      debugFrontend: false,\n      doNotCopyPreferences: true\n    });\n\n    const configItem = await frontend.waitForSelector('body /deep/ .list-item');\n    configItem.hover();\n    const runButton = await frontend.waitForSelector('body /deep/ .list-item /deep/ [aria-label=Run]', {\n      visible: true\n    });\n    runButton.click();\n    const consoleMessage = await frontend.waitForSelector('body /deep/ .console-message-wrapper:nth-child(3) .console-message-text');\n    assert.equal('42', await frontend.evaluate(x => x.innerText, consoleMessage));\n\n    await frontend.close();\n    await util.promisify(removeFolder)(configDir);\n  });\n\n  xit('run, pause at start, kill', async function() {\n    const configDir = await fsMkdtemp(path.join(os.tmpdir(), 'ndb-test-'));\n    const frontend = await launch({\n      configDir: configDir,\n      argv: ['.'],\n      cwd: path.join(__dirname, 'assets', 'test-project'),\n      debugFrontend: false,\n      doNotCopyPreferences: true\n    });\n\n    const [pauseAtStartCheckbox, configItem] = await Promise.all([\n      frontend.waitForSelector('body /deep/ #pause-at-start-checkbox'),\n      frontend.waitForSelector('body /deep/ .list-item')\n    ]);\n    await pauseAtStartCheckbox.click();\n    configItem.hover();\n    const runButton = await frontend.waitForSelector('body /deep/ .list-item /deep/ [aria-label=Run]', {\n      visible: true\n    });\n    runButton.click();\n    const executionLine = await frontend.waitForSelector('.cm-execution-line .CodeMirror-line');\n    const executionLineText = await frontend.evaluate(x => x.innerText, executionLine);\n    assert.equal(executionLineText, 'console.log(42);');\n\n    const processItem = await frontend.waitForSelector('body /deep/ li.selected');\n    processItem.hover();\n\n    const killButton = await frontend.waitForSelector('body /deep/ li.selected /deep/ [aria-label=Kill]');\n    killButton.click();\n    await frontend.waitForSelector('body /deep/ #no-running-nodes-msg', {\n      visible: true\n    });\n\n    await frontend.close();\n    await util.promisify(removeFolder)(configDir);\n  });\n\n  xit('terminal', async function() {\n    const configDir = await fsMkdtemp(path.join(os.tmpdir(), 'ndb-test-'));\n    const frontend = await launch({\n      configDir: configDir,\n      argv: ['.'],\n      cwd: path.join(__dirname, 'assets', 'test-project'),\n      debugFrontend: false,\n      doNotCopyPreferences: true\n    });\n\n    const [pauseAtStartCheckbox, terminalTab, resumeButton, consoleTab] = await Promise.all([\n      frontend.waitForSelector('body /deep/ #pause-at-start-checkbox'),\n      frontend.waitForSelector('body /deep/ #tab-ndb\\\\.terminal'),\n      frontend.waitForSelector('body /deep/ [aria-label=\"Pause script execution\"]'),\n      frontend.waitForSelector('body /deep/ #tab-console-view')\n    ]);\n    await pauseAtStartCheckbox.click();\n    terminalTab.click();\n    const terminal = await frontend.waitForSelector('body /deep/ .xterm-cursor-layer', {\n      visible: true\n    });\n    await frontend.click('body /deep/ .xterm-cursor-layer');\n    await frontend.type('body /deep/ .xterm-cursor-layer', 'node -e \"console.log(42)\"');\n    await terminal.press('Enter');\n\n    const executionLine = await frontend.waitForSelector('.cm-execution-line .CodeMirror-line');\n    const executionLineText = await frontend.evaluate(x => x.innerText, executionLine);\n    assert.equal(executionLineText, 'console.log(42);');\n\n    resumeButton.click();\n\n    await frontend.waitForSelector('body /deep/ #no-running-nodes-msg', {\n      visible: true\n    });\n\n    consoleTab.click();\n    const consoleMessage = await frontend.waitForSelector('body /deep/ .console-message-wrapper:nth-child(2) .console-message-text');\n    assert.equal('42', await frontend.evaluate(x => x.innerText, consoleMessage));\n\n    await frontend.close();\n    await util.promisify(removeFolder)(configDir);\n  });\n\n  xit('terminal exit', async function() {\n    const configDir = await fsMkdtemp(path.join(os.tmpdir(), 'ndb-test-'));\n    const frontend = await launch({\n      configDir: configDir,\n      argv: ['.'],\n      cwd: path.join(__dirname, 'assets', 'test-project'),\n      debugFrontend: false,\n      doNotCopyPreferences: true\n    });\n\n    const [terminalTab, consoleTab] = await Promise.all([\n      frontend.waitForSelector('body /deep/ #tab-ndb\\\\.terminal'),\n      frontend.waitForSelector('body /deep/ #tab-console-view'),\n    ]);\n    terminalTab.click();\n    const terminal = await frontend.waitForSelector('body /deep/ .xterm-cursor-layer', {\n      visible: true\n    });\n    await frontend.click('body /deep/ .xterm-cursor-layer');\n    await frontend.type('body /deep/ .xterm-cursor-layer', 'exit');\n    await terminal.press('Enter');\n    // we need better way to wait until terminal reconnected.\n    await new Promise(resolve => setTimeout(resolve, 300));\n    await frontend.type('body /deep/ .xterm-cursor-layer', 'node -e \"console.log(42)\"');\n    await terminal.press('Enter');\n\n    consoleTab.click();\n    const consoleMessage = await frontend.waitForSelector('body /deep/ .console-message-wrapper:nth-child(2) .console-message-text');\n    assert.equal('42', await frontend.evaluate(x => x.innerText, consoleMessage));\n\n    await frontend.close();\n    await util.promisify(removeFolder)(configDir);\n  });\n\n  xit('repl and uncaught error', async function() {\n    const configDir = await fsMkdtemp(path.join(os.tmpdir(), 'ndb-test-'));\n    const frontend = await launch({\n      configDir: configDir,\n      argv: ['.'],\n      cwd: path.join(__dirname, 'assets', 'test-project'),\n      debugFrontend: false,\n      doNotCopyPreferences: true\n    });\n    const consolePrompt = await frontend.waitForSelector('body /deep/ #console-prompt');\n    await frontend.type('body /deep/ #console-prompt', 'require(\"child_process\").spawn(\"!@#$%\")');\n    await consolePrompt.press('Enter');\n    await frontend.type('body /deep/ #console-prompt', 'console.log(42)');\n    consolePrompt.press('Enter');\n    const consoleMessage = await frontend.waitForSelector('body /deep/ .console-message-wrapper:nth-child(6) .console-message-text');\n    assert.equal('42', await frontend.evaluate(x => x.innerText, consoleMessage));\n    await frontend.close();\n    await util.promisify(removeFolder)(configDir);\n  });\n\n  beforeAll(async function(state) {\n    const DEVTOOLS_DIR = path.dirname(\n        require.resolve('chrome-devtools-frontend/front_end/shell.json'));\n    const frontendFolder = await fsMkdtemp(path.join(os.tmpdir(), 'ndb-test-frontend-'));\n    await new ReleaseBuilder([\n      path.join(__dirname, '..', 'front_end'),\n      DEVTOOLS_DIR,\n      path.join(__dirname, '..'),\n      path.join(__dirname, '..', '..', '..')\n    ], frontendFolder).buildApp('integration_test_runner');\n    state.frontendFolder = frontendFolder;\n  });\n\n  afterAll(async function(state) {\n    return util.promisify(removeFolder)(state.frontendFolder);\n  });\n\n  it('breakpoint inside .mjs file', async function(state) {\n    const configDir = await fsMkdtemp(path.join(os.tmpdir(), 'ndb-test-'));\n    const frontend = await launch({\n      configDir: configDir,\n      argv: ['.'],\n      cwd: path.join(__dirname, 'assets', 'test-project'),\n      debugFrontend: false,\n      doNotCopyPreferences: true,\n      appName: 'integration_test_runner',\n      releaseFrontendFolder: state.frontendFolder,\n      doNotProcessExit: true\n    });\n    await setupHelpers(frontend);\n    await frontend.showScriptSource('index.mjs');\n    await frontend.setBreakpoint(6, '');\n    await frontend.waitForConfigurations();\n\n    {\n      frontend.runConfiguration('run-module');\n      const {frames: [{location}]} = await frontend.waitUntilPaused();\n      assert.equal(6, location.lineNumber);\n      assert.equal(2, location.columnNumber);\n      await frontend.resumeExecution();\n    }\n\n    {\n      frontend.runConfiguration('run-module-without-flag');\n      const {frames: [{location}]} = await frontend.waitUntilPaused();\n      assert.equal(6, location.lineNumber);\n      assert.equal(2, location.columnNumber);\n      await frontend.resumeExecution();\n    }\n\n    await frontend.close();\n    await util.promisify(removeFolder)(configDir);\n  });\n\n  it('Stay attached', async function(state) {\n    const configDir = await fsMkdtemp(path.join(os.tmpdir(), 'ndb-test-'));\n    const frontend = await launch({\n      configDir: configDir,\n      argv: ['.'],\n      cwd: path.join(__dirname, 'assets', 'test-project'),\n      debugFrontend: false,\n      doNotCopyPreferences: true,\n      appName: 'integration_test_runner',\n      releaseFrontendFolder: state.frontendFolder,\n      doNotProcessExit: true\n    });\n    await setupHelpers(frontend);\n    await frontend.setSetting('waitAtEnd', true);\n    frontend.runConfiguration('atexit');\n    await frontend.waitForConsoleMessage('42');\n    const processes = await frontend.nodeProcess();\n    processes.sort();\n    assert.equal(`node -e process.once('exit', _ => console.log(42))`, processes[0]);\n    assert.equal(`node npm run atexit`, processes[1]);\n    const targetDestroyed = frontend.waitTargetDestroyed(2);\n    await frontend.killProcess(`node -e process.once('exit', _ => console.log(42))`);\n    await targetDestroyed;\n    assert.deepStrictEqual([], await frontend.nodeProcess());\n    await frontend.close();\n    await util.promisify(removeFolder)(configDir);\n  });\n};\n\n// eslint-disable-next-line\nfunction sleep() {\n  return new Promise(resolve => setTimeout(resolve, 2147483647));\n}\n\nasync function setupHelpers(frontend) {\n  await frontend.evaluate(() => self.runtime.loadModulePromise('sources_test_runner'));\n  await frontend.evaluate(_ => SourcesTestRunner.startDebuggerTest());\n  frontend.waitForConfigurations = function() {\n    return this.waitForSelector('body /deep/ div.configuration-item');\n  };\n\n  frontend.showScriptSource = function(name) {\n    return this.evaluate(name => SourcesTestRunner.showScriptSourcePromise(name), name);\n  };\n\n  frontend.setBreakpoint = function(line, condition) {\n    return this.evaluate((line, condition) => {\n      const sourcesView = Sources.SourcesPanel.instance().sourcesView();\n      const frame = sourcesView.currentSourceFrame();\n      SourcesTestRunner.setBreakpoint(frame, line, condition, true);\n    }, line, condition);\n  };\n\n  frontend.runConfiguration = async function(name) {\n    const handle = await this.evaluateHandle(name => {\n      const items = runtime.sharedInstance(Ndb.RunConfiguration).contentElement.querySelectorAll('div.list-item');\n      return Array.from(items).find(e => e.innerText.split('\\n')[0] === name);\n    }, name);\n    const element = handle.asElement();\n    await element.hover();\n    const runButton = await element.$('div.controls-buttons');\n    await runButton.click();\n  };\n\n  frontend.waitUntilPaused = function() {\n    return this.evaluate(_ => new Promise(resolve => {\n      SourcesTestRunner.waitUntilPaused(frames => resolve({frames: frames.map(frame => frame._payload)}));\n    }));\n  };\n\n  frontend.resumeExecution = function() {\n    return this.evaluate(_ => new Promise(resolve => SourcesTestRunner.resumeExecution(resolve)));\n  };\n\n  frontend.setSetting = function(name, value) {\n    return this.evaluate((name, value) => Common.moduleSetting(name).set(value), name, value);\n  };\n\n  frontend.waitForConsoleMessage = function(text) {\n    return this.evaluate(text =>\n      new Promise(resolve => TestRunner.addSniffer(SDK.ConsoleModel.prototype, 'addMessage', msg => {\n        console.log(msg.messageText);\n        if (msg.messageText === text)\n          resolve();\n      }, true))\n    , text);\n  };\n\n  frontend.nodeProcess = function() {\n    return this.evaluate(_ => {\n      const titles = self.runtime.sharedInstance(Ndb.NodeProcesses).contentElement.querySelectorAll('div /deep/ .process-title');\n      return Array.from(titles).map(el => el.innerText);\n    });\n  };\n\n  frontend.killProcess = async function(name) {\n    const handle = await this.evaluateHandle(name => {\n      const titles = self.runtime.sharedInstance(Ndb.NodeProcesses).contentElement.querySelectorAll('div /deep/ li');\n      return Array.from(titles).find(e => e.innerText.split('\\n')[0] === name);\n    }, name);\n    const element = handle.asElement();\n    await element.hover();\n    const btn = await element.$('div.controls-buttons');\n    await btn.click();\n  };\n\n  frontend.waitTargetDestroyed = async function(num) {\n    return this.evaluate(num =>\n      new Promise(resolve =>  Ndb.NodeProcessManager.instance().then(manager => {\n        manager.addEventListener(Ndb.NodeProcessManager.Events.Finished, _ => !--num && resolve());\n      }))\n    , num);\n  };\n}\n"
  },
  {
    "path": "test/integration.js",
    "content": "/**\n * @license Copyright 2018 Google Inc. All Rights Reserved.\n * 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\n * 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.\n */\n\nconst {TestRunner, Reporter} = require('../utils/testrunner/');\nlet parallel = 1;\nif (process.env.NDB_PARALLEL_TESTS)\n  parallel = parseInt(process.env.NDB_PARALLEL_TESTS.trim(), 10);\nconst timeout = 10000;\nconst testRunner = new TestRunner({timeout, parallel});\n\nrequire('./basic.spec.js').addTests({testRunner});\n\nnew Reporter(testRunner);\ntestRunner.run();\n"
  },
  {
    "path": "test/platform.spec.js",
    "content": "/**\n * @license Copyright 2018 Google Inc. All Rights Reserved.\n * 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\n * 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.\n */\n\nconst {TestRunner, Reporter, Matchers} = require('../utils/testrunner/');\nlet parallel = 1;\nif (process.env.NDB_PARALLEL_TESTS)\n  parallel = parseInt(process.env.NDB_PARALLEL_TESTS.trim(), 10);\nconst timeout = 10000;\nconst testRunner = new TestRunner({timeout, parallel});\nconst {expect} = new Matchers();\naddTests(testRunner);\nnew Reporter(testRunner);\ntestRunner.run();\n\nconst { execFile } = require('child_process');\n\n// Tests for specific Node platform features.\nfunction addTests(testRunner) {\n  // eslint-disable-next-line\n  const {beforeAll, afterAll} = testRunner;\n  // eslint-disable-next-line\n  const {it, fit, xit} = testRunner;\n\n  xit('--title flag (fails on Node v8.x)', async function() {\n    const result = await new Promise(resolve => execFile(\n        process.execPath, ['--title=abc', '-p', 'process.title'], (error, stdout, stderr) => {\n          resolve(stdout + stderr);\n        }));\n    expect(result).toBe('abc\\n');\n  });\n}\n"
  },
  {
    "path": "utils/testrunner/Matchers.js",
    "content": "/**\n * Copyright 2017 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nmodule.exports = class Matchers {\n  constructor(customMatchers = {}) {\n    this._matchers = {};\n    Object.assign(this._matchers, DefaultMatchers);\n    Object.assign(this._matchers, customMatchers);\n    this.expect = this.expect.bind(this);\n  }\n\n  addMatcher(name, matcher) {\n    this._matchers[name] = matcher;\n  }\n\n  expect(value) {\n    return new Expect(value, this._matchers);\n  }\n};\n\nclass Expect {\n  constructor(value, matchers) {\n    this.not = {};\n    this.not.not = this;\n    for (const matcherName of Object.keys(matchers)) {\n      const matcher = matchers[matcherName];\n      this[matcherName] = applyMatcher.bind(null, matcherName, matcher, false /* inverse */, value);\n      this.not[matcherName] = applyMatcher.bind(null, matcherName, matcher, true /* inverse */, value);\n    }\n\n    function applyMatcher(matcherName, matcher, inverse, value, ...args) {\n      const result = matcher.call(null, value, ...args);\n      const message = `expect.${inverse ? 'not.' : ''}${matcherName} failed` + (result.message ? `: ${result.message}` : '');\n      if (result.pass === inverse)\n        throw new Error(message);\n    }\n  }\n}\n\nconst DefaultMatchers = {\n  toBe: function(value, other, message) {\n    message = message || `${value} == ${other}`;\n    return { pass: value === other, message };\n  },\n\n  toBeFalsy: function(value, message) {\n    message = message || `${value}`;\n    return { pass: !value, message };\n  },\n\n  toBeTruthy: function(value, message) {\n    message = message || `${value}`;\n    return { pass: !!value, message };\n  },\n\n  toBeGreaterThan: function(value, other, message) {\n    message = message || `${value} > ${other}`;\n    return { pass: value > other, message };\n  },\n\n  toBeGreaterThanOrEqual: function(value, other, message) {\n    message = message || `${value} >= ${other}`;\n    return { pass: value >= other, message };\n  },\n\n  toBeLessThan: function(value, other, message) {\n    message = message || `${value} < ${other}`;\n    return { pass: value < other, message };\n  },\n\n  toBeLessThanOrEqual: function(value, other, message) {\n    message = message || `${value} <= ${other}`;\n    return { pass: value <= other, message };\n  },\n\n  toBeNull: function(value, message) {\n    message = message || `${value} == null`;\n    return { pass: value === null, message };\n  },\n\n  toContain: function(value, other, message) {\n    message = message || `${value} ⊇ ${other}`;\n    return { pass: value.includes(other), message };\n  },\n\n  toEqual: function(value, other, message) {\n    const valueJson = stringify(value);\n    const otherJson = stringify(other);\n    message = message || `${valueJson} ≈ ${otherJson}`;\n    return { pass: valueJson === otherJson, message };\n  },\n\n  toBeCloseTo: function(value, other, precision, message) {\n    return {\n      pass: Math.abs(value - other) < Math.pow(10, -precision),\n      message\n    };\n  }\n};\n\nfunction stringify(value) {\n  function stabilize(key, object) {\n    if (typeof object !== 'object' || object === undefined || object === null)\n      return object;\n    const result = {};\n    for (const key of Object.keys(object).sort())\n      result[key] = object[key];\n    return result;\n  }\n\n  return JSON.stringify(stabilize(null, value), stabilize);\n}\n"
  },
  {
    "path": "utils/testrunner/Multimap.js",
    "content": "/**\n * Copyright 2017 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nclass Multimap {\n  constructor() {\n    this._map = new Map();\n  }\n\n  set(key, value) {\n    let set = this._map.get(key);\n    if (!set) {\n      set = new Set();\n      this._map.set(key, set);\n    }\n    set.add(value);\n  }\n\n  get(key) {\n    let result = this._map.get(key);\n    if (!result)\n      result = new Set();\n    return result;\n  }\n\n  has(key) {\n    return this._map.has(key);\n  }\n\n  hasValue(key, value) {\n    const set = this._map.get(key);\n    if (!set)\n      return false;\n    return set.has(value);\n  }\n\n  /**\n   * @return {number}\n   */\n  get size() {\n    return this._map.size;\n  }\n\n  delete(key, value) {\n    const values = this.get(key);\n    const result = values.delete(value);\n    if (!values.size)\n      this._map.delete(key);\n    return result;\n  }\n\n  deleteAll(key) {\n    this._map.delete(key);\n  }\n\n  firstValue(key) {\n    const set = this._map.get(key);\n    if (!set)\n      return null;\n    return set.values().next().value;\n  }\n\n  firstKey() {\n    return this._map.keys().next().value;\n  }\n\n  valuesArray() {\n    const result = [];\n    for (const key of this._map.keys())\n      result.push(...Array.from(this._map.get(key).values()));\n    return result;\n  }\n\n  keysArray() {\n    return Array.from(this._map.keys());\n  }\n\n  clear() {\n    this._map.clear();\n  }\n}\n\nmodule.exports = Multimap;\n"
  },
  {
    "path": "utils/testrunner/README.md",
    "content": "# TestRunner\n\n- testrunner is a library: no additional binary required; tests are `node.js` scripts\n- parallel wrt IO operations\n- supports async/await\n- modular\n- well-isolated state per execution thread\n\nExample\n\n```js\nconst {TestRunner, Reporter, Matchers} = require('../utils/testrunner');\n\n// Runner holds and runs all the tests\nconst runner = new TestRunner({\n  parallel: 2, // run 2 parallel threads\n  timeout: 1000, // setup timeout of 1 second per test\n});\n// Simple expect-like matchers\nconst {expect} = new Matchers();\n\n// Extract jasmine-like DSL into the global namespace\nconst {describe, xdescribe, fdescribe} = runner;\nconst {it, fit, xit} = runner;\nconst {beforeAll, beforeEach, afterAll, afterEach} = runner;\n\nbeforeAll(state => {\n  state.parallelIndex; // either 0 or 1 in this example, depending on the executing thread\n  state.foo = 'bar'; // set state for every test\n});\n\ndescribe('math', () => {\n  it('to be sane', async (state, test) => {\n    state.parallelIndex; // Very first test will always be ran by the 0's thread\n    state.foo; // this will be 'bar'\n    expect(2 + 2).toBe(4);\n  });\n});\n\n// Reporter subscribes to TestRunner events and displays information in terminal\nconst reporter = new Reporter(runner);\n\n// Run all tests.\nrunner.run();\n```\n"
  },
  {
    "path": "utils/testrunner/Reporter.js",
    "content": "/**\n * Copyright 2017 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nconst RED_COLOR = '\\x1b[31m';\nconst GREEN_COLOR = '\\x1b[32m';\nconst YELLOW_COLOR = '\\x1b[33m';\nconst RESET_COLOR = '\\x1b[0m';\n\nclass Reporter {\n  constructor(runner) {\n    this._runner = runner;\n    runner.on('started', this._onStarted.bind(this));\n    runner.on('terminated', this._onTerminated.bind(this));\n    runner.on('finished', this._onFinished.bind(this));\n    runner.on('teststarted', this._onTestStarted.bind(this));\n    runner.on('testfinished', this._onTestFinished.bind(this));\n  }\n\n  _onStarted() {\n    this._timestamp = Date.now();\n    console.log(`Running ${YELLOW_COLOR}${this._runner.parallel()}${RESET_COLOR} worker(s):\\n`);\n  }\n\n  _onTerminated(message, error) {\n    this._printTestResults();\n    console.log(`${RED_COLOR}## TERMINATED ##${RESET_COLOR}`);\n    console.log('Message:');\n    console.log(`  ${RED_COLOR}${message}${RESET_COLOR}`);\n    if (error && error.stack) {\n      console.log('Stack:');\n      console.log(error.stack.split('\\n').map(line => '  ' + line).join('\\n'));\n    }\n    process.exit(2);\n  }\n\n  _onFinished() {\n    this._printTestResults();\n    const failedTests = this._runner.failedTests();\n    process.exit(failedTests.length > 0 ? 1 : 0);\n  }\n\n  _printTestResults() {\n    // 2 newlines after completing all tests.\n    console.log('\\n');\n\n    const failedTests = this._runner.failedTests();\n    if (failedTests.length > 0) {\n      console.log('\\nFailures:');\n      for (let i = 0; i < failedTests.length; ++i) {\n        const test = failedTests[i];\n        console.log(`${i + 1}) ${test.fullName} (${formatLocation(test)})`);\n        if (test.result === 'timedout') {\n          console.log('  Message:');\n          console.log(`    ${YELLOW_COLOR}Timeout Exceeded ${this._runner.timeout()}ms${RESET_COLOR}`);\n        } else {\n          console.log('  Message:');\n          console.log(`    ${RED_COLOR}${test.error.message || test.error}${RESET_COLOR}`);\n          console.log('  Stack:');\n          if (test.error.stack) {\n            const stack = test.error.stack.split('\\n').map(line => '    ' + line);\n            let i = 0;\n            while (i < stack.length && !stack[i].includes(__dirname))\n              ++i;\n            while (i < stack.length && stack[i].includes(__dirname))\n              ++i;\n            if (i < stack.length) {\n              const indent = stack[i].match(/^\\s*/)[0];\n              stack[i] = stack[i].substring(0, indent.length - 3) + YELLOW_COLOR + '⇨ ' + RESET_COLOR +  stack[i].substring(indent.length - 1);\n            }\n            console.log(stack.join('\\n'));\n          }\n        }\n        if (test.output) {\n          console.log('  Output:');\n          console.log(test.output.split('\\n').map(line => '    ' + line).join('\\n'));\n        }\n        console.log('');\n      }\n    }\n\n    const tests = this._runner.tests();\n    const skippedTests = tests.filter(test => test.result === 'skipped');\n    if (skippedTests.length > 0) {\n      console.log('\\nSkipped:');\n      for (let i = 0; i < skippedTests.length; ++i) {\n        const test = skippedTests[i];\n        console.log(`${i + 1}) ${test.fullName}`);\n        console.log(`  ${YELLOW_COLOR}Temporary disabled with xit${RESET_COLOR} ${formatLocation(test)}\\n`);\n      }\n    }\n\n    const executedTests = tests.filter(test => test.result);\n    console.log(`\\nRan ${executedTests.length} of ${tests.length} test(s)`);\n    const milliseconds = Date.now() - this._timestamp;\n    const seconds = milliseconds / 1000;\n    console.log(`Finished in ${YELLOW_COLOR}${seconds}${RESET_COLOR} seconds`);\n\n    function formatLocation(test) {\n      const location = test.location;\n      if (!location)\n        return '';\n      return `${location.fileName}:${location.lineNumber}:${location.columnNumber}`;\n    }\n  }\n\n  _onTestStarted() {\n  }\n\n  _onTestFinished(test) {\n    if (test.result === 'ok')\n      process.stdout.write(`${GREEN_COLOR}.${RESET_COLOR}`);\n    else if (test.result === 'skipped')\n      process.stdout.write(`${YELLOW_COLOR}*${RESET_COLOR}`);\n    else if (test.result === 'failed')\n      process.stdout.write(`${RED_COLOR}F${RESET_COLOR}`);\n    else if (test.result === 'timedout')\n      process.stdout.write(`${RED_COLOR}T${RESET_COLOR}`);\n  }\n}\n\nmodule.exports = Reporter;\n"
  },
  {
    "path": "utils/testrunner/TestRunner.js",
    "content": "/**\n * Copyright 2017 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nconst path = require('path');\nconst EventEmitter = require('events');\nconst Multimap = require('./Multimap');\n\nconst TimeoutError = new Error('Timeout');\nconst TerminatedError = new Error('Terminated');\n\nconst MAJOR_NODEJS_VERSION = parseInt(process.version.substring(1).split('.')[0], 10);\n\nclass UserCallback {\n  constructor(callback, timeout) {\n    this._callback = callback;\n    this._terminatePromise = new Promise(resolve => {\n      this._terminateCallback = resolve;\n    });\n\n    this.timeout = timeout;\n    this.location = this._getLocation();\n  }\n\n  async run(...args) {\n    const timeoutPromise = new Promise(resolve => {\n      setTimeout(resolve.bind(null, TimeoutError), this.timeout);\n    });\n    try {\n      return await Promise.race([\n        Promise.resolve().then(this._callback.bind(null, ...args)).then(() => null).catch(e => e),\n        timeoutPromise,\n        this._terminatePromise\n      ]);\n    } catch (e) {\n      return e;\n    }\n  }\n\n  _getLocation() {\n    const error = new Error();\n    const stackFrames = error.stack.split('\\n').slice(1);\n    // Find first stackframe that doesn't point to this file.\n    for (let frame of stackFrames) {\n      frame = frame.trim();\n      if (!frame.startsWith('at '))\n        return null;\n      if (frame.endsWith(')')) {\n        const from = frame.indexOf('(');\n        frame = frame.substring(from + 1, frame.length - 1);\n      } else {\n        frame = frame.substring('at '.length + 1);\n      }\n\n      const match = frame.match(/^(.*):(\\d+):(\\d+)$/);\n      if (!match)\n        return null;\n      const filePath = match[1];\n      const lineNumber = match[2];\n      const columnNumber = match[3];\n      if (filePath === __filename)\n        continue;\n      const fileName = filePath.split(path.sep).pop();\n      return { fileName, filePath, lineNumber, columnNumber };\n    }\n    return null;\n  }\n\n  terminate() {\n    this._terminateCallback(TerminatedError);\n  }\n}\n\nconst TestMode = {\n  Run: 'run',\n  Skip: 'skip',\n  Focus: 'focus'\n};\n\nconst TestResult = {\n  Ok: 'ok',\n  Skipped: 'skipped', // User skipped the test\n  Failed: 'failed', // Exception happened during running\n  TimedOut: 'timedout', // Timeout Exceeded while running\n};\n\nclass Test {\n  constructor(suite, name, callback, declaredMode, timeout) {\n    this.suite = suite;\n    this.name = name;\n    this.fullName = (suite.fullName + ' ' + name).trim();\n    this.declaredMode = declaredMode;\n    this._userCallback = new UserCallback(callback, timeout);\n    this.location = this._userCallback.location;\n\n    // Test results\n    this.result = null;\n    this.error = null;\n  }\n}\n\nclass Suite {\n  constructor(parentSuite, name, declaredMode) {\n    this.parentSuite = parentSuite;\n    this.name = name;\n    this.fullName = (parentSuite ? parentSuite.fullName + ' ' + name : name).trim();\n    this.declaredMode = declaredMode;\n    /** @type {!Array<(!Test|!Suite)>} */\n    this.children = [];\n\n    this.beforeAll = null;\n    this.beforeEach = null;\n    this.afterAll = null;\n    this.afterEach = null;\n  }\n}\n\nclass TestPass {\n  constructor(runner, rootSuite, tests, parallel) {\n    this._runner = runner;\n    this._parallel = parallel;\n    this._runningUserCallbacks = new Multimap();\n\n    this._rootSuite = rootSuite;\n    this._workerDistribution = new Multimap();\n\n    let workerId = 0;\n    for (const test of tests) {\n      // Reset results for tests that will be run.\n      test.result = null;\n      test.error = null;\n      this._workerDistribution.set(test, workerId);\n      for (let suite = test.suite; suite; suite = suite.parentSuite)\n        this._workerDistribution.set(suite, workerId);\n      // Do not shard skipped tests across workers.\n      if (test.declaredMode !== TestMode.Skip)\n        workerId = (workerId + 1) % parallel;\n    }\n\n    this._termination = null;\n  }\n\n  async run() {\n    const terminations = [\n      createTermination.call(this, 'SIGINT', 'SIGINT received'),\n      createTermination.call(this, 'SIGHUP', 'SIGHUP received'),\n      createTermination.call(this, 'SIGTERM', 'SIGTERM received'),\n      createTermination.call(this, 'unhandledRejection', 'UNHANDLED PROMISE REJECTION'),\n    ];\n    for (const termination of terminations)\n      process.on(termination.event, termination.handler);\n\n    const workerPromises = [];\n    for (let i = 0; i < this._parallel; ++i)\n      workerPromises.push(this._runSuite(i, [this._rootSuite], {parallelIndex: i}));\n    await Promise.all(workerPromises);\n\n    for (const termination of terminations)\n      process.removeListener(termination.event, termination.handler);\n    return this._termination;\n\n    function createTermination(event, message) {\n      return {\n        event,\n        message,\n        handler: error => this._terminate(message, error)\n      };\n    }\n  }\n\n  async _runSuite(workerId, suitesStack, state) {\n    if (this._termination)\n      return;\n    const currentSuite = suitesStack[suitesStack.length - 1];\n    if (!this._workerDistribution.hasValue(currentSuite, workerId))\n      return;\n    await this._runHook(workerId, currentSuite, 'beforeAll', state);\n    for (const child of currentSuite.children) {\n      if (!this._workerDistribution.hasValue(child, workerId))\n        continue;\n      if (child instanceof Test) {\n        for (let i = 0; i < suitesStack.length; i++)\n          await this._runHook(workerId, suitesStack[i], 'beforeEach', state, child);\n        await this._runTest(workerId, child, state);\n        for (let i = suitesStack.length - 1; i >= 0; i--)\n          await this._runHook(workerId, suitesStack[i], 'afterEach', state, child);\n      } else {\n        suitesStack.push(child);\n        await this._runSuite(workerId, suitesStack, state);\n        suitesStack.pop();\n      }\n    }\n    await this._runHook(workerId, currentSuite, 'afterAll', state);\n  }\n\n  async _runTest(workerId, test, state) {\n    if (this._termination)\n      return;\n    this._runner._willStartTest(test);\n    if (test.declaredMode === TestMode.Skip) {\n      test.result = TestResult.Skipped;\n      this._runner._didFinishTest(test);\n      return;\n    }\n    this._runningUserCallbacks.set(workerId, test._userCallback);\n    const error = await test._userCallback.run(state, test);\n    this._runningUserCallbacks.delete(workerId, test._userCallback);\n    if (this._termination)\n      return;\n    test.error = error;\n    if (!error)\n      test.result = TestResult.Ok;\n    else if (test.error === TimeoutError)\n      test.result = TestResult.TimedOut;\n    else\n      test.result = TestResult.Failed;\n    this._runner._didFinishTest(test);\n  }\n\n  async _runHook(workerId, suite, hookName, ...args) {\n    if (this._termination)\n      return;\n    const hook = suite[hookName];\n    if (!hook)\n      return;\n    this._runningUserCallbacks.set(workerId, hook);\n    const error = await hook.run(...args);\n    this._runningUserCallbacks.delete(workerId, hook);\n    if (error === TimeoutError) {\n      const location = `${hook.location.fileName}:${hook.location.lineNumber}:${hook.location.columnNumber}`;\n      const message = `${location} - Timeout Exceeded ${hook.timeout}ms while running \"${hookName}\" in suite \"${suite.fullName}\"`;\n      this._terminate(message, null);\n    } else if (error) {\n      const location = `${hook.location.fileName}:${hook.location.lineNumber}:${hook.location.columnNumber}`;\n      const message = `${location} - FAILED while running \"${hookName}\" in suite \"${suite.fullName}\"`;\n      this._terminate(message, error);\n    }\n  }\n\n  _terminate(message, error) {\n    if (this._termination)\n      return;\n    this._termination = {message, error};\n    for (const userCallback of this._runningUserCallbacks.valuesArray())\n      userCallback.terminate();\n  }\n}\n\nclass TestRunner extends EventEmitter {\n  constructor(options = {}) {\n    super();\n    this._rootSuite = new Suite(null, '', TestMode.Run);\n    this._currentSuite = this._rootSuite;\n    this._tests = [];\n    // Default timeout is 10 seconds.\n    this._timeout = options.timeout === 0 ? 2147483647 : options.timeout || 10 * 1000;\n    this._parallel = options.parallel || 1;\n    this._retryFailures = !!options.retryFailures;\n\n    this._hasFocusedTestsOrSuites = false;\n\n    if (MAJOR_NODEJS_VERSION >= 8) {\n      const inspector = require('inspector');\n      if (inspector.url()) {\n        console.log('TestRunner detected inspector; overriding certain properties to be debugger-friendly');\n        console.log('  - timeout = 0 (Infinite)');\n        this._timeout = 2147483647;\n        this._parallel = 1;\n      }\n    }\n\n    // bind methods so that they can be used as a DSL.\n    this.describe = this._addSuite.bind(this, TestMode.Run);\n    this.fdescribe = this._addSuite.bind(this, TestMode.Focus);\n    this.xdescribe = this._addSuite.bind(this, TestMode.Skip);\n    this.it = this._addTest.bind(this, TestMode.Run);\n    this.fit = this._addTest.bind(this, TestMode.Focus);\n    this.xit = this._addTest.bind(this, TestMode.Skip);\n    this.beforeAll = this._addHook.bind(this, 'beforeAll');\n    this.beforeEach = this._addHook.bind(this, 'beforeEach');\n    this.afterAll = this._addHook.bind(this, 'afterAll');\n    this.afterEach = this._addHook.bind(this, 'afterEach');\n  }\n\n  _addTest(mode, name, callback) {\n    let suite = this._currentSuite;\n    let isSkipped = suite.declaredMode === TestMode.Skip;\n    while ((suite = suite.parentSuite))\n      isSkipped |= suite.declaredMode === TestMode.Skip;\n    const test = new Test(this._currentSuite, name, callback, isSkipped ? TestMode.Skip : mode, this._timeout);\n    this._currentSuite.children.push(test);\n    this._tests.push(test);\n    this._hasFocusedTestsOrSuites = this._hasFocusedTestsOrSuites || mode === TestMode.Focus;\n  }\n\n  _addSuite(mode, name, callback) {\n    const oldSuite = this._currentSuite;\n    const suite = new Suite(this._currentSuite, name, mode);\n    this._currentSuite.children.push(suite);\n    this._currentSuite = suite;\n    callback();\n    this._currentSuite = oldSuite;\n    this._hasFocusedTestsOrSuites = this._hasFocusedTestsOrSuites || mode === TestMode.Focus;\n  }\n\n  _addHook(hookName, callback) {\n    assert(this._currentSuite[hookName] === null, `Only one ${hookName} hook available per suite`);\n    const hook = new UserCallback(callback, this._timeout);\n    this._currentSuite[hookName] = hook;\n  }\n\n  async run() {\n    this.emit(TestRunner.Events.Started);\n    const pass = new TestPass(this, this._rootSuite, this._runnableTests(), this._parallel);\n    const termination = await pass.run();\n    if (termination)\n      this.emit(TestRunner.Events.Terminated, termination.message, termination.error);\n    else\n      this.emit(TestRunner.Events.Finished);\n  }\n\n  timeout() {\n    return this._timeout;\n  }\n\n  _runnableTests() {\n    if (!this._hasFocusedTestsOrSuites)\n      return this._tests;\n\n    const tests = [];\n    const blacklistSuites = new Set();\n    // First pass: pick \"fit\" and blacklist parent suites\n    for (const test of this._tests) {\n      if (test.declaredMode !== TestMode.Focus)\n        continue;\n      tests.push(test);\n      for (let suite = test.suite; suite; suite = suite.parentSuite)\n        blacklistSuites.add(suite);\n    }\n    // Second pass: pick all tests that belong to non-blacklisted \"fdescribe\"\n    for (const test of this._tests) {\n      let insideFocusedSuite = false;\n      for (let suite = test.suite; suite; suite = suite.parentSuite) {\n        if (!blacklistSuites.has(suite) && suite.declaredMode === TestMode.Focus) {\n          insideFocusedSuite = true;\n          break;\n        }\n      }\n      if (insideFocusedSuite)\n        tests.push(test);\n    }\n    return tests;\n  }\n\n  hasFocusedTestsOrSuites() {\n    return this._hasFocusedTestsOrSuites;\n  }\n\n  tests() {\n    return this._tests.slice();\n  }\n\n  failedTests() {\n    return this._tests.filter(test => test.result === 'failed' || test.result === 'timedout');\n  }\n\n  parallel() {\n    return this._parallel;\n  }\n\n  _willStartTest(test) {\n    this.emit('teststarted', test);\n  }\n\n  _didFinishTest(test) {\n    this.emit('testfinished', test);\n  }\n}\n\n/**\n * @param {*} value\n * @param {string=} message\n */\nfunction assert(value, message) {\n  if (!value)\n    throw new Error(message);\n}\n\nTestRunner.Events = {\n  Started: 'started',\n  TestStarted: 'teststarted',\n  TestFinished: 'testfinished',\n  Terminated: 'terminated',\n  Finished: 'finished',\n};\n\nmodule.exports = TestRunner;\n"
  },
  {
    "path": "utils/testrunner/examples/fail.js",
    "content": "/**\n * Copyright 2017 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nconst {TestRunner, Reporter, Matchers} = require('..');\n\nconst runner = new TestRunner();\nconst reporter = new Reporter(runner);\nconst {expect} = new Matchers();\n\nconst {describe, xdescribe, fdescribe} = runner;\nconst {it, fit, xit} = runner;\nconst {beforeAll, beforeEach, afterAll, afterEach} = runner;\n\ndescribe('testsuite', () => {\n  it('toBe', async (state) => {\n    expect(2 + 2).toBe(5);\n  });\n  it('toBeFalsy', async (state) => {\n    expect(true).toBeFalsy();\n  });\n  it('toBeTruthy', async (state) => {\n    expect(false).toBeTruthy();\n  });\n  it('toBeGreaterThan', async (state) => {\n    expect(2).toBeGreaterThan(3);\n  });\n  it('toBeNull', async (state) => {\n    expect(2).toBeNull();\n  });\n  it('toContain', async (state) => {\n    expect('asdf').toContain('e');\n  });\n  it('not.toContain', async (state) => {\n    expect('asdf').not.toContain('a');\n  });\n  it('toEqual', async (state) => {\n    expect([1,2,3]).toEqual([1,2,3,4]);\n  });\n});\n\nrunner.run();\n"
  },
  {
    "path": "utils/testrunner/examples/hookfail.js",
    "content": "/**\n * Copyright 2017 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nconst {TestRunner, Reporter, Matchers} = require('..');\n\nconst runner = new TestRunner();\nconst reporter = new Reporter(runner);\nconst {expect} = new Matchers();\n\nconst {describe, xdescribe, fdescribe} = runner;\nconst {it, fit, xit} = runner;\nconst {beforeAll, beforeEach, afterAll, afterEach} = runner;\n\ndescribe('testsuite', () => {\n  beforeAll(() => {\n    expect(false).toBeTruthy();\n  });\n  it('test', async () => {\n  });\n});\n\nrunner.run();\n"
  },
  {
    "path": "utils/testrunner/examples/hooktimeout.js",
    "content": "/**\n * Copyright 2017 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nconst {TestRunner, Reporter, Matchers} = require('..');\n\nconst runner = new TestRunner({ timeout: 100 });\nconst reporter = new Reporter(runner);\nconst {expect} = new Matchers();\n\nconst {describe, xdescribe, fdescribe} = runner;\nconst {it, fit, xit} = runner;\nconst {beforeAll, beforeEach, afterAll, afterEach} = runner;\n\ndescribe('testsuite', () => {\n  beforeAll(async () => {\n    await new Promise(() => {});\n  });\n  it('something', async (state) => {\n  });\n});\n\nrunner.run();\n"
  },
  {
    "path": "utils/testrunner/examples/timeout.js",
    "content": "/**\n * Copyright 2017 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nconst {TestRunner, Reporter} = require('..');\n\nconst runner = new TestRunner({ timeout: 100 });\nconst reporter = new Reporter(runner);\n\nconst {describe, xdescribe, fdescribe} = runner;\nconst {it, fit, xit} = runner;\nconst {beforeAll, beforeEach, afterAll, afterEach} = runner;\n\ndescribe('testsuite', () => {\n  it('timeout', async (state) => {\n    await new Promise(() => {});\n  });\n});\n\nrunner.run();\n"
  },
  {
    "path": "utils/testrunner/examples/unhandledpromiserejection.js",
    "content": "/**\n * Copyright 2017 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nconst {TestRunner, Reporter} = require('..');\n\nconst runner = new TestRunner();\nconst reporter = new Reporter(runner);\n\nconst {describe, xdescribe, fdescribe} = runner;\nconst {it, fit, xit} = runner;\nconst {beforeAll, beforeEach, afterAll, afterEach} = runner;\n\ndescribe('testsuite', () => {\n  it('failure', async (state) => {\n    Promise.reject(new Error('fail!'));\n  });\n  it('slow', async () => {\n    await new Promise(x => setTimeout(x, 1000));\n  });\n});\n\nrunner.run();\n"
  },
  {
    "path": "utils/testrunner/index.js",
    "content": "/**\n * Copyright 2017 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nconst TestRunner = require('./TestRunner');\nconst Reporter = require('./Reporter');\nconst Matchers = require('./Matchers');\n\nmodule.exports = { TestRunner, Reporter, Matchers };\n"
  },
  {
    "path": "version.js",
    "content": "const fs = require('fs');\n\n(function main() {\n  const version = require('./package.json').version;\n  let preload = fs.readFileSync('./lib/preload/ndb/preload.js', 'utf8');\n  preload = preload.replace(/process\\.versions\\['ndb'\\] = '[\\d+\\.]+';/, `process.versions['ndb'] = '${version}';`);\n  fs.writeFileSync('./lib/preload/ndb/preload.js', preload, 'utf8');\n})();\n"
  }
]