UNPKG

@codingame/monaco-vscode-extensions-service-override

Version:

VSCode public API plugged on the monaco editor - extensions service-override

976 lines (973 loc) 54.5 kB
import { __decorate, __param } from '@codingame/monaco-vscode-api/external/tslib/tslib.es6'; import { ExtensionType, TargetPlatform, parseEnabledApiProposalNames } from '@codingame/monaco-vscode-api/vscode/vs/platform/extensions/common/extensions'; import { IBuiltinExtensionsScannerService } from '@codingame/monaco-vscode-api/vscode/vs/platform/extensions/common/extensions.service'; import { IBrowserWorkbenchEnvironmentService } from '@codingame/monaco-vscode-api/vscode/vs/workbench/services/environment/browser/environmentService.service'; import { IExtensionGalleryService } from '@codingame/monaco-vscode-api/vscode/vs/platform/extensionManagement/common/extensionManagement.service'; import '@codingame/monaco-vscode-api/vscode/vs/platform/instantiation/common/instantiation'; import { isWeb, Language } from '@codingame/monaco-vscode-api/vscode/vs/base/common/platform'; import '@codingame/monaco-vscode-api/vscode/vs/platform/instantiation/common/extensions'; import { joinPath } from '@codingame/monaco-vscode-api/vscode/vs/base/common/resources'; import { URI } from '@codingame/monaco-vscode-api/vscode/vs/base/common/uri'; import { FileOperationResult } from '@codingame/monaco-vscode-api/vscode/vs/platform/files/common/files'; import { IFileService } from '@codingame/monaco-vscode-api/vscode/vs/platform/files/common/files.service'; import { Queue } from '@codingame/monaco-vscode-api/vscode/vs/base/common/async'; import { VSBuffer } from '@codingame/monaco-vscode-api/vscode/vs/base/common/buffer'; import { ILogService } from '@codingame/monaco-vscode-api/vscode/vs/platform/log/common/log.service'; import { CancellationToken } from '@codingame/monaco-vscode-api/vscode/vs/base/common/cancellation'; import { isMalicious, areSameExtensions, getExtensionId, getGalleryExtensionId } from '@codingame/monaco-vscode-api/vscode/vs/platform/extensionManagement/common/extensionManagementUtil'; import { Disposable } from '@codingame/monaco-vscode-api/vscode/vs/base/common/lifecycle'; import { localizeManifest } from '@codingame/monaco-vscode-api/vscode/vs/platform/extensionManagement/common/extensionNls'; import { localize, localize2 } from '@codingame/monaco-vscode-api/vscode/vs/nls'; import { semverExports } from '@codingame/monaco-vscode-api/_virtual/semver'; import { isString, isUndefined } from '@codingame/monaco-vscode-api/vscode/vs/base/common/types'; import { getErrorMessage } from '@codingame/monaco-vscode-api/vscode/vs/base/common/errors'; import { ResourceMap } from '@codingame/monaco-vscode-api/vscode/vs/base/common/map'; import { IExtensionManifestPropertiesService } from '@codingame/monaco-vscode-api/vscode/vs/workbench/services/extensions/common/extensionManifestPropertiesService.service'; import { migratePlatformSpecificExtensionGalleryResourceURL } from '../../../../platform/extensionResourceLoader/common/extensionResourceLoader.js'; import { IExtensionResourceLoaderService } from '@codingame/monaco-vscode-api/vscode/vs/platform/extensionResourceLoader/common/extensionResourceLoader.service'; import { registerAction2, Action2 } from '@codingame/monaco-vscode-api/vscode/vs/platform/actions/common/actions'; import { Categories } from '@codingame/monaco-vscode-api/vscode/vs/platform/action/common/actionCommonCategories'; import { IsWebContext } from '@codingame/monaco-vscode-api/vscode/vs/platform/contextkey/common/contextkeys'; import { IEditorService } from '@codingame/monaco-vscode-api/vscode/vs/workbench/services/editor/common/editorService.service'; import { basename } from '@codingame/monaco-vscode-api/vscode/vs/base/common/path'; import { IExtensionStorageService } from '@codingame/monaco-vscode-api/vscode/vs/platform/extensionManagement/common/extensionStorage.service'; import { isNonEmptyArray } from '@codingame/monaco-vscode-api/vscode/vs/base/common/arrays'; import { LifecyclePhase } from '@codingame/monaco-vscode-api/vscode/vs/workbench/services/lifecycle/common/lifecycle'; import { ILifecycleService } from '@codingame/monaco-vscode-api/vscode/vs/workbench/services/lifecycle/common/lifecycle.service'; import { StorageScope, StorageTarget } from '@codingame/monaco-vscode-api/vscode/vs/platform/storage/common/storage'; import { IStorageService } from '@codingame/monaco-vscode-api/vscode/vs/platform/storage/common/storage.service'; import { IProductService } from '@codingame/monaco-vscode-api/vscode/vs/platform/product/common/productService.service'; import { validateExtensionManifest } from '@codingame/monaco-vscode-api/vscode/vs/platform/extensions/common/extensionValidator'; import Severity from '@codingame/monaco-vscode-api/vscode/vs/base/common/severity'; import { IUserDataProfileService } from '@codingame/monaco-vscode-api/vscode/vs/workbench/services/userDataProfile/common/userDataProfile.service'; import { IUserDataProfilesService } from '@codingame/monaco-vscode-api/vscode/vs/platform/userDataProfile/common/userDataProfile.service'; import { IUriIdentityService } from '@codingame/monaco-vscode-api/vscode/vs/platform/uriIdentity/common/uriIdentity.service'; function isGalleryExtensionInfo(obj) { const galleryExtensionInfo = obj; return typeof galleryExtensionInfo?.id === "string" && (galleryExtensionInfo.preRelease === undefined || typeof galleryExtensionInfo.preRelease === "boolean") && (galleryExtensionInfo.migrateStorageFrom === undefined || typeof galleryExtensionInfo.migrateStorageFrom === "string"); } function isUriComponents(obj) { if (!obj) { return false; } const thing = obj; return typeof thing?.path === "string" && typeof thing?.scheme === "string"; } let WebExtensionsScannerService = class WebExtensionsScannerService extends Disposable { constructor( environmentService, builtinExtensionsScannerService, fileService, logService, galleryService, extensionManifestPropertiesService, extensionResourceLoaderService, extensionStorageService, storageService, productService, userDataProfilesService, uriIdentityService, lifecycleService ) { super(); this.environmentService = environmentService; this.builtinExtensionsScannerService = builtinExtensionsScannerService; this.fileService = fileService; this.logService = logService; this.galleryService = galleryService; this.extensionManifestPropertiesService = extensionManifestPropertiesService; this.extensionResourceLoaderService = extensionResourceLoaderService; this.extensionStorageService = extensionStorageService; this.storageService = storageService; this.productService = productService; this.userDataProfilesService = userDataProfilesService; this.uriIdentityService = uriIdentityService; this.systemExtensionsCacheResource = undefined; this.customBuiltinExtensionsCacheResource = undefined; this.resourcesAccessQueueMap = ( new ResourceMap()); if (isWeb) { this.systemExtensionsCacheResource = joinPath(environmentService.userRoamingDataHome, "systemExtensionsCache.json"); this.customBuiltinExtensionsCacheResource = joinPath( environmentService.userRoamingDataHome, "customBuiltinExtensionsCache.json" ); lifecycleService.when(LifecyclePhase.Eventually).then(() => this.updateCaches()); } this.extensionsEnabledWithApiProposalVersion = productService.extensionsEnabledWithApiProposalVersion?.map(id => id.toLowerCase()) ?? []; } readCustomBuiltinExtensionsInfoFromEnv() { if (!this._customBuiltinExtensionsInfoPromise) { this._customBuiltinExtensionsInfoPromise = (async () => { let extensions = []; const extensionLocations = []; const extensionGalleryResources = []; const extensionsToMigrate = []; const customBuiltinExtensionsInfo = this.environmentService.options && Array.isArray(this.environmentService.options.additionalBuiltinExtensions) ? ( this.environmentService.options.additionalBuiltinExtensions.map(additionalBuiltinExtension => isString(additionalBuiltinExtension) ? { id: additionalBuiltinExtension } : additionalBuiltinExtension)) : []; for (const e of customBuiltinExtensionsInfo) { if (isGalleryExtensionInfo(e)) { extensions.push({ id: e.id, preRelease: !!e.preRelease }); if (e.migrateStorageFrom) { extensionsToMigrate.push([e.migrateStorageFrom, e.id]); } } else if (isUriComponents(e)) { const extensionLocation = URI.revive(e); if (await this.extensionResourceLoaderService.isExtensionGalleryResource(extensionLocation)) { extensionGalleryResources.push(extensionLocation); } else { extensionLocations.push(extensionLocation); } } } if (extensions.length) { extensions = await this.checkAdditionalBuiltinExtensions(extensions); } if (extensions.length) { this.logService.info("Found additional builtin gallery extensions in env", extensions); } if (extensionLocations.length) { this.logService.info("Found additional builtin location extensions in env", ( extensionLocations.map(e => ( e.toString())))); } if (extensionGalleryResources.length) { this.logService.info( "Found additional builtin extension gallery resources in env", ( extensionGalleryResources.map(e => ( e.toString()))) ); } return { extensions, extensionsToMigrate, extensionLocations, extensionGalleryResources }; })(); } return this._customBuiltinExtensionsInfoPromise; } async checkAdditionalBuiltinExtensions(extensions) { const extensionsControlManifest = await this.galleryService.getExtensionsControlManifest(); const result = []; for (const extension of extensions) { if (isMalicious({ id: extension.id }, extensionsControlManifest.malicious)) { this.logService.info( `Checking additional builtin extensions: Ignoring '${extension.id}' because it is reported to be malicious.` ); continue; } const deprecationInfo = extensionsControlManifest.deprecated[extension.id.toLowerCase()]; if (deprecationInfo?.extension?.autoMigrate) { const preReleaseExtensionId = deprecationInfo.extension.id; this.logService.info( `Checking additional builtin extensions: '${extension.id}' is deprecated, instead using '${preReleaseExtensionId}'` ); result.push({ id: preReleaseExtensionId, preRelease: !!extension.preRelease }); } else { result.push(extension); } } return result; } async readSystemExtensions() { const systemExtensions = await this.builtinExtensionsScannerService.scanBuiltinExtensions(); const cachedSystemExtensions = await Promise.all(( (await this.readSystemExtensionsCache()).map(e => this.toScannedExtension(e, true, ExtensionType.System)))); const result = ( new Map()); for (const extension of [...systemExtensions, ...cachedSystemExtensions]) { const existing = result.get(extension.identifier.id.toLowerCase()); if (existing) { if (semverExports.gt(existing.manifest.version, extension.manifest.version)) { continue; } } result.set(extension.identifier.id.toLowerCase(), extension); } return [...( result.values())]; } async readCustomBuiltinExtensions(scanOptions) { const [customBuiltinExtensionsFromLocations, customBuiltinExtensionsFromGallery] = await Promise.all([ this.getCustomBuiltinExtensionsFromLocations(scanOptions), this.getCustomBuiltinExtensionsFromGallery(scanOptions) ]); const customBuiltinExtensions = [ ...customBuiltinExtensionsFromLocations, ...customBuiltinExtensionsFromGallery ]; await this.migrateExtensionsStorage(customBuiltinExtensions); return customBuiltinExtensions; } async getCustomBuiltinExtensionsFromLocations(scanOptions) { const { extensionLocations } = await this.readCustomBuiltinExtensionsInfoFromEnv(); if (!extensionLocations.length) { return []; } const result = []; await Promise.allSettled(( extensionLocations.map(async extensionLocation => { try { const webExtension = await this.toWebExtension(extensionLocation); const extension = await this.toScannedExtension(webExtension, true); if (extension.isValid || !scanOptions?.skipInvalidExtensions) { result.push(extension); } else { this.logService.info( `Skipping invalid additional builtin extension ${webExtension.identifier.id}` ); } } catch (error) { this.logService.info(`Error while fetching the additional builtin extension ${( extensionLocation.toString())}.`, getErrorMessage(error)); } }))); return result; } async getCustomBuiltinExtensionsFromGallery(scanOptions) { if (!this.galleryService.isEnabled()) { this.logService.info( "Ignoring fetching additional builtin extensions from gallery as it is disabled." ); return []; } const result = []; const { extensions, extensionGalleryResources } = await this.readCustomBuiltinExtensionsInfoFromEnv(); try { const cacheValue = JSON.stringify({ extensions: extensions.sort((a, b) => a.id.localeCompare(b.id)), extensionGalleryResources: ( extensionGalleryResources.map(e => ( e.toString()))).sort() }); const useCache = this.storageService.get("additionalBuiltinExtensions", StorageScope.APPLICATION, "{}") === cacheValue; const webExtensions = await (useCache ? this.getCustomBuiltinExtensionsFromCache() : this.updateCustomBuiltinExtensionsCache()); if (webExtensions.length) { await Promise.all(( webExtensions.map(async webExtension => { try { const extension = await this.toScannedExtension(webExtension, true); if (extension.isValid || !scanOptions?.skipInvalidExtensions) { result.push(extension); } else { this.logService.info( `Skipping invalid additional builtin gallery extension ${webExtension.identifier.id}` ); } } catch (error) { this.logService.info( `Ignoring additional builtin extension ${webExtension.identifier.id} because there is an error while converting it into scanned extension`, getErrorMessage(error) ); } }))); } this.storageService.store( "additionalBuiltinExtensions", cacheValue, StorageScope.APPLICATION, StorageTarget.MACHINE ); } catch (error) { this.logService.info( "Ignoring following additional builtin extensions as there is an error while fetching them from gallery", ( extensions.map(( { id } ) => id)), getErrorMessage(error) ); } return result; } async getCustomBuiltinExtensionsFromCache() { const cachedCustomBuiltinExtensions = await this.readCustomBuiltinExtensionsCache(); const webExtensionsMap = ( new Map()); for (const webExtension of cachedCustomBuiltinExtensions) { const existing = webExtensionsMap.get(webExtension.identifier.id.toLowerCase()); if (existing) { if (semverExports.gt(existing.version, webExtension.version)) { continue; } } if (webExtension.metadata?.isPreReleaseVersion && !webExtension.metadata?.preRelease) { webExtension.metadata.preRelease = true; } webExtensionsMap.set(webExtension.identifier.id.toLowerCase(), webExtension); } return [...( webExtensionsMap.values())]; } async migrateExtensionsStorage(customBuiltinExtensions) { if (!this._migrateExtensionsStoragePromise) { this._migrateExtensionsStoragePromise = (async () => { const { extensionsToMigrate } = await this.readCustomBuiltinExtensionsInfoFromEnv(); if (!extensionsToMigrate.length) { return; } const fromExtensions = await this.galleryService.getExtensions(( extensionsToMigrate.map(([id]) => ({ id }))), CancellationToken.None); try { await Promise.allSettled(( extensionsToMigrate.map(async ([from, to]) => { const toExtension = customBuiltinExtensions.find(extension => areSameExtensions(extension.identifier, { id: to })); if (toExtension) { const fromExtension = fromExtensions.find(extension => areSameExtensions(extension.identifier, { id: from })); const fromExtensionManifest = fromExtension ? await this.galleryService.getManifest(fromExtension, CancellationToken.None) : null; const fromExtensionId = fromExtensionManifest ? getExtensionId(fromExtensionManifest.publisher, fromExtensionManifest.name) : from; const toExtensionId = getExtensionId(toExtension.manifest.publisher, toExtension.manifest.name); this.extensionStorageService.addToMigrationList(fromExtensionId, toExtensionId); } else { this.logService.info( `Skipped migrating extension storage from '${from}' to '${to}', because the '${to}' extension is not found.` ); } }))); } catch (error) { this.logService.error(error); } })(); } return this._migrateExtensionsStoragePromise; } async updateCaches() { await this.updateSystemExtensionsCache(); await this.updateCustomBuiltinExtensionsCache(); } async updateSystemExtensionsCache() { const systemExtensions = await this.builtinExtensionsScannerService.scanBuiltinExtensions(); const cachedSystemExtensions = (await this.readSystemExtensionsCache()).filter(cached => { const systemExtension = systemExtensions.find(e => areSameExtensions(e.identifier, cached.identifier)); return systemExtension && semverExports.gt(cached.version, systemExtension.manifest.version); }); await this.writeSystemExtensionsCache(() => cachedSystemExtensions); } async updateCustomBuiltinExtensionsCache() { if (!this._updateCustomBuiltinExtensionsCachePromise) { this._updateCustomBuiltinExtensionsCachePromise = (async () => { this.logService.info("Updating additional builtin extensions cache"); const { extensions, extensionGalleryResources } = await this.readCustomBuiltinExtensionsInfoFromEnv(); const [galleryWebExtensions, extensionGalleryResourceWebExtensions] = await Promise.all([ this.resolveBuiltinGalleryExtensions(extensions), this.resolveBuiltinExtensionGalleryResources(extensionGalleryResources) ]); const webExtensionsMap = ( new Map()); for (const webExtension of [...galleryWebExtensions, ...extensionGalleryResourceWebExtensions]) { webExtensionsMap.set(webExtension.identifier.id.toLowerCase(), webExtension); } await this.resolveDependenciesAndPackedExtensions(extensionGalleryResourceWebExtensions, webExtensionsMap); const webExtensions = [...( webExtensionsMap.values())]; await this.writeCustomBuiltinExtensionsCache(() => webExtensions); return webExtensions; })(); } return this._updateCustomBuiltinExtensionsCachePromise; } async resolveBuiltinExtensionGalleryResources(extensionGalleryResources) { if (extensionGalleryResources.length === 0) { return []; } const result = ( new Map()); const extensionInfos = []; await Promise.all(( extensionGalleryResources.map(async extensionGalleryResource => { try { const webExtension = await this.toWebExtensionFromExtensionGalleryResource(extensionGalleryResource); result.set(webExtension.identifier.id.toLowerCase(), webExtension); extensionInfos.push({ id: webExtension.identifier.id, version: webExtension.version }); } catch (error) { this.logService.info( `Ignoring additional builtin extension from gallery resource ${( extensionGalleryResource.toString())} because there is an error while converting it into web extension`, getErrorMessage(error) ); } }))); const galleryExtensions = await this.galleryService.getExtensions(extensionInfos, CancellationToken.None); for (const galleryExtension of galleryExtensions) { const webExtension = result.get(galleryExtension.identifier.id.toLowerCase()); if (webExtension) { result.set(galleryExtension.identifier.id.toLowerCase(), { ...webExtension, identifier: { id: webExtension.identifier.id, uuid: galleryExtension.identifier.uuid }, readmeUri: galleryExtension.assets.readme ? ( URI.parse(galleryExtension.assets.readme.uri)) : undefined, changelogUri: galleryExtension.assets.changelog ? ( URI.parse(galleryExtension.assets.changelog.uri)) : undefined, metadata: { isPreReleaseVersion: galleryExtension.properties.isPreReleaseVersion, preRelease: galleryExtension.properties.isPreReleaseVersion, isBuiltin: true, pinned: true } }); } } return [...( result.values())]; } async resolveBuiltinGalleryExtensions(extensions) { if (extensions.length === 0) { return []; } const webExtensions = []; const galleryExtensionsMap = await this.getExtensionsWithDependenciesAndPackedExtensions(extensions); const missingExtensions = extensions.filter(( { id } ) => !( galleryExtensionsMap.has(id.toLowerCase()))); if (missingExtensions.length) { this.logService.info( "Skipping the additional builtin extensions because their compatible versions are not found.", missingExtensions ); } await Promise.all(( [...( galleryExtensionsMap.values())].map(async gallery => { try { const webExtension = await this.toWebExtensionFromGallery(gallery, { isPreReleaseVersion: gallery.properties.isPreReleaseVersion, preRelease: gallery.properties.isPreReleaseVersion, isBuiltin: true }); webExtensions.push(webExtension); } catch (error) { this.logService.info( `Ignoring additional builtin extension ${gallery.identifier.id} because there is an error while converting it into web extension`, getErrorMessage(error) ); } }))); return webExtensions; } async resolveDependenciesAndPackedExtensions(webExtensions, result) { const extensionInfos = []; for (const webExtension of webExtensions) { for (const e of [ ...(webExtension.manifest?.extensionDependencies ?? []), ...(webExtension.manifest?.extensionPack ?? []) ]) { if (!( result.has(e.toLowerCase()))) { extensionInfos.push({ id: e, version: webExtension.version }); } } } if (extensionInfos.length === 0) { return; } const galleryExtensions = await this.getExtensionsWithDependenciesAndPackedExtensions(extensionInfos, ( new Set([...( result.keys())]))); await Promise.all(( [...( galleryExtensions.values())].map(async gallery => { try { const webExtension = await this.toWebExtensionFromGallery(gallery, { isPreReleaseVersion: gallery.properties.isPreReleaseVersion, preRelease: gallery.properties.isPreReleaseVersion, isBuiltin: true }); result.set(webExtension.identifier.id.toLowerCase(), webExtension); } catch (error) { this.logService.info( `Ignoring additional builtin extension ${gallery.identifier.id} because there is an error while converting it into web extension`, getErrorMessage(error) ); } }))); } async getExtensionsWithDependenciesAndPackedExtensions( toGet, seen = ( new Set()), result = ( new Map()) ) { if (toGet.length === 0) { return result; } const extensions = await this.galleryService.getExtensions(toGet, { compatible: true, targetPlatform: TargetPlatform.WEB }, CancellationToken.None); const packsAndDependencies = ( new Map()); for (const extension of extensions) { result.set(extension.identifier.id.toLowerCase(), extension); for (const id of [ ...(isNonEmptyArray(extension.properties.dependencies) ? extension.properties.dependencies : []), ...(isNonEmptyArray(extension.properties.extensionPack) ? extension.properties.extensionPack : []) ]) { if (!( result.has(id.toLowerCase())) && !( packsAndDependencies.has(id.toLowerCase())) && !( seen.has(id.toLowerCase()))) { const extensionInfo = toGet.find(e => areSameExtensions(e, extension.identifier)); packsAndDependencies.set(id.toLowerCase(), { id, preRelease: extensionInfo?.preRelease }); } } } return this.getExtensionsWithDependenciesAndPackedExtensions([...( packsAndDependencies.values())].filter(( { id } ) => !( result.has(id.toLowerCase()))), seen, result); } async scanSystemExtensions() { return this.readSystemExtensions(); } async scanUserExtensions(profileLocation, scanOptions) { const extensions = ( new Map()); const customBuiltinExtensions = await this.readCustomBuiltinExtensions(scanOptions); for (const extension of customBuiltinExtensions) { extensions.set(extension.identifier.id.toLowerCase(), extension); } const installedExtensions = await this.scanInstalledExtensions(profileLocation, scanOptions); for (const extension of installedExtensions) { extensions.set(extension.identifier.id.toLowerCase(), extension); } return [...( extensions.values())]; } async scanExtensionsUnderDevelopment() { const devExtensions = this.environmentService.options?.developmentOptions?.extensions; const result = []; if (Array.isArray(devExtensions)) { await Promise.allSettled(( devExtensions.map(async devExtension => { try { const location = URI.revive(devExtension); if (URI.isUri(location)) { const webExtension = await this.toWebExtension(location); result.push(await this.toScannedExtension(webExtension, false)); } else { this.logService.info( `Skipping the extension under development ${devExtension} as it is not URI type.` ); } } catch (error) { this.logService.info(`Error while fetching the extension under development ${( devExtension.toString())}.`, getErrorMessage(error)); } }))); } return result; } async scanExistingExtension(extensionLocation, extensionType, profileLocation) { if (extensionType === ExtensionType.System) { const systemExtensions = await this.scanSystemExtensions(); return systemExtensions.find(e => ( e.location.toString()) === ( extensionLocation.toString())) || null; } const userExtensions = await this.scanUserExtensions(profileLocation); return userExtensions.find(e => ( e.location.toString()) === ( extensionLocation.toString())) || null; } async scanExtensionManifest(extensionLocation) { try { return await this.getExtensionManifest(extensionLocation); } catch (error) { this.logService.warn(`Error while fetching manifest from ${( extensionLocation.toString())}`, getErrorMessage(error)); return null; } } async addExtensionFromGallery(galleryExtension, metadata, profileLocation) { const webExtension = await this.toWebExtensionFromGallery(galleryExtension, metadata); return this.addWebExtension(webExtension, profileLocation); } async addExtension(location, metadata, profileLocation) { const webExtension = await this.toWebExtension( location, undefined, undefined, undefined, undefined, undefined, undefined, metadata ); const extension = await this.toScannedExtension(webExtension, false); await this.addToInstalledExtensions([webExtension], profileLocation); return extension; } async removeExtension(extension, profileLocation) { await this.writeInstalledExtensions(profileLocation, installedExtensions => installedExtensions.filter( installedExtension => !areSameExtensions(installedExtension.identifier, extension.identifier) )); } async updateMetadata(extension, metadata, profileLocation) { let updatedExtension = undefined; await this.writeInstalledExtensions(profileLocation, installedExtensions => { const result = []; for (const installedExtension of installedExtensions) { if (areSameExtensions(extension.identifier, installedExtension.identifier)) { installedExtension.metadata = { ...installedExtension.metadata, ...metadata }; updatedExtension = installedExtension; result.push(installedExtension); } else { result.push(installedExtension); } } return result; }); if (!updatedExtension) { throw ( new Error("Extension not found")); } return this.toScannedExtension(updatedExtension, extension.isBuiltin); } async copyExtensions(fromProfileLocation, toProfileLocation, filter) { const extensionsToCopy = []; const fromWebExtensions = await this.readInstalledExtensions(fromProfileLocation); await Promise.all(( fromWebExtensions.map(async webExtension => { const scannedExtension = await this.toScannedExtension(webExtension, false); if (filter(scannedExtension)) { extensionsToCopy.push(webExtension); } }))); if (extensionsToCopy.length) { await this.addToInstalledExtensions(extensionsToCopy, toProfileLocation); } } async addWebExtension(webExtension, profileLocation) { const isSystem = !!(await this.scanSystemExtensions()).find(e => areSameExtensions(e.identifier, webExtension.identifier)); const isBuiltin = !!webExtension.metadata?.isBuiltin; const extension = await this.toScannedExtension(webExtension, isBuiltin); if (isSystem) { await this.writeSystemExtensionsCache(systemExtensions => { systemExtensions = systemExtensions.filter( extension => !areSameExtensions(extension.identifier, webExtension.identifier) ); systemExtensions.push(webExtension); return systemExtensions; }); return extension; } if (isBuiltin) { await this.writeCustomBuiltinExtensionsCache(customBuiltinExtensions => { customBuiltinExtensions = customBuiltinExtensions.filter( extension => !areSameExtensions(extension.identifier, webExtension.identifier) ); customBuiltinExtensions.push(webExtension); return customBuiltinExtensions; }); const installedExtensions = await this.readInstalledExtensions(profileLocation); if (( installedExtensions.some(e => areSameExtensions(e.identifier, webExtension.identifier)))) { await this.addToInstalledExtensions([webExtension], profileLocation); } return extension; } await this.addToInstalledExtensions([webExtension], profileLocation); return extension; } async addToInstalledExtensions(webExtensions, profileLocation) { await this.writeInstalledExtensions(profileLocation, installedExtensions => { installedExtensions = installedExtensions.filter(installedExtension => ( webExtensions.some( extension => !areSameExtensions(installedExtension.identifier, extension.identifier) ))); installedExtensions.push(...webExtensions); return installedExtensions; }); } async scanInstalledExtensions(profileLocation, scanOptions) { let installedExtensions = await this.readInstalledExtensions(profileLocation); if (!this.uriIdentityService.extUri.isEqual( profileLocation, this.userDataProfilesService.defaultProfile.extensionsResource )) { installedExtensions = installedExtensions.filter(i => !i.metadata?.isApplicationScoped); const defaultProfileExtensions = await this.readInstalledExtensions(this.userDataProfilesService.defaultProfile.extensionsResource); installedExtensions.push(...defaultProfileExtensions.filter(i => i.metadata?.isApplicationScoped)); } installedExtensions.sort( (a, b) => a.identifier.id < b.identifier.id ? -1 : a.identifier.id > b.identifier.id ? 1 : semverExports.rcompare(a.version, b.version) ); const result = ( new Map()); for (const webExtension of installedExtensions) { const existing = result.get(webExtension.identifier.id.toLowerCase()); if (existing && semverExports.gt(existing.manifest.version, webExtension.version)) { continue; } const extension = await this.toScannedExtension(webExtension, false); if (extension.isValid || !scanOptions?.skipInvalidExtensions) { result.set(extension.identifier.id.toLowerCase(), extension); } else { this.logService.info(`Skipping invalid installed extension ${webExtension.identifier.id}`); } } return [...( result.values())]; } async toWebExtensionFromGallery(galleryExtension, metadata) { const extensionLocation = await this.extensionResourceLoaderService.getExtensionGalleryResourceURL({ publisher: galleryExtension.publisher, name: galleryExtension.name, version: galleryExtension.version, targetPlatform: galleryExtension.properties.targetPlatform === TargetPlatform.WEB ? TargetPlatform.WEB : undefined }, "extension"); if (!extensionLocation) { throw ( new Error("No extension gallery service configured.")); } return this.toWebExtensionFromExtensionGalleryResource( extensionLocation, galleryExtension.identifier, galleryExtension.assets.readme ? ( URI.parse(galleryExtension.assets.readme.uri)) : undefined, galleryExtension.assets.changelog ? ( URI.parse(galleryExtension.assets.changelog.uri)) : undefined, metadata ); } async toWebExtensionFromExtensionGalleryResource(extensionLocation, identifier, readmeUri, changelogUri, metadata) { const extensionResources = await this.listExtensionResources(extensionLocation); const packageNLSResources = this.getPackageNLSResourceMapFromResources(extensionResources); const fallbackPackageNLSResource = extensionResources.find(e => basename(e) === "package.nls.json"); return this.toWebExtension( extensionLocation, identifier, undefined, packageNLSResources, fallbackPackageNLSResource ? ( URI.parse(fallbackPackageNLSResource)) : null, readmeUri, changelogUri, metadata ); } getPackageNLSResourceMapFromResources(extensionResources) { const packageNLSResources = ( new Map()); extensionResources.forEach(e => { const regexResult = /package\.nls\.([\w-]+)\.json/.exec(basename(e)); if (regexResult?.[1]) { packageNLSResources.set(regexResult[1], ( URI.parse(e))); } }); return packageNLSResources; } async toWebExtension( extensionLocation, identifier, manifest, packageNLSUris, fallbackPackageNLSUri, readmeUri, changelogUri, metadata ) { if (!manifest) { try { manifest = await this.getExtensionManifest(extensionLocation); } catch (error) { throw ( new Error(`Error while fetching manifest from the location '${( extensionLocation.toString())}'. ${getErrorMessage(error)}`)); } } if (!this.extensionManifestPropertiesService.canExecuteOnWeb(manifest)) { throw ( new Error(( localize( 16565, "Cannot add '{0}' because this extension is not a web extension.", manifest.displayName || manifest.name )))); } if (fallbackPackageNLSUri === undefined) { try { fallbackPackageNLSUri = joinPath(extensionLocation, "package.nls.json"); await this.extensionResourceLoaderService.readExtensionResource(fallbackPackageNLSUri); } catch (error) { fallbackPackageNLSUri = undefined; } } const defaultManifestTranslations = fallbackPackageNLSUri ? URI.isUri(fallbackPackageNLSUri) ? await this.getTranslations(fallbackPackageNLSUri) : fallbackPackageNLSUri : null; return { identifier: { id: getGalleryExtensionId(manifest.publisher, manifest.name), uuid: identifier?.uuid }, version: manifest.version, location: extensionLocation, manifest, readmeUri, changelogUri, packageNLSUris, fallbackPackageNLSUri: URI.isUri(fallbackPackageNLSUri) ? fallbackPackageNLSUri : undefined, defaultManifestTranslations, metadata }; } async toScannedExtension(webExtension, isBuiltin, type = ExtensionType.User) { const validations = []; let manifest = webExtension.manifest; if (!manifest) { try { manifest = await this.getExtensionManifest(webExtension.location); } catch (error) { validations.push([ Severity.Error, `Error while fetching manifest from the location '${webExtension.location}'. ${getErrorMessage(error)}` ]); } } if (!manifest) { const [publisher, name] = webExtension.identifier.id.split("."); manifest = { name, publisher, version: webExtension.version, engines: { vscode: "*" } }; } const packageNLSUri = webExtension.packageNLSUris?.get(Language.value().toLowerCase()); const fallbackPackageNLS = webExtension.defaultManifestTranslations ?? webExtension.fallbackPackageNLSUri; if (packageNLSUri) { manifest = await this.translateManifest(manifest, packageNLSUri, fallbackPackageNLS); } else if (fallbackPackageNLS) { manifest = await this.translateManifest(manifest, fallbackPackageNLS); } const uuid = webExtension.metadata?.id; const validateApiVersion = this.extensionsEnabledWithApiProposalVersion.includes(webExtension.identifier.id.toLowerCase()); validations.push(...validateExtensionManifest( this.productService.version, this.productService.date, webExtension.location, manifest, false, validateApiVersion )); let isValid = true; for (const [severity, message] of validations) { if (severity === Severity.Error) { isValid = false; this.logService.error(message); } } if (manifest.enabledApiProposals && validateApiVersion) { manifest.enabledApiProposals = parseEnabledApiProposalNames([...manifest.enabledApiProposals]); } return { identifier: { id: webExtension.identifier.id, uuid: webExtension.identifier.uuid || uuid }, location: webExtension.location, manifest, type, isBuiltin, readmeUrl: webExtension.readmeUri, changelogUrl: webExtension.changelogUri, metadata: webExtension.metadata, targetPlatform: TargetPlatform.WEB, validations, isValid, preRelease: !!webExtension.metadata?.preRelease }; } async listExtensionResources(extensionLocation) { try { const result = await this.extensionResourceLoaderService.readExtensionResource(extensionLocation); return JSON.parse(result); } catch (error) { this.logService.warn("Error while fetching extension resources list", getErrorMessage(error)); } return []; } async translateManifest(manifest, nlsURL, fallbackNLS) { try { const translations = URI.isUri(nlsURL) ? await this.getTranslations(nlsURL) : nlsURL; const fallbackTranslations = URI.isUri(fallbackNLS) ? await this.getTranslations(fallbackNLS) : fallbackNLS; if (translations) { manifest = localizeManifest(this.logService, manifest, translations, fallbackTranslations); } } catch (error) {} return manifest; } async getExtensionManifest(location) { const url = joinPath(location, "package.json"); const content = await this.extensionResourceLoaderService.readExtensionResource(url); return JSON.parse(content); } async getTranslations(nlsUrl) { try { const content = await this.extensionResourceLoaderService.readExtensionResource(nlsUrl); return JSON.parse(content); } catch (error) { this.logService.error(`Error while fetching translations of an extension`, ( nlsUrl.toString()), getErrorMessage(error)); } return undefined; } async readInstalledExtensions(profileLocation) { return this.withWebExtensions(profileLocation); } writeInstalledExtensions(profileLocation, updateFn) { return this.withWebExtensions(profileLocation, updateFn); } readCustomBuiltinExtensionsCache() { return this.withWebExtensions(this.customBuiltinExtensionsCacheResource); } writeCustomBuiltinExtensionsCache(updateFn) { return this.withWebExtensions(this.customBuiltinExtensionsCacheResource, updateFn); } readSystemExtensionsCache() { return this.withWebExtensions(this.systemExtensionsCacheResource); } writeSystemExtensionsCache(updateFn) { return this.withWebExtensions(this.systemExtensionsCacheResource, updateFn); } async withWebExtensions(file, updateFn) { if (!file) { return []; } return this.getResourceAccessQueue(file).queue(async () => { let webExtensions = []; try { const content = await this.fileService.readFile(file); const storedWebExtensions = JSON.parse(( content.value.toString())); for (const e of storedWebExtensions) { if (!e.location || !e.identifier || !e.version) { this.logService.info("Ignoring invalid extension while scanning", storedWebExtensions); continue; } let packageNLSUris; if (e.packageNLSUris) { packageNLSUris = ( new Map()); Object.entries(e.packageNLSUris).forEach(([key, value]) => packageNLSUris.set(key, URI.revive(value))); } webExtensions.push({ identifier: e.identifier, version: e.version, location: URI.revive(e.location), manifest: e.manifest, readmeUri: URI.revive(e.readmeUri), changelogUri: URI.revive(e.changelogUri), packageNLSUris, fallbackPackageNLSUri: URI.revive(e.fallbackPackageNLSUri), defaultManifestTranslations: e.defaultManifestTranslations, packageNLSUri: URI.revive(e.packageNLSUri), metadata: e.metadata }); } try { webExtensions = await this.migrateWebExtensions(webExtensions, file); } catch (error) { this.logService.error(`Error while migrating scanned extensions in ${( file.toString())}`, getErrorMessage(error)); } } catch (error) { if (error.fileOperationResult !== FileOperationResult.FILE_NOT_FOUND) { this.logService.error(error); } } if (updateFn) { await this.storeWebExtensions(webExtensions = updateFn(webExtensions), file); } return webExtensions; }); } async migrateWebExtensions(webExtensions, file) { let update = false; webExtensions = await Promise.all(( webExtensions.map(async webExtension => { if (!webExtension.manifest) { try { webExtension.manifest = await this.getExtensionManifest(webExtension.location);