├── .gitattributes ├── .gitignore ├── .npmignore ├── .vscode ├── launch.json └── settings.json ├── LICENSE ├── README.md ├── bin └── electron-inspector.js ├── bugs.gif ├── lib ├── cli.js ├── index.js └── rebuild.js ├── npm-shrinkwrap.json ├── package.json └── src ├── cli.ts ├── electron-rebuild.d.ts ├── electron.d.ts ├── index.ts ├── node-pre-gyp.d.ts ├── rebuild.ts └── tsconfig.json /.gitattributes: -------------------------------------------------------------------------------- 1 | /bin/*.js eol=lf 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/.BROWSE.VC.DB* 2 | /node_modules/** 3 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /.vscode/ 2 | /src/ 3 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Launch", 6 | "type": "node", 7 | "request": "launch", 8 | "program": "${workspaceRoot}/lib/index.js", 9 | "stopOnEntry": true, 10 | "args": [], 11 | "cwd": "${workspaceRoot}", 12 | "preLaunchTask": null, 13 | "runtimeExecutable": null, 14 | "runtimeArgs": [ 15 | "--nolazy" 16 | ], 17 | "env": { 18 | "NODE_ENV": "development" 19 | }, 20 | "console": "internalConsole", 21 | "sourceMaps": false, 22 | "outDir": null 23 | }, 24 | { 25 | "name": "Attach", 26 | "type": "node", 27 | "request": "attach", 28 | "port": 5858, 29 | "address": "localhost", 30 | "restart": false, 31 | "sourceMaps": false, 32 | "outDir": null, 33 | "localRoot": "${workspaceRoot}", 34 | "remoteRoot": null 35 | }, 36 | { 37 | "name": "Attach to Process", 38 | "type": "node", 39 | "request": "attach", 40 | "processId": "${command.PickProcess}", 41 | "port": 5858, 42 | "sourceMaps": false, 43 | "outDir": null 44 | } 45 | ] 46 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "typescript.tsdk": "node_modules/typescript/lib" 4 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Vadim Macagon 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # electron-inspector 2 | Debugger UI for the main Electron process 3 | 4 | ## Overview 5 | 6 | This package wraps [`node-inspector`][node-inspector-npm], which can be used to debug JavaScript 7 | code running in the main Electron process. Getting `node-inspector` running can require somewhere 8 | between a little and [a lot of effort][node-inspector-electron-v13x] depending on the Electron 9 | version you wish to debug. The goal of `electron-inspector` is to get `node-inspector` running 10 | with minimum effort on your part. 11 | 12 | [node-inspector-npm]: https://www.npmjs.com/package/node-inspector 13 | [node-inspector-electron-v13x]: http://vadim.macagon.com/blog/2016/09/11/rebuilding-node-inspector-for-electron-v13x/ 14 | 15 | ## Prerequisites 16 | 17 | - NPM v3 18 | - NodeJS v4.5 (or later) 19 | - [`electron`][electron-npm], or [`electron-prebuilt`][electron-prebuilt-npm], or [`electron-prebuilt-compile`][electron-prebuilt-compile-npm] 20 | - [`electron-rebuild`][electron-rebuild-npm] (optional) 21 | 22 | [electron-npm]: https://www.npmjs.com/package/electron 23 | [electron-prebuilt-npm]: https://www.npmjs.com/package/electron-prebuilt 24 | [electron-prebuilt-compile-npm]: https://www.npmjs.com/package/electron-prebuilt-compile 25 | [electron-rebuild-npm]: https://www.npmjs.com/package/electron-rebuild 26 | 27 | ## Quick Start 28 | 29 | `electron-inspector` should be installed as a local dev dependency of your Electron app: 30 | 31 | ```shell 32 | npm install electron-inspector --save-dev 33 | ``` 34 | 35 | The easiest way to run the inspector in a cross-platform manner is to add an NPM script to your 36 | `package.json`, for example: 37 | 38 | ```json 39 | "scripts": { 40 | "inspect-main": "electron-inspector" 41 | } 42 | ``` 43 | 44 | Then run the `inspect-main` script on the command line with: 45 | 46 | ```shell 47 | npm run inspect-main 48 | ``` 49 | 50 | Alternatively, if you don't want to mess with your `package.json` you can directly execute 51 | `electron-inspector` (macOS / Linux), or `.\\node_modules\\.bin\\electron-inspector` (Windows). 52 | 53 | On startup `electron-inspector` will check for compatibility of the native modules in 54 | `node-inspector` with the Electron version you wish to debug, if the compatibility check 55 | fails and `electron-rebuild` is installed then the native modules will be automatically 56 | rebuilt. You can disable auto-rebuild using the `--no-auto-rebuild` command line option. 57 | 58 | When `electron-inspector` finally gets `node-inspector` running you will see a URL printed to the 59 | console window. For example: 60 | 61 | ```shell 62 | Visit http://127.0.0.1:8080/?port=5858 to start debugging. 63 | ``` 64 | 65 | You can then [start Electron in debug mode][electron-debug] and open the given URL in your browser. 66 | 67 | [electron-debug]: http://electron.atom.io/docs/tutorial/debugging-main-process/#enable-debug-mode-for-electron 68 | 69 | ## Known Bugs 70 | 71 | - Somewhere around Chrome 54 the DevTools front-end in `node-inspector` started crashing on startup 72 | because it hasn't been updated in quite a while. `electron-inspector` contains a workaround for 73 | the crash but upon opening the DevTools front-end you'll see 74 | `Error: No NativeModule in target scope` printed out in the console, ignore it. 75 | - On some systems when you launch Electron with the `--debug-brk` flag in order to pause execution 76 | on the first line of code it may look like the DevTools front-end never actually loads your app 77 | code. In this case your app is actually paused on the first line of code, it's just that the 78 | front-end wasn't refreshed to reflect that fact. You can force the DevTools front-end to refresh 79 | by pressing the `Pause script execution (F8)` button. 80 | 81 | ![Bugs](bugs.gif "Working around the bugs") 82 | 83 | ## Configuration 84 | 85 | `node-inspector` can be configured in [multiple ways][node-inspector-config], `electron-inspector` 86 | will pass through most of the supported command line options. 87 | 88 | [node-inspector-config]: https://www.npmjs.com/package/node-inspector#configuration 89 | 90 | ### Command Line Options 91 | 92 | `electron-inspector` accepts most of the commandline options `node-inspector` does: 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 111 | 112 | 113 | 114 | 116 | 120 | 121 | 122 | 123 | 128 | 129 | 130 | 131 | 135 | 136 | 137 | 142 | 143 | 144 | 145 | 148 | 149 | 152 | 153 | 154 | 155 | 157 | 161 | 162 | 163 | 164 | 167 | 168 | 171 | 172 | 173 | 174 | 176 | 179 | 180 | 181 | 182 | 184 | 187 | 188 | 189 | 190 | 192 | 197 | 198 | 199 | 205 | 206 | 207 | 208 | 212 | 215 | 216 | 217 | 218 | 223 | 224 | 225 | 226 | 231 | 232 | 233 |
OptionAliasDefaultDescription
--help 107 | 108 | 109 | Display information about the available options. 110 |
--auto-rebuild 115 | true 117 | Toggle automatic rebuild of native node-inspector modules, this only works if 118 | electron-rebuild is installed. 119 |
--electron 124 | 125 | 126 | Path to the Electron executable that should be used to run node-inspector. 127 |
node-inspector 132 | 133 | 134 |
--config 138 | 139 | 140 | Path to file with node-inspector config information. 141 |
--debug-port 146 | -d 147 | 5858 150 | Debug port of the Electron process you wish to debug. 151 |
--web-host 156 | 0.0.0.0 158 | Host to listen on for node-inspector web interface, 127.0.0.1 by 159 | default. 160 |
--web-port 165 | -p 166 | 8080 169 | Port to listen on for node-inspector web interface. 170 |
--save-live-edit 175 | false 177 | Save live edit changes to disk (update the edited files). 178 |
--preload 183 | true 185 | Preload *.js files. You can disable this option to speed up the startup. 186 |
--inject 191 | true 193 | Enable injection of debugger extensions into the debugged process. It's possible disable only 194 | part of injections using subkeys, e.g. --no-inject.network. 195 | Allowed keys: network, profiles, console. 196 |
--hidden 200 | 201 | 202 | Array of files to hide from the UI, breakpoints in these files will be ignored. All paths are 203 | interpreted as regular expressions. 204 |
--stack-trace-limit 209 | 210 | 50 211 | 213 | Number of stack frames to show on a breakpoint. 214 |
--ssl-key 219 | 220 | 221 | Path to file containing a valid SSL key. 222 |
--ssl-cert 227 | 228 | 229 | Path to file containing a valid SSL certificate. 230 |
234 | 235 | # License 236 | 237 | MIT 238 | -------------------------------------------------------------------------------- /bin/electron-inspector.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | 3 | require('../lib/cli'); 4 | -------------------------------------------------------------------------------- /bugs.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enlight/electron-inspector/cb2cc7bd555b99919e8be54b3be114049b0d34ad/bugs.gif -------------------------------------------------------------------------------- /lib/cli.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Vadim Macagon 2 | // MIT License, see LICENSE file for full terms. 3 | "use strict"; 4 | const yargs = require("yargs"); 5 | const index_1 = require("./index"); 6 | const argv = yargs 7 | .usage('Usage: $0 [options]') 8 | .option('debug-port', { 9 | alias: 'd', 10 | type: 'number', 11 | default: 5858, 12 | describe: 'Debug port of the Electron process you wish to debug.' 13 | }) 14 | .option('web-host', { 15 | type: 'string', 16 | default: '0.0.0.0', 17 | describe: "Host to listen on for node-inspector's web interface." 18 | }) 19 | .option('web-port', { 20 | alias: ['p', 'port'], 21 | type: 'number', 22 | default: 8080, 23 | describe: "Port to listen on for node-inspector's web interface." 24 | }) 25 | .option('save-live-edit', { 26 | type: 'boolean', 27 | default: false, 28 | describe: 'Save live edit changes to disk (update the edited files).' 29 | }) 30 | .option('preload', { 31 | type: 'boolean', 32 | default: true, 33 | describe: 'Preload *.js files. You can disable this option to speed up the startup.' 34 | }) 35 | .option('inject', { 36 | type: 'boolean', 37 | default: true, 38 | description: 'Enable/disable injection of debugger extensions into the debugged process.\n' + 39 | "It's posible to disable only some of the injections using subkeys.\n" + 40 | 'Available subkeys: network, profiles, console' 41 | }) 42 | .option('hidden', { 43 | type: 'string', 44 | describe: 'Array of files to hide from the UI.' + 45 | 'Breakpoints in these files will be ignored.\n' + 46 | 'All paths are interpreted as regular expressions.' 47 | }) 48 | .option('stack-trace-limit', { 49 | type: 'number', 50 | default: 50, 51 | describe: 'Number of stack frames to show on a breakpoint.' 52 | }) 53 | .option('ssl-key', { 54 | type: 'string', 55 | describe: 'A file containing a valid SSL key.' 56 | }) 57 | .option('ssl-cert', { 58 | type: 'string', 59 | describe: 'A file containing a valid SSL certificate.' 60 | }) 61 | .option('electron', { 62 | type: 'string', 63 | describe: 'Path to the Electron executable that should be used to run node-inspector.' 64 | }) 65 | .option('auto-rebuild', { 66 | type: 'boolean', 67 | default: true, 68 | describe: 'Toggle automatic rebuild of native node-inspector modules,\n' + 69 | 'this only works if electron-rebuild is installed.' 70 | }) 71 | .help() 72 | .argv; 73 | index_1.inspect(argv); 74 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Vadim Macagon 2 | // MIT License, see LICENSE file for full terms. 3 | "use strict"; 4 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 5 | return new (P || (P = Promise))(function (resolve, reject) { 6 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 7 | function rejected(value) { try { step(generator.throw(value)); } catch (e) { reject(e); } } 8 | function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } 9 | step((generator = generator.apply(thisArg, _arguments)).next()); 10 | }); 11 | }; 12 | const path = require("path"); 13 | const fs = require("fs"); 14 | const child_process = require("child_process"); 15 | const node_pre_gyp_1 = require("node-pre-gyp"); 16 | function getElectronPackageVersion(moduleName) { 17 | // try to grab the version from the electron-prebuilt package if it's installed 18 | const packageText = fs.readFileSync(require.resolve(`${moduleName}/package.json`), 'utf8'); 19 | const packageJson = JSON.parse(packageText); 20 | return packageJson.version; 21 | } 22 | const electronVersionRegex = /^v(\d{1,2}\.\d{1,2}\.\d{1,2})$/; 23 | /** 24 | * Obtain the path of the Electron executable and its version. 25 | * 26 | * @param exePath Path of an Electron executable, if omitted an attempt will be made to find it 27 | * by looking for `electron`, `electron-prebuilt`, or `electron-prebuilt-compile`. 28 | * @return `null` if an Electron executable wasn't found, or its version couldn't be determined. 29 | */ 30 | function getElectronInfo(exePath) { 31 | // if a path to the electron executable was provided run it to figure out its version 32 | if (exePath) { 33 | if (fs.existsSync(exePath)) { 34 | try { 35 | const stdout = child_process.execFileSync(exePath, ['--version'], { encoding: 'utf8' }); 36 | const version = stdout.replace(/[\r\n]/g, ''); 37 | const match = electronVersionRegex.exec(version); 38 | if (match) { 39 | return { 40 | executablePath: exePath, 41 | version: match[1] 42 | }; 43 | } 44 | } 45 | catch (error) { 46 | } 47 | } 48 | return null; 49 | } 50 | const candidates = ['electron-prebuilt', 'electron', 'electron-prebuilt-compile']; 51 | for (let candidate of candidates) { 52 | try { 53 | return { 54 | // the path to the electron executable is exported by the module 55 | executablePath: require(candidate), 56 | version: getElectronPackageVersion(candidate) 57 | }; 58 | } 59 | catch (error) { 60 | } 61 | } 62 | return null; 63 | } 64 | /** 65 | * Check if the `v8-profiler` and `v8-debug` native binaries are compatibile with the given 66 | * Electron version. 67 | * 68 | * NOTE: If the `v8-profiler` or the `v8-debug` modules aren't installed at all this function will 69 | * throw an error. 70 | * 71 | * @param Electron version, e.g. 1.3.0. 72 | * @return `true` if the native binaries are compatible. 73 | */ 74 | function isInspectorCompatible(electronVersion) { 75 | // require.resolve() will throw if it fails to find the module 76 | let packageDir = path.dirname(require.resolve(path.join(__dirname, '../node_modules/v8-profiler'))); 77 | let binaryFile = node_pre_gyp_1.find(path.join(packageDir, 'package.json'), { runtime: 'electron', target: electronVersion }); 78 | if (!fs.existsSync(binaryFile)) { 79 | return false; 80 | } 81 | packageDir = path.dirname(require.resolve(path.join(__dirname, '../node_modules/v8-debug'))); 82 | binaryFile = node_pre_gyp_1.find(path.join(packageDir, 'package.json'), { runtime: 'electron', target: electronVersion }); 83 | return fs.existsSync(binaryFile); 84 | } 85 | function getNodeInspectorCmdLineArgs(options) { 86 | const args = []; 87 | if (options.debugPort != null) { 88 | args.push('-d', options.debugPort.toString()); 89 | } 90 | if (options.webHost) { 91 | args.push('--web-host', options.webHost); 92 | } 93 | if (options.webPort != null) { 94 | args.push('--web-port', options.webPort.toString()); 95 | } 96 | if (options.saveLiveEdit) { 97 | args.push('--save-live-edit', options.saveLiveEdit.toString()); 98 | } 99 | if (options.preload === false) { 100 | args.push('--no-preload'); 101 | } 102 | if (options.inject === false) { 103 | args.push('--no-inject'); 104 | } 105 | if (options.hidden) { 106 | if (options.hidden instanceof Array) { 107 | options.hidden.forEach(pattern => args.push('--hidden', pattern)); 108 | } 109 | else { 110 | args.push('--hidden', options.hidden); 111 | } 112 | } 113 | if (options.stackTraceLimit != null) { 114 | args.push('--stack-trace-limit', options.stackTraceLimit.toString()); 115 | } 116 | if (options.sslKey) { 117 | args.push('--ssl-key', options.sslKey); 118 | } 119 | if (options.sslCert) { 120 | args.push('--ssl-cert', options.sslCert); 121 | } 122 | return args; 123 | } 124 | /** 125 | * Launch an Electron process that runs `node-inspector`. 126 | * 127 | * @param electronPath Full path to the Electron executable. 128 | */ 129 | function launchInspector(electronPath, options) { 130 | const scriptPath = require.resolve('node-inspector/bin/inspector.js'); 131 | const inspector = child_process.fork(scriptPath, getNodeInspectorCmdLineArgs(options), { execPath: electronPath, silent: true }); 132 | inspector.on('error', (error) => console.error(error)); 133 | inspector.on('message', (msg) => { 134 | if (msg.event === 'SERVER.LISTENING') { 135 | // node-inspector will print the address to the console, 136 | // so there's no need to do anything here 137 | console.info(`Visit ${msg.address.url} to start debugging.`); 138 | } 139 | else if (msg.event === 'SERVER.ERROR') { 140 | console.error(`Cannot start the server: ${msg.error.code}.`); 141 | } 142 | }); 143 | inspector.on('close', (exitCode, signal) => { 144 | process.exit(exitCode); 145 | }); 146 | } 147 | /** 148 | * Rebuild the native modules `node-inspector` uses to target the given Electron version. 149 | */ 150 | function rebuildInspector(electronPath, electronVersion, arch) { 151 | return __awaiter(this, void 0, void 0, function* () { 152 | console.log('node-inspector binaries are incompatible or missing.'); 153 | console.log('Attempting to rebuild...'); 154 | let rebuild; 155 | try { 156 | rebuild = require('./rebuild').rebuild; 157 | } 158 | catch (error) { 159 | console.log('Failed to load electron-prebuilt, abandoning rebuild.'); 160 | throw error; 161 | } 162 | yield rebuild(electronPath, electronVersion, arch); 163 | }); 164 | } 165 | function inspect(options) { 166 | return __awaiter(this, void 0, void 0, function* () { 167 | const electron = getElectronInfo(options.electron); 168 | if (!electron) { 169 | console.log('Electron not found.'); 170 | return; 171 | } 172 | if (!isInspectorCompatible(electron.version)) { 173 | if (options.autoRebuild) { 174 | yield rebuildInspector(electron.executablePath, electron.version); 175 | } 176 | else { 177 | console.warn(`Native node-inspector modules are incompatible with Electron ${electron.version}, ` + 178 | 'and auto-rebuild is disabled, node-inspector may fail to run.'); 179 | } 180 | } 181 | launchInspector(electron.executablePath, options); 182 | }); 183 | } 184 | exports.inspect = inspect; 185 | -------------------------------------------------------------------------------- /lib/rebuild.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Vadim Macagon 2 | // MIT License, see LICENSE file for full terms. 3 | "use strict"; 4 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 5 | return new (P || (P = Promise))(function (resolve, reject) { 6 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 7 | function rejected(value) { try { step(generator.throw(value)); } catch (e) { reject(e); } } 8 | function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } 9 | step((generator = generator.apply(thisArg, _arguments)).next()); 10 | }); 11 | }; 12 | const path = require("path"); 13 | const electron_rebuild_1 = require("electron-rebuild"); 14 | /** 15 | * Rebuild any native NodeJS modules in the given `node_modules` directory to work with the 16 | * specified Electron runtime. If the Electron path and version are not specified they'll be 17 | * obtained from the `electron-prebuilt` package. 18 | */ 19 | function rebuild(executablePath, version, arch) { 20 | return __awaiter(this, void 0, void 0, function* () { 21 | let modulesDir = path.join(__dirname, '../node_modules'); 22 | console.log(`Rebuilding native node-inspector modules for Electron ${version}`); 23 | yield electron_rebuild_1.installNodeHeaders(version, null, null, arch); 24 | console.log(`Rebuilding in ${modulesDir}`); 25 | yield electron_rebuild_1.rebuildNativeModules(version, modulesDir, 'v8-profiler', null, arch); 26 | yield electron_rebuild_1.rebuildNativeModules(version, modulesDir, 'v8-debug', null, arch); 27 | console.log(`Rebuild complete`); 28 | }); 29 | } 30 | exports.rebuild = rebuild; 31 | -------------------------------------------------------------------------------- /npm-shrinkwrap.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "electron-inspector", 3 | "version": "0.1.4", 4 | "dependencies": { 5 | "node-inspector": { 6 | "version": "0.12.8", 7 | "from": "enlight/node-inspector", 8 | "resolved": "git://github.com/enlight/node-inspector.git#accd46758501d8187e55b32b54e3eff65c3fdaa3" 9 | }, 10 | "v8-debug": { 11 | "version": "0.7.7", 12 | "from": "v8-debug@>=0.7.1 <0.8.0", 13 | "resolved": "https://registry.npmjs.org/v8-debug/-/v8-debug-0.7.7.tgz" 14 | }, 15 | "v8-profiler": { 16 | "version": "5.6.5", 17 | "from": "enlight/v8-profiler#v5.6.5-electron-v1.3", 18 | "resolved": "git://github.com/enlight/v8-profiler.git#dc3a4e15f06dd6eaca705e69e235d21d5962eaf5" 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "electron-inspector", 3 | "version": "0.1.4", 4 | "engines": { 5 | "node": ">=4.5.0" 6 | }, 7 | "description": "Debugger UI for the main Electron process", 8 | "main": "lib/index.js", 9 | "bin": { 10 | "electron-inspector": "bin/electron-inspector.js" 11 | }, 12 | "scripts": { 13 | "prepublish": "npm run build", 14 | "build": "tsc -p src/tsconfig.json" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/enlight/electron-inspector.git" 19 | }, 20 | "keywords": [ 21 | "electron", 22 | "node-inspector", 23 | "inspector", 24 | "debug", 25 | "debugger" 26 | ], 27 | "author": "Vadim Macagon ", 28 | "license": "MIT", 29 | "bugs": { 30 | "url": "https://github.com/enlight/electron-inspector/issues" 31 | }, 32 | "homepage": "https://github.com/enlight/electron-inspector#readme", 33 | "dependencies": { 34 | "node-inspector": "git+https://github.com/enlight/node-inspector", 35 | "v8-debug": "^0.7.7", 36 | "v8-profiler": "git+https://github.com/enlight/v8-profiler.git#v5.6.5-electron-v1.3", 37 | "yargs": "^5.0.0" 38 | }, 39 | "devDependencies": { 40 | "@types/node": "^6.0.38", 41 | "@types/yargs": "0.0.30", 42 | "electron": "^1.3.5", 43 | "electron-rebuild": "^1.2.1", 44 | "tslint": "^3.15.0-dev.0", 45 | "typescript": "^2.1.0-dev.20160912" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/cli.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Vadim Macagon 2 | // MIT License, see LICENSE file for full terms. 3 | 4 | import * as yargs from 'yargs'; 5 | import { inspect, IOptions, NODE_INSPECTOR_DEFAULTS } from './index'; 6 | 7 | const argv: yargs.Argv & IOptions = yargs 8 | .usage('Usage: electron-inspector [options]') 9 | // node-inspector options 10 | .option('config', { 11 | type: 'string', 12 | describe: 'Path to file with node-inspector config information.' 13 | }) 14 | .option('debug-port', { 15 | alias: 'd', 16 | type: 'number', 17 | default: NODE_INSPECTOR_DEFAULTS.debugPort, 18 | describe: 'Debug port of the Electron process you wish to debug.' 19 | }) 20 | .option('web-host', { 21 | type: 'string', 22 | default: NODE_INSPECTOR_DEFAULTS.webHost, 23 | describe: "Host to listen on for node-inspector's web interface." 24 | }) 25 | .option('web-port', { 26 | alias: ['p', 'port'], 27 | type: 'number', 28 | default: NODE_INSPECTOR_DEFAULTS.webPort, 29 | describe: "Port to listen on for node-inspector's web interface." 30 | }) 31 | .option('save-live-edit', { 32 | type: 'boolean', 33 | default: NODE_INSPECTOR_DEFAULTS.saveLiveEdit, 34 | describe: 'Save live edit changes to disk (update the edited files).' 35 | }) 36 | .option('preload', { 37 | type: 'boolean', 38 | default: NODE_INSPECTOR_DEFAULTS.preload, 39 | describe: 'Preload *.js files. You can disable this option to speed up the startup.' 40 | }) 41 | .option('inject', { 42 | type: 'boolean', 43 | default: NODE_INSPECTOR_DEFAULTS.inject, 44 | description: 'Enable/disable injection of debugger extensions into the debugged process.\n' + 45 | "It's posible to disable only some of the injections using subkeys.\n" + 46 | 'Available subkeys: network, profiles, console' 47 | }) 48 | .option('hidden', { 49 | type: 'string', 50 | describe: 'Array of files to hide from the UI.' + 51 | 'Breakpoints in these files will be ignored.\n' + 52 | 'All paths are interpreted as regular expressions.' 53 | }) 54 | .option('stack-trace-limit', { 55 | type: 'number', 56 | default: NODE_INSPECTOR_DEFAULTS.stackTraceLimit, 57 | describe: 'Number of stack frames to show on a breakpoint.' 58 | }) 59 | .option('ssl-key', { 60 | type: 'string', 61 | describe: 'A file containing a valid SSL key.' 62 | }) 63 | .option('ssl-cert', { 64 | type: 'string', 65 | describe: 'A file containing a valid SSL certificate.' 66 | }) 67 | // electron-inspector options 68 | .option('electron', { 69 | type: 'string', 70 | describe: 'Path to the Electron executable that should be used to run node-inspector.' 71 | }) 72 | .option('auto-rebuild', { 73 | type: 'boolean', 74 | default: true, 75 | describe: 'Toggle automatic rebuild of native node-inspector modules,\n' + 76 | 'this only works if electron-rebuild is installed.' 77 | }) 78 | .help() 79 | .argv; 80 | 81 | inspect(argv); 82 | -------------------------------------------------------------------------------- /src/electron-rebuild.d.ts: -------------------------------------------------------------------------------- 1 | // Type definitions for electron-rebuild 1.0.2 2 | // Project: https://github.com/electronjs/electron-rebuild/ 3 | // Definitions by: Vadim Macagon 4 | 5 | declare module "electron-rebuild" { 6 | /** 7 | * Determine whether native modules need to be rebuilt (not terribly accurate right now). 8 | * 9 | * @param pathToElectronExecutable Path to the Electron executable native modules should be 10 | * rebuilt for. 11 | * @param electronVersion If omitted it will be determined from Electron executable. 12 | * @return A promise that will be resolved with `true` iff native modules need to be rebuilt. 13 | */ 14 | export function shouldRebuildNativeModules( 15 | pathToElectronExecutable: string, electronVersion?: string 16 | ): Promise; 17 | 18 | /** 19 | * Download and install the NodeJS SDK for the given Electron runtime. 20 | * 21 | * @param electronVersion Electron version to download the NodeJS SDK for. 22 | * @param nodeDistUrl URL to download the NodeJS SDK from. 23 | * @param headersDir The directory to download the NodeJS SDK to. 24 | * @param arch `x64` or `ia32` 25 | * @return A promise that will be resolved when the operation completes. 26 | */ 27 | export function installNodeHeaders( 28 | electronVersion: string, nodeDistUrl?: string, headersDir?: string, arch?: string 29 | ): Promise; 30 | 31 | /** 32 | * Rebuild the native modules in the given `node_modules` directory for the given Electron version. 33 | * 34 | * @param electronVersion Electron version to rebuild module for. 35 | * @param nodeModulesPath The path to the `node_modules` directory containing native modules. 36 | * @param whichModule Name of a specific module that should be rebuilt, if specified no other 37 | * modules will be rebuilt. 38 | * @param headersDir Path to the NodeJS SDK to use when rebuilding the native modules. 39 | * @return A promise that will be resolved when the operation completes. 40 | */ 41 | export function rebuildNativeModules( 42 | electronVersion: string, 43 | nodeModulesPath: string, 44 | whichModule?: string, 45 | headersDir?: string, 46 | arch?: string 47 | ): Promise; 48 | } 49 | 50 | declare module "electron-rebuild/lib/node-pre-gyp-fix" { 51 | export function preGypFixRun( 52 | cwd: string, shouldRun: boolean, electronPath: string, explicitNodeVersion?: string 53 | ): Promise; 54 | } 55 | -------------------------------------------------------------------------------- /src/electron.d.ts: -------------------------------------------------------------------------------- 1 | declare module "electron"; 2 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Vadim Macagon 2 | // MIT License, see LICENSE file for full terms. 3 | 4 | import * as path from 'path'; 5 | import * as fs from 'fs'; 6 | import * as child_process from 'child_process'; 7 | import { find as findNativeModule } from 'node-pre-gyp'; 8 | import { RebuildFunction } from './rebuild'; 9 | 10 | // default values of various `node-inspector` options 11 | export const NODE_INSPECTOR_DEFAULTS = { 12 | debugPort: 5858, 13 | webHost: '0.0.0.0', 14 | webPort: 8080, 15 | saveLiveEdit: false, 16 | preload: true, 17 | inject: true, 18 | stackTraceLimit: 50 19 | } 20 | 21 | function getElectronPackageVersion(moduleName: string): string { 22 | // try to grab the version from the electron-prebuilt package if it's installed 23 | const packageText = fs.readFileSync(require.resolve(`${moduleName}/package.json`), 'utf8'); 24 | const packageJson = JSON.parse(packageText); 25 | return packageJson.version; 26 | } 27 | 28 | const electronVersionRegex = /^v(\d{1,2}\.\d{1,2}\.\d{1,2})$/; 29 | 30 | /** 31 | * Obtain the path of the Electron executable and its version. 32 | * 33 | * @param exePath Path of an Electron executable, if omitted an attempt will be made to find it 34 | * by looking for `electron`, `electron-prebuilt`, or `electron-prebuilt-compile`. 35 | * @return `null` if an Electron executable wasn't found, or its version couldn't be determined. 36 | */ 37 | function getElectronInfo(exePath?: string): { executablePath: string, version: string } | null { 38 | // if a path to the electron executable was provided run it to figure out its version 39 | if (exePath) { 40 | if (fs.existsSync(exePath)) { 41 | try { 42 | const stdout = child_process.execFileSync(exePath, ['--version'], { encoding: 'utf8' }); 43 | const version = stdout.replace(/[\r\n]/g, ''); 44 | const match = electronVersionRegex.exec(version); 45 | if (match) { 46 | return { 47 | executablePath: exePath, 48 | version: match[1] 49 | }; 50 | } 51 | } catch (error) { 52 | // noop 53 | } 54 | } 55 | return null; 56 | } 57 | 58 | const candidates = ['electron-prebuilt', 'electron', 'electron-prebuilt-compile']; 59 | for (let candidate of candidates) { 60 | try { 61 | return { 62 | // the path to the electron executable is exported by the module 63 | executablePath: require(candidate), 64 | version: getElectronPackageVersion(candidate) 65 | }; 66 | } catch (error) { 67 | // noop 68 | } 69 | } 70 | return null; 71 | } 72 | 73 | /** 74 | * Check if the `v8-profiler` and `v8-debug` native binaries are compatibile with the given 75 | * Electron version. 76 | * 77 | * NOTE: If the `v8-profiler` or the `v8-debug` modules aren't installed at all this function will 78 | * throw an error. 79 | * 80 | * @param Electron version, e.g. 1.3.0. 81 | * @return `true` if the native binaries are compatible. 82 | */ 83 | function isInspectorCompatible(electronVersion: string): boolean { 84 | // require.resolve() will throw if it fails to find the module 85 | let packageDir = path.dirname( 86 | require.resolve(path.join(__dirname, '../node_modules/v8-profiler')) 87 | ); 88 | let binaryFile = findNativeModule( 89 | path.join(packageDir, 'package.json'), 90 | { runtime: 'electron', target: electronVersion } 91 | ); 92 | if (!fs.existsSync(binaryFile)) { 93 | return false; 94 | } 95 | 96 | packageDir = path.dirname(require.resolve(path.join(__dirname, '../node_modules/v8-debug'))); 97 | binaryFile = findNativeModule( 98 | path.join(packageDir, 'package.json'), 99 | { runtime: 'electron', target: electronVersion } 100 | ); 101 | return fs.existsSync(binaryFile); 102 | } 103 | 104 | export interface INodeInspectorOptions { 105 | config?: string; 106 | debugPort?: number; 107 | webHost?: string; 108 | webPort?: number; 109 | saveLiveEdit?: boolean; 110 | preload?: boolean; 111 | inject?: boolean; // FIXME: can also be an object 112 | hidden?: string | string[]; 113 | stackTraceLimit?: number; 114 | sslKey?: string; 115 | sslCert?: string; 116 | } 117 | 118 | /** 119 | * Build an array of command line args that will be passed to `node-inspector`. 120 | * 121 | * Only options whose value differs from the default will be added to the 122 | * argument list, this makes it possible to override `node-inspector` options 123 | * via a config file. 124 | */ 125 | function getNodeInspectorCmdLineArgs(options: INodeInspectorOptions): string[] { 126 | const args: string[] = []; 127 | if (options.config) { 128 | args.push('--config', options.config); 129 | } 130 | if ((options.debugPort != null) && (options.debugPort !== NODE_INSPECTOR_DEFAULTS.debugPort)) { 131 | args.push('-d', options.debugPort.toString()); 132 | } 133 | if (options.webHost && (options.webHost !== NODE_INSPECTOR_DEFAULTS.webHost)) { 134 | args.push('--web-host', options.webHost); 135 | } 136 | if ((options.webPort != null) && (options.webPort !== NODE_INSPECTOR_DEFAULTS.webPort)) { 137 | args.push('--web-port', options.webPort.toString()); 138 | } 139 | if (options.saveLiveEdit) { 140 | args.push('--save-live-edit', options.saveLiveEdit.toString()) 141 | } 142 | if (options.preload === false) { 143 | args.push('--no-preload'); 144 | } 145 | if (options.inject === false) { 146 | args.push('--no-inject'); 147 | } 148 | if (options.hidden) { 149 | if (options.hidden instanceof Array) { 150 | options.hidden.forEach(pattern => args.push('--hidden', pattern)); 151 | } else { 152 | args.push('--hidden', options.hidden); 153 | } 154 | } 155 | if ((options.stackTraceLimit != null) && 156 | (options.stackTraceLimit !== NODE_INSPECTOR_DEFAULTS.stackTraceLimit)) { 157 | args.push('--stack-trace-limit', options.stackTraceLimit.toString()); 158 | } 159 | if (options.sslKey) { 160 | args.push('--ssl-key', options.sslKey); 161 | } 162 | if (options.sslCert) { 163 | args.push('--ssl-cert', options.sslCert); 164 | } 165 | return args; 166 | } 167 | 168 | /** 169 | * Launch an Electron process that runs `node-inspector`. 170 | * 171 | * @param electronPath Full path to the Electron executable. 172 | */ 173 | function launchInspector(electronPath: string, options: INodeInspectorOptions): void { 174 | const scriptPath = require.resolve('node-inspector/bin/inspector.js'); 175 | const inspector = child_process.fork( 176 | scriptPath, getNodeInspectorCmdLineArgs(options), 177 | { execPath: electronPath, silent: true } 178 | ); 179 | 180 | inspector.on('error', (error: Error) => console.error(error)); 181 | 182 | inspector.on('message', (msg: any) => { 183 | if (msg.event === 'SERVER.LISTENING') { 184 | // node-inspector will print the address to the console, 185 | // so there's no need to do anything here 186 | console.info(`Visit ${msg.address.url} to start debugging.`); 187 | } else if (msg.event === 'SERVER.ERROR') { 188 | console.error(`Cannot start the server: ${msg.error.code}.`); 189 | } 190 | }); 191 | 192 | inspector.on('close', (exitCode: number, signal: string) => { 193 | process.exit(exitCode); 194 | }); 195 | } 196 | 197 | /** 198 | * Rebuild the native modules `node-inspector` uses to target the given Electron version. 199 | */ 200 | async function rebuildInspector( 201 | electronPath: string, electronVersion: string, arch?: string 202 | ): Promise { 203 | console.log('node-inspector binaries are incompatible or missing.'); 204 | console.log('Attempting to rebuild...'); 205 | let rebuild: RebuildFunction; 206 | try { 207 | rebuild = require('./rebuild').rebuild; 208 | } catch (error) { 209 | console.log('Failed to load electron-prebuilt, abandoning rebuild.'); 210 | throw error; 211 | } 212 | await rebuild(electronPath, electronVersion, arch); 213 | } 214 | 215 | export interface IOptions extends INodeInspectorOptions { 216 | /** Path to Electron executable. */ 217 | electron?: string; 218 | autoRebuild: boolean; 219 | } 220 | 221 | export async function inspect(options: IOptions): Promise { 222 | const electron = getElectronInfo(options.electron); 223 | if (!electron) { 224 | console.log('Electron not found.'); 225 | return; 226 | } 227 | 228 | if (!isInspectorCompatible(electron.version)) { 229 | if (options.autoRebuild) { 230 | await rebuildInspector(electron.executablePath, electron.version); 231 | } else { 232 | console.warn( 233 | `Native node-inspector modules are incompatible with Electron ${electron.version}, `+ 234 | 'and auto-rebuild is disabled, node-inspector may fail to run.' 235 | ); 236 | } 237 | } 238 | 239 | launchInspector(electron.executablePath, options); 240 | } 241 | -------------------------------------------------------------------------------- /src/node-pre-gyp.d.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Vadim Macagon 2 | // MIT License, see LICENSE file for full terms. 3 | 4 | declare module "node-pre-gyp" { 5 | export interface FindOptions { 6 | /** Defaults to the directory containing the package file. */ 7 | module_root?: string; 8 | /** If not specified the runtime will be determined from the current process. */ 9 | runtime?: string; 10 | /** Version of the runtime, if not specified will be determined from the current process. */ 11 | target?: string; 12 | /** Defaults to `process.platform`. */ 13 | target_platform?: string; 14 | /** Defaults to `process.arch`. */ 15 | target_arch?: string; 16 | /** 17 | * If `true` the search algo will look for the debug binary, otherwise it will look for the 18 | * release binary. Defaults to `false`. 19 | */ 20 | debug?: boolean; 21 | } 22 | 23 | /** 24 | * Finds a native module binary in the given package. 25 | * 26 | * @param packageFile Path to the `package.json` of the package to search. 27 | * @return A path that can be used to require the `.node` binary. 28 | */ 29 | export function find(packageFile: string, options: FindOptions): string; 30 | } 31 | -------------------------------------------------------------------------------- /src/rebuild.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Vadim Macagon 2 | // MIT License, see LICENSE file for full terms. 3 | 4 | import * as path from 'path'; 5 | import { installNodeHeaders, rebuildNativeModules } from 'electron-rebuild'; 6 | import { preGypFixRun } from 'electron-rebuild/lib/node-pre-gyp-fix'; 7 | 8 | /** 9 | * Rebuild any native NodeJS modules in the given `node_modules` directory to work with the 10 | * specified Electron runtime. If the Electron path and version are not specified they'll be 11 | * obtained from the `electron-prebuilt` package. 12 | */ 13 | export async function rebuild( 14 | executablePath: string, version: string, arch: string 15 | ): Promise { 16 | let modulesDir = path.join(__dirname, '../node_modules'); 17 | console.log(`Rebuilding native node-inspector modules for Electron ${version}`); 18 | await installNodeHeaders(version, null, null, arch); 19 | console.log(`Rebuilding ${modulesDir}/v8-profiler`); 20 | await rebuildNativeModules(version, modulesDir, 'v8-profiler', null, arch); 21 | // `node-inspector` will be launched in a "run as node" Electron process, 22 | // so `node-pre-gyp` will be looking for a `node-vXX-platform-arch` directory 23 | await preGypFixRun(path.join(modulesDir, 'v8-profiler'), true, executablePath); 24 | console.log(`Rebuilding ${modulesDir}/v8-debug`); 25 | await rebuildNativeModules(version, modulesDir, 'v8-debug', null, arch); 26 | await preGypFixRun(path.join(modulesDir, 'v8-debug'), true, executablePath); 27 | console.log(`Done.`); 28 | } 29 | 30 | // Export the type of the function so that it can be referenced without actually requiring this 31 | // module (which is useful for conditional imports in TypeScript). 32 | export type RebuildFunction = typeof rebuild; 33 | -------------------------------------------------------------------------------- /src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es5", 5 | "noImplicitAny": true, 6 | "sourceMap": false, 7 | "lib": [ 8 | "es5", 9 | "dom", 10 | "es2015.promise" 11 | ], 12 | "outDir": "../lib" 13 | }, 14 | "files": [ 15 | "electron-rebuild.d.ts", 16 | "electron.d.ts", 17 | "node-pre-gyp.d.ts", 18 | "index.ts", 19 | "rebuild.ts", 20 | "cli.ts" 21 | ] 22 | } --------------------------------------------------------------------------------