@lvce-editor/process-explorer 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +3 -0
- package/dist/index.js +873 -0
- package/package.json +74 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Lvce Editor
|
|
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.
|
package/README.md
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,873 @@
|
|
|
1
|
+
import { IpcChildWithWebSocket, IpcChildWithElectronMessagePort, IpcChildWithElectronUtilityProcess, IpcChildWithNodeWorker, IpcChildWithNodeForkedProcess } from '@lvce-editor/ipc';
|
|
2
|
+
import { object, number as number$1, string } from '@lvce-editor/assert';
|
|
3
|
+
import { VError } from '@lvce-editor/verror';
|
|
4
|
+
import { readFile } from 'node:fs/promises';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { execFile as execFile$1 } from 'node:child_process';
|
|
7
|
+
import { promisify } from 'node:util';
|
|
8
|
+
|
|
9
|
+
const Two = '2.0';
|
|
10
|
+
|
|
11
|
+
class AssertionError extends Error {
|
|
12
|
+
constructor(message) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.name = 'AssertionError';
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
const getType = value => {
|
|
18
|
+
switch (typeof value) {
|
|
19
|
+
case 'number':
|
|
20
|
+
return 'number';
|
|
21
|
+
case 'function':
|
|
22
|
+
return 'function';
|
|
23
|
+
case 'string':
|
|
24
|
+
return 'string';
|
|
25
|
+
case 'object':
|
|
26
|
+
if (value === null) {
|
|
27
|
+
return 'null';
|
|
28
|
+
}
|
|
29
|
+
if (Array.isArray(value)) {
|
|
30
|
+
return 'array';
|
|
31
|
+
}
|
|
32
|
+
return 'object';
|
|
33
|
+
case 'boolean':
|
|
34
|
+
return 'boolean';
|
|
35
|
+
default:
|
|
36
|
+
return 'unknown';
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
const number = value => {
|
|
40
|
+
const type = getType(value);
|
|
41
|
+
if (type !== 'number') {
|
|
42
|
+
throw new AssertionError('expected value to be of type number');
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const state$1$1 = {
|
|
47
|
+
callbacks: Object.create(null)
|
|
48
|
+
};
|
|
49
|
+
const set = (id, fn) => {
|
|
50
|
+
state$1$1.callbacks[id] = fn;
|
|
51
|
+
};
|
|
52
|
+
const get = id => {
|
|
53
|
+
return state$1$1.callbacks[id];
|
|
54
|
+
};
|
|
55
|
+
const remove = id => {
|
|
56
|
+
delete state$1$1.callbacks[id];
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const state$2 = {
|
|
60
|
+
id: 0
|
|
61
|
+
};
|
|
62
|
+
const create$3 = () => {
|
|
63
|
+
return ++state$2.id;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const warn = (...args) => {
|
|
67
|
+
console.warn(...args);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const withResolvers$1 = () => {
|
|
71
|
+
/**
|
|
72
|
+
* @type {any}
|
|
73
|
+
*/
|
|
74
|
+
let _resolve;
|
|
75
|
+
const promise = new Promise(resolve => {
|
|
76
|
+
_resolve = resolve;
|
|
77
|
+
});
|
|
78
|
+
return {
|
|
79
|
+
resolve: _resolve,
|
|
80
|
+
promise
|
|
81
|
+
};
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const registerPromise = () => {
|
|
85
|
+
const id = create$3();
|
|
86
|
+
const {
|
|
87
|
+
resolve,
|
|
88
|
+
promise
|
|
89
|
+
} = withResolvers$1();
|
|
90
|
+
set(id, resolve);
|
|
91
|
+
return {
|
|
92
|
+
id,
|
|
93
|
+
promise
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
const resolve = (id, args) => {
|
|
97
|
+
number(id);
|
|
98
|
+
const fn = get(id);
|
|
99
|
+
if (!fn) {
|
|
100
|
+
console.log(args);
|
|
101
|
+
warn(`callback ${id} may already be disposed`);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
fn(args);
|
|
105
|
+
remove(id);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const create$2 = (method, params) => {
|
|
109
|
+
const {
|
|
110
|
+
id,
|
|
111
|
+
promise
|
|
112
|
+
} = registerPromise();
|
|
113
|
+
const message = {
|
|
114
|
+
jsonrpc: Two,
|
|
115
|
+
method,
|
|
116
|
+
params,
|
|
117
|
+
id
|
|
118
|
+
};
|
|
119
|
+
return {
|
|
120
|
+
message,
|
|
121
|
+
promise
|
|
122
|
+
};
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
class JsonRpcError extends Error {
|
|
126
|
+
constructor(message) {
|
|
127
|
+
super(message);
|
|
128
|
+
this.name = 'JsonRpcError';
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const NewLine$1 = '\n';
|
|
133
|
+
|
|
134
|
+
const DomException = 'DOMException';
|
|
135
|
+
const ReferenceError$1 = 'ReferenceError';
|
|
136
|
+
const SyntaxError$1 = 'SyntaxError';
|
|
137
|
+
const TypeError$1 = 'TypeError';
|
|
138
|
+
|
|
139
|
+
const getErrorConstructor = (message, type) => {
|
|
140
|
+
if (type) {
|
|
141
|
+
switch (type) {
|
|
142
|
+
case DomException:
|
|
143
|
+
// @ts-ignore
|
|
144
|
+
return DOMException;
|
|
145
|
+
case TypeError$1:
|
|
146
|
+
return TypeError;
|
|
147
|
+
case SyntaxError$1:
|
|
148
|
+
return SyntaxError;
|
|
149
|
+
case ReferenceError$1:
|
|
150
|
+
return ReferenceError;
|
|
151
|
+
default:
|
|
152
|
+
return Error;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (message.startsWith('TypeError: ')) {
|
|
156
|
+
return TypeError;
|
|
157
|
+
}
|
|
158
|
+
if (message.startsWith('SyntaxError: ')) {
|
|
159
|
+
return SyntaxError;
|
|
160
|
+
}
|
|
161
|
+
if (message.startsWith('ReferenceError: ')) {
|
|
162
|
+
return ReferenceError;
|
|
163
|
+
}
|
|
164
|
+
return Error;
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const constructError = (message, type, name) => {
|
|
168
|
+
const ErrorConstructor = getErrorConstructor(message, type);
|
|
169
|
+
// @ts-ignore
|
|
170
|
+
if (ErrorConstructor === DOMException && name) {
|
|
171
|
+
return new ErrorConstructor(message, name);
|
|
172
|
+
}
|
|
173
|
+
if (ErrorConstructor === Error) {
|
|
174
|
+
const error = new Error(message);
|
|
175
|
+
if (name && name !== 'VError') {
|
|
176
|
+
error.name = name;
|
|
177
|
+
}
|
|
178
|
+
return error;
|
|
179
|
+
}
|
|
180
|
+
return new ErrorConstructor(message);
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const getNewLineIndex = (string, startIndex = undefined) => {
|
|
184
|
+
return string.indexOf(NewLine$1, startIndex);
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const joinLines = lines => {
|
|
188
|
+
return lines.join(NewLine$1);
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const MethodNotFound = -32601;
|
|
192
|
+
const Custom = -32001;
|
|
193
|
+
|
|
194
|
+
const splitLines$1 = lines => {
|
|
195
|
+
return lines.split(NewLine$1);
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
const getParentStack = error => {
|
|
199
|
+
let parentStack = error.stack || error.data || error.message || '';
|
|
200
|
+
if (parentStack.startsWith(' at')) {
|
|
201
|
+
parentStack = error.message + NewLine$1 + parentStack;
|
|
202
|
+
}
|
|
203
|
+
return parentStack;
|
|
204
|
+
};
|
|
205
|
+
const restoreJsonRpcError = error => {
|
|
206
|
+
if (error && error instanceof Error) {
|
|
207
|
+
return error;
|
|
208
|
+
}
|
|
209
|
+
const currentStack = joinLines(splitLines$1(new Error().stack).slice(1));
|
|
210
|
+
if (error && error.code && error.code === MethodNotFound) {
|
|
211
|
+
const restoredError = new JsonRpcError(error.message);
|
|
212
|
+
const parentStack = getParentStack(error);
|
|
213
|
+
restoredError.stack = parentStack + NewLine$1 + currentStack;
|
|
214
|
+
return restoredError;
|
|
215
|
+
}
|
|
216
|
+
if (error && error.message) {
|
|
217
|
+
const restoredError = constructError(error.message, error.type, error.name);
|
|
218
|
+
if (error.data) {
|
|
219
|
+
if (error.data.stack && error.data.type && error.message) {
|
|
220
|
+
restoredError.stack = error.data.type + ': ' + error.message + NewLine$1 + error.data.stack + NewLine$1 + currentStack;
|
|
221
|
+
} else if (error.data.stack) {
|
|
222
|
+
restoredError.stack = error.data.stack;
|
|
223
|
+
}
|
|
224
|
+
if (error.data.codeFrame) {
|
|
225
|
+
// @ts-ignore
|
|
226
|
+
restoredError.codeFrame = error.data.codeFrame;
|
|
227
|
+
}
|
|
228
|
+
if (error.data.code) {
|
|
229
|
+
// @ts-ignore
|
|
230
|
+
restoredError.code = error.data.code;
|
|
231
|
+
}
|
|
232
|
+
if (error.data.type) {
|
|
233
|
+
// @ts-ignore
|
|
234
|
+
restoredError.name = error.data.type;
|
|
235
|
+
}
|
|
236
|
+
} else {
|
|
237
|
+
if (error.stack) {
|
|
238
|
+
// TODO accessing stack might be slow
|
|
239
|
+
const lowerStack = restoredError.stack || '';
|
|
240
|
+
// @ts-ignore
|
|
241
|
+
const indexNewLine = getNewLineIndex(lowerStack);
|
|
242
|
+
const parentStack = getParentStack(error);
|
|
243
|
+
// @ts-ignore
|
|
244
|
+
restoredError.stack = parentStack + lowerStack.slice(indexNewLine);
|
|
245
|
+
}
|
|
246
|
+
if (error.codeFrame) {
|
|
247
|
+
// @ts-ignore
|
|
248
|
+
restoredError.codeFrame = error.codeFrame;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return restoredError;
|
|
252
|
+
}
|
|
253
|
+
if (typeof error === 'string') {
|
|
254
|
+
return new Error(`JsonRpc Error: ${error}`);
|
|
255
|
+
}
|
|
256
|
+
return new Error(`JsonRpc Error: ${error}`);
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
const unwrapJsonRpcResult = responseMessage => {
|
|
260
|
+
if ('error' in responseMessage) {
|
|
261
|
+
const restoredError = restoreJsonRpcError(responseMessage.error);
|
|
262
|
+
throw restoredError;
|
|
263
|
+
}
|
|
264
|
+
if ('result' in responseMessage) {
|
|
265
|
+
return responseMessage.result;
|
|
266
|
+
}
|
|
267
|
+
throw new JsonRpcError('unexpected response message');
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
const create$1 = (message, error) => {
|
|
271
|
+
return {
|
|
272
|
+
jsonrpc: Two,
|
|
273
|
+
id: message.id,
|
|
274
|
+
error
|
|
275
|
+
};
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
const E_COMMAND_NOT_FOUND = 'E_COMMAND_NOT_FOUND';
|
|
279
|
+
|
|
280
|
+
const getErrorProperty = (error, prettyError) => {
|
|
281
|
+
if (error && error.code === E_COMMAND_NOT_FOUND) {
|
|
282
|
+
return {
|
|
283
|
+
code: MethodNotFound,
|
|
284
|
+
message: error.message,
|
|
285
|
+
data: error.stack
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
return {
|
|
289
|
+
code: Custom,
|
|
290
|
+
message: prettyError.message,
|
|
291
|
+
data: {
|
|
292
|
+
stack: prettyError.stack,
|
|
293
|
+
codeFrame: prettyError.codeFrame,
|
|
294
|
+
type: prettyError.type,
|
|
295
|
+
code: prettyError.code
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
};
|
|
299
|
+
const getErrorResponse = (message, error, ipc, preparePrettyError, logError) => {
|
|
300
|
+
const prettyError = preparePrettyError(error);
|
|
301
|
+
logError(error, prettyError);
|
|
302
|
+
const errorProperty = getErrorProperty(error, prettyError);
|
|
303
|
+
return create$1(message, errorProperty);
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
const create = (message, result) => {
|
|
307
|
+
return {
|
|
308
|
+
jsonrpc: Two,
|
|
309
|
+
id: message.id,
|
|
310
|
+
result: result ?? null
|
|
311
|
+
};
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
const getSuccessResponse = (message, result) => {
|
|
315
|
+
const resultProperty = result ?? null;
|
|
316
|
+
return create(message, resultProperty);
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
const getResponse = async (message, ipc, execute, preparePrettyError, logError, requiresSocket) => {
|
|
320
|
+
try {
|
|
321
|
+
const result = requiresSocket(message.method) ? await execute(message.method, ipc, ...message.params) : await execute(message.method, ...message.params);
|
|
322
|
+
return getSuccessResponse(message, result);
|
|
323
|
+
} catch (error) {
|
|
324
|
+
return getErrorResponse(message, error, ipc, preparePrettyError, logError);
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
const handleJsonRpcMessage = async (ipc, message, execute, resolve, preparePrettyError, logError, requiresSocket) => {
|
|
329
|
+
if ('id' in message) {
|
|
330
|
+
if ('method' in message) {
|
|
331
|
+
const response = await getResponse(message, ipc, execute, preparePrettyError, logError, requiresSocket);
|
|
332
|
+
try {
|
|
333
|
+
ipc.send(response);
|
|
334
|
+
} catch (error) {
|
|
335
|
+
const errorResponse = getErrorResponse(message, error, ipc, preparePrettyError, logError);
|
|
336
|
+
ipc.send(errorResponse);
|
|
337
|
+
}
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
resolve(message.id, message);
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
if ('method' in message) {
|
|
344
|
+
await getResponse(message, ipc, execute, preparePrettyError, logError, requiresSocket);
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
throw new JsonRpcError('unexpected message');
|
|
348
|
+
};
|
|
349
|
+
const invoke$1 = async (ipc, method, ...params) => {
|
|
350
|
+
const {
|
|
351
|
+
message,
|
|
352
|
+
promise
|
|
353
|
+
} = create$2(method, params);
|
|
354
|
+
ipc.send(message);
|
|
355
|
+
const responseMessage = await promise;
|
|
356
|
+
const result = unwrapJsonRpcResult(responseMessage);
|
|
357
|
+
return result;
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
const state$1 = {
|
|
361
|
+
commands: Object.create(null),
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
const registerCommand = (key, fn) => {
|
|
365
|
+
state$1.commands[key] = fn;
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
const registerCommands = (commandMap) => {
|
|
369
|
+
for (const [key, value] of Object.entries(commandMap)) {
|
|
370
|
+
registerCommand(key, value);
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
const getCommand = (key) => {
|
|
375
|
+
return state$1.commands[key]
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
const execute = (command, ...args) => {
|
|
379
|
+
const fn = getCommand(command);
|
|
380
|
+
if (!fn) {
|
|
381
|
+
throw new Error(`Command not found ${command}`)
|
|
382
|
+
}
|
|
383
|
+
return fn(...args)
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
const preparePrettyError = (error) => {
|
|
387
|
+
return error
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
const logError = (error) => {
|
|
391
|
+
console.error(error);
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
const requiresSocket = () => {
|
|
395
|
+
return false
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
const handleMessage = (event) => {
|
|
399
|
+
return handleJsonRpcMessage(event.target, event.data, execute, resolve, preparePrettyError, logError, requiresSocket)
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
const handleIpc = (ipc) => {
|
|
403
|
+
if ('addEventListener' in ipc) {
|
|
404
|
+
ipc.addEventListener('message', handleMessage);
|
|
405
|
+
} else {
|
|
406
|
+
// deprecated
|
|
407
|
+
ipc.on('message', handleMessage);
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
const NodeWorker = 1;
|
|
412
|
+
const NodeForkedProcess = 2;
|
|
413
|
+
const ElectronUtilityProcess = 3;
|
|
414
|
+
const ElectronMessagePort = 4;
|
|
415
|
+
const WebSocket = 6;
|
|
416
|
+
|
|
417
|
+
const Auto = () => {
|
|
418
|
+
const { argv } = process;
|
|
419
|
+
if (argv.includes('--ipc-type=node-worker')) {
|
|
420
|
+
return NodeWorker
|
|
421
|
+
}
|
|
422
|
+
if (argv.includes('--ipc-type=node-forked-process')) {
|
|
423
|
+
return NodeForkedProcess
|
|
424
|
+
}
|
|
425
|
+
if (argv.includes('--ipc-type=electron-utility-process')) {
|
|
426
|
+
return ElectronUtilityProcess
|
|
427
|
+
}
|
|
428
|
+
throw new Error(`[shared-process] unknown ipc type`)
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
const getModule$1 = (method) => {
|
|
432
|
+
switch (method) {
|
|
433
|
+
case NodeForkedProcess:
|
|
434
|
+
return IpcChildWithNodeForkedProcess
|
|
435
|
+
case NodeWorker:
|
|
436
|
+
return IpcChildWithNodeWorker
|
|
437
|
+
case ElectronUtilityProcess:
|
|
438
|
+
return IpcChildWithElectronUtilityProcess
|
|
439
|
+
case ElectronMessagePort:
|
|
440
|
+
return IpcChildWithElectronMessagePort
|
|
441
|
+
case WebSocket:
|
|
442
|
+
return IpcChildWithWebSocket
|
|
443
|
+
default:
|
|
444
|
+
throw new Error('unexpected ipc type')
|
|
445
|
+
}
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
const listen$1 = async ({ method, ...params }) => {
|
|
449
|
+
const module = await getModule$1(method);
|
|
450
|
+
// @ts-ignore
|
|
451
|
+
const rawIpc = await module.listen(params);
|
|
452
|
+
// @ts-ignore
|
|
453
|
+
if (module.signal) {
|
|
454
|
+
// @ts-ignore
|
|
455
|
+
module.signal(rawIpc);
|
|
456
|
+
}
|
|
457
|
+
// @ts-ignore
|
|
458
|
+
const ipc = module.wrap(rawIpc);
|
|
459
|
+
return ipc
|
|
460
|
+
};
|
|
461
|
+
|
|
462
|
+
const listen = async () => {
|
|
463
|
+
const ipc = await listen$1({ method: Auto() });
|
|
464
|
+
handleIpc(ipc);
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
const MainProcess = -5;
|
|
468
|
+
|
|
469
|
+
const state = {
|
|
470
|
+
/**
|
|
471
|
+
* @type {any}
|
|
472
|
+
*/
|
|
473
|
+
ipc: undefined,
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
const invoke = (method, ...params) => {
|
|
477
|
+
return invoke$1(state.ipc, method, ...params)
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
const handleElectronMessagePort = async (messagePort, ipcId) => {
|
|
481
|
+
object(messagePort);
|
|
482
|
+
// Assert.number(ipcId)
|
|
483
|
+
const ipc = await listen$1({
|
|
484
|
+
method: ElectronMessagePort,
|
|
485
|
+
messagePort,
|
|
486
|
+
});
|
|
487
|
+
handleIpc(ipc);
|
|
488
|
+
if (ipcId === MainProcess) {
|
|
489
|
+
state.ipc = ipc;
|
|
490
|
+
}
|
|
491
|
+
};
|
|
492
|
+
|
|
493
|
+
const getMainProcessId = () => {
|
|
494
|
+
return process.ppid
|
|
495
|
+
};
|
|
496
|
+
|
|
497
|
+
const isWindows = process.platform === 'win32';
|
|
498
|
+
|
|
499
|
+
const getModule = () => {
|
|
500
|
+
if (isWindows) {
|
|
501
|
+
return Promise.resolve().then(function () { return ListProcessesWithMemoryUsageWindows; })
|
|
502
|
+
}
|
|
503
|
+
return Promise.resolve().then(function () { return ListProcessesWithMemoryUsageUnix; })
|
|
504
|
+
};
|
|
505
|
+
|
|
506
|
+
const listProcessesWithMemoryUsage$2 = async (rootPid) => {
|
|
507
|
+
const module = await getModule();
|
|
508
|
+
return module.listProcessesWithMemoryUsage(rootPid)
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
const commandMap = {
|
|
512
|
+
'HandleElectronMessagePort.handleElectronMessagePort': handleElectronMessagePort,
|
|
513
|
+
'ProcessId.getMainProcessId': getMainProcessId,
|
|
514
|
+
'ListProcessesWithMemoryUsage.listProcessesWithMemoryUsage': listProcessesWithMemoryUsage$2,
|
|
515
|
+
// 'ElectronContextMenu.openContextMenu': ElectronWebContentsView.handleContextMenu,
|
|
516
|
+
};
|
|
517
|
+
|
|
518
|
+
const main = async () => {
|
|
519
|
+
registerCommands(commandMap);
|
|
520
|
+
await listen();
|
|
521
|
+
};
|
|
522
|
+
|
|
523
|
+
main();
|
|
524
|
+
|
|
525
|
+
const getName = (pid, cmd, rootPid, pidMap) => {
|
|
526
|
+
number$1(pid);
|
|
527
|
+
string(cmd);
|
|
528
|
+
number$1(rootPid);
|
|
529
|
+
object(pidMap);
|
|
530
|
+
if (pid === rootPid) {
|
|
531
|
+
return 'main'
|
|
532
|
+
}
|
|
533
|
+
if (cmd.includes('--type=zygote')) {
|
|
534
|
+
return 'zygote'
|
|
535
|
+
}
|
|
536
|
+
if (cmd.includes('--type=gpu-process')) {
|
|
537
|
+
return 'gpu-process'
|
|
538
|
+
}
|
|
539
|
+
if (cmd.includes('extensionHostMain.js')) {
|
|
540
|
+
return 'extension-host'
|
|
541
|
+
}
|
|
542
|
+
if (cmd.includes('ptyHostMain.js')) {
|
|
543
|
+
return 'pty-host'
|
|
544
|
+
}
|
|
545
|
+
if (cmd.includes('--lvce-window-kind=process-explorer')) {
|
|
546
|
+
return 'process-explorer'
|
|
547
|
+
}
|
|
548
|
+
if (pid in pidMap) {
|
|
549
|
+
return pidMap[pid] || `<unknown>`
|
|
550
|
+
}
|
|
551
|
+
if (cmd.includes('--type=renderer')) {
|
|
552
|
+
return `renderer`
|
|
553
|
+
}
|
|
554
|
+
if (cmd.includes('--type=utility')) {
|
|
555
|
+
return 'utility'
|
|
556
|
+
}
|
|
557
|
+
if (cmd.includes('tsserver.js')) {
|
|
558
|
+
return 'tsserver.js'
|
|
559
|
+
}
|
|
560
|
+
if (cmd.includes('typingsInstaller.js')) {
|
|
561
|
+
return 'typingsInstaller.js'
|
|
562
|
+
}
|
|
563
|
+
if (cmd.includes('extensionHostHelperProcessMain.js')) {
|
|
564
|
+
return 'extension-host-helper-process'
|
|
565
|
+
}
|
|
566
|
+
if (cmd.includes('/bin/rg') || cmd.includes('rg.exe')) {
|
|
567
|
+
return 'ripgrep'
|
|
568
|
+
}
|
|
569
|
+
if (cmd.startsWith('bash')) {
|
|
570
|
+
return 'bash'
|
|
571
|
+
}
|
|
572
|
+
if (cmd.startsWith(`/opt/sublime_text/sublime_text `)) {
|
|
573
|
+
return 'sublime-text'
|
|
574
|
+
}
|
|
575
|
+
if (cmd.includes('\\conhost.exe')) {
|
|
576
|
+
return 'conhost.exe'
|
|
577
|
+
}
|
|
578
|
+
return `${cmd}`
|
|
579
|
+
};
|
|
580
|
+
|
|
581
|
+
const ENOENT = 'ENOENT';
|
|
582
|
+
const ERR_DLOPEN_FAILED = 'ERR_DLOPEN_FAILED';
|
|
583
|
+
const ESRCH = 'ESRCH';
|
|
584
|
+
|
|
585
|
+
const isDlOpenError = (error) => {
|
|
586
|
+
return error && error instanceof Error && 'code' in error && error.code === ERR_DLOPEN_FAILED
|
|
587
|
+
};
|
|
588
|
+
|
|
589
|
+
const loadWindowProcessTree = async () => {
|
|
590
|
+
try {
|
|
591
|
+
return await import('@vscode/windows-process-tree')
|
|
592
|
+
} catch (error) {
|
|
593
|
+
if (isDlOpenError(error)) {
|
|
594
|
+
throw new VError(
|
|
595
|
+
`Failed to load windows process tree: The native module "@vscode/windows-process-tree" is not compatible with this node version and must be compiled against a matching electron version using electron-rebuild`,
|
|
596
|
+
)
|
|
597
|
+
}
|
|
598
|
+
throw new VError(error, `Failed to load windows process tree`)
|
|
599
|
+
}
|
|
600
|
+
};
|
|
601
|
+
|
|
602
|
+
const withResolvers = () => {
|
|
603
|
+
/**
|
|
604
|
+
* @type {any}
|
|
605
|
+
*/
|
|
606
|
+
let _resolve;
|
|
607
|
+
/**
|
|
608
|
+
* @type {any}
|
|
609
|
+
*/
|
|
610
|
+
let _reject;
|
|
611
|
+
const promise = new Promise((resolve, reject) => {
|
|
612
|
+
_resolve = resolve;
|
|
613
|
+
_reject = reject;
|
|
614
|
+
});
|
|
615
|
+
return {
|
|
616
|
+
resolve: _resolve,
|
|
617
|
+
reject: _reject,
|
|
618
|
+
promise,
|
|
619
|
+
}
|
|
620
|
+
};
|
|
621
|
+
|
|
622
|
+
/**
|
|
623
|
+
*
|
|
624
|
+
* @param {number} rootPid
|
|
625
|
+
* @param {number} flags
|
|
626
|
+
* @returns {Promise<any[] | undefined>}
|
|
627
|
+
*/
|
|
628
|
+
const getProcessList = async (rootPid, flags) => {
|
|
629
|
+
const WindowsProcessTree = await loadWindowProcessTree();
|
|
630
|
+
const { resolve, promise } = withResolvers();
|
|
631
|
+
WindowsProcessTree.getProcessList(rootPid, resolve, flags);
|
|
632
|
+
return promise
|
|
633
|
+
};
|
|
634
|
+
|
|
635
|
+
/**
|
|
636
|
+
*
|
|
637
|
+
* @param {any[]} processList
|
|
638
|
+
* @returns Promise< WindowsProcessTree.IProcessCpuInfo[]>
|
|
639
|
+
*/
|
|
640
|
+
const addCpuUsage = async (processList) => {
|
|
641
|
+
const WindowsProcessTree = await loadWindowProcessTree();
|
|
642
|
+
const { resolve, promise } = withResolvers();
|
|
643
|
+
WindowsProcessTree.getProcessCpuUsage(processList, resolve);
|
|
644
|
+
return promise
|
|
645
|
+
};
|
|
646
|
+
|
|
647
|
+
const Memory = 1;
|
|
648
|
+
const CommandLine = 2;
|
|
649
|
+
|
|
650
|
+
const createPidMap = async () => {
|
|
651
|
+
return invoke('CreatePidMap.createPidMap')
|
|
652
|
+
};
|
|
653
|
+
|
|
654
|
+
// listProcesses windows implementation based on https://github.com/microsoft/vscode/blob/c0769274fa136b45799edeccc0d0a2f645b75caf/src/vs/base/node/ps.ts (License MIT)
|
|
655
|
+
|
|
656
|
+
|
|
657
|
+
/**
|
|
658
|
+
* @param {import('@vscode/windows-process-tree').IProcessCpuInfo} item
|
|
659
|
+
* @param {number} rootPid
|
|
660
|
+
* @param {object} pidMap
|
|
661
|
+
*/
|
|
662
|
+
const toResultItem = (item, rootPid, pidMap) => {
|
|
663
|
+
return {
|
|
664
|
+
name: getName(item.pid, item.commandLine, rootPid, pidMap),
|
|
665
|
+
pid: item.pid,
|
|
666
|
+
ppid: item.ppid,
|
|
667
|
+
memory: item.memory,
|
|
668
|
+
cmd: item.commandLine,
|
|
669
|
+
}
|
|
670
|
+
};
|
|
671
|
+
|
|
672
|
+
/**
|
|
673
|
+
*
|
|
674
|
+
* @param {import('@vscode/windows-process-tree').IProcessCpuInfo[]} completeProcessList
|
|
675
|
+
* @param {number} rootPid
|
|
676
|
+
*/
|
|
677
|
+
const toResult = (completeProcessList, rootPid, pidMap) => {
|
|
678
|
+
const results = [];
|
|
679
|
+
for (const item of completeProcessList) {
|
|
680
|
+
results.push(toResultItem(item, rootPid, pidMap));
|
|
681
|
+
}
|
|
682
|
+
return results
|
|
683
|
+
};
|
|
684
|
+
|
|
685
|
+
const listProcessesWithMemoryUsage$1 = async (rootPid) => {
|
|
686
|
+
try {
|
|
687
|
+
const processList = await getProcessList(rootPid, CommandLine | Memory);
|
|
688
|
+
if (!processList) {
|
|
689
|
+
throw new VError(`Root process ${rootPid} not found`)
|
|
690
|
+
}
|
|
691
|
+
const pidMap = await createPidMap();
|
|
692
|
+
const completeProcessList = await addCpuUsage(processList);
|
|
693
|
+
const result = toResult(completeProcessList, rootPid, pidMap);
|
|
694
|
+
return result
|
|
695
|
+
} catch (error) {
|
|
696
|
+
// @ts-ignore
|
|
697
|
+
throw new VError(error, `Failed to list processes`)
|
|
698
|
+
}
|
|
699
|
+
};
|
|
700
|
+
|
|
701
|
+
const ListProcessesWithMemoryUsageWindows = {
|
|
702
|
+
__proto__: null,
|
|
703
|
+
listProcessesWithMemoryUsage: listProcessesWithMemoryUsage$1
|
|
704
|
+
};
|
|
705
|
+
|
|
706
|
+
const Utf8 = 'utf8';
|
|
707
|
+
|
|
708
|
+
const isEnoentErrorWindows = (error) => {
|
|
709
|
+
return error && error.message && error.message.includes('The system cannot find the path specified.')
|
|
710
|
+
};
|
|
711
|
+
|
|
712
|
+
const isEnoentErrorLinux = (error) => {
|
|
713
|
+
return error.code === ENOENT
|
|
714
|
+
};
|
|
715
|
+
|
|
716
|
+
const isEnoentError = (error) => {
|
|
717
|
+
if (!error) {
|
|
718
|
+
return false
|
|
719
|
+
}
|
|
720
|
+
return isEnoentErrorLinux(error) || isEnoentErrorWindows(error)
|
|
721
|
+
};
|
|
722
|
+
|
|
723
|
+
const isEsrchError = (error) => {
|
|
724
|
+
return error && error.code === ESRCH
|
|
725
|
+
};
|
|
726
|
+
|
|
727
|
+
const isMacOs = process.platform === 'darwin';
|
|
728
|
+
|
|
729
|
+
const EmptyString = '';
|
|
730
|
+
const NewLine = '\n';
|
|
731
|
+
const Space = ' ';
|
|
732
|
+
|
|
733
|
+
const parseMemory = (content) => {
|
|
734
|
+
const trimmedContent = content.trim();
|
|
735
|
+
const numberBlocks = trimmedContent.split(Space);
|
|
736
|
+
const pageSize = 4096;
|
|
737
|
+
const rss = Number.parseInt(numberBlocks[1]) * pageSize;
|
|
738
|
+
const shared = Number.parseInt(numberBlocks[2]) * pageSize;
|
|
739
|
+
const memory = rss - shared;
|
|
740
|
+
return memory
|
|
741
|
+
};
|
|
742
|
+
|
|
743
|
+
const getContent = async (pid) => {
|
|
744
|
+
try {
|
|
745
|
+
const filePath = join('/proc', `${pid}`, 'statm');
|
|
746
|
+
const content = await readFile(filePath, Utf8);
|
|
747
|
+
return content
|
|
748
|
+
} catch (error) {
|
|
749
|
+
if (isEnoentError(error) || isEsrchError(error)) {
|
|
750
|
+
return ''
|
|
751
|
+
}
|
|
752
|
+
throw error
|
|
753
|
+
}
|
|
754
|
+
};
|
|
755
|
+
|
|
756
|
+
const getAccurateMemoryUsage = async (pid) => {
|
|
757
|
+
try {
|
|
758
|
+
number$1(pid);
|
|
759
|
+
if (isMacOs) {
|
|
760
|
+
return 0
|
|
761
|
+
}
|
|
762
|
+
const content = await getContent(pid);
|
|
763
|
+
if (!content) {
|
|
764
|
+
return -1
|
|
765
|
+
}
|
|
766
|
+
const memory = parseMemory(content);
|
|
767
|
+
return memory
|
|
768
|
+
} catch (error) {
|
|
769
|
+
throw new VError(error, 'Failed to get accurate memory usage')
|
|
770
|
+
}
|
|
771
|
+
};
|
|
772
|
+
|
|
773
|
+
const addAccurateMemoryUsage = async (process) => {
|
|
774
|
+
const accurateMemoryUsage = await getAccurateMemoryUsage(process.pid);
|
|
775
|
+
return {
|
|
776
|
+
...process,
|
|
777
|
+
memory: accurateMemoryUsage,
|
|
778
|
+
}
|
|
779
|
+
};
|
|
780
|
+
|
|
781
|
+
const SIGINT = 'SIGINT';
|
|
782
|
+
|
|
783
|
+
const execFile = promisify(execFile$1);
|
|
784
|
+
|
|
785
|
+
const getPsOutput = async () => {
|
|
786
|
+
try {
|
|
787
|
+
const { stdout } = await execFile('ps', ['-ax', '-o', 'pid=,ppid=,pcpu=,pmem=,command=']);
|
|
788
|
+
return stdout.trim()
|
|
789
|
+
} catch (error) {
|
|
790
|
+
// @ts-ignore
|
|
791
|
+
if (error && error.signal === SIGINT) {
|
|
792
|
+
return ''
|
|
793
|
+
}
|
|
794
|
+
throw new VError(error, `Failed to execute ps`)
|
|
795
|
+
}
|
|
796
|
+
};
|
|
797
|
+
|
|
798
|
+
const hasPositiveMemoryUsage = (process) => {
|
|
799
|
+
return process.memory >= 0
|
|
800
|
+
};
|
|
801
|
+
|
|
802
|
+
const splitLines = (lines) => {
|
|
803
|
+
return lines.split(NewLine)
|
|
804
|
+
};
|
|
805
|
+
|
|
806
|
+
// parse ps output based on vscode https://github.com/microsoft/vscode/blob/c0769274fa136b45799edeccc0d0a2f645b75caf/src/vs/base/node/ps.ts (License MIT)
|
|
807
|
+
|
|
808
|
+
|
|
809
|
+
const PID_CMD = /^\s*(\d+)\s+(\d+)\s+([\d.]+)\s+([\d.]+)\s+(.+)$/s;
|
|
810
|
+
|
|
811
|
+
const parsePsOutputLine = (line) => {
|
|
812
|
+
string(line);
|
|
813
|
+
const matches = PID_CMD.exec(line.trim());
|
|
814
|
+
if (matches && matches.length === 6) {
|
|
815
|
+
return {
|
|
816
|
+
pid: Number.parseInt(matches[1]),
|
|
817
|
+
ppid: Number.parseInt(matches[2]),
|
|
818
|
+
cmd: matches[5],
|
|
819
|
+
// load: parseInt(matches[3]),
|
|
820
|
+
// mem: parseInt(matches[4]),
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
throw new Error(`line could not be parsed: ${line}`)
|
|
824
|
+
};
|
|
825
|
+
|
|
826
|
+
const parsePsOutput = (stdout, rootPid, pidMap) => {
|
|
827
|
+
string(stdout);
|
|
828
|
+
number$1(rootPid);
|
|
829
|
+
object(pidMap);
|
|
830
|
+
if (stdout === EmptyString) {
|
|
831
|
+
return []
|
|
832
|
+
}
|
|
833
|
+
const lines = splitLines(stdout);
|
|
834
|
+
const result = [];
|
|
835
|
+
const depthMap = Object.create(null);
|
|
836
|
+
depthMap[rootPid] = 1;
|
|
837
|
+
const parsedLines = lines.map(parsePsOutputLine);
|
|
838
|
+
for (const parsedLine of parsedLines) {
|
|
839
|
+
const { pid, ppid, cmd } = parsedLine;
|
|
840
|
+
const depth = pid === rootPid ? 1 : depthMap[ppid];
|
|
841
|
+
if (!depth) {
|
|
842
|
+
continue
|
|
843
|
+
}
|
|
844
|
+
result.push({
|
|
845
|
+
...parsedLine,
|
|
846
|
+
depth,
|
|
847
|
+
name: getName(pid, cmd, rootPid, pidMap),
|
|
848
|
+
});
|
|
849
|
+
depthMap[pid] = depth + 1;
|
|
850
|
+
}
|
|
851
|
+
return result
|
|
852
|
+
};
|
|
853
|
+
|
|
854
|
+
const listProcessesWithMemoryUsage = async (rootPid) => {
|
|
855
|
+
// console.time('getPsOutput')
|
|
856
|
+
const stdout = await getPsOutput();
|
|
857
|
+
const pidMap = await createPidMap();
|
|
858
|
+
// console.log({ stdout })
|
|
859
|
+
// console.timeEnd('getPsOutput')
|
|
860
|
+
// console.time('parsePsOutput')
|
|
861
|
+
const parsed = parsePsOutput(stdout, rootPid, pidMap);
|
|
862
|
+
// console.timeEnd('parsePsOutput')
|
|
863
|
+
// console.time('addAccurateMemoryUsage')
|
|
864
|
+
const parsedWithAccurateMemoryUsage = await Promise.all(parsed.map(addAccurateMemoryUsage));
|
|
865
|
+
// console.timeEnd('addAccurateMemoryUsage')
|
|
866
|
+
const filtered = parsedWithAccurateMemoryUsage.filter(hasPositiveMemoryUsage);
|
|
867
|
+
return filtered
|
|
868
|
+
};
|
|
869
|
+
|
|
870
|
+
const ListProcessesWithMemoryUsageUnix = {
|
|
871
|
+
__proto__: null,
|
|
872
|
+
listProcessesWithMemoryUsage
|
|
873
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lvce-editor/process-explorer",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Process Explorer",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"Lvce Editor"
|
|
9
|
+
],
|
|
10
|
+
"author": "Lvce Editor",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "https://github.com/lvce-editor/lvce-editor.git",
|
|
15
|
+
"directory": "packages/process-explorer"
|
|
16
|
+
},
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": ">=18"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@lvce-editor/assert": "^1.2.0",
|
|
22
|
+
"@lvce-editor/ipc": "^8.2.1",
|
|
23
|
+
"@lvce-editor/json-rpc": "^1.3.0",
|
|
24
|
+
"@lvce-editor/verror": "^1.2.0"
|
|
25
|
+
},
|
|
26
|
+
"optionalDependencies": {
|
|
27
|
+
"@vscode/windows-process-tree": "^0.6.0"
|
|
28
|
+
},
|
|
29
|
+
"xo": {
|
|
30
|
+
"rules": {
|
|
31
|
+
"unicorn/filename-case": "off",
|
|
32
|
+
"indent": "off",
|
|
33
|
+
"semi": "off",
|
|
34
|
+
"no-unused-vars": "off",
|
|
35
|
+
"unicorn/numeric-separators-style": "off",
|
|
36
|
+
"no-extra-semi": "off",
|
|
37
|
+
"arrow-body-style": "off",
|
|
38
|
+
"padded-blocks": "off",
|
|
39
|
+
"capitalized-comments": "off",
|
|
40
|
+
"padding-line-between-statements": "off",
|
|
41
|
+
"arrow-parens": "off",
|
|
42
|
+
"no-warning-comments": "off",
|
|
43
|
+
"array-bracket-spacing": "off",
|
|
44
|
+
"comma-spacing": "off",
|
|
45
|
+
"unicorn/no-array-callback-reference": "off",
|
|
46
|
+
"comma-dangle": "off",
|
|
47
|
+
"operator-linebreak": "off",
|
|
48
|
+
"no-case-declarations": "off",
|
|
49
|
+
"no-undef": "off",
|
|
50
|
+
"object-curly-spacing": "off",
|
|
51
|
+
"object-shorthand": "off",
|
|
52
|
+
"complexity": "off",
|
|
53
|
+
"no-labels": "off",
|
|
54
|
+
"no-multi-assign": "off",
|
|
55
|
+
"max-params": "off",
|
|
56
|
+
"no-bitwise": "off",
|
|
57
|
+
"unicorn/prefer-math-trunc": "off",
|
|
58
|
+
"no-await-in-loop": "off",
|
|
59
|
+
"unicorn/prefer-add-event-listener": "off",
|
|
60
|
+
"no-unused-expressions": "off",
|
|
61
|
+
"node/prefer-global/process": "off",
|
|
62
|
+
"unicorn/prevent-abbreviations": "off",
|
|
63
|
+
"unicorn/no-process-exit": "off",
|
|
64
|
+
"quotes": "off",
|
|
65
|
+
"n/prefer-global/process": [
|
|
66
|
+
"error",
|
|
67
|
+
"always"
|
|
68
|
+
]
|
|
69
|
+
},
|
|
70
|
+
"ignores": [
|
|
71
|
+
"distmin"
|
|
72
|
+
]
|
|
73
|
+
}
|
|
74
|
+
}
|