Skip to content
This repository has been archived by the owner on Jun 20, 2018. It is now read-only.

Theia plugin metadata #1

Merged
merged 9 commits into from
Apr 20, 2018
9 changes: 5 additions & 4 deletions packages/plugin/src/api/plugin-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,18 @@
*/
import { createProxyIdentifier, ProxyIdentifier } from './rpc-protocol';
import * as theia from '@theia/plugin';
import { PluginLifecycle, PluginModel } from '../common/plugin-protocol';

export interface HostedPluginManagerExt {
$loadPlugin(ext: Plugin): void;
$initialize(contextPath: string): void;
$loadPlugin(plugin: Plugin): void;
$stopPlugin(): PromiseLike<void>;
}

export interface Plugin {
name: string;
publisher: string;
version: string;
pluginPath: string;
model: PluginModel;
lifecycle: PluginLifecycle;
}

export interface CommandRegistryMain {
Expand Down
48 changes: 27 additions & 21 deletions packages/plugin/src/browser/hosted-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*/
import { injectable, inject, interfaces } from 'inversify';
import { HostedPluginServer, Plugin } from '../common/plugin-protocol';
import { HostedPluginServer, PluginModel, PluginLifecycle } from '../common/plugin-protocol';
import { PluginWorker } from './plugin-worker';
import { setUpPluginApi } from './main-context';
import { MAIN_RPC_CONTEXT } from '../api/plugin-api';
import { MAIN_RPC_CONTEXT, Plugin } from '../api/plugin-api';
import { HostedPluginWatcher } from './hosted-plugin-watcher';
import { RPCProtocol, RPCProtocolImpl } from '../api/rpc-protocol';
@injectable()
Expand All @@ -20,36 +20,42 @@ export class HostedPluginSupport {
}

checkAndLoadPlugin(container: interfaces.Container): void {
this.server.getHostedPlugin().then(plugin => {
if (plugin) {
this.loadPlugin(plugin, container);
this.server.getHostedPlugin().then((pluginMedata: any) => {
if (pluginMedata) {
this.loadPlugin(pluginMedata.model, pluginMedata.lifecycle, container);
}
});
}

private loadPlugin(plugin: Plugin, container: interfaces.Container): void {
if (plugin.theiaPlugin!.worker) {
console.log(`Loading hosted plugin: ${plugin.name}`);
private loadPlugin(pluginModel: PluginModel, pluginLifecycle: PluginLifecycle, container: interfaces.Container): void {
if (pluginModel.entryPoint!.frontend) {
console.log(`Loading hosted plugin: ${pluginModel.name}`);
this.worker = new PluginWorker();
setUpPluginApi(this.worker.rpc, container);
const hostedExtManager = this.worker.rpc.getProxy(MAIN_RPC_CONTEXT.HOSTED_PLUGIN_MANAGER_EXT);
hostedExtManager.$loadPlugin({
pluginPath: plugin.theiaPlugin.worker!,
name: plugin.name,
publisher: plugin.publisher,
version: plugin.version
});
const plugin: Plugin = {
pluginPath: pluginModel.entryPoint.frontend!,
model: pluginModel,
lifecycle: pluginLifecycle
};
if (pluginLifecycle.frontendInitPath) {
hostedExtManager.$initialize(pluginLifecycle.frontendInitPath);
}
hostedExtManager.$loadPlugin(plugin);
}
if (plugin.theiaPlugin!.node) {
if (pluginModel.entryPoint!.backend) {
const rpc = this.createServerRpc();
setUpPluginApi(rpc, container);
const hostedExtManager = rpc.getProxy(MAIN_RPC_CONTEXT.HOSTED_PLUGIN_MANAGER_EXT);
hostedExtManager.$loadPlugin({
pluginPath: plugin.theiaPlugin.node!,
name: plugin.name,
publisher: plugin.publisher,
version: plugin.version
});
const plugin: Plugin = {
pluginPath: pluginModel.entryPoint.backend!,
model: pluginModel,
lifecycle: pluginLifecycle
};
if (pluginLifecycle.backendInitPath) {
hostedExtManager.$initialize(pluginLifecycle.backendInitPath);
}
hostedExtManager.$loadPlugin(plugin);
}
}

Expand Down
120 changes: 117 additions & 3 deletions packages/plugin/src/common/plugin-protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,128 @@
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*/
import { JsonRpcServer } from '@theia/core/lib/common/messaging/proxy-factory';
import { RPCProtocol } from '../api/rpc-protocol';
import { Disposable } from '../plugin/types-impl';

export const hostedServicePath = '/services/hostedPlugin';

export interface Plugin {
/**
* Plugin engine (API) type, i.e. 'theiaPlugin', 'vscode', etc.
*/
export type PluginEngine = string;

/**
* This interface describes a package.json object.
*/
export interface PluginPackage {
name: string;
publisher: string;
version: string;
engines: {
[type in PluginEngine]: string;
};
theiaPlugin?: {
frontend?: string;
backend?: string;
};
main?: string;
displayName: string;
description: string;
contributes: {};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is contributes ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's added for future use. It is an object which describes contribution points of a plugin.

}

export const PluginScanner = Symbol('PluginScanner');

/**
* This scanner process package.json object and returns plugin metadata objects.
*/
export interface PluginScanner {
/**
* The type of plugin's API (engine name)
*/
apiType: PluginEngine;

/**
* Creates plugin's model.
*
* @param {PluginPackage} plugin
* @returns {PluginModel}
*/
getModel(plugin: PluginPackage): PluginModel;

/**
* Creates plugin's lifecycle.
*
* @returns {PluginLifecycle}
*/
getLifecycle(plugin: PluginPackage): PluginLifecycle;
}

/**
* This interface describes a plugin model object, which is populated from package.json.
*/
export interface PluginModel {
name: string;
publisher: string;
version: string;
theiaPlugin: { worker?: string, node?: string };
displayName: string;
description: string;
engine: {
type: PluginEngine;
version: string;
};
entryPoint: {
frontend?: string;
backend?: string;
};
}

/**
* This interface describes a plugin lifecycle object.
*/
export interface PluginLifecycle {
startMethod: string;
stopMethod: string;
/**
* Frontend module name, frontend plugin should expose this name.
*/
frontendModuleName?: string;
/**
* Path to the script which should do some initialization before frontend plugin is loaded.
*/
frontendInitPath?: string;
/**
* Path to the script which should do some initialization before backend plugin is loaded.
*/
backendInitPath?: string;
}

/**
* The export function of initialization module of backend plugin.
*/
export interface BackendInitializationFn {
(rpc: RPCProtocol): void;
}

export interface PluginContext {
subscriptions: Disposable[];
}

export interface ExtensionContext {
subscriptions: Disposable[];
}

export interface PluginMetadata {
model: PluginModel;
lifecycle: PluginLifecycle;
}

export function getPluginId(plugin: PluginPackage | PluginModel): string {
return `${plugin.publisher}_${plugin.name}`;
}

export function buildFrontendModuleName(plugin: PluginPackage | PluginModel): string {
return `${plugin.publisher}_${plugin.name}`.replace(/\W/g, '_');
}

export const HostedPluginClient = Symbol('HostedPluginClient');
Expand All @@ -22,6 +136,6 @@ export interface HostedPluginClient {

export const HostedPluginServer = Symbol('HostedPluginServer');
export interface HostedPluginServer extends JsonRpcServer<HostedPluginClient> {
getHostedPlugin(): Promise<Plugin | undefined>;
getHostedPlugin(): Promise<PluginMetadata | undefined>;
onMessage(message: string): Promise<void>;
}
46 changes: 46 additions & 0 deletions packages/plugin/src/node/context/backend-init-theia.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright (C) 2015-2018 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/

import { BackendInitializationFn } from '../../common/plugin-protocol';
import { createAPI } from '../../plugin/plugin-context';

export const doInitialization: BackendInitializationFn = (rpc: any) => {
const theia = createAPI(rpc);

// add theia into global goal
const g = global as any;
g['theia'] = theia;

const NODE_MODULE_NAMES = ['@theia/plugin', '@wiptheia/plugin'];
const module = require('module');

// add theia object as module into npm cache
NODE_MODULE_NAMES.forEach(moduleName => {
require.cache[moduleName] = {
id: moduleName,
filename: moduleName,
loaded: true,
exports: theia
};
});

// save original resolve method
const internalResolve = module._resolveFilename;

// if we try to resolve theia module, return the filename entry to use cache.
module._resolveFilename = (request: string, parent: {}) => {
if (NODE_MODULE_NAMES.indexOf(request) !== -1) {
return request;
}
const retVal = internalResolve(request, parent);
return retVal;
};
};
43 changes: 43 additions & 0 deletions packages/plugin/src/node/context/backend-init-vscode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright (C) 2018 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/

import { BackendInitializationFn } from '../../common/plugin-protocol';
import { createAPI } from '../../plugin/plugin-context';

export const doInitialization: BackendInitializationFn = (rpc: any) => {
const module = require('module');
const vscodeModuleName = 'vscode';
const theia = createAPI(rpc);

// add theia into global goal as 'vscode'
const g = global as any;
g[vscodeModuleName] = theia;

// add vscode object as module into npm cache
require.cache[vscodeModuleName] = {
id: vscodeModuleName,
filename: vscodeModuleName,
loaded: true,
exports: g[vscodeModuleName]
};

// save original resolve method
const internalResolve = module._resolveFilename;

// if we try to resolve vscode module, return the filename entry to use cache.
module._resolveFilename = (request: string, parent: {}) => {
if (vscodeModuleName === request) {
return request;
}
const retVal = internalResolve(request, parent);
return retVal;
};
};
8 changes: 4 additions & 4 deletions packages/plugin/src/node/hosted-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import * as path from 'path';
import * as cp from "child_process";
import { injectable, inject } from "inversify";
import { Plugin, HostedPluginClient } from '../common/plugin-protocol';
import { HostedPluginClient, PluginModel } from '../common/plugin-protocol';
import { ILogger, ConnectionErrorHandler } from "@theia/core/lib/common";
import { Emitter } from '@theia/core/lib/common/event';
import { createIpcEnv } from "@theia/core/lib/node/messaging/ipc-protocol";
Expand Down Expand Up @@ -37,8 +37,8 @@ export class HostedPluginSupport {
this.client = client;
}

runPlugin(plugin: Plugin): void {
if (plugin.theiaPlugin.node) {
runPlugin(plugin: PluginModel): void {
if (plugin.entryPoint.backend) {
this.runPluginServer(plugin);
}
}
Expand Down Expand Up @@ -75,7 +75,7 @@ export class HostedPluginSupport {
});
}

private runPluginServer(plugin: Plugin): void {
private runPluginServer(plugin: PluginModel): void {
if (this.cp) {
this.terminatePluginServer(this.cp);
}
Expand Down
Loading