diff --git a/packages/core/package.json b/packages/core/package.json index 8f711f2ff350b..b17aa8f77b00f 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -43,7 +43,7 @@ "vscode-languageserver-types": "^3.15.0-next", "vscode-uri": "^1.0.8", "vscode-ws-jsonrpc": "^0.1.1", - "ws": "^5.2.2", + "ws": "^7.1.2", "yargs": "^11.1.0" }, "publishConfig": { diff --git a/packages/core/src/node/messaging/connection-container-module.ts b/packages/core/src/node/messaging/connection-container-module.ts index a8004e840f1ab..ce42257f2ac1d 100644 --- a/packages/core/src/node/messaging/connection-container-module.ts +++ b/packages/core/src/node/messaging/connection-container-module.ts @@ -21,7 +21,7 @@ import { JsonRpcProxyFactory, ConnectionHandler, JsonRpcConnectionHandler, JsonR export type BindFrontendService = (path: string, serviceIdentifier: interfaces.ServiceIdentifier) => interfaces.BindingWhenOnSyntax; export type BindBackendService = ( - path: string, serviceIdentifier: interfaces.ServiceIdentifier, onActivation?: (service: T, proxy: JsonRpcProxy) => T + path: string, serviceIdentifier: interfaces.ServiceIdentifier, onActivation?: (service: T, proxy: JsonRpcProxy, context: interfaces.Context) => T ) => void; export type ConnectionContainerModuleCallBack = (registry: { bind: interfaces.Bind @@ -86,7 +86,7 @@ export const ConnectionContainerModule: symbol & { create(callback: ConnectionCo bind(ConnectionHandler).toDynamicValue(context => new JsonRpcConnectionHandler(path, proxy => { const service = context.container.get(serviceIdentifier); - return onActivation ? onActivation(service, proxy) : service; + return onActivation ? onActivation(service, proxy, context) : service; }) ).inSingletonScope(); }; diff --git a/packages/core/src/node/messaging/messaging-contribution.ts b/packages/core/src/node/messaging/messaging-contribution.ts index 90a149dc5e3a9..1ab7cbe683f0d 100644 --- a/packages/core/src/node/messaging/messaging-contribution.ts +++ b/packages/core/src/node/messaging/messaging-contribution.ts @@ -84,7 +84,10 @@ export class MessagingContribution implements BackendApplicationContribution, Me onStart(server: http.Server | https.Server): void { const wss = new ws.Server({ server, - perMessageDeflate: false + perMessageDeflate: { + // don't compress if a message is less than 256kb + threshold: 256 * 1024 + } }); interface CheckAliveWS extends ws { alive: boolean; diff --git a/packages/plugin-dev/src/node/hosted-plugin-reader.ts b/packages/plugin-dev/src/node/hosted-plugin-reader.ts index a866d3bbcb49f..26a68fd4294f7 100644 --- a/packages/plugin-dev/src/node/hosted-plugin-reader.ts +++ b/packages/plugin-dev/src/node/hosted-plugin-reader.ts @@ -34,7 +34,7 @@ export class HostedPluginReader implements BackendApplicationContribution { protected deployerHandler: HostedPluginDeployerHandler; async initialize(): Promise { - this.pluginReader.doGetPluginMetadata(process.env.HOSTED_PLUGIN) + this.pluginReader.doGetPluginMetadata(process.env.HOSTED_PLUGIN, {}) .then(this.hostedPlugin.resolve.bind(this.hostedPlugin)); const pluginPath = process.env.HOSTED_PLUGIN; diff --git a/packages/plugin-ext-vscode/src/node/plugin-vscode-init.ts b/packages/plugin-ext-vscode/src/node/plugin-vscode-init.ts index 7f02a8f511d77..4a049bce4de63 100644 --- a/packages/plugin-ext-vscode/src/node/plugin-vscode-init.ts +++ b/packages/plugin-ext-vscode/src/node/plugin-vscode-init.ts @@ -44,7 +44,8 @@ export const doInitialization: BackendInitializationFn = (apiFactory: PluginAPIF vscode.commands.registerCommand = function (command: theia.CommandDescription | string, handler?: (...args: any[]) => T | Thenable, thisArg?: any): any { // use of the ID when registering commands if (typeof command === 'string') { - const commands = plugin.model.contributes && plugin.model.contributes.commands; + const rawCommands = plugin.rawModel.contributes && plugin.rawModel.contributes.commands; + const commands = rawCommands ? Array.isArray(rawCommands) ? rawCommands : [rawCommands] : undefined; if (handler && commands && commands.some(item => item.command === command)) { return vscode.commands.registerHandler(command, handler, thisArg); } diff --git a/packages/plugin-ext-vscode/src/node/scanner-vscode.ts b/packages/plugin-ext-vscode/src/node/scanner-vscode.ts index 204f287852230..84e15473bcc02 100644 --- a/packages/plugin-ext-vscode/src/node/scanner-vscode.ts +++ b/packages/plugin-ext-vscode/src/node/scanner-vscode.ts @@ -15,7 +15,7 @@ ********************************************************************************/ import { injectable } from 'inversify'; -import { PluginScanner, PluginEngine, PluginPackage, PluginModel, PluginLifecycle } from '@theia/plugin-ext'; +import { PluginScanner, PluginEngine, PluginPackage, PluginModel, PluginLifecycle, PluginModelOptions } from '@theia/plugin-ext'; import { TheiaPluginScanner } from '@theia/plugin-ext/lib/hosted/node/scanners/scanner-theia'; @injectable() @@ -27,13 +27,14 @@ export class VsCodePluginScanner extends TheiaPluginScanner implements PluginSca return this.VSCODE_TYPE; } - getModel(plugin: PluginPackage): PluginModel { + getModel(plugin: PluginPackage, options: PluginModelOptions): PluginModel { // translate vscode builtins, as they are published with a prefix. See https://github.com/theia-ide/vscode-builtin-extensions/blob/master/src/republish.js#L50 const built_prefix = '@theia/vscode-builtin-'; if (plugin && plugin.name && plugin.name.startsWith(built_prefix)) { plugin.name = plugin.name.substr(built_prefix.length); } const result: PluginModel = { + packagePath: plugin.packagePath, // see id definition: https://github.com/microsoft/vscode/blob/15916055fe0cb9411a5f36119b3b012458fe0a1d/src/vs/platform/extensions/common/extensions.ts#L167-L169 id: `${plugin.publisher.toLowerCase()}.${plugin.name.toLowerCase()}`, name: plugin.name, @@ -47,10 +48,16 @@ export class VsCodePluginScanner extends TheiaPluginScanner implements PluginSca }, entryPoint: { backend: plugin.main - }, - extensionDependencies: this.getDeployableDependencies(plugin.extensionDependencies || []) + } }; - result.contributes = this.readContributions(plugin); + if (options.contributions) { + result.activationEvents = plugin.activationEvents; + result.contributes = this.readContributions(plugin); + } + if (options.dependencies) { + result.rawExtensionDependencies = plugin.extensionDependencies; + result.extensionDependencies = this.getDeployableDependencies(plugin.extensionDependencies || []); + } return result; } diff --git a/packages/plugin-ext/src/common/plugin-api-rpc.ts b/packages/plugin-ext/src/common/plugin-api-rpc.ts index 99e72066820dd..ec3632c028cd0 100644 --- a/packages/plugin-ext/src/common/plugin-api-rpc.ts +++ b/packages/plugin-ext/src/common/plugin-api-rpc.ts @@ -66,7 +66,6 @@ import { import { ExtPluginApi } from './plugin-ext-api-contribution'; import { KeysToAnyValues, KeysToKeysToAnyValue } from './types'; import { CancellationToken, Progress, ProgressOptions } from '@theia/plugin'; -import { IJSONSchema, IJSONSchemaSnippet } from '@theia/core/lib/common/json-schema'; import { DebuggerDescription } from '@theia/debug/lib/common/debug-service'; import { DebugProtocol } from 'vscode-debugprotocol'; import { SymbolInformation } from 'vscode-languageserver-types'; @@ -76,16 +75,6 @@ import { MaybePromise } from '@theia/core/lib/common/types'; import { QuickOpenItem, QuickOpenItemOptions } from '@theia/core/lib/common/quick-open-model'; import { QuickTitleButton } from '@theia/core/lib/common/quick-open-model'; -export interface PluginInitData { - plugins: PluginMetadata[]; - preferences: PreferenceData; - globalState: KeysToKeysToAnyValue; - workspaceState: KeysToKeysToAnyValue; - env: EnvInit; - extApi?: ExtPluginApi[]; - activationEvents: string[] -} - export interface PreferenceData { [scope: number]: any; } @@ -100,7 +89,7 @@ export interface Plugin { export interface ConfigStorage { hostLogPath: string; - hostStoragePath: string, + hostStoragePath?: string, } export interface EnvInit { @@ -142,6 +131,7 @@ export const emptyPlugin: Plugin = { type: 'empty', version: 'empty' }, + packagePath: 'empty', entryPoint: { } @@ -162,12 +152,35 @@ export const emptyPlugin: Plugin = { } }; +export interface PluginManagerInitializeParams { + preferences: PreferenceData + globalState: KeysToKeysToAnyValue + workspaceState: KeysToKeysToAnyValue + env: EnvInit + extApi?: ExtPluginApi[] +} + +export interface PluginManagerStartParams { + plugins: PluginMetadata[] + configStorage: ConfigStorage + activationEvents: string[] +} + export interface PluginManagerExt { - $stop(pluginId?: string): PromiseLike; - $init(pluginInit: PluginInitData, configStorage: ConfigStorage): PromiseLike; + /** initialize the manager, should be called only once */ + $initiliaze(params: PluginManagerInitializeParams): Promise; + + /** load and activate plugins */ + $start(params: PluginManagerStartParams): Promise; + + /** deactivate the plugin */ + $stop(pluginId: string): Promise; + + /** deactivate all plugins */ + $stop(): Promise; - $updateStoragePath(path: string | undefined): PromiseLike; + $updateStoragePath(path: string | undefined): Promise; $activateByEvent(event: string): Promise; } @@ -1236,9 +1249,6 @@ export interface DebugExt { $sessionDidChange(sessionId: string | undefined): void; $provideDebugConfigurations(debugType: string, workspaceFolder: string | undefined): Promise; $resolveDebugConfigurations(debugConfiguration: theia.DebugConfiguration, workspaceFolder: string | undefined): Promise; - $getSupportedLanguages(debugType: string): Promise; - $getSchemaAttributes(debugType: string): Promise; - $getConfigurationSnippets(debugType: string): Promise; $createDebugSession(debugConfiguration: theia.DebugConfiguration): Promise; $terminateDebugSession(sessionId: string): Promise; $getTerminalCreationOptions(debugType: string): Promise; diff --git a/packages/plugin-ext/src/common/plugin-protocol.ts b/packages/plugin-ext/src/common/plugin-protocol.ts index cc5d2cf3edf52..692a933e5a233 100644 --- a/packages/plugin-ext/src/common/plugin-protocol.ts +++ b/packages/plugin-ext/src/common/plugin-protocol.ts @@ -204,6 +204,13 @@ export interface PluginProblemPatternContribution extends ProblemPatternContribu name: string; } +export interface PluginModelOptions { + /** whether to load contributions info */ + contributions?: boolean + /** whether to load dependencies info */ + dependencies?: boolean +} + export const PluginScanner = Symbol('PluginScanner'); /** * This scanner process package.json object and returns plugin metadata objects. @@ -220,7 +227,7 @@ export interface PluginScanner { * @param {PluginPackage} plugin * @returns {PluginModel} */ - getModel(plugin: PluginPackage): PluginModel; + getModel(plugin: PluginPackage, options: PluginModelOptions): PluginModel; /** * Creates plugin's lifecycle. @@ -378,12 +385,21 @@ export interface PluginModel { frontend?: string; backend?: string; }; + packagePath: string; + + // frontend contributions info -- start contributes?: PluginContribution; + activationEvents?: string[]; + // frontend contributions info -- end + + // backend dependencies info -- start /** * The deployable form of extensionDependencies from package.json, * i.e. not `publisher.name`, but `vscode:extension/publisher.name`. */ extensionDependencies?: string[]; + rawExtensionDependencies?: string[]; + // backend dependencies info -- end } /** @@ -583,7 +599,6 @@ export interface ExtensionContext { export interface PluginMetadata { host: string; - source: PluginPackage; model: PluginModel; lifecycle: PluginLifecycle; } @@ -615,15 +630,19 @@ export interface PluginDeployerHandler { deployFrontendPlugins(frontendPlugins: PluginDeployerEntry[]): Promise; deployBackendPlugins(backendPlugins: PluginDeployerEntry[]): Promise; - getPluginMetadata(pluginToBeInstalled: PluginDeployerEntry): Promise + getPluginMetadata(pluginToBeInstalled: PluginDeployerEntry, options: PluginModelOptions): Promise +} + +export interface GetDeployedPluginsParams { + pluginIds: string[] } export const HostedPluginServer = Symbol('HostedPluginServer'); export interface HostedPluginServer extends JsonRpcServer { - getDeployedMetadata(): Promise; - getDeployedFrontendMetadata(): Promise; - getDeployedBackendMetadata(): Promise; + getDeployedPluginIds(): Promise; + + getDeployedPlugins(params: GetDeployedPluginsParams): Promise; getExtPluginAPI(): Promise; diff --git a/packages/plugin-ext/src/hosted/browser/hosted-plugin.ts b/packages/plugin-ext/src/hosted/browser/hosted-plugin.ts index e095bf7b65255..ef4bb6f37fa51 100644 --- a/packages/plugin-ext/src/hosted/browser/hosted-plugin.ts +++ b/packages/plugin-ext/src/hosted/browser/hosted-plugin.ts @@ -21,6 +21,8 @@ // tslint:disable:no-any +import debounce = require('lodash.debounce'); +import { UUID } from '@phosphor/coreutils'; import { injectable, inject, interfaces, named, postConstruct } from 'inversify'; import { PluginWorker } from '../../main/browser/plugin-worker'; import { PluginMetadata, getPluginId, HostedPluginServer } from '../../common/plugin-protocol'; @@ -33,16 +35,14 @@ import { ILogger, ContributionProvider, CommandRegistry, WillExecuteCommandEvent, CancellationTokenSource, JsonRpcProxy, ProgressService } from '@theia/core'; -import { PreferenceServiceImpl, PreferenceProviderProvider } from '@theia/core/lib/browser'; +import { PreferenceServiceImpl, PreferenceProviderProvider } from '@theia/core/lib/browser/preferences'; import { WorkspaceService } from '@theia/workspace/lib/browser'; import { PluginContributionHandler } from '../../main/browser/plugin-contribution-handler'; import { getQueryParameters } from '../../main/browser/env-main'; -import { ExtPluginApi, MainPluginApiProvider } from '../../common/plugin-ext-api-contribution'; +import { MainPluginApiProvider } from '../../common/plugin-ext-api-contribution'; import { PluginPathsService } from '../../main/common/plugin-paths-protocol'; import { getPreferences } from '../../main/browser/preference-registry-main'; import { PluginServer } from '../../common/plugin-protocol'; -import { KeysToKeysToAnyValue } from '../../common/types'; -import { FileStat } from '@theia/filesystem/lib/common/filesystem'; import { MonacoTextmateService } from '@theia/monaco/lib/browser/textmate'; import { Deferred } from '@theia/core/lib/common/promise-util'; import { DebugSessionManager } from '@theia/debug/lib/browser/debug-session-manager'; @@ -61,7 +61,10 @@ export const PluginProgressLocation = 'plugin'; @injectable() export class HostedPluginSupport { - container: interfaces.Container; + + protected readonly clientId = UUID.uuid4(); + + protected container: interfaces.Container; @inject(ILogger) protected readonly logger: ILogger; @@ -167,38 +170,32 @@ export class HostedPluginSupport { this.server.onDidOpenConnection(() => this.load()); } - async load(): Promise { + protected loadQueue: Promise = Promise.resolve(undefined); + load = debounce(() => this.loadQueue = this.loadQueue.then(async () => { try { - await this.progressService.withProgress('', PluginProgressLocation, async () => { - const roots = this.workspaceService.tryGetRoots(); - const [plugins, logPath, storagePath, pluginAPIs, globalStates, workspaceStates] = await Promise.all([ - this.server.getDeployedMetadata(), - this.pluginPathsService.getHostLogPath(), - this.getStoragePath(), - this.server.getExtPluginAPI(), - this.pluginServer.getAllStorageValues(undefined), - this.pluginServer.getAllStorageValues({ workspace: this.workspaceService.workspace, roots }) - ]); - await this.doLoad({ plugins, logPath, storagePath, pluginAPIs, globalStates, workspaceStates, roots }, this.container); - }); + await this.progressService.withProgress('', PluginProgressLocation, () => this.doLoad()); } catch (e) { console.error('Failed to load plugins:', e); } - } + }), 50, { trailing: true }); - protected async doLoad(initData: PluginsInitializationData, container: interfaces.Container): Promise { + protected async doLoad(): Promise { const toDisconnect = new DisposableCollection(Disposable.create(() => { /* mark as connected */ })); this.server.onDidCloseConnection(() => toDisconnect.dispose()); + // process empty plugins as well in order to properly remove stale plugin widgets + await this.syncPlugins(); + // make sure that the previous state, including plugin widgets, is restored // and core layout is initialized, i.e. explorer, scm, debug views are already added to the shell // but shell is not yet revealed await this.appState.reachedState('initialized_layout'); + if (toDisconnect.disposed) { // if disconnected then don't try to load plugin contributions return; } - const contributionsByHost = this.loadContributions(initData.plugins, toDisconnect); + const contributionsByHost = this.loadContributions(toDisconnect); await this.viewRegistry.initWidgets(); // remove restored plugin widgets which were not registered by contributions @@ -209,35 +206,74 @@ export class HostedPluginSupport { // if disconnected then don't try to init plugin code and dynamic contributions return; } - toDisconnect.push(this.startPlugins(contributionsByHost, initData, container)); + await this.startPlugins(contributionsByHost, toDisconnect); + } + + /** + * Sync loaded and deployed plugins: + * - undeployed plugins are unloaded + * - newly deployed plugins are initialized + */ + protected async syncPlugins(): Promise { + let initialized = 0; + const syncPluginsMeasurement = this.createMeasurement('syncPlugins'); + + const toUnload = new Set(this.contributions.keys()); + try { + const pluginIds: string[] = []; + const deployedPluginIds = await this.server.getDeployedPluginIds(); + for (const pluginId of deployedPluginIds) { + toUnload.delete(pluginId); + if (!this.contributions.has(pluginId)) { + pluginIds.push(pluginId); + } + } + for (const pluginId of toUnload) { + const contribution = this.contributions.get(pluginId); + if (contribution) { + contribution.dispose(); + } + } + if (pluginIds.length) { + const plugins = await this.server.getDeployedPlugins({ pluginIds }); + for (const plugin of plugins) { + const pluginId = plugin.model.id; + const contributions = new PluginContributions(plugin); + this.contributions.set(pluginId, contributions); + contributions.push(Disposable.create(() => this.contributions.delete(pluginId))); + initialized++; + } + } + } finally { + if (initialized || toUnload.size) { + this.onDidChangePluginsEmitter.fire(undefined); + } + } + + const pluginCount = `${initialized} plugin${initialized === 1 ? '' : 's'}`; + console.log(`Sync of ${pluginCount} took: ${syncPluginsMeasurement()} ms`); } /** * Always synchronous in order to simplify handling disconnections. * @throws never */ - protected loadContributions(plugins: PluginMetadata[], toDisconnect: DisposableCollection): Map { + protected loadContributions(toDisconnect: DisposableCollection): Map { + let loaded = 0; + const loadPluginsMeasurement = this.createMeasurement('loadPlugins'); + const hostContributions = new Map(); - const toUnload = new Set(this.contributions.keys()); - let loaded = false; - for (const plugin of plugins) { + for (const contributions of this.contributions.values()) { + const plugin = contributions.plugin; const pluginId = plugin.model.id; - toUnload.delete(pluginId); - - let contributions = this.contributions.get(pluginId); - if (!contributions) { - contributions = new PluginContributions(plugin); - this.contributions.set(pluginId, contributions); - contributions.push(Disposable.create(() => this.contributions.delete(pluginId))); - loaded = true; - } if (contributions.state === PluginContributions.State.INITIALIZING) { contributions.state = PluginContributions.State.LOADING; - contributions.push(Disposable.create(() => console.log(`[${plugin.model.id}]: Unloaded plugin.`))); - contributions.push(this.contributionHandler.handleContributions(plugin)); + contributions.push(Disposable.create(() => console.log(`[${this.clientId}][${pluginId}]: Unloaded plugin.`))); + contributions.push(this.contributionHandler.handleContributions(this.clientId, plugin)); contributions.state = PluginContributions.State.LOADED; - console.log(`[${plugin.model.id}]: Loaded contributions.`); + console.log(`[${this.clientId}][${pluginId}]: Loaded contributions.`); + loaded++; } if (contributions.state === PluginContributions.State.LOADED) { @@ -248,89 +284,122 @@ export class HostedPluginSupport { hostContributions.set(host, dynamicContributions); toDisconnect.push(Disposable.create(() => { contributions!.state = PluginContributions.State.LOADED; - console.log(`[${plugin.model.id}]: Disconnected.`); + console.log(`[${this.clientId}][${pluginId}]: Disconnected.`); })); } } - for (const pluginId of toUnload) { - const contribution = this.contributions.get(pluginId); - if (contribution) { - contribution.dispose(); - } - } - if (loaded || toUnload.size) { - this.onDidChangePluginsEmitter.fire(undefined); - } + + const pluginCount = `${loaded} plugin${loaded === 1 ? '' : 's'}`; + console.log(`Load contributions of ${pluginCount} took: ${loadPluginsMeasurement()} ms`); + return hostContributions; } - protected startPlugins( - contributionsByHost: Map, - initData: PluginsInitializationData, - container: interfaces.Container - ): Disposable { - const toDisconnect = new DisposableCollection(); + protected async startPlugins(contributionsByHost: Map, toDisconnect: DisposableCollection): Promise { + let started = 0; + const startPluginsMeasurement = this.createMeasurement('startPlugins'); + + const [hostLogPath, hostStoragePath] = await Promise.all([ + this.pluginPathsService.getHostLogPath(), + this.getStoragePath() + ]); + if (toDisconnect.disposed) { + return; + } + const thenable: Promise[] = []; + const configStorage = { hostLogPath, hostStoragePath }; for (const [host, hostContributions] of contributionsByHost) { - const manager = this.obtainManager(host, hostContributions, container, toDisconnect); - this.initPlugins(manager, { - ...initData, - plugins: hostContributions.map(contributions => contributions.plugin) - }).then(() => { - if (toDisconnect.disposed) { - return; - } - for (const contributions of hostContributions) { - const plugin = contributions.plugin; - const id = plugin.model.id; - contributions.state = PluginContributions.State.STARTED; - console.log(`[${id}]: Started plugin.`); - toDisconnect.push(contributions.push(Disposable.create(() => { - console.log(`[${id}]: Stopped plugin.`); - manager.$stop(id); - }))); - - this.activateByWorkspaceContains(manager, plugin); - } + const manager = await this.obtainManager(host, hostContributions, toDisconnect); + if (!manager) { + return; + } + const plugins = hostContributions.map(contributions => { + const metadata = { + ...contributions.plugin, + model: { + ...contributions.plugin.model + } + }; + // remove contributions info in order to reduce size of JSON-RPC messages + delete metadata.model.contributes; + delete metadata.model.activationEvents; + return metadata; }); + thenable.push((async () => { + try { + const activationEvents = [...this.activationEvents]; + await manager.$start({ plugins, configStorage, activationEvents }); + if (toDisconnect.disposed) { + return; + } + for (const contributions of hostContributions) { + started++; + const plugin = contributions.plugin; + const id = plugin.model.id; + contributions.state = PluginContributions.State.STARTED; + console.log(`[${this.clientId}][${id}]: Started plugin.`); + toDisconnect.push(contributions.push(Disposable.create(() => { + console.log(`[${this.clientId}][${id}]: Stopped plugin.`); + manager.$stop(id); + }))); + + this.activateByWorkspaceContains(manager, plugin); + } + } catch (e) { + console.error(`Failed to start plugins for '${host}' host`, e); + } + })()); } - return toDisconnect; + await Promise.all(thenable); + + const pluginCount = `${started} plugin${started === 1 ? '' : 's'}`; + console.log(`Start of ${pluginCount} took: ${startPluginsMeasurement()} ms`); } - protected obtainManager(host: string, hostContributions: PluginContributions[], container: interfaces.Container, toDispose: DisposableCollection): PluginManagerExt { + protected async obtainManager(host: string, hostContributions: PluginContributions[], toDisconnect: DisposableCollection): Promise { let manager = this.managers.get(host); if (!manager) { const pluginId = getPluginId(hostContributions[0].plugin.model); - const rpc = this.initRpc(host, pluginId, container); - toDispose.push(rpc); + const rpc = this.initRpc(host, pluginId); + toDisconnect.push(rpc); + manager = rpc.getProxy(MAIN_RPC_CONTEXT.HOSTED_PLUGIN_MANAGER_EXT); this.managers.set(host, manager); - toDispose.push(Disposable.create(() => this.managers.delete(host))); + toDisconnect.push(Disposable.create(() => this.managers.delete(host))); + + const [extApi, globalState, workspaceState] = await Promise.all([ + this.server.getExtPluginAPI(), + this.pluginServer.getAllStorageValues(undefined), + this.pluginServer.getAllStorageValues({ + workspace: this.workspaceService.workspace, + roots: this.workspaceService.tryGetRoots() + }) + ]); + if (toDisconnect.disposed) { + return undefined; + } + + await manager.$initiliaze({ + preferences: getPreferences(this.preferenceProviderProvider, this.workspaceService.tryGetRoots()), + globalState, + workspaceState, + env: { queryParams: getQueryParameters(), language: navigator.language }, + extApi + }); + if (toDisconnect.disposed) { + return undefined; + } } return manager; } - protected initRpc(host: PluginHost, pluginId: string, container: interfaces.Container): RPCProtocol { + protected initRpc(host: PluginHost, pluginId: string): RPCProtocol { const rpc = host === 'frontend' ? new PluginWorker().rpc : this.createServerRpc(pluginId, host); - setUpPluginApi(rpc, container); - this.mainPluginApiProviders.getContributions().forEach(p => p.initialize(rpc, container)); + setUpPluginApi(rpc, this.container); + this.mainPluginApiProviders.getContributions().forEach(p => p.initialize(rpc, this.container)); return rpc; } - protected async initPlugins(manager: PluginManagerExt, data: PluginsInitializationData): Promise { - await manager.$init({ - plugins: data.plugins, - preferences: getPreferences(this.preferenceProviderProvider, data.roots), - globalState: data.globalStates, - workspaceState: data.workspaceStates, - env: { queryParams: getQueryParameters(), language: navigator.language }, - extApi: data.pluginAPIs, - activationEvents: [...this.activationEvents] - }, { - hostLogPath: data.logPath, - hostStoragePath: data.storagePath || '' - }); - } - private createServerRpc(pluginID: string, hostID: string): RPCProtocol { return new RPCProtocolImpl({ onMessage: this.watcher.onPostMessageEvent, @@ -420,13 +489,13 @@ export class HostedPluginSupport { } protected async activateByWorkspaceContains(manager: PluginManagerExt, plugin: PluginMetadata): Promise { - if (!plugin.source.activationEvents) { + if (!plugin.model.activationEvents) { return; } const paths: string[] = []; const includePatterns: string[] = []; // should be aligned with https://github.com/microsoft/vscode/blob/da5fb7d5b865aa522abc7e82c10b746834b98639/src/vs/workbench/api/node/extHostExtensionService.ts#L460-L469 - for (const activationEvent of plugin.source.activationEvents) { + for (const activationEvent of plugin.model.activationEvents) { if (/^workspaceContains:/.test(activationEvent)) { const fileNameOrGlob = activationEvent.substr('workspaceContains:'.length); if (fileNameOrGlob.indexOf('*') >= 0 || fileNameOrGlob.indexOf('?') >= 0) { @@ -471,16 +540,21 @@ export class HostedPluginSupport { } } -} + protected createMeasurement(name: string): () => number { + performance.clearMeasures(name); + + const startMarker = `${name}-start`; + const endMarker = `${name}-end`; + performance.mark(startMarker); + return () => { + performance.mark(endMarker); + performance.measure(name, startMarker, endMarker); + const duration = performance.getEntriesByName(name)[0].duration; + performance.clearMeasures(name); + return duration; + }; + } -interface PluginsInitializationData { - plugins: PluginMetadata[], - logPath: string, - storagePath: string | undefined, - pluginAPIs: ExtPluginApi[], - globalStates: KeysToKeysToAnyValue, - workspaceStates: KeysToKeysToAnyValue, - roots: FileStat[], } export class PluginContributions extends DisposableCollection { diff --git a/packages/plugin-ext/src/hosted/browser/worker/worker-main.ts b/packages/plugin-ext/src/hosted/browser/worker/worker-main.ts index d0a04cf39e70f..93f3f7419d0d0 100644 --- a/packages/plugin-ext/src/hosted/browser/worker/worker-main.ts +++ b/packages/plugin-ext/src/hosted/browser/worker/worker-main.ts @@ -19,7 +19,7 @@ import { RPCProtocolImpl } from '../../../common/rpc-protocol'; import { PluginManagerExtImpl } from '../../../plugin/plugin-manager'; import { MAIN_RPC_CONTEXT, Plugin, emptyPlugin } from '../../../common/plugin-api-rpc'; import { createAPIFactory } from '../../../plugin/plugin-context'; -import { getPluginId, PluginMetadata } from '../../../common/plugin-protocol'; +import { getPluginId, PluginMetadata, PluginPackage, PluginModel } from '../../../common/plugin-protocol'; import * as theia from '@theia/plugin'; import { PreferenceRegistryExtImpl } from '../../../plugin/preference-registry'; import { ExtPluginApi } from '../../../common/plugin-ext-api-contribution'; @@ -58,6 +58,20 @@ const preferenceRegistryExt = new PreferenceRegistryExtImpl(rpc, workspaceExt); const debugExt = createDebugExtStub(rpc); const clipboardExt = new ClipboardExt(rpc); +function stubPluginPackage(pluginModel: PluginModel): PluginPackage { + return { + name: pluginModel.name, + displayName: pluginModel.displayName, + engines: { + [pluginModel.engine.type]: pluginModel.engine.version + }, + packagePath: pluginModel.packagePath, + publisher: pluginModel.publisher, + version: pluginModel.version, + description: '' + }; +} + const pluginManager = new PluginManagerExtImpl({ // tslint:disable-next-line:no-any loadPlugin(plugin: Plugin): any { @@ -90,10 +104,10 @@ const pluginManager = new PluginManagerExtImpl({ } const plugin: Plugin = { pluginPath: pluginModel.entryPoint.frontend!, - pluginFolder: plg.source.packagePath, + pluginFolder: pluginModel.packagePath, model: pluginModel, lifecycle: pluginLifecycle, - rawModel: plg.source + rawModel: stubPluginPackage(pluginModel) }; result.push(plugin); const apiImpl = apiFactory(plugin); @@ -102,10 +116,10 @@ const pluginManager = new PluginManagerExtImpl({ } else { foreign.push({ pluginPath: pluginModel.entryPoint.backend!, - pluginFolder: plg.source.packagePath, + pluginFolder: pluginModel.packagePath, model: pluginModel, lifecycle: pluginLifecycle, - rawModel: plg.source + rawModel: stubPluginPackage(pluginModel) }); } } diff --git a/packages/plugin-ext/src/hosted/node-electron/scanner-theia-electron.ts b/packages/plugin-ext/src/hosted/node-electron/scanner-theia-electron.ts index 5b97178383772..d4c7e9e9b0837 100644 --- a/packages/plugin-ext/src/hosted/node-electron/scanner-theia-electron.ts +++ b/packages/plugin-ext/src/hosted/node-electron/scanner-theia-electron.ts @@ -16,11 +16,11 @@ import { TheiaPluginScanner } from '../node/scanners/scanner-theia'; import { injectable } from 'inversify'; -import { PluginPackage, PluginModel } from '../../common/plugin-protocol'; +import { PluginPackage, PluginModel, PluginModelOptions } from '../../common/plugin-protocol'; @injectable() export class TheiaPluginScannerElectron extends TheiaPluginScanner { - getModel(plugin: PluginPackage): PluginModel { - const result = super.getModel(plugin); + getModel(plugin: PluginPackage, options: PluginModelOptions): PluginModel { + const result = super.getModel(plugin, options); if (result.entryPoint.frontend) { result.entryPoint.frontend = plugin.packagePath + result.entryPoint.frontend; } diff --git a/packages/plugin-ext/src/hosted/node/hosted-plugin-deployer-handler.ts b/packages/plugin-ext/src/hosted/node/hosted-plugin-deployer-handler.ts index 216443f25f3e9..33e664c2d8fb6 100644 --- a/packages/plugin-ext/src/hosted/node/hosted-plugin-deployer-handler.ts +++ b/packages/plugin-ext/src/hosted/node/hosted-plugin-deployer-handler.ts @@ -16,7 +16,7 @@ import { injectable, inject } from 'inversify'; import { ILogger } from '@theia/core'; -import { PluginDeployerHandler, PluginDeployerEntry, PluginMetadata } from '../../common/plugin-protocol'; +import { PluginDeployerHandler, PluginDeployerEntry, PluginMetadata, PluginModelOptions } from '../../common/plugin-protocol'; import { HostedPluginReader } from './plugin-reader'; import { Deferred } from '@theia/core/lib/common/promise-util'; @@ -32,12 +32,12 @@ export class HostedPluginDeployerHandler implements PluginDeployerHandler { /** * Managed plugin metadata backend entries. */ - private currentBackendPluginsMetadata: PluginMetadata[] = []; + private readonly currentBackendPluginsMetadata = new Map(); /** * Managed plugin metadata frontend entries. */ - private currentFrontendPluginsMetadata: PluginMetadata[] = []; + private readonly currentFrontendPluginsMetadata = new Map(); private backendPluginsMetadataDeferred = new Deferred(); @@ -47,29 +47,37 @@ export class HostedPluginDeployerHandler implements PluginDeployerHandler { // await first deploy await this.frontendPluginsMetadataDeferred.promise; // fetch the last deployed state - return this.currentFrontendPluginsMetadata; + return [...this.currentFrontendPluginsMetadata.values()]; } async getDeployedBackendMetadata(): Promise { // await first deploy await this.backendPluginsMetadataDeferred.promise; // fetch the last deployed state - return this.currentBackendPluginsMetadata; + return [...this.currentBackendPluginsMetadata.values()]; } - getPluginMetadata(plugin: PluginDeployerEntry): Promise { - return this.reader.getPluginMetadata(plugin.path()); + getDeployedPluginMetadata(pluginId: string): PluginMetadata | undefined { + const metadata = this.currentBackendPluginsMetadata.get(pluginId); + if (metadata) { + return metadata; + } + return this.currentFrontendPluginsMetadata.get(pluginId); + } + + getPluginMetadata(plugin: PluginDeployerEntry, options: PluginModelOptions): Promise { + return this.reader.getPluginMetadata(plugin.path(), options); } async deployFrontendPlugins(frontendPlugins: PluginDeployerEntry[]): Promise { for (const plugin of frontendPlugins) { - const metadata = await this.reader.getPluginMetadata(plugin.path()); + const metadata = await this.getPluginMetadata(plugin, { contributions: true }); if (metadata) { - if (this.currentFrontendPluginsMetadata.some(value => value.model.id === metadata.model.id)) { + if (this.currentFrontendPluginsMetadata.has(metadata.model.id)) { continue; } - this.currentFrontendPluginsMetadata.push(metadata); + this.currentFrontendPluginsMetadata.set(metadata.model.id, metadata); this.logger.info(`Deploying frontend plugin "${metadata.model.name}@${metadata.model.version}" from "${metadata.model.entryPoint.frontend || plugin.path()}"`); } } @@ -80,13 +88,13 @@ export class HostedPluginDeployerHandler implements PluginDeployerHandler { async deployBackendPlugins(backendPlugins: PluginDeployerEntry[]): Promise { for (const plugin of backendPlugins) { - const metadata = await this.reader.getPluginMetadata(plugin.path()); + const metadata = await this.reader.getPluginMetadata(plugin.path(), { contributions: true }); if (metadata) { - if (this.currentBackendPluginsMetadata.some(value => value.model.id === metadata.model.id)) { + if (this.currentBackendPluginsMetadata.has(metadata.model.id)) { continue; } - this.currentBackendPluginsMetadata.push(metadata); + this.currentBackendPluginsMetadata.set(metadata.model.id, metadata); this.logger.info(`Deploying backend plugin "${metadata.model.name}@${metadata.model.version}" from "${metadata.model.entryPoint.backend || plugin.path()}"`); } } diff --git a/packages/plugin-ext/src/hosted/node/metadata-scanner.ts b/packages/plugin-ext/src/hosted/node/metadata-scanner.ts index 040f4fa111846..4e5bead9a1999 100644 --- a/packages/plugin-ext/src/hosted/node/metadata-scanner.ts +++ b/packages/plugin-ext/src/hosted/node/metadata-scanner.ts @@ -15,7 +15,7 @@ ********************************************************************************/ import { injectable, multiInject } from 'inversify'; -import { PluginPackage, PluginScanner, PluginMetadata } from '../../common/plugin-protocol'; +import { PluginPackage, PluginScanner, PluginMetadata, PluginModelOptions } from '../../common/plugin-protocol'; @injectable() export class MetadataScanner { @@ -29,12 +29,11 @@ export class MetadataScanner { }); } - getPluginMetadata(plugin: PluginPackage): PluginMetadata { + getPluginMetadata(plugin: PluginPackage, options: PluginModelOptions): PluginMetadata { const scanner = this.getScanner(plugin); return { host: 'main', - source: plugin, - model: scanner.getModel(plugin), + model: scanner.getModel(plugin, options), lifecycle: scanner.getLifecycle(plugin) }; } diff --git a/packages/plugin-ext/src/hosted/node/plugin-host-rpc.ts b/packages/plugin-ext/src/hosted/node/plugin-host-rpc.ts index 9decabc2591f6..c0b2192ce8432 100644 --- a/packages/plugin-ext/src/hosted/node/plugin-host-rpc.ts +++ b/packages/plugin-ext/src/hosted/node/plugin-host-rpc.ts @@ -27,6 +27,7 @@ import { WorkspaceExtImpl } from '../../plugin/workspace'; import { MessageRegistryExt } from '../../plugin/message-registry'; import { EnvNodeExtImpl } from '../../plugin/node/env-node-ext'; import { ClipboardExt } from '../../plugin/clipboard-ext'; +import { loadManifest } from './plugin-manifest-loader'; /** * Handle the RPC calls. @@ -128,40 +129,46 @@ export class PluginHostRPC { console.error(e); } }, - init(raw: PluginMetadata[]): [Plugin[], Plugin[]] { + async init(raw: PluginMetadata[]): Promise<[Plugin[], Plugin[]]> { console.log('PLUGIN_HOST(' + process.pid + '): PluginManagerExtImpl/init()'); const result: Plugin[] = []; const foreign: Plugin[] = []; for (const plg of raw) { - const pluginModel = plg.model; - const pluginLifecycle = plg.lifecycle; - - if (pluginModel.entryPoint!.frontend) { - foreign.push({ - pluginPath: pluginModel.entryPoint.frontend!, - pluginFolder: plg.source.packagePath, - model: pluginModel, - lifecycle: pluginLifecycle, - rawModel: plg.source - }); - } else { - let backendInitPath = pluginLifecycle.backendInitPath; - // if no init path, try to init as regular Theia plugin - if (!backendInitPath) { - backendInitPath = __dirname + '/scanners/backend-init-theia.js'; - } + try { + const pluginModel = plg.model; + const pluginLifecycle = plg.lifecycle; + + const rawModel = await loadManifest(pluginModel.packagePath); + rawModel.packagePath = pluginModel.packagePath; + if (pluginModel.entryPoint!.frontend) { + foreign.push({ + pluginPath: pluginModel.entryPoint.frontend!, + pluginFolder: pluginModel.packagePath, + model: pluginModel, + lifecycle: pluginLifecycle, + rawModel + }); + } else { + let backendInitPath = pluginLifecycle.backendInitPath; + // if no init path, try to init as regular Theia plugin + if (!backendInitPath) { + backendInitPath = __dirname + '/scanners/backend-init-theia.js'; + } - const plugin: Plugin = { - pluginPath: pluginModel.entryPoint.backend!, - pluginFolder: plg.source.packagePath, - model: pluginModel, - lifecycle: pluginLifecycle, - rawModel: plg.source - }; + const plugin: Plugin = { + pluginPath: pluginModel.entryPoint.backend!, + pluginFolder: pluginModel.packagePath, + model: pluginModel, + lifecycle: pluginLifecycle, + rawModel + }; - self.initContext(backendInitPath, plugin); + self.initContext(backendInitPath, plugin); - result.push(plugin); + result.push(plugin); + } + } catch (e) { + console.error(`Failed to initialize ${plg.model.id} plugin.`, e); } } return [result, foreign]; diff --git a/packages/plugin-ext/src/hosted/node/plugin-manifest-loader.ts b/packages/plugin-ext/src/hosted/node/plugin-manifest-loader.ts new file mode 100644 index 0000000000000..6b64f6f8b0ae2 --- /dev/null +++ b/packages/plugin-ext/src/hosted/node/plugin-manifest-loader.ts @@ -0,0 +1,71 @@ +/******************************************************************************** + * Copyright (C) 2018 Red Hat, Inc. and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +// tslint:disable:no-any + +import * as path from 'path'; +import * as fs from 'fs-extra'; + +const NLS_REGEX = /^%([\w\d.-]+)%$/i; + +export async function loadManifest(pluginPath: string): Promise { + const [manifest, translations] = await Promise.all([ + fs.readJson(path.join(pluginPath, 'package.json')), + loadTranslations(pluginPath) + ]); + return manifest && translations && Object.keys(translations).length ? + localize(manifest, translations) : + manifest; +} + +async function loadTranslations(pluginPath: string): Promise { + try { + return await fs.readJson(path.join(pluginPath, 'package.nls.json')); + } catch (e) { + if (e.code !== 'ENOENT') { + throw e; + } + return {}; + } +} + +function localize(value: any, translations: { + [key: string]: string +}): any { + if (typeof value === 'string') { + const match = NLS_REGEX.exec(value); + return match && translations[match[1]] || value; + } + if (Array.isArray(value)) { + const result = []; + for (const item of value) { + result.push(localize(item, translations)); + } + return result; + } + if (value === null) { + return value; + } + if (typeof value === 'object') { + const result: { [key: string]: any } = {}; + // tslint:disable-next-line:forin + for (const propertyName in value) { + result[propertyName] = localize(value[propertyName], translations); + } + return result; + } + return value; +} diff --git a/packages/plugin-ext/src/hosted/node/plugin-reader.ts b/packages/plugin-ext/src/hosted/node/plugin-reader.ts index d1611818605da..fb2bcac4c1b1d 100644 --- a/packages/plugin-ext/src/hosted/node/plugin-reader.ts +++ b/packages/plugin-ext/src/hosted/node/plugin-reader.ts @@ -14,17 +14,15 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -// tslint:disable:no-any - import * as path from 'path'; -import * as fs from 'fs-extra'; import * as express from 'express'; import * as escape_html from 'escape-html'; import { ILogger } from '@theia/core'; import { inject, injectable, optional, multiInject } from 'inversify'; import { BackendApplicationContribution } from '@theia/core/lib/node/backend-application'; -import { PluginMetadata, getPluginId, MetadataProcessor } from '../../common/plugin-protocol'; +import { PluginMetadata, getPluginId, MetadataProcessor, PluginModelOptions } from '../../common/plugin-protocol'; import { MetadataScanner } from './metadata-scanner'; +import { loadManifest } from './plugin-manifest-loader'; @injectable() export class HostedPluginReader implements BackendApplicationContribution { @@ -77,33 +75,33 @@ export class HostedPluginReader implements BackendApplicationContribution { res.status(404).send(`The plugin with id '${escape_html(pluginId)}' does not exist.`); } - async getPluginMetadata(pluginPath: string): Promise { - return this.doGetPluginMetadata(pluginPath); + async getPluginMetadata(pluginPath: string, options: PluginModelOptions): Promise { + return this.doGetPluginMetadata(pluginPath, options); } /** * MUST never throw to isolate plugin deployment */ - async doGetPluginMetadata(pluginPath: string | undefined): Promise { + async doGetPluginMetadata(pluginPath: string | undefined, options: PluginModelOptions): Promise { try { if (!pluginPath) { return undefined; } pluginPath = path.normalize(pluginPath + '/'); - return await this.loadPluginMetadata(pluginPath); + return await this.loadPluginMetadata(pluginPath, options); } catch (e) { this.logger.error(`Failed to load plugin metadata from "${pluginPath}"`, e); return undefined; } } - protected async loadPluginMetadata(pluginPath: string): Promise { - const manifest = await this.loadManifest(pluginPath); + protected async loadPluginMetadata(pluginPath: string, options: PluginModelOptions): Promise { + const manifest = await loadManifest(pluginPath); if (!manifest) { return undefined; } manifest.packagePath = pluginPath; - const pluginMetadata = this.scanner.getPluginMetadata(manifest); + const pluginMetadata = this.scanner.getPluginMetadata(manifest, options); if (pluginMetadata.model.entryPoint.backend) { pluginMetadata.model.entryPoint.backend = path.resolve(pluginPath, pluginMetadata.model.entryPoint.backend); } @@ -119,55 +117,4 @@ export class HostedPluginReader implements BackendApplicationContribution { return pluginMetadata; } - protected async loadManifest(pluginPath: string): Promise { - const [manifest, translations] = await Promise.all([ - fs.readJson(path.join(pluginPath, 'package.json')), - this.loadTranslations(pluginPath) - ]); - return manifest && translations && Object.keys(translations).length ? - this.localize(manifest, translations) : - manifest; - } - - protected async loadTranslations(pluginPath: string): Promise { - try { - return await fs.readJson(path.join(pluginPath, 'package.nls.json')); - } catch (e) { - if (e.code !== 'ENOENT') { - throw e; - } - return {}; - } - } - - protected localize(value: any, translations: { - [key: string]: string - }): any { - if (typeof value === 'string') { - const match = HostedPluginReader.NLS_REGEX.exec(value); - return match && translations[match[1]] || value; - } - if (Array.isArray(value)) { - const result = []; - for (const item of value) { - result.push(this.localize(item, translations)); - } - return result; - } - if (value === null) { - return value; - } - if (typeof value === 'object') { - const result: { [key: string]: any } = {}; - // tslint:disable-next-line:forin - for (const propertyName in value) { - result[propertyName] = this.localize(value[propertyName], translations); - } - return result; - } - return value; - } - - static NLS_REGEX = /^%([\w\d.-]+)%$/i; - } diff --git a/packages/plugin-ext/src/hosted/node/plugin-service.ts b/packages/plugin-ext/src/hosted/node/plugin-service.ts index e39cdfac9f9c0..8dd401d29507e 100644 --- a/packages/plugin-ext/src/hosted/node/plugin-service.ts +++ b/packages/plugin-ext/src/hosted/node/plugin-service.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { injectable, inject, named, postConstruct } from 'inversify'; -import { HostedPluginServer, HostedPluginClient, PluginMetadata, PluginDeployer } from '../../common/plugin-protocol'; +import { HostedPluginServer, HostedPluginClient, PluginMetadata, PluginDeployer, GetDeployedPluginsParams } from '../../common/plugin-protocol'; import { HostedPluginSupport } from './hosted-plugin'; import { ILogger, Disposable } from '@theia/core'; import { ContributionProvider } from '@theia/core'; @@ -63,28 +63,47 @@ export class HostedPluginServerImpl implements HostedPluginServer { this.hostedPlugin.setClient(client); } - getDeployedFrontendMetadata(): Promise { - return this.deployerHandler.getDeployedFrontendMetadata(); - } - - async getDeployedMetadata(): Promise { + async getDeployedPluginIds(): Promise { const backendMetadata = await this.deployerHandler.getDeployedBackendMetadata(); if (backendMetadata.length > 0) { this.hostedPlugin.runPluginServer(); } - const allMetadata: PluginMetadata[] = []; - allMetadata.push(...await this.deployerHandler.getDeployedFrontendMetadata()); - allMetadata.push(...backendMetadata); - - // ask remote as well - const extraBackendPluginsMetadata = await this.hostedPlugin.getExtraPluginMetadata(); - allMetadata.push(...extraBackendPluginsMetadata); - - return allMetadata; + const plugins = new Set(); + for (const plugin of await this.deployerHandler.getDeployedFrontendMetadata()) { + plugins.add(plugin.model.id); + } + for (const plugin of backendMetadata) { + plugins.add(plugin.model.id); + } + const extraPluginMetadata = await this.hostedPlugin.getExtraPluginMetadata(); + for (const plugin of extraPluginMetadata) { + plugins.add(plugin.model.id); + } + return [...plugins.values()]; } - getDeployedBackendMetadata(): Promise { - return Promise.resolve(this.deployerHandler.getDeployedBackendMetadata()); + async getDeployedPlugins({ pluginIds }: GetDeployedPluginsParams): Promise { + if (!pluginIds.length) { + return []; + } + const plugins = []; + let extraPluginMetadata: Map | undefined; + for (const pluginId of pluginIds) { + let plugin = this.deployerHandler.getDeployedPluginMetadata(pluginId); + if (!plugin) { + if (!extraPluginMetadata) { + extraPluginMetadata = new Map(); + for (const metadata of await this.hostedPlugin.getExtraPluginMetadata()) { + extraPluginMetadata.set(metadata.model.id, metadata); + } + } + plugin = extraPluginMetadata.get(pluginId); + } + if (plugin) { + plugins.push(plugin); + } + } + return plugins; } onMessage(message: string): Promise { diff --git a/packages/plugin-ext/src/hosted/node/scanners/scanner-theia.ts b/packages/plugin-ext/src/hosted/node/scanners/scanner-theia.ts index bc5679e88716a..286e0a6b1b77f 100644 --- a/packages/plugin-ext/src/hosted/node/scanners/scanner-theia.ts +++ b/packages/plugin-ext/src/hosted/node/scanners/scanner-theia.ts @@ -43,7 +43,8 @@ import { SnippetContribution, PluginPackageCommand, PluginCommand, - IconUrl + IconUrl, + PluginModelOptions } from '../../../common/plugin-protocol'; import * as fs from 'fs'; import * as path from 'path'; @@ -82,8 +83,9 @@ export class TheiaPluginScanner implements PluginScanner { return this._apiType; } - getModel(plugin: PluginPackage): PluginModel { + getModel(plugin: PluginPackage, options: PluginModelOptions): PluginModel { const result: PluginModel = { + packagePath: plugin.packagePath, // see id definition: https://github.com/microsoft/vscode/blob/15916055fe0cb9411a5f36119b3b012458fe0a1d/src/vs/platform/extensions/common/extensions.ts#L167-L169 id: `${plugin.publisher.toLowerCase()}.${plugin.name.toLowerCase()}`, name: plugin.name, @@ -98,10 +100,15 @@ export class TheiaPluginScanner implements PluginScanner { entryPoint: { frontend: plugin.theiaPlugin!.frontend, backend: plugin.theiaPlugin!.backend - }, - extensionDependencies: plugin.extensionDependencies || [] + } }; - result.contributes = this.readContributions(plugin); + if (options.contributions) { + result.activationEvents = plugin.activationEvents; + result.contributes = this.readContributions(plugin); + } + if (options.dependencies) { + // skip it since there is no way to load transitive dependencies for Theia plugins yet + } return result; } diff --git a/packages/plugin-ext/src/main/browser/debug/debug-main.ts b/packages/plugin-ext/src/main/browser/debug/debug-main.ts index 8c33989e75272..4967d88c9b9de 100644 --- a/packages/plugin-ext/src/main/browser/debug/debug-main.ts +++ b/packages/plugin-ext/src/main/browser/debug/debug-main.ts @@ -47,7 +47,6 @@ import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposa import { PluginDebugSessionFactory } from './plugin-debug-session-factory'; import { PluginWebSocketChannel } from '../../../common/connection'; import { PluginDebugAdapterContributionRegistrator, PluginDebugService } from './plugin-debug-service'; -import { DebugSchemaUpdater } from '@theia/debug/lib/browser/debug-schema-updater'; import { FileSystem } from '@theia/filesystem/lib/common'; import { HostedPluginSupport } from '../../../hosted/browser/hosted-plugin'; @@ -66,7 +65,6 @@ export class DebugMainImpl implements DebugMain, Disposable { private readonly debugPreferences: DebugPreferences; private readonly sessionContributionRegistrator: PluginDebugSessionContributionRegistrator; private readonly adapterContributionRegistrator: PluginDebugAdapterContributionRegistrator; - private readonly debugSchemaUpdater: DebugSchemaUpdater; private readonly fileSystem: FileSystem; private readonly pluginService: HostedPluginSupport; @@ -87,7 +85,6 @@ export class DebugMainImpl implements DebugMain, Disposable { this.debugPreferences = container.get(DebugPreferences); this.adapterContributionRegistrator = container.get(PluginDebugService); this.sessionContributionRegistrator = container.get(PluginDebugSessionContributionRegistry); - this.debugSchemaUpdater = container.get(DebugSchemaUpdater); this.fileSystem = container.get(FileSystem); this.pluginService = container.get(HostedPluginSupport); @@ -146,7 +143,6 @@ export class DebugMainImpl implements DebugMain, Disposable { ); const toDispose = new DisposableCollection( - Disposable.create(() => this.debugSchemaUpdater.update()), Disposable.create(() => this.debuggerContributions.delete(debugType)) ); this.debuggerContributions.set(debugType, toDispose); @@ -160,8 +156,6 @@ export class DebugMainImpl implements DebugMain, Disposable { }) ]); this.toDispose.push(Disposable.create(() => this.$unregisterDebuggerConfiguration(debugType))); - - this.debugSchemaUpdater.update(); } async $unregisterDebuggerConfiguration(debugType: string): Promise { diff --git a/packages/plugin-ext/src/main/browser/debug/plugin-debug-adapter-contribution.ts b/packages/plugin-ext/src/main/browser/debug/plugin-debug-adapter-contribution.ts index d37d41893db80..98fbe0ce8c2ea 100644 --- a/packages/plugin-ext/src/main/browser/debug/plugin-debug-adapter-contribution.ts +++ b/packages/plugin-ext/src/main/browser/debug/plugin-debug-adapter-contribution.ts @@ -16,7 +16,6 @@ import { DebugExt, } from '../../../common/plugin-api-rpc'; import { DebugConfiguration } from '@theia/debug/lib/common/debug-configuration'; -import { IJSONSchemaSnippet, IJSONSchema } from '@theia/core/lib/common/json-schema'; import { MaybePromise } from '@theia/core/lib/common/types'; import { DebuggerDescription } from '@theia/debug/lib/common/debug-service'; import { HostedPluginSupport } from '../../../hosted/browser/hosted-plugin'; @@ -38,18 +37,6 @@ export class PluginDebugAdapterContribution { return this.description.label; } - get languages(): MaybePromise { - return this.debugExt.$getSupportedLanguages(this.type); - } - - async getSchemaAttributes(): Promise { - return this.debugExt.$getSchemaAttributes(this.type); - } - - async getConfigurationSnippets(): Promise { - return this.debugExt.$getConfigurationSnippets(this.type); - } - async provideDebugConfigurations(workspaceFolderUri: string | undefined): Promise { return this.debugExt.$provideDebugConfigurations(this.type, workspaceFolderUri); } diff --git a/packages/plugin-ext/src/main/browser/debug/plugin-debug-service.ts b/packages/plugin-ext/src/main/browser/debug/plugin-debug-service.ts index 243723cb842d1..6cabe2a706d6c 100644 --- a/packages/plugin-ext/src/main/browser/debug/plugin-debug-service.ts +++ b/packages/plugin-ext/src/main/browser/debug/plugin-debug-service.ts @@ -22,6 +22,7 @@ import { PluginDebugAdapterContribution } from './plugin-debug-adapter-contribut import { injectable, inject, postConstruct } from 'inversify'; import { WebSocketConnectionProvider } from '@theia/core/lib/browser/messaging/ws-connection-provider'; import { WorkspaceService } from '@theia/workspace/lib/browser'; +import { DebuggerContribution } from '../../../common/plugin-protocol'; /** * Debug adapter contribution registrator. @@ -45,6 +46,8 @@ export interface PluginDebugAdapterContributionRegistrator { */ @injectable() export class PluginDebugService implements DebugService, PluginDebugAdapterContributionRegistrator { + + protected readonly debuggers: DebuggerContribution[] = []; protected readonly contributors = new Map(); protected readonly toDispose = new DisposableCollection(); @@ -88,8 +91,14 @@ export class PluginDebugService implements DebugService, PluginDebugAdapterContr } async debugTypes(): Promise { - const debugTypes = await this.delegated.debugTypes(); - return debugTypes.concat(Array.from(this.contributors.keys())); + const debugTypes = new Set(await this.delegated.debugTypes()); + for (const contribution of this.debuggers) { + debugTypes.add(contribution.type); + } + for (const debugType of this.contributors.keys()) { + debugTypes.add(debugType); + } + return [...debugTypes]; } async provideDebugConfigurations(debugType: string, workspaceFolderUri: string | undefined): Promise { @@ -123,14 +132,24 @@ export class PluginDebugService implements DebugService, PluginDebugAdapterContr return this.delegated.resolveDebugConfiguration(resolved, workspaceFolderUri); } + registerDebugger(contribution: DebuggerContribution): Disposable { + this.debuggers.push(contribution); + return Disposable.create(() => { + const index = this.debuggers.indexOf(contribution); + if (index !== -1) { + this.debuggers.splice(index, 1); + } + }); + } + async getDebuggersForLanguage(language: string): Promise { const debuggers = await this.delegated.getDebuggersForLanguage(language); - for (const contributor of this.contributors.values()) { - const languages = await contributor.languages; + for (const contributor of this.debuggers) { + const languages = contributor.languages; if (languages && languages.indexOf(language) !== -1) { - const { type } = contributor; - debuggers.push({ type, label: await contributor.label || type }); + const { label, type } = contributor; + debuggers.push({ type, label: label || type }); } } @@ -138,20 +157,22 @@ export class PluginDebugService implements DebugService, PluginDebugAdapterContr } async getSchemaAttributes(debugType: string): Promise { - const contributor = this.contributors.get(debugType); - if (contributor) { - return contributor.getSchemaAttributes && contributor.getSchemaAttributes() || []; - } else { - return this.delegated.getSchemaAttributes(debugType); + let schemas = await this.delegated.getSchemaAttributes(debugType); + for (const contribution of this.debuggers) { + if (contribution.configurationAttributes && + (contribution.type === debugType || contribution.type === '*' || debugType === '*')) { + schemas = schemas.concat(contribution.configurationAttributes); + } } + return schemas; } async getConfigurationSnippets(): Promise { let snippets = await this.delegated.getConfigurationSnippets(); - for (const contributor of this.contributors.values()) { - if (contributor.getConfigurationSnippets) { - snippets = snippets.concat(await contributor.getConfigurationSnippets()); + for (const contribution of this.debuggers) { + if (contribution.configurationSnippets) { + snippets = snippets.concat(contribution.configurationSnippets); } } diff --git a/packages/plugin-ext/src/main/browser/plugin-contribution-handler.ts b/packages/plugin-ext/src/main/browser/plugin-contribution-handler.ts index 8d35109ac2ca7..a353aa777e318 100644 --- a/packages/plugin-ext/src/main/browser/plugin-contribution-handler.ts +++ b/packages/plugin-ext/src/main/browser/plugin-contribution-handler.ts @@ -29,6 +29,8 @@ import { CommandRegistry, Command, CommandHandler } from '@theia/core/lib/common import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable'; import { Emitter } from '@theia/core/lib/common/event'; import { TaskDefinitionRegistry, ProblemMatcherRegistry, ProblemPatternRegistry } from '@theia/task/lib/browser'; +import { PluginDebugService } from './debug/plugin-debug-service'; +import { DebugSchemaUpdater } from '@theia/debug/lib/browser/debug-schema-updater'; @injectable() export class PluginContributionHandler { @@ -71,6 +73,12 @@ export class PluginContributionHandler { @inject(ProblemPatternRegistry) protected readonly problemPatternRegistry: ProblemPatternRegistry; + @inject(PluginDebugService) + protected readonly debugService: PluginDebugService; + + @inject(DebugSchemaUpdater) + protected readonly debugSchema: DebugSchemaUpdater; + protected readonly commandHandlers = new Map(); protected readonly onDidRegisterCommandHandlerEmitter = new Emitter(); @@ -81,7 +89,7 @@ export class PluginContributionHandler { * @throws never, loading of each contribution should handle errors * in order to avoid preventing loading of other contibutions or extensions */ - handleContributions(plugin: PluginMetadata): Disposable { + handleContributions(clientId: string, plugin: PluginMetadata): Disposable { const contributions = plugin.model.contributes; if (!contributions) { return Disposable.NULL; @@ -91,7 +99,7 @@ export class PluginContributionHandler { try { toDispose.push(contribute()); } catch (e) { - console.error(`[${plugin.model.id}]: Failed to load '${id}' contribution.`, e); + console.error(`[${clientId}][${plugin.model.id}]: Failed to load '${id}' contribution.`, e); } }; @@ -243,6 +251,16 @@ export class PluginContributionHandler { } } + if (contributions.debuggers && contributions.debuggers.length) { + toDispose.push(Disposable.create(() => this.debugSchema.update())); + for (const contribution of contributions.debuggers) { + pushContribution(`debuggers.${contribution.type}`, + () => this.debugService.registerDebugger(contribution) + ); + } + this.debugSchema.update(); + } + return toDispose; } diff --git a/packages/plugin-ext/src/main/node/plugin-deployer-impl.ts b/packages/plugin-ext/src/main/node/plugin-deployer-impl.ts index 8f3613eed317c..189119c84c4cf 100644 --- a/packages/plugin-ext/src/main/node/plugin-deployer-impl.ts +++ b/packages/plugin-ext/src/main/node/plugin-deployer-impl.ts @@ -126,7 +126,9 @@ export class PluginDeployerImpl implements PluginDeployer { await this.applyFileHandlers(pluginDeployerEntries); await this.applyDirectoryFileHandlers(pluginDeployerEntries); for (const deployerEntry of pluginDeployerEntries) { - const metadata = await this.pluginDeployerHandler.getPluginMetadata(deployerEntry); + const metadata = await this.pluginDeployerHandler.getPluginMetadata(deployerEntry, { + dependencies: true + }); if (metadata && !pluginsToDeploy.has(metadata.model.id)) { pluginsToDeploy.set(metadata.model.id, deployerEntry); chunk.push(metadata); @@ -137,7 +139,7 @@ export class PluginDeployerImpl implements PluginDeployer { } } for (const metadata of chunk) { - const extensionDependencies = metadata.source.extensionDependencies; + const extensionDependencies = metadata.model.rawExtensionDependencies; const deployableExtensionDependencies = metadata.model.extensionDependencies; if (extensionDependencies && deployableExtensionDependencies) { for (let dependencyIndex = 0; dependencyIndex < extensionDependencies.length; dependencyIndex++) { diff --git a/packages/plugin-ext/src/plugin/node/debug/debug.ts b/packages/plugin-ext/src/plugin/node/debug/debug.ts index 821958875bf44..e6787709b6ca1 100644 --- a/packages/plugin-ext/src/plugin/node/debug/debug.ts +++ b/packages/plugin-ext/src/plugin/node/debug/debug.ts @@ -14,15 +14,14 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { Emitter } from '@theia/core/lib/common/event'; -import { IJSONSchema, IJSONSchemaSnippet } from '@theia/core/lib/common/json-schema'; import { Path } from '@theia/core/lib/common/path'; import { CommunicationProvider } from '@theia/debug/lib/common/debug-model'; import * as theia from '@theia/plugin'; import URI from 'vscode-uri'; import { Breakpoint } from '../../../common/plugin-api-rpc-model'; import { DebugExt, DebugMain, PLUGIN_RPC_CONTEXT as Ext, TerminalOptionsExt } from '../../../common/plugin-api-rpc'; +import { PluginPackageDebuggersContribution } from '../../../common/plugin-protocol'; import { RPCProtocol } from '../../../common/rpc-protocol'; -import { DebuggerContribution } from '../../../common'; import { PluginWebSocketChannel } from '../../../common/connection'; import { CommandRegistryImpl } from '../../command-registry'; import { ConnectionExtImpl } from '../../connection-ext'; @@ -46,7 +45,11 @@ export class DebugExtImpl implements DebugExt { // providers by type private configurationProviders = new Map>(); - private debuggersContributions = new Map(); + /** + * Only use internally, don't send it to the frontend. It's expensive! + * It's already there as a part of the plugin metadata. + */ + private debuggersContributions = new Map(); private descriptorFactories = new Map(); private trackerFactories: [string, theia.DebugAdapterTrackerFactory][] = []; private contributionPaths = new Map(); @@ -87,8 +90,8 @@ export class DebugExtImpl implements DebugExt { * @param pluginFolder plugin folder path * @param contributions available debuggers contributions */ - registerDebuggersContributions(pluginFolder: string, contributions: DebuggerContribution[]): void { - contributions.forEach((contribution: DebuggerContribution) => { + registerDebuggersContributions(pluginFolder: string, contributions: PluginPackageDebuggersContribution[]): void { + contributions.forEach(contribution => { this.contributionPaths.set(contribution.type, pluginFolder); this.debuggersContributions.set(contribution.type, contribution); this.proxy.$registerDebuggerContribution({ @@ -229,21 +232,6 @@ export class DebugExtImpl implements DebugExt { } } - async $getSupportedLanguages(debugType: string): Promise { - const contribution = this.debuggersContributions.get(debugType); - return contribution && contribution.languages || []; - } - - async $getSchemaAttributes(debugType: string): Promise { - const contribution = this.debuggersContributions.get(debugType); - return contribution && contribution.configurationAttributes || []; - } - - async $getConfigurationSnippets(debugType: string): Promise { - const contribution = this.debuggersContributions.get(debugType); - return contribution && contribution.configurationSnippets || []; - } - async $getTerminalCreationOptions(debugType: string): Promise { return this.doGetTerminalCreationOptions(debugType); } diff --git a/packages/plugin-ext/src/plugin/node/debug/plugin-debug-adapter-executable-resolver.ts b/packages/plugin-ext/src/plugin/node/debug/plugin-debug-adapter-executable-resolver.ts index b3ae5312466e8..a03cc830aaebb 100644 --- a/packages/plugin-ext/src/plugin/node/debug/plugin-debug-adapter-executable-resolver.ts +++ b/packages/plugin-ext/src/plugin/node/debug/plugin-debug-adapter-executable-resolver.ts @@ -16,13 +16,14 @@ import * as path from 'path'; import * as theia from '@theia/plugin'; -import { PlatformSpecificAdapterContribution, DebuggerContribution } from '../../../common'; +import { PlatformSpecificAdapterContribution, PluginPackageDebuggersContribution } from '../../../common'; import { isWindows, isOSX } from '@theia/core/lib/common/os'; /** * Resolves [DebugAdapterExecutable](#DebugAdapterExecutable) based on contribution. */ -export async function resolveDebugAdapterExecutable(pluginPath: string, debuggerContribution: DebuggerContribution): Promise { +export async function resolveDebugAdapterExecutable( + pluginPath: string, debuggerContribution: PluginPackageDebuggersContribution): Promise { const info = toPlatformInfo(debuggerContribution); let program = (info && info.program || debuggerContribution.program); if (!program) { @@ -43,7 +44,7 @@ export async function resolveDebugAdapterExecutable(pluginPath: string, debugger }; } -function toPlatformInfo(executable: DebuggerContribution): PlatformSpecificAdapterContribution | undefined { +function toPlatformInfo(executable: PluginPackageDebuggersContribution): PlatformSpecificAdapterContribution | undefined { if (isWindows && !process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432')) { return executable.winx86 || executable.win || executable.windows; } diff --git a/packages/plugin-ext/src/plugin/plugin-context.ts b/packages/plugin-ext/src/plugin/plugin-context.ts index fb7d461b269ba..2535a00eb9cff 100644 --- a/packages/plugin-ext/src/plugin/plugin-context.ts +++ b/packages/plugin-ext/src/plugin/plugin-context.ts @@ -641,7 +641,7 @@ export function createAPIFactory( } }; - const debuggersContributions = plugin.model.contributes && plugin.model.contributes.debuggers || []; + const debuggersContributions = plugin.rawModel.contributes && plugin.rawModel.contributes.debuggers || []; debugExt.assistedInject(connectionExt, commandRegistry); debugExt.registerDebuggersContributions(plugin.pluginFolder, debuggersContributions); const debug: typeof theia.debug = { diff --git a/packages/plugin-ext/src/plugin/plugin-manager.ts b/packages/plugin-ext/src/plugin/plugin-manager.ts index 2f4b432282235..32ca7307bf4d4 100644 --- a/packages/plugin-ext/src/plugin/plugin-manager.ts +++ b/packages/plugin-ext/src/plugin/plugin-manager.ts @@ -20,11 +20,12 @@ import { MainMessageType, MessageRegistryMain, PluginManagerExt, - PluginInitData, PluginManager, Plugin, PluginAPI, - ConfigStorage + ConfigStorage, + PluginManagerInitializeParams, + PluginManagerStartParams } from '../common/plugin-api-rpc'; import { PluginMetadata } from '../common/plugin-protocol'; import * as theia from '@theia/plugin'; @@ -43,7 +44,7 @@ export interface PluginHost { // tslint:disable-next-line:no-any loadPlugin(plugin: Plugin): any; - init(data: PluginMetadata[]): [Plugin[], Plugin[]]; + init(data: PluginMetadata[]): Promise<[Plugin[], Plugin[]]> | [Plugin[], Plugin[]]; initExtApi(extApi: ExtPluginApi[]): void; @@ -136,46 +137,46 @@ export class PluginManagerExtImpl implements PluginManagerExt, PluginManager { } } - async $init(pluginInit: PluginInitData, configStorage: ConfigStorage): Promise { + async $initiliaze(params: PluginManagerInitializeParams): Promise { this.storageProxy = this.rpc.set( MAIN_RPC_CONTEXT.STORAGE_EXT, new KeyValueStorageProxy(this.rpc.getProxy(PLUGIN_RPC_CONTEXT.STORAGE_MAIN), - pluginInit.globalState, - pluginInit.workspaceState) + params.globalState, + params.workspaceState) ); - // init query parameters - this.envExt.setQueryParameters(pluginInit.env.queryParams); - this.envExt.setLanguage(pluginInit.env.language); + this.envExt.setQueryParameters(params.env.queryParams); + this.envExt.setLanguage(params.env.language); - this.preferencesManager.init(pluginInit.preferences); + this.preferencesManager.init(params.preferences); - if (pluginInit.extApi) { - this.host.initExtApi(pluginInit.extApi); + if (params.extApi) { + this.host.initExtApi(params.extApi); } + } - const [plugins, foreignPlugins] = this.host.init(pluginInit.plugins); + async $start(params: PluginManagerStartParams): Promise { + const [plugins, foreignPlugins] = await this.host.init(params.plugins); // add foreign plugins for (const plugin of foreignPlugins) { - this.registerPlugin(plugin, configStorage); + this.registerPlugin(plugin, params.configStorage); } // add own plugins, before initialization for (const plugin of plugins) { - this.registerPlugin(plugin, configStorage); + this.registerPlugin(plugin, params.configStorage); } // run eager plugins await this.$activateByEvent('*'); - for (const activationEvent of pluginInit.activationEvents) { + for (const activationEvent of params.activationEvents) { await this.$activateByEvent(activationEvent); } if (this.host.loadTests) { return this.host.loadTests(); } - this.fireOnDidChange(); - return Promise.resolve(); + this.fireOnDidChange(); } protected registerPlugin(plugin: Plugin, configStorage: ConfigStorage): void { @@ -248,11 +249,10 @@ export class PluginManagerExtImpl implements PluginManagerExt, PluginManager { return loading; } - $updateStoragePath(path: string | undefined): PromiseLike { + async $updateStoragePath(path: string | undefined): Promise { this.pluginContextsMap.forEach((pluginContext: theia.PluginContext, pluginId: string) => { pluginContext.storagePath = path ? join(path, pluginId) : undefined; }); - return Promise.resolve(); } async $activateByEvent(activationEvent: string): Promise { @@ -271,7 +271,7 @@ export class PluginManagerExtImpl implements PluginManagerExt, PluginManager { const subscriptions: theia.Disposable[] = []; const asAbsolutePath = (relativePath: string): string => join(plugin.pluginFolder, relativePath); const logPath = join(configStorage.hostLogPath, plugin.model.id); // todo check format - const storagePath = join(configStorage.hostStoragePath, plugin.model.id); + const storagePath = join(configStorage.hostStoragePath || '', plugin.model.id); const pluginContext: theia.PluginContext = { extensionPath: plugin.pluginFolder, globalState: new Memento(plugin.model.id, true, this.storageProxy), diff --git a/yarn.lock b/yarn.lock index 8e090dde90702..7a8e33f9ec63f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1584,6 +1584,11 @@ async-each@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d" +async-limiter@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" + integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== + async-limiter@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8" @@ -11406,12 +11411,20 @@ write-pkg@^3.1.0: sort-keys "^2.0.0" write-json-file "^2.2.0" -ws@^5.2.0, ws@^5.2.2: +ws@^5.2.0: version "5.2.2" resolved "https://registry.yarnpkg.com/ws/-/ws-5.2.2.tgz#dffef14866b8e8dc9133582514d1befaf96e980f" + integrity sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA== dependencies: async-limiter "~1.0.0" +ws@^7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.1.2.tgz#c672d1629de8bb27a9699eb599be47aeeedd8f73" + integrity sha512-gftXq3XI81cJCgkUiAVixA0raD9IVmXqsylCrjRygw4+UOOGzPoxnQ6r/CnVL9i+mDncJo94tSkyrtuuQVBmrg== + dependencies: + async-limiter "^1.0.0" + xdg-basedir@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-2.0.0.tgz#edbc903cc385fc04523d966a335504b5504d1bd2"