@lvce-editor/shared-process 0.15.16 → 0.15.18
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/config/builtinCommands.json +8 -0
- package/config/defaultSettings.json +2 -0
- package/extensions/builtin.language-basics-python/extension.json +1 -1
- package/extensions/builtin.language-basics-xml/extension.json +2 -1
- package/extensions/builtin.language-basics-xml/src/tokenizeXml.js +1 -1
- package/extensions/builtin.theme-atom-one-dark/color-theme.json +4 -0
- package/package.json +4 -4
- package/src/parts/AttachDebugger/AttachDebugger.ipc.js +7 -0
- package/src/parts/AttachDebugger/AttachDebugger.js +6 -0
- package/src/parts/Callback/Callback.js +17 -26
- package/src/parts/ChildProcessError/ChildProcessError.js +4 -1
- package/src/parts/CleanStack/CleanStack.js +4 -0
- package/src/parts/CommandNotFoundError/CommandNotFoundError.js +2 -1
- package/src/parts/CreateCpuProfile/CreateCpuProfile.js +59 -0
- package/src/parts/DestroySocket/DestroySocket.js +5 -0
- package/src/parts/DevtoolsProtocolRpc/DevtoolsProtocolRpc.js +32 -0
- package/src/parts/ElectronInitialize/ElectronInitialize.ipc.js +8 -0
- package/src/parts/ElectronInitialize/ElectronInitialize.js +11 -0
- package/src/parts/ErrorCodes/ErrorCodes.js +2 -0
- package/src/parts/ErrorHandling/ErrorHandling.js +12 -14
- package/src/parts/FirstWebSocketEventType/FirstWebSocketEventType.js +2 -0
- package/src/parts/GetActualPath/GetActualPath.js +8 -0
- package/src/parts/GetErrorResponse/GetErrorResponse.js +6 -3
- package/src/parts/GetFirstWebSocketEvent/GetFirstWebSocketEvent.js +41 -0
- package/src/parts/GetHelpfulChildProcessError/GetHelpfulChildProcessError.js +13 -3
- package/src/parts/GetResponse/GetResponse.js +3 -3
- package/src/parts/HandleElectronMessagePort/HandleElectronMessagePort.ipc.js +7 -0
- package/src/parts/HandleElectronMessagePort/HandleElectronMessagePort.js +14 -0
- package/src/parts/HandleIpc/HandleIpc.js +30 -0
- package/src/parts/HandleNodeMessagePort/HandleNodeMessagePort.ipc.js +7 -0
- package/src/parts/HandleNodeMessagePort/HandleNodeMessagePort.js +12 -0
- package/src/parts/HandleWebSocket/HandleWebSocket.ipc.js +7 -0
- package/src/parts/HandleWebSocket/HandleWebSocket.js +26 -0
- package/src/parts/HandleWebSocketForExtensionHost/HandleWebSocketForExtensionHost.js +15 -0
- package/src/parts/HandleWebSocketForExtensionHostHelperProcess/HandleWebSocketForExtensionHostHelperProcess.js +6 -0
- package/src/parts/HandleWebSocketForSharedProcess/HandleWebSocketForSharedProcess.js +23 -0
- package/src/parts/HandleWebSocketForTerminalProcess/HandleWebSocketForTerminalProcess.js +22 -0
- package/src/parts/HandleWebSocketForUnknown/HandleWebSocketForUnknown.js +10 -0
- package/src/parts/HandleWebSocketModule/HandleWebSocketModule.js +20 -0
- package/src/parts/Id/Id.js +7 -0
- package/src/parts/IpcChild/IpcChild.js +5 -2
- package/src/parts/IpcChildModule/IpcChildModule.js +8 -0
- package/src/parts/IpcChildType/IpcChildType.js +7 -0
- package/src/parts/IpcChildWithElectronMessagePort/IpcChildWithElectronMessagePort.js +41 -0
- package/src/parts/IpcChildWithElectronUtilityProcess/IpcChildWithElectronUtilityProcess.js +51 -0
- package/src/parts/IpcChildWithNodeForkedProcess/IpcChildWithNodeForkedProcess.js +25 -1
- package/src/parts/IpcChildWithNodeMessagePort/IpcChildWithNodeMessagePort.js +41 -0
- package/src/parts/IpcChildWithNodeWorker/IpcChildWithNodeWorker.js +17 -1
- package/src/parts/IpcChildWithWebSocket/IpcChildWithWebSocket.js +51 -0
- package/src/parts/IpcError/IpcError.js +6 -0
- package/src/parts/IpcParentWithNodeForkedProcess/IpcParentWithNodeForkedProcess.js +7 -3
- package/src/parts/IsElectron/IsElectron.js +3 -0
- package/src/parts/IsMessagePort/IsMessagePort.js +3 -0
- package/src/parts/IsMessagePortMain/IsMessagePortMain.js +3 -0
- package/src/parts/IsSocket/IsSocket.js +5 -0
- package/src/parts/IsWebSocketOpen/IsWebSocketOpen.js +5 -0
- package/src/parts/JsonRpc/JsonRpc.js +19 -7
- package/src/parts/Module/Module.js +14 -0
- package/src/parts/ModuleId/ModuleId.js +7 -0
- package/src/parts/ModuleMap/ModuleMap.js +18 -0
- package/src/parts/ParentIpc/ParentIpc.js +10 -188
- package/src/parts/PrettyError/PrettyError.js +4 -19
- package/src/parts/Process/Process.ipc.js +1 -0
- package/src/parts/Process/Process.js +8 -0
- package/src/parts/ProcessDisplayName/ProcessDisplayName.js +1 -0
- package/src/parts/ProtocolType/ProtocolType.js +2 -1
- package/src/parts/PtyHost/PtyHost.js +81 -0
- package/src/parts/RebuildNodePty/RebuildNodePty.ipc.js +7 -0
- package/src/parts/RebuildNodePty/RebuildNodePty.js +48 -0
- package/src/parts/Signal/Signal.js +2 -0
- package/src/parts/Terminal/Terminal.js +52 -130
- package/src/parts/TerminalState/TerminalState.js +15 -0
- package/src/parts/WebSocketServer/WebSocketServer.js +11 -25
- package/src/sharedProcessMain.cjs +1 -0
- package/src/sharedProcessMain.js +3 -2
- package/extensions/builtin.language-basics-json5/README.md +0 -16
- package/extensions/builtin.language-basics-json5/extension.json +0 -12
- package/extensions/builtin.language-basics-json5/src/tokenizeJson5.js +0 -321
- package/src/parts/Socket/Socket.js +0 -77
- /package/src/parts/{GetWindowsNsisDOwnloadUrl → GetWindowsNsisDownloadUrl}/GetWindowsNsisDownloadUrl.js +0 -0
|
@@ -1,15 +1,7 @@
|
|
|
1
|
-
import
|
|
2
|
-
import * as
|
|
3
|
-
import * as
|
|
4
|
-
|
|
5
|
-
import * as ErrorCodes from '../ErrorCodes/ErrorCodes.js'
|
|
6
|
-
import * as ExtensionHostHelperProcessIpc from '../ExtensionHostHelperProcessIpc/ExtensionHostHelperProcessIpc.js'
|
|
7
|
-
import * as ExtensionHostIpc from '../ExtensionHostIpc/ExtensionHostIpc.js'
|
|
8
|
-
import * as ExtensionHostRpc from '../ExtensionHostRpc/ExtensionHostRpc.js'
|
|
9
|
-
import * as GetResponse from '../GetResponse/GetResponse.js'
|
|
10
|
-
import * as Logger from '../Logger/Logger.js'
|
|
11
|
-
import * as ProtocolType from '../ProtocolType/ProtocolType.js'
|
|
12
|
-
import { VError } from '../VError/VError.js'
|
|
1
|
+
import * as HandleIpc from '../HandleIpc/HandleIpc.js'
|
|
2
|
+
import * as IpcChild from '../IpcChild/IpcChild.js'
|
|
3
|
+
import * as IpcChildType from '../IpcChildType/IpcChildType.js'
|
|
4
|
+
|
|
13
5
|
// TODO add tests for this
|
|
14
6
|
|
|
15
7
|
// TODO handle structure: one shared process multiple extension hosts
|
|
@@ -20,181 +12,11 @@ export const state = {
|
|
|
20
12
|
electronPortMap: new Map(),
|
|
21
13
|
}
|
|
22
14
|
|
|
23
|
-
// TODO apparent there are multiple ways for listening to ipc
|
|
24
|
-
// 1. parentPort
|
|
25
|
-
// 2. process.send
|
|
26
|
-
// instead of if/else use interface and abstraction: ParentPortIpc.listen(), ProcessIpc.listen(), ParentPortIpc.send(message), ProcessIpc.send(message)
|
|
27
|
-
|
|
28
|
-
export const electronSend = (message) => {
|
|
29
|
-
if (parentPort) {
|
|
30
|
-
// @ts-ignore
|
|
31
|
-
// process.send(message)
|
|
32
|
-
parentPort.postMessage(message)
|
|
33
|
-
} else if (process.send) {
|
|
34
|
-
process.send(message)
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const handleWebSocketSharedProcess = (message, handle) => {
|
|
39
|
-
// TODO when it is an extension host websocket, spawn extension host
|
|
40
|
-
handle.on('error', (error) => {
|
|
41
|
-
if (error && error.code === ErrorCodes.ECONNRESET) {
|
|
42
|
-
return
|
|
43
|
-
}
|
|
44
|
-
console.info('[info shared process: handle error]', error)
|
|
45
|
-
})
|
|
46
|
-
Command.execute(/* WebSocketServer.handleUpgrade */ 'WebSocketServer.handleUpgrade', /* message */ message, /* handle */ handle)
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// TODO lazyload modules for spawning extension host, they are only needed later
|
|
50
|
-
const handleWebSocketExtensionHost = async (message, handle) => {
|
|
51
|
-
// console.log('[shared-process] received extension host websocket', message)
|
|
52
|
-
const ipc = await ExtensionHostIpc.create()
|
|
53
|
-
console.info('[sharedprocess] creating extension ipc')
|
|
54
|
-
const rpc = await ExtensionHostRpc.create(ipc, handle)
|
|
55
|
-
console.info('[sharedprocess] created extension host rpc')
|
|
56
|
-
ipc._process.send(message, handle)
|
|
57
|
-
// rpc.send(message)
|
|
58
|
-
// console.log('spawned extension host')
|
|
59
|
-
// console.log(rpc)
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const handleWebSocketExtensionHostHelperProcess = async (message, handle) => {
|
|
63
|
-
const ipc = await ExtensionHostHelperProcessIpc.create()
|
|
64
|
-
ipc._process.send(message, handle)
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const handleWebSocketUnknown = (message, handle, protocol) => {
|
|
68
|
-
Logger.warn(`[shared-process] unsupported sec-websocket-procotol ${protocol}`)
|
|
69
|
-
try {
|
|
70
|
-
handle.destroy()
|
|
71
|
-
} catch {
|
|
72
|
-
// ignore
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const handleWebSocket = (message, handle) => {
|
|
77
|
-
const headers = message.headers
|
|
78
|
-
if (!headers) {
|
|
79
|
-
throw new VError('missing websocket headers')
|
|
80
|
-
}
|
|
81
|
-
const protocol = headers['sec-websocket-protocol']
|
|
82
|
-
if (!protocol) {
|
|
83
|
-
throw new VError('missing sec websocket protocol header')
|
|
84
|
-
}
|
|
85
|
-
switch (protocol) {
|
|
86
|
-
case ProtocolType.SharedProcess:
|
|
87
|
-
return handleWebSocketSharedProcess(message, handle)
|
|
88
|
-
case ProtocolType.ExtensionHost:
|
|
89
|
-
return handleWebSocketExtensionHost(message, handle)
|
|
90
|
-
case ProtocolType.ExtensionHostHelperProcess:
|
|
91
|
-
return handleWebSocketExtensionHostHelperProcess(message, handle)
|
|
92
|
-
default:
|
|
93
|
-
return handleWebSocketUnknown(message, handle, protocol)
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const handleJsonRpcResult = (message) => {
|
|
98
|
-
Callback.resolve(message.id, message.result)
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const handleJsonRpcMessage = async (message, handle) => {
|
|
102
|
-
if (message.id) {
|
|
103
|
-
const response = await GetResponse.getResponse(message, handle)
|
|
104
|
-
electronSend(response)
|
|
105
|
-
} else {
|
|
106
|
-
// TODO handle error
|
|
107
|
-
Command.execute(message.method, null, ...message.params)
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const handleMessageFromParentProcess = async (message, handle) => {
|
|
112
|
-
if (handle) {
|
|
113
|
-
return handleWebSocket(message, handle)
|
|
114
|
-
}
|
|
115
|
-
if (message.result) {
|
|
116
|
-
return handleJsonRpcResult(message)
|
|
117
|
-
}
|
|
118
|
-
if (message.method) {
|
|
119
|
-
return handleJsonRpcMessage(message, handle)
|
|
120
|
-
}
|
|
121
|
-
console.warn('unknown message', message)
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
*
|
|
126
|
-
* @param {{initialize:{port: MessagePort, folder:string}}} initializeMessage
|
|
127
|
-
*/
|
|
128
|
-
const electronInitialize = (initializeMessage) => {
|
|
129
|
-
const port = initializeMessage.initialize.port
|
|
130
|
-
// TODO handle error
|
|
131
|
-
const fakeSocket = {
|
|
132
|
-
send: port.postMessage.bind(port),
|
|
133
|
-
on(event, listener) {
|
|
134
|
-
switch (event) {
|
|
135
|
-
case 'close':
|
|
136
|
-
port.on('close', listener)
|
|
137
|
-
break
|
|
138
|
-
case 'message':
|
|
139
|
-
port.on('message', listener)
|
|
140
|
-
break
|
|
141
|
-
default:
|
|
142
|
-
console.warn('socket event not implemented', event, listener)
|
|
143
|
-
break
|
|
144
|
-
}
|
|
145
|
-
},
|
|
146
|
-
}
|
|
147
|
-
const handleOtherMessagesFromMessagePort = async (message) => {
|
|
148
|
-
// console.log('got port message', message)
|
|
149
|
-
if (message.result) {
|
|
150
|
-
Callback.resolve(message.id, message.result)
|
|
151
|
-
} else if (message.method) {
|
|
152
|
-
if (message.id) {
|
|
153
|
-
const response = await GetResponse.getResponse(message, fakeSocket)
|
|
154
|
-
port.postMessage(response)
|
|
155
|
-
} else {
|
|
156
|
-
console.warn(`[shared process] sending messages without id is deprecated: ${message.method}`)
|
|
157
|
-
Command.execute(message.method, fakeSocket, ...message.params)
|
|
158
|
-
}
|
|
159
|
-
} else {
|
|
160
|
-
console.warn('unknown message', message)
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
port.on('message', handleOtherMessagesFromMessagePort)
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
const handleMessageFromParentProcessElectron = async (message) => {
|
|
167
|
-
if (message.initialize) {
|
|
168
|
-
electronInitialize(message)
|
|
169
|
-
return
|
|
170
|
-
}
|
|
171
|
-
if ('result' in message) {
|
|
172
|
-
Callback.resolve(message.id, message.result)
|
|
173
|
-
return
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
console.log('unknown message from electron', message)
|
|
177
|
-
console.log({ message })
|
|
178
|
-
}
|
|
179
|
-
|
|
180
15
|
// TODO maybe rename to hydrate
|
|
181
|
-
export const listen = () => {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
// @ts-ignore
|
|
188
|
-
parentPort.on('message', handleMessageFromParentProcessElectron)
|
|
189
|
-
electronSend('ready')
|
|
190
|
-
} else {
|
|
191
|
-
Debug.debug('is not electron')
|
|
192
|
-
// otherwise listen to web process ipc
|
|
193
|
-
// and when a socket is transferred,
|
|
194
|
-
// listen to socket messages also
|
|
195
|
-
process.on('message', handleMessageFromParentProcess)
|
|
196
|
-
if (process.send) {
|
|
197
|
-
process.send('ready')
|
|
198
|
-
}
|
|
199
|
-
}
|
|
16
|
+
export const listen = async () => {
|
|
17
|
+
const method = IpcChildType.Auto()
|
|
18
|
+
const ipc = await IpcChild.listen({
|
|
19
|
+
method,
|
|
20
|
+
})
|
|
21
|
+
HandleIpc.handleIpc(ipc)
|
|
200
22
|
}
|
|
@@ -1,22 +1,14 @@
|
|
|
1
1
|
import { codeFrameColumns } from '@babel/code-frame'
|
|
2
2
|
import { LinesAndColumns } from 'lines-and-columns'
|
|
3
3
|
import { readFileSync } from 'node:fs'
|
|
4
|
-
import { fileURLToPath } from 'node:url'
|
|
5
|
-
import { AssertionError } from '../AssertionError/AssertionError.js'
|
|
6
4
|
import * as CleanStack from '../CleanStack/CleanStack.js'
|
|
7
5
|
import * as EncodingType from '../EncodingType/EncodingType.js'
|
|
8
6
|
import * as ErrorCodes from '../ErrorCodes/ErrorCodes.js'
|
|
7
|
+
import * as GetActualPath from '../GetActualPath/GetActualPath.js'
|
|
9
8
|
import * as JoinLines from '../JoinLines/JoinLines.js'
|
|
10
9
|
import * as Json from '../Json/Json.js'
|
|
11
10
|
import * as SplitLines from '../SplitLines/SplitLines.js'
|
|
12
11
|
|
|
13
|
-
const getActualPath = (fileUri) => {
|
|
14
|
-
if (fileUri.startsWith('file://')) {
|
|
15
|
-
return fileURLToPath(fileUri)
|
|
16
|
-
}
|
|
17
|
-
return fileUri
|
|
18
|
-
}
|
|
19
|
-
|
|
20
12
|
const RE_MODULE_NOT_FOUND_STACK = /Cannot find package '([^']+)' imported from (.+)$/
|
|
21
13
|
|
|
22
14
|
const prepareModuleNotFoundError = (error) => {
|
|
@@ -61,13 +53,6 @@ const prepareModuleNotFoundError = (error) => {
|
|
|
61
53
|
}
|
|
62
54
|
}
|
|
63
55
|
|
|
64
|
-
const getStackLinesToCut = (error) => {
|
|
65
|
-
if (error instanceof AssertionError) {
|
|
66
|
-
return 1
|
|
67
|
-
}
|
|
68
|
-
return 0
|
|
69
|
-
}
|
|
70
|
-
|
|
71
56
|
export const prepare = (error) => {
|
|
72
57
|
if (error && error.code === ErrorCodes.ERR_MODULE_NOT_FOUND) {
|
|
73
58
|
return prepareModuleNotFoundError(error)
|
|
@@ -79,8 +64,7 @@ export const prepare = (error) => {
|
|
|
79
64
|
error = cause
|
|
80
65
|
}
|
|
81
66
|
}
|
|
82
|
-
const
|
|
83
|
-
const lines = CleanStack.cleanStack(error.stack).slice(linesToCut)
|
|
67
|
+
const lines = CleanStack.cleanStack(error.stack)
|
|
84
68
|
const file = lines[0]
|
|
85
69
|
let codeFrame = ''
|
|
86
70
|
if (error.codeFrame) {
|
|
@@ -92,7 +76,7 @@ export const prepare = (error) => {
|
|
|
92
76
|
}
|
|
93
77
|
if (match) {
|
|
94
78
|
const [_, path, line, column] = match
|
|
95
|
-
const actualPath = getActualPath(path)
|
|
79
|
+
const actualPath = GetActualPath.getActualPath(path)
|
|
96
80
|
const rawLines = readFileSync(actualPath, EncodingType.Utf8)
|
|
97
81
|
const location = {
|
|
98
82
|
start: {
|
|
@@ -109,6 +93,7 @@ export const prepare = (error) => {
|
|
|
109
93
|
stack: relevantStack,
|
|
110
94
|
codeFrame,
|
|
111
95
|
type: error.constructor.name,
|
|
96
|
+
code: error.code,
|
|
112
97
|
}
|
|
113
98
|
}
|
|
114
99
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const processDisplayName = 'shared process'
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
export const SharedProcess = 'lvce.shared-process'
|
|
2
1
|
export const ExtensionHost = 'lvce.extension-host'
|
|
3
2
|
export const ExtensionHostHelperProcess = 'lvce.extension-host-helper-process'
|
|
3
|
+
export const SharedProcess = 'lvce.shared-process'
|
|
4
|
+
export const TerminalProcess = 'lvce.terminal-process'
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import exitHook from 'exit-hook'
|
|
2
|
+
import * as Debug from '../Debug/Debug.js'
|
|
3
|
+
import * as IpcParent from '../IpcParent/IpcParent.js'
|
|
4
|
+
import * as IpcParentType from '../IpcParentType/IpcParentType.js'
|
|
5
|
+
import * as PtyHostPath from '../PtyHostPath/PtyHostPath.js'
|
|
6
|
+
|
|
7
|
+
export const state = {
|
|
8
|
+
/**
|
|
9
|
+
* @type {any}
|
|
10
|
+
*/
|
|
11
|
+
ptyHost: undefined,
|
|
12
|
+
/**
|
|
13
|
+
* @type {any}
|
|
14
|
+
*/
|
|
15
|
+
ptyHostPromise: undefined,
|
|
16
|
+
/**
|
|
17
|
+
* @type {any[]}
|
|
18
|
+
*/
|
|
19
|
+
pendingMessages: [],
|
|
20
|
+
send(message) {
|
|
21
|
+
state.pendingMessages.push(message)
|
|
22
|
+
},
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const cleanUpAll = () => {
|
|
26
|
+
if (state.ptyHost) {
|
|
27
|
+
state.ptyHost.dispose()
|
|
28
|
+
state.ptyHost = undefined
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const handleProcessExit = () => {
|
|
33
|
+
Debug.debug('shared process exit, clean terminals')
|
|
34
|
+
cleanUpAll()
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const createPtyHost = async () => {
|
|
38
|
+
exitHook(handleProcessExit)
|
|
39
|
+
const ptyHostPath = await PtyHostPath.getPtyHostPath()
|
|
40
|
+
const ptyHost = await IpcParent.create({
|
|
41
|
+
method: IpcParentType.NodeForkedProcess,
|
|
42
|
+
path: ptyHostPath,
|
|
43
|
+
argv: [],
|
|
44
|
+
stdio: 'inherit',
|
|
45
|
+
name: 'Terminal Process',
|
|
46
|
+
})
|
|
47
|
+
const handleClose = () => {
|
|
48
|
+
ptyHost.off('close', handleClose)
|
|
49
|
+
state.ptyHost = undefined
|
|
50
|
+
state.ptyHostPromise = undefined
|
|
51
|
+
}
|
|
52
|
+
ptyHost.on('close', handleClose)
|
|
53
|
+
state.ptyHost = ptyHost
|
|
54
|
+
return ptyHost
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export const getOrCreate = () => {
|
|
58
|
+
Debug.debug('creating pty host')
|
|
59
|
+
if (!state.ptyHostPromise) {
|
|
60
|
+
state.ptyHostPromise = createPtyHost()
|
|
61
|
+
}
|
|
62
|
+
return state.ptyHostPromise
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export const getCurrentInstance = () => {
|
|
66
|
+
return state.ptyHost
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export const disposeAll = () => {
|
|
70
|
+
if (state.ptyHost) {
|
|
71
|
+
state.ptyHost.removeAllListeners()
|
|
72
|
+
if (state.ptyHost) {
|
|
73
|
+
state.ptyHost.dispose()
|
|
74
|
+
state.ptyHost = undefined
|
|
75
|
+
}
|
|
76
|
+
state.ptyHostState = /* None */ 0
|
|
77
|
+
state.send = (message) => {
|
|
78
|
+
state.pendingMessages.push(message)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process'
|
|
2
|
+
import * as IsElectron from '../IsElectron/IsElectron.js'
|
|
3
|
+
import * as Path from '../Path/Path.js'
|
|
4
|
+
import * as Root from '../Root/Root.js'
|
|
5
|
+
import { VError } from '../VError/VError.js'
|
|
6
|
+
|
|
7
|
+
const getElectronRebuildPath = () => {
|
|
8
|
+
return Path.join(Root.root, 'packages', 'main-process', 'node_modules', '.bin', 'electron-rebuild')
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const getPtyHostPath = () => {
|
|
12
|
+
return Path.join(Root.root, 'packages', 'pty-host')
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param {string} cwd
|
|
17
|
+
*/
|
|
18
|
+
const rebuildNodePtyElectron = async (cwd) => {
|
|
19
|
+
const electronRebuildPath = getElectronRebuildPath()
|
|
20
|
+
const childProcess = spawn(electronRebuildPath, [], {
|
|
21
|
+
cwd,
|
|
22
|
+
stdio: 'inherit',
|
|
23
|
+
})
|
|
24
|
+
// TODO wait for child process to be finished
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @param {string} cwd
|
|
29
|
+
*/
|
|
30
|
+
const rebuildNodePtyNode = async (cwd) => {
|
|
31
|
+
const childProcess = spawn('npm', ['rebuild'], {
|
|
32
|
+
cwd,
|
|
33
|
+
stdio: 'inherit',
|
|
34
|
+
})
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const rebuildNodePty = async () => {
|
|
38
|
+
try {
|
|
39
|
+
const ptyHostPath = getPtyHostPath()
|
|
40
|
+
if (IsElectron.isElectron()) {
|
|
41
|
+
await rebuildNodePtyElectron(ptyHostPath)
|
|
42
|
+
} else {
|
|
43
|
+
await rebuildNodePtyNode(ptyHostPath)
|
|
44
|
+
}
|
|
45
|
+
} catch (error) {
|
|
46
|
+
throw new VError(error, `Failed to rebuild node-pty`)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -1,138 +1,63 @@
|
|
|
1
|
-
import exitHook from 'exit-hook'
|
|
2
1
|
import * as Assert from '../Assert/Assert.js'
|
|
3
|
-
import * as Debug from '../Debug/Debug.js'
|
|
4
|
-
import * as IpcParent from '../IpcParent/IpcParent.js'
|
|
5
|
-
import * as IpcParentType from '../IpcParentType/IpcParentType.js'
|
|
6
2
|
import * as JsonRpcVersion from '../JsonRpcVersion/JsonRpcVersion.js'
|
|
7
|
-
import * as
|
|
3
|
+
import * as PtyHost from '../PtyHost/PtyHost.js'
|
|
4
|
+
import * as TerminalState from '../TerminalState/TerminalState.js'
|
|
5
|
+
import { VError } from '../VError/VError.js'
|
|
8
6
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
// }
|
|
31
|
-
// return {
|
|
32
|
-
// command: 'bash',
|
|
33
|
-
// args: ['-i'],
|
|
34
|
-
// }
|
|
35
|
-
// }
|
|
36
|
-
|
|
37
|
-
const cleanUpAll = () => {
|
|
38
|
-
if (state.ptyHost) {
|
|
39
|
-
state.ptyHost.kill()
|
|
7
|
+
const createTerminal = (ptyHost, socket) => {
|
|
8
|
+
const handleMessage = (message) => {
|
|
9
|
+
socket.send(message)
|
|
10
|
+
}
|
|
11
|
+
const handleClose = () => {
|
|
12
|
+
// socket.off('close', handleClose)
|
|
13
|
+
ptyHost.off('message', handleMessage)
|
|
14
|
+
ptyHost.dispose()
|
|
15
|
+
}
|
|
16
|
+
const dispose = () => {
|
|
17
|
+
ptyHost.off('message', handleMessage)
|
|
18
|
+
socket.off('close', handleClose)
|
|
19
|
+
}
|
|
20
|
+
ptyHost.on('message', handleMessage)
|
|
21
|
+
socket.on('close', handleClose)
|
|
22
|
+
return {
|
|
23
|
+
ptyHost,
|
|
24
|
+
socket,
|
|
25
|
+
handleMessage,
|
|
26
|
+
handleClose,
|
|
27
|
+
dispose,
|
|
40
28
|
}
|
|
41
29
|
}
|
|
42
30
|
|
|
43
|
-
const handleProcessExit = () => {
|
|
44
|
-
Debug.debug('shared process exit, clean terminals')
|
|
45
|
-
cleanUpAll()
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const createPtyHost = async () => {
|
|
49
|
-
exitHook(handleProcessExit)
|
|
50
|
-
const ptyHostPath = await PtyHostPath.getPtyHostPath()
|
|
51
|
-
const ptyHost = await IpcParent.create({
|
|
52
|
-
method: IpcParentType.NodeForkedProcess,
|
|
53
|
-
path: ptyHostPath,
|
|
54
|
-
argv: ['--ipc-type=node-forked-process'],
|
|
55
|
-
stdio: 'inherit',
|
|
56
|
-
name: 'Terminal Process',
|
|
57
|
-
})
|
|
58
|
-
return ptyHost
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// const send = (method, ...params) => {
|
|
62
|
-
|
|
63
|
-
// state.ptyHost.send({
|
|
64
|
-
// jsonrpc: '2.0',
|
|
65
|
-
// method,
|
|
66
|
-
// params,
|
|
67
|
-
// })
|
|
68
|
-
// }
|
|
69
|
-
|
|
70
31
|
export const create = async (socket, id, cwd) => {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
method: 'Viewlet.send',
|
|
88
|
-
params: ['Terminal', 'handleData', data],
|
|
89
|
-
})
|
|
90
|
-
}
|
|
91
|
-
ptyHost.on('message', handleMessage)
|
|
92
|
-
state.send = (message) => {
|
|
93
|
-
ptyHost.send(message)
|
|
94
|
-
}
|
|
95
|
-
while (state.pendingMessages.length > 0) {
|
|
96
|
-
state.send(state.pendingMessages.shift())
|
|
97
|
-
}
|
|
98
|
-
state.ptyHost = ptyHost
|
|
99
|
-
break
|
|
100
|
-
}
|
|
101
|
-
case /* Creating */ 1: {
|
|
102
|
-
break
|
|
103
|
-
}
|
|
104
|
-
case /* Ready */ 2: {
|
|
105
|
-
break
|
|
106
|
-
}
|
|
107
|
-
default:
|
|
108
|
-
break
|
|
32
|
+
try {
|
|
33
|
+
Assert.object(socket)
|
|
34
|
+
Assert.number(id)
|
|
35
|
+
Assert.string(cwd)
|
|
36
|
+
// TODO race condition because of await
|
|
37
|
+
const ptyHost = await PtyHost.getOrCreate()
|
|
38
|
+
const terminal = createTerminal(ptyHost, socket)
|
|
39
|
+
TerminalState.add(id, terminal)
|
|
40
|
+
// TODO use invoke
|
|
41
|
+
ptyHost.send({
|
|
42
|
+
jsonrpc: JsonRpcVersion.Two,
|
|
43
|
+
method: 'Terminal.create',
|
|
44
|
+
params: [id, cwd],
|
|
45
|
+
})
|
|
46
|
+
} catch (error) {
|
|
47
|
+
throw new VError(error, `Failed to create terminal`)
|
|
109
48
|
}
|
|
110
|
-
state.send({
|
|
111
|
-
jsonrpc: JsonRpcVersion.Two,
|
|
112
|
-
method: 'Terminal.create',
|
|
113
|
-
params: [id, cwd],
|
|
114
|
-
})
|
|
115
|
-
const handleClose = () => {
|
|
116
|
-
if (state.ptyHost) {
|
|
117
|
-
state.ptyHost.dispose()
|
|
118
|
-
state.ptyHost = undefined
|
|
119
|
-
state.ptyHostState = /* None */ 0
|
|
120
|
-
state.send = (message) => {
|
|
121
|
-
state.pendingMessages.push(message)
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
socket.on('close', handleClose)
|
|
126
49
|
}
|
|
127
50
|
|
|
128
51
|
export const write = (id, data) => {
|
|
129
52
|
Assert.number(id)
|
|
130
53
|
Assert.string(data)
|
|
131
|
-
|
|
54
|
+
const ptyHost = PtyHost.getCurrentInstance()
|
|
55
|
+
if (!ptyHost) {
|
|
132
56
|
console.log('[shared-process] pty host not ready')
|
|
133
57
|
return
|
|
134
58
|
}
|
|
135
|
-
|
|
59
|
+
// TODO should use invoke
|
|
60
|
+
ptyHost.send({
|
|
136
61
|
jsonrpc: JsonRpcVersion.Two,
|
|
137
62
|
method: 'Terminal.write',
|
|
138
63
|
params: [id, data],
|
|
@@ -143,8 +68,13 @@ export const resize = (state, columns, rows) => {
|
|
|
143
68
|
// state.pty.resize(columns, rows)
|
|
144
69
|
}
|
|
145
70
|
|
|
146
|
-
export const dispose = (id) => {
|
|
147
|
-
|
|
71
|
+
export const dispose = async (id) => {
|
|
72
|
+
const terminal = TerminalState.get(id)
|
|
73
|
+
terminal.dispose()
|
|
74
|
+
TerminalState.remove(id)
|
|
75
|
+
const ptyHost = await PtyHost.getOrCreate()
|
|
76
|
+
// TODO use invoke
|
|
77
|
+
ptyHost.send({
|
|
148
78
|
jsonrpc: JsonRpcVersion.Two,
|
|
149
79
|
method: 'Terminal.dispose',
|
|
150
80
|
params: [id],
|
|
@@ -152,13 +82,5 @@ export const dispose = (id) => {
|
|
|
152
82
|
}
|
|
153
83
|
|
|
154
84
|
export const disposeAll = () => {
|
|
155
|
-
|
|
156
|
-
state.ptyHost.removeAllListeners()
|
|
157
|
-
state.ptyHost.kill()
|
|
158
|
-
state.ptyHost = undefined
|
|
159
|
-
state.ptyHostState = /* None */ 0
|
|
160
|
-
state.send = (message) => {
|
|
161
|
-
state.pendingMessages.push(message)
|
|
162
|
-
}
|
|
163
|
-
}
|
|
85
|
+
PtyHost.disposeAll()
|
|
164
86
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const state = {
|
|
2
|
+
terminals: Object.create(null),
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export const add = (id, terminal) => {
|
|
6
|
+
state.terminals[id] = terminal
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const get = (id) => {
|
|
10
|
+
return state.terminals[id]
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const remove = (id) => {
|
|
14
|
+
delete state.terminals[id]
|
|
15
|
+
}
|