Path: blob/main/src/vs/workbench/api/node/extHostCLIServer.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { createRandomIPCHandle } from '../../../base/parts/ipc/node/ipc.net.js';6import * as http from 'http';7import * as fs from 'fs';8import { IExtHostCommands } from '../common/extHostCommands.js';9import { IWindowOpenable, IOpenWindowOptions } from '../../../platform/window/common/window.js';10import { URI } from '../../../base/common/uri.js';11import { ILogService } from '../../../platform/log/common/log.js';12import { hasWorkspaceFileExtension } from '../../../platform/workspace/common/workspace.js';1314export interface OpenCommandPipeArgs {15type: 'open';16fileURIs?: string[];17folderURIs?: string[];18forceNewWindow?: boolean;19diffMode?: boolean;20mergeMode?: boolean;21addMode?: boolean;22removeMode?: boolean;23gotoLineMode?: boolean;24forceReuseWindow?: boolean;25waitMarkerFilePath?: string;26remoteAuthority?: string | null;27}2829export interface OpenExternalCommandPipeArgs {30type: 'openExternal';31uris: string[];32}3334export interface StatusPipeArgs {35type: 'status';36}3738export interface ExtensionManagementPipeArgs {39type: 'extensionManagement';40list?: { showVersions?: boolean; category?: string };41install?: string[];42uninstall?: string[];43force?: boolean;44}4546export type PipeCommand = OpenCommandPipeArgs | StatusPipeArgs | OpenExternalCommandPipeArgs | ExtensionManagementPipeArgs;4748export interface ICommandsExecuter {49executeCommand<T>(id: string, ...args: any[]): Promise<T>;50}5152export class CLIServerBase {53private readonly _server: http.Server;5455constructor(56private readonly _commands: ICommandsExecuter,57private readonly logService: ILogService,58private readonly _ipcHandlePath: string,59) {60this._server = http.createServer((req, res) => this.onRequest(req, res));61this.setup().catch(err => {62logService.error(err);63return '';64});65}6667public get ipcHandlePath() {68return this._ipcHandlePath;69}7071private async setup(): Promise<string> {72try {73this._server.listen(this.ipcHandlePath);74this._server.on('error', err => this.logService.error(err));75} catch (err) {76this.logService.error('Could not start open from terminal server.');77}7879return this._ipcHandlePath;80}8182private onRequest(req: http.IncomingMessage, res: http.ServerResponse): void {83const sendResponse = (statusCode: number, returnObj: string | undefined) => {84res.writeHead(statusCode, { 'content-type': 'application/json' });85res.end(JSON.stringify(returnObj || null), (err?: any) => err && this.logService.error(err)); // CodeQL [SM01524] Only the message portion of errors are passed in.86};8788const chunks: string[] = [];89req.setEncoding('utf8');90req.on('data', (d: string) => chunks.push(d));91req.on('end', async () => {92try {93const data: PipeCommand | any = JSON.parse(chunks.join(''));94let returnObj: string | undefined;95switch (data.type) {96case 'open':97returnObj = await this.open(data);98break;99case 'openExternal':100returnObj = await this.openExternal(data);101break;102case 'status':103returnObj = await this.getStatus(data);104break;105case 'extensionManagement':106returnObj = await this.manageExtensions(data);107break;108default:109sendResponse(404, `Unknown message type: ${data.type}`);110break;111}112sendResponse(200, returnObj);113} catch (e) {114const message = e instanceof Error ? e.message : JSON.stringify(e);115sendResponse(500, message);116this.logService.error('Error while processing pipe request', e);117}118});119}120121private async open(data: OpenCommandPipeArgs): Promise<undefined> {122const { fileURIs, folderURIs, forceNewWindow, diffMode, mergeMode, addMode, removeMode, forceReuseWindow, gotoLineMode, waitMarkerFilePath, remoteAuthority } = data;123const urisToOpen: IWindowOpenable[] = [];124if (Array.isArray(folderURIs)) {125for (const s of folderURIs) {126try {127urisToOpen.push({ folderUri: URI.parse(s) });128} catch (e) {129// ignore130}131}132}133if (Array.isArray(fileURIs)) {134for (const s of fileURIs) {135try {136if (hasWorkspaceFileExtension(s)) {137urisToOpen.push({ workspaceUri: URI.parse(s) });138} else {139urisToOpen.push({ fileUri: URI.parse(s) });140}141} catch (e) {142// ignore143}144}145}146const waitMarkerFileURI = waitMarkerFilePath ? URI.file(waitMarkerFilePath) : undefined;147const preferNewWindow = !forceReuseWindow && !waitMarkerFileURI && !addMode && !removeMode;148const windowOpenArgs: IOpenWindowOptions = { forceNewWindow, diffMode, mergeMode, addMode, removeMode, gotoLineMode, forceReuseWindow, preferNewWindow, waitMarkerFileURI, remoteAuthority };149this._commands.executeCommand('_remoteCLI.windowOpen', urisToOpen, windowOpenArgs);150}151152private async openExternal(data: OpenExternalCommandPipeArgs): Promise<undefined> {153for (const uriString of data.uris) {154const uri = URI.parse(uriString);155const urioOpen = uri.scheme === 'file' ? uri : uriString; // workaround for #112577156await this._commands.executeCommand('_remoteCLI.openExternal', urioOpen);157}158}159160private async manageExtensions(data: ExtensionManagementPipeArgs): Promise<string | undefined> {161const toExtOrVSIX = (inputs: string[] | undefined) => inputs?.map(input => /\.vsix$/i.test(input) ? URI.parse(input) : input);162const commandArgs = {163list: data.list,164install: toExtOrVSIX(data.install),165uninstall: toExtOrVSIX(data.uninstall),166force: data.force167};168return await this._commands.executeCommand<string | undefined>('_remoteCLI.manageExtensions', commandArgs);169}170171private async getStatus(data: StatusPipeArgs): Promise<string | undefined> {172return await this._commands.executeCommand<string | undefined>('_remoteCLI.getSystemStatus');173}174175dispose(): void {176this._server.close();177178if (this._ipcHandlePath && process.platform !== 'win32' && fs.existsSync(this._ipcHandlePath)) {179fs.unlinkSync(this._ipcHandlePath);180}181}182}183184export class CLIServer extends CLIServerBase {185constructor(186@IExtHostCommands commands: IExtHostCommands,187@ILogService logService: ILogService188) {189super(commands, logService, createRandomIPCHandle());190}191}192193194