Change:
<!-- Please search existing issues to avoid creating duplicates, and review our troubleshooting tips: --<!-- Please attach logs to help us diagnose your issue. Learn more here: and here: --<!-- Also please test using the latest insiders build to make sure your issue has not already been fixed: --- VSCode Version: 1.85.1 Local OS Version: Ubuntu 23.10 Remote OS Version: Remote Extension/Connection Type: Containers <!-- If your bug is related to Remote-SSH, please use the SSH-specific reporter: --Steps to Reproduce: the following file: / rebuild the container in vscode installed python extension will randomly be the latest one (currently v2023.22.1) instead of the pinned one. I suspect that this could be triggered by the fact that the extension depends on . Here's a log of a faulty install: <!-- Check to see if the problem is general, with a specific extension, or only happens when remote --Does this issue occur when you try this locally?: Not tested <!-- If your issue only appears in Codespaces, please visit: --
Thanks for opening. This sounds like a key potential insight. curious to get your thoughts here.
I think you mentioned that version pinning doesn't work from the server CLI?
Extension pinning is supported in CLI now. But this would have been caused by the extension dependency from extension as mentioned.
Thanks for creating this issue! It looks like you may be using an old version of VS Code, the latest stable release is 1.85.2. Please try upgrading to the latest version and checking whether this issue remains. Happy Coding!

Discussion:
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'; <del> import { adoptToGalleryExtensionId, areSameExtensions, ExtensionKey, getGalleryExtensionId, getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; </del> <ins> import { areSameExtensions, ExtensionKey, getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; </ins> 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';

protected async installExtensions(extensions: InstallableExtension[]): Promise<InstallExtensionResult[]> { const results: InstallExtensionResult[] = []; <del> 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 }); } })); 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() </del> <ins> 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: [] }); } </ins> }; <del> 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 []; </del> <ins> try { // 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 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 { createInstallExtensionTask(manifest, extension, installExtensionTaskOptions); } </ins> } <del> } 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`); } })); </del> <ins> // 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 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); </ins> } <del> } 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); </del> } <del> if (isNonEmptyArray(manifest.extensionPack)) { this.logService.warn(`Cannot install packed extensions of extension:`, installExtensionTask.identifier.id, error.message); </del> <ins> } catch (error) { // 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; </ins> } <del> } else { this.logService.error('Error while preparing to install dependencies and extension packs of the extension:', installExtensionTask.identifier.id); throw error; </del> } } <del> } 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)); } else { this.logService.info('Found extensions with circular dependencies', extensionsWithoutDepsToInstall.map(({ task }) => task.identifier.id)); extensionsToInstall = [...extensionsToInstallMap.values()]; } </del> <ins> })); </ins> <del> // Install extensions in parallel and wait until all extensions are installed / failed await this.joinAllSettled(extensionsToInstall.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, 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 */ } } </del> <ins> // 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 */ } </ins> } <ins> } </ins> <del> 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()); } })); } </del> <ins> 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; } })); </ins> if (alreadyRequestedInstallations.length) { await this.joinAllSettled(alreadyRequestedInstallations); } <del> installResults.forEach(({ identifier }) => this.logService.info(`Extension installed successfully:`, identifier.id)); return installResults; </del> <ins> for (const result of successResults) { this.logService.info(`Extension installed successfully:`, result.identifier.id); results.push(result); } return results; </ins> } catch (error) { <del> // cancel all tasks allInstallExtensionTasks.forEach(({ task }) => task.cancel()); </del> // rollback installed extensions <del> 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)); } </del> <ins> 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)); </ins> } <del> } catch (error) { // ignore error this.logService.warn('Error while rolling back extensions', getErrorMessage(error), installResults.map(({ identifier }) => identifier.id)); } </del> <ins> })); </ins> } <del> return allInstallExtensionTasks.map(({ task }) => ({ identifier: task.identifier, operation: InstallOperation.Install, source: task.source, context: installExtensionTaskOptions.context, profileLocation: installExtensionTaskOptions.profileLocation, error })); </del> <ins> // 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; </ins> } finally { // Finally, remove all the tasks from the cache <del> for (const { task } of allInstallExtensionTasks) { </del> <ins> for (const { task } of installingExtensionsMap.values()) { </ins> if (task.source && !URI.isUri(task.source)) { <del> this.installingExtensions.delete(getInstallExtensionTaskKey(task.source)); </del> <ins> this.installingExtensions.delete(getInstallExtensionTaskKey(task.source, task.profileLocation)); </ins> } } <ins> if (results.length) { this._onDidInstallExtensions.fire(results); } </ins> } }

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 }; <del> type CLIInstallExtensionInfo = { id: string; version?: string; installOptions: InstallOptions }; </del> <ins> type InstallGalleryExtensionInfo = { id: string; version?: string; installOptions: InstallOptions }; </ins> export class ExtensionManagementCLI {

const failed: string[] = []; try { <del> const installedExtensionsManifests: IExtensionManifest[] = []; </del> if (extensions.length) { this.logger.info(this.location ? localize('installingExtensionsOnLocation', "Installing extensions on {0}...", this.location) : localize('installingExtensions', "Installing extensions...")); } const installVSIXInfos: InstallVSIXInfo[] = []; <del> let installExtensionInfos: CLIInstallExtensionInfo[] = []; </del> <ins> const installExtensionInfos: InstallGalleryExtensionInfo[] = []; </ins> 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 } }); };

if (installVSIXInfos.length) { await Promise.all(installVSIXInfos.map(async ({ vsix, installOptions }) => { try { <del> const manifest = await this.installVSIX(vsix, installOptions, force, installed); if (manifest) { installedExtensionsManifests.push(manifest); } </del> <ins> await this.installVSIX(vsix, installOptions, force, installed); </ins> } catch (err) { this.logger.error(err); failed.push(vsix.toString());

} if (installExtensionInfos.length) { <del> 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); } })); } </del> <ins> const failedGalleryExtensions = await this.installGalleryExtensions(installExtensionInfos, installed, force); failed.push(...failedGalleryExtensions); </ins> } } catch (error) { this.logger.error(localize('error while installing extensions', "Error while installing extensions: {0}", getErrorMessage(error)));

} } <del> private async installVSIX(vsix: URI, installOptions: InstallOptions, force: boolean, installedExtensions: ILocalExtension[]): Promise<IExtensionManifest | null> { </del> <ins> 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> { </ins> const manifest = await this.extensionManagementService.getManifest(vsix); if (!manifest) {

try { await this.extensionManagementService.install(vsix, installOptions); this.logger.info(localize('successVsixInstall', "Extension '{0}' was successfully installed.", basename(vsix))); <del> return manifest; </del> } catch (error) { if (isCancellationError(error)) { this.logger.info(localize('cancelVsixInstall', "Cancelled installing extension '{0}'.", basename(vsix))); <del> return null; </del> } else { throw error; } } } <del> return null; </del> } <del> private async getGalleryExtensions(extensions: CLIInstallExtensionInfo[]): Promise<Map<string, IGalleryExtension>> { </del> <ins> private async getGalleryExtensions(extensions: InstallGalleryExtensionInfo[]): Promise<Map<string, IGalleryExtension>> { </ins> const galleryExtensions = new Map<string, IGalleryExtension>(); const preRelease = extensions.some(e => e.installOptions.installPreReleaseVersion); const targetPlatform = await this.extensionManagementService.getTargetPlatform();

return galleryExtensions; } <del> 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; } } } </del> protected validateExtensionKind(_manifest: IExtensionManifest): boolean { return true; }