Skip to content
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

fix #232043 #233596

Merged
merged 7 commits into from
Nov 12, 2024
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
14 changes: 13 additions & 1 deletion cli/src/bin/code/legacy_args.rs
Original file line number Diff line number Diff line change
@@ -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 <id>
// --install-extension=id -> ext install <id>
// --uninstall-extension=id -> ext uninstall <id>
// --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 {
27 changes: 27 additions & 0 deletions cli/src/commands/args.rs
Original file line number Diff line number Diff line change
@@ -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<String>,
}

#[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<String>,

/// Specify the location to download the VSIX.
#[clap(long, value_name = "location")]
pub location: Option<String>,

}

#[derive(Args, Debug, Clone)]
pub struct VersionArgs {
#[clap(subcommand)]
2 changes: 1 addition & 1 deletion resources/completions/bash/code
Original file line number Diff line number Diff line change
@@ -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
3 changes: 2 additions & 1 deletion resources/completions/zsh/_code
Original file line number Diff line number Diff line change
@@ -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)'
1 change: 1 addition & 0 deletions src/vs/code/node/cli.ts
Original file line number Diff line number Diff line change
@@ -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']
8 changes: 8 additions & 0 deletions src/vs/code/node/cliProcessMain.ts
Original file line number Diff line number Diff line change
@@ -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 };
2 changes: 2 additions & 0 deletions src/vs/platform/environment/common/argv.ts
Original file line number Diff line number Diff line change
@@ -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
2 changes: 2 additions & 0 deletions src/vs/platform/environment/node/argv.ts
Original file line number Diff line number Diff line change
@@ -97,6 +97,7 @@ export const OPTIONS: OptionDescriptions<Required<NativeParsedArgs>> = {
'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<Required<NativeParsedArgs>> = {
'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' },
Original file line number Diff line number Diff line change
@@ -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<void> {
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<void> {
const failed: string[] = [];

4 changes: 3 additions & 1 deletion src/vs/workbench/api/browser/mainThreadCLICommands.ts
Original file line number Diff line number Diff line change
@@ -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;
Loading
Oops, something went wrong.