deploy: current vibn theia state
Made-with: Cursor
This commit is contained in:
13
packages/plugin-ext-headless/.eslintrc.js
Normal file
13
packages/plugin-ext-headless/.eslintrc.js
Normal file
@@ -0,0 +1,13 @@
|
||||
/** @type {import('eslint').Linter.Config} */
|
||||
module.exports = {
|
||||
extends: [
|
||||
'../../configs/build.eslintrc.json'
|
||||
],
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: 'tsconfig.json'
|
||||
},
|
||||
rules: {
|
||||
'no-null/no-null': 'off',
|
||||
}
|
||||
};
|
||||
33
packages/plugin-ext-headless/README.md
Normal file
33
packages/plugin-ext-headless/README.md
Normal file
@@ -0,0 +1,33 @@
|
||||
<div align='center'>
|
||||
|
||||
<br />
|
||||
|
||||
<img src='https://raw.githubusercontent.com/eclipse-theia/theia/master/logo/theia.svg?sanitize=true' alt='theia-ext-logo' width='100px' />
|
||||
|
||||
<h2>ECLIPSE THEIA - HEADLESS PLUGIN-EXT EXTENSION</h2>
|
||||
|
||||
<hr />
|
||||
|
||||
</div>
|
||||
|
||||
## Description
|
||||
|
||||
The `@theia/plugin-ext-headless` extension contributes functionality for the backend-only "headless `plugin`" API.
|
||||
The plugin extension host managed by this extension is scoped to the single Theia NodeJS instance.
|
||||
This is unlike the plugin extension hosts managed by the [`@theia/plugin-ext` extension][plugin-ext] which are scoped on a per-frontend-connection basis.
|
||||
|
||||
[plugin-ext]: ../plugin-ext/README.md
|
||||
|
||||
## Implementation
|
||||
|
||||
The implementation is derived from the [`@theia/plugin-ext` extension][plugin-ext] for frontend-scoped plugins.
|
||||
|
||||
## License
|
||||
|
||||
- [Eclipse Public License 2.0](http://www.eclipse.org/legal/epl-2.0/)
|
||||
- [一 (Secondary) GNU General Public License, version 2 with the GNU Classpath Exception](https://projects.eclipse.org/license/secondary-gpl-2.0-cp)
|
||||
|
||||
## Trademark
|
||||
|
||||
"Theia" is a trademark of the Eclipse Foundation
|
||||
<https://www.eclipse.org/theia>
|
||||
56
packages/plugin-ext-headless/package.json
Normal file
56
packages/plugin-ext-headless/package.json
Normal file
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"name": "@theia/plugin-ext-headless",
|
||||
"version": "1.68.0",
|
||||
"description": "Theia - Headless (Backend-only) Plugin Extension",
|
||||
"main": "lib/common/index.js",
|
||||
"typings": "lib/common/index.d.ts",
|
||||
"dependencies": {
|
||||
"@theia/core": "1.68.0",
|
||||
"@theia/plugin-ext": "1.68.0",
|
||||
"@theia/terminal": "1.68.0",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"theiaExtensions": [
|
||||
{
|
||||
"backend": "lib/plugin-ext-headless-module",
|
||||
"backendElectron": "lib/plugin-ext-headless-electron-module"
|
||||
}
|
||||
],
|
||||
"keywords": [
|
||||
"theia-extension"
|
||||
],
|
||||
"license": "EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/eclipse-theia/theia.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/eclipse-theia/theia/issues"
|
||||
},
|
||||
"homepage": "https://github.com/eclipse-theia/theia",
|
||||
"files": [
|
||||
"lib",
|
||||
"src"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "theiaext build",
|
||||
"clean": "theiaext clean",
|
||||
"compile": "theiaext compile",
|
||||
"lint": "theiaext lint",
|
||||
"test": "theiaext test",
|
||||
"watch": "theiaext watch"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@theia/ext-scripts": "1.68.0",
|
||||
"@types/decompress": "^4.2.2",
|
||||
"@types/escape-html": "^0.0.20",
|
||||
"@types/lodash.clonedeep": "^4.5.3"
|
||||
},
|
||||
"nyc": {
|
||||
"extends": "../../configs/nyc.json"
|
||||
},
|
||||
"gitHead": "21358137e41342742707f660b8e222f940a27652"
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2024 EclipseSource 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
|
||||
// *****************************************************************************
|
||||
|
||||
/**
|
||||
* Service identifier for Inversify container modules that are used
|
||||
* to configure the child Container in which scope the services supporting
|
||||
* the Headless Plugin Host are isolated from connection-scoped frontend/backend
|
||||
* plugin hosts and the rest of the Theia Node server.
|
||||
*/
|
||||
export const HeadlessPluginContainerModule = Symbol('HeadlessPluginContainerModule');
|
||||
@@ -0,0 +1,38 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2024 EclipseSource 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
|
||||
// *****************************************************************************
|
||||
|
||||
export * from '@theia/plugin-ext';
|
||||
|
||||
declare module '@theia/plugin-ext' {
|
||||
/**
|
||||
* Extension of the package manifest interface defined by the core plugin framework.
|
||||
*/
|
||||
interface PluginPackage {
|
||||
/**
|
||||
* Analogues of declarations offered by VS Code plugins, but for the headless instantiation.
|
||||
*/
|
||||
headless?: {
|
||||
/** Activation events supported in headless mode, if any. */
|
||||
activationEvents?: string[];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Name for a `string[]` injection binding contributing headless activation event names
|
||||
* supported by the application.
|
||||
*/
|
||||
export const SupportedHeadlessActivationEvents = Symbol('SupportedHeadlessActivationEvents');
|
||||
@@ -0,0 +1,46 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2024 EclipseSource 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 { createProxyIdentifier } from '@theia/plugin-ext/lib/common/rpc-protocol';
|
||||
import { AbstractPluginManagerExt, EnvInit } from '@theia/plugin-ext';
|
||||
import { KeysToKeysToAnyValue } from '@theia/plugin-ext/lib/common/types';
|
||||
import {
|
||||
MAIN_RPC_CONTEXT, PLUGIN_RPC_CONTEXT
|
||||
} from '@theia/plugin-ext/lib/common/plugin-api-rpc';
|
||||
import { ExtPluginApi } from './plugin-ext-headless-api-contribution';
|
||||
|
||||
export const HEADLESSPLUGIN_RPC_CONTEXT = {
|
||||
MESSAGE_REGISTRY_MAIN: PLUGIN_RPC_CONTEXT.MESSAGE_REGISTRY_MAIN,
|
||||
ENV_MAIN: PLUGIN_RPC_CONTEXT.ENV_MAIN,
|
||||
NOTIFICATION_MAIN: PLUGIN_RPC_CONTEXT.NOTIFICATION_MAIN,
|
||||
LOCALIZATION_MAIN: PLUGIN_RPC_CONTEXT.LOCALIZATION_MAIN,
|
||||
};
|
||||
|
||||
export const HEADLESSMAIN_RPC_CONTEXT = {
|
||||
HOSTED_PLUGIN_MANAGER_EXT: createProxyIdentifier<HeadlessPluginManagerExt>('HeadlessPluginManagerExt'),
|
||||
NOTIFICATION_EXT: MAIN_RPC_CONTEXT.NOTIFICATION_EXT,
|
||||
};
|
||||
|
||||
export type HeadlessEnvInit = Pick<EnvInit, 'language' | 'shell' | 'appName' | 'appHost'>;
|
||||
|
||||
export interface HeadlessPluginManagerInitializeParams {
|
||||
activationEvents: string[];
|
||||
globalState: KeysToKeysToAnyValue;
|
||||
env: HeadlessEnvInit;
|
||||
extApi?: ExtPluginApi[];
|
||||
}
|
||||
|
||||
export interface HeadlessPluginManagerExt extends AbstractPluginManagerExt<HeadlessPluginManagerInitializeParams> { }
|
||||
23
packages/plugin-ext-headless/src/common/index.ts
Normal file
23
packages/plugin-ext-headless/src/common/index.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2024 EclipseSource 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
|
||||
// *****************************************************************************
|
||||
|
||||
export * from './headless-plugin-container';
|
||||
export {
|
||||
ExtPluginApi, ExtPluginHeadlessApi, ExtPluginApiProvider,
|
||||
ExtPluginHeadlessApiProvider
|
||||
} from './plugin-ext-headless-api-contribution';
|
||||
export { PluginPackage, SupportedHeadlessActivationEvents } from './headless-plugin-protocol';
|
||||
export * from './headless-plugin-rpc';
|
||||
@@ -0,0 +1,60 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2024 EclipseSource 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 { PluginManager } from '@theia/plugin-ext';
|
||||
import { RPCProtocol } from '@theia/plugin-ext/lib/common/rpc-protocol';
|
||||
|
||||
export * from '@theia/plugin-ext';
|
||||
|
||||
declare module '@theia/plugin-ext' {
|
||||
/**
|
||||
* Plugin API extension description.
|
||||
* This interface describes scripts for all three plugin runtimes: frontend (WebWorker), backend (NodeJs), and headless (NodeJs).
|
||||
*/
|
||||
interface ExtPluginApi extends ExtPluginHeadlessApi {
|
||||
// Note that the frontendInitPath and backendInitPath properties are included by
|
||||
// Typescript interface merge from the @theia/plugin-ext::ExtPluginApi interface.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provider for headless extension API description.
|
||||
*/
|
||||
export interface ExtPluginHeadlessApiProvider {
|
||||
/**
|
||||
* Provide API description.
|
||||
*/
|
||||
provideApi(): ExtPluginHeadlessApi;
|
||||
}
|
||||
|
||||
/**
|
||||
* Headless Plugin API extension description.
|
||||
* This interface describes a script for the headless (NodeJs) runtime outside of the scope of frontend connections.
|
||||
*/
|
||||
export interface ExtPluginHeadlessApi {
|
||||
/**
|
||||
* Path to the script which should be loaded to provide api, module should export `provideApi` function with
|
||||
* [ExtPluginApiBackendInitializationFn](#ExtPluginApiBackendInitializationFn) signature
|
||||
*/
|
||||
headlessInitPath?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Signature of the extension API initialization function for APIs contributed to headless plugins.
|
||||
*/
|
||||
export interface ExtPluginApiHeadlessInitializationFn {
|
||||
(rpc: RPCProtocol, pluginManager: PluginManager): void;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2024 EclipseSource 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 { interfaces } from '@theia/core/shared/inversify';
|
||||
import { bindCommonHostedBackend } from '../node/plugin-ext-headless-hosted-module';
|
||||
|
||||
export function bindElectronBackend(bind: interfaces.Bind): void {
|
||||
bindCommonHostedBackend(bind);
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2024 EclipseSource 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
|
||||
// *****************************************************************************
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
// some code copied and modified from https://github.com/microsoft/vscode/blob/da5fb7d5b865aa522abc7e82c10b746834b98639/src/vs/workbench/api/node/extHostExtensionService.ts
|
||||
|
||||
import { generateUuid } from '@theia/core/lib/common/uuid';
|
||||
import { injectable, inject, named } from '@theia/core/shared/inversify';
|
||||
import { getPluginId, DeployedPlugin, HostedPluginServer, PluginDeployer } from '@theia/plugin-ext/lib/common/plugin-protocol';
|
||||
import { setUpPluginApi } from '../../main/node/main-context';
|
||||
import { RPCProtocol, RPCProtocolImpl } from '@theia/plugin-ext/lib/common/rpc-protocol';
|
||||
import { ContributionProvider, Disposable, DisposableCollection, nls } from '@theia/core';
|
||||
import { environment } from '@theia/core/shared/@theia/application-package/lib/environment';
|
||||
import { IPCChannel } from '@theia/core/lib/node';
|
||||
import { BackendApplicationConfigProvider } from '@theia/core/lib/node/backend-application-config-provider';
|
||||
import { HostedPluginProcess } from '@theia/plugin-ext/lib/hosted/node/hosted-plugin-process';
|
||||
import { IShellTerminalServer } from '@theia/terminal/lib/common/shell-terminal-protocol';
|
||||
import { HeadlessPluginManagerExt, HEADLESSMAIN_RPC_CONTEXT } from '../../common/headless-plugin-rpc';
|
||||
import { AbstractHostedPluginSupport, PluginContributions } from '@theia/plugin-ext/lib/hosted/common/hosted-plugin';
|
||||
import { TheiaHeadlessPluginScanner } from './scanners/scanner-theia-headless';
|
||||
import { SupportedHeadlessActivationEvents } from '../../common/headless-plugin-protocol';
|
||||
import { PluginDeployerImpl } from '@theia/plugin-ext/lib/main/node/plugin-deployer-impl';
|
||||
|
||||
import URI from '@theia/core/lib/common/uri';
|
||||
import * as fs from 'fs';
|
||||
import * as asyncFs from 'fs/promises';
|
||||
|
||||
export type HeadlessPluginHost = string;
|
||||
|
||||
export function isHeadlessPlugin(plugin: DeployedPlugin): boolean {
|
||||
return !!plugin.metadata.model.entryPoint.headless;
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class HeadlessHostedPluginSupport extends AbstractHostedPluginSupport<HeadlessPluginManagerExt, HostedPluginServer> {
|
||||
|
||||
@inject(HostedPluginProcess)
|
||||
protected readonly pluginProcess: HostedPluginProcess;
|
||||
|
||||
@inject(IShellTerminalServer)
|
||||
protected readonly shellTerminalServer: IShellTerminalServer;
|
||||
|
||||
@inject(TheiaHeadlessPluginScanner)
|
||||
protected readonly scanner: TheiaHeadlessPluginScanner;
|
||||
|
||||
@inject(PluginDeployer)
|
||||
protected readonly pluginDeployer: PluginDeployerImpl;
|
||||
|
||||
@inject(ContributionProvider)
|
||||
@named(SupportedHeadlessActivationEvents)
|
||||
protected readonly supportedActivationEventsContributions: ContributionProvider<string[]>;
|
||||
|
||||
constructor() {
|
||||
super(generateUuid());
|
||||
}
|
||||
|
||||
shutDown(): void {
|
||||
this.pluginProcess.terminatePluginServer();
|
||||
}
|
||||
|
||||
protected createTheiaReadyPromise(): Promise<unknown> {
|
||||
return Promise.all([this.envServer.getVariables()]);
|
||||
}
|
||||
|
||||
// Only load headless plugins
|
||||
protected acceptPlugin(plugin: DeployedPlugin): boolean | DeployedPlugin {
|
||||
if (!isHeadlessPlugin(plugin)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (plugin.metadata.model.engine.type === this.scanner.apiType) {
|
||||
// Easy case: take it as it is
|
||||
return true;
|
||||
}
|
||||
|
||||
// Adapt it for headless
|
||||
return this.scanner.adaptForHeadless(plugin);
|
||||
}
|
||||
|
||||
protected handleContributions(_plugin: DeployedPlugin): Disposable {
|
||||
// We have no contribution points, yet, for headless plugins
|
||||
return Disposable.NULL;
|
||||
}
|
||||
|
||||
protected override async beforeSyncPlugins(toDisconnect: DisposableCollection): Promise<void> {
|
||||
await super.beforeSyncPlugins(toDisconnect);
|
||||
|
||||
// Plugin deployment is asynchronous, so wait until that's finished.
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
this.pluginDeployer.onDidDeploy(resolve);
|
||||
toDisconnect.push(Disposable.create(reject));
|
||||
});
|
||||
}
|
||||
|
||||
protected async obtainManager(host: string, hostContributions: PluginContributions[], toDisconnect: DisposableCollection): Promise<HeadlessPluginManagerExt | undefined> {
|
||||
let manager = this.managers.get(host);
|
||||
if (!manager) {
|
||||
const pluginId = getPluginId(hostContributions[0].plugin.metadata.model);
|
||||
const rpc = this.initRpc(host, pluginId);
|
||||
toDisconnect.push(rpc);
|
||||
|
||||
manager = rpc.getProxy(HEADLESSMAIN_RPC_CONTEXT.HOSTED_PLUGIN_MANAGER_EXT);
|
||||
this.managers.set(host, manager);
|
||||
toDisconnect.push(Disposable.create(() => this.managers.delete(host)));
|
||||
|
||||
const [extApi, globalState] = await Promise.all([
|
||||
this.server.getExtPluginAPI(),
|
||||
this.pluginServer.getAllStorageValues(undefined)
|
||||
]);
|
||||
if (toDisconnect.disposed) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const activationEvents = this.supportedActivationEventsContributions.getContributions().flatMap(array => array);
|
||||
const shell = await this.shellTerminalServer.getDefaultShell();
|
||||
const isElectron = environment.electron.is();
|
||||
|
||||
await manager.$init({
|
||||
activationEvents,
|
||||
globalState,
|
||||
env: {
|
||||
language: nls.locale || nls.defaultLocale,
|
||||
shell,
|
||||
appName: BackendApplicationConfigProvider.get().applicationName,
|
||||
appHost: isElectron ? 'desktop' : 'web' // TODO: 'web' could be the embedder's name, e.g. 'github.dev'
|
||||
},
|
||||
extApi
|
||||
});
|
||||
if (toDisconnect.disposed) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
return manager;
|
||||
}
|
||||
|
||||
protected initRpc(host: HeadlessPluginHost, pluginId: string): RPCProtocol {
|
||||
const rpc = this.createServerRpc(host);
|
||||
this.container.bind(RPCProtocol).toConstantValue(rpc);
|
||||
setUpPluginApi(rpc, this.container);
|
||||
this.mainPluginApiProviders.getContributions().forEach(p => p.initialize(rpc, this.container));
|
||||
return rpc;
|
||||
}
|
||||
|
||||
protected createServerRpc(pluginHostId: string): RPCProtocol {
|
||||
const channel = new IPCChannel(this.pluginProcess['childProcess']);
|
||||
|
||||
return new RPCProtocolImpl(channel);
|
||||
}
|
||||
|
||||
protected async getStoragePath(): Promise<string | undefined> {
|
||||
// Headless plugins are associated with the main Node process, so
|
||||
// their storage is the global storage.
|
||||
return this.getHostGlobalStoragePath();
|
||||
}
|
||||
|
||||
protected async getHostGlobalStoragePath(): Promise<string> {
|
||||
const configDirUri = await this.envServer.getConfigDirUri();
|
||||
const globalStorageFolderUri = new URI(configDirUri).resolve('globalStorage');
|
||||
const globalStorageFolderUrl = new URL(globalStorageFolderUri.toString());
|
||||
|
||||
let stat: fs.Stats | undefined;
|
||||
|
||||
try {
|
||||
stat = await asyncFs.stat(globalStorageFolderUrl);
|
||||
} catch (_) {
|
||||
// OK, no such directory
|
||||
}
|
||||
|
||||
if (stat && !stat.isDirectory()) {
|
||||
throw new Error(`Global storage folder is not a directory: ${globalStorageFolderUri}`);
|
||||
}
|
||||
|
||||
// Make sure that folder by the path exists
|
||||
if (!stat) {
|
||||
await asyncFs.mkdir(globalStorageFolderUrl, { recursive: true });
|
||||
}
|
||||
|
||||
const globalStorageFolderFsPath = await asyncFs.realpath(globalStorageFolderUrl);
|
||||
if (!globalStorageFolderFsPath) {
|
||||
throw new Error(`Could not resolve the FS path for URI: ${globalStorageFolderUri}`);
|
||||
}
|
||||
return globalStorageFolderFsPath;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2024 EclipseSource 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 { injectable } from '@theia/core/shared/inversify';
|
||||
import { HostedPluginServerImpl } from '@theia/plugin-ext/lib/hosted/node/plugin-service';
|
||||
|
||||
@injectable()
|
||||
export class HeadlessHostedPluginServerImpl extends HostedPluginServerImpl {
|
||||
protected override getServerName(): string {
|
||||
return 'headless-hosted-plugin';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2024 EclipseSource 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 * as path from 'path';
|
||||
import { bindContributionProvider } from '@theia/core/lib/common/contribution-provider';
|
||||
import { BackendApplicationContribution } from '@theia/core/lib/node';
|
||||
import { ContainerModule, interfaces } from '@theia/core/shared/inversify';
|
||||
import { ExtPluginApiProvider, HostedPluginServer, PluginHostEnvironmentVariable, PluginScanner } from '@theia/plugin-ext';
|
||||
import { HostedPluginSupport } from '@theia/plugin-ext/lib/hosted/node/hosted-plugin';
|
||||
import { HostedPluginProcess, HostedPluginProcessConfiguration } from '@theia/plugin-ext/lib/hosted/node/hosted-plugin-process';
|
||||
import { BackendPluginHostableFilter } from '@theia/plugin-ext/lib/hosted/node/plugin-service';
|
||||
import { MaybePromise } from '@theia/core';
|
||||
import { HeadlessPluginContainerModule } from '../../common/headless-plugin-container';
|
||||
import { HeadlessHostedPluginSupport, isHeadlessPlugin } from './headless-hosted-plugin';
|
||||
import { TheiaHeadlessPluginScanner } from './scanners/scanner-theia-headless';
|
||||
import { SupportedHeadlessActivationEvents } from '../../common/headless-plugin-protocol';
|
||||
import { HeadlessHostedPluginServerImpl } from './headless-plugin-service';
|
||||
|
||||
export function bindCommonHostedBackend(bind: interfaces.Bind): void {
|
||||
bind(HostedPluginProcess).toSelf().inSingletonScope();
|
||||
bind(HostedPluginSupport).toSelf().inSingletonScope();
|
||||
|
||||
bindContributionProvider(bind, Symbol.for(ExtPluginApiProvider));
|
||||
bindContributionProvider(bind, PluginHostEnvironmentVariable);
|
||||
bindContributionProvider(bind, SupportedHeadlessActivationEvents);
|
||||
|
||||
bind(HeadlessHostedPluginServerImpl).toSelf().inSingletonScope();
|
||||
bind(HostedPluginServer).toService(HeadlessHostedPluginServerImpl);
|
||||
bind(HeadlessHostedPluginSupport).toSelf().inSingletonScope();
|
||||
bind(BackendPluginHostableFilter).toConstantValue(isHeadlessPlugin);
|
||||
|
||||
bind(HostedPluginProcessConfiguration).toConstantValue({
|
||||
path: path.join(__dirname, 'plugin-host-headless'),
|
||||
});
|
||||
}
|
||||
|
||||
export function bindHeadlessHosted(bind: interfaces.Bind): void {
|
||||
bind(TheiaHeadlessPluginScanner).toSelf().inSingletonScope();
|
||||
bind(PluginScanner).toService(TheiaHeadlessPluginScanner);
|
||||
bind(SupportedHeadlessActivationEvents).toConstantValue(['*', 'onStartupFinished']);
|
||||
|
||||
bind(BackendApplicationContribution).toDynamicValue(({container}) => {
|
||||
let hostedPluginSupport: HeadlessHostedPluginSupport | undefined;
|
||||
|
||||
return {
|
||||
onStart(): MaybePromise<void> {
|
||||
// Create a child container to isolate the Headless Plugin hosting stack
|
||||
// from all connection-scoped frontend/backend plugin hosts and
|
||||
// also to avoid leaking it into the global container scope
|
||||
const headlessPluginsContainer = container.createChild();
|
||||
const modules = container.getAll<ContainerModule>(HeadlessPluginContainerModule);
|
||||
headlessPluginsContainer.load(...modules);
|
||||
|
||||
hostedPluginSupport = headlessPluginsContainer.get(HeadlessHostedPluginSupport);
|
||||
hostedPluginSupport.onStart(headlessPluginsContainer);
|
||||
},
|
||||
|
||||
onStop(): void {
|
||||
hostedPluginSupport?.shutDown();
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2024 EclipseSource 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 '@theia/core/shared/reflect-metadata';
|
||||
import { ContainerModule } from '@theia/core/shared/inversify';
|
||||
import { RPCProtocol, RPCProtocolImpl } from '@theia/plugin-ext/lib/common/rpc-protocol';
|
||||
import { AbstractPluginHostRPC, PluginContainerModuleLoader } from '@theia/plugin-ext/lib/hosted/node/plugin-host-rpc';
|
||||
import { AbstractPluginManagerExtImpl, MinimalTerminalServiceExt } from '@theia/plugin-ext/lib/plugin/plugin-manager';
|
||||
import { HeadlessPluginHostRPC } from './plugin-host-headless-rpc';
|
||||
import { HeadlessPluginManagerExtImpl } from '../../plugin/headless-plugin-manager';
|
||||
import { IPCChannel } from '@theia/core/lib/node';
|
||||
import { InternalPluginContainerModule } from '@theia/plugin-ext/lib/plugin/node/plugin-container-module';
|
||||
|
||||
import { EnvExtImpl } from '@theia/plugin-ext/lib/plugin/env';
|
||||
import { EnvNodeExtImpl } from '@theia/plugin-ext/lib/plugin/node/env-node-ext';
|
||||
import { LocalizationExt } from '@theia/plugin-ext';
|
||||
import { LocalizationExtImpl } from '@theia/plugin-ext/lib/plugin/localization-ext';
|
||||
import { InternalStorageExt } from '@theia/plugin-ext/lib/plugin/plugin-storage';
|
||||
import { InternalSecretsExt } from '@theia/plugin-ext/lib/plugin/secrets-ext';
|
||||
import { EnvironmentVariableCollectionImpl } from '@theia/plugin-ext/lib/plugin/terminal-ext';
|
||||
import { Disposable } from '@theia/core';
|
||||
|
||||
export default new ContainerModule(bind => {
|
||||
const channel = new IPCChannel();
|
||||
bind(RPCProtocol).toConstantValue(new RPCProtocolImpl(channel));
|
||||
|
||||
bind(PluginContainerModuleLoader).toDynamicValue(({ container }) =>
|
||||
(module: ContainerModule) => {
|
||||
container.load(module);
|
||||
const internalModule = module as InternalPluginContainerModule;
|
||||
const pluginApiCache = internalModule.initializeApi?.(container);
|
||||
return pluginApiCache;
|
||||
}).inSingletonScope();
|
||||
|
||||
bind(AbstractPluginHostRPC).toService(HeadlessPluginHostRPC);
|
||||
bind(HeadlessPluginHostRPC).toSelf().inSingletonScope();
|
||||
bind(AbstractPluginManagerExtImpl).toService(HeadlessPluginManagerExtImpl);
|
||||
bind(HeadlessPluginManagerExtImpl).toSelf().inSingletonScope();
|
||||
bind(EnvExtImpl).to(EnvNodeExtImpl).inSingletonScope();
|
||||
bind(LocalizationExt).to(LocalizationExtImpl).inSingletonScope();
|
||||
|
||||
const dummySecrets: InternalSecretsExt = {
|
||||
get: () => Promise.resolve(undefined),
|
||||
store: () => Promise.resolve(undefined),
|
||||
delete: () => Promise.resolve(undefined),
|
||||
$onDidChangePassword: () => Promise.resolve(),
|
||||
onDidChangePassword: () => Disposable.NULL,
|
||||
keys: () => Promise.resolve([]),
|
||||
};
|
||||
const dummyStorage: InternalStorageExt = {
|
||||
init: () => undefined,
|
||||
setPerPluginData: () => Promise.resolve(false),
|
||||
getPerPluginData: () => ({}),
|
||||
storageDataChangedEvent: () => Disposable.NULL,
|
||||
$updatePluginsWorkspaceData: () => undefined
|
||||
};
|
||||
const dummyTerminalService: MinimalTerminalServiceExt = {
|
||||
$initEnvironmentVariableCollections: () => undefined,
|
||||
$setShell: () => undefined,
|
||||
getEnvironmentVariableCollection: () => new EnvironmentVariableCollectionImpl(false),
|
||||
};
|
||||
bind(InternalSecretsExt).toConstantValue(dummySecrets);
|
||||
bind(InternalStorageExt).toConstantValue(dummyStorage);
|
||||
bind(MinimalTerminalServiceExt).toConstantValue(dummyTerminalService);
|
||||
});
|
||||
@@ -0,0 +1,80 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2024 EclipseSource 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 { dynamicRequire } from '@theia/core/lib/node/dynamic-require';
|
||||
import { ContainerModule, injectable, inject } from '@theia/core/shared/inversify';
|
||||
import { EnvExtImpl } from '@theia/plugin-ext/lib/plugin/env';
|
||||
import { LocalizationExt } from '@theia/plugin-ext';
|
||||
import { LocalizationExtImpl } from '@theia/plugin-ext/lib/plugin/localization-ext';
|
||||
import { HEADLESSMAIN_RPC_CONTEXT } from '../../common/headless-plugin-rpc';
|
||||
import { HeadlessPluginManagerExtImpl } from '../../plugin/headless-plugin-manager';
|
||||
import { AbstractPluginHostRPC, ExtInterfaces } from '@theia/plugin-ext/lib/hosted/node/plugin-host-rpc';
|
||||
import { PluginModel } from '@theia/plugin-ext/lib/common/plugin-protocol';
|
||||
import { ExtPluginApi, ExtPluginApiHeadlessInitializationFn } from '../../common/plugin-ext-headless-api-contribution';
|
||||
|
||||
type HeadlessExtInterfaces = Pick<ExtInterfaces, 'envExt'|'localizationExt'>;
|
||||
|
||||
/**
|
||||
* The RPC handler for headless plugins.
|
||||
*/
|
||||
@injectable()
|
||||
export class HeadlessPluginHostRPC extends AbstractPluginHostRPC<HeadlessPluginManagerExtImpl, null, HeadlessExtInterfaces> {
|
||||
@inject(EnvExtImpl)
|
||||
protected readonly envExt: EnvExtImpl;
|
||||
|
||||
@inject(LocalizationExt)
|
||||
protected readonly localizationExt: LocalizationExtImpl;
|
||||
|
||||
constructor() {
|
||||
super('HEADLESS_PLUGIN_HOST', undefined,
|
||||
{
|
||||
$pluginManager: HEADLESSMAIN_RPC_CONTEXT.HOSTED_PLUGIN_MANAGER_EXT,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
protected createExtInterfaces(): HeadlessExtInterfaces {
|
||||
return {
|
||||
envExt: this.envExt,
|
||||
localizationExt: this.localizationExt
|
||||
};
|
||||
}
|
||||
|
||||
protected createAPIFactory(_extInterfaces: HeadlessExtInterfaces): null {
|
||||
// As yet there is no default API namespace for backend plugins to access the Theia framework
|
||||
return null;
|
||||
}
|
||||
|
||||
protected override getBackendPluginPath(pluginModel: PluginModel): string | undefined {
|
||||
return pluginModel.entryPoint.headless;
|
||||
}
|
||||
|
||||
protected initExtApi(extApi: ExtPluginApi): void {
|
||||
interface PluginExports {
|
||||
containerModule?: ContainerModule;
|
||||
provideApi?: ExtPluginApiHeadlessInitializationFn;
|
||||
}
|
||||
if (extApi.headlessInitPath) {
|
||||
const { containerModule, provideApi } = dynamicRequire<PluginExports>(extApi.headlessInitPath);
|
||||
if (containerModule) {
|
||||
this.loadContainerModule(containerModule);
|
||||
}
|
||||
if (provideApi) {
|
||||
provideApi(this.rpc, this.pluginManager);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
// *****************************************************************************
|
||||
// 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 '@theia/core/shared/reflect-metadata';
|
||||
import { Container } from '@theia/core/shared/inversify';
|
||||
import { ConnectionClosedError, RPCProtocol } from '@theia/plugin-ext/lib/common/rpc-protocol';
|
||||
import { ProcessTerminatedMessage, ProcessTerminateMessage } from '@theia/plugin-ext/lib/hosted/node/hosted-plugin-protocol';
|
||||
import { HeadlessPluginHostRPC } from './plugin-host-headless-rpc';
|
||||
import pluginHostModule from './plugin-host-headless-module';
|
||||
|
||||
const banner = `HEADLESS_PLUGIN_HOST(${process.pid}):`;
|
||||
console.log(banner, 'Starting instance');
|
||||
|
||||
// override exit() function, to do not allow plugin kill this node
|
||||
process.exit = function (code?: number): void {
|
||||
const err = new Error('A plugin called process.exit() but it was blocked.');
|
||||
console.warn(banner, err.stack);
|
||||
} as (code?: number) => never;
|
||||
|
||||
// same for 'crash'(works only in electron)
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const proc = process as any;
|
||||
if (proc.crash) {
|
||||
proc.crash = function (): void {
|
||||
const err = new Error('A plugin called process.crash() but it was blocked.');
|
||||
console.warn(banner, err.stack);
|
||||
};
|
||||
}
|
||||
|
||||
process.on('uncaughtException', (err: Error) => {
|
||||
console.error(banner, err);
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const unhandledPromises: Promise<any>[] = [];
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
process.on('unhandledRejection', (reason: any, promise: Promise<any>) => {
|
||||
unhandledPromises.push(promise);
|
||||
setTimeout(() => {
|
||||
const index = unhandledPromises.indexOf(promise);
|
||||
if (index >= 0) {
|
||||
promise.catch(err => {
|
||||
unhandledPromises.splice(index, 1);
|
||||
if (terminating && (ConnectionClosedError.is(err) || ConnectionClosedError.is(reason))) {
|
||||
// during termination it is expected that pending rpc request are rejected
|
||||
return;
|
||||
}
|
||||
console.error(banner, `Promise rejection not handled in one second: ${err} , reason: ${reason}`);
|
||||
if (err && err.stack) {
|
||||
console.error(banner, `With stack trace: ${err.stack}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
process.on('rejectionHandled', (promise: Promise<any>) => {
|
||||
const index = unhandledPromises.indexOf(promise);
|
||||
if (index >= 0) {
|
||||
unhandledPromises.splice(index, 1);
|
||||
}
|
||||
});
|
||||
|
||||
let terminating = false;
|
||||
|
||||
const container = new Container();
|
||||
container.load(pluginHostModule);
|
||||
|
||||
const rpc: RPCProtocol = container.get(RPCProtocol);
|
||||
const pluginHostRPC = container.get(HeadlessPluginHostRPC);
|
||||
|
||||
process.on('message', async (message: string) => {
|
||||
if (terminating) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const msg = JSON.parse(message);
|
||||
if (ProcessTerminateMessage.is(msg)) {
|
||||
terminating = true;
|
||||
if (msg.stopTimeout) {
|
||||
await Promise.race([
|
||||
pluginHostRPC.terminate(),
|
||||
new Promise(resolve => setTimeout(resolve, msg.stopTimeout))
|
||||
]);
|
||||
} else {
|
||||
await pluginHostRPC.terminate();
|
||||
}
|
||||
rpc.dispose();
|
||||
if (process.send) {
|
||||
process.send(JSON.stringify({ type: ProcessTerminatedMessage.TYPE }));
|
||||
}
|
||||
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(banner, e);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,85 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2024 EclipseSource 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
|
||||
// *****************************************************************************
|
||||
|
||||
/* eslint-disable @theia/localization-check */
|
||||
|
||||
import { injectable } from '@theia/core/shared/inversify';
|
||||
import { DeployedPlugin, PluginPackage, PluginEntryPoint } from '@theia/plugin-ext';
|
||||
import { AbstractPluginScanner } from '@theia/plugin-ext/lib/hosted/node/scanners/scanner-theia';
|
||||
import { deepClone } from '@theia/core/lib/common/objects';
|
||||
|
||||
@injectable()
|
||||
export class TheiaHeadlessPluginScanner extends AbstractPluginScanner {
|
||||
|
||||
constructor() {
|
||||
super('theiaHeadlessPlugin');
|
||||
}
|
||||
|
||||
protected getEntryPoint(plugin: PluginPackage): PluginEntryPoint {
|
||||
if (plugin?.theiaPlugin?.headless) {
|
||||
return {
|
||||
headless: plugin.theiaPlugin.headless
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
headless: plugin.main
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Adapt the given `plugin`'s metadata for headless deployment, where it does not
|
||||
* already natively specify its headless deployment, such as is the case for plugins
|
||||
* declaring the `"vscode"` or `"theiaPlugin"` engine. This consists of cloning the
|
||||
* relevant properties of its deployment metadata and modifying them as required,
|
||||
* including but not limited to:
|
||||
*
|
||||
* - renaming the `lifecycle` start and stop functions as 'activate' and 'deactivate'
|
||||
* following the VS Code naming convention (in case the `plugin` is a Theia-style
|
||||
* plugin that uses 'start' and 'stop')
|
||||
* - deleting inapplicable information such as frontend and backend init script paths
|
||||
* - filtering/rewriting contributions and/or activation events
|
||||
*
|
||||
* The cloning is necessary to retain the original information for the non-headless
|
||||
* deployments that the plugin also supports.
|
||||
*/
|
||||
adaptForHeadless(plugin: DeployedPlugin): DeployedPlugin {
|
||||
return {
|
||||
type: plugin.type,
|
||||
metadata: this.adaptMetadataForHeadless(plugin),
|
||||
contributes: this.adaptContributesForHeadless(plugin)
|
||||
};
|
||||
}
|
||||
|
||||
protected adaptMetadataForHeadless(plugin: DeployedPlugin): DeployedPlugin['metadata'] {
|
||||
const result = deepClone(plugin.metadata);
|
||||
|
||||
const lifecycle = result.lifecycle;
|
||||
delete lifecycle.frontendInitPath;
|
||||
delete lifecycle.backendInitPath;
|
||||
|
||||
// Same as in VS Code
|
||||
lifecycle.startMethod = 'activate';
|
||||
lifecycle.stopMethod = 'deactivate';
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected adaptContributesForHeadless(plugin: DeployedPlugin): DeployedPlugin['contributes'] {
|
||||
// We don't yet support and contribution points in headless plugins
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
17
packages/plugin-ext-headless/src/index.ts
Normal file
17
packages/plugin-ext-headless/src/index.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2024 EclipseSource 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
|
||||
// *****************************************************************************
|
||||
|
||||
export * from './common';
|
||||
@@ -0,0 +1,35 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2024 EclipseSource 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 { injectable } from '@theia/core/shared/inversify';
|
||||
|
||||
import { PluginDeployerDirectoryHandlerContext, PluginDeployerEntryType, PluginPackage } from '@theia/plugin-ext';
|
||||
import { AbstractPluginDirectoryHandler } from '@theia/plugin-ext/lib/main/node/handlers/plugin-theia-directory-handler';
|
||||
|
||||
@injectable()
|
||||
export class PluginTheiaHeadlessDirectoryHandler extends AbstractPluginDirectoryHandler {
|
||||
|
||||
protected acceptManifest(plugin: PluginPackage): boolean {
|
||||
return plugin?.engines?.theiaPlugin === undefined && 'theiaHeadlessPlugin' in plugin.engines;
|
||||
}
|
||||
|
||||
async handle(context: PluginDeployerDirectoryHandlerContext): Promise<void> {
|
||||
await this.copyDirectory(context);
|
||||
const types: PluginDeployerEntryType[] = [PluginDeployerEntryType.HEADLESS];
|
||||
context.pluginEntry().accept(...types);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2024 EclipseSource 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 { injectable } from '@theia/core/shared/inversify';
|
||||
import {
|
||||
CancellationToken,
|
||||
ProgressClient, ProgressMessage, ProgressUpdate
|
||||
} from '@theia/core';
|
||||
|
||||
/**
|
||||
* A simple progress client for headless plugins that just writes debug messages to the console
|
||||
* because there is no one connected frontend to which it is appropriate to send the messages.
|
||||
*/
|
||||
@injectable()
|
||||
export class HeadlessProgressClient implements ProgressClient {
|
||||
async showProgress(_progressId: string, message: ProgressMessage, cancellationToken: CancellationToken): Promise<string | undefined> {
|
||||
if (cancellationToken.isCancellationRequested) {
|
||||
return ProgressMessage.Cancel;
|
||||
}
|
||||
console.debug(message.text);
|
||||
}
|
||||
|
||||
async reportProgress(_progressId: string, update: ProgressUpdate, message: ProgressMessage, cancellationToken: CancellationToken): Promise<void> {
|
||||
if (cancellationToken.isCancellationRequested) {
|
||||
return;
|
||||
}
|
||||
const progress = update.work && update.work.total ? `[${100 * Math.min(update.work.done, update.work.total) / update.work.total}%]` : '';
|
||||
const text = `${progress} ${update.message ?? 'completed ...'}`;
|
||||
console.debug(text);
|
||||
}
|
||||
}
|
||||
35
packages/plugin-ext-headless/src/main/node/main-context.ts
Normal file
35
packages/plugin-ext-headless/src/main/node/main-context.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2024 EclipseSource 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 { interfaces } from '@theia/core/shared/inversify';
|
||||
import { RPCProtocol } from '@theia/plugin-ext/lib/common/rpc-protocol';
|
||||
import { EnvMainImpl } from '@theia/plugin-ext/lib/main/common/env-main';
|
||||
import { BasicMessageRegistryMainImpl } from '@theia/plugin-ext/lib/main/common/basic-message-registry-main';
|
||||
import { BasicNotificationMainImpl } from '@theia/plugin-ext/lib/main/common/basic-notification-main';
|
||||
|
||||
import { HEADLESSMAIN_RPC_CONTEXT, HEADLESSPLUGIN_RPC_CONTEXT } from '../../common/headless-plugin-rpc';
|
||||
|
||||
// This sets up only the minimal plugin API required by the plugin manager to report
|
||||
// messages and notifications to the main side and to initialize plugins.
|
||||
export function setUpPluginApi(rpc: RPCProtocol, container: interfaces.Container): void {
|
||||
const envMain = new EnvMainImpl(rpc, container);
|
||||
rpc.set(HEADLESSPLUGIN_RPC_CONTEXT.ENV_MAIN, envMain);
|
||||
|
||||
const messageRegistryMain = new BasicMessageRegistryMainImpl(container);
|
||||
rpc.set(HEADLESSPLUGIN_RPC_CONTEXT.MESSAGE_REGISTRY_MAIN, messageRegistryMain);
|
||||
|
||||
const notificationMain = new BasicNotificationMainImpl(rpc, container, HEADLESSMAIN_RPC_CONTEXT.NOTIFICATION_EXT);
|
||||
rpc.set(HEADLESSPLUGIN_RPC_CONTEXT.NOTIFICATION_MAIN, notificationMain);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2024 EclipseSource 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 { interfaces } from '@theia/core/shared/inversify';
|
||||
import {
|
||||
MessageClient, MessageService,
|
||||
ProgressClient, ProgressService,
|
||||
bindContributionProvider
|
||||
} from '@theia/core';
|
||||
import { MainPluginApiProvider, PluginDeployerDirectoryHandler } from '@theia/plugin-ext';
|
||||
import { PluginTheiaHeadlessDirectoryHandler } from './handlers/plugin-theia-headless-directory-handler';
|
||||
import { HeadlessProgressClient } from './headless-progress-client';
|
||||
|
||||
export function bindHeadlessMain(bind: interfaces.Bind): void {
|
||||
bind(PluginDeployerDirectoryHandler).to(PluginTheiaHeadlessDirectoryHandler).inSingletonScope();
|
||||
}
|
||||
|
||||
export function bindBackendMain(bind: interfaces.Bind, unbind: interfaces.Unbind, isBound: interfaces.IsBound, rebind: interfaces.Rebind): void {
|
||||
bindContributionProvider(bind, MainPluginApiProvider);
|
||||
|
||||
//
|
||||
// Main API dependencies
|
||||
//
|
||||
|
||||
bind(MessageService).toSelf().inSingletonScope();
|
||||
bind(MessageClient).toSelf().inSingletonScope(); // Just logs to console
|
||||
bind(ProgressService).toSelf().inSingletonScope();
|
||||
bind(ProgressClient).to(HeadlessProgressClient).inSingletonScope();
|
||||
}
|
||||
25
packages/plugin-ext-headless/src/package.spec.ts
Normal file
25
packages/plugin-ext-headless/src/package.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2024 EclipseSource 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
|
||||
// *****************************************************************************
|
||||
|
||||
/*
|
||||
* This is a placeholder for tests that the extension package should implement
|
||||
* but as yet does not.
|
||||
* Please delete this file when a real test is implemented.
|
||||
*/
|
||||
|
||||
describe('plugin-ext-headless package', () => {
|
||||
it('placeholder to enable mocha', () => true);
|
||||
});
|
||||
@@ -0,0 +1,32 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2024 EclipseSource 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 { ContainerModule } from '@theia/core/shared/inversify';
|
||||
import { HeadlessPluginContainerModule } from './common/headless-plugin-container';
|
||||
import { bindElectronBackend } from './hosted/node-electron/plugin-ext-headless-hosted-electron-module';
|
||||
import { bindHeadlessMain, bindBackendMain } from './main/node/plugin-ext-headless-main-module';
|
||||
import { bindHeadlessHosted } from './hosted/node/plugin-ext-headless-hosted-module';
|
||||
|
||||
const backendElectronModule = new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
bindBackendMain(bind, unbind, isBound, rebind);
|
||||
bindElectronBackend(bind);
|
||||
});
|
||||
|
||||
export default new ContainerModule(bind => {
|
||||
bind(HeadlessPluginContainerModule).toConstantValue(backendElectronModule);
|
||||
bindHeadlessMain(bind);
|
||||
bindHeadlessHosted(bind);
|
||||
});
|
||||
@@ -0,0 +1,31 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2024 EclipseSource 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 { ContainerModule } from '@theia/core/shared/inversify';
|
||||
import { HeadlessPluginContainerModule } from './common/headless-plugin-container';
|
||||
import { bindHeadlessHosted, bindCommonHostedBackend } from './hosted/node/plugin-ext-headless-hosted-module';
|
||||
import { bindHeadlessMain, bindBackendMain } from './main/node/plugin-ext-headless-main-module';
|
||||
|
||||
const backendModule = new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
bindBackendMain(bind, unbind, isBound, rebind);
|
||||
bindCommonHostedBackend(bind);
|
||||
});
|
||||
|
||||
export default new ContainerModule(bind => {
|
||||
bind(HeadlessPluginContainerModule).toConstantValue(backendModule);
|
||||
bindHeadlessMain(bind);
|
||||
bindHeadlessHosted(bind);
|
||||
});
|
||||
@@ -0,0 +1,50 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2024 EclipseSource 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 { injectable } from '@theia/core/shared/inversify';
|
||||
import { AbstractPluginManagerExtImpl } from '@theia/plugin-ext/lib/plugin/plugin-manager';
|
||||
import { HeadlessPluginManagerExt, HeadlessPluginManagerInitializeParams } from '../common/headless-plugin-rpc';
|
||||
import { Plugin } from '@theia/plugin-ext';
|
||||
|
||||
@injectable()
|
||||
export class HeadlessPluginManagerExtImpl extends AbstractPluginManagerExtImpl<HeadlessPluginManagerInitializeParams> implements HeadlessPluginManagerExt {
|
||||
|
||||
private readonly supportedActivationEvents = new Set<string>();
|
||||
|
||||
async $init(params: HeadlessPluginManagerInitializeParams): Promise<void> {
|
||||
params.activationEvents?.forEach(event => this.supportedActivationEvents.add(event));
|
||||
|
||||
this.storage.init(params.globalState, {});
|
||||
|
||||
this.envExt.setLanguage(params.env.language);
|
||||
this.envExt.setApplicationName(params.env.appName);
|
||||
this.envExt.setAppHost(params.env.appHost);
|
||||
|
||||
if (params.extApi) {
|
||||
this.host.initExtApi(params.extApi);
|
||||
}
|
||||
}
|
||||
|
||||
protected override getActivationEvents(plugin: Plugin): string[] | undefined {
|
||||
const result = plugin.rawModel?.headless?.activationEvents;
|
||||
return Array.isArray(result) ? result : undefined;
|
||||
}
|
||||
|
||||
protected isSupportedActivationEvent(activationEvent: string): boolean {
|
||||
return this.supportedActivationEvents.has(activationEvent.split(':')[0]);
|
||||
}
|
||||
|
||||
}
|
||||
27
packages/plugin-ext-headless/tsconfig.json
Normal file
27
packages/plugin-ext-headless/tsconfig.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"extends": "../../configs/base.tsconfig",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"rootDir": "src",
|
||||
"outDir": "lib",
|
||||
"lib": [
|
||||
"es6",
|
||||
"dom",
|
||||
"webworker"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../core"
|
||||
},
|
||||
{
|
||||
"path": "../plugin-ext"
|
||||
},
|
||||
{
|
||||
"path": "../terminal"
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user