@theia/plugin-ext-headless 1.46.0-next.72
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/README.md +32 -0
- package/lib/common/headless-plugin-container.d.ts +8 -0
- package/lib/common/headless-plugin-container.d.ts.map +1 -0
- package/lib/common/headless-plugin-container.js +26 -0
- package/lib/common/headless-plugin-container.js.map +1 -0
- package/lib/common/headless-plugin-protocol.d.ts +21 -0
- package/lib/common/headless-plugin-protocol.d.ts.map +1 -0
- package/lib/common/headless-plugin-protocol.js +35 -0
- package/lib/common/headless-plugin-protocol.js.map +1 -0
- package/lib/common/headless-plugin-rpc.d.ts +23 -0
- package/lib/common/headless-plugin-rpc.d.ts.map +1 -0
- package/lib/common/headless-plugin-rpc.js +31 -0
- package/lib/common/headless-plugin-rpc.js.map +1 -0
- package/lib/common/index.d.ts +5 -0
- package/lib/common/index.d.ts.map +1 -0
- package/lib/common/index.js +35 -0
- package/lib/common/index.js.map +1 -0
- package/lib/common/plugin-ext-headless-api-contribution.d.ts +38 -0
- package/lib/common/plugin-ext-headless-api-contribution.d.ts.map +1 -0
- package/lib/common/plugin-ext-headless-api-contribution.js +29 -0
- package/lib/common/plugin-ext-headless-api-contribution.js.map +1 -0
- package/lib/hosted/node/headless-hosted-plugin.d.ts +30 -0
- package/lib/hosted/node/headless-hosted-plugin.d.ts.map +1 -0
- package/lib/hosted/node/headless-hosted-plugin.js +192 -0
- package/lib/hosted/node/headless-hosted-plugin.js.map +1 -0
- package/lib/hosted/node/plugin-ext-headless-hosted-module.d.ts +4 -0
- package/lib/hosted/node/plugin-ext-headless-hosted-module.d.ts.map +1 -0
- package/lib/hosted/node/plugin-ext-headless-hosted-module.js +69 -0
- package/lib/hosted/node/plugin-ext-headless-hosted-module.js.map +1 -0
- package/lib/hosted/node/plugin-host-headless-module.d.ts +5 -0
- package/lib/hosted/node/plugin-host-headless-module.d.ts.map +1 -0
- package/lib/hosted/node/plugin-host-headless-module.js +73 -0
- package/lib/hosted/node/plugin-host-headless-module.js.map +1 -0
- package/lib/hosted/node/plugin-host-headless-rpc.d.ts +21 -0
- package/lib/hosted/node/plugin-host-headless-rpc.d.ts.map +1 -0
- package/lib/hosted/node/plugin-host-headless-rpc.js +82 -0
- package/lib/hosted/node/plugin-host-headless-rpc.js.map +1 -0
- package/lib/hosted/node/plugin-host-headless.d.ts +2 -0
- package/lib/hosted/node/plugin-host-headless.d.ts.map +1 -0
- package/lib/hosted/node/plugin-host-headless.js +104 -0
- package/lib/hosted/node/plugin-host-headless.js.map +1 -0
- package/lib/hosted/node/scanners/scanner-theia-headless.d.ts +26 -0
- package/lib/hosted/node/scanners/scanner-theia-headless.d.ts.map +1 -0
- package/lib/hosted/node/scanners/scanner-theia-headless.js +91 -0
- package/lib/hosted/node/scanners/scanner-theia-headless.js.map +1 -0
- package/lib/hosted/node-electron/plugin-ext-headless-hosted-electron-module.d.ts +3 -0
- package/lib/hosted/node-electron/plugin-ext-headless-hosted-electron-module.d.ts.map +1 -0
- package/lib/hosted/node-electron/plugin-ext-headless-hosted-electron-module.js +24 -0
- package/lib/hosted/node-electron/plugin-ext-headless-hosted-electron-module.js.map +1 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +29 -0
- package/lib/index.js.map +1 -0
- package/lib/main/node/handlers/plugin-theia-headless-directory-handler.d.ts +7 -0
- package/lib/main/node/handlers/plugin-theia-headless-directory-handler.d.ts.map +1 -0
- package/lib/main/node/handlers/plugin-theia-headless-directory-handler.js +43 -0
- package/lib/main/node/handlers/plugin-theia-headless-directory-handler.js.map +1 -0
- package/lib/main/node/headless-progress-client.d.ts +10 -0
- package/lib/main/node/headless-progress-client.d.ts.map +1 -0
- package/lib/main/node/headless-progress-client.js +52 -0
- package/lib/main/node/headless-progress-client.js.map +1 -0
- package/lib/main/node/main-context.d.ts +4 -0
- package/lib/main/node/main-context.d.ts.map +1 -0
- package/lib/main/node/main-context.js +19 -0
- package/lib/main/node/main-context.js.map +1 -0
- package/lib/main/node/plugin-ext-headless-main-module.d.ts +4 -0
- package/lib/main/node/plugin-ext-headless-main-module.d.ts.map +1 -0
- package/lib/main/node/plugin-ext-headless-main-module.js +38 -0
- package/lib/main/node/plugin-ext-headless-main-module.js.map +1 -0
- package/lib/package.spec.d.ts +1 -0
- package/lib/package.spec.d.ts.map +1 -0
- package/lib/package.spec.js +24 -0
- package/lib/package.spec.js.map +1 -0
- package/lib/plugin/headless-plugin-manager.d.ts +10 -0
- package/lib/plugin/headless-plugin-manager.d.ts.map +1 -0
- package/lib/plugin/headless-plugin-manager.js +56 -0
- package/lib/plugin/headless-plugin-manager.js.map +1 -0
- package/lib/plugin-ext-headless-electron-module.d.ts +4 -0
- package/lib/plugin-ext-headless-electron-module.d.ts.map +1 -0
- package/lib/plugin-ext-headless-electron-module.js +32 -0
- package/lib/plugin-ext-headless-electron-module.js.map +1 -0
- package/lib/plugin-ext-headless-module.d.ts +4 -0
- package/lib/plugin-ext-headless-module.d.ts.map +1 -0
- package/lib/plugin-ext-headless-module.js +31 -0
- package/lib/plugin-ext-headless-module.js.map +1 -0
- package/package.json +56 -0
- package/src/common/headless-plugin-container.ts +23 -0
- package/src/common/headless-plugin-protocol.ts +38 -0
- package/src/common/headless-plugin-rpc.ts +46 -0
- package/src/common/index.ts +23 -0
- package/src/common/plugin-ext-headless-api-contribution.ts +60 -0
- package/src/hosted/node/headless-hosted-plugin.ts +199 -0
- package/src/hosted/node/plugin-ext-headless-hosted-module.ts +75 -0
- package/src/hosted/node/plugin-host-headless-module.ts +76 -0
- package/src/hosted/node/plugin-host-headless-rpc.ts +80 -0
- package/src/hosted/node/plugin-host-headless.ts +111 -0
- package/src/hosted/node/scanners/scanner-theia-headless.ts +85 -0
- package/src/hosted/node-electron/plugin-ext-headless-hosted-electron-module.ts +22 -0
- package/src/index.ts +17 -0
- package/src/main/node/handlers/plugin-theia-headless-directory-handler.ts +35 -0
- package/src/main/node/headless-progress-client.ts +44 -0
- package/src/main/node/main-context.ts +35 -0
- package/src/main/node/plugin-ext-headless-main-module.ts +42 -0
- package/src/package.spec.ts +25 -0
- package/src/plugin/headless-plugin-manager.ts +50 -0
- package/src/plugin-ext-headless-electron-module.ts +32 -0
- package/src/plugin-ext-headless-module.ts +31 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2024 EclipseSource and others.
|
|
3
|
+
//
|
|
4
|
+
// This program and the accompanying materials are made available under the
|
|
5
|
+
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
//
|
|
8
|
+
// This Source Code may also be made available under the following Secondary
|
|
9
|
+
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
// with the GNU Classpath Exception which is available at
|
|
12
|
+
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
//
|
|
14
|
+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
+
// *****************************************************************************
|
|
16
|
+
|
|
17
|
+
import { PluginManager } from '@theia/plugin-ext';
|
|
18
|
+
import { RPCProtocol } from '@theia/plugin-ext/lib/common/rpc-protocol';
|
|
19
|
+
|
|
20
|
+
export * from '@theia/plugin-ext';
|
|
21
|
+
|
|
22
|
+
declare module '@theia/plugin-ext' {
|
|
23
|
+
/**
|
|
24
|
+
* Plugin API extension description.
|
|
25
|
+
* This interface describes scripts for all three plugin runtimes: frontend (WebWorker), backend (NodeJs), and headless (NodeJs).
|
|
26
|
+
*/
|
|
27
|
+
interface ExtPluginApi extends ExtPluginHeadlessApi {
|
|
28
|
+
// Note that the frontendInitPath and backendInitPath properties are included by
|
|
29
|
+
// Typescript interface merge from the @theia/plugin-ext::ExtPluginApi interface.
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Provider for headless extension API description.
|
|
35
|
+
*/
|
|
36
|
+
export interface ExtPluginHeadlessApiProvider {
|
|
37
|
+
/**
|
|
38
|
+
* Provide API description.
|
|
39
|
+
*/
|
|
40
|
+
provideApi(): ExtPluginHeadlessApi;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Headless Plugin API extension description.
|
|
45
|
+
* This interface describes a script for the headless (NodeJs) runtime outside of the scope of frontend connections.
|
|
46
|
+
*/
|
|
47
|
+
export interface ExtPluginHeadlessApi {
|
|
48
|
+
/**
|
|
49
|
+
* Path to the script which should be loaded to provide api, module should export `provideApi` function with
|
|
50
|
+
* [ExtPluginApiBackendInitializationFn](#ExtPluginApiBackendInitializationFn) signature
|
|
51
|
+
*/
|
|
52
|
+
headlessInitPath?: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Signature of the extension API initialization function for APIs contributed to headless plugins.
|
|
57
|
+
*/
|
|
58
|
+
export interface ExtPluginApiHeadlessInitializationFn {
|
|
59
|
+
(rpc: RPCProtocol, pluginManager: PluginManager): void;
|
|
60
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2024 EclipseSource and others.
|
|
3
|
+
//
|
|
4
|
+
// This program and the accompanying materials are made available under the
|
|
5
|
+
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
//
|
|
8
|
+
// This Source Code may also be made available under the following Secondary
|
|
9
|
+
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
// with the GNU Classpath Exception which is available at
|
|
12
|
+
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
//
|
|
14
|
+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
+
// *****************************************************************************
|
|
16
|
+
/*---------------------------------------------------------------------------------------------
|
|
17
|
+
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
18
|
+
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
19
|
+
*--------------------------------------------------------------------------------------------*/
|
|
20
|
+
// some code copied and modified from https://github.com/microsoft/vscode/blob/da5fb7d5b865aa522abc7e82c10b746834b98639/src/vs/workbench/api/node/extHostExtensionService.ts
|
|
21
|
+
|
|
22
|
+
import { generateUuid } from '@theia/core/lib/common/uuid';
|
|
23
|
+
import { injectable, inject, named } from '@theia/core/shared/inversify';
|
|
24
|
+
import { getPluginId, DeployedPlugin, HostedPluginServer, PluginDeployer } from '@theia/plugin-ext/lib/common/plugin-protocol';
|
|
25
|
+
import { setUpPluginApi } from '../../main/node/main-context';
|
|
26
|
+
import { RPCProtocol, RPCProtocolImpl } from '@theia/plugin-ext/lib/common/rpc-protocol';
|
|
27
|
+
import { ContributionProvider, Disposable, DisposableCollection, nls } from '@theia/core';
|
|
28
|
+
import { environment } from '@theia/core/shared/@theia/application-package/lib/environment';
|
|
29
|
+
import { IPCChannel } from '@theia/core/lib/node';
|
|
30
|
+
import { BackendApplicationConfigProvider } from '@theia/core/lib/node/backend-application-config-provider';
|
|
31
|
+
import { HostedPluginProcess } from '@theia/plugin-ext/lib/hosted/node/hosted-plugin-process';
|
|
32
|
+
import { IShellTerminalServer } from '@theia/terminal/lib/common/shell-terminal-protocol';
|
|
33
|
+
import { HeadlessPluginManagerExt, HEADLESSMAIN_RPC_CONTEXT } from '../../common/headless-plugin-rpc';
|
|
34
|
+
import { AbstractHostedPluginSupport, PluginContributions } from '@theia/plugin-ext/lib/hosted/common/hosted-plugin';
|
|
35
|
+
import { TheiaHeadlessPluginScanner } from './scanners/scanner-theia-headless';
|
|
36
|
+
import { SupportedHeadlessActivationEvents } from '../../common/headless-plugin-protocol';
|
|
37
|
+
import { PluginDeployerImpl } from '@theia/plugin-ext/lib/main/node/plugin-deployer-impl';
|
|
38
|
+
|
|
39
|
+
import URI from '@theia/core/lib/common/uri';
|
|
40
|
+
import * as fs from 'fs';
|
|
41
|
+
import * as asyncFs from 'fs/promises';
|
|
42
|
+
|
|
43
|
+
export type HeadlessPluginHost = string;
|
|
44
|
+
|
|
45
|
+
export function isHeadlessPlugin(plugin: DeployedPlugin): boolean {
|
|
46
|
+
return !!plugin.metadata.model.entryPoint.headless;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
@injectable()
|
|
50
|
+
export class HeadlessHostedPluginSupport extends AbstractHostedPluginSupport<HeadlessPluginManagerExt, HostedPluginServer> {
|
|
51
|
+
|
|
52
|
+
@inject(HostedPluginProcess)
|
|
53
|
+
protected readonly pluginProcess: HostedPluginProcess;
|
|
54
|
+
|
|
55
|
+
@inject(IShellTerminalServer)
|
|
56
|
+
protected readonly shellTerminalServer: IShellTerminalServer;
|
|
57
|
+
|
|
58
|
+
@inject(TheiaHeadlessPluginScanner)
|
|
59
|
+
protected readonly scanner: TheiaHeadlessPluginScanner;
|
|
60
|
+
|
|
61
|
+
@inject(PluginDeployer)
|
|
62
|
+
protected readonly pluginDeployer: PluginDeployerImpl;
|
|
63
|
+
|
|
64
|
+
@inject(ContributionProvider)
|
|
65
|
+
@named(SupportedHeadlessActivationEvents)
|
|
66
|
+
protected readonly supportedActivationEventsContributions: ContributionProvider<string[]>;
|
|
67
|
+
|
|
68
|
+
constructor() {
|
|
69
|
+
super(generateUuid());
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
shutDown(): void {
|
|
73
|
+
this.pluginProcess.terminatePluginServer();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
protected createTheiaReadyPromise(): Promise<unknown> {
|
|
77
|
+
return Promise.all([this.envServer.getVariables()]);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Only load headless plugins
|
|
81
|
+
protected acceptPlugin(plugin: DeployedPlugin): boolean | DeployedPlugin {
|
|
82
|
+
if (!isHeadlessPlugin(plugin)) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (plugin.metadata.model.engine.type === this.scanner.apiType) {
|
|
87
|
+
// Easy case: take it as it is
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Adapt it for headless
|
|
92
|
+
return this.scanner.adaptForHeadless(plugin);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
protected handleContributions(_plugin: DeployedPlugin): Disposable {
|
|
96
|
+
// We have no contribution points, yet, for headless plugins
|
|
97
|
+
return Disposable.NULL;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
protected override async beforeSyncPlugins(toDisconnect: DisposableCollection): Promise<void> {
|
|
101
|
+
await super.beforeSyncPlugins(toDisconnect);
|
|
102
|
+
|
|
103
|
+
// Plugin deployment is asynchronous, so wait until that's finished.
|
|
104
|
+
return new Promise<void>((resolve, reject) => {
|
|
105
|
+
this.pluginDeployer.onDidDeploy(resolve);
|
|
106
|
+
toDisconnect.push(Disposable.create(reject));
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
protected async obtainManager(host: string, hostContributions: PluginContributions[], toDisconnect: DisposableCollection): Promise<HeadlessPluginManagerExt | undefined> {
|
|
111
|
+
let manager = this.managers.get(host);
|
|
112
|
+
if (!manager) {
|
|
113
|
+
const pluginId = getPluginId(hostContributions[0].plugin.metadata.model);
|
|
114
|
+
const rpc = this.initRpc(host, pluginId);
|
|
115
|
+
toDisconnect.push(rpc);
|
|
116
|
+
|
|
117
|
+
manager = rpc.getProxy(HEADLESSMAIN_RPC_CONTEXT.HOSTED_PLUGIN_MANAGER_EXT);
|
|
118
|
+
this.managers.set(host, manager);
|
|
119
|
+
toDisconnect.push(Disposable.create(() => this.managers.delete(host)));
|
|
120
|
+
|
|
121
|
+
const [extApi, globalState] = await Promise.all([
|
|
122
|
+
this.server.getExtPluginAPI(),
|
|
123
|
+
this.pluginServer.getAllStorageValues(undefined)
|
|
124
|
+
]);
|
|
125
|
+
if (toDisconnect.disposed) {
|
|
126
|
+
return undefined;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const activationEvents = this.supportedActivationEventsContributions.getContributions().flatMap(array => array);
|
|
130
|
+
const shell = await this.shellTerminalServer.getDefaultShell();
|
|
131
|
+
const isElectron = environment.electron.is();
|
|
132
|
+
|
|
133
|
+
await manager.$init({
|
|
134
|
+
activationEvents,
|
|
135
|
+
globalState,
|
|
136
|
+
env: {
|
|
137
|
+
language: nls.locale || nls.defaultLocale,
|
|
138
|
+
shell,
|
|
139
|
+
appName: BackendApplicationConfigProvider.get().applicationName,
|
|
140
|
+
appHost: isElectron ? 'desktop' : 'web' // TODO: 'web' could be the embedder's name, e.g. 'github.dev'
|
|
141
|
+
},
|
|
142
|
+
extApi
|
|
143
|
+
});
|
|
144
|
+
if (toDisconnect.disposed) {
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return manager;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
protected initRpc(host: HeadlessPluginHost, pluginId: string): RPCProtocol {
|
|
152
|
+
const rpc = this.createServerRpc(host);
|
|
153
|
+
this.container.bind(RPCProtocol).toConstantValue(rpc);
|
|
154
|
+
setUpPluginApi(rpc, this.container);
|
|
155
|
+
this.mainPluginApiProviders.getContributions().forEach(p => p.initialize(rpc, this.container));
|
|
156
|
+
return rpc;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
protected createServerRpc(pluginHostId: string): RPCProtocol {
|
|
160
|
+
const channel = new IPCChannel(this.pluginProcess['childProcess']);
|
|
161
|
+
|
|
162
|
+
return new RPCProtocolImpl(channel);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
protected async getStoragePath(): Promise<string | undefined> {
|
|
166
|
+
// Headless plugins are associated with the main Node process, so
|
|
167
|
+
// their storage is the global storage.
|
|
168
|
+
return this.getHostGlobalStoragePath();
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
protected async getHostGlobalStoragePath(): Promise<string> {
|
|
172
|
+
const configDirUri = await this.envServer.getConfigDirUri();
|
|
173
|
+
const globalStorageFolderUri = new URI(configDirUri).resolve('globalStorage');
|
|
174
|
+
const globalStorageFolderUrl = new URL(globalStorageFolderUri.toString());
|
|
175
|
+
|
|
176
|
+
let stat: fs.Stats | undefined;
|
|
177
|
+
|
|
178
|
+
try {
|
|
179
|
+
stat = await asyncFs.stat(globalStorageFolderUrl);
|
|
180
|
+
} catch (_) {
|
|
181
|
+
// OK, no such directory
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (stat && !stat.isDirectory()) {
|
|
185
|
+
throw new Error(`Global storage folder is not a directory: ${globalStorageFolderUri}`);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Make sure that folder by the path exists
|
|
189
|
+
if (!stat) {
|
|
190
|
+
await asyncFs.mkdir(globalStorageFolderUrl, { recursive: true });
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const globalStorageFolderFsPath = await asyncFs.realpath(globalStorageFolderUrl);
|
|
194
|
+
if (!globalStorageFolderFsPath) {
|
|
195
|
+
throw new Error(`Could not resolve the FS path for URI: ${globalStorageFolderUri}`);
|
|
196
|
+
}
|
|
197
|
+
return globalStorageFolderFsPath;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2024 EclipseSource and others.
|
|
3
|
+
//
|
|
4
|
+
// This program and the accompanying materials are made available under the
|
|
5
|
+
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
//
|
|
8
|
+
// This Source Code may also be made available under the following Secondary
|
|
9
|
+
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
// with the GNU Classpath Exception which is available at
|
|
12
|
+
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
//
|
|
14
|
+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
+
// *****************************************************************************
|
|
16
|
+
|
|
17
|
+
import * as path from 'path';
|
|
18
|
+
import { bindContributionProvider } from '@theia/core/lib/common/contribution-provider';
|
|
19
|
+
import { BackendApplicationContribution } from '@theia/core/lib/node';
|
|
20
|
+
import { ContainerModule, interfaces } from '@theia/core/shared/inversify';
|
|
21
|
+
import { ExtPluginApiProvider, HostedPluginServer, PluginHostEnvironmentVariable, PluginScanner } from '@theia/plugin-ext';
|
|
22
|
+
import { HostedPluginSupport } from '@theia/plugin-ext/lib/hosted/node/hosted-plugin';
|
|
23
|
+
import { HostedPluginProcess, HostedPluginProcessConfiguration } from '@theia/plugin-ext/lib/hosted/node/hosted-plugin-process';
|
|
24
|
+
import { BackendPluginHostableFilter, HostedPluginServerImpl } from '@theia/plugin-ext/lib/hosted/node/plugin-service';
|
|
25
|
+
import { MaybePromise } from '@theia/core';
|
|
26
|
+
import { HeadlessPluginContainerModule } from '../../common/headless-plugin-container';
|
|
27
|
+
import { HeadlessHostedPluginSupport, isHeadlessPlugin } from './headless-hosted-plugin';
|
|
28
|
+
import { TheiaHeadlessPluginScanner } from './scanners/scanner-theia-headless';
|
|
29
|
+
import { SupportedHeadlessActivationEvents } from '../../common/headless-plugin-protocol';
|
|
30
|
+
|
|
31
|
+
export function bindCommonHostedBackend(bind: interfaces.Bind): void {
|
|
32
|
+
bind(HostedPluginProcess).toSelf().inSingletonScope();
|
|
33
|
+
bind(HostedPluginSupport).toSelf().inSingletonScope();
|
|
34
|
+
|
|
35
|
+
bindContributionProvider(bind, Symbol.for(ExtPluginApiProvider));
|
|
36
|
+
bindContributionProvider(bind, PluginHostEnvironmentVariable);
|
|
37
|
+
bindContributionProvider(bind, SupportedHeadlessActivationEvents);
|
|
38
|
+
|
|
39
|
+
bind(HostedPluginServerImpl).toSelf().inSingletonScope();
|
|
40
|
+
bind(HostedPluginServer).toService(HostedPluginServerImpl);
|
|
41
|
+
bind(HeadlessHostedPluginSupport).toSelf().inSingletonScope();
|
|
42
|
+
bind(BackendPluginHostableFilter).toConstantValue(isHeadlessPlugin);
|
|
43
|
+
|
|
44
|
+
bind(HostedPluginProcessConfiguration).toConstantValue({
|
|
45
|
+
path: path.join(__dirname, 'plugin-host-headless'),
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function bindHeadlessHosted(bind: interfaces.Bind): void {
|
|
50
|
+
bind(TheiaHeadlessPluginScanner).toSelf().inSingletonScope();
|
|
51
|
+
bind(PluginScanner).toService(TheiaHeadlessPluginScanner);
|
|
52
|
+
bind(SupportedHeadlessActivationEvents).toConstantValue(['*', 'onStartupFinished']);
|
|
53
|
+
|
|
54
|
+
bind(BackendApplicationContribution).toDynamicValue(({container}) => {
|
|
55
|
+
let hostedPluginSupport: HeadlessHostedPluginSupport | undefined;
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
onStart(): MaybePromise<void> {
|
|
59
|
+
// Create a child container to isolate the Headless Plugin hosting stack
|
|
60
|
+
// from all connection-scoped frontend/backend plugin hosts and
|
|
61
|
+
// also to avoid leaking it into the global container scope
|
|
62
|
+
const headlessPluginsContainer = container.createChild();
|
|
63
|
+
const modules = container.getAll<ContainerModule>(HeadlessPluginContainerModule);
|
|
64
|
+
headlessPluginsContainer.load(...modules);
|
|
65
|
+
|
|
66
|
+
hostedPluginSupport = headlessPluginsContainer.get(HeadlessHostedPluginSupport);
|
|
67
|
+
hostedPluginSupport.onStart(headlessPluginsContainer);
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
onStop(): void {
|
|
71
|
+
hostedPluginSupport?.shutDown();
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
});
|
|
75
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2024 EclipseSource and others.
|
|
3
|
+
//
|
|
4
|
+
// This program and the accompanying materials are made available under the
|
|
5
|
+
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
//
|
|
8
|
+
// This Source Code may also be made available under the following Secondary
|
|
9
|
+
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
// with the GNU Classpath Exception which is available at
|
|
12
|
+
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
//
|
|
14
|
+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
+
// *****************************************************************************
|
|
16
|
+
import '@theia/core/shared/reflect-metadata';
|
|
17
|
+
import { ContainerModule } from '@theia/core/shared/inversify';
|
|
18
|
+
import { RPCProtocol, RPCProtocolImpl } from '@theia/plugin-ext/lib/common/rpc-protocol';
|
|
19
|
+
import { AbstractPluginHostRPC, PluginContainerModuleLoader } from '@theia/plugin-ext/lib/hosted/node/plugin-host-rpc';
|
|
20
|
+
import { AbstractPluginManagerExtImpl, MinimalTerminalServiceExt } from '@theia/plugin-ext/lib/plugin/plugin-manager';
|
|
21
|
+
import { HeadlessPluginHostRPC } from './plugin-host-headless-rpc';
|
|
22
|
+
import { HeadlessPluginManagerExtImpl } from '../../plugin/headless-plugin-manager';
|
|
23
|
+
import { IPCChannel } from '@theia/core/lib/node';
|
|
24
|
+
import { InternalPluginContainerModule } from '@theia/plugin-ext/lib/plugin/node/plugin-container-module';
|
|
25
|
+
|
|
26
|
+
import { EnvExtImpl } from '@theia/plugin-ext/lib/plugin/env';
|
|
27
|
+
import { EnvNodeExtImpl } from '@theia/plugin-ext/lib/plugin/node/env-node-ext';
|
|
28
|
+
import { LocalizationExt } from '@theia/plugin-ext';
|
|
29
|
+
import { LocalizationExtImpl } from '@theia/plugin-ext/lib/plugin/localization-ext';
|
|
30
|
+
import { InternalStorageExt } from '@theia/plugin-ext/lib/plugin/plugin-storage';
|
|
31
|
+
import { InternalSecretsExt } from '@theia/plugin-ext/lib/plugin/secrets-ext';
|
|
32
|
+
import { EnvironmentVariableCollectionImpl } from '@theia/plugin-ext/lib/plugin/terminal-ext';
|
|
33
|
+
import { Disposable } from '@theia/core';
|
|
34
|
+
|
|
35
|
+
export default new ContainerModule(bind => {
|
|
36
|
+
const channel = new IPCChannel();
|
|
37
|
+
bind(RPCProtocol).toConstantValue(new RPCProtocolImpl(channel));
|
|
38
|
+
|
|
39
|
+
bind(PluginContainerModuleLoader).toDynamicValue(({ container }) =>
|
|
40
|
+
(module: ContainerModule) => {
|
|
41
|
+
container.load(module);
|
|
42
|
+
const internalModule = module as InternalPluginContainerModule;
|
|
43
|
+
const pluginApiCache = internalModule.initializeApi?.(container);
|
|
44
|
+
return pluginApiCache;
|
|
45
|
+
}).inSingletonScope();
|
|
46
|
+
|
|
47
|
+
bind(AbstractPluginHostRPC).toService(HeadlessPluginHostRPC);
|
|
48
|
+
bind(HeadlessPluginHostRPC).toSelf().inSingletonScope();
|
|
49
|
+
bind(AbstractPluginManagerExtImpl).toService(HeadlessPluginManagerExtImpl);
|
|
50
|
+
bind(HeadlessPluginManagerExtImpl).toSelf().inSingletonScope();
|
|
51
|
+
bind(EnvExtImpl).to(EnvNodeExtImpl).inSingletonScope();
|
|
52
|
+
bind(LocalizationExt).to(LocalizationExtImpl).inSingletonScope();
|
|
53
|
+
|
|
54
|
+
const dummySecrets: InternalSecretsExt = {
|
|
55
|
+
get: () => Promise.resolve(undefined),
|
|
56
|
+
store: () => Promise.resolve(undefined),
|
|
57
|
+
delete: () => Promise.resolve(undefined),
|
|
58
|
+
$onDidChangePassword: () => Promise.resolve(),
|
|
59
|
+
onDidChangePassword: () => Disposable.NULL,
|
|
60
|
+
};
|
|
61
|
+
const dummyStorage: InternalStorageExt = {
|
|
62
|
+
init: () => undefined,
|
|
63
|
+
setPerPluginData: () => Promise.resolve(false),
|
|
64
|
+
getPerPluginData: () => ({}),
|
|
65
|
+
storageDataChangedEvent: () => Disposable.NULL,
|
|
66
|
+
$updatePluginsWorkspaceData: () => undefined
|
|
67
|
+
};
|
|
68
|
+
const dummyTerminalService: MinimalTerminalServiceExt = {
|
|
69
|
+
$initEnvironmentVariableCollections: () => undefined,
|
|
70
|
+
$setShell: () => undefined,
|
|
71
|
+
getEnvironmentVariableCollection: () => new EnvironmentVariableCollectionImpl(false),
|
|
72
|
+
};
|
|
73
|
+
bind(InternalSecretsExt).toConstantValue(dummySecrets);
|
|
74
|
+
bind(InternalStorageExt).toConstantValue(dummyStorage);
|
|
75
|
+
bind(MinimalTerminalServiceExt).toConstantValue(dummyTerminalService);
|
|
76
|
+
});
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2024 EclipseSource and others.
|
|
3
|
+
//
|
|
4
|
+
// This program and the accompanying materials are made available under the
|
|
5
|
+
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
//
|
|
8
|
+
// This Source Code may also be made available under the following Secondary
|
|
9
|
+
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
// with the GNU Classpath Exception which is available at
|
|
12
|
+
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
//
|
|
14
|
+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
+
// *****************************************************************************
|
|
16
|
+
|
|
17
|
+
import { dynamicRequire } from '@theia/core/lib/node/dynamic-require';
|
|
18
|
+
import { ContainerModule, injectable, inject } from '@theia/core/shared/inversify';
|
|
19
|
+
import { EnvExtImpl } from '@theia/plugin-ext/lib/plugin/env';
|
|
20
|
+
import { LocalizationExt } from '@theia/plugin-ext';
|
|
21
|
+
import { LocalizationExtImpl } from '@theia/plugin-ext/lib/plugin/localization-ext';
|
|
22
|
+
import { HEADLESSMAIN_RPC_CONTEXT } from '../../common/headless-plugin-rpc';
|
|
23
|
+
import { HeadlessPluginManagerExtImpl } from '../../plugin/headless-plugin-manager';
|
|
24
|
+
import { AbstractPluginHostRPC, ExtInterfaces } from '@theia/plugin-ext/lib/hosted/node/plugin-host-rpc';
|
|
25
|
+
import { PluginModel } from '@theia/plugin-ext/lib/common/plugin-protocol';
|
|
26
|
+
import { ExtPluginApi, ExtPluginApiHeadlessInitializationFn } from '../../common/plugin-ext-headless-api-contribution';
|
|
27
|
+
|
|
28
|
+
type HeadlessExtInterfaces = Pick<ExtInterfaces, 'envExt'|'localizationExt'>;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The RPC handler for headless plugins.
|
|
32
|
+
*/
|
|
33
|
+
@injectable()
|
|
34
|
+
export class HeadlessPluginHostRPC extends AbstractPluginHostRPC<HeadlessPluginManagerExtImpl, null, HeadlessExtInterfaces> {
|
|
35
|
+
@inject(EnvExtImpl)
|
|
36
|
+
protected readonly envExt: EnvExtImpl;
|
|
37
|
+
|
|
38
|
+
@inject(LocalizationExt)
|
|
39
|
+
protected readonly localizationExt: LocalizationExtImpl;
|
|
40
|
+
|
|
41
|
+
constructor() {
|
|
42
|
+
super('HEADLESS_PLUGIN_HOST', undefined,
|
|
43
|
+
{
|
|
44
|
+
$pluginManager: HEADLESSMAIN_RPC_CONTEXT.HOSTED_PLUGIN_MANAGER_EXT,
|
|
45
|
+
}
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
protected createExtInterfaces(): HeadlessExtInterfaces {
|
|
50
|
+
return {
|
|
51
|
+
envExt: this.envExt,
|
|
52
|
+
localizationExt: this.localizationExt
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
protected createAPIFactory(_extInterfaces: HeadlessExtInterfaces): null {
|
|
57
|
+
// As yet there is no default API namespace for backend plugins to access the Theia framework
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
protected override getBackendPluginPath(pluginModel: PluginModel): string | undefined {
|
|
62
|
+
return pluginModel.entryPoint.headless;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
protected initExtApi(extApi: ExtPluginApi): void {
|
|
66
|
+
interface PluginExports {
|
|
67
|
+
containerModule?: ContainerModule;
|
|
68
|
+
provideApi?: ExtPluginApiHeadlessInitializationFn;
|
|
69
|
+
}
|
|
70
|
+
if (extApi.headlessInitPath) {
|
|
71
|
+
const { containerModule, provideApi } = dynamicRequire<PluginExports>(extApi.headlessInitPath);
|
|
72
|
+
if (containerModule) {
|
|
73
|
+
this.loadContainerModule(containerModule);
|
|
74
|
+
}
|
|
75
|
+
if (provideApi) {
|
|
76
|
+
provideApi(this.rpc, this.pluginManager);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2018 Red Hat, Inc. and others.
|
|
3
|
+
//
|
|
4
|
+
// This program and the accompanying materials are made available under the
|
|
5
|
+
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
//
|
|
8
|
+
// This Source Code may also be made available under the following Secondary
|
|
9
|
+
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
// with the GNU Classpath Exception which is available at
|
|
12
|
+
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
//
|
|
14
|
+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
+
// *****************************************************************************
|
|
16
|
+
import '@theia/core/shared/reflect-metadata';
|
|
17
|
+
import { Container } from '@theia/core/shared/inversify';
|
|
18
|
+
import { ConnectionClosedError, RPCProtocol } from '@theia/plugin-ext/lib/common/rpc-protocol';
|
|
19
|
+
import { ProcessTerminatedMessage, ProcessTerminateMessage } from '@theia/plugin-ext/lib/hosted/node/hosted-plugin-protocol';
|
|
20
|
+
import { HeadlessPluginHostRPC } from './plugin-host-headless-rpc';
|
|
21
|
+
import pluginHostModule from './plugin-host-headless-module';
|
|
22
|
+
|
|
23
|
+
const banner = `HEADLESS_PLUGIN_HOST(${process.pid}):`;
|
|
24
|
+
console.log(banner, 'Starting instance');
|
|
25
|
+
|
|
26
|
+
// override exit() function, to do not allow plugin kill this node
|
|
27
|
+
process.exit = function (code?: number): void {
|
|
28
|
+
const err = new Error('A plugin called process.exit() but it was blocked.');
|
|
29
|
+
console.warn(banner, err.stack);
|
|
30
|
+
} as (code?: number) => never;
|
|
31
|
+
|
|
32
|
+
// same for 'crash'(works only in electron)
|
|
33
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
34
|
+
const proc = process as any;
|
|
35
|
+
if (proc.crash) {
|
|
36
|
+
proc.crash = function (): void {
|
|
37
|
+
const err = new Error('A plugin called process.crash() but it was blocked.');
|
|
38
|
+
console.warn(banner, err.stack);
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
process.on('uncaughtException', (err: Error) => {
|
|
43
|
+
console.error(banner, err);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
47
|
+
const unhandledPromises: Promise<any>[] = [];
|
|
48
|
+
|
|
49
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
50
|
+
process.on('unhandledRejection', (reason: any, promise: Promise<any>) => {
|
|
51
|
+
unhandledPromises.push(promise);
|
|
52
|
+
setTimeout(() => {
|
|
53
|
+
const index = unhandledPromises.indexOf(promise);
|
|
54
|
+
if (index >= 0) {
|
|
55
|
+
promise.catch(err => {
|
|
56
|
+
unhandledPromises.splice(index, 1);
|
|
57
|
+
if (terminating && (ConnectionClosedError.is(err) || ConnectionClosedError.is(reason))) {
|
|
58
|
+
// during termination it is expected that pending rpc request are rejected
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
console.error(banner, `Promise rejection not handled in one second: ${err} , reason: ${reason}`);
|
|
62
|
+
if (err && err.stack) {
|
|
63
|
+
console.error(banner, `With stack trace: ${err.stack}`);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}, 1000);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
71
|
+
process.on('rejectionHandled', (promise: Promise<any>) => {
|
|
72
|
+
const index = unhandledPromises.indexOf(promise);
|
|
73
|
+
if (index >= 0) {
|
|
74
|
+
unhandledPromises.splice(index, 1);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
let terminating = false;
|
|
79
|
+
|
|
80
|
+
const container = new Container();
|
|
81
|
+
container.load(pluginHostModule);
|
|
82
|
+
|
|
83
|
+
const rpc: RPCProtocol = container.get(RPCProtocol);
|
|
84
|
+
const pluginHostRPC = container.get(HeadlessPluginHostRPC);
|
|
85
|
+
|
|
86
|
+
process.on('message', async (message: string) => {
|
|
87
|
+
if (terminating) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
const msg = JSON.parse(message);
|
|
92
|
+
if (ProcessTerminateMessage.is(msg)) {
|
|
93
|
+
terminating = true;
|
|
94
|
+
if (msg.stopTimeout) {
|
|
95
|
+
await Promise.race([
|
|
96
|
+
pluginHostRPC.terminate(),
|
|
97
|
+
new Promise(resolve => setTimeout(resolve, msg.stopTimeout))
|
|
98
|
+
]);
|
|
99
|
+
} else {
|
|
100
|
+
await pluginHostRPC.terminate();
|
|
101
|
+
}
|
|
102
|
+
rpc.dispose();
|
|
103
|
+
if (process.send) {
|
|
104
|
+
process.send(JSON.stringify({ type: ProcessTerminatedMessage.TYPE }));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
}
|
|
108
|
+
} catch (e) {
|
|
109
|
+
console.error(banner, e);
|
|
110
|
+
}
|
|
111
|
+
});
|