@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.
Files changed (107) hide show
  1. package/README.md +32 -0
  2. package/lib/common/headless-plugin-container.d.ts +8 -0
  3. package/lib/common/headless-plugin-container.d.ts.map +1 -0
  4. package/lib/common/headless-plugin-container.js +26 -0
  5. package/lib/common/headless-plugin-container.js.map +1 -0
  6. package/lib/common/headless-plugin-protocol.d.ts +21 -0
  7. package/lib/common/headless-plugin-protocol.d.ts.map +1 -0
  8. package/lib/common/headless-plugin-protocol.js +35 -0
  9. package/lib/common/headless-plugin-protocol.js.map +1 -0
  10. package/lib/common/headless-plugin-rpc.d.ts +23 -0
  11. package/lib/common/headless-plugin-rpc.d.ts.map +1 -0
  12. package/lib/common/headless-plugin-rpc.js +31 -0
  13. package/lib/common/headless-plugin-rpc.js.map +1 -0
  14. package/lib/common/index.d.ts +5 -0
  15. package/lib/common/index.d.ts.map +1 -0
  16. package/lib/common/index.js +35 -0
  17. package/lib/common/index.js.map +1 -0
  18. package/lib/common/plugin-ext-headless-api-contribution.d.ts +38 -0
  19. package/lib/common/plugin-ext-headless-api-contribution.d.ts.map +1 -0
  20. package/lib/common/plugin-ext-headless-api-contribution.js +29 -0
  21. package/lib/common/plugin-ext-headless-api-contribution.js.map +1 -0
  22. package/lib/hosted/node/headless-hosted-plugin.d.ts +30 -0
  23. package/lib/hosted/node/headless-hosted-plugin.d.ts.map +1 -0
  24. package/lib/hosted/node/headless-hosted-plugin.js +192 -0
  25. package/lib/hosted/node/headless-hosted-plugin.js.map +1 -0
  26. package/lib/hosted/node/plugin-ext-headless-hosted-module.d.ts +4 -0
  27. package/lib/hosted/node/plugin-ext-headless-hosted-module.d.ts.map +1 -0
  28. package/lib/hosted/node/plugin-ext-headless-hosted-module.js +69 -0
  29. package/lib/hosted/node/plugin-ext-headless-hosted-module.js.map +1 -0
  30. package/lib/hosted/node/plugin-host-headless-module.d.ts +5 -0
  31. package/lib/hosted/node/plugin-host-headless-module.d.ts.map +1 -0
  32. package/lib/hosted/node/plugin-host-headless-module.js +73 -0
  33. package/lib/hosted/node/plugin-host-headless-module.js.map +1 -0
  34. package/lib/hosted/node/plugin-host-headless-rpc.d.ts +21 -0
  35. package/lib/hosted/node/plugin-host-headless-rpc.d.ts.map +1 -0
  36. package/lib/hosted/node/plugin-host-headless-rpc.js +82 -0
  37. package/lib/hosted/node/plugin-host-headless-rpc.js.map +1 -0
  38. package/lib/hosted/node/plugin-host-headless.d.ts +2 -0
  39. package/lib/hosted/node/plugin-host-headless.d.ts.map +1 -0
  40. package/lib/hosted/node/plugin-host-headless.js +104 -0
  41. package/lib/hosted/node/plugin-host-headless.js.map +1 -0
  42. package/lib/hosted/node/scanners/scanner-theia-headless.d.ts +26 -0
  43. package/lib/hosted/node/scanners/scanner-theia-headless.d.ts.map +1 -0
  44. package/lib/hosted/node/scanners/scanner-theia-headless.js +91 -0
  45. package/lib/hosted/node/scanners/scanner-theia-headless.js.map +1 -0
  46. package/lib/hosted/node-electron/plugin-ext-headless-hosted-electron-module.d.ts +3 -0
  47. package/lib/hosted/node-electron/plugin-ext-headless-hosted-electron-module.d.ts.map +1 -0
  48. package/lib/hosted/node-electron/plugin-ext-headless-hosted-electron-module.js +24 -0
  49. package/lib/hosted/node-electron/plugin-ext-headless-hosted-electron-module.js.map +1 -0
  50. package/lib/index.d.ts +2 -0
  51. package/lib/index.d.ts.map +1 -0
  52. package/lib/index.js +29 -0
  53. package/lib/index.js.map +1 -0
  54. package/lib/main/node/handlers/plugin-theia-headless-directory-handler.d.ts +7 -0
  55. package/lib/main/node/handlers/plugin-theia-headless-directory-handler.d.ts.map +1 -0
  56. package/lib/main/node/handlers/plugin-theia-headless-directory-handler.js +43 -0
  57. package/lib/main/node/handlers/plugin-theia-headless-directory-handler.js.map +1 -0
  58. package/lib/main/node/headless-progress-client.d.ts +10 -0
  59. package/lib/main/node/headless-progress-client.d.ts.map +1 -0
  60. package/lib/main/node/headless-progress-client.js +52 -0
  61. package/lib/main/node/headless-progress-client.js.map +1 -0
  62. package/lib/main/node/main-context.d.ts +4 -0
  63. package/lib/main/node/main-context.d.ts.map +1 -0
  64. package/lib/main/node/main-context.js +19 -0
  65. package/lib/main/node/main-context.js.map +1 -0
  66. package/lib/main/node/plugin-ext-headless-main-module.d.ts +4 -0
  67. package/lib/main/node/plugin-ext-headless-main-module.d.ts.map +1 -0
  68. package/lib/main/node/plugin-ext-headless-main-module.js +38 -0
  69. package/lib/main/node/plugin-ext-headless-main-module.js.map +1 -0
  70. package/lib/package.spec.d.ts +1 -0
  71. package/lib/package.spec.d.ts.map +1 -0
  72. package/lib/package.spec.js +24 -0
  73. package/lib/package.spec.js.map +1 -0
  74. package/lib/plugin/headless-plugin-manager.d.ts +10 -0
  75. package/lib/plugin/headless-plugin-manager.d.ts.map +1 -0
  76. package/lib/plugin/headless-plugin-manager.js +56 -0
  77. package/lib/plugin/headless-plugin-manager.js.map +1 -0
  78. package/lib/plugin-ext-headless-electron-module.d.ts +4 -0
  79. package/lib/plugin-ext-headless-electron-module.d.ts.map +1 -0
  80. package/lib/plugin-ext-headless-electron-module.js +32 -0
  81. package/lib/plugin-ext-headless-electron-module.js.map +1 -0
  82. package/lib/plugin-ext-headless-module.d.ts +4 -0
  83. package/lib/plugin-ext-headless-module.d.ts.map +1 -0
  84. package/lib/plugin-ext-headless-module.js +31 -0
  85. package/lib/plugin-ext-headless-module.js.map +1 -0
  86. package/package.json +56 -0
  87. package/src/common/headless-plugin-container.ts +23 -0
  88. package/src/common/headless-plugin-protocol.ts +38 -0
  89. package/src/common/headless-plugin-rpc.ts +46 -0
  90. package/src/common/index.ts +23 -0
  91. package/src/common/plugin-ext-headless-api-contribution.ts +60 -0
  92. package/src/hosted/node/headless-hosted-plugin.ts +199 -0
  93. package/src/hosted/node/plugin-ext-headless-hosted-module.ts +75 -0
  94. package/src/hosted/node/plugin-host-headless-module.ts +76 -0
  95. package/src/hosted/node/plugin-host-headless-rpc.ts +80 -0
  96. package/src/hosted/node/plugin-host-headless.ts +111 -0
  97. package/src/hosted/node/scanners/scanner-theia-headless.ts +85 -0
  98. package/src/hosted/node-electron/plugin-ext-headless-hosted-electron-module.ts +22 -0
  99. package/src/index.ts +17 -0
  100. package/src/main/node/handlers/plugin-theia-headless-directory-handler.ts +35 -0
  101. package/src/main/node/headless-progress-client.ts +44 -0
  102. package/src/main/node/main-context.ts +35 -0
  103. package/src/main/node/plugin-ext-headless-main-module.ts +42 -0
  104. package/src/package.spec.ts +25 -0
  105. package/src/plugin/headless-plugin-manager.ts +50 -0
  106. package/src/plugin-ext-headless-electron-module.ts +32 -0
  107. 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
+ });