Skip to content

Modernize CLI main #114623

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jan 20, 2021
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
216 changes: 141 additions & 75 deletions src/vs/code/node/cliProcessMain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
*--------------------------------------------------------------------------------------------*/

import { release } from 'os';
import * as fs from 'fs';
import { gracefulify } from 'graceful-fs';
import { isAbsolute, join } from 'vs/base/common/path';
import { raceTimeout } from 'vs/base/common/async';
import product from 'vs/platform/product/common/product';
Expand Down Expand Up @@ -36,108 +38,119 @@ import { buildTelemetryMessage } from 'vs/platform/telemetry/node/telemetry';
import { FileService } from 'vs/platform/files/common/fileService';
import { IFileService } from 'vs/platform/files/common/files';
import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { Disposable } from 'vs/base/common/lifecycle';
import { IProductService } from 'vs/platform/product/common/productService';
import { ExtensionManagementCLIService } from 'vs/platform/extensionManagement/common/extensionManagementCLIService';
import { URI } from 'vs/base/common/uri';
import { LocalizationsService } from 'vs/platform/localizations/node/localizations';
import { ILocalizationsService } from 'vs/platform/localizations/common/localizations';
import { setUnexpectedErrorHandler } from 'vs/base/common/errors';
import { toErrorMessage } from 'vs/base/common/errorMessage';

export class Main {
export class CliMain extends Disposable {

constructor(
@INativeEnvironmentService private readonly environmentService: INativeEnvironmentService,
@IExtensionManagementCLIService private readonly extensionManagementCLIService: IExtensionManagementCLIService
) { }

async run(argv: NativeParsedArgs): Promise<void> {
if (argv['install-source']) {
await this.setInstallSource(argv['install-source']);
return;
}
private argv: NativeParsedArgs
) {
super();

if (argv['list-extensions']) {
await this.extensionManagementCLIService.listExtensions(!!argv['show-versions'], argv['category']);
} else if (argv['install-extension'] || argv['install-builtin-extension']) {
await this.extensionManagementCLIService.installExtensions(this.asExtensionIdOrVSIX(argv['install-extension'] || []), argv['install-builtin-extension'] || [], !!argv['do-not-sync'], !!argv['force']);
} else if (argv['uninstall-extension']) {
await this.extensionManagementCLIService.uninstallExtensions(this.asExtensionIdOrVSIX(argv['uninstall-extension']), !!argv['force']);
} else if (argv['locate-extension']) {
await this.extensionManagementCLIService.locateExtension(argv['locate-extension']);
} else if (argv['telemetry']) {
console.log(buildTelemetryMessage(this.environmentService.appRoot, this.environmentService.extensionsPath));
}
}
// Enable gracefulFs
gracefulify(fs);

private asExtensionIdOrVSIX(inputs: string[]): (string | URI)[] {
return inputs.map(input => /\.vsix$/i.test(input) ? URI.file(isAbsolute(input) ? input : join(process.cwd(), input)) : input);
this.registerListeners();
}

private setInstallSource(installSource: string): Promise<void> {
return writeFile(this.environmentService.installSourcePath, installSource.slice(0, 30));
private registerListeners(): void {

// Dispose on exit
process.once('exit', () => this.dispose());
}

}
async run(): Promise<void> {

const eventPrefix = 'monacoworkbench';
// Services
const [instantiationService, appenders] = await this.initServices();

export async function main(argv: NativeParsedArgs): Promise<void> {
const services = new ServiceCollection();
const disposables = new DisposableStore();

const environmentService = new NativeEnvironmentService(argv);
const logLevel = getLogLevel(environmentService);
const loggers: ILogService[] = [];
loggers.push(new SpdLogService('cli', environmentService.logsPath, logLevel));
if (logLevel === LogLevel.Trace) {
loggers.push(new ConsoleLogService(logLevel));
return instantiationService.invokeFunction(async accessor => {
const logService = accessor.get(ILogService);
const environmentService = accessor.get(INativeEnvironmentService);
const extensionManagementCLIService = accessor.get(IExtensionManagementCLIService);

// Log info
logService.info('CLI main', this.argv);

// Error handler
this.registerErrorHandler(logService);

// Run based on argv
await this.doRun(environmentService, extensionManagementCLIService);

// Flush the remaining data in AI adapter (with 1s timeout)
return raceTimeout(combinedAppender(...appenders).flush(), 1000);
});
}
const logService = new MultiplexLogService(loggers);
process.once('exit', () => logService.dispose());
logService.info('main', argv);

await Promise.all<void | undefined>([environmentService.appSettingsHome.fsPath, environmentService.extensionsPath]
.map((path): undefined | Promise<void> => path ? mkdirp(path) : undefined));
private async initServices(): Promise<[IInstantiationService, AppInsightsAppender[]]> {
const services = new ServiceCollection();

// Environment
const environmentService = new NativeEnvironmentService(this.argv);
services.set(IEnvironmentService, environmentService);
services.set(INativeEnvironmentService, environmentService);

// Init folders
await Promise.all([environmentService.appSettingsHome.fsPath, environmentService.extensionsPath].map(path => path ? mkdirp(path) : undefined));

// Files
const fileService = new FileService(logService);
disposables.add(fileService);
services.set(IFileService, fileService);
// Log
const logLevel = getLogLevel(environmentService);
const loggers: ILogService[] = [];
loggers.push(new SpdLogService('cli', environmentService.logsPath, logLevel));
if (logLevel === LogLevel.Trace) {
loggers.push(new ConsoleLogService(logLevel));
}

const logService = this._register(new MultiplexLogService(loggers));
services.set(ILogService, logService);

const diskFileSystemProvider = new DiskFileSystemProvider(logService);
disposables.add(diskFileSystemProvider);
fileService.registerProvider(Schemas.file, diskFileSystemProvider);
// Files
const fileService = this._register(new FileService(logService));
services.set(IFileService, fileService);

const configurationService = new ConfigurationService(environmentService.settingsResource, fileService);
disposables.add(configurationService);
await configurationService.initialize();
const diskFileSystemProvider = this._register(new DiskFileSystemProvider(logService));
fileService.registerProvider(Schemas.file, diskFileSystemProvider);

services.set(IEnvironmentService, environmentService);
services.set(INativeEnvironmentService, environmentService);
// Configuration
const configurationService = this._register(new ConfigurationService(environmentService.settingsResource, fileService));
services.set(IConfigurationService, configurationService);

services.set(ILogService, logService);
services.set(IConfigurationService, configurationService);
services.set(IStateService, new SyncDescriptor(StateService));
services.set(IProductService, { _serviceBrand: undefined, ...product });
// Init config
await configurationService.initialize();

const instantiationService: IInstantiationService = new InstantiationService(services);
// State
const stateService = new StateService(environmentService, logService);
services.set(IStateService, stateService);

return instantiationService.invokeFunction(async accessor => {
const stateService = accessor.get(IStateService);
// Product
services.set(IProductService, { _serviceBrand: undefined, ...product });

const { appRoot, extensionsPath, extensionDevelopmentLocationURI, isBuilt, installSourcePath } = environmentService;

const services = new ServiceCollection();
// Request
services.set(IRequestService, new SyncDescriptor(RequestService));

// Extensions
services.set(IExtensionManagementService, new SyncDescriptor(ExtensionManagementService));
services.set(IExtensionGalleryService, new SyncDescriptor(ExtensionGalleryService));
services.set(IExtensionManagementCLIService, new SyncDescriptor(ExtensionManagementCLIService));

// Localizations
services.set(ILocalizationsService, new SyncDescriptor(LocalizationsService));

// Telemetry
const appenders: AppInsightsAppender[] = [];
if (isBuilt && !extensionDevelopmentLocationURI && !environmentService.disableTelemetry && product.enableTelemetry) {
if (product.aiConfig && product.aiConfig.asimovKey) {
appenders.push(new AppInsightsAppender(eventPrefix, null, product.aiConfig.asimovKey));
appenders.push(new AppInsightsAppender('monacoworkbench', null, product.aiConfig.asimovKey));
}

const config: ITelemetryServiceConfig = {
Expand All @@ -153,17 +166,70 @@ export async function main(argv: NativeParsedArgs): Promise<void> {
services.set(ITelemetryService, NullTelemetryService);
}

const instantiationService2 = instantiationService.createChild(services);
const main = instantiationService2.createInstance(Main);
return [new InstantiationService(services), appenders];
}

private registerErrorHandler(logService: ILogService): void {

// Install handler for unexpected errors
setUnexpectedErrorHandler(error => {
const message = toErrorMessage(error, true);
if (!message) {
return;
}

logService.error(message);
});
}

private async doRun(environmentService: INativeEnvironmentService, extensionManagementCLIService: IExtensionManagementCLIService): Promise<void> {

// Install Source
if (this.argv['install-source']) {
return this.setInstallSource(environmentService, this.argv['install-source']);
}

// List Extensions
if (this.argv['list-extensions']) {
return extensionManagementCLIService.listExtensions(!!this.argv['show-versions'], this.argv['category']);
}

// Install Extension
else if (this.argv['install-extension'] || this.argv['install-builtin-extension']) {
return extensionManagementCLIService.installExtensions(this.asExtensionIdOrVSIX(this.argv['install-extension'] || []), this.argv['install-builtin-extension'] || [], !!this.argv['do-not-sync'], !!this.argv['force']);
}

try {
await main.run(argv);
// Uninstall Extension
else if (this.argv['uninstall-extension']) {
return extensionManagementCLIService.uninstallExtensions(this.asExtensionIdOrVSIX(this.argv['uninstall-extension']), !!this.argv['force']);
}

// Locate Extension
else if (this.argv['locate-extension']) {
return extensionManagementCLIService.locateExtension(this.argv['locate-extension']);
}

// Flush the remaining data in AI adapter.
// If it does not complete in 1 second, exit the process.
await raceTimeout(combinedAppender(...appenders).flush(), 1000);
} finally {
disposables.dispose();
// Telemetry
else if (this.argv['telemetry']) {
console.log(buildTelemetryMessage(environmentService.appRoot, environmentService.extensionsPath));
}
});
}

private asExtensionIdOrVSIX(inputs: string[]): (string | URI)[] {
return inputs.map(input => /\.vsix$/i.test(input) ? URI.file(isAbsolute(input) ? input : join(process.cwd(), input)) : input);
}

private setInstallSource(environmentService: INativeEnvironmentService, installSource: string): Promise<void> {
return writeFile(environmentService.installSourcePath, installSource.slice(0, 30));
}
}

export async function main(argv: NativeParsedArgs): Promise<void> {
const cliMain = new CliMain(argv);

try {
await cliMain.run();
} finally {
cliMain.dispose();
}
}