Skip to content

Commit

Permalink
Add a command to list installed plugins. Fixes #12298 (#12818)
Browse files Browse the repository at this point in the history
Contributed on behalf of STMicroelectronics

Signed-off-by: Thomas Mäder <[email protected]>
  • Loading branch information
tsmaeder authored Nov 24, 2023
1 parent 964f69c commit 6004188
Show file tree
Hide file tree
Showing 14 changed files with 164 additions and 47 deletions.
35 changes: 24 additions & 11 deletions dev-packages/application-manager/src/generator/backend-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,11 @@ ${Array.from(electronMainModules?.values() ?? [], jsModulePath => `\
await load(require('${jsModulePath}'));`).join(EOL)}
await start();
} catch (reason) {
console.error('Failed to start the electron application.');
if (reason) {
console.error(reason);
if (typeof reason !== 'number') {
console.error('Failed to start the electron application.');
if (reason) {
console.error(reason);
}
}
app.quit();
};
Expand Down Expand Up @@ -139,8 +141,17 @@ async function start(port, host, argv = process.argv) {
if (!container.isBound(BackendApplicationServer)) {
container.bind(BackendApplicationServer).toConstantValue({ configure: defaultServeStatic });
}
await container.get(CliManager).initializeCli(argv);
return container.get(BackendApplication).start(port, host);
let result = undefined;
await container.get(CliManager).initializeCli(argv.slice(2),
() => container.get(BackendApplication).configured,
async () => {
result = container.get(BackendApplication).start(port, host);
});
if (result) {
return result;
} else {
return Promise.reject(0);
}
}
module.exports = async (port, host, argv) => {
Expand All @@ -149,9 +160,11 @@ ${Array.from(backendModules.values(), jsModulePath => `\
await load(require('${jsModulePath}'));`).join(EOL)}
return await start(port, host, argv);
} catch (error) {
console.error('Failed to start the backend application:');
console.error(error);
process.exitCode = 1;
if (typeof error !== 'number') {
console.error('Failed to start the backend application:');
console.error(error);
process.exitCode = 1;
}
throw error;
}
}
Expand All @@ -168,9 +181,9 @@ BackendApplicationConfigProvider.set(${this.prettyStringify(this.pck.props.backe
const serverModule = require('./server');
const serverAddress = main.start(serverModule());
serverAddress.then(({ port, address, family }) => {
if (process && process.send) {
process.send({ port, address, family });
serverAddress.then((addressInfo) => {
if (process && process.send && addressInfo) {
process.send(addressInfo);
}
});
Expand Down
38 changes: 30 additions & 8 deletions examples/api-samples/src/node/sample-mock-open-vsx-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ import { inject, injectable } from '@theia/core/shared/inversify';
import { OVSXMockClient, VSXExtensionRaw } from '@theia/ovsx-client';
import * as path from 'path';
import { SampleAppInfo } from '../common/vsx/sample-app-info';
import * as http from 'http';
import * as https from 'https';
import { Deferred } from '@theia/core/lib/common/promise-util';

type VersionedId = `${string}.${string}@${string}`;

Expand All @@ -35,6 +38,16 @@ export class SampleMockOpenVsxServer implements BackendApplicationContribution {
@inject(SampleAppInfo)
protected appInfo: SampleAppInfo;

protected mockClient: OVSXMockClient;
protected staticFileHandlers: Map<string, express.RequestHandler<{
namespace: string;
name: string;
version: string;
}, express.Response>>;

private readyDeferred = new Deferred<void>();
private ready = this.readyDeferred.promise;

get mockServerPath(): string {
return '/mock-open-vsx';
}
Expand All @@ -43,23 +56,30 @@ export class SampleMockOpenVsxServer implements BackendApplicationContribution {
return '../../sample-plugins';
}

async configure(app: express.Application): Promise<void> {
async onStart?(server: http.Server | https.Server): Promise<void> {
const selfOrigin = await this.appInfo.getSelfOrigin();
const baseUrl = `${selfOrigin}${this.mockServerPath}`;
const pluginsDb = await this.findMockPlugins(this.pluginsDbPath, baseUrl);
const staticFileHandlers = new Map(Array.from(pluginsDb.entries(), ([key, value]) => [key, express.static(value.path)]));
const mockClient = new OVSXMockClient(Array.from(pluginsDb.values(), value => value.data));
this.staticFileHandlers = new Map(Array.from(pluginsDb.entries(), ([key, value]) => [key, express.static(value.path)]));
this.mockClient = new OVSXMockClient(Array.from(pluginsDb.values(), value => value.data));
this.readyDeferred.resolve();
}

async configure(app: express.Application): Promise<void> {
app.use(
this.mockServerPath + '/api',
express.Router()
.get('/-/query', async (req, res) => {
res.json(await mockClient.query(this.sanitizeQuery(req.query)));
await this.ready;
res.json(await this.mockClient.query(this.sanitizeQuery(req.query)));
})
.get('/-/search', async (req, res) => {
res.json(await mockClient.search(this.sanitizeQuery(req.query)));
await this.ready;
res.json(await this.mockClient.search(this.sanitizeQuery(req.query)));
})
.get('/:namespace', async (req, res) => {
const extensions = mockClient.extensions
await this.ready;
const extensions = this.mockClient.extensions
.filter(ext => req.params.namespace === ext.namespace)
.map(ext => `${ext.namespaceUrl}/${ext.name}`);
if (extensions.length === 0) {
Expand All @@ -72,15 +92,17 @@ export class SampleMockOpenVsxServer implements BackendApplicationContribution {
}
})
.get('/:namespace/:name', async (req, res) => {
res.json(mockClient.extensions.find(ext => req.params.namespace === ext.namespace && req.params.name === ext.name));
await this.ready;
res.json(this.mockClient.extensions.find(ext => req.params.namespace === ext.namespace && req.params.name === ext.name));
})
.get('/:namespace/:name/reviews', async (req, res) => {
res.json([]);
})
// implicitly GET/HEAD because of the express.static handlers
.use('/:namespace/:name/:version/file', async (req, res, next) => {
await this.ready;
const versionedId = this.getVersionedId(req.params.namespace, req.params.name, req.params.version);
const staticFileHandler = staticFileHandlers.get(versionedId);
const staticFileHandler = this.staticFileHandlers.get(versionedId);
if (!staticFileHandler) {
return next();
}
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/electron-main/electron-main-application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -564,8 +564,8 @@ export class ElectronMainApplication {
backendProcess.on('error', error => {
reject(error);
});
backendProcess.on('exit', () => {
reject(new Error('backend process exited'));
backendProcess.on('exit', code => {
reject(code);
});
app.on('quit', () => {
// Only issue a kill signal if the backend process is running.
Expand Down
25 changes: 14 additions & 11 deletions packages/core/src/node/backend-application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,8 @@ export class BackendApplication {
@inject(Stopwatch)
protected readonly stopwatch: Stopwatch;

private _configured: Promise<void>;

constructor(
@inject(ContributionProvider) @named(BackendApplicationContribution)
protected readonly contributionsProvider: ContributionProvider<BackendApplicationContribution>,
Expand Down Expand Up @@ -198,7 +200,7 @@ export class BackendApplication {
}

protected async initialize(): Promise<void> {
for (const contribution of this.contributionsProvider.getContributions()) {
await Promise.all(this.contributionsProvider.getContributions().map(async contribution => {
if (contribution.initialize) {
try {
await this.measure(contribution.constructor.name + '.initialize',
Expand All @@ -208,18 +210,20 @@ export class BackendApplication {
console.error('Could not initialize contribution', error);
}
}
}
}));
}

get configured(): Promise<void> {
return this._configured;
}

@postConstruct()
protected init(): void {
this.configure();
this._configured = this.configure();
}

protected async configure(): Promise<void> {
// Do not await the initialization because contributions are expected to handle
// concurrent initialize/configure in undefined order if they provide both
this.initialize();
await this.initialize();

this.app.get('*.js', this.serveGzipped.bind(this, 'text/javascript'));
this.app.get('*.js.map', this.serveGzipped.bind(this, 'application/json'));
Expand All @@ -233,17 +237,16 @@ export class BackendApplication {
this.app.get('*.woff', this.serveGzipped.bind(this, 'font/woff'));
this.app.get('*.woff2', this.serveGzipped.bind(this, 'font/woff2'));

for (const contribution of this.contributionsProvider.getContributions()) {
await Promise.all(this.contributionsProvider.getContributions().map(async contribution => {
if (contribution.configure) {
try {
await this.measure(contribution.constructor.name + '.configure',
() => contribution.configure!(this.app)
);
await contribution.configure!(this.app);
} catch (error) {
console.error('Could not configure contribution', error);
}
}
}
}));
console.info('configured all backend app contributions');
}

use(...handlers: express.Handler[]): void {
Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/node/cli.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ describe('CliManager', () => {
value.resolve(args['foo'] as string);
}
});
await manager.initializeCli(['-f', 'bla']);
await manager.initializeCli(['-f', 'bla'], () => Promise.resolve(), () => Promise.resolve());
chai.assert.equal(await value.promise, 'bla');
});

Expand All @@ -59,14 +59,14 @@ describe('CliManager', () => {
value.resolve(args['bar'] as string);
}
});
await manager.initializeCli(['--foo']);
await manager.initializeCli(['--foo'], () => Promise.resolve(), () => Promise.resolve());
chai.assert.equal(await value.promise, 'my-default');
});

it('prints help and exits', async () =>
assertExits(async () => {
const manager = new TestCliManager();
await manager.initializeCli(['--help']);
await manager.initializeCli(['--help'], () => Promise.resolve(), () => Promise.resolve());
})
);
});
Expand Down
14 changes: 9 additions & 5 deletions packages/core/src/node/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,22 +35,26 @@ export class CliManager {
constructor(@inject(ContributionProvider) @named(CliContribution)
protected readonly contributionsProvider: ContributionProvider<CliContribution>) { }

async initializeCli(argv: string[]): Promise<void> {
async initializeCli<T>(argv: string[], postSetArguments: () => Promise<void>, defaultCommand: () => Promise<void>): Promise<void> {
const pack = require('../../package.json');
const version = pack.version;
const command = yargs.version(version);
command.exitProcess(this.isExit());
for (const contrib of this.contributionsProvider.getContributions()) {
contrib.configure(command);
}
const args = command
await command
.detectLocale(false)
.showHelpOnFail(false, 'Specify --help for available options')
.help('help')
.middleware(async args => {
for (const contrib of this.contributionsProvider.getContributions()) {
await contrib.setArguments(args);
}
await postSetArguments();
})
.command('$0', false, () => { }, defaultCommand)
.parse(argv);
for (const contrib of this.contributionsProvider.getContributions()) {
await contrib.setArguments(args);
}
}

protected isExit(): boolean {
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/node/messaging/ipc-protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export function checkParentAlive(): void {
} catch {
process.exit();
}
}, 5000);
}, 5000).unref(); // we don't want this timeout to keep the process alive
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion packages/plugin-ext/src/common/plugin-protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -429,7 +429,7 @@ export interface PluginDeployerStartContext {
export const PluginDeployer = Symbol('PluginDeployer');
export interface PluginDeployer {

start(): void;
start(): Promise<void>;

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,13 @@ export class HostedPluginDeployerHandler implements PluginDeployerHandler {
return Array.from(this.deployedBackendPlugins.keys());
}

async getDeployedBackendPlugins(): Promise<DeployedPlugin[]> {
// await first deploy
await this.backendPluginsMetadataDeferred.promise;
// fetch the last deployed state
return Array.from(this.deployedBackendPlugins.values());
}

getDeployedPluginsById(pluginId: string): DeployedPlugin[] {
const matches: DeployedPlugin[] = [];
const handle = (plugins: Iterable<DeployedPlugin>): void => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export class PluginDeployerContribution implements BackendApplicationContributio
@inject(PluginDeployer)
protected pluginDeployer: PluginDeployer;

initialize(): void {
this.pluginDeployer.start();
initialize(): Promise<void> {
return this.pluginDeployer.start();
}
}
4 changes: 2 additions & 2 deletions packages/plugin-ext/src/main/node/plugin-deployer-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,9 @@ export class PluginDeployerImpl implements PluginDeployer {
@inject(ContributionProvider) @named(PluginDeployerParticipant)
protected readonly participants: ContributionProvider<PluginDeployerParticipant>;

public start(): void {
public start(): Promise<void> {
this.logger.debug('Starting the deployer with the list of resolvers', this.pluginResolvers);
this.doStart();
return this.doStart();
}

public async initResolvers(): Promise<Array<void>> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import { WebviewBackendSecurityWarnings } from './webview-backend-security-warni
import { PluginUninstallationManager } from './plugin-uninstallation-manager';
import { LocalizationServerImpl } from '@theia/core/lib/node/i18n/localization-server';
import { PluginLocalizationServer } from './plugin-localization-server';
import { PluginMgmtCliContribution } from './plugin-mgmt-cli-contribution';

export function bindMainBackend(bind: interfaces.Bind, unbind: interfaces.Unbind, isBound: interfaces.IsBound, rebind: interfaces.Rebind): void {
bind(PluginApiContribution).toSelf().inSingletonScope();
Expand Down Expand Up @@ -85,6 +86,9 @@ export function bindMainBackend(bind: interfaces.Bind, unbind: interfaces.Unbind
bind(PluginCliContribution).toSelf().inSingletonScope();
bind(CliContribution).toService(PluginCliContribution);

bind(PluginMgmtCliContribution).toSelf().inSingletonScope();
bind(CliContribution).toService(PluginMgmtCliContribution);

bind(WebviewBackendSecurityWarnings).toSelf().inSingletonScope();
bind(BackendApplicationContribution).toService(WebviewBackendSecurityWarnings);

Expand Down
Loading

0 comments on commit 6004188

Please sign in to comment.