mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-02 08:15:56 +01:00
Enable auto-update for built-in extensions
Add autoUpdateBuiltinExtensions product config to define which built-in extensions should auto-update. Version-scoped to current VS Code release (same major.minor). Includes gallery version validation, scanner dedup filtering, stale version cleanup, and application-scoped installs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -207,6 +207,7 @@ export interface IProductConfiguration {
|
||||
readonly excludeVersionRange?: string;
|
||||
}>;
|
||||
readonly extensionsForceVersionByQuality?: readonly string[];
|
||||
readonly autoUpdateBuiltinExtensions?: readonly string[];
|
||||
|
||||
readonly msftInternalDomains?: string[];
|
||||
readonly linkProtectionTrustedDomains?: readonly string[];
|
||||
|
||||
@@ -331,11 +331,16 @@ export abstract class AbstractExtensionManagementService extends CommontExtensio
|
||||
};
|
||||
|
||||
try {
|
||||
const systemExtensions = await this.getInstalled(ExtensionType.System);
|
||||
// Start installing extensions
|
||||
for (const { manifest, extension, options } of extensions) {
|
||||
const isApplicationScoped = options.isApplicationScoped || options.isBuiltin || isApplicationScopedExtension(manifest);
|
||||
const extensionId = getGalleryExtensionId(manifest.publisher, manifest.name);
|
||||
const isSystemExtension = systemExtensions.some(e => areSameExtensions(e.identifier, { id: extensionId }));
|
||||
const isBuiltin = options.isBuiltin || isSystemExtension;
|
||||
const isApplicationScoped = options.isApplicationScoped || isBuiltin || isApplicationScopedExtension(manifest);
|
||||
const installExtensionTaskOptions: InstallExtensionTaskOptions = {
|
||||
...options,
|
||||
isBuiltin,
|
||||
isApplicationScoped,
|
||||
profileLocation: isApplicationScoped ? this.userDataProfilesService.defaultProfile.extensionsResource : options.profileLocation ?? this.getCurrentExtensionsManifestLocation(),
|
||||
productVersion: options.productVersion ?? { version: this.productService.version, date: this.productService.date }
|
||||
|
||||
@@ -1000,6 +1000,15 @@ export abstract class AbstractExtensionGalleryService implements IExtensionGalle
|
||||
return false;
|
||||
}
|
||||
|
||||
// For auto-update extensions defined in product, only allow versions with same major.minor as the product version
|
||||
if (this.productService.autoUpdateBuiltinExtensions?.some(id => id.toLowerCase() === extension.id.toLowerCase())) {
|
||||
const productMajorMinor = `${semver.major(productVersion.version)}.${semver.minor(productVersion.version)}`;
|
||||
const extensionMajorMinor = `${semver.major(extension.version)}.${semver.minor(extension.version)}`;
|
||||
if (productMajorMinor !== extensionMajorMinor) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Specific version
|
||||
if (isString(version)) {
|
||||
if (extension.version !== version) {
|
||||
|
||||
@@ -286,6 +286,7 @@ export interface ILocalExtension extends IExtension {
|
||||
preRelease: boolean;
|
||||
updated: boolean;
|
||||
pinned: boolean;
|
||||
autoUpdate: boolean;
|
||||
source: InstallSource;
|
||||
size: number;
|
||||
}
|
||||
|
||||
@@ -55,6 +55,7 @@ interface IRelaxedScannedExtension {
|
||||
isValid: boolean;
|
||||
validations: readonly [Severity, string][];
|
||||
preRelease: boolean;
|
||||
autoUpdate: boolean;
|
||||
}
|
||||
|
||||
export type IScannedExtension = Readonly<IRelaxedScannedExtension> & { manifest: IExtensionManifest };
|
||||
@@ -351,6 +352,9 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem
|
||||
}
|
||||
|
||||
private dedupExtensions(system: IScannedExtension[] | undefined, user: IScannedExtension[] | undefined, development: IScannedExtension[] | undefined, targetPlatform: TargetPlatform, pickLatest: boolean): IScannedExtension[] {
|
||||
const autoUpdateBuiltinExtensions = this.productService.autoUpdateBuiltinExtensions;
|
||||
const productVersion = autoUpdateBuiltinExtensions?.length ? this.getProductVersion() : undefined;
|
||||
const productMajorMinor = productVersion ? `${semver.major(productVersion.version)}.${semver.minor(productVersion.version)}` : undefined;
|
||||
const pick = (existing: IScannedExtension, extension: IScannedExtension, isDevelopment: boolean): boolean => {
|
||||
if (!isDevelopment) {
|
||||
if (existing.metadata?.isApplicationScoped && !extension.metadata?.isApplicationScoped) {
|
||||
@@ -399,7 +403,18 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem
|
||||
this.logService.debug(`Skipping obsolete system extension ${extension.location.path}.`);
|
||||
return;
|
||||
}
|
||||
if (productMajorMinor && autoUpdateBuiltinExtensions?.some(id => id.toLowerCase() === extension.identifier.id.toLowerCase())) {
|
||||
const extensionMajorMinor = `${semver.major(extension.manifest.version)}.${semver.minor(extension.manifest.version)}`;
|
||||
if (productMajorMinor !== extensionMajorMinor) {
|
||||
this.logService.info(`Skipping auto-update builtin extension ${extension.identifier.id} with version ${extension.manifest.version} because it does not match the product version ${productVersion.version}`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!existing || pick(existing, extension, false)) {
|
||||
// Mark as builtin when extension is an auto-update builtin extension
|
||||
if (autoUpdateBuiltinExtensions?.some(id => id.toLowerCase() === extension.identifier.id.toLowerCase())) {
|
||||
extension = { ...extension, isBuiltin: true };
|
||||
}
|
||||
result.set(extension.identifier.id, extension);
|
||||
}
|
||||
});
|
||||
@@ -563,6 +578,8 @@ type NlsConfiguration = {
|
||||
class ExtensionsScanner extends Disposable {
|
||||
|
||||
private readonly extensionsEnabledWithApiProposalVersion: string[];
|
||||
private readonly productQuality: string | undefined;
|
||||
private readonly autoUpdateBuiltinExtensions: ReadonlySet<string>;
|
||||
|
||||
constructor(
|
||||
@IExtensionsProfileScannerService protected readonly extensionsProfileScannerService: IExtensionsProfileScannerService,
|
||||
@@ -574,6 +591,8 @@ class ExtensionsScanner extends Disposable {
|
||||
) {
|
||||
super();
|
||||
this.extensionsEnabledWithApiProposalVersion = productService.extensionsEnabledWithApiProposalVersion?.map(id => id.toLowerCase()) ?? [];
|
||||
this.productQuality = productService.quality;
|
||||
this.autoUpdateBuiltinExtensions = new Set(productService.autoUpdateBuiltinExtensions?.map(id => id.toLowerCase()) ?? []);
|
||||
}
|
||||
|
||||
async scanExtensions(input: ExtensionScannerInput): Promise<IRelaxedScannedExtension[]> {
|
||||
@@ -712,6 +731,7 @@ class ExtensionsScanner extends Disposable {
|
||||
isValid,
|
||||
validations,
|
||||
preRelease: !!metadata?.preRelease,
|
||||
autoUpdate: type === ExtensionType.System && this.autoUpdateBuiltinExtensions.has(id.toLowerCase()) && this.productQuality === 'stable',
|
||||
};
|
||||
if (input.validate) {
|
||||
extension = this.validate(extension, input);
|
||||
|
||||
@@ -549,6 +549,8 @@ export class ExtensionsScanner extends Disposable {
|
||||
@IExtensionsProfileScannerService private readonly extensionsProfileScannerService: IExtensionsProfileScannerService,
|
||||
@IUriIdentityService private readonly uriIdentityService: IUriIdentityService,
|
||||
@ITelemetryService private readonly telemetryService: ITelemetryService,
|
||||
@IProductService private readonly productService: IProductService,
|
||||
@IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService,
|
||||
@ILogService private readonly logService: ILogService,
|
||||
) {
|
||||
super();
|
||||
@@ -558,6 +560,7 @@ export class ExtensionsScanner extends Disposable {
|
||||
|
||||
async cleanUp(): Promise<void> {
|
||||
await this.removeTemporarilyDeletedFolders();
|
||||
await this.removeStaleAutoUpdateBuiltinExtensions();
|
||||
await this.deleteExtensionsMarkedForRemoval();
|
||||
//TODO: Remove this initiialization after coupe of releases
|
||||
await this.initializeExtensionSize();
|
||||
@@ -914,6 +917,7 @@ export class ExtensionsScanner extends Disposable {
|
||||
installedTimestamp: extension.metadata?.installedTimestamp,
|
||||
updated: !!extension.metadata?.updated,
|
||||
pinned: !!extension.metadata?.pinned,
|
||||
autoUpdate: extension.autoUpdate,
|
||||
private: !!extension.metadata?.private,
|
||||
isWorkspaceScoped: false,
|
||||
source: extension.metadata?.source ?? (extension.identifier.uuid ? 'gallery' : 'vsix'),
|
||||
@@ -932,6 +936,28 @@ export class ExtensionsScanner extends Disposable {
|
||||
}));
|
||||
}
|
||||
|
||||
private async removeStaleAutoUpdateBuiltinExtensions(): Promise<void> {
|
||||
const autoUpdateBuiltinExtensions = this.productService.autoUpdateBuiltinExtensions;
|
||||
if (!autoUpdateBuiltinExtensions?.length) {
|
||||
return;
|
||||
}
|
||||
const productVersion = this.productService.version;
|
||||
const productMajorMinor = `${semver.major(productVersion)}.${semver.minor(productVersion)}`;
|
||||
const extensions = await this.extensionsScannerService.scanAllUserExtensions();
|
||||
const staleExtensions = extensions.filter(extension => {
|
||||
if (!autoUpdateBuiltinExtensions.some(id => id.toLowerCase() === extension.identifier.id.toLowerCase())) {
|
||||
return false;
|
||||
}
|
||||
const extensionMajorMinor = `${semver.major(extension.manifest.version)}.${semver.minor(extension.manifest.version)}`;
|
||||
return productMajorMinor !== extensionMajorMinor;
|
||||
});
|
||||
if (staleExtensions.length) {
|
||||
this.logService.info('Removing stale auto-update builtin extensions:', staleExtensions.map(e => `${e.identifier.id}@${e.manifest.version}`).join(', '));
|
||||
await this.extensionsProfileScannerService.removeExtensionsFromProfile(staleExtensions.map(e => e.identifier), this.userDataProfilesService.defaultProfile.extensionsResource);
|
||||
await Promise.allSettled(staleExtensions.map(e => this.deleteExtension(e, 'stale auto-update builtin')));
|
||||
}
|
||||
}
|
||||
|
||||
private async deleteExtensionsMarkedForRemoval(): Promise<void> {
|
||||
let removed: IStringDictionary<boolean>;
|
||||
try {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import assert from 'assert';
|
||||
import { newWriteableBufferStream } from '../../../../base/common/buffer.js';
|
||||
import { joinPath } from '../../../../base/common/resources.js';
|
||||
import { URI } from '../../../../base/common/uri.js';
|
||||
import { isUUID } from '../../../../base/common/uuid.js';
|
||||
@@ -11,16 +12,20 @@ import { mock } from '../../../../base/test/common/mock.js';
|
||||
import { IConfigurationService } from '../../../configuration/common/configuration.js';
|
||||
import { TestConfigurationService } from '../../../configuration/test/common/testConfigurationService.js';
|
||||
import { IEnvironmentService } from '../../../environment/common/environment.js';
|
||||
import { IRawGalleryExtensionVersion, sortExtensionVersions, filterLatestExtensionVersionsForTargetPlatform } from '../../common/extensionGalleryService.js';
|
||||
import { IAllowedExtensionsService, IGalleryExtension } from '../../common/extensionManagement.js';
|
||||
import { IExtensionGalleryManifestService } from '../../common/extensionGalleryManifest.js';
|
||||
import { AbstractExtensionGalleryService, ExtensionGalleryServiceWithNoStorageService, IRawGalleryExtensionVersion, sortExtensionVersions, filterLatestExtensionVersionsForTargetPlatform } from '../../common/extensionGalleryService.js';
|
||||
import { IFileService } from '../../../files/common/files.js';
|
||||
import { FileService } from '../../../files/common/fileService.js';
|
||||
import { InMemoryFileSystemProvider } from '../../../files/common/inMemoryFilesystemProvider.js';
|
||||
import { NullLogService } from '../../../log/common/log.js';
|
||||
import { TestInstantiationService } from '../../../instantiation/test/common/instantiationServiceMock.js';
|
||||
import { ILogService, NullLogService } from '../../../log/common/log.js';
|
||||
import product from '../../../product/common/product.js';
|
||||
import { IProductService } from '../../../product/common/productService.js';
|
||||
import { IRequestService } from '../../../request/common/request.js';
|
||||
import { resolveMarketplaceHeaders } from '../../../externalServices/common/marketplace.js';
|
||||
import { InMemoryStorageService, IStorageService } from '../../../storage/common/storage.js';
|
||||
import { TelemetryConfiguration, TELEMETRY_SETTING_ID } from '../../../telemetry/common/telemetry.js';
|
||||
import { ITelemetryService, TelemetryConfiguration, TELEMETRY_SETTING_ID } from '../../../telemetry/common/telemetry.js';
|
||||
import { TargetPlatform } from '../../../extensions/common/extensions.js';
|
||||
import { NullTelemetryService } from '../../../telemetry/common/telemetryUtils.js';
|
||||
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';
|
||||
@@ -459,3 +464,103 @@ suite('Extension Gallery Service', () => {
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
suite('Extension Gallery Service - Auto Update Builtin Extensions', () => {
|
||||
|
||||
const disposables = ensureNoDisposablesAreLeakedInTestSuite();
|
||||
|
||||
function createGalleryService(autoUpdateBuiltinExtensions: string[], productVersion: string = '1.66.0'): AbstractExtensionGalleryService {
|
||||
const instantiationService = disposables.add(new TestInstantiationService());
|
||||
const logService = new NullLogService();
|
||||
const fileService = disposables.add(new FileService(logService));
|
||||
const fileSystemProvider = disposables.add(new InMemoryFileSystemProvider());
|
||||
disposables.add(fileService.registerProvider('vscode-tests', fileSystemProvider));
|
||||
instantiationService.stub(ILogService, logService);
|
||||
instantiationService.stub(IFileService, fileService);
|
||||
instantiationService.stub(IRequestService, { async request() { return { res: { statusCode: 200 }, stream: newWriteableBufferStream() }; } });
|
||||
instantiationService.stub(IEnvironmentService, new EnvironmentServiceMock(joinPath(URI.file('tests').with({ scheme: 'vscode-tests' }), 'machineid')));
|
||||
instantiationService.stub(ITelemetryService, NullTelemetryService);
|
||||
instantiationService.stub(IConfigurationService, new TestConfigurationService());
|
||||
instantiationService.stub(IAllowedExtensionsService, { isAllowed: () => true });
|
||||
instantiationService.stub(IExtensionGalleryManifestService, { extensionGalleryManifest: undefined, extensionGalleryManifestStatus: 0 });
|
||||
instantiationService.stub(IProductService, {
|
||||
_serviceBrand: undefined,
|
||||
version: productVersion,
|
||||
autoUpdateBuiltinExtensions,
|
||||
});
|
||||
return disposables.add(instantiationService.createInstance(ExtensionGalleryServiceWithNoStorageService));
|
||||
}
|
||||
|
||||
function aGalleryExtension(id: string, version: string): IGalleryExtension {
|
||||
const [publisher, name] = id.split('.');
|
||||
return {
|
||||
identifier: { id, uuid: id },
|
||||
name,
|
||||
version,
|
||||
publisher,
|
||||
publisherDisplayName: publisher,
|
||||
allTargetPlatforms: [TargetPlatform.UNDEFINED],
|
||||
properties: {
|
||||
isPreReleaseVersion: false,
|
||||
targetPlatform: TargetPlatform.UNDEFINED,
|
||||
engine: undefined,
|
||||
enabledApiProposals: undefined,
|
||||
},
|
||||
assets: { manifest: null },
|
||||
} as unknown as IGalleryExtension;
|
||||
}
|
||||
|
||||
test('extension with matching major.minor is compatible', async () => {
|
||||
const galleryService = createGalleryService(['pub.name'], '1.66.0');
|
||||
const extension = aGalleryExtension('pub.name', '1.66.1');
|
||||
|
||||
const result = await galleryService.isExtensionCompatible(extension, false, TargetPlatform.UNDEFINED, { version: '1.66.0' });
|
||||
|
||||
assert.strictEqual(result, true);
|
||||
});
|
||||
|
||||
test('extension with mismatched major.minor is not compatible', async () => {
|
||||
const galleryService = createGalleryService(['pub.name'], '1.66.0');
|
||||
const extension = aGalleryExtension('pub.name', '1.67.0');
|
||||
|
||||
const result = await galleryService.isExtensionCompatible(extension, false, TargetPlatform.UNDEFINED, { version: '1.66.0' });
|
||||
|
||||
assert.strictEqual(result, false);
|
||||
});
|
||||
|
||||
test('extension not in autoUpdateBuiltinExtensions is not version-restricted', async () => {
|
||||
const galleryService = createGalleryService(['pub.other'], '1.66.0');
|
||||
const extension = aGalleryExtension('pub.name', '1.67.0');
|
||||
|
||||
const result = await galleryService.isExtensionCompatible(extension, false, TargetPlatform.UNDEFINED, { version: '1.66.0' });
|
||||
|
||||
assert.strictEqual(result, true);
|
||||
});
|
||||
|
||||
test('extension with same major but different minor is not compatible', async () => {
|
||||
const galleryService = createGalleryService(['pub.name'], '1.66.0');
|
||||
const extension = aGalleryExtension('pub.name', '1.65.5');
|
||||
|
||||
const result = await galleryService.isExtensionCompatible(extension, false, TargetPlatform.UNDEFINED, { version: '1.66.0' });
|
||||
|
||||
assert.strictEqual(result, false);
|
||||
});
|
||||
|
||||
test('version check is case insensitive for extension id', async () => {
|
||||
const galleryService = createGalleryService(['Pub.Name'], '1.66.0');
|
||||
const extension = aGalleryExtension('pub.name', '1.67.0');
|
||||
|
||||
const result = await galleryService.isExtensionCompatible(extension, false, TargetPlatform.UNDEFINED, { version: '1.66.0' });
|
||||
|
||||
assert.strictEqual(result, false);
|
||||
});
|
||||
|
||||
test('no version restriction when autoUpdateBuiltinExtensions is empty', async () => {
|
||||
const galleryService = createGalleryService([], '1.66.0');
|
||||
const extension = aGalleryExtension('pub.name', '1.67.0');
|
||||
|
||||
const result = await galleryService.isExtensionCompatible(extension, false, TargetPlatform.UNDEFINED, { version: '1.66.0' });
|
||||
|
||||
assert.strictEqual(result, true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -340,6 +340,114 @@ suite('NativeExtensionsScanerService Test', () => {
|
||||
function anExtensionManifest(manifest: Partial<IScannedExtensionManifest>): Partial<IExtensionManifest> {
|
||||
return { engines: { vscode: '^1.66.0' }, version: '1.0.0', main: 'main.js', activationEvents: ['*'], ...manifest };
|
||||
}
|
||||
|
||||
suite('auto update builtin extensions', () => {
|
||||
|
||||
test('scan user extension with matching product version is included', async () => {
|
||||
instantiationService.stub(IProductService, { version: '1.66.0', quality: 'stable', autoUpdateBuiltinExtensions: ['pub.name'] });
|
||||
await aUserExtension(anExtensionManifest({ 'name': 'name', 'publisher': 'pub', version: '1.66.1' }));
|
||||
const testObject: IExtensionsScannerService = disposables.add(instantiationService.createInstance(ExtensionsScannerService));
|
||||
|
||||
const actual = await testObject.scanAllUserExtensions({ includeAllVersions: false, includeInvalid: false });
|
||||
|
||||
assert.deepStrictEqual(actual.length, 1);
|
||||
assert.deepStrictEqual(actual[0].manifest.version, '1.66.1');
|
||||
});
|
||||
|
||||
test('scan user extension with mismatched product version is excluded', async () => {
|
||||
instantiationService.stub(IProductService, { version: '1.66.0', quality: 'stable', autoUpdateBuiltinExtensions: ['pub.name'] });
|
||||
await aUserExtension(anExtensionManifest({ 'name': 'name', 'publisher': 'pub', version: '1.67.0' }));
|
||||
const testObject: IExtensionsScannerService = disposables.add(instantiationService.createInstance(ExtensionsScannerService));
|
||||
|
||||
const actual = await testObject.scanAllUserExtensions({ includeAllVersions: false, includeInvalid: false });
|
||||
|
||||
assert.deepStrictEqual(actual.length, 0);
|
||||
});
|
||||
|
||||
test('scan user extension not in autoUpdateBuiltinExtensions is not filtered', async () => {
|
||||
instantiationService.stub(IProductService, { version: '1.66.0', quality: 'stable', autoUpdateBuiltinExtensions: ['pub.other'] });
|
||||
await aUserExtension(anExtensionManifest({ 'name': 'name', 'publisher': 'pub', version: '1.67.0' }));
|
||||
const testObject: IExtensionsScannerService = disposables.add(instantiationService.createInstance(ExtensionsScannerService));
|
||||
|
||||
const actual = await testObject.scanAllUserExtensions({ includeAllVersions: false, includeInvalid: false });
|
||||
|
||||
assert.deepStrictEqual(actual.length, 1);
|
||||
});
|
||||
|
||||
test('scan picks matching version when multiple versions exist', async () => {
|
||||
instantiationService.stub(IProductService, { version: '1.66.0', quality: 'stable', autoUpdateBuiltinExtensions: ['pub.name'] });
|
||||
await aUserExtension(anExtensionManifest({ 'name': 'name', 'publisher': 'pub', version: '1.66.1' }));
|
||||
await aUserExtension(anExtensionManifest({ 'name': 'name', 'publisher': 'pub', version: '1.67.0' }));
|
||||
const testObject: IExtensionsScannerService = disposables.add(instantiationService.createInstance(ExtensionsScannerService));
|
||||
|
||||
const actual = await testObject.scanAllUserExtensions({ includeAllVersions: false, includeInvalid: false });
|
||||
|
||||
assert.deepStrictEqual(actual.length, 1);
|
||||
assert.deepStrictEqual(actual[0].manifest.version, '1.66.1');
|
||||
});
|
||||
|
||||
test('scan all extensions prefers matching user extension over system extension', async () => {
|
||||
instantiationService.stub(IProductService, { version: '1.66.0', quality: 'stable', autoUpdateBuiltinExtensions: ['pub.name'] });
|
||||
await aSystemExtension(anExtensionManifest({ 'name': 'name', 'publisher': 'pub', version: '1.66.0' }));
|
||||
await aUserExtension(anExtensionManifest({ 'name': 'name', 'publisher': 'pub', version: '1.66.1' }));
|
||||
const testObject: IExtensionsScannerService = disposables.add(instantiationService.createInstance(ExtensionsScannerService));
|
||||
|
||||
const actual = await testObject.scanAllExtensions({}, { profileLocation: instantiationService.get(IUserDataProfilesService).defaultProfile.extensionsResource, includeInvalid: false });
|
||||
|
||||
const extension = actual.find(e => e.identifier.id === 'pub.name');
|
||||
assert.ok(extension);
|
||||
assert.deepStrictEqual(extension.manifest.version, '1.66.1');
|
||||
assert.deepStrictEqual(extension.isBuiltin, true);
|
||||
});
|
||||
|
||||
test('scan all extensions falls back to system extension when user extension has mismatched version', async () => {
|
||||
instantiationService.stub(IProductService, { version: '1.66.0', quality: 'stable', autoUpdateBuiltinExtensions: ['pub.name'] });
|
||||
await aSystemExtension(anExtensionManifest({ 'name': 'name', 'publisher': 'pub', version: '1.66.0' }));
|
||||
await aUserExtension(anExtensionManifest({ 'name': 'name', 'publisher': 'pub', version: '1.67.0' }));
|
||||
const testObject: IExtensionsScannerService = disposables.add(instantiationService.createInstance(ExtensionsScannerService));
|
||||
|
||||
const actual = await testObject.scanAllExtensions({}, { profileLocation: instantiationService.get(IUserDataProfilesService).defaultProfile.extensionsResource, includeInvalid: false });
|
||||
|
||||
const extension = actual.find(e => e.identifier.id === 'pub.name');
|
||||
assert.ok(extension);
|
||||
assert.deepStrictEqual(extension.manifest.version, '1.66.0');
|
||||
assert.deepStrictEqual(extension.type, ExtensionType.System);
|
||||
});
|
||||
|
||||
test('system extension has autoUpdate set to true when in autoUpdateBuiltinExtensions and quality is stable', async () => {
|
||||
instantiationService.stub(IProductService, { version: '1.66.0', quality: 'stable', autoUpdateBuiltinExtensions: ['pub.name'] });
|
||||
await aSystemExtension(anExtensionManifest({ 'name': 'name', 'publisher': 'pub' }));
|
||||
const testObject: IExtensionsScannerService = disposables.add(instantiationService.createInstance(ExtensionsScannerService));
|
||||
|
||||
const actual = await testObject.scanSystemExtensions({});
|
||||
|
||||
assert.deepStrictEqual(actual.length, 1);
|
||||
assert.deepStrictEqual(actual[0].autoUpdate, true);
|
||||
});
|
||||
|
||||
test('system extension has autoUpdate set to false when not in autoUpdateBuiltinExtensions', async () => {
|
||||
instantiationService.stub(IProductService, { version: '1.66.0', quality: 'stable', autoUpdateBuiltinExtensions: ['pub.other'] });
|
||||
await aSystemExtension(anExtensionManifest({ 'name': 'name', 'publisher': 'pub' }));
|
||||
const testObject: IExtensionsScannerService = disposables.add(instantiationService.createInstance(ExtensionsScannerService));
|
||||
|
||||
const actual = await testObject.scanSystemExtensions({});
|
||||
|
||||
assert.deepStrictEqual(actual.length, 1);
|
||||
assert.deepStrictEqual(actual[0].autoUpdate, false);
|
||||
});
|
||||
|
||||
test('system extension has autoUpdate set to false when quality is not stable', async () => {
|
||||
instantiationService.stub(IProductService, { version: '1.66.0', quality: 'insider', autoUpdateBuiltinExtensions: ['pub.name'] });
|
||||
await aSystemExtension(anExtensionManifest({ 'name': 'name', 'publisher': 'pub' }));
|
||||
const testObject: IExtensionsScannerService = disposables.add(instantiationService.createInstance(ExtensionsScannerService));
|
||||
|
||||
const actual = await testObject.scanSystemExtensions({});
|
||||
|
||||
assert.deepStrictEqual(actual.length, 1);
|
||||
assert.deepStrictEqual(actual[0].autoUpdate, false);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
suite('ExtensionScannerInput', () => {
|
||||
|
||||
@@ -1959,8 +1959,8 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
|
||||
// Skip if check updates only for builtin extensions and current extension is not builtin.
|
||||
continue;
|
||||
}
|
||||
if (installed.isBuiltin && !installed.local?.pinned && (installed.type === ExtensionType.System || !installed.local?.identifier.uuid)) {
|
||||
// Skip checking updates for a builtin extension if it is a system extension or if it does not has Marketplace identifier
|
||||
if (!installed.local?.autoUpdate && installed.isBuiltin && !installed.local?.pinned && (installed.type === ExtensionType.System || !installed.local?.identifier.uuid)) {
|
||||
// Skip checking updates for a builtin extension if it is a system extension or if it does not have a Marketplace identifier
|
||||
continue;
|
||||
}
|
||||
if (installed.local?.source === 'resource') {
|
||||
@@ -2235,6 +2235,11 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
|
||||
return false;
|
||||
}
|
||||
|
||||
if (extension.local?.autoUpdate) {
|
||||
// Extensions marked for auto-update are always auto-updated
|
||||
return true;
|
||||
}
|
||||
|
||||
const autoUpdateValue = this.getAutoUpdateValue();
|
||||
|
||||
if (autoUpdateValue === false) {
|
||||
|
||||
@@ -1412,6 +1412,7 @@ class WorkspaceExtensionsManagementService extends Disposable {
|
||||
installedTimestamp: extension.metadata?.installedTimestamp,
|
||||
updated: !!extension.metadata?.updated,
|
||||
pinned: !!extension.metadata?.pinned,
|
||||
autoUpdate: false,
|
||||
isWorkspaceScoped: true,
|
||||
private: false,
|
||||
source: 'resource',
|
||||
|
||||
@@ -261,6 +261,7 @@ function toLocalExtension(extension: IExtension): ILocalExtension {
|
||||
targetPlatform: TargetPlatform.WEB,
|
||||
updated: !!metadata.updated,
|
||||
pinned: !!metadata?.pinned,
|
||||
autoUpdate: false,
|
||||
private: !!metadata.private,
|
||||
isWorkspaceScoped: false,
|
||||
source: metadata?.source ?? (extension.identifier.uuid ? 'gallery' : 'resource'),
|
||||
|
||||
Reference in New Issue
Block a user