From e9d6c6afc0b99827d7732099ced7e3bb3101ca4c Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 12 Nov 2024 14:09:05 +0100 Subject: [PATCH] fix #232043 (#233596) * fix #232043 * fix compilation error * fix compilation * fix compilation * fix compilation * fix compilation * fix compilation --- cli/src/bin/code/legacy_args.rs | 14 ++++++- cli/src/commands/args.rs | 27 +++++++++++++ resources/completions/bash/code | 2 +- resources/completions/zsh/_code | 3 +- src/vs/code/node/cli.ts | 1 + src/vs/code/node/cliProcessMain.ts | 8 ++++ src/vs/platform/environment/common/argv.ts | 2 + src/vs/platform/environment/node/argv.ts | 2 + .../common/extensionManagementCLI.ts | 38 +++++++++++++++++++ .../api/browser/mainThreadCLICommands.ts | 4 +- 10 files changed, 97 insertions(+), 4 deletions(-) diff --git a/cli/src/bin/code/legacy_args.rs b/cli/src/bin/code/legacy_args.rs index 0bd92c92fd3..5a8e0f01071 100644 --- a/cli/src/bin/code/legacy_args.rs +++ b/cli/src/bin/code/legacy_args.rs @@ -7,7 +7,7 @@ use std::collections::HashMap; use cli::commands::args::{ CliCore, Commands, DesktopCodeOptions, ExtensionArgs, ExtensionSubcommand, - InstallExtensionArgs, ListExtensionArgs, UninstallExtensionArgs, + InstallExtensionArgs, ListExtensionArgs, UninstallExtensionArgs, DownloadExtensionArgs, }; /// Tries to parse the argv using the legacy CLI interface, looking for its @@ -64,6 +64,7 @@ pub fn try_parse_legacy( // Now translate them to subcommands. // --list-extensions -> ext list // --update-extensions -> update + // --download-extension -> ext download // --install-extension=id -> ext install // --uninstall-extension=id -> ext uninstall // --status -> status @@ -79,6 +80,17 @@ pub fn try_parse_legacy( })), ..Default::default() }) + } else if let Some(exts) = args.get("download-extension") { + Some(CliCore { + subcommand: Some(Commands::Extension(ExtensionArgs { + subcommand: ExtensionSubcommand::Download(DownloadExtensionArgs { + id: exts.to_vec(), + location: get_first_arg_value("location"), + }), + desktop_code_options, + })), + ..Default::default() + }) } else if let Some(exts) = args.remove("install-extension") { Some(CliCore { subcommand: Some(Commands::Extension(ExtensionArgs { diff --git a/cli/src/commands/args.rs b/cli/src/commands/args.rs index 21efc1c6667..3f4dfd9b7a4 100644 --- a/cli/src/commands/args.rs +++ b/cli/src/commands/args.rs @@ -272,6 +272,8 @@ pub enum ExtensionSubcommand { Uninstall(UninstallExtensionArgs), /// Update the installed extensions. Update, + /// Download an extension. + Download(DownloadExtensionArgs), } impl ExtensionSubcommand { @@ -305,6 +307,16 @@ impl ExtensionSubcommand { ExtensionSubcommand::Update => { target.push("--update-extensions".to_string()); } + ExtensionSubcommand::Download(args) => { + for id in args.id.iter() { + target.push(format!("--download-extension={id}")); + } + if let Some(location) = &args.location { + if !location.is_empty() { + target.push(format!("--location={location}")); + } + } + } } } } @@ -347,6 +359,21 @@ pub struct UninstallExtensionArgs { pub id: Vec, } +#[derive(Args, Debug, Clone)] +pub struct DownloadExtensionArgs { + /// Id of the extension to download. The identifier of an + /// extension is '${publisher}.${name}'. Should provide '--location' to specify the location to download the VSIX. + /// To download a specific version provide '@${version}'. + /// For example: 'vscode.csharp@1.2.3'. + #[clap(name = "ext-id")] + pub id: Vec, + + /// Specify the location to download the VSIX. + #[clap(long, value_name = "location")] + pub location: Option, + +} + #[derive(Args, Debug, Clone)] pub struct VersionArgs { #[clap(subcommand)] diff --git a/resources/completions/bash/code b/resources/completions/bash/code index d141c297b96..92b1b4cfcfb 100644 --- a/resources/completions/bash/code +++ b/resources/completions/bash/code @@ -49,7 +49,7 @@ _@@APPNAME@@() --list-extensions --show-versions --install-extension --uninstall-extension --enable-proposed-api --verbose --log -s --status -p --performance --prof-startup --disable-extensions - --disable-extension --inspect-extensions --update-extensions + --disable-extension --inspect-extensions --update-extensions --download-extension --inspect-brk-extensions --disable-gpu' -- "$cur") ) [[ $COMPREPLY == *= ]] && compopt -o nospace return diff --git a/resources/completions/zsh/_code b/resources/completions/zsh/_code index eafa37c81c7..f49211447ba 100644 --- a/resources/completions/zsh/_code +++ b/resources/completions/zsh/_code @@ -20,8 +20,9 @@ arguments=( '--category[filters installed extension list by category, when using --list-extensions]' '--show-versions[show versions of installed extensions, when using --list-extensions]' '--install-extension[install an extension]:id or path:_files -g "*.vsix(-.)"' - '--uninstall-extension[uninstall an extension]:id or path:_files -g "*.vsix(-.)"' + '--uninstall-extension[uninstall an extension]:id' '--update-extensions[update the installed extensions]' + '--download-extension[download an extension]:id' '--enable-proposed-api[enables proposed API features for extensions]::extension id: ' '--verbose[print verbose output (implies --wait)]' '--log[log level to use]:level [info]:(critical error warn info debug trace off)' diff --git a/src/vs/code/node/cli.ts b/src/vs/code/node/cli.ts index e91bea07d6a..39033f27227 100644 --- a/src/vs/code/node/cli.ts +++ b/src/vs/code/node/cli.ts @@ -33,6 +33,7 @@ function shouldSpawnCliProcess(argv: NativeParsedArgs): boolean { return !!argv['install-source'] || !!argv['list-extensions'] || !!argv['install-extension'] + || !!argv['download-extension'] || !!argv['uninstall-extension'] || !!argv['update-extensions'] || !!argv['locate-extension'] diff --git a/src/vs/code/node/cliProcessMain.ts b/src/vs/code/node/cliProcessMain.ts index d3b20ecea47..bbca94ab07c 100644 --- a/src/vs/code/node/cliProcessMain.ts +++ b/src/vs/code/node/cliProcessMain.ts @@ -282,6 +282,14 @@ class CliMain extends Disposable { return instantiationService.createInstance(ExtensionManagementCLI, new ConsoleLogger(LogLevel.Info, false)).listExtensions(!!this.argv['show-versions'], this.argv['category'], profileLocation); } + // Download Extensions + else if (this.argv['download-extension']) { + if (!this.argv['location']) { + throw new Error('The location argument is required to download an extension.'); + } + return instantiationService.createInstance(ExtensionManagementCLI, new ConsoleLogger(LogLevel.Info, false)).downloadExtensions(this.argv['download-extension'], URI.parse(this.argv['location'])); + } + // Install Extension else if (this.argv['install-extension'] || this.argv['install-builtin-extension']) { const installOptions: InstallOptions = { isMachineScoped: !!this.argv['do-not-sync'], installPreReleaseVersion: !!this.argv['pre-release'], profileLocation }; diff --git a/src/vs/platform/environment/common/argv.ts b/src/vs/platform/environment/common/argv.ts index 818021ff0c1..f2ad98bdd17 100644 --- a/src/vs/platform/environment/common/argv.ts +++ b/src/vs/platform/environment/common/argv.ts @@ -75,6 +75,8 @@ export interface NativeParsedArgs { 'disable-extensions'?: boolean; 'disable-extension'?: string[]; // undefined or array of 1 or more 'list-extensions'?: boolean; + 'download-extension'?: string[]; + 'location'?: string; 'show-versions'?: boolean; 'category'?: string; 'install-extension'?: string[]; // undefined or array of 1 or more diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index 379e327c646..48809d5e894 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -97,6 +97,7 @@ export const OPTIONS: OptionDescriptions> = { 'list-extensions': { type: 'boolean', cat: 'e', description: localize('listExtensions', "List the installed extensions.") }, 'show-versions': { type: 'boolean', cat: 'e', description: localize('showVersions', "Show versions of installed extensions, when using --list-extensions.") }, 'category': { type: 'string', allowEmptyValue: true, cat: 'e', description: localize('category', "Filters installed extensions by provided category, when using --list-extensions."), args: 'category' }, + 'download-extension': { type: 'string[]', cat: 'e', args: 'ext-id', description: localize('downloadExtension', "Downloads the extension VSIX that can be installable. The argument is an identifier of an extension that is '${publisher}.${name}'. To download a specific version provide '@${version}'. For example: 'vscode.csharp@1.2.3'. Should provide '--location' to specify the location to download the VSIX.") }, 'install-extension': { type: 'string[]', cat: 'e', args: 'ext-id | path', description: localize('installExtension', "Installs or updates an extension. The argument is either an extension id or a path to a VSIX. The identifier of an extension is '${publisher}.${name}'. Use '--force' argument to update to latest version. To install a specific version provide '@${version}'. For example: 'vscode.csharp@1.2.3'.") }, 'pre-release': { type: 'boolean', cat: 'e', description: localize('install prerelease', "Installs the pre-release version of the extension, when using --install-extension") }, 'uninstall-extension': { type: 'string[]', cat: 'e', args: 'ext-id', description: localize('uninstallExtension', "Uninstalls an extension.") }, @@ -163,6 +164,7 @@ export const OPTIONS: OptionDescriptions> = { 'file-chmod': { type: 'boolean' }, 'install-builtin-extension': { type: 'string[]' }, 'force': { type: 'boolean' }, + 'location': { type: 'string' }, 'do-not-sync': { type: 'boolean' }, 'trace': { type: 'boolean' }, 'trace-category-filter': { type: 'string' }, diff --git a/src/vs/platform/extensionManagement/common/extensionManagementCLI.ts b/src/vs/platform/extensionManagement/common/extensionManagementCLI.ts index 69ba77c8c84..36a9901341a 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagementCLI.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagementCLI.ts @@ -14,6 +14,7 @@ import { EXTENSION_IDENTIFIER_REGEX, IExtensionGalleryService, IExtensionInfo, I import { areSameExtensions, getExtensionId, getGalleryExtensionId, getIdAndVersion } from './extensionManagementUtil.js'; import { ExtensionType, EXTENSION_CATEGORIES, IExtensionManifest } from '../../extensions/common/extensions.js'; import { ILogger } from '../../log/common/log.js'; +import { IUriIdentityService } from '../../uriIdentity/common/uriIdentity.js'; const notFound = (id: string) => localize('notFound', "Extension '{0}' not found.", id); @@ -28,6 +29,7 @@ export class ExtensionManagementCLI { protected readonly logger: ILogger, @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, + @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, ) { } protected get location(): string | undefined { @@ -70,6 +72,42 @@ export class ExtensionManagementCLI { } } + public async downloadExtensions(extensions: string[], target: URI): Promise { + if (!extensions.length) { + return; + } + + this.logger.info(localize('downloadingExtensions', "Downloading extensions...")); + + const extensionsInfo: IExtensionInfo[] = []; + for (const extension of extensions) { + const [id, version] = getIdAndVersion(extension); + extensionsInfo.push({ id, version: version !== 'prerelease' ? version : undefined, preRelease: version === 'prerelease' }); + } + + try { + const galleryExtensions = await this.extensionGalleryService.getExtensions(extensionsInfo, CancellationToken.None); + const targetPlatform = await this.extensionManagementService.getTargetPlatform(); + await Promise.allSettled(extensionsInfo.map(async extensionInfo => { + const galleryExtension = galleryExtensions.find(e => areSameExtensions(e.identifier, { id: extensionInfo.id })); + if (!galleryExtension) { + this.logger.error(`${notFound(extensionInfo.id)}\n${useId}`); + return; + } + const compatible = await this.extensionGalleryService.getCompatibleExtension(galleryExtension, !!extensionInfo.hasPreRelease, targetPlatform); + try { + await this.extensionGalleryService.download(compatible ?? galleryExtension, this.uriIdentityService.extUri.joinPath(target, `${galleryExtension.identifier.id}-${galleryExtension.version}.vsix`), InstallOperation.None); + this.logger.info(localize('successDownload', "Extension '{0}' was successfully downloaded.", extensionInfo.id)); + } catch (error) { + this.logger.error(localize('error while downloading extension', "Error while downloading extension '{0}': {1}", extensionInfo.id, getErrorMessage(error))); + } + })); + } catch (error) { + this.logger.error(localize('error while downloading extensions', "Error while downloading extensions: {0}", getErrorMessage(error))); + throw error; + } + } + public async installExtensions(extensions: (string | URI)[], builtinExtensions: (string | URI)[], installOptions: InstallOptions, force: boolean): Promise { const failed: string[] = []; diff --git a/src/vs/workbench/api/browser/mainThreadCLICommands.ts b/src/vs/workbench/api/browser/mainThreadCLICommands.ts index be73afd9fd4..eaf95ea867c 100644 --- a/src/vs/workbench/api/browser/mainThreadCLICommands.ts +++ b/src/vs/workbench/api/browser/mainThreadCLICommands.ts @@ -18,6 +18,7 @@ import { ServiceCollection } from '../../../platform/instantiation/common/servic import { ILabelService } from '../../../platform/label/common/label.js'; import { AbstractMessageLogger, ILogger, LogLevel } from '../../../platform/log/common/log.js'; import { IOpenerService } from '../../../platform/opener/common/opener.js'; +import { IUriIdentityService } from '../../../platform/uriIdentity/common/uriIdentity.js'; import { IOpenWindowOptions, IWindowOpenable } from '../../../platform/window/common/window.js'; import { IWorkbenchEnvironmentService } from '../../services/environment/common/environmentService.js'; import { IExtensionManagementServerService } from '../../services/extensionManagement/common/extensionManagement.js'; @@ -103,11 +104,12 @@ class RemoteExtensionManagementCLI extends ExtensionManagementCLI { logger: ILogger, @IExtensionManagementService extensionManagementService: IExtensionManagementService, @IExtensionGalleryService extensionGalleryService: IExtensionGalleryService, + @IUriIdentityService uriIdentityService: IUriIdentityService, @ILabelService labelService: ILabelService, @IWorkbenchEnvironmentService envService: IWorkbenchEnvironmentService, @IExtensionManifestPropertiesService private readonly _extensionManifestPropertiesService: IExtensionManifestPropertiesService, ) { - super(logger, extensionManagementService, extensionGalleryService); + super(logger, extensionManagementService, extensionGalleryService, uriIdentityService); const remoteAuthority = envService.remoteAuthority; this._location = remoteAuthority ? labelService.getHostLabel(Schemas.vscodeRemote, remoteAuthority) : undefined;