mirror of
https://github.com/Microsoft/vscode
synced 2024-09-19 10:40:41 +00:00
parent
69684cd43c
commit
dcea438aca
|
@ -18,7 +18,7 @@ import {
|
|||
IExtensionsControlManifest, StatisticType, isTargetPlatformCompatible, TargetPlatformToString, ExtensionManagementErrorCode,
|
||||
InstallOptions, InstallVSIXOptions, UninstallOptions, Metadata, InstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionResult, UninstallExtensionEvent, IExtensionManagementService, InstallExtensionInfo, EXTENSION_INSTALL_DEP_PACK_CONTEXT, ExtensionGalleryError
|
||||
} from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { adoptToGalleryExtensionId, areSameExtensions, ExtensionKey, getGalleryExtensionId, getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { areSameExtensions, ExtensionKey, getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { ExtensionType, IExtensionManifest, isApplicationScopedExtension, TargetPlatform } from 'vs/platform/extensions/common/extensions';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
|
@ -204,198 +204,167 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
|
|||
|
||||
protected async installExtensions(extensions: InstallableExtension[]): Promise<InstallExtensionResult[]> {
|
||||
const results: InstallExtensionResult[] = [];
|
||||
await Promise.allSettled(extensions.map(async e => {
|
||||
try {
|
||||
const result = await this.installExtension(e, (taskToWait: IInstallExtensionTask, taskToWaitFor: IInstallExtensionTask): boolean => {
|
||||
if (extensions.some(e => adoptToGalleryExtensionId(taskToWaitFor.identifier.id) === getGalleryExtensionId(e.manifest.publisher, e.manifest.name))) {
|
||||
return false;
|
||||
}
|
||||
return this.canWaitForTask(taskToWait, taskToWaitFor);
|
||||
});
|
||||
results.push(...result);
|
||||
} catch (error) {
|
||||
results.push({ identifier: { id: getGalleryExtensionId(e.manifest.publisher, e.manifest.name) }, operation: InstallOperation.Install, source: e.extension, error });
|
||||
|
||||
const installingExtensionsMap = new Map<string, { task: IInstallExtensionTask; manifest: IExtensionManifest }>();
|
||||
const alreadyRequestedInstallations: Promise<any>[] = [];
|
||||
const successResults: (InstallExtensionResult & { local: ILocalExtension; profileLocation: URI })[] = [];
|
||||
|
||||
const getInstallExtensionTaskKey = (extension: IGalleryExtension, profileLocation: URI) => `${ExtensionKey.create(extension).toString()}-${profileLocation.toString()}`;
|
||||
const createInstallExtensionTask = (manifest: IExtensionManifest, extension: IGalleryExtension | URI, options: InstallExtensionTaskOptions): void => {
|
||||
const installExtensionTask = this.createInstallExtensionTask(manifest, extension, options);
|
||||
const key = URI.isUri(extension) ? extension.path : `${extension.identifier.id.toLowerCase()}-${options.profileLocation.toString()}`;
|
||||
installingExtensionsMap.set(key, { task: installExtensionTask, manifest });
|
||||
this._onInstallExtension.fire({ identifier: installExtensionTask.identifier, source: extension, profileLocation: options.profileLocation });
|
||||
this.logService.info('Installing extension:', installExtensionTask.identifier.id);
|
||||
// only cache gallery extensions tasks
|
||||
if (!URI.isUri(extension)) {
|
||||
this.installingExtensions.set(getInstallExtensionTaskKey(extension, options.profileLocation), { task: installExtensionTask, waitingTasks: [] });
|
||||
}
|
||||
}));
|
||||
this._onDidInstallExtensions.fire(results);
|
||||
return results;
|
||||
}
|
||||
|
||||
private async installExtension({ manifest, extension, options }: InstallableExtension, shouldWait: (taskToWait: IInstallExtensionTask, taskToWaitFor: IInstallExtensionTask) => boolean): Promise<InstallExtensionResult[]> {
|
||||
|
||||
const isApplicationScoped = options.isApplicationScoped || options.isBuiltin || isApplicationScopedExtension(manifest);
|
||||
const installExtensionTaskOptions: InstallExtensionTaskOptions = {
|
||||
...options,
|
||||
installOnlyNewlyAddedFromExtensionPack: URI.isUri(extension) ? options.installOnlyNewlyAddedFromExtensionPack : true, /* always true for gallery extensions */
|
||||
isApplicationScoped,
|
||||
profileLocation: isApplicationScoped ? this.userDataProfilesService.defaultProfile.extensionsResource : options.profileLocation ?? this.getCurrentExtensionsManifestLocation()
|
||||
};
|
||||
const getInstallExtensionTaskKey = (extension: IGalleryExtension) => `${ExtensionKey.create(extension).toString()}${installExtensionTaskOptions.profileLocation ? `-${installExtensionTaskOptions.profileLocation.toString()}` : ''}`;
|
||||
|
||||
// only cache gallery extensions tasks
|
||||
if (!URI.isUri(extension)) {
|
||||
const installingExtension = this.installingExtensions.get(getInstallExtensionTaskKey(extension));
|
||||
if (installingExtension) {
|
||||
this.logService.info('Extensions is already requested to install', extension.identifier.id);
|
||||
await installingExtension.task.waitUntilTaskIsFinished();
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
const allInstallExtensionTasks: { task: IInstallExtensionTask; manifest: IExtensionManifest }[] = [];
|
||||
const alreadyRequestedInstallations: Promise<void>[] = [];
|
||||
const installResults: (InstallExtensionResult & { local: ILocalExtension })[] = [];
|
||||
const installExtensionTask = this.createInstallExtensionTask(manifest, extension, installExtensionTaskOptions);
|
||||
if (!URI.isUri(extension)) {
|
||||
this.installingExtensions.set(getInstallExtensionTaskKey(extension), { task: installExtensionTask, waitingTasks: [] });
|
||||
}
|
||||
this._onInstallExtension.fire({ identifier: installExtensionTask.identifier, source: extension, profileLocation: installExtensionTaskOptions.profileLocation });
|
||||
this.logService.info('Installing extension:', installExtensionTask.identifier.id);
|
||||
allInstallExtensionTasks.push({ task: installExtensionTask, manifest });
|
||||
let installExtensionHasDependents: boolean = false;
|
||||
|
||||
try {
|
||||
if (installExtensionTaskOptions.donotIncludePackAndDependencies) {
|
||||
this.logService.info('Installing the extension without checking dependencies and pack', installExtensionTask.identifier.id);
|
||||
} else {
|
||||
try {
|
||||
const allDepsAndPackExtensionsToInstall = await this.getAllDepsAndPackExtensions(installExtensionTask.identifier, manifest, !!installExtensionTaskOptions.installOnlyNewlyAddedFromExtensionPack, !!installExtensionTaskOptions.installPreReleaseVersion, installExtensionTaskOptions.profileLocation);
|
||||
const installed = await this.getInstalled(undefined, installExtensionTaskOptions.profileLocation);
|
||||
const options: InstallExtensionTaskOptions = { ...installExtensionTaskOptions, donotIncludePackAndDependencies: true, context: { ...installExtensionTaskOptions.context, [EXTENSION_INSTALL_DEP_PACK_CONTEXT]: true } };
|
||||
for (const { gallery, manifest } of distinct(allDepsAndPackExtensionsToInstall, ({ gallery }) => gallery.identifier.id)) {
|
||||
installExtensionHasDependents = installExtensionHasDependents || !!manifest.extensionDependencies?.some(id => areSameExtensions({ id }, installExtensionTask.identifier));
|
||||
const key = getInstallExtensionTaskKey(gallery);
|
||||
const existingInstallingExtension = this.installingExtensions.get(key);
|
||||
if (existingInstallingExtension) {
|
||||
if (shouldWait(installExtensionTask, existingInstallingExtension.task)) {
|
||||
const identifier = existingInstallingExtension.task.identifier;
|
||||
this.logService.info('Waiting for already requested installing extension', identifier.id, installExtensionTask.identifier.id);
|
||||
existingInstallingExtension.waitingTasks.push(installExtensionTask);
|
||||
// add promise that waits until the extension is completely installed, ie., onDidInstallExtensions event is triggered for this extension
|
||||
alreadyRequestedInstallations.push(
|
||||
Event.toPromise(
|
||||
Event.filter(this.onDidInstallExtensions, results => results.some(result => areSameExtensions(result.identifier, identifier)))
|
||||
).then(results => {
|
||||
this.logService.info('Finished waiting for already requested installing extension', identifier.id, installExtensionTask.identifier.id);
|
||||
const result = results.find(result => areSameExtensions(result.identifier, identifier));
|
||||
if (!result?.local) {
|
||||
// Extension failed to install
|
||||
throw new Error(`Extension ${identifier.id} is not installed`);
|
||||
}
|
||||
}));
|
||||
}
|
||||
} else if (!installed.some(({ identifier }) => areSameExtensions(identifier, gallery.identifier))) {
|
||||
const task = this.createInstallExtensionTask(manifest, gallery, options);
|
||||
this.installingExtensions.set(key, { task, waitingTasks: [installExtensionTask] });
|
||||
this._onInstallExtension.fire({ identifier: task.identifier, source: gallery, profileLocation: installExtensionTaskOptions.profileLocation });
|
||||
this.logService.info('Installing extension:', task.identifier.id, installExtensionTask.identifier.id);
|
||||
allInstallExtensionTasks.push({ task, manifest });
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// Installing through VSIX
|
||||
if (URI.isUri(installExtensionTask.source)) {
|
||||
// Ignore installing dependencies and packs
|
||||
if (isNonEmptyArray(manifest.extensionDependencies)) {
|
||||
this.logService.warn(`Cannot install dependencies of extension:`, installExtensionTask.identifier.id, error.message);
|
||||
}
|
||||
if (isNonEmptyArray(manifest.extensionPack)) {
|
||||
this.logService.warn(`Cannot install packed extensions of extension:`, installExtensionTask.identifier.id, error.message);
|
||||
}
|
||||
} else {
|
||||
this.logService.error('Error while preparing to install dependencies and extension packs of the extension:', installExtensionTask.identifier.id);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Start installing extensions
|
||||
for (const { manifest, extension, options } of extensions) {
|
||||
const isApplicationScoped = options.isApplicationScoped || options.isBuiltin || isApplicationScopedExtension(manifest);
|
||||
const installExtensionTaskOptions: InstallExtensionTaskOptions = {
|
||||
...options,
|
||||
installOnlyNewlyAddedFromExtensionPack: URI.isUri(extension) ? options.installOnlyNewlyAddedFromExtensionPack : true, /* always true for gallery extensions */
|
||||
isApplicationScoped,
|
||||
profileLocation: isApplicationScoped ? this.userDataProfilesService.defaultProfile.extensionsResource : options.profileLocation ?? this.getCurrentExtensionsManifestLocation()
|
||||
};
|
||||
|
||||
const extensionsToInstallMap = allInstallExtensionTasks.reduce((result, { task, manifest }) => {
|
||||
result.set(task.identifier.id.toLowerCase(), { task, manifest });
|
||||
return result;
|
||||
}, new Map<string, { task: IInstallExtensionTask; manifest: IExtensionManifest }>());
|
||||
|
||||
while (extensionsToInstallMap.size) {
|
||||
let extensionsToInstall;
|
||||
const extensionsWithoutDepsToInstall = [...extensionsToInstallMap.values()].filter(({ manifest }) => !manifest.extensionDependencies?.some(id => extensionsToInstallMap.has(id.toLowerCase())));
|
||||
if (extensionsWithoutDepsToInstall.length) {
|
||||
extensionsToInstall = extensionsToInstallMap.size === 1 ? extensionsWithoutDepsToInstall
|
||||
/* If the main extension has no dependents remove it and install it at the end */
|
||||
: extensionsWithoutDepsToInstall.filter(({ task }) => !(task === installExtensionTask && !installExtensionHasDependents));
|
||||
const existingInstallExtensionTask = !URI.isUri(extension) ? this.installingExtensions.get(getInstallExtensionTaskKey(extension, installExtensionTaskOptions.profileLocation)) : undefined;
|
||||
if (existingInstallExtensionTask) {
|
||||
this.logService.info('Extension is already requested to install', existingInstallExtensionTask.task.identifier.id);
|
||||
alreadyRequestedInstallations.push(existingInstallExtensionTask.task.waitUntilTaskIsFinished());
|
||||
} else {
|
||||
this.logService.info('Found extensions with circular dependencies', extensionsWithoutDepsToInstall.map(({ task }) => task.identifier.id));
|
||||
extensionsToInstall = [...extensionsToInstallMap.values()];
|
||||
createInstallExtensionTask(manifest, extension, installExtensionTaskOptions);
|
||||
}
|
||||
}
|
||||
|
||||
// Install extensions in parallel and wait until all extensions are installed / failed
|
||||
await this.joinAllSettled(extensionsToInstall.map(async ({ task }) => {
|
||||
const startTime = new Date().getTime();
|
||||
// collect and start installing all dependencies and pack extensions
|
||||
await Promise.all([...installingExtensionsMap.values()].map(async ({ task, manifest }) => {
|
||||
if (task.options.donotIncludePackAndDependencies) {
|
||||
this.logService.info('Installing the extension without checking dependencies and pack', task.identifier.id);
|
||||
} else {
|
||||
try {
|
||||
const local = await task.run();
|
||||
await this.joinAllSettled(this.participants.map(participant => participant.postInstall(local, task.source, installExtensionTaskOptions, CancellationToken.None)));
|
||||
if (!URI.isUri(task.source)) {
|
||||
const isUpdate = task.operation === InstallOperation.Update;
|
||||
const durationSinceUpdate = isUpdate ? undefined : (new Date().getTime() - task.source.lastUpdated) / 1000;
|
||||
reportTelemetry(this.telemetryService, isUpdate ? 'extensionGallery:update' : 'extensionGallery:install', {
|
||||
extensionData: getGalleryExtensionTelemetryData(task.source),
|
||||
verificationStatus: task.verificationStatus,
|
||||
duration: new Date().getTime() - startTime,
|
||||
durationSinceUpdate
|
||||
});
|
||||
// In web, report extension install statistics explicitly. In Desktop, statistics are automatically updated while downloading the VSIX.
|
||||
if (isWeb && task.operation !== InstallOperation.Update) {
|
||||
try {
|
||||
await this.galleryService.reportStatistic(local.manifest.publisher, local.manifest.name, local.manifest.version, StatisticType.Install);
|
||||
} catch (error) { /* ignore */ }
|
||||
const allDepsAndPackExtensionsToInstall = await this.getAllDepsAndPackExtensions(task.identifier, manifest, !!task.options.installOnlyNewlyAddedFromExtensionPack, !!task.options.installPreReleaseVersion, task.options.profileLocation);
|
||||
const installed = await this.getInstalled(undefined, task.options.profileLocation);
|
||||
const options: InstallExtensionTaskOptions = { ...task.options, donotIncludePackAndDependencies: true, context: { ...task.options.context, [EXTENSION_INSTALL_DEP_PACK_CONTEXT]: true } };
|
||||
for (const { gallery, manifest } of distinct(allDepsAndPackExtensionsToInstall, ({ gallery }) => gallery.identifier.id)) {
|
||||
if (installingExtensionsMap.has(`${gallery.identifier.id.toLowerCase()}-${options.profileLocation.toString()}`)) {
|
||||
continue;
|
||||
}
|
||||
const existingInstallingExtension = this.installingExtensions.get(getInstallExtensionTaskKey(gallery, options.profileLocation));
|
||||
if (existingInstallingExtension) {
|
||||
if (this.canWaitForTask(task, existingInstallingExtension.task)) {
|
||||
const identifier = existingInstallingExtension.task.identifier;
|
||||
this.logService.info('Waiting for already requested installing extension', identifier.id, task.identifier.id);
|
||||
existingInstallingExtension.waitingTasks.push(task);
|
||||
// add promise that waits until the extension is completely installed, ie., onDidInstallExtensions event is triggered for this extension
|
||||
alreadyRequestedInstallations.push(
|
||||
Event.toPromise(
|
||||
Event.filter(this.onDidInstallExtensions, results => results.some(result => areSameExtensions(result.identifier, identifier)))
|
||||
).then(results => {
|
||||
this.logService.info('Finished waiting for already requested installing extension', identifier.id, task.identifier.id);
|
||||
const result = results.find(result => areSameExtensions(result.identifier, identifier));
|
||||
if (!result?.local) {
|
||||
// Extension failed to install
|
||||
throw new Error(`Extension ${identifier.id} is not installed`);
|
||||
}
|
||||
}));
|
||||
}
|
||||
} else if (!installed.some(({ identifier }) => areSameExtensions(identifier, gallery.identifier))) {
|
||||
createInstallExtensionTask(manifest, gallery, options);
|
||||
}
|
||||
}
|
||||
|
||||
installResults.push({ local, identifier: task.identifier, operation: task.operation, source: task.source, context: task.options.context, profileLocation: task.profileLocation, applicationScoped: local.isApplicationScoped });
|
||||
} catch (error) {
|
||||
this.logService.error('Error while installing the extension', task.identifier.id, getErrorMessage(error));
|
||||
throw error;
|
||||
} finally { extensionsToInstallMap.delete(task.identifier.id.toLowerCase()); }
|
||||
}));
|
||||
}
|
||||
// Installing through VSIX
|
||||
if (URI.isUri(task.source)) {
|
||||
// Ignore installing dependencies and packs
|
||||
if (isNonEmptyArray(manifest.extensionDependencies)) {
|
||||
this.logService.warn(`Cannot install dependencies of extension:`, task.identifier.id, error.message);
|
||||
}
|
||||
if (isNonEmptyArray(manifest.extensionPack)) {
|
||||
this.logService.warn(`Cannot install packed extensions of extension:`, task.identifier.id, error.message);
|
||||
}
|
||||
} else {
|
||||
this.logService.error('Error while preparing to install dependencies and extension packs of the extension:', task.identifier.id);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
// Install extensions in parallel and wait until all extensions are installed / failed
|
||||
await this.joinAllSettled([...installingExtensionsMap.values()].map(async ({ task }) => {
|
||||
const startTime = new Date().getTime();
|
||||
try {
|
||||
const local = await task.run();
|
||||
await this.joinAllSettled(this.participants.map(participant => participant.postInstall(local, task.source, task.options, CancellationToken.None)));
|
||||
if (!URI.isUri(task.source)) {
|
||||
const isUpdate = task.operation === InstallOperation.Update;
|
||||
const durationSinceUpdate = isUpdate ? undefined : (new Date().getTime() - task.source.lastUpdated) / 1000;
|
||||
reportTelemetry(this.telemetryService, isUpdate ? 'extensionGallery:update' : 'extensionGallery:install', {
|
||||
extensionData: getGalleryExtensionTelemetryData(task.source),
|
||||
verificationStatus: task.verificationStatus,
|
||||
duration: new Date().getTime() - startTime,
|
||||
durationSinceUpdate
|
||||
});
|
||||
// In web, report extension install statistics explicitly. In Desktop, statistics are automatically updated while downloading the VSIX.
|
||||
if (isWeb && task.operation !== InstallOperation.Update) {
|
||||
try {
|
||||
await this.galleryService.reportStatistic(local.manifest.publisher, local.manifest.name, local.manifest.version, StatisticType.Install);
|
||||
} catch (error) { /* ignore */ }
|
||||
}
|
||||
}
|
||||
|
||||
successResults.push({ local, identifier: task.identifier, operation: task.operation, source: task.source, context: task.options.context, profileLocation: task.profileLocation, applicationScoped: local.isApplicationScoped });
|
||||
} catch (error) {
|
||||
this.logService.error('Error while installing the extension', task.identifier.id, getErrorMessage(error));
|
||||
throw error;
|
||||
}
|
||||
}));
|
||||
|
||||
if (alreadyRequestedInstallations.length) {
|
||||
await this.joinAllSettled(alreadyRequestedInstallations);
|
||||
}
|
||||
|
||||
installResults.forEach(({ identifier }) => this.logService.info(`Extension installed successfully:`, identifier.id));
|
||||
return installResults;
|
||||
|
||||
for (const result of successResults) {
|
||||
this.logService.info(`Extension installed successfully:`, result.identifier.id);
|
||||
results.push(result);
|
||||
}
|
||||
return results;
|
||||
} catch (error) {
|
||||
|
||||
// cancel all tasks
|
||||
allInstallExtensionTasks.forEach(({ task }) => task.cancel());
|
||||
|
||||
// rollback installed extensions
|
||||
if (installResults.length) {
|
||||
try {
|
||||
const result = await Promise.allSettled(installResults.map(({ local }) => this.createUninstallExtensionTask(local, { versionOnly: true, profileLocation: installExtensionTaskOptions.profileLocation }).run()));
|
||||
for (let index = 0; index < result.length; index++) {
|
||||
const r = result[index];
|
||||
const { identifier } = installResults[index];
|
||||
if (r.status === 'fulfilled') {
|
||||
this.logService.info('Rollback: Uninstalled extension', identifier.id);
|
||||
} else {
|
||||
this.logService.warn('Rollback: Error while uninstalling extension', identifier.id, getErrorMessage(r.reason));
|
||||
}
|
||||
if (successResults.length) {
|
||||
await Promise.allSettled(successResults.map(async ({ local, profileLocation }) => {
|
||||
try {
|
||||
await this.createUninstallExtensionTask(local, { versionOnly: true, profileLocation }).run();
|
||||
this.logService.info('Rollback: Uninstalled extension', local.identifier.id);
|
||||
} catch (error) {
|
||||
this.logService.warn('Rollback: Error while uninstalling extension', local.identifier.id, getErrorMessage(error));
|
||||
}
|
||||
} catch (error) {
|
||||
// ignore error
|
||||
this.logService.warn('Error while rolling back extensions', getErrorMessage(error), installResults.map(({ identifier }) => identifier.id));
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
return allInstallExtensionTasks.map(({ task }) => ({ identifier: task.identifier, operation: InstallOperation.Install, source: task.source, context: installExtensionTaskOptions.context, profileLocation: installExtensionTaskOptions.profileLocation, error }));
|
||||
// cancel all tasks and collect error results
|
||||
for (const { task } of installingExtensionsMap.values()) {
|
||||
task.cancel();
|
||||
results.push({ identifier: task.identifier, operation: InstallOperation.Install, source: task.source, context: task.options.context, profileLocation: task.profileLocation, error });
|
||||
}
|
||||
|
||||
throw error;
|
||||
} finally {
|
||||
// Finally, remove all the tasks from the cache
|
||||
for (const { task } of allInstallExtensionTasks) {
|
||||
for (const { task } of installingExtensionsMap.values()) {
|
||||
if (task.source && !URI.isUri(task.source)) {
|
||||
this.installingExtensions.delete(getInstallExtensionTaskKey(task.source));
|
||||
this.installingExtensions.delete(getInstallExtensionTaskKey(task.source, task.profileLocation));
|
||||
}
|
||||
}
|
||||
if (results.length) {
|
||||
this._onDidInstallExtensions.fire(results);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ const notFound = (id: string) => localize('notFound', "Extension '{0}' not found
|
|||
const useId = localize('useId', "Make sure you use the full extension ID, including the publisher, e.g.: {0}", 'ms-dotnettools.csharp');
|
||||
|
||||
type InstallVSIXInfo = { vsix: URI; installOptions: InstallOptions };
|
||||
type CLIInstallExtensionInfo = { id: string; version?: string; installOptions: InstallOptions };
|
||||
type InstallGalleryExtensionInfo = { id: string; version?: string; installOptions: InstallOptions };
|
||||
|
||||
export class ExtensionManagementCLI {
|
||||
|
||||
|
@ -74,13 +74,12 @@ export class ExtensionManagementCLI {
|
|||
const failed: string[] = [];
|
||||
|
||||
try {
|
||||
const installedExtensionsManifests: IExtensionManifest[] = [];
|
||||
if (extensions.length) {
|
||||
this.logger.info(this.location ? localize('installingExtensionsOnLocation', "Installing extensions on {0}...", this.location) : localize('installingExtensions', "Installing extensions..."));
|
||||
}
|
||||
|
||||
const installVSIXInfos: InstallVSIXInfo[] = [];
|
||||
let installExtensionInfos: CLIInstallExtensionInfo[] = [];
|
||||
const installExtensionInfos: InstallGalleryExtensionInfo[] = [];
|
||||
const addInstallExtensionInfo = (id: string, version: string | undefined, isBuiltin: boolean) => {
|
||||
installExtensionInfos.push({ id, version: version !== 'prerelease' ? version : undefined, installOptions: { ...installOptions, isBuiltin, installPreReleaseVersion: version === 'prerelease' || installOptions.installPreReleaseVersion } });
|
||||
};
|
||||
|
@ -106,10 +105,7 @@ export class ExtensionManagementCLI {
|
|||
if (installVSIXInfos.length) {
|
||||
await Promise.all(installVSIXInfos.map(async ({ vsix, installOptions }) => {
|
||||
try {
|
||||
const manifest = await this.installVSIX(vsix, installOptions, force, installed);
|
||||
if (manifest) {
|
||||
installedExtensionsManifests.push(manifest);
|
||||
}
|
||||
await this.installVSIX(vsix, installOptions, force, installed);
|
||||
} catch (err) {
|
||||
this.logger.error(err);
|
||||
failed.push(vsix.toString());
|
||||
|
@ -118,40 +114,8 @@ export class ExtensionManagementCLI {
|
|||
}
|
||||
|
||||
if (installExtensionInfos.length) {
|
||||
installExtensionInfos = installExtensionInfos.filter(({ id, version }) => {
|
||||
const installedExtension = installed.find(i => areSameExtensions(i.identifier, { id }));
|
||||
if (installedExtension) {
|
||||
if (!force && (!version || (version === 'prerelease' && installedExtension.preRelease))) {
|
||||
this.logger.info(localize('alreadyInstalled-checkAndUpdate', "Extension '{0}' v{1} is already installed. Use '--force' option to update to latest version or provide '@<version>' to install a specific version, for example: '{2}@1.2.3'.", id, installedExtension.manifest.version, id));
|
||||
return false;
|
||||
}
|
||||
if (version && installedExtension.manifest.version === version) {
|
||||
this.logger.info(localize('alreadyInstalled', "Extension '{0}' is already installed.", `${id}@${version}`));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
if (installExtensionInfos.length) {
|
||||
const galleryExtensions = await this.getGalleryExtensions(installExtensionInfos);
|
||||
await Promise.all(installExtensionInfos.map(async extensionInfo => {
|
||||
const gallery = galleryExtensions.get(extensionInfo.id.toLowerCase());
|
||||
if (gallery) {
|
||||
try {
|
||||
const manifest = await this.installFromGallery(extensionInfo, gallery, installed);
|
||||
if (manifest) {
|
||||
installedExtensionsManifests.push(manifest);
|
||||
}
|
||||
} catch (err) {
|
||||
this.logger.error(err.message || err.stack || err);
|
||||
failed.push(extensionInfo.id);
|
||||
}
|
||||
} else {
|
||||
this.logger.error(`${notFound(extensionInfo.version ? `${extensionInfo.id}@${extensionInfo.version}` : extensionInfo.id)}\n${useId}`);
|
||||
failed.push(extensionInfo.id);
|
||||
}
|
||||
}));
|
||||
}
|
||||
const failedGalleryExtensions = await this.installGalleryExtensions(installExtensionInfos, installed, force);
|
||||
failed.push(...failedGalleryExtensions);
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error(localize('error while installing extensions', "Error while installing extensions: {0}", getErrorMessage(error)));
|
||||
|
@ -205,7 +169,81 @@ export class ExtensionManagementCLI {
|
|||
}
|
||||
}
|
||||
|
||||
private async installVSIX(vsix: URI, installOptions: InstallOptions, force: boolean, installedExtensions: ILocalExtension[]): Promise<IExtensionManifest | null> {
|
||||
private async installGalleryExtensions(installExtensionInfos: InstallGalleryExtensionInfo[], installed: ILocalExtension[], force: boolean): Promise<string[]> {
|
||||
installExtensionInfos = installExtensionInfos.filter(({ id, version }) => {
|
||||
const installedExtension = installed.find(i => areSameExtensions(i.identifier, { id }));
|
||||
if (installedExtension) {
|
||||
if (!force && (!version || (version === 'prerelease' && installedExtension.preRelease))) {
|
||||
this.logger.info(localize('alreadyInstalled-checkAndUpdate', "Extension '{0}' v{1} is already installed. Use '--force' option to update to latest version or provide '@<version>' to install a specific version, for example: '{2}@1.2.3'.", id, installedExtension.manifest.version, id));
|
||||
return false;
|
||||
}
|
||||
if (version && installedExtension.manifest.version === version) {
|
||||
this.logger.info(localize('alreadyInstalled', "Extension '{0}' is already installed.", `${id}@${version}`));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
if (!installExtensionInfos.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const failed: string[] = [];
|
||||
const extensionsToInstall: InstallExtensionInfo[] = [];
|
||||
const galleryExtensions = await this.getGalleryExtensions(installExtensionInfos);
|
||||
await Promise.all(installExtensionInfos.map(async ({ id, version, installOptions }) => {
|
||||
const gallery = galleryExtensions.get(id.toLowerCase());
|
||||
if (!gallery) {
|
||||
this.logger.error(`${notFound(version ? `${id}@${version}` : id)}\n${useId}`);
|
||||
failed.push(id);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const manifest = await this.extensionGalleryService.getManifest(gallery, CancellationToken.None);
|
||||
if (manifest && !this.validateExtensionKind(manifest)) {
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
this.logger.error(err.message || err.stack || err);
|
||||
failed.push(id);
|
||||
return;
|
||||
}
|
||||
const installedExtension = installed.find(e => areSameExtensions(e.identifier, gallery.identifier));
|
||||
if (installedExtension) {
|
||||
if (gallery.version === installedExtension.manifest.version) {
|
||||
this.logger.info(localize('alreadyInstalled', "Extension '{0}' is already installed.", version ? `${id}@${version}` : id));
|
||||
return;
|
||||
}
|
||||
this.logger.info(localize('updateMessage', "Updating the extension '{0}' to the version {1}", id, gallery.version));
|
||||
}
|
||||
if (installOptions.isBuiltin) {
|
||||
this.logger.info(version ? localize('installing builtin with version', "Installing builtin extension '{0}' v{1}...", id, version) : localize('installing builtin ', "Installing builtin extension '{0}'...", id));
|
||||
} else {
|
||||
this.logger.info(version ? localize('installing with version', "Installing extension '{0}' v{1}...", id, version) : localize('installing', "Installing extension '{0}'...", id));
|
||||
}
|
||||
extensionsToInstall.push({
|
||||
extension: gallery,
|
||||
options: { ...installOptions, installGivenVersion: !!version },
|
||||
});
|
||||
}));
|
||||
|
||||
if (extensionsToInstall.length) {
|
||||
const installationResult = await this.extensionManagementService.installGalleryExtensions(extensionsToInstall);
|
||||
for (const extensionResult of installationResult) {
|
||||
if (extensionResult.error) {
|
||||
this.logger.error(localize('errorInstallingExtension', "Error while installing extension {0}: {1}", extensionResult.identifier.id, getErrorMessage(extensionResult.error)));
|
||||
failed.push(extensionResult.identifier.id);
|
||||
} else {
|
||||
this.logger.info(localize('successInstall', "Extension '{0}' v{1} was successfully installed.", extensionResult.identifier.id, extensionResult.local?.manifest.version));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return failed;
|
||||
}
|
||||
|
||||
private async installVSIX(vsix: URI, installOptions: InstallOptions, force: boolean, installedExtensions: ILocalExtension[]): Promise<void> {
|
||||
|
||||
const manifest = await this.extensionManagementService.getManifest(vsix);
|
||||
if (!manifest) {
|
||||
|
@ -217,20 +255,17 @@ export class ExtensionManagementCLI {
|
|||
try {
|
||||
await this.extensionManagementService.install(vsix, installOptions);
|
||||
this.logger.info(localize('successVsixInstall', "Extension '{0}' was successfully installed.", basename(vsix)));
|
||||
return manifest;
|
||||
} catch (error) {
|
||||
if (isCancellationError(error)) {
|
||||
this.logger.info(localize('cancelVsixInstall', "Cancelled installing extension '{0}'.", basename(vsix)));
|
||||
return null;
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private async getGalleryExtensions(extensions: CLIInstallExtensionInfo[]): Promise<Map<string, IGalleryExtension>> {
|
||||
private async getGalleryExtensions(extensions: InstallGalleryExtensionInfo[]): Promise<Map<string, IGalleryExtension>> {
|
||||
const galleryExtensions = new Map<string, IGalleryExtension>();
|
||||
const preRelease = extensions.some(e => e.installOptions.installPreReleaseVersion);
|
||||
const targetPlatform = await this.extensionManagementService.getTargetPlatform();
|
||||
|
@ -249,41 +284,6 @@ export class ExtensionManagementCLI {
|
|||
return galleryExtensions;
|
||||
}
|
||||
|
||||
private async installFromGallery({ id, version, installOptions }: CLIInstallExtensionInfo, galleryExtension: IGalleryExtension, installed: ILocalExtension[]): Promise<IExtensionManifest | null> {
|
||||
const manifest = await this.extensionGalleryService.getManifest(galleryExtension, CancellationToken.None);
|
||||
if (manifest && !this.validateExtensionKind(manifest)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const installedExtension = installed.find(e => areSameExtensions(e.identifier, galleryExtension.identifier));
|
||||
if (installedExtension) {
|
||||
if (galleryExtension.version === installedExtension.manifest.version) {
|
||||
this.logger.info(localize('alreadyInstalled', "Extension '{0}' is already installed.", version ? `${id}@${version}` : id));
|
||||
return null;
|
||||
}
|
||||
this.logger.info(localize('updateMessage', "Updating the extension '{0}' to the version {1}", id, galleryExtension.version));
|
||||
}
|
||||
|
||||
try {
|
||||
if (installOptions.isBuiltin) {
|
||||
this.logger.info(version ? localize('installing builtin with version', "Installing builtin extension '{0}' v{1}...", id, version) : localize('installing builtin ', "Installing builtin extension '{0}'...", id));
|
||||
} else {
|
||||
this.logger.info(version ? localize('installing with version', "Installing extension '{0}' v{1}...", id, version) : localize('installing', "Installing extension '{0}'...", id));
|
||||
}
|
||||
|
||||
const local = await this.extensionManagementService.installFromGallery(galleryExtension, { ...installOptions, installGivenVersion: !!version });
|
||||
this.logger.info(localize('successInstall', "Extension '{0}' v{1} was successfully installed.", id, local.manifest.version));
|
||||
return manifest;
|
||||
} catch (error) {
|
||||
if (isCancellationError(error)) {
|
||||
this.logger.info(localize('cancelInstall', "Cancelled installing extension '{0}'.", id));
|
||||
return null;
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected validateExtensionKind(_manifest: IExtensionManifest): boolean {
|
||||
return true;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue