@theia/plugin-ext-headless 1.46.0-next.106

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 +26 -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 +26 -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 +20 -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 +184 -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 +74 -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 +83 -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 +20 -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 +38 -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 +47 -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 +51 -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 +57 -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,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
+ });
@@ -0,0 +1,85 @@
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
+ /* eslint-disable @theia/localization-check */
18
+
19
+ import { injectable } from '@theia/core/shared/inversify';
20
+ import { DeployedPlugin, PluginPackage, PluginEntryPoint } from '@theia/plugin-ext';
21
+ import { AbstractPluginScanner } from '@theia/plugin-ext/lib/hosted/node/scanners/scanner-theia';
22
+ import { deepClone } from '@theia/core/lib/common/objects';
23
+
24
+ @injectable()
25
+ export class TheiaHeadlessPluginScanner extends AbstractPluginScanner {
26
+
27
+ constructor() {
28
+ super('theiaHeadlessPlugin');
29
+ }
30
+
31
+ protected getEntryPoint(plugin: PluginPackage): PluginEntryPoint {
32
+ if (plugin?.theiaPlugin?.headless) {
33
+ return {
34
+ headless: plugin.theiaPlugin.headless
35
+ };
36
+ };
37
+
38
+ return {
39
+ headless: plugin.main
40
+ };
41
+ }
42
+
43
+ /**
44
+ * Adapt the given `plugin`'s metadata for headless deployment, where it does not
45
+ * already natively specify its headless deployment, such as is the case for plugins
46
+ * declaring the `"vscode"` or `"theiaPlugin"` engine. This consists of cloning the
47
+ * relevant properties of its deployment metadata and modifying them as required,
48
+ * including but not limited to:
49
+ *
50
+ * - renaming the `lifecycle` start and stop functions as 'activate' and 'deactivate'
51
+ * following the VS Code naming convention (in case the `plugin` is a Theia-style
52
+ * plugin that uses 'start' and 'stop')
53
+ * - deleting inapplicable information such as frontend and backend init script paths
54
+ * - filtering/rewriting contributions and/or activation events
55
+ *
56
+ * The cloning is necessary to retain the original information for the non-headless
57
+ * deployments that the plugin also supports.
58
+ */
59
+ adaptForHeadless(plugin: DeployedPlugin): DeployedPlugin {
60
+ return {
61
+ type: plugin.type,
62
+ metadata: this.adaptMetadataForHeadless(plugin),
63
+ contributes: this.adaptContributesForHeadless(plugin)
64
+ };
65
+ }
66
+
67
+ protected adaptMetadataForHeadless(plugin: DeployedPlugin): DeployedPlugin['metadata'] {
68
+ const result = deepClone(plugin.metadata);
69
+
70
+ const lifecycle = result.lifecycle;
71
+ delete lifecycle.frontendInitPath;
72
+ delete lifecycle.backendInitPath;
73
+
74
+ // Same as in VS Code
75
+ lifecycle.startMethod = 'activate';
76
+ lifecycle.stopMethod = 'deactivate';
77
+
78
+ return result;
79
+ }
80
+
81
+ protected adaptContributesForHeadless(plugin: DeployedPlugin): DeployedPlugin['contributes'] {
82
+ // We don't yet support and contribution points in headless plugins
83
+ return undefined;
84
+ }
85
+ }
@@ -0,0 +1,22 @@
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 { interfaces } from '@theia/core/shared/inversify';
18
+ import { bindCommonHostedBackend } from '../node/plugin-ext-headless-hosted-module';
19
+
20
+ export function bindElectronBackend(bind: interfaces.Bind): void {
21
+ bindCommonHostedBackend(bind);
22
+ }
package/src/index.ts ADDED
@@ -0,0 +1,17 @@
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
+ export * from './common';