protected createMessageTransports()

in client/src/node/main.ts [243:503]


	protected createMessageTransports(encoding: string): Promise<MessageTransports> {

		function getEnvironment(env: any, fork: boolean): any {
			if (!env && !fork) {
				return undefined;
			}
			const result: any = Object.create(null);
			Object.keys(process.env).forEach(key => result[key] = process.env[key]);
			if (fork) {
				result['ELECTRON_RUN_AS_NODE'] = '1';
				result['ELECTRON_NO_ASAR'] = '1';
			}
			if (env) {
				Object.keys(env).forEach(key => result[key] = env[key]);
			}
			return result;
		}

		const debugStartWith: string[] = ['--debug=', '--debug-brk=', '--inspect=', '--inspect-brk='];
		const debugEquals: string[] = ['--debug', '--debug-brk', '--inspect', '--inspect-brk'];
		function startedInDebugMode(): boolean {
			let args: string[] = (process as any).execArgv;
			if (args) {
				return args.some((arg) => {
					return debugStartWith.some(value => arg.startsWith(value)) ||
						debugEquals.some(value => arg === value);
				});
			}
			return false;
		}

		function assertStdio(process: cp.ChildProcess): asserts process is cp.ChildProcessWithoutNullStreams {
			if (process.stdin === null || process.stdout === null || process.stderr === null) {
				throw new Error('Process created without stdio streams');
			}
		}

		const server = this._serverOptions;
		// We got a function.
		if (Is.func(server)) {
			return server().then((result) => {
				if (MessageTransports.is(result)) {
					this._isDetached = !!result.detached;
					return result;
				} else if (StreamInfo.is(result)) {
					this._isDetached = !!result.detached;
					return { reader: new StreamMessageReader(result.reader), writer: new StreamMessageWriter(result.writer) };
				} else {
					let cp: ChildProcess;
					if (ChildProcessInfo.is(result)) {
						cp = result.process;
						this._isDetached = result.detached;
					} else {
						cp = result;
						this._isDetached = false;
					}
					cp.stderr!.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding)));
					return { reader: new StreamMessageReader(cp.stdout!), writer: new StreamMessageWriter(cp.stdin!) };
				}
			});
		}
		let json: NodeModule | Executable;
		let runDebug = <{ run: any; debug: any }>server;
		if (runDebug.run || runDebug.debug) {
			if (this._forceDebug || startedInDebugMode()) {
				json = runDebug.debug;
				this._isInDebugMode = true;
			} else {
				json = runDebug.run;
				this._isInDebugMode = false;
			}
		} else {
			json = server as NodeModule | Executable;
		}
		return this._getServerWorkingDir(json.options).then(serverWorkingDir => {
			if (NodeModule.is(json) && json.module) {
				let node = json;
				let transport = node.transport || TransportKind.stdio;
				if (node.runtime) {
					const args: string[] = [];
					const options: ForkOptions = node.options ?? Object.create(null);
					if (options.execArgv) {
						options.execArgv.forEach(element => args.push(element));
					}
					args.push(node.module);
					if (node.args) {
						node.args.forEach(element => args.push(element));
					}
					const execOptions: cp.SpawnOptionsWithoutStdio = Object.create(null);
					execOptions.cwd = serverWorkingDir;
					execOptions.env = getEnvironment(options.env, false);
					const runtime = this._getRuntimePath(node.runtime, serverWorkingDir);
					let pipeName: string | undefined = undefined;
					if (transport === TransportKind.ipc) {
						// exec options not correctly typed in lib
						execOptions.stdio = <any>[null, null, null, 'ipc'];
						args.push('--node-ipc');
					} else if (transport === TransportKind.stdio) {
						args.push('--stdio');
					} else if (transport === TransportKind.pipe) {
						pipeName = generateRandomPipeName();
						args.push(`--pipe=${pipeName}`);
					} else if (Transport.isSocket(transport)) {
						args.push(`--socket=${transport.port}`);
					}
					args.push(`--clientProcessId=${process.pid.toString()}`);
					if (transport === TransportKind.ipc || transport === TransportKind.stdio) {
						const serverProcess = cp.spawn(runtime, args, execOptions);
						if (!serverProcess || !serverProcess.pid) {
							return Promise.reject<MessageTransports>(`Launching server using runtime ${runtime} failed.`);
						}
						this._serverProcess = serverProcess;
						serverProcess.stderr.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding)));
						if (transport === TransportKind.ipc) {
							serverProcess.stdout.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding)));
							return Promise.resolve({ reader: new IPCMessageReader(serverProcess), writer: new IPCMessageWriter(serverProcess) });
						} else {
							return Promise.resolve({ reader: new StreamMessageReader(serverProcess.stdout), writer: new StreamMessageWriter(serverProcess.stdin) });
						}
					} else if (transport === TransportKind.pipe) {
						return createClientPipeTransport(pipeName!).then((transport) => {
							const process = cp.spawn(runtime, args, execOptions);
							if (!process || !process.pid) {
								return Promise.reject<MessageTransports>(`Launching server using runtime ${runtime} failed.`);
							}
							this._serverProcess = process;
							process.stderr.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding)));
							process.stdout.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding)));
							return transport.onConnected().then((protocol) => {
								return { reader: protocol[0], writer: protocol[1] };
							});
						});
					} else if (Transport.isSocket(transport)) {
						return createClientSocketTransport(transport.port).then((transport) => {
							const process = cp.spawn(runtime, args, execOptions);
							if (!process || !process.pid) {
								return Promise.reject<MessageTransports>(`Launching server using runtime ${runtime} failed.`);
							}
							this._serverProcess = process;
							process.stderr.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding)));
							process.stdout.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding)));
							return transport.onConnected().then((protocol) => {
								return { reader: protocol[0], writer: protocol[1] };
							});
						});
					}
				} else {
					let pipeName: string | undefined = undefined;
					return new Promise<MessageTransports>((resolve, reject) => {
						const args = (node.args && node.args.slice()) ?? [];
						if (transport === TransportKind.ipc) {
							args.push('--node-ipc');
						} else if (transport === TransportKind.stdio) {
							args.push('--stdio');
						} else if (transport === TransportKind.pipe) {
							pipeName = generateRandomPipeName();
							args.push(`--pipe=${pipeName}`);
						} else if (Transport.isSocket(transport)) {
							args.push(`--socket=${transport.port}`);
						}
						args.push(`--clientProcessId=${process.pid.toString()}`);
						const options: cp.ForkOptions = node.options ?? Object.create(null);
						options.env = getEnvironment(options.env, true);
						options.execArgv = options.execArgv || [];
						options.cwd = serverWorkingDir;
						options.silent = true;
						if (transport === TransportKind.ipc || transport === TransportKind.stdio) {
							const sp = cp.fork(node.module, args || [], options);
							assertStdio(sp);
							this._serverProcess = sp;
							sp.stderr.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding)));
							if (transport === TransportKind.ipc) {
								sp.stdout.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding)));
								resolve({ reader: new IPCMessageReader(this._serverProcess), writer: new IPCMessageWriter(this._serverProcess) });
							} else {
								resolve({ reader: new StreamMessageReader(sp.stdout), writer: new StreamMessageWriter(sp.stdin) });
							}
						} else if (transport === TransportKind.pipe) {
							createClientPipeTransport(pipeName!).then((transport) => {
								const sp = cp.fork(node.module, args || [], options);
								assertStdio(sp);
								this._serverProcess = sp;
								sp.stderr.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding)));
								sp.stdout.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding)));
								transport.onConnected().then((protocol) => {
									resolve({ reader: protocol[0], writer: protocol[1] });
								}, reject);
							}, reject);
						} else if (Transport.isSocket(transport)) {
							createClientSocketTransport(transport.port).then((transport) => {
								const sp = cp.fork(node.module, args || [], options);
								assertStdio(sp);
								this._serverProcess = sp;
								sp.stderr.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding)));
								sp.stdout.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding)));
								transport.onConnected().then((protocol) => {
									resolve({ reader: protocol[0], writer: protocol[1] });
								}, reject);
							}, reject);
						}
					});
				}
			} else if (Executable.is(json) && json.command) {
				const command: Executable = <Executable>json;
				const args: string[] = json.args !== undefined ? json.args.slice(0) : [];
				let pipeName: string | undefined = undefined;
				const transport = json.transport;
				if (transport === TransportKind.stdio) {
					args.push('--stdio');
				} else if (transport === TransportKind.pipe) {
					pipeName = generateRandomPipeName();
					args.push(`--pipe=${pipeName}`);
				} else if (Transport.isSocket(transport)) {
					args.push(`--socket=${transport.port}`);
				} else if (transport === TransportKind.ipc) {
					throw new Error(`Transport kind ipc is not support for command executable`);
				}
				const options = Object.assign({}, command.options);
				options.cwd = options.cwd || serverWorkingDir;
				if (transport === undefined || transport === TransportKind.stdio) {
					const serverProcess = cp.spawn(command.command, args, options);
					if (!serverProcess || !serverProcess.pid) {
						return Promise.reject<MessageTransports>(`Launching server using command ${command.command} failed.`);
					}
					serverProcess.stderr.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding)));
					this._serverProcess = serverProcess;
					this._isDetached = !!options.detached;
					return Promise.resolve({ reader: new StreamMessageReader(serverProcess.stdout), writer: new StreamMessageWriter(serverProcess.stdin) });
				} else if (transport === TransportKind.pipe) {
					return 	createClientPipeTransport(pipeName!).then((transport) => {
						const serverProcess = cp.spawn(command.command, args, options);
						if (!serverProcess || !serverProcess.pid) {
							throw new Error(`Launching server using command ${command.command} failed.`);
						}
						this._serverProcess = serverProcess;
						this._isDetached = !!options.detached;
						serverProcess.stderr.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding)));
						serverProcess.stdout.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding)));
						return transport.onConnected().then((protocol) => {
							return { reader: protocol[0], writer: protocol[1] };
						});
					});
				} else if (Transport.isSocket(transport)) {
					return createClientSocketTransport(transport.port).then((transport) => {
						const serverProcess = cp.spawn(command.command, args, options);
						if (!serverProcess || !serverProcess.pid) {
							throw new Error(`Launching server using command ${command.command} failed.`);
						}
						this._serverProcess = serverProcess;
						this._isDetached = !!options.detached;
						serverProcess.stderr.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding)));
						serverProcess.stdout.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding)));
						return transport.onConnected().then((protocol) => {
							return { reader: protocol[0], writer: protocol[1] };
						});
					});
				}
			}
			return Promise.reject<MessageTransports>(new Error(`Unsupported server configuration ` + JSON.stringify(server, null, 4)));
		});
	}