mirror of
https://github.com/microsoft/vscode.git
synced 2026-07-10 16:49:36 +01:00
Merge pull request #199893 from samhanic/cli-extension-update
[CLI] extension update command
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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']);
|
||||
|
||||
|
||||
@@ -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'];
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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']);
|
||||
|
||||
@@ -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[];
|
||||
|
||||
Reference in New Issue
Block a user