vscode/src/vs/platform/extensionManagement/common/extensionManagement.ts

491 lines
17 KiB
TypeScript

/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { CancellationToken } from 'vs/base/common/cancellation';
import { IStringDictionary } from 'vs/base/common/collections';
import { Event } from 'vs/base/common/event';
import { FileAccess } from 'vs/base/common/network';
import { IPager } from 'vs/base/common/paging';
import { Platform } from 'vs/base/common/platform';
import { URI } from 'vs/base/common/uri';
import { localize } from 'vs/nls';
import { ExtensionType, IExtension, IExtensionManifest } from 'vs/platform/extensions/common/extensions';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
export const EXTENSION_IDENTIFIER_PATTERN = '^([a-z0-9A-Z][a-z0-9-A-Z]*)\\.([a-z0-9A-Z][a-z0-9-A-Z]*)$';
export const EXTENSION_IDENTIFIER_REGEX = new RegExp(EXTENSION_IDENTIFIER_PATTERN);
export const WEB_EXTENSION_TAG = '__web_extension';
export const enum TargetPlatform {
WIN32_X64 = 'win32-x64',
WIN32_IA32 = 'win32-ia32',
WIN32_ARM64 = 'win32-arm64',
LINUX_X64 = 'linux-x64',
LINUX_ARM64 = 'linux-arm64',
LINUX_ARMHF = 'linux-armhf',
ALPINE_X64 = 'alpine-x64',
ALPINE_ARM64 = 'alpine-arm64',
DARWIN_X64 = 'darwin-x64',
DARWIN_ARM64 = 'darwin-arm64',
WEB = 'web',
UNIVERSAL = 'universal',
UNKNOWN = 'unknown',
UNDEFINED = 'undefined',
}
export function TargetPlatformToString(targetPlatform: TargetPlatform) {
switch (targetPlatform) {
case TargetPlatform.WIN32_X64: return 'Windows 64 bit';
case TargetPlatform.WIN32_IA32: return 'Windows 32 bit';
case TargetPlatform.WIN32_ARM64: return 'Windows ARM';
case TargetPlatform.LINUX_X64: return 'Linux 64 bit';
case TargetPlatform.LINUX_ARM64: return 'Linux ARM 64';
case TargetPlatform.LINUX_ARMHF: return 'Linux ARM';
case TargetPlatform.ALPINE_X64: return 'Alpine Linux 64 bit';
case TargetPlatform.ALPINE_ARM64: return 'Alpine ARM 64';
case TargetPlatform.DARWIN_X64: return 'Mac';
case TargetPlatform.DARWIN_ARM64: return 'Mac Silicon';
case TargetPlatform.WEB: return 'Web';
case TargetPlatform.UNIVERSAL: return TargetPlatform.UNIVERSAL;
case TargetPlatform.UNKNOWN: return TargetPlatform.UNKNOWN;
case TargetPlatform.UNDEFINED: return TargetPlatform.UNDEFINED;
}
}
export function toTargetPlatform(targetPlatform: string): TargetPlatform {
switch (targetPlatform) {
case TargetPlatform.WIN32_X64: return TargetPlatform.WIN32_X64;
case TargetPlatform.WIN32_IA32: return TargetPlatform.WIN32_IA32;
case TargetPlatform.WIN32_ARM64: return TargetPlatform.WIN32_ARM64;
case TargetPlatform.LINUX_X64: return TargetPlatform.LINUX_X64;
case TargetPlatform.LINUX_ARM64: return TargetPlatform.LINUX_ARM64;
case TargetPlatform.LINUX_ARMHF: return TargetPlatform.LINUX_ARMHF;
case TargetPlatform.ALPINE_X64: return TargetPlatform.ALPINE_X64;
case TargetPlatform.ALPINE_ARM64: return TargetPlatform.ALPINE_ARM64;
case TargetPlatform.DARWIN_X64: return TargetPlatform.DARWIN_X64;
case TargetPlatform.DARWIN_ARM64: return TargetPlatform.DARWIN_ARM64;
case TargetPlatform.WEB: return TargetPlatform.WEB;
case TargetPlatform.UNIVERSAL: return TargetPlatform.UNIVERSAL;
default: return TargetPlatform.UNKNOWN;
}
}
export function getTargetPlatform(platform: Platform | 'alpine', arch: string | undefined): TargetPlatform {
switch (platform) {
case Platform.Windows:
if (arch === 'x64') {
return TargetPlatform.WIN32_X64;
}
if (arch === 'ia32') {
return TargetPlatform.WIN32_IA32;
}
if (arch === 'arm64') {
return TargetPlatform.WIN32_ARM64;
}
return TargetPlatform.UNKNOWN;
case Platform.Linux:
if (arch === 'x64') {
return TargetPlatform.LINUX_X64;
}
if (arch === 'arm64') {
return TargetPlatform.LINUX_ARM64;
}
if (arch === 'arm') {
return TargetPlatform.LINUX_ARMHF;
}
return TargetPlatform.UNKNOWN;
case 'alpine':
if (arch === 'x64') {
return TargetPlatform.ALPINE_X64;
}
if (arch === 'arm64') {
return TargetPlatform.ALPINE_ARM64;
}
return TargetPlatform.UNKNOWN;
case Platform.Mac:
if (arch === 'x64') {
return TargetPlatform.DARWIN_X64;
}
if (arch === 'arm64') {
return TargetPlatform.DARWIN_ARM64;
}
return TargetPlatform.UNKNOWN;
case Platform.Web: return TargetPlatform.WEB;
}
}
export function isNotWebExtensionInWebTargetPlatform(allTargetPlatforms: TargetPlatform[], productTargetPlatform: TargetPlatform): boolean {
// Not a web extension in web target platform
return productTargetPlatform === TargetPlatform.WEB && !allTargetPlatforms.includes(TargetPlatform.WEB);
}
export function isTargetPlatformCompatible(extensionTargetPlatform: TargetPlatform, allTargetPlatforms: TargetPlatform[], productTargetPlatform: TargetPlatform): boolean {
// Not compatible when extension is not a web extension in web target platform
if (isNotWebExtensionInWebTargetPlatform(allTargetPlatforms, productTargetPlatform)) {
return false;
}
// Compatible when extension target platform is not defined
if (extensionTargetPlatform === TargetPlatform.UNDEFINED) {
return true;
}
// Compatible when extension target platform is universal
if (extensionTargetPlatform === TargetPlatform.UNIVERSAL) {
return true;
}
// Not compatible when extension target platform is unknown
if (extensionTargetPlatform === TargetPlatform.UNKNOWN) {
return false;
}
// Compatible when extension and product target platforms matches
if (extensionTargetPlatform === productTargetPlatform) {
return true;
}
// Fallback
const fallbackTargetPlatforms = getFallbackTargetPlarforms(productTargetPlatform);
return fallbackTargetPlatforms.includes(extensionTargetPlatform);
}
export function getFallbackTargetPlarforms(targetPlatform: TargetPlatform): TargetPlatform[] {
switch (targetPlatform) {
case TargetPlatform.WIN32_X64: return [TargetPlatform.WIN32_IA32];
case TargetPlatform.WIN32_ARM64: return [TargetPlatform.WIN32_IA32];
}
return [];
}
export interface IGalleryExtensionProperties {
dependencies?: string[];
extensionPack?: string[];
engine?: string;
localizedLanguages?: string[];
targetPlatform: TargetPlatform;
isPreReleaseVersion: boolean;
}
export interface IGalleryExtensionAsset {
uri: string;
fallbackUri: string;
}
export interface IGalleryExtensionAssets {
manifest: IGalleryExtensionAsset | null;
readme: IGalleryExtensionAsset | null;
changelog: IGalleryExtensionAsset | null;
license: IGalleryExtensionAsset | null;
repository: IGalleryExtensionAsset | null;
download: IGalleryExtensionAsset;
icon: IGalleryExtensionAsset;
coreTranslations: [string, IGalleryExtensionAsset][];
}
export function isIExtensionIdentifier(thing: any): thing is IExtensionIdentifier {
return thing
&& typeof thing === 'object'
&& typeof thing.id === 'string'
&& (!thing.uuid || typeof thing.uuid === 'string');
}
/* __GDPR__FRAGMENT__
"ExtensionIdentifier" : {
"id" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"uuid": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
}
*/
export interface IExtensionIdentifier {
id: string;
uuid?: string;
}
export interface IExtensionIdentifierWithVersion extends IExtensionIdentifier {
id: string;
uuid?: string;
version: string;
}
export interface IGalleryExtensionIdentifier extends IExtensionIdentifier {
uuid: string;
}
export interface IGalleryExtensionVersion {
version: string;
date: string;
isPreReleaseVersion: boolean;
}
export interface IGalleryExtension {
name: string;
identifier: IGalleryExtensionIdentifier;
version: string;
displayName: string;
publisherId: string;
publisher: string;
publisherDisplayName: string;
publisherDomain?: { link: string, verified: boolean };
description: string;
installCount: number;
rating: number;
ratingCount: number;
categories: readonly string[];
tags: readonly string[];
releaseDate: number;
lastUpdated: number;
preview: boolean;
hasPreReleaseVersion: boolean;
allTargetPlatforms: TargetPlatform[];
assets: IGalleryExtensionAssets;
properties: IGalleryExtensionProperties;
telemetryData?: any;
}
export interface IGalleryMetadata {
id: string;
publisherId: string;
publisherDisplayName: string;
}
export interface ILocalExtension extends IExtension {
isMachineScoped: boolean;
publisherId: string | null;
publisherDisplayName: string | null;
installedTimestamp?: number;
isPreReleaseVersion: boolean;
preRelease: boolean;
}
export const enum SortBy {
NoneOrRelevance = 0,
LastUpdatedDate = 1,
Title = 2,
PublisherName = 3,
InstallCount = 4,
PublishedDate = 10,
AverageRating = 6,
WeightedRating = 12
}
export const enum SortOrder {
Default = 0,
Ascending = 1,
Descending = 2
}
export interface IQueryOptions {
text?: string;
ids?: string[];
names?: string[];
pageSize?: number;
sortBy?: SortBy;
sortOrder?: SortOrder;
source?: string;
includePreRelease?: boolean;
}
export const enum StatisticType {
Install = 'install',
Uninstall = 'uninstall'
}
export interface IExtensionsControlManifest {
malicious: IExtensionIdentifier[];
unsupportedPreReleaseExtensions?: IStringDictionary<{ id: string, displayName: string }>;
}
export const enum InstallOperation {
None = 0,
Install,
Update
}
export interface ITranslation {
contents: { [key: string]: {} };
}
export const IExtensionGalleryService = createDecorator<IExtensionGalleryService>('extensionGalleryService');
export interface IExtensionGalleryService {
readonly _serviceBrand: undefined;
isEnabled(): boolean;
query(options: IQueryOptions, token: CancellationToken): Promise<IPager<IGalleryExtension>>;
getExtensions(identifiers: ReadonlyArray<IExtensionIdentifier | IExtensionIdentifierWithVersion>, token: CancellationToken): Promise<IGalleryExtension[]>;
getExtensions(identifiers: ReadonlyArray<IExtensionIdentifier | IExtensionIdentifierWithVersion>, includePreRelease: boolean, token: CancellationToken): Promise<IGalleryExtension[]>;
download(extension: IGalleryExtension, location: URI, operation: InstallOperation): Promise<void>;
reportStatistic(publisher: string, name: string, version: string, type: StatisticType): Promise<void>;
getReadme(extension: IGalleryExtension, token: CancellationToken): Promise<string>;
getManifest(extension: IGalleryExtension, token: CancellationToken): Promise<IExtensionManifest | null>;
getChangelog(extension: IGalleryExtension, token: CancellationToken): Promise<string>;
getCoreTranslation(extension: IGalleryExtension, languageId: string): Promise<ITranslation | null>;
getExtensionsControlManifest(): Promise<IExtensionsControlManifest>;
isExtensionCompatible(extension: IGalleryExtension, includePreRelease: boolean, targetPlatform: TargetPlatform): Promise<boolean>;
getCompatibleExtension(extension: IGalleryExtension, includePreRelease: boolean, targetPlatform: TargetPlatform): Promise<IGalleryExtension | null>;
getCompatibleExtension(id: IExtensionIdentifier, includePreRelease: boolean, targetPlatform: TargetPlatform): Promise<IGalleryExtension | null>;
getAllCompatibleVersions(extension: IGalleryExtension, includePreRelease: boolean, targetPlatform: TargetPlatform): Promise<IGalleryExtensionVersion[]>;
}
export interface InstallExtensionEvent {
identifier: IExtensionIdentifier;
source: URI | IGalleryExtension;
}
export interface InstallExtensionResult {
readonly identifier: IExtensionIdentifier;
readonly operation: InstallOperation;
readonly source?: URI | IGalleryExtension;
readonly local?: ILocalExtension;
}
export interface DidUninstallExtensionEvent {
identifier: IExtensionIdentifier;
error?: string;
}
export enum ExtensionManagementErrorCode {
Unsupported = 'Unsupported',
Malicious = 'Malicious',
Incompatible = 'Incompatible',
IncompatiblePreRelease = 'IncompatiblePreRelease',
IncompatibleTargetPlatform = 'IncompatibleTargetPlatform',
Invalid = 'Invalid',
Download = 'Download',
Extract = 'Extract',
Delete = 'Delete',
Rename = 'Rename',
CorruptZip = 'CorruptZip',
IncompleteZip = 'IncompleteZip',
Internal = 'Internal',
}
export class ExtensionManagementError extends Error {
constructor(message: string, readonly code: ExtensionManagementErrorCode) {
super(message);
this.name = code;
}
}
export type InstallOptions = { isBuiltin?: boolean, isMachineScoped?: boolean, donotIncludePackAndDependencies?: boolean, installGivenVersion?: boolean, installPreReleaseVersion?: boolean };
export type InstallVSIXOptions = Omit<InstallOptions, 'installGivenVersion'> & { installOnlyNewlyAddedFromExtensionPack?: boolean };
export type UninstallOptions = { donotIncludePack?: boolean, donotCheckDependents?: boolean };
export interface IExtensionManagementParticipant {
postInstall(local: ILocalExtension, source: URI | IGalleryExtension, options: InstallOptions | InstallVSIXOptions, token: CancellationToken): Promise<void>;
postUninstall(local: ILocalExtension, options: UninstallOptions, token: CancellationToken): Promise<void>;
}
export const IExtensionManagementService = createDecorator<IExtensionManagementService>('extensionManagementService');
export interface IExtensionManagementService {
readonly _serviceBrand: undefined;
onInstallExtension: Event<InstallExtensionEvent>;
onDidInstallExtensions: Event<readonly InstallExtensionResult[]>;
onUninstallExtension: Event<IExtensionIdentifier>;
onDidUninstallExtension: Event<DidUninstallExtensionEvent>;
zip(extension: ILocalExtension): Promise<URI>;
unzip(zipLocation: URI): Promise<IExtensionIdentifier>;
getManifest(vsix: URI): Promise<IExtensionManifest>;
install(vsix: URI, options?: InstallVSIXOptions): Promise<ILocalExtension>;
canInstall(extension: IGalleryExtension): Promise<boolean>;
installFromGallery(extension: IGalleryExtension, options?: InstallOptions): Promise<ILocalExtension>;
uninstall(extension: ILocalExtension, options?: UninstallOptions): Promise<void>;
reinstallFromGallery(extension: ILocalExtension): Promise<void>;
getInstalled(type?: ExtensionType, donotIgnoreInvalidExtensions?: boolean): Promise<ILocalExtension[]>;
getExtensionsControlManifest(): Promise<IExtensionsControlManifest>;
updateMetadata(local: ILocalExtension, metadata: IGalleryMetadata): Promise<ILocalExtension>;
updateExtensionScope(local: ILocalExtension, isMachineScoped: boolean): Promise<ILocalExtension>;
registerParticipant(pariticipant: IExtensionManagementParticipant): void;
getTargetPlatform(): Promise<TargetPlatform>;
}
export const DISABLED_EXTENSIONS_STORAGE_PATH = 'extensionsIdentifiers/disabled';
export const ENABLED_EXTENSIONS_STORAGE_PATH = 'extensionsIdentifiers/enabled';
export const IGlobalExtensionEnablementService = createDecorator<IGlobalExtensionEnablementService>('IGlobalExtensionEnablementService');
export interface IGlobalExtensionEnablementService {
readonly _serviceBrand: undefined;
readonly onDidChangeEnablement: Event<{ readonly extensions: IExtensionIdentifier[], readonly source?: string }>;
getDisabledExtensions(): IExtensionIdentifier[];
enableExtension(extension: IExtensionIdentifier, source?: string): Promise<boolean>;
disableExtension(extension: IExtensionIdentifier, source?: string): Promise<boolean>;
}
export type IConfigBasedExtensionTip = {
readonly extensionId: string,
readonly extensionName: string,
readonly isExtensionPack: boolean,
readonly configName: string,
readonly important: boolean,
};
export type IExecutableBasedExtensionTip = {
readonly extensionId: string,
readonly extensionName: string,
readonly isExtensionPack: boolean,
readonly exeName: string,
readonly exeFriendlyName: string,
readonly windowsPath?: string,
};
export type IWorkspaceTips = { readonly remoteSet: string[]; readonly recommendations: string[]; };
export const IExtensionTipsService = createDecorator<IExtensionTipsService>('IExtensionTipsService');
export interface IExtensionTipsService {
readonly _serviceBrand: undefined;
getConfigBasedTips(folder: URI): Promise<IConfigBasedExtensionTip[]>;
getImportantExecutableBasedTips(): Promise<IExecutableBasedExtensionTip[]>;
getOtherExecutableBasedTips(): Promise<IExecutableBasedExtensionTip[]>;
getAllWorkspacesTips(): Promise<IWorkspaceTips[]>;
}
export const DefaultIconPath = FileAccess.asBrowserUri('./media/defaultIcon.png', require).toString(true);
export const ExtensionsLabel = localize('extensions', "Extensions");
export const ExtensionsLocalizedLabel = { value: ExtensionsLabel, original: 'Extensions' };
export const ExtensionsChannelId = 'extensions';
export const PreferencesLabel = localize('preferences', "Preferences");
export const PreferencesLocalizedLabel = { value: PreferencesLabel, original: 'Preferences' };
export interface CLIOutput {
log(s: string): void;
error(s: string): void;
}
export const IExtensionManagementCLIService = createDecorator<IExtensionManagementCLIService>('IExtensionManagementCLIService');
export interface IExtensionManagementCLIService {
readonly _serviceBrand: undefined;
listExtensions(showVersions: boolean, category?: string, output?: CLIOutput): Promise<void>;
installExtensions(extensions: (string | URI)[], builtinExtensionIds: string[], installOptions: InstallOptions, force: boolean, output?: CLIOutput): Promise<void>;
uninstallExtensions(extensions: (string | URI)[], force: boolean, output?: CLIOutput): Promise<void>;
locateExtension(extensions: string[], output?: CLIOutput): Promise<void>;
}