/
index.ts
240 lines (221 loc) · 7.58 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
// Copyright (c) 2016 Vadim Macagon
// MIT License, see LICENSE file for full terms.
import * as path from 'path';
import * as fs from 'fs';
import * as child_process from 'child_process';
import { find as findNativeModule } from 'node-pre-gyp';
import { RebuildFunction } from './rebuild';
// default values of various `node-inspector` options
export const NODE_INSPECTOR_DEFAULTS = {
debugPort: 5858,
webHost: '0.0.0.0',
webPort: 8080,
saveLiveEdit: false,
preload: true,
inject: true,
stackTraceLimit: 50
}
function getElectronPackageVersion(moduleName: string): string {
// try to grab the version from the electron-prebuilt package if it's installed
const packageText = fs.readFileSync(require.resolve(`${moduleName}/package.json`), 'utf8');
const packageJson = JSON.parse(packageText);
return packageJson.version;
}
const electronVersionRegex = /^v(\d{1,2}\.\d{1,2}\.\d{1,2})$/;
/**
* Obtain the path of the Electron executable and its version.
*
* @param exePath Path of an Electron executable, if omitted an attempt will be made to find it
* by looking for `electron`, `electron-prebuilt`, or `electron-prebuilt-compile`.
* @return `null` if an Electron executable wasn't found, or its version couldn't be determined.
*/
function getElectronInfo(exePath?: string): { executablePath: string, version: string } | null {
// if a path to the electron executable was provided run it to figure out its version
if (exePath) {
if (fs.existsSync(exePath)) {
try {
const stdout = child_process.execFileSync(exePath, ['--version'], { encoding: 'utf8' });
const version = stdout.replace(/[\r\n]/g, '');
const match = electronVersionRegex.exec(version);
if (match) {
return {
executablePath: exePath,
version: match[1]
};
}
} catch (error) {
// noop
}
}
return null;
}
const candidates = ['electron-prebuilt', 'electron', 'electron-prebuilt-compile'];
for (let candidate of candidates) {
try {
return {
// the path to the electron executable is exported by the module
executablePath: require(candidate),
version: getElectronPackageVersion(candidate)
};
} catch (error) {
// noop
}
}
return null;
}
/**
* Check if the `v8-profiler` and `v8-debug` native binaries are compatibile with the given
* Electron version.
*
* NOTE: If the `v8-profiler` or the `v8-debug` modules aren't installed at all this function will
* throw an error.
*
* @param Electron version, e.g. 1.3.0.
* @return `true` if the native binaries are compatible.
*/
function isInspectorCompatible(electronVersion: string): boolean {
// require.resolve() will throw if it fails to find the module
let packageDir = path.dirname(
require.resolve(path.join(__dirname, '../node_modules/v8-profiler'))
);
let binaryFile = findNativeModule(
path.join(packageDir, 'package.json'),
{ runtime: 'electron', target: electronVersion }
);
if (!fs.existsSync(binaryFile)) {
return false;
}
packageDir = path.dirname(require.resolve(path.join(__dirname, '../node_modules/v8-debug')));
binaryFile = findNativeModule(
path.join(packageDir, 'package.json'),
{ runtime: 'electron', target: electronVersion }
);
return fs.existsSync(binaryFile);
}
export interface INodeInspectorOptions {
config?: string;
debugPort?: number;
webHost?: string;
webPort?: number;
saveLiveEdit?: boolean;
preload?: boolean;
inject?: boolean; // FIXME: can also be an object
hidden?: string | string[];
stackTraceLimit?: number;
sslKey?: string;
sslCert?: string;
}
/**
* Build an array of command line args that will be passed to `node-inspector`.
*
* Only options whose value differs from the default will be added to the
* argument list, this makes it possible to override `node-inspector` options
* via a config file.
*/
function getNodeInspectorCmdLineArgs(options: INodeInspectorOptions): string[] {
const args: string[] = [];
if (options.config) {
args.push('--config', options.config);
}
if ((options.debugPort != null) && (options.debugPort !== NODE_INSPECTOR_DEFAULTS.debugPort)) {
args.push('-d', options.debugPort.toString());
}
if (options.webHost && (options.webHost !== NODE_INSPECTOR_DEFAULTS.webHost)) {
args.push('--web-host', options.webHost);
}
if ((options.webPort != null) && (options.webPort !== NODE_INSPECTOR_DEFAULTS.webPort)) {
args.push('--web-port', options.webPort.toString());
}
if (options.saveLiveEdit) {
args.push('--save-live-edit', options.saveLiveEdit.toString())
}
if (options.preload === false) {
args.push('--no-preload');
}
if (options.inject === false) {
args.push('--no-inject');
}
if (options.hidden) {
if (options.hidden instanceof Array) {
options.hidden.forEach(pattern => args.push('--hidden', pattern));
} else {
args.push('--hidden', options.hidden);
}
}
if ((options.stackTraceLimit != null) &&
(options.stackTraceLimit !== NODE_INSPECTOR_DEFAULTS.stackTraceLimit)) {
args.push('--stack-trace-limit', options.stackTraceLimit.toString());
}
if (options.sslKey) {
args.push('--ssl-key', options.sslKey);
}
if (options.sslCert) {
args.push('--ssl-cert', options.sslCert);
}
return args;
}
/**
* Launch an Electron process that runs `node-inspector`.
*
* @param electronPath Full path to the Electron executable.
*/
function launchInspector(electronPath: string, options: INodeInspectorOptions): void {
const scriptPath = require.resolve('node-inspector/bin/inspector.js');
const inspector = child_process.fork(
scriptPath, getNodeInspectorCmdLineArgs(options),
{ execPath: electronPath, silent: true }
);
inspector.on('error', (error: Error) => console.error(error));
inspector.on('message', (msg: any) => {
if (msg.event === 'SERVER.LISTENING') {
// node-inspector will print the address to the console,
// so there's no need to do anything here
console.info(`Visit ${msg.address.url} to start debugging.`);
} else if (msg.event === 'SERVER.ERROR') {
console.error(`Cannot start the server: ${msg.error.code}.`);
}
});
inspector.on('close', (exitCode: number, signal: string) => {
process.exit(exitCode);
});
}
/**
* Rebuild the native modules `node-inspector` uses to target the given Electron version.
*/
async function rebuildInspector(
electronPath: string, electronVersion: string, arch?: string
): Promise<void> {
console.log('node-inspector binaries are incompatible or missing.');
console.log('Attempting to rebuild...');
let rebuild: RebuildFunction;
try {
rebuild = require('./rebuild').rebuild;
} catch (error) {
console.log('Failed to load electron-prebuilt, abandoning rebuild.');
throw error;
}
await rebuild(electronPath, electronVersion, arch);
}
export interface IOptions extends INodeInspectorOptions {
/** Path to Electron executable. */
electron?: string;
autoRebuild: boolean;
}
export async function inspect(options: IOptions): Promise<void> {
const electron = getElectronInfo(options.electron);
if (!electron) {
console.log('Electron not found.');
return;
}
if (!isInspectorCompatible(electron.version)) {
if (options.autoRebuild) {
await rebuildInspector(electron.executablePath, electron.version);
} else {
console.warn(
`Native node-inspector modules are incompatible with Electron ${electron.version}, `+
'and auto-rebuild is disabled, node-inspector may fail to run.'
);
}
}
launchInspector(electron.executablePath, options);
}