@lvce-editor/pty-host 0.15.15 → 0.15.17
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/package.json +5 -2
- package/src/parts/Assert/Assert.js +7 -5
- package/src/parts/AssertionError/AssertionError.js +6 -0
- package/src/parts/CleanStack/CleanStack.js +93 -0
- package/src/parts/Command/Command.js +9 -0
- package/src/parts/CommandMap/CommandMap.js +10 -0
- package/src/parts/CommandNotFoundError/CommandNotFoundError.js +9 -0
- package/src/parts/CommandState/CommandState.js +17 -0
- package/src/parts/EncodingType/EncodingType.js +1 -0
- package/src/parts/ErrorCodes/ErrorCodes.js +18 -0
- package/src/parts/FirstWebSocketEventType/FirstWebSocketEventType.js +2 -0
- package/src/parts/GetErrorResponse/GetErrorResponse.js +56 -0
- package/src/parts/GetFirstWebSocketEvent/GetFirstWebSocketEvent.js +41 -0
- package/src/parts/GetResponse/GetResponse.js +15 -0
- package/src/parts/GetSuccessResponse/GetSuccessResponse.js +12 -0
- package/src/parts/HandleElectronMessagePort/HandleElectronMessagePort.js +14 -0
- package/src/parts/HandleIpc/HandleIpc.js +13 -0
- package/src/parts/HandleWebSocket/HandleWebSocket.js +18 -0
- package/src/parts/IpcChild/IpcChild.js +3 -2
- package/src/parts/IpcChildModule/IpcChildModule.js +6 -0
- package/src/parts/IpcChildType/IpcChildType.js +6 -0
- package/src/parts/IpcChildWithElectronMessagePort/IpcChildWithElectronMessagePort.js +33 -0
- package/src/parts/IpcChildWithElectronUtilityProcess/IpcChildWithElectronUtilityProcess.js +46 -0
- package/src/parts/IpcChildWithNodeForkedProcess/IpcChildWithNodeForkedProcess.js +17 -1
- package/src/parts/IpcChildWithWebSocket/IpcChildWithWebSocket.js +51 -0
- package/src/parts/IsWebSocketOpen/IsWebSocketOpen.js +5 -0
- package/src/parts/JoinLines/JoinLines.js +3 -0
- package/src/parts/Json/Json.js +17 -0
- package/src/parts/JsonError/JsonError.js +64 -0
- package/src/parts/JsonParsingError/JsonParsingError.js +17 -0
- package/src/parts/JsonRpcErrorCode/JsonRpcErrorCode.js +2 -0
- package/src/parts/JsonRpcVersion/JsonRpcVersion.js +1 -0
- package/src/parts/Logger/Logger.js +50 -0
- package/src/parts/Main/Main.js +11 -0
- package/src/parts/PrettyError/PrettyError.js +139 -0
- package/src/parts/PrintPrettyError/PrintPrettyError.js +5 -0
- package/src/parts/Pty/Pty.js +22 -10
- package/src/parts/PtyController/PtyController.js +16 -8
- package/src/parts/RequiresSocket/RequiresSocket.js +5 -0
- package/src/parts/SplitLines/SplitLines.js +3 -0
- package/src/parts/WebSocketServer/WebSocketServer.ipc.js +7 -0
- package/src/parts/WebSocketServer/WebSocketServer.js +24 -0
- package/src/ptyHostMain.cjs +1 -0
- package/src/ptyHostMain.js +2 -21
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lvce-editor/pty-host",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.17",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -16,8 +16,11 @@
|
|
|
16
16
|
"node": ">=16"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
+
"@babel/code-frame": "^7.21.4",
|
|
19
20
|
"debug": "^4.3.4",
|
|
20
|
-
"
|
|
21
|
+
"lines-and-columns": "^2.0.3",
|
|
22
|
+
"verror": "^1.10.1",
|
|
23
|
+
"ws": "^8.13.0"
|
|
21
24
|
},
|
|
22
25
|
"optionalDependencies": {
|
|
23
26
|
"node-pty": "^0.11.0-beta27"
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { AssertionError } from '../AssertionError/AssertionError.js'
|
|
2
|
+
|
|
1
3
|
const getType = (value) => {
|
|
2
4
|
switch (typeof value) {
|
|
3
5
|
case 'number':
|
|
@@ -24,34 +26,34 @@ const getType = (value) => {
|
|
|
24
26
|
export const object = (value) => {
|
|
25
27
|
const type = getType(value)
|
|
26
28
|
if (type !== 'object') {
|
|
27
|
-
throw new
|
|
29
|
+
throw new AssertionError('expected value to be of type object')
|
|
28
30
|
}
|
|
29
31
|
}
|
|
30
32
|
|
|
31
33
|
export const number = (value) => {
|
|
32
34
|
const type = getType(value)
|
|
33
35
|
if (type !== 'number') {
|
|
34
|
-
throw new
|
|
36
|
+
throw new AssertionError('expected value to be of type number')
|
|
35
37
|
}
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
export const array = (value) => {
|
|
39
41
|
const type = getType(value)
|
|
40
42
|
if (type !== 'array') {
|
|
41
|
-
throw new
|
|
43
|
+
throw new AssertionError('expected value to be of type array')
|
|
42
44
|
}
|
|
43
45
|
}
|
|
44
46
|
|
|
45
47
|
export const string = (value) => {
|
|
46
48
|
const type = getType(value)
|
|
47
49
|
if (type !== 'string') {
|
|
48
|
-
throw new
|
|
50
|
+
throw new AssertionError('expected value to be of type string')
|
|
49
51
|
}
|
|
50
52
|
}
|
|
51
53
|
|
|
52
54
|
export const boolean = (value) => {
|
|
53
55
|
const type = getType(value)
|
|
54
56
|
if (type !== 'boolean') {
|
|
55
|
-
throw new
|
|
57
|
+
throw new AssertionError('expected value to be of type boolean')
|
|
56
58
|
}
|
|
57
59
|
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import * as SplitLines from '../SplitLines/SplitLines.js'
|
|
2
|
+
|
|
3
|
+
const RE_AT = /^\s+at/
|
|
4
|
+
const RE_AT_PROMISE_INDEX = /^\s*at async Promise.all \(index \d+\)$/
|
|
5
|
+
const RE_OBJECT_AS = /^\s*at (async )?Object\.\w+ \[as ([\w\.]+)\]/
|
|
6
|
+
const RE_GET_RESPONSE = /^\s*at async getResponse/
|
|
7
|
+
const RE_WEBSOCKET_HANDLE_MESSAGE = /^\s*at async WebSocket.handleMessage/
|
|
8
|
+
const RE_EXECUTE_COMMAND_ASYNC = /^\s*at executeCommandAsync/
|
|
9
|
+
const RE_HANDLE_OTHER_MESSAGES_FROM_MESSAGE_PORT = /^\s*at async MessagePort\.handleOtherMessagesFromMessagePort/
|
|
10
|
+
|
|
11
|
+
const isInternalLine = (line) => {
|
|
12
|
+
return line.includes('node:') || RE_AT_PROMISE_INDEX.test(line) || line.includes('node_modules/ws')
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const isRelevantLine = (line) => {
|
|
16
|
+
return !isInternalLine(line)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const isApplicationUsefulLine = (line, index) => {
|
|
20
|
+
if (index === 0) {
|
|
21
|
+
return true
|
|
22
|
+
}
|
|
23
|
+
if (RE_GET_RESPONSE.test(line)) {
|
|
24
|
+
return false
|
|
25
|
+
}
|
|
26
|
+
if (RE_WEBSOCKET_HANDLE_MESSAGE.test(line)) {
|
|
27
|
+
return false
|
|
28
|
+
}
|
|
29
|
+
if (RE_EXECUTE_COMMAND_ASYNC.test(line)) {
|
|
30
|
+
return false
|
|
31
|
+
}
|
|
32
|
+
if (RE_HANDLE_OTHER_MESSAGES_FROM_MESSAGE_PORT.test(line)) {
|
|
33
|
+
return false
|
|
34
|
+
}
|
|
35
|
+
return true
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const isNormalStackLine = (line) => {
|
|
39
|
+
return RE_AT.test(line) && !RE_AT_PROMISE_INDEX.test(line)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const cleanLine = (line) => {
|
|
43
|
+
if (line.startsWith(' at exports.')) {
|
|
44
|
+
return ' at ' + line.slice(' at exports.'.length)
|
|
45
|
+
}
|
|
46
|
+
if (line.startsWith(' at async exports.')) {
|
|
47
|
+
return ' at async ' + line.slice(' at async exports.'.length)
|
|
48
|
+
}
|
|
49
|
+
if (line.startsWith(' at async Module.')) {
|
|
50
|
+
return ' at async ' + line.slice(' at async Module.'.length)
|
|
51
|
+
}
|
|
52
|
+
if (line.startsWith(' at Module.')) {
|
|
53
|
+
return ' at ' + line.slice(' at Module.'.length)
|
|
54
|
+
}
|
|
55
|
+
const objectMatch = line.match(RE_OBJECT_AS)
|
|
56
|
+
if (objectMatch) {
|
|
57
|
+
const rest = line.slice(objectMatch[0].length)
|
|
58
|
+
if (objectMatch[1]) {
|
|
59
|
+
return ' at ' + objectMatch[1] + objectMatch[2] + rest
|
|
60
|
+
}
|
|
61
|
+
return ' at ' + objectMatch[2] + rest
|
|
62
|
+
}
|
|
63
|
+
return line
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const getDetails = (lines) => {
|
|
67
|
+
const index = lines.findIndex(isNormalStackLine)
|
|
68
|
+
return {
|
|
69
|
+
custom: lines.slice(0, index),
|
|
70
|
+
actualStack: lines.slice(index).map(cleanLine),
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const RE_PATH_1 = /^\/(.*)(\d+)$/
|
|
75
|
+
|
|
76
|
+
const mergeCustom = (custom, relevantStack) => {
|
|
77
|
+
if (custom.length === 0) {
|
|
78
|
+
return relevantStack
|
|
79
|
+
}
|
|
80
|
+
const firstLine = custom[0]
|
|
81
|
+
if (RE_PATH_1.test(firstLine)) {
|
|
82
|
+
return [` at ${firstLine}`, ...relevantStack]
|
|
83
|
+
}
|
|
84
|
+
return relevantStack
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export const cleanStack = (stack) => {
|
|
88
|
+
const lines = SplitLines.splitLines(stack)
|
|
89
|
+
const { custom, actualStack } = getDetails(lines)
|
|
90
|
+
const relevantStack = actualStack.filter(isRelevantLine).filter(isApplicationUsefulLine)
|
|
91
|
+
const merged = mergeCustom(custom, relevantStack)
|
|
92
|
+
return merged
|
|
93
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as PtyController from '../PtyController/PtyController.js'
|
|
2
|
+
import * as HandleWebSocket from '../HandleWebSocket/HandleWebSocket.js'
|
|
3
|
+
import * as HandleElectronMessagePort from '../HandleElectronMessagePort/HandleElectronMessagePort.js'
|
|
4
|
+
|
|
5
|
+
export const commandMap = {
|
|
6
|
+
'Terminal.create': PtyController.create,
|
|
7
|
+
'Terminal.write': PtyController.write,
|
|
8
|
+
'HandleWebSocket.handleWebSocket': HandleWebSocket.handleWebSocket,
|
|
9
|
+
'HandleElectronMessagePort.handleElectronMessagePort': HandleElectronMessagePort.handleElectronMessagePort,
|
|
10
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export const state = {
|
|
2
|
+
commands: Object.create(null),
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export const registerCommand = (key, fn) => {
|
|
6
|
+
state.commands[key] = fn
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const registerCommands = (commandMap) => {
|
|
10
|
+
for (const [key, value] of Object.entries(commandMap)) {
|
|
11
|
+
registerCommand(key, value)
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const getCommand = (key) => {
|
|
16
|
+
return state.commands[key]
|
|
17
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const Utf8 = 'utf8'
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export const E_COLOR_THEME_NOT_FOUND = 'E_COLOR_THEME_NOT_FOUND'
|
|
2
|
+
export const E_COMMAND_NOT_FOUND = 'E_COMMAND_NOT_FOUND'
|
|
3
|
+
export const E_ICON_THEME_NOT_FOUND = 'E_ICON_THEME_NOT_FOUND'
|
|
4
|
+
export const E_INCOMPATIBLE_NATIVE_MODULE = 'E_INCOMPATIBLE_NATIVE_MODULE'
|
|
5
|
+
export const E_MANIFEST_NOT_FOUND = 'E_MANIFEST_NOT_FOUND'
|
|
6
|
+
export const E_MODULES_NOT_SUPPORTED_IN_ELECTRON = 'E_MODULES_NOT_SUPPORTED_IN_ELECTRON'
|
|
7
|
+
export const EACCES = 'EACCES'
|
|
8
|
+
export const ECONNRESET = 'ECONNRESET'
|
|
9
|
+
export const EEXIST = 'EEXIST'
|
|
10
|
+
export const EISDIR = 'EISDIR'
|
|
11
|
+
export const ELOOP = 'ELOOP'
|
|
12
|
+
export const ENOENT = 'ENOENT'
|
|
13
|
+
export const ENOTDIR = 'ENOTDIR'
|
|
14
|
+
export const EPERM = 'EPERM'
|
|
15
|
+
export const EPIPE = 'EPIPE'
|
|
16
|
+
export const ERR_IPC_CHANNEL_CLOSED = 'ERR_IPC_CHANNEL_CLOSED'
|
|
17
|
+
export const ERR_MODULE_NOT_FOUND = 'ERR_MODULE_NOT_FOUND'
|
|
18
|
+
export const EXDEV = 'EXDEV'
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { CommandNotFoundError } from '../CommandNotFoundError/CommandNotFoundError.js'
|
|
2
|
+
import * as ErrorCodes from '../ErrorCodes/ErrorCodes.js'
|
|
3
|
+
import * as JsonRpcErrorCode from '../JsonRpcErrorCode/JsonRpcErrorCode.js'
|
|
4
|
+
import * as JsonRpcVersion from '../JsonRpcVersion/JsonRpcVersion.js'
|
|
5
|
+
import * as PrettyError from '../PrettyError/PrettyError.js'
|
|
6
|
+
import * as PrintPrettyError from '../PrintPrettyError/PrintPrettyError.js'
|
|
7
|
+
|
|
8
|
+
const shouldLogError = (error) => {
|
|
9
|
+
if (error && error.code === ErrorCodes.ENOENT) {
|
|
10
|
+
return false
|
|
11
|
+
}
|
|
12
|
+
return true
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const getErrorResponse = (message, error) => {
|
|
16
|
+
if (error && error instanceof CommandNotFoundError) {
|
|
17
|
+
return {
|
|
18
|
+
jsonrpc: JsonRpcVersion.Two,
|
|
19
|
+
id: message.id,
|
|
20
|
+
error: {
|
|
21
|
+
code: JsonRpcErrorCode.MethodNotFound,
|
|
22
|
+
message: error.message,
|
|
23
|
+
data: error.stack,
|
|
24
|
+
},
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (!shouldLogError(error)) {
|
|
28
|
+
return {
|
|
29
|
+
jsonrpc: JsonRpcVersion.Two,
|
|
30
|
+
id: message.id,
|
|
31
|
+
error: {
|
|
32
|
+
code: JsonRpcErrorCode.Custom,
|
|
33
|
+
message: `${error}`,
|
|
34
|
+
data: {
|
|
35
|
+
code: error.code,
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const prettyError = PrettyError.prepare(error)
|
|
41
|
+
PrintPrettyError.printPrettyError(prettyError, `[terminal-process] `)
|
|
42
|
+
return {
|
|
43
|
+
jsonrpc: JsonRpcVersion.Two,
|
|
44
|
+
id: message.id,
|
|
45
|
+
error: {
|
|
46
|
+
code: JsonRpcErrorCode.Custom,
|
|
47
|
+
message: prettyError.message,
|
|
48
|
+
data: {
|
|
49
|
+
stack: prettyError.stack,
|
|
50
|
+
codeFrame: prettyError.codeFrame,
|
|
51
|
+
type: prettyError.type,
|
|
52
|
+
code: prettyError.code,
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import * as FirstWebSocketEventType from '../FirstWebSocketEventType/FirstWebSocketEventType.js'
|
|
2
|
+
import { WebSocket } from 'ws'
|
|
3
|
+
|
|
4
|
+
export const getFirstWebSocketEvent = async (webSocket) => {
|
|
5
|
+
switch (webSocket.readyState) {
|
|
6
|
+
case WebSocket.OPEN:
|
|
7
|
+
return {
|
|
8
|
+
type: FirstWebSocketEventType.Open,
|
|
9
|
+
event: undefined,
|
|
10
|
+
}
|
|
11
|
+
case WebSocket.CLOSED:
|
|
12
|
+
return {
|
|
13
|
+
type: FirstWebSocketEventType.Close,
|
|
14
|
+
event: undefined,
|
|
15
|
+
}
|
|
16
|
+
default:
|
|
17
|
+
break
|
|
18
|
+
}
|
|
19
|
+
const { type, event } = await new Promise((resolve) => {
|
|
20
|
+
const cleanup = (value) => {
|
|
21
|
+
webSocket.off('open', handleOpen)
|
|
22
|
+
webSocket.off('close', handleClose)
|
|
23
|
+
resolve(value)
|
|
24
|
+
}
|
|
25
|
+
const handleOpen = (event) => {
|
|
26
|
+
cleanup({
|
|
27
|
+
type: FirstWebSocketEventType.Open,
|
|
28
|
+
event,
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
const handleClose = (event) => {
|
|
32
|
+
cleanup({
|
|
33
|
+
type: FirstWebSocketEventType.Close,
|
|
34
|
+
event,
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
webSocket.on('open', handleOpen)
|
|
38
|
+
webSocket.on('close', handleClose)
|
|
39
|
+
})
|
|
40
|
+
return { type, event }
|
|
41
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import * as Command from '../Command/Command.js'
|
|
2
|
+
import * as GetErrorResponse from '../GetErrorResponse/GetErrorResponse.js'
|
|
3
|
+
import * as GetSuccessResponse from '../GetSuccessResponse/GetSuccessResponse.js'
|
|
4
|
+
import * as RequiresSocket from '../RequiresSocket/RequiresSocket.js'
|
|
5
|
+
|
|
6
|
+
export const getResponse = async (message, handle) => {
|
|
7
|
+
try {
|
|
8
|
+
const result = RequiresSocket.requiresSocket(message.method)
|
|
9
|
+
? await Command.execute(message.method, ...message.params, handle)
|
|
10
|
+
: await Command.execute(message.method, ...message.params)
|
|
11
|
+
return GetSuccessResponse.getSuccessResponse(message, result)
|
|
12
|
+
} catch (error) {
|
|
13
|
+
return GetErrorResponse.getErrorResponse(message, error)
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import * as JsonRpcVersion from '../JsonRpcVersion/JsonRpcVersion.js'
|
|
2
|
+
|
|
3
|
+
export const getSuccessResponse = (message, result) => {
|
|
4
|
+
if (!message.id) {
|
|
5
|
+
return undefined
|
|
6
|
+
}
|
|
7
|
+
return {
|
|
8
|
+
jsonrpc: JsonRpcVersion.Two,
|
|
9
|
+
id: message.id,
|
|
10
|
+
result: result ?? null,
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import * as Assert from '../Assert/Assert.js'
|
|
2
|
+
import * as HandleIpc from '../HandleIpc/HandleIpc.js'
|
|
3
|
+
import * as IpcChild from '../IpcChild/IpcChild.js'
|
|
4
|
+
import * as IpcChildType from '../IpcChildType/IpcChildType.js'
|
|
5
|
+
|
|
6
|
+
export const handleElectronMessagePort = async (messagePort) => {
|
|
7
|
+
Assert.object(messagePort)
|
|
8
|
+
const ipc = await IpcChild.listen({
|
|
9
|
+
method: IpcChildType.ElectronMessagePort,
|
|
10
|
+
messagePort,
|
|
11
|
+
})
|
|
12
|
+
HandleIpc.handleIpc(ipc)
|
|
13
|
+
messagePort.start()
|
|
14
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import * as Assert from '../Assert/Assert.js'
|
|
2
|
+
import * as GetResponse from '../GetResponse/GetResponse.js'
|
|
3
|
+
|
|
4
|
+
export const handleIpc = (ipc) => {
|
|
5
|
+
Assert.object(ipc)
|
|
6
|
+
const handleMessage = async (message) => {
|
|
7
|
+
const response = await GetResponse.getResponse(message, ipc)
|
|
8
|
+
if (response) {
|
|
9
|
+
ipc.send(response)
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
ipc.on('message', handleMessage)
|
|
13
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import * as Assert from '../Assert/Assert.js'
|
|
2
|
+
import * as HandleIpc from '../HandleIpc/HandleIpc.js'
|
|
3
|
+
import * as IpcChild from '../IpcChild/IpcChild.js'
|
|
4
|
+
import * as IpcChildType from '../IpcChildType/IpcChildType.js'
|
|
5
|
+
import * as WebSocketServer from '../WebSocketServer/WebSocketServer.js'
|
|
6
|
+
|
|
7
|
+
export const handleWebSocket = async (request, handle) => {
|
|
8
|
+
Assert.object(request)
|
|
9
|
+
Assert.object(handle)
|
|
10
|
+
const webSocket = await WebSocketServer.handleUpgrade(request, handle)
|
|
11
|
+
webSocket.pause()
|
|
12
|
+
const ipc = await IpcChild.listen({
|
|
13
|
+
method: IpcChildType.WebSocket,
|
|
14
|
+
webSocket,
|
|
15
|
+
})
|
|
16
|
+
HandleIpc.handleIpc(ipc)
|
|
17
|
+
webSocket.resume()
|
|
18
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import * as IpcChildModule from '../IpcChildModule/IpcChildModule.js'
|
|
2
2
|
|
|
3
|
-
export const listen = async ({ method }) => {
|
|
3
|
+
export const listen = async ({ method, ...params }) => {
|
|
4
4
|
const module = await IpcChildModule.getModule(method)
|
|
5
|
-
|
|
5
|
+
// @ts-ignore
|
|
6
|
+
const rawIpc = await module.listen(params)
|
|
6
7
|
const ipc = module.wrap(rawIpc)
|
|
7
8
|
return ipc
|
|
8
9
|
}
|
|
@@ -6,6 +6,12 @@ export const getModule = (method) => {
|
|
|
6
6
|
return import('../IpcChildWithNodeForkedProcess/IpcChildWithNodeForkedProcess.js')
|
|
7
7
|
case IpcChildType.NodeWorker:
|
|
8
8
|
return import('../IpcChildWithNodeWorker/IpcChildWithNodeWorker.js')
|
|
9
|
+
case IpcChildType.WebSocket:
|
|
10
|
+
return import('../IpcChildWithWebSocket/IpcChildWithWebSocket.js')
|
|
11
|
+
case IpcChildType.ElectronUtilityProcess:
|
|
12
|
+
return import('../IpcChildWithElectronUtilityProcess/IpcChildWithElectronUtilityProcess.js')
|
|
13
|
+
case IpcChildType.ElectronMessagePort:
|
|
14
|
+
return import('../IpcChildWithElectronMessagePort/IpcChildWithElectronMessagePort.js')
|
|
9
15
|
default:
|
|
10
16
|
throw new Error('unexpected ipc type')
|
|
11
17
|
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
export const NodeWorker = 1
|
|
2
2
|
export const NodeForkedProcess = 2
|
|
3
|
+
export const WebSocket = 3
|
|
4
|
+
export const ElectronUtilityProcess = 4
|
|
5
|
+
export const ElectronMessagePort = 5
|
|
3
6
|
|
|
4
7
|
export const Auto = () => {
|
|
5
8
|
const { argv } = process
|
|
@@ -9,5 +12,8 @@ export const Auto = () => {
|
|
|
9
12
|
if (argv.includes('--ipc-type=node-forked-process')) {
|
|
10
13
|
return NodeForkedProcess
|
|
11
14
|
}
|
|
15
|
+
if (argv.includes('--ipc-type=electron-utility-process')) {
|
|
16
|
+
return ElectronUtilityProcess
|
|
17
|
+
}
|
|
12
18
|
throw new Error(`[pty-host] unknown ipc type`)
|
|
13
19
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export const listen = ({ messagePort }) => {
|
|
2
|
+
return messagePort
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
const getActualData = (event) => {
|
|
6
|
+
return event.data
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const wrap = (messagePort) => {
|
|
10
|
+
return {
|
|
11
|
+
messagePort,
|
|
12
|
+
on(event, listener) {
|
|
13
|
+
if (event === 'message') {
|
|
14
|
+
const wrappedListener = (event) => {
|
|
15
|
+
const actualData = getActualData(event)
|
|
16
|
+
listener(actualData)
|
|
17
|
+
}
|
|
18
|
+
this.messagePort.on(event, wrappedListener)
|
|
19
|
+
} else {
|
|
20
|
+
throw new Error('unsupported event type')
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
off(event, listener) {
|
|
24
|
+
this.messagePort.off(event, listener)
|
|
25
|
+
},
|
|
26
|
+
send(message) {
|
|
27
|
+
this.messagePort.postMessage(message)
|
|
28
|
+
},
|
|
29
|
+
dispose() {
|
|
30
|
+
this.messagePort.close()
|
|
31
|
+
},
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export const listen = () => {
|
|
2
|
+
// @ts-ignore
|
|
3
|
+
const parentPort = process.parentPort
|
|
4
|
+
if (!parentPort) {
|
|
5
|
+
throw new Error('parent port must be defined')
|
|
6
|
+
}
|
|
7
|
+
parentPort.postMessage('ready')
|
|
8
|
+
return parentPort
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const getActualData = (event) => {
|
|
12
|
+
const { data, ports } = event
|
|
13
|
+
if (ports.length === 0) {
|
|
14
|
+
return data
|
|
15
|
+
}
|
|
16
|
+
return {
|
|
17
|
+
...data,
|
|
18
|
+
params: [...data.params, ...ports],
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const wrap = (parentPort) => {
|
|
23
|
+
return {
|
|
24
|
+
parentPort,
|
|
25
|
+
on(event, listener) {
|
|
26
|
+
if (event === 'message') {
|
|
27
|
+
const wrappedListener = (event) => {
|
|
28
|
+
const actualData = getActualData(event)
|
|
29
|
+
listener(actualData)
|
|
30
|
+
}
|
|
31
|
+
this.parentPort.on(event, wrappedListener)
|
|
32
|
+
} else {
|
|
33
|
+
throw new Error('unsupported event type')
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
off(event, listener) {
|
|
37
|
+
this.parentPort.off(event, listener)
|
|
38
|
+
},
|
|
39
|
+
send(message) {
|
|
40
|
+
this.parentPort.postMessage(message)
|
|
41
|
+
},
|
|
42
|
+
dispose() {
|
|
43
|
+
this.parentPort.close()
|
|
44
|
+
},
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -6,11 +6,27 @@ export const listen = async () => {
|
|
|
6
6
|
return process
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
+
const getActualData = (message, handle) => {
|
|
10
|
+
if (handle) {
|
|
11
|
+
return {
|
|
12
|
+
...message,
|
|
13
|
+
params: [...message.params, handle],
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return message
|
|
17
|
+
}
|
|
18
|
+
|
|
9
19
|
export const wrap = (process) => {
|
|
10
20
|
return {
|
|
11
21
|
process,
|
|
12
22
|
on(event, listener) {
|
|
13
|
-
|
|
23
|
+
if (event === 'message') {
|
|
24
|
+
const wrappedListener = (message, handle) => {
|
|
25
|
+
const actualData = getActualData(message, handle)
|
|
26
|
+
listener(actualData)
|
|
27
|
+
}
|
|
28
|
+
this.process.on(event, wrappedListener)
|
|
29
|
+
}
|
|
14
30
|
},
|
|
15
31
|
off(event, listener) {
|
|
16
32
|
this.process.off(event, listener)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import * as GetFirstWebSocketEvent from '../GetFirstWebSocketEvent/GetFirstWebSocketEvent.js'
|
|
2
|
+
import * as IsWebSocketOpen from '../IsWebSocketOpen/IsWebSocketOpen.js'
|
|
3
|
+
|
|
4
|
+
export const listen = async ({ webSocket }) => {
|
|
5
|
+
if (!IsWebSocketOpen.isWebSocketOpen(webSocket)) {
|
|
6
|
+
const { type, event } = await GetFirstWebSocketEvent.getFirstWebSocketEvent(webSocket)
|
|
7
|
+
console.log({ type, event })
|
|
8
|
+
}
|
|
9
|
+
return webSocket
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
class MessageEvent extends Event {
|
|
13
|
+
constructor(data, target) {
|
|
14
|
+
super('message')
|
|
15
|
+
this.data = data
|
|
16
|
+
this.target = target
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const wrap = (webSocket) => {
|
|
21
|
+
return {
|
|
22
|
+
webSocket,
|
|
23
|
+
/**
|
|
24
|
+
* @type {any}
|
|
25
|
+
*/
|
|
26
|
+
wrappedListener: undefined,
|
|
27
|
+
on(event, listener) {
|
|
28
|
+
switch (event) {
|
|
29
|
+
case 'message':
|
|
30
|
+
const wrappedListener = (message) => {
|
|
31
|
+
const data = JSON.parse(message.toString())
|
|
32
|
+
listener(data)
|
|
33
|
+
}
|
|
34
|
+
webSocket.on('message', wrappedListener)
|
|
35
|
+
break
|
|
36
|
+
default:
|
|
37
|
+
throw new Error('unknown event listener type')
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
off(event, listener) {
|
|
41
|
+
this.webSocket.off(event, listener)
|
|
42
|
+
},
|
|
43
|
+
send(message) {
|
|
44
|
+
const stringifiedMessage = JSON.stringify(message)
|
|
45
|
+
this.webSocket.send(stringifiedMessage)
|
|
46
|
+
},
|
|
47
|
+
dispose() {
|
|
48
|
+
this.webSocket.close()
|
|
49
|
+
},
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// parsing error handling based on https://github.com/sindresorhus/parse-json/blob/main/index.js
|
|
2
|
+
|
|
3
|
+
import { JsonParsingError } from '../JsonParsingError/JsonParsingError.js'
|
|
4
|
+
|
|
5
|
+
export const parse = async (string, filePath) => {
|
|
6
|
+
try {
|
|
7
|
+
return JSON.parse(string)
|
|
8
|
+
} catch (error) {
|
|
9
|
+
const JsonError = await import('../JsonError/JsonError.js')
|
|
10
|
+
const errorProps = JsonError.getErrorPropsFromError(error, string, filePath)
|
|
11
|
+
throw new JsonParsingError(errorProps.message, errorProps.codeFrame, errorProps.stack)
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const stringify = (value) => {
|
|
16
|
+
return JSON.stringify(value, null, 2) + '\n'
|
|
17
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { codeFrameColumns } from '@babel/code-frame'
|
|
2
|
+
import { LinesAndColumns } from 'lines-and-columns'
|
|
3
|
+
|
|
4
|
+
// parsing error handling based on https://github.com/sindresorhus/parse-json/blob/main/index.js
|
|
5
|
+
|
|
6
|
+
const emptyError = {
|
|
7
|
+
message: '',
|
|
8
|
+
stack: '',
|
|
9
|
+
codeFrame: '',
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const getErrorPropsFromError = (error, string, filePath) => {
|
|
13
|
+
const indexMatch = error.message.match(/in JSON at position (\d+)/)
|
|
14
|
+
if (indexMatch && indexMatch.length > 0) {
|
|
15
|
+
const lines = new LinesAndColumns(string)
|
|
16
|
+
const index = Number(indexMatch[1])
|
|
17
|
+
const location = lines.locationForIndex(index)
|
|
18
|
+
if (location) {
|
|
19
|
+
const line = location.line + 1
|
|
20
|
+
const column = location.column + 1
|
|
21
|
+
const codeFrame = codeFrameColumns(string, { start: { line, column } }, { highlightCode: false })
|
|
22
|
+
return {
|
|
23
|
+
codeFrame,
|
|
24
|
+
message: 'Json Parsing Error',
|
|
25
|
+
stack: ` at ${filePath}:${line}:${column}`,
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (string.length === 0) {
|
|
30
|
+
return {
|
|
31
|
+
codeFrame: ``,
|
|
32
|
+
message: 'Json Parsing Error: Cannot parse empty string',
|
|
33
|
+
stack: ` at ${filePath}`,
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (error.message === 'Unexpected end of JSON input') {
|
|
38
|
+
const lines = new LinesAndColumns(string)
|
|
39
|
+
const index = string.length - 1
|
|
40
|
+
const location = lines.locationForIndex(index)
|
|
41
|
+
if (location) {
|
|
42
|
+
const codeFrame = codeFrameColumns(string, { start: { line: location.line + 1, column: location.column + 1 } }, { highlightCode: false })
|
|
43
|
+
return {
|
|
44
|
+
codeFrame,
|
|
45
|
+
message: 'Json Parsing Error',
|
|
46
|
+
stack: ` at ${filePath}`,
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
codeFrame: ``,
|
|
52
|
+
message: 'Json Parsing Error',
|
|
53
|
+
stack: ` at ${filePath}`,
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export const getErrorProps = (string, filePath) => {
|
|
58
|
+
try {
|
|
59
|
+
JSON.parse(string)
|
|
60
|
+
return emptyError
|
|
61
|
+
} catch (error) {
|
|
62
|
+
return getErrorPropsFromError(error, string, filePath)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import * as JoinLines from '../JoinLines/JoinLines.js'
|
|
2
|
+
import * as SplitLines from '../SplitLines/SplitLines.js'
|
|
3
|
+
|
|
4
|
+
export class JsonParsingError extends Error {
|
|
5
|
+
constructor(message, codeFrame, stack) {
|
|
6
|
+
super(message)
|
|
7
|
+
this.name = 'JsonParsingError'
|
|
8
|
+
if (codeFrame) {
|
|
9
|
+
this.codeFrame = codeFrame
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (stack) {
|
|
13
|
+
const parentStack = JoinLines.joinLines(SplitLines.splitLines(this.stack).slice(1))
|
|
14
|
+
this.stack = this.name + ': ' + this.message + '\n' + stack + '\n' + parentStack
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const Two = '2.0'
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Console } from 'node:console'
|
|
2
|
+
import { createWriteStream } from 'node:fs'
|
|
3
|
+
import { tmpdir } from 'node:os'
|
|
4
|
+
|
|
5
|
+
// TODO mock this module when used in unit tests
|
|
6
|
+
|
|
7
|
+
const state = {
|
|
8
|
+
/**
|
|
9
|
+
* @type {Console|undefined}
|
|
10
|
+
*/
|
|
11
|
+
console: undefined,
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const createConsole = () => {
|
|
15
|
+
const logFile = `${tmpdir()}/log-terminal-process.txt`
|
|
16
|
+
const writeStream = createWriteStream(logFile)
|
|
17
|
+
const logger = new Console(writeStream)
|
|
18
|
+
return logger
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const getOrCreateLogger = () => {
|
|
22
|
+
if (!state.console) {
|
|
23
|
+
state.console = createConsole()
|
|
24
|
+
}
|
|
25
|
+
return state.console
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const log = (...args) => {
|
|
29
|
+
const logger = getOrCreateLogger()
|
|
30
|
+
logger.log(...args)
|
|
31
|
+
console.log(...args)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const info = (...args) => {
|
|
35
|
+
const logger = getOrCreateLogger()
|
|
36
|
+
logger.info(...args)
|
|
37
|
+
console.info(...args)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export const warn = (...args) => {
|
|
41
|
+
const logger = getOrCreateLogger()
|
|
42
|
+
logger.warn(...args)
|
|
43
|
+
console.warn(...args)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const error = (...args) => {
|
|
47
|
+
const logger = getOrCreateLogger()
|
|
48
|
+
logger.error(...args)
|
|
49
|
+
console.error(...args)
|
|
50
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import * as CommandMap from '../CommandMap/CommandMap.js'
|
|
2
|
+
import * as CommandState from '../CommandState/CommandState.js'
|
|
3
|
+
import * as HandleIpc from '../HandleIpc/HandleIpc.js'
|
|
4
|
+
import * as IpcChild from '../IpcChild/IpcChild.js'
|
|
5
|
+
import * as IpcChildType from '../IpcChildType/IpcChildType.js'
|
|
6
|
+
|
|
7
|
+
export const main = async () => {
|
|
8
|
+
CommandState.registerCommands(CommandMap.commandMap)
|
|
9
|
+
const ipc = await IpcChild.listen({ method: IpcChildType.Auto() })
|
|
10
|
+
HandleIpc.handleIpc(ipc)
|
|
11
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { codeFrameColumns } from '@babel/code-frame'
|
|
2
|
+
import { LinesAndColumns } from 'lines-and-columns'
|
|
3
|
+
import { readFileSync } from 'node:fs'
|
|
4
|
+
import { fileURLToPath } from 'node:url'
|
|
5
|
+
import { AssertionError } from '../AssertionError/AssertionError.js'
|
|
6
|
+
import * as CleanStack from '../CleanStack/CleanStack.js'
|
|
7
|
+
import * as EncodingType from '../EncodingType/EncodingType.js'
|
|
8
|
+
import * as ErrorCodes from '../ErrorCodes/ErrorCodes.js'
|
|
9
|
+
import * as JoinLines from '../JoinLines/JoinLines.js'
|
|
10
|
+
import * as Json from '../Json/Json.js'
|
|
11
|
+
import * as SplitLines from '../SplitLines/SplitLines.js'
|
|
12
|
+
|
|
13
|
+
const getActualPath = (fileUri) => {
|
|
14
|
+
if (fileUri.startsWith('file://')) {
|
|
15
|
+
return fileURLToPath(fileUri)
|
|
16
|
+
}
|
|
17
|
+
return fileUri
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const RE_MODULE_NOT_FOUND_STACK = /Cannot find package '([^']+)' imported from (.+)$/
|
|
21
|
+
|
|
22
|
+
const prepareModuleNotFoundError = (error) => {
|
|
23
|
+
const { message } = error
|
|
24
|
+
const match = message.match(RE_MODULE_NOT_FOUND_STACK)
|
|
25
|
+
if (!match) {
|
|
26
|
+
return {
|
|
27
|
+
message,
|
|
28
|
+
stack: error.stack,
|
|
29
|
+
codeFrame: '',
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
const notFoundModule = match[1]
|
|
33
|
+
const importedFrom = match[2]
|
|
34
|
+
const rawLines = readFileSync(importedFrom, EncodingType.Utf8)
|
|
35
|
+
let line = 0
|
|
36
|
+
let column = 0
|
|
37
|
+
const splittedLines = SplitLines.splitLines(rawLines)
|
|
38
|
+
for (let i = 0; i < splittedLines.length; i++) {
|
|
39
|
+
const splittedLine = splittedLines[i]
|
|
40
|
+
const index = splittedLine.indexOf(notFoundModule)
|
|
41
|
+
if (index !== -1) {
|
|
42
|
+
line = i + 1
|
|
43
|
+
column = index
|
|
44
|
+
break
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const location = {
|
|
48
|
+
start: {
|
|
49
|
+
line,
|
|
50
|
+
column,
|
|
51
|
+
},
|
|
52
|
+
}
|
|
53
|
+
const codeFrame = codeFrameColumns(rawLines, location)
|
|
54
|
+
const stackLines = SplitLines.splitLines(error.stack)
|
|
55
|
+
const newStackLines = [stackLines[0], ` at ${importedFrom}:${line}:${column}`, ...stackLines.slice(1)]
|
|
56
|
+
const newStack = JoinLines.joinLines(newStackLines)
|
|
57
|
+
return {
|
|
58
|
+
message,
|
|
59
|
+
stack: newStack,
|
|
60
|
+
codeFrame,
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const getStackLinesToCut = (error) => {
|
|
65
|
+
if (error instanceof AssertionError) {
|
|
66
|
+
return 1
|
|
67
|
+
}
|
|
68
|
+
return 0
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export const prepare = (error) => {
|
|
72
|
+
if (error && error.code === ErrorCodes.ERR_MODULE_NOT_FOUND) {
|
|
73
|
+
return prepareModuleNotFoundError(error)
|
|
74
|
+
}
|
|
75
|
+
const { message } = error
|
|
76
|
+
if (error && error.cause) {
|
|
77
|
+
const cause = error.cause()
|
|
78
|
+
if (cause) {
|
|
79
|
+
error = cause
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const linesToCut = getStackLinesToCut(error)
|
|
83
|
+
const lines = CleanStack.cleanStack(error.stack).slice(linesToCut)
|
|
84
|
+
const file = lines[0]
|
|
85
|
+
let codeFrame = ''
|
|
86
|
+
if (error.codeFrame) {
|
|
87
|
+
codeFrame = error.codeFrame
|
|
88
|
+
} else if (file) {
|
|
89
|
+
let match = file.match(/\((.*):(\d+):(\d+)\)$/)
|
|
90
|
+
if (!match) {
|
|
91
|
+
match = file.match(/at (.*):(\d+):(\d+)$/)
|
|
92
|
+
}
|
|
93
|
+
if (match) {
|
|
94
|
+
const [_, path, line, column] = match
|
|
95
|
+
const actualPath = getActualPath(path)
|
|
96
|
+
const rawLines = readFileSync(actualPath, EncodingType.Utf8)
|
|
97
|
+
const location = {
|
|
98
|
+
start: {
|
|
99
|
+
line: Number.parseInt(line),
|
|
100
|
+
column: Number.parseInt(column),
|
|
101
|
+
},
|
|
102
|
+
}
|
|
103
|
+
codeFrame = codeFrameColumns(rawLines, location)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
const relevantStack = JoinLines.joinLines(lines)
|
|
107
|
+
return {
|
|
108
|
+
message,
|
|
109
|
+
stack: relevantStack,
|
|
110
|
+
codeFrame,
|
|
111
|
+
type: error.constructor.name,
|
|
112
|
+
code: error.code,
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const fixBackslashes = (string) => {
|
|
117
|
+
return string.replaceAll('\\\\', '\\')
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export const prepareJsonError = (json, property, message) => {
|
|
121
|
+
const string = fixBackslashes(Json.stringify(json))
|
|
122
|
+
const stringifiedPropertyName = `"${property}"`
|
|
123
|
+
const index = string.indexOf(stringifiedPropertyName) // TODO this could be wrong in some cases, find a better way
|
|
124
|
+
console.log({ string, index })
|
|
125
|
+
const jsonError = {
|
|
126
|
+
stack: '',
|
|
127
|
+
}
|
|
128
|
+
if (index !== -1) {
|
|
129
|
+
const lines = new LinesAndColumns(string)
|
|
130
|
+
const location = lines.locationForIndex(index + stringifiedPropertyName.length + 1)
|
|
131
|
+
const codeFrame = codeFrameColumns(string, {
|
|
132
|
+
// @ts-ignore
|
|
133
|
+
start: { line: location.line + 1, column: location.column + 1 },
|
|
134
|
+
})
|
|
135
|
+
jsonError.codeFrame = codeFrame
|
|
136
|
+
}
|
|
137
|
+
// jsonError.stack = `${bottomMessage}\n at ${filePath}`
|
|
138
|
+
return jsonError
|
|
139
|
+
}
|
package/src/parts/Pty/Pty.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { spawn } from 'node-pty'
|
|
2
2
|
import * as Platform from '../Platform/Platform.js'
|
|
3
3
|
import * as Assert from '../Assert/Assert.js'
|
|
4
|
+
import VError from 'verror'
|
|
4
5
|
|
|
5
6
|
const getSpawnOptions = () => {
|
|
6
7
|
if (Platform.isWindows) {
|
|
@@ -16,14 +17,20 @@ const getSpawnOptions = () => {
|
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
export const create = ({ env = {}, cwd } = {}) => {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
20
|
+
try {
|
|
21
|
+
Assert.string(cwd)
|
|
22
|
+
const spawnOptions = getSpawnOptions()
|
|
23
|
+
const pty = spawn(spawnOptions.command, spawnOptions.args, {
|
|
24
|
+
encoding: null,
|
|
25
|
+
cwd,
|
|
26
|
+
// cols: 10,
|
|
27
|
+
// rows: 10,
|
|
28
|
+
})
|
|
29
|
+
return pty
|
|
30
|
+
} catch (error) {
|
|
31
|
+
// @ts-ignore
|
|
32
|
+
throw new VError(error, `Failed to create terminal`)
|
|
33
|
+
}
|
|
27
34
|
}
|
|
28
35
|
|
|
29
36
|
export const onData = (pty, fn) => {
|
|
@@ -32,8 +39,13 @@ export const onData = (pty, fn) => {
|
|
|
32
39
|
}
|
|
33
40
|
|
|
34
41
|
export const write = (pty, data) => {
|
|
35
|
-
|
|
36
|
-
|
|
42
|
+
try {
|
|
43
|
+
Assert.object(pty)
|
|
44
|
+
pty.write(data)
|
|
45
|
+
} catch (error) {
|
|
46
|
+
// @ts-ignore
|
|
47
|
+
throw new VError(error, `Failed to write data to terminal`)
|
|
48
|
+
}
|
|
37
49
|
}
|
|
38
50
|
|
|
39
51
|
export const resize = (pty, columns, rows) => {
|
|
@@ -1,22 +1,24 @@
|
|
|
1
1
|
import * as Pty from '../Pty/Pty.js'
|
|
2
2
|
import * as Debug from '../Debug/Debug.js'
|
|
3
|
+
import * as Assert from '../Assert/Assert.js'
|
|
3
4
|
|
|
4
5
|
export const state = {
|
|
5
6
|
ptyMap: Object.create(null),
|
|
6
7
|
}
|
|
7
8
|
|
|
8
9
|
// TODO maybe merge pty and pty controller
|
|
9
|
-
export const create = (id, cwd) => {
|
|
10
|
+
export const create = (id, cwd, ipc) => {
|
|
11
|
+
Assert.number(id)
|
|
12
|
+
Assert.string(cwd)
|
|
13
|
+
Assert.object(ipc)
|
|
10
14
|
Debug.debug(`create ${id} ${cwd}`)
|
|
11
15
|
const pty = Pty.create({ cwd })
|
|
12
16
|
const handleData = (data) => {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
})
|
|
19
|
-
}
|
|
17
|
+
ipc.send({
|
|
18
|
+
jsonrpc: '2.0',
|
|
19
|
+
method: 'Viewlet.send',
|
|
20
|
+
params: [id, 'handleData', data],
|
|
21
|
+
})
|
|
20
22
|
}
|
|
21
23
|
Pty.onData(pty, handleData)
|
|
22
24
|
state.ptyMap[id] = pty
|
|
@@ -24,11 +26,17 @@ export const create = (id, cwd) => {
|
|
|
24
26
|
|
|
25
27
|
export const write = (id, data) => {
|
|
26
28
|
const pty = state.ptyMap[id]
|
|
29
|
+
if (!pty) {
|
|
30
|
+
throw new Error(`pty ${id} not found`)
|
|
31
|
+
}
|
|
27
32
|
Pty.write(pty, data)
|
|
28
33
|
}
|
|
29
34
|
|
|
30
35
|
export const dispose = (id) => {
|
|
31
36
|
const pty = state.ptyMap[id]
|
|
37
|
+
if (!pty) {
|
|
38
|
+
throw new Error(`pty ${id} not found`)
|
|
39
|
+
}
|
|
32
40
|
Pty.dispose(pty)
|
|
33
41
|
delete state.ptyMap[id]
|
|
34
42
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Buffer } from 'node:buffer'
|
|
2
|
+
import * as _ws from 'ws'
|
|
3
|
+
|
|
4
|
+
// workaround for jest or node bug
|
|
5
|
+
const WebSocketServer = _ws.WebSocketServer
|
|
6
|
+
? _ws.WebSocketServer
|
|
7
|
+
: // @ts-ignore
|
|
8
|
+
_ws.default.WebSocketServer
|
|
9
|
+
|
|
10
|
+
const webSocketServer = new WebSocketServer({
|
|
11
|
+
noServer: true,
|
|
12
|
+
|
|
13
|
+
// TODO not sure if ws compress is working at all
|
|
14
|
+
// perMessageDeflate: true
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
export const handleUpgrade = (request, socket) => {
|
|
18
|
+
return new Promise((resolve, reject) => {
|
|
19
|
+
const upgradeCallback = (ws) => {
|
|
20
|
+
resolve(ws)
|
|
21
|
+
}
|
|
22
|
+
webSocketServer.handleUpgrade(request, socket, Buffer.alloc(0), upgradeCallback)
|
|
23
|
+
})
|
|
24
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import('./ptyHostMain.js')
|
package/src/ptyHostMain.js
CHANGED
|
@@ -1,26 +1,7 @@
|
|
|
1
|
-
import * as
|
|
2
|
-
import * as IpcChild from './parts/IpcChild/IpcChild.js'
|
|
3
|
-
import * as IpcChildType from './parts/IpcChildType/IpcChildType.js'
|
|
4
|
-
import * as PtyController from './parts/PtyController/PtyController.js'
|
|
5
|
-
|
|
6
|
-
const handleMessage = (message) => {
|
|
7
|
-
switch (message.method) {
|
|
8
|
-
case 'Terminal.create':
|
|
9
|
-
Debug.debug('create terminal')
|
|
10
|
-
PtyController.create(...message.params)
|
|
11
|
-
break
|
|
12
|
-
case 'Terminal.write':
|
|
13
|
-
PtyController.write(...message.params)
|
|
14
|
-
break
|
|
15
|
-
default:
|
|
16
|
-
console.warn('unknown message', message)
|
|
17
|
-
break
|
|
18
|
-
}
|
|
19
|
-
}
|
|
1
|
+
import * as Main from './parts/Main/Main.js'
|
|
20
2
|
|
|
21
3
|
const main = async () => {
|
|
22
|
-
|
|
23
|
-
ipc.on('message', handleMessage)
|
|
4
|
+
await Main.main()
|
|
24
5
|
}
|
|
25
6
|
|
|
26
7
|
main()
|