Merge pull request #199893 from samhanic/cli-extension-update

[CLI] extension update command
This commit is contained in:
Sandeep Somavarapu
2024-01-08 12:48:07 +05:30
committed by GitHub
14 changed files with 89 additions and 10 deletions
+9
View File
@@ -60,6 +60,7 @@ pub fn try_parse_legacy(
// Now translate them to subcommands.
// --list-extensions -> ext list
// --update-extensions -> update
// --install-extension=id -> ext install <id>
// --uninstall-extension=id -> ext uninstall <id>
// --status -> status
@@ -87,6 +88,14 @@ pub fn try_parse_legacy(
})),
..Default::default()
})
} else if let Some(_exts) = args.remove("update-extensions") {
Some(CliCore {
subcommand: Some(Commands::Extension(ExtensionArgs {
subcommand: ExtensionSubcommand::Update,
desktop_code_options,
})),
..Default::default()
})
} else if let Some(exts) = args.remove("uninstall-extension") {
Some(CliCore {
subcommand: Some(Commands::Extension(ExtensionArgs {
+5
View File
@@ -254,6 +254,8 @@ pub enum ExtensionSubcommand {
Install(InstallExtensionArgs),
/// Uninstall an extension.
Uninstall(UninstallExtensionArgs),
/// Update the installed extensions.
Update,
}
impl ExtensionSubcommand {
@@ -284,6 +286,9 @@ impl ExtensionSubcommand {
target.push(format!("--uninstall-extension={}", id));
}
}
ExtensionSubcommand::Update => {
target.push("--update-extensions".to_string());
}
}
}
}
+4
View File
@@ -58,6 +58,7 @@ pub struct CodeServerArgs {
// extension management
pub install_extensions: Vec<String>,
pub uninstall_extensions: Vec<String>,
pub update_extensions: bool,
pub list_extensions: bool,
pub show_versions: bool,
pub category: Option<String>,
@@ -129,6 +130,9 @@ impl CodeServerArgs {
for extension in &self.uninstall_extensions {
args.push(format!("--uninstall-extension={}", extension));
}
if self.update_extensions {
args.push(String::from("--update-extensions"));
}
if self.list_extensions {
args.push(String::from("--list-extensions"));
if self.show_versions {
+1 -1
View File
@@ -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
--disable-extension --inspect-extensions --update-extensions
--inspect-brk-extensions --disable-gpu' -- "$cur") )
[[ $COMPREPLY == *= ]] && compopt -o nospace
return
+1
View File
@@ -21,6 +21,7 @@ arguments=(
'--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(-.)"'
'--update-extensions[update the installed extensions]'
'--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)'
+2 -2
View File
@@ -20,7 +20,7 @@ async function start() {
// Do a quick parse to determine if a server or the cli needs to be started
const parsedArgs = minimist(process.argv.slice(2), {
boolean: ['start-server', 'list-extensions', 'print-ip-address', 'help', 'version', 'accept-server-license-terms'],
boolean: ['start-server', 'list-extensions', 'print-ip-address', 'help', 'version', 'accept-server-license-terms', 'update-extensions'],
string: ['install-extension', 'install-builtin-extension', 'uninstall-extension', 'locate-extension', 'socket-path', 'host', 'port', 'compatibility'],
alias: { help: 'h', version: 'v' }
});
@@ -34,7 +34,7 @@ async function start() {
});
const extensionLookupArgs = ['list-extensions', 'locate-extension'];
const extensionInstallArgs = ['install-extension', 'install-builtin-extension', 'uninstall-extension'];
const extensionInstallArgs = ['install-extension', 'install-builtin-extension', 'uninstall-extension', 'update-extensions'];
const shouldSpawnCli = parsedArgs.help || parsedArgs.version || extensionLookupArgs.some(a => !!parsedArgs[a]) || (extensionInstallArgs.some(a => !!parsedArgs[a]) && !parsedArgs['start-server']);
+1
View File
@@ -34,6 +34,7 @@ function shouldSpawnCliProcess(argv: NativeParsedArgs): boolean {
|| !!argv['list-extensions']
|| !!argv['install-extension']
|| !!argv['uninstall-extension']
|| !!argv['update-extensions']
|| !!argv['locate-extension']
|| !!argv['telemetry'];
}
+4
View File
@@ -280,6 +280,10 @@ class CliMain extends Disposable {
return instantiationService.createInstance(ExtensionManagementCLI, new ConsoleLogger(LogLevel.Info, false)).uninstallExtensions(this.asExtensionIdOrVSIX(this.argv['uninstall-extension']), !!this.argv['force'], profileLocation);
}
else if (this.argv['update-extensions']) {
return instantiationService.createInstance(ExtensionManagementCLI, new ConsoleLogger(LogLevel.Info, false)).updateExtensions(profileLocation);
}
// Locate Extension
else if (this.argv['locate-extension']) {
return instantiationService.createInstance(ExtensionManagementCLI, new ConsoleLogger(LogLevel.Info, false)).locateExtension(this.argv['locate-extension']);
@@ -82,6 +82,7 @@ export interface NativeParsedArgs {
'pre-release'?: boolean;
'install-builtin-extension'?: string[]; // undefined or array of 1 or more
'uninstall-extension'?: string[]; // undefined or array of 1 or more
'update-extensions'?: boolean;
'locate-extension'?: string[]; // undefined or array of 1 or more
'enable-proposed-api'?: string[]; // undefined or array of 1 or more
'open-url'?: boolean;
+1
View File
@@ -100,6 +100,7 @@ export const OPTIONS: OptionDescriptions<Required<NativeParsedArgs>> = {
'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.") },
'update-extensions': { type: 'boolean', cat: 'e', description: localize('updateExtensions', "Update the installed extensions.") },
'enable-proposed-api': { type: 'string[]', allowEmptyValue: true, cat: 'e', args: 'ext-id', description: localize('experimentalApis', "Enables proposed API features for extensions. Can receive one or more extension IDs to enable individually.") },
'version': { type: 'boolean', cat: 't', alias: 'v', description: localize('version', "Print version.") },
@@ -10,7 +10,7 @@ import { basename } from 'vs/base/common/resources';
import { gt } from 'vs/base/common/semver/semver';
import { URI } from 'vs/base/common/uri';
import { localize } from 'vs/nls';
import { EXTENSION_IDENTIFIER_REGEX, IExtensionGalleryService, IExtensionInfo, IExtensionManagementService, IGalleryExtension, ILocalExtension, InstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement';
import { EXTENSION_IDENTIFIER_REGEX, IExtensionGalleryService, IExtensionInfo, IExtensionManagementService, IGalleryExtension, ILocalExtension, InstallOptions, InstallExtensionInfo, InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement';
import { areSameExtensions, getExtensionId, getGalleryExtensionId, getIdAndVersion } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { ExtensionType, EXTENSION_CATEGORIES, IExtensionManifest } from 'vs/platform/extensions/common/extensions';
import { ILogger } from 'vs/platform/log/common/log';
@@ -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 InstallExtensionInfo = { id: string; version?: string; installOptions: InstallOptions };
type CLIInstallExtensionInfo = { id: string; version?: string; installOptions: InstallOptions };
export class ExtensionManagementCLI {
@@ -80,7 +80,7 @@ export class ExtensionManagementCLI {
}
const installVSIXInfos: InstallVSIXInfo[] = [];
let installExtensionInfos: InstallExtensionInfo[] = [];
let installExtensionInfos: CLIInstallExtensionInfo[] = [];
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 } });
};
@@ -163,6 +163,48 @@ export class ExtensionManagementCLI {
}
}
public async updateExtensions(profileLocation?: URI): Promise<void> {
const installedExtensions = await this.extensionManagementService.getInstalled(ExtensionType.User, profileLocation);
const installedExtensionsQuery: IExtensionInfo[] = [];
for (const extension of installedExtensions) {
if (!!extension.identifier.uuid) { // No need to check new version for an unpublished extension
installedExtensionsQuery.push({ ...extension.identifier, preRelease: extension.preRelease });
}
}
this.logger.trace(localize('updateExtensionsQuery', "Fetching latest versions for {0} extensions", installedExtensionsQuery.length));
const availableVersions = await this.extensionGalleryService.getExtensions(installedExtensionsQuery, { compatible: true }, CancellationToken.None);
const extensionsToUpdate: InstallExtensionInfo[] = [];
for (const newVersion of availableVersions) {
for (const oldVersion of installedExtensions) {
if (areSameExtensions(oldVersion.identifier, newVersion.identifier) && gt(newVersion.version, oldVersion.manifest.version)) {
extensionsToUpdate.push({
extension: newVersion,
options: { operation: InstallOperation.Update, installPreReleaseVersion: oldVersion.isPreReleaseVersion }
});
}
}
}
if (!extensionsToUpdate.length) {
this.logger.info(localize('updateExtensionsNoExtensions', "No extension to update"));
return;
}
this.logger.info(localize('updateExtensionsNewVersionsAvailable', "Updating extensions: {0}", extensionsToUpdate.map(ext => ext.extension.identifier.id).join(', ')));
const installationResult = await this.extensionManagementService.installGalleryExtensions(extensionsToUpdate);
for (const extensionResult of installationResult) {
if (extensionResult.error) {
this.logger.error(localize('errorUpdatingExtension', "Error while updating extension {0}: {1}", extensionResult.identifier.id, getErrorMessage(extensionResult.error)));
} else {
this.logger.info(localize('successUpdate', "Extension '{0}' v{1} was successfully updated.", extensionResult.identifier.id, extensionResult.local?.manifest.version));
}
}
}
private async installVSIX(vsix: URI, installOptions: InstallOptions, force: boolean, installedExtensions: ILocalExtension[]): Promise<IExtensionManifest | null> {
const manifest = await this.extensionManagementService.getManifest(vsix);
@@ -188,7 +230,7 @@ export class ExtensionManagementCLI {
return null;
}
private async getGalleryExtensions(extensions: InstallExtensionInfo[]): Promise<Map<string, IGalleryExtension>> {
private async getGalleryExtensions(extensions: CLIInstallExtensionInfo[]): Promise<Map<string, IGalleryExtension>> {
const galleryExtensions = new Map<string, IGalleryExtension>();
const preRelease = extensions.some(e => e.installOptions.installPreReleaseVersion);
const targetPlatform = await this.extensionManagementService.getTargetPlatform();
@@ -207,7 +249,7 @@ export class ExtensionManagementCLI {
return galleryExtensions;
}
private async installFromGallery({ id, version, installOptions }: InstallExtensionInfo, galleryExtension: IGalleryExtension, installed: ILocalExtension[]): Promise<IExtensionManifest | null> {
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;
@@ -159,6 +159,11 @@ class CliMain extends Disposable {
return extensionManagementCLI.uninstallExtensions(this.asExtensionIdOrVSIX(this.args['uninstall-extension']), !!this.args['force']);
}
// Update the installed extensions
else if (this.args['update-extensions']) {
return extensionManagementCLI.updateExtensions();
}
// Locate Extension
else if (this.args['locate-extension']) {
return extensionManagementCLI.locateExtension(this.args['locate-extension']);
+6 -2
View File
@@ -67,6 +67,7 @@ const isSupportedForPipe = (optionId: keyof RemoteParsedArgs) => {
case 'status':
case 'install-extension':
case 'uninstall-extension':
case 'update-extensions':
case 'list-extensions':
case 'force':
case 'show-versions':
@@ -217,7 +218,7 @@ export async function main(desc: ProductDescription, args: string[]): Promise<vo
}
if (cliCommand) {
if (parsedArgs['install-extension'] !== undefined || parsedArgs['uninstall-extension'] !== undefined || parsedArgs['list-extensions']) {
if (parsedArgs['install-extension'] !== undefined || parsedArgs['uninstall-extension'] !== undefined || parsedArgs['list-extensions'] || parsedArgs['update-extensions']) {
const cmdLine: string[] = [];
parsedArgs['install-extension']?.forEach(id => cmdLine.push('--install-extension', id));
parsedArgs['uninstall-extension']?.forEach(id => cmdLine.push('--uninstall-extension', id));
@@ -227,6 +228,9 @@ export async function main(desc: ProductDescription, args: string[]): Promise<vo
cmdLine.push(`--${opt}=${value}`);
}
});
if (parsedArgs['update-extensions']) {
cmdLine.push('--update-extensions');
}
const cp = _cp.fork(join(__dirname, '../../../server-main.js'), cmdLine, { stdio: 'inherit' });
cp.on('error', err => console.log(err));
@@ -293,7 +297,7 @@ export async function main(desc: ProductDescription, args: string[]): Promise<vo
return;
}
if (parsedArgs['install-extension'] !== undefined || parsedArgs['uninstall-extension'] !== undefined || parsedArgs['list-extensions']) {
if (parsedArgs['install-extension'] !== undefined || parsedArgs['uninstall-extension'] !== undefined || parsedArgs['list-extensions'] || parsedArgs['update-extensions']) {
sendToPipe({
type: 'extensionManagement',
list: parsedArgs['list-extensions'] ? { showVersions: parsedArgs['show-versions'], category: parsedArgs['category'] } : undefined,
@@ -59,6 +59,7 @@ export const serverOptions: OptionDescriptions<Required<ServerParsedArgs>> = {
'builtin-extensions-dir': OPTIONS['builtin-extensions-dir'],
'install-extension': OPTIONS['install-extension'],
'install-builtin-extension': OPTIONS['install-builtin-extension'],
'update-extensions': OPTIONS['update-extensions'],
'uninstall-extension': OPTIONS['uninstall-extension'],
'list-extensions': OPTIONS['list-extensions'],
'locate-extension': OPTIONS['locate-extension'],
@@ -177,6 +178,7 @@ export interface ServerParsedArgs {
'builtin-extensions-dir'?: string;
'install-extension'?: string[];
'install-builtin-extension'?: string[];
'update-extensions'?: boolean;
'uninstall-extension'?: string[];
'list-extensions'?: boolean;
'locate-extension'?: string[];