deploy: current vibn theia state
Some checks failed
Playwright Tests / Playwright Tests (ubuntu-22.04, Node.js 22.x) (push) Has been cancelled
3PP License Check / 3PP License Check (11, 22.x, ubuntu-22.04) (push) Has been cancelled
Publish packages to NPM / Perform Publishing (push) Has been cancelled

Made-with: Cursor
This commit is contained in:
2026-02-27 12:01:08 -08:00
commit 8bb5110148
3782 changed files with 640947 additions and 0 deletions

View File

@@ -0,0 +1,231 @@
// *****************************************************************************
// Copyright (C) 2018 Red Hat, Inc. and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
import { ConnectionErrorHandler, ContributionProvider, ILogger, MessageService } from '@theia/core/lib/common';
import { Deferred } from '@theia/core/lib/common/promise-util';
import { BinaryMessagePipe } from '@theia/core/lib/node/messaging/binary-message-pipe';
import { createIpcEnv } from '@theia/core/lib/node/messaging/ipc-protocol';
import { inject, injectable, named } from '@theia/core/shared/inversify';
import * as cp from 'child_process';
import { Duplex } from 'stream';
import { HostedPluginClient, PLUGIN_HOST_BACKEND, PluginHostEnvironmentVariable, ServerPluginRunner } from '../../common/plugin-protocol';
import { HostedPluginCliContribution } from './hosted-plugin-cli-contribution';
import { HostedPluginLocalizationService } from './hosted-plugin-localization-service';
import { ProcessTerminateMessage, ProcessTerminatedMessage } from './hosted-plugin-protocol';
import { ProcessUtils } from '@theia/core/lib/node/process-utils';
export interface IPCConnectionOptions {
readonly serverName: string;
readonly logger: ILogger;
readonly args: string[];
readonly errorHandler?: ConnectionErrorHandler;
}
export const HostedPluginProcessConfiguration = Symbol('HostedPluginProcessConfiguration');
export interface HostedPluginProcessConfiguration {
readonly path: string
}
@injectable()
export class HostedPluginProcess implements ServerPluginRunner {
@inject(HostedPluginProcessConfiguration)
protected configuration: HostedPluginProcessConfiguration;
@inject(ILogger)
protected readonly logger: ILogger;
@inject(HostedPluginCliContribution)
protected readonly cli: HostedPluginCliContribution;
@inject(ContributionProvider)
@named(PluginHostEnvironmentVariable)
protected readonly pluginHostEnvironmentVariables: ContributionProvider<PluginHostEnvironmentVariable>;
@inject(MessageService)
protected readonly messageService: MessageService;
@inject(HostedPluginLocalizationService)
protected readonly localizationService: HostedPluginLocalizationService;
@inject(ProcessUtils)
protected readonly processUtils: ProcessUtils;
private childProcess: cp.ChildProcess | undefined;
private messagePipe?: BinaryMessagePipe;
private client: HostedPluginClient;
private terminatingPluginServer = false;
public setClient(client: HostedPluginClient): void {
if (this.client) {
if (this.childProcess) {
this.runPluginServer();
}
}
this.client = client;
}
public clientClosed(): void {
}
public setDefault(defaultRunner: ServerPluginRunner): void {
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
public acceptMessage(pluginHostId: string, message: Uint8Array): boolean {
return pluginHostId === 'main';
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
public onMessage(pluginHostId: string, message: Uint8Array): void {
if (this.messagePipe) {
this.messagePipe.send(message);
}
}
async terminatePluginServer(): Promise<void> {
if (this.childProcess === undefined) {
return;
}
this.terminatingPluginServer = true;
// eslint-disable-next-line @typescript-eslint/no-shadow
const cp = this.childProcess;
this.childProcess = undefined;
const waitForTerminated = new Deferred<void>();
cp.on('message', message => {
const msg = JSON.parse(message as string);
if (ProcessTerminatedMessage.is(msg)) {
waitForTerminated.resolve();
}
});
const stopTimeout = this.cli.pluginHostStopTimeout;
cp.send(JSON.stringify({ type: ProcessTerminateMessage.TYPE, stopTimeout }));
const terminateTimeout = this.cli.pluginHostTerminateTimeout;
if (terminateTimeout) {
await Promise.race([
waitForTerminated.promise,
new Promise(resolve => setTimeout(resolve, terminateTimeout))
]);
} else {
await waitForTerminated.promise;
}
this.killProcessTree(cp.pid!);
}
killProcessTree(parentPid: number): void {
this.processUtils.terminateProcessTree(parentPid);
}
protected killProcess(pid: number): void {
try {
process.kill(pid);
} catch (e) {
if (e && 'code' in e && e.code === 'ESRCH') {
return;
}
this.logger.error(`[${pid}] failed to kill`, e);
}
}
public runPluginServer(serverName?: string): void {
if (this.childProcess) {
this.terminatePluginServer();
}
this.terminatingPluginServer = false;
this.childProcess = this.fork({
serverName: serverName ?? 'hosted-plugin',
logger: this.logger,
args: []
});
this.messagePipe = new BinaryMessagePipe(this.childProcess.stdio[4] as Duplex);
this.messagePipe.onMessage(buffer => {
if (this.client) {
this.client.postMessage(PLUGIN_HOST_BACKEND, buffer);
}
});
}
readonly HOSTED_PLUGIN_ENV_REGEXP_EXCLUSION = new RegExp('HOSTED_PLUGIN*');
private fork(options: IPCConnectionOptions): cp.ChildProcess {
// create env and add PATH to it so any executable from root process is available
const env = createIpcEnv({ env: process.env });
for (const key of Object.keys(env)) {
if (this.HOSTED_PLUGIN_ENV_REGEXP_EXCLUSION.test(key)) {
delete env[key];
}
}
env['VSCODE_NLS_CONFIG'] = JSON.stringify(this.localizationService.getNlsConfig());
// apply external env variables
this.pluginHostEnvironmentVariables.getContributions().forEach(envVar => envVar.process(env));
if (this.cli.extensionTestsPath) {
env.extensionTestsPath = this.cli.extensionTestsPath;
}
const forkOptions: cp.ForkOptions = {
silent: true,
env: env,
execArgv: [],
// 5th element MUST be 'overlapped' for it to work properly on Windows.
// 'overlapped' works just like 'pipe' on non-Windows platforms.
// See: https://nodejs.org/docs/latest-v14.x/api/child_process.html#child_process_options_stdio
stdio: ['pipe', 'pipe', 'pipe', 'ipc', 'overlapped']
};
const inspectArgPrefix = `--${options.serverName}-inspect`;
const inspectArg = process.argv.find(v => v.startsWith(inspectArgPrefix));
if (inspectArg !== undefined) {
forkOptions.execArgv = ['--nolazy', `--inspect${inspectArg.substring(inspectArgPrefix.length)}`];
}
const childProcess = cp.fork(this.configuration.path, options.args, forkOptions);
childProcess.stdout!.on('data', data => this.logger.info(`[${options.serverName}: ${childProcess.pid}] ${data.toString().trim()}`));
childProcess.stderr!.on('data', data => this.logger.error(`[${options.serverName}: ${childProcess.pid}] ${data.toString().trim()}`));
this.logger.debug(`[${options.serverName}: ${childProcess.pid}] IPC started`);
childProcess.once('exit', (code: number, signal: string) => this.onChildProcessExit(options.serverName, childProcess.pid!, code, signal));
childProcess.on('error', err => this.onChildProcessError(err));
return childProcess;
}
private onChildProcessExit(serverName: string, pid: number, code: number, signal: string): void {
if (this.terminatingPluginServer) {
return;
}
this.logger.error(`[${serverName}: ${pid}] IPC exited, with signal: ${signal}, and exit code: ${code}`);
const message = 'Plugin runtime crashed unexpectedly, all plugins are not working, please reload the page.';
let hintMessage: string = 'If it doesn\'t help, please check Theia server logs.';
if (signal && signal.toUpperCase() === 'SIGKILL') {
// May happen in case of OOM or manual force stop.
hintMessage = 'Probably there is not enough memory for the plugins. ' + hintMessage;
}
this.messageService.error(message + ' ' + hintMessage, { timeout: 15 * 60 * 1000 });
}
private onChildProcessError(err: Error): void {
this.logger.error(`Error from plugin host: ${err.message}`);
}
}