diff --git a/src/vs/base/common/product.ts b/src/vs/base/common/product.ts index c6e335aa3e757..026545ba4ae90 100644 --- a/src/vs/base/common/product.ts +++ b/src/vs/base/common/product.ts @@ -173,6 +173,7 @@ export interface IProductConfiguration { readonly extensionPointExtensionKind?: { readonly [extensionPointId: string]: ('ui' | 'workspace' | 'web')[] }; readonly extensionSyncedKeys?: { readonly [extensionId: string]: string[] }; + readonly extensionsEnabledWithApiProposalVersion?: string[]; readonly extensionEnabledApiProposals?: { readonly [extensionId: string]: string[] }; readonly extensionUntrustedWorkspaceSupport?: { readonly [extensionId: string]: ExtensionUntrustedWorkspaceSupport }; readonly extensionVirtualWorkspacesSupport?: { readonly [extensionId: string]: ExtensionVirtualWorkspaceSupport }; diff --git a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts index 093a6694e99e3..6c90b5a8f1922 100644 --- a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts @@ -598,6 +598,7 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi private readonly extensionsControlUrl: string | undefined; private readonly commonHeadersPromise: Promise>; + private readonly extensionsEnabledWithApiProposalVersion: string[]; constructor( storageService: IStorageService | undefined, @@ -614,6 +615,7 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi this.extensionsGalleryUrl = isPPEEnabled ? config.servicePPEUrl : config?.serviceUrl; this.extensionsGallerySearchUrl = isPPEEnabled ? undefined : config?.searchUrl; this.extensionsControlUrl = config?.controlUrl; + this.extensionsEnabledWithApiProposalVersion = productService.extensionsEnabledWithApiProposalVersion?.map(id => id.toLowerCase()) ?? []; this.commonHeadersPromise = resolveMarketplaceHeaders( productService.version, productService, @@ -717,13 +719,23 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi return false; } - if (!areApiProposalsCompatible(extension.properties.enabledApiProposals ?? [])) { + if (!this.areApiProposalsCompatible(extension.identifier, extension.properties.enabledApiProposals)) { return false; } return true; } + private areApiProposalsCompatible(extensionIdentifier: IExtensionIdentifier, enabledApiProposals: string[] | undefined): boolean { + if (!enabledApiProposals) { + return true; + } + if (!this.extensionsEnabledWithApiProposalVersion.includes(extensionIdentifier.id.toLowerCase())) { + return true; + } + return areApiProposalsCompatible(enabledApiProposals); + } + private async isValidVersion(extension: string, rawGalleryExtensionVersion: IRawGalleryExtensionVersion, versionType: 'release' | 'prerelease' | 'any', compatible: boolean, allTargetPlatforms: TargetPlatform[], targetPlatform: TargetPlatform, productVersion: IProductVersion = { version: this.productService.version, date: this.productService.date }): Promise { if (!isTargetPlatformCompatible(getTargetPlatformForExtensionVersion(rawGalleryExtensionVersion), allTargetPlatforms, targetPlatform)) { return false; @@ -933,7 +945,7 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi } // Allow any version if includePreRelease flag is set otherwise only release versions are allowed if (await this.isValidVersion( - getGalleryExtensionId(rawGalleryExtension.publisher.publisherName, rawGalleryExtension.extensionName), + extensionIdentifier.id, rawGalleryExtensionVersion, includePreRelease ? 'any' : 'release', criteria.compatible, @@ -941,7 +953,7 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi criteria.targetPlatform, criteria.productVersion) ) { - if (criteria.compatible && !areApiProposalsCompatible(getEnabledApiProposals(rawGalleryExtensionVersion))) { + if (criteria.compatible && !this.areApiProposalsCompatible(extensionIdentifier, getEnabledApiProposals(rawGalleryExtensionVersion))) { return null; } return toExtension(rawGalleryExtension, rawGalleryExtensionVersion, allTargetPlatforms, queryContext); @@ -1196,7 +1208,7 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi true, allTargetPlatforms, targetPlatform)) - && areApiProposalsCompatible(getEnabledApiProposals(version)) + && this.areApiProposalsCompatible(extensionIdentifier, getEnabledApiProposals(version)) ) { validVersions.push(version); } diff --git a/src/vs/platform/extensionManagement/common/extensionsScannerService.ts b/src/vs/platform/extensionManagement/common/extensionsScannerService.ts index d29baae7a975b..74c8eeaeb1f5e 100644 --- a/src/vs/platform/extensionManagement/common/extensionsScannerService.ts +++ b/src/vs/platform/extensionManagement/common/extensionsScannerService.ts @@ -554,14 +554,18 @@ type NlsConfiguration = { class ExtensionsScanner extends Disposable { + private readonly extensionsEnabledWithApiProposalVersion: string[]; + constructor( private readonly obsoleteFile: URI, @IExtensionsProfileScannerService protected readonly extensionsProfileScannerService: IExtensionsProfileScannerService, @IUriIdentityService protected readonly uriIdentityService: IUriIdentityService, @IFileService protected readonly fileService: IFileService, + @IProductService productService: IProductService, @ILogService protected readonly logService: ILogService ) { super(); + this.extensionsEnabledWithApiProposalVersion = productService.extensionsEnabledWithApiProposalVersion?.map(id => id.toLowerCase()) ?? []; } async scanExtensions(input: ExtensionScannerInput): Promise { @@ -653,6 +657,9 @@ class ExtensionsScanner extends Disposable { const type = metadata?.isSystem ? ExtensionType.System : input.type; const isBuiltin = type === ExtensionType.System || !!metadata?.isBuiltin; manifest = await this.translateManifest(input.location, manifest, ExtensionScannerInput.createNlsConfiguration(input)); + if (manifest.enabledApiProposals && !this.extensionsEnabledWithApiProposalVersion?.includes(id.toLowerCase())) { + manifest.enabledApiProposals = parseEnabledApiProposalNames([...manifest.enabledApiProposals]); + } const extension: IRelaxedScannedExtension = { type, identifier, @@ -689,7 +696,7 @@ class ExtensionsScanner extends Disposable { return extension; } - async scanExtensionManifest(extensionLocation: URI): Promise { + private async scanExtensionManifest(extensionLocation: URI): Promise { const manifestLocation = joinPath(extensionLocation, 'package.json'); let content; try { @@ -878,9 +885,10 @@ class CachedExtensionsScanner extends ExtensionsScanner { @IExtensionsProfileScannerService extensionsProfileScannerService: IExtensionsProfileScannerService, @IUriIdentityService uriIdentityService: IUriIdentityService, @IFileService fileService: IFileService, + @IProductService productService: IProductService, @ILogService logService: ILogService ) { - super(obsoleteFile, extensionsProfileScannerService, uriIdentityService, fileService, logService); + super(obsoleteFile, extensionsProfileScannerService, uriIdentityService, fileService, productService, logService); } override async scanExtensions(input: ExtensionScannerInput): Promise { @@ -983,7 +991,6 @@ export function toExtensionDescription(extension: IScannedExtension, isUnderDeve targetPlatform: extension.targetPlatform, publisherDisplayName: extension.publisherDisplayName, ...extension.manifest, - enabledApiProposals: extension.manifest.enabledApiProposals ? parseEnabledApiProposalNames([...extension.manifest.enabledApiProposals]) : undefined, }; } diff --git a/src/vs/platform/extensions/common/extensions.ts b/src/vs/platform/extensions/common/extensions.ts index dc022ce00985c..24ba29d210f33 100644 --- a/src/vs/platform/extensions/common/extensions.ts +++ b/src/vs/platform/extensions/common/extensions.ts @@ -458,7 +458,7 @@ export class ExtensionIdentifierMap { } } -interface IRelaxedExtensionDescription extends IRelaxedExtensionManifest { +export interface IRelaxedExtensionDescription extends IRelaxedExtensionManifest { id?: string; identifier: ExtensionIdentifier; uuid?: string; @@ -470,9 +470,7 @@ interface IRelaxedExtensionDescription extends IRelaxedExtensionManifest { extensionLocation: URI; } -export type IExtensionDescription = Readonly & { - enabledApiProposals: string[] | undefined; // This needs to be updated while validating & updating the proposals. -}; +export type IExtensionDescription = Readonly; export function isApplicationScopedExtension(manifest: IExtensionManifest): boolean { return isLanguagePackExtension(manifest); diff --git a/src/vs/workbench/services/extensions/common/extensions.ts b/src/vs/workbench/services/extensions/common/extensions.ts index bc9ce39aa7002..1ca33752d78c5 100644 --- a/src/vs/workbench/services/extensions/common/extensions.ts +++ b/src/vs/workbench/services/extensions/common/extensions.ts @@ -9,7 +9,7 @@ import { URI } from 'vs/base/common/uri'; import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc'; import { getExtensionId, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { ImplicitActivationEvents } from 'vs/platform/extensionManagement/common/implicitActivationEvents'; -import { ExtensionIdentifier, ExtensionIdentifierMap, ExtensionIdentifierSet, ExtensionType, IExtension, IExtensionContributions, IExtensionDescription, parseEnabledApiProposalNames, TargetPlatform } from 'vs/platform/extensions/common/extensions'; +import { ExtensionIdentifier, ExtensionIdentifierMap, ExtensionIdentifierSet, ExtensionType, IExtension, IExtensionContributions, IExtensionDescription, TargetPlatform } from 'vs/platform/extensions/common/extensions'; import { ApiProposalName } from 'vs/platform/extensions/common/extensionsApiProposals'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IV8Profile } from 'vs/platform/profiling/common/profiling'; @@ -28,8 +28,7 @@ export const nullExtensionDescription = Object.freeze({ isBuiltin: false, targetPlatform: TargetPlatform.UNDEFINED, isUserBuiltin: false, - isUnderDevelopment: false, - enabledApiProposals: undefined, + isUnderDevelopment: false }); export type WebWorkerExtHostConfigValue = boolean | 'auto'; @@ -570,8 +569,7 @@ export function toExtensionDescription(extension: IExtension, isUnderDevelopment uuid: extension.identifier.uuid, targetPlatform: extension.targetPlatform, publisherDisplayName: extension.publisherDisplayName, - ...extension.manifest, - enabledApiProposals: extension.manifest.enabledApiProposals ? parseEnabledApiProposalNames([...extension.manifest.enabledApiProposals]) : undefined, + ...extension.manifest }; } diff --git a/src/vs/workbench/services/extensions/common/extensionsProposedApi.ts b/src/vs/workbench/services/extensions/common/extensionsProposedApi.ts index aad00bc62ccb8..76d560e77b126 100644 --- a/src/vs/workbench/services/extensions/common/extensionsProposedApi.ts +++ b/src/vs/workbench/services/extensions/common/extensionsProposedApi.ts @@ -15,6 +15,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { Extensions, IExtensionFeatureMarkdownRenderer, IExtensionFeaturesRegistry, IRenderedData } from 'vs/workbench/services/extensionManagement/common/extensionFeatures'; import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; +import { Mutable } from 'vs/base/common/types'; export class ExtensionsProposedApi { @@ -60,7 +61,7 @@ export class ExtensionsProposedApi { } } - private doUpdateEnabledApiProposals(extension: IExtensionDescription): void { + private doUpdateEnabledApiProposals(extension: Mutable): void { const key = ExtensionIdentifier.toKey(extension.identifier);