UNPKG

@sussudio/platform

Version:

Internal APIs for VS Code's service injection the base services.

1,230 lines (1,229 loc) 42.2 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? (desc = Object.getOwnPropertyDescriptor(target, key)) : desc, d; if (typeof Reflect === 'object' && typeof Reflect.decorate === 'function') r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if ((d = decorators[i])) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __param = (this && this.__param) || function (paramIndex, decorator) { return function (target, key) { decorator(target, key, paramIndex); }; }; import { coalesce } from '@sussudio/base/common/arrays.mjs'; import { ThrottledDelayer } from '@sussudio/base/common/async.mjs'; import * as objects from '@sussudio/base/common/objects.mjs'; import { VSBuffer } from '@sussudio/base/common/buffer.mjs'; import { getErrorMessage } from '@sussudio/base/common/errors.mjs'; import { getNodeType, parse } from '@sussudio/base/common/json.mjs'; import { getParseErrorMessage } from '@sussudio/base/common/jsonErrorMessages.mjs'; import { Disposable } from '@sussudio/base/common/lifecycle.mjs'; import { FileAccess, Schemas } from '@sussudio/base/common/network.mjs'; import * as path from '@sussudio/base/common/path.mjs'; import * as platform from '@sussudio/base/common/platform.mjs'; import { basename, isEqual, joinPath } from '@sussudio/base/common/resources.mjs'; import * as semver from '@sussudio/base/common/semver/semver.mjs'; import Severity from '@sussudio/base/common/severity.mjs'; import { isEmptyObject } from '@sussudio/base/common/types.mjs'; import { URI } from '@sussudio/base/common/uri.mjs'; import { localize } from 'vscode-nls.mjs'; import { IEnvironmentService } from '../../environment/common/environment.mjs'; import { areSameExtensions, computeTargetPlatform, ExtensionKey, getExtensionId, getGalleryExtensionId, } from './extensionManagementUtil.mjs'; import { ExtensionIdentifier, UNDEFINED_PUBLISHER, BUILTIN_MANIFEST_CACHE_FILE, USER_MANIFEST_CACHE_FILE, MANIFEST_CACHE_FOLDER, } from '../../extensions/common/extensions.mjs'; import { validateExtensionManifest } from '../../extensions/common/extensionValidator.mjs'; import { IFileService, toFileOperationResult } from '../../files/common/files.mjs'; import { createDecorator, IInstantiationService } from '../../instantiation/common/instantiation.mjs'; import { ILogService } from '../../log/common/log.mjs'; import { IProductService } from '../../product/common/productService.mjs'; import { Emitter } from '@sussudio/base/common/event.mjs'; import { revive } from '@sussudio/base/common/marshalling.mjs'; import { ExtensionsProfileScanningError, IExtensionsProfileScannerService, } from './extensionsProfileScannerService.mjs'; import { IUserDataProfilesService } from '../../userDataProfile/common/userDataProfile.mjs'; import { IUriIdentityService } from '../../uriIdentity/common/uriIdentity.mjs'; import { localizeManifest } from './extensionNls.mjs'; import { ImplicitActivationEvents } from './implicitActivationEvents.mjs'; export var Translations; (function (Translations) { function equals(a, b) { if (a === b) { return true; } const aKeys = Object.keys(a); const bKeys = new Set(); for (const key of Object.keys(b)) { bKeys.add(key); } if (aKeys.length !== bKeys.size) { return false; } for (const key of aKeys) { if (a[key] !== b[key]) { return false; } bKeys.delete(key); } return bKeys.size === 0; } Translations.equals = equals; })(Translations || (Translations = {})); export const IExtensionsScannerService = createDecorator('IExtensionsScannerService'); let AbstractExtensionsScannerService = class AbstractExtensionsScannerService extends Disposable { systemExtensionsLocation; userExtensionsLocation; extensionsControlLocation; cacheLocation; userDataProfilesService; extensionsProfileScannerService; fileService; logService; environmentService; productService; uriIdentityService; instantiationService; _serviceBrand; _onDidChangeCache = this._register(new Emitter()); onDidChangeCache = this._onDidChangeCache.event; obsoleteFile = joinPath(this.userExtensionsLocation, '.obsolete'); systemExtensionsCachedScanner = this._register( this.instantiationService.createInstance( CachedExtensionsScanner, joinPath(this.cacheLocation, BUILTIN_MANIFEST_CACHE_FILE), this.obsoleteFile, ), ); userExtensionsCachedScanner = this._register( this.instantiationService.createInstance( CachedExtensionsScanner, joinPath(this.cacheLocation, USER_MANIFEST_CACHE_FILE), this.obsoleteFile, ), ); extensionsScanner = this._register(this.instantiationService.createInstance(ExtensionsScanner, this.obsoleteFile)); constructor( systemExtensionsLocation, userExtensionsLocation, extensionsControlLocation, cacheLocation, userDataProfilesService, extensionsProfileScannerService, fileService, logService, environmentService, productService, uriIdentityService, instantiationService, ) { super(); this.systemExtensionsLocation = systemExtensionsLocation; this.userExtensionsLocation = userExtensionsLocation; this.extensionsControlLocation = extensionsControlLocation; this.cacheLocation = cacheLocation; this.userDataProfilesService = userDataProfilesService; this.extensionsProfileScannerService = extensionsProfileScannerService; this.fileService = fileService; this.logService = logService; this.environmentService = environmentService; this.productService = productService; this.uriIdentityService = uriIdentityService; this.instantiationService = instantiationService; this._register( this.systemExtensionsCachedScanner.onDidChangeCache(() => this._onDidChangeCache.fire(0 /* ExtensionType.System */), ), ); this._register( this.userExtensionsCachedScanner.onDidChangeCache(() => this._onDidChangeCache.fire(1 /* ExtensionType.User */)), ); } _targetPlatformPromise; getTargetPlatform() { if (!this._targetPlatformPromise) { this._targetPlatformPromise = computeTargetPlatform(this.fileService, this.logService); } return this._targetPlatformPromise; } async scanAllExtensions(systemScanOptions, userScanOptions, includeExtensionsUnderDev) { const [system, user] = await Promise.all([ this.scanSystemExtensions(systemScanOptions), this.scanUserExtensions(userScanOptions), ]); const development = includeExtensionsUnderDev ? await this.scanExtensionsUnderDevelopment(systemScanOptions, [...system, ...user]) : []; return this.dedupExtensions(system, user, development, await this.getTargetPlatform(), true); } async scanSystemExtensions(scanOptions) { const promises = []; promises.push(this.scanDefaultSystemExtensions(!!scanOptions.useCache, scanOptions.language)); promises.push(this.scanDevSystemExtensions(scanOptions.language, !!scanOptions.checkControlFile)); const [defaultSystemExtensions, devSystemExtensions] = await Promise.all(promises); return this.applyScanOptions( [...defaultSystemExtensions, ...devSystemExtensions], 0 /* ExtensionType.System */, scanOptions, false, ); } async scanUserExtensions(scanOptions) { const location = scanOptions.profileLocation ?? this.userExtensionsLocation; this.logService.trace('Started scanning user extensions', location); const profileScanOptions = this.uriIdentityService.extUri.isEqual( scanOptions.profileLocation, this.userDataProfilesService.defaultProfile.extensionsResource, ) ? { bailOutWhenFileNotFound: true } : undefined; const extensionsScannerInput = await this.createExtensionScannerInput( location, !!scanOptions.profileLocation, 1 /* ExtensionType.User */, !scanOptions.includeUninstalled, scanOptions.language, true, profileScanOptions, ); const extensionsScanner = scanOptions.useCache && !extensionsScannerInput.devMode && extensionsScannerInput.excludeObsolete ? this.userExtensionsCachedScanner : this.extensionsScanner; let extensions; try { extensions = await extensionsScanner.scanExtensions(extensionsScannerInput); } catch (error) { if ( error instanceof ExtensionsProfileScanningError && error.code === 'ERROR_PROFILE_NOT_FOUND' /* ExtensionsProfileScanningErrorCode.ERROR_PROFILE_NOT_FOUND */ ) { await this.doInitializeDefaultProfileExtensions(); extensions = await extensionsScanner.scanExtensions(extensionsScannerInput); } else { throw error; } } extensions = await this.applyScanOptions(extensions, 1 /* ExtensionType.User */, scanOptions, true); this.logService.trace('Scanned user extensions:', extensions.length); return extensions; } async scanExtensionsUnderDevelopment(scanOptions, existingExtensions) { if (this.environmentService.isExtensionDevelopment && this.environmentService.extensionDevelopmentLocationURI) { const extensions = ( await Promise.all( this.environmentService.extensionDevelopmentLocationURI .filter((extLoc) => extLoc.scheme === Schemas.file) .map(async (extensionDevelopmentLocationURI) => { const input = await this.createExtensionScannerInput( extensionDevelopmentLocationURI, false, 1 /* ExtensionType.User */, true, scanOptions.language, false /* do not validate */, undefined, ); const extensions = await this.extensionsScanner.scanOneOrMultipleExtensions(input); return extensions.map((extension) => { // Override the extension type from the existing extensions extension.type = existingExtensions.find((e) => areSameExtensions(e.identifier, extension.identifier))?.type ?? extension.type; // Validate the extension return this.extensionsScanner.validate(extension, input); }); }), ) ).flat(); return this.applyScanOptions(extensions, 'development', scanOptions, true); } return []; } async scanExistingExtension(extensionLocation, extensionType, scanOptions) { const extensionsScannerInput = await this.createExtensionScannerInput( extensionLocation, false, extensionType, true, scanOptions.language, true, undefined, ); const extension = await this.extensionsScanner.scanExtension(extensionsScannerInput); if (!extension) { return null; } if (!scanOptions.includeInvalid && !extension.isValid) { return null; } return extension; } async scanOneOrMultipleExtensions(extensionLocation, extensionType, scanOptions) { const extensionsScannerInput = await this.createExtensionScannerInput( extensionLocation, false, extensionType, true, scanOptions.language, true, undefined, ); const extensions = await this.extensionsScanner.scanOneOrMultipleExtensions(extensionsScannerInput); return this.applyScanOptions(extensions, extensionType, scanOptions, true); } async scanMetadata(extensionLocation) { const manifestLocation = joinPath(extensionLocation, 'package.json'); const content = (await this.fileService.readFile(manifestLocation)).value.toString(); const manifest = JSON.parse(content); return manifest.__metadata; } async updateMetadata(extensionLocation, metaData) { const manifestLocation = joinPath(extensionLocation, 'package.json'); const content = (await this.fileService.readFile(manifestLocation)).value.toString(); const manifest = JSON.parse(content); // unset if false metaData.isMachineScoped = metaData.isMachineScoped || undefined; metaData.isBuiltin = metaData.isBuiltin || undefined; metaData.installedTimestamp = metaData.installedTimestamp || undefined; manifest.__metadata = { ...manifest.__metadata, ...metaData }; await this.fileService.writeFile( joinPath(extensionLocation, 'package.json'), VSBuffer.fromString(JSON.stringify(manifest, null, '\t')), ); } async initializeDefaultProfileExtensions() { try { await this.extensionsProfileScannerService.scanProfileExtensions( this.userDataProfilesService.defaultProfile.extensionsResource, { bailOutWhenFileNotFound: true }, ); } catch (error) { if ( error instanceof ExtensionsProfileScanningError && error.code === 'ERROR_PROFILE_NOT_FOUND' /* ExtensionsProfileScanningErrorCode.ERROR_PROFILE_NOT_FOUND */ ) { await this.doInitializeDefaultProfileExtensions(); } else { throw error; } } } initializeDefaultProfileExtensionsPromise = undefined; async doInitializeDefaultProfileExtensions() { if (!this.initializeDefaultProfileExtensionsPromise) { this.initializeDefaultProfileExtensionsPromise = (async () => { try { this.logService.info( 'Started initializing default profile extensions in extensions installation folder.', this.userExtensionsLocation.toString(), ); const userExtensions = await this.scanUserExtensions({ includeInvalid: true }); if (userExtensions.length) { await this.extensionsProfileScannerService.addExtensionsToProfile( userExtensions.map((e) => [e, e.metadata]), this.userDataProfilesService.defaultProfile.extensionsResource, ); } else { try { await this.fileService.createFile( this.userDataProfilesService.defaultProfile.extensionsResource, VSBuffer.fromString(JSON.stringify([])), ); } catch (error) { if (toFileOperationResult(error) !== 1 /* FileOperationResult.FILE_NOT_FOUND */) { this.logService.warn( 'Failed to create default profile extensions manifest in extensions installation folder.', this.userExtensionsLocation.toString(), getErrorMessage(error), ); } } } this.logService.info( 'Completed initializing default profile extensions in extensions installation folder.', this.userExtensionsLocation.toString(), ); } catch (error) { this.logService.error(error); } finally { this.initializeDefaultProfileExtensionsPromise = undefined; } })(); } return this.initializeDefaultProfileExtensionsPromise; } async applyScanOptions(extensions, type, scanOptions, pickLatest) { if (!scanOptions.includeAllVersions) { extensions = this.dedupExtensions( type === 0 /* ExtensionType.System */ ? extensions : undefined, type === 1 /* ExtensionType.User */ ? extensions : undefined, type === 'development' ? extensions : undefined, await this.getTargetPlatform(), pickLatest, ); } if (!scanOptions.includeInvalid) { extensions = extensions.filter((extension) => extension.isValid); } return extensions.sort((a, b) => { const aLastSegment = path.basename(a.location.fsPath); const bLastSegment = path.basename(b.location.fsPath); if (aLastSegment < bLastSegment) { return -1; } if (aLastSegment > bLastSegment) { return 1; } return 0; }); } dedupExtensions(system, user, development, targetPlatform, pickLatest) { const pick = (existing, extension, isDevelopment) => { if (existing.isValid && !extension.isValid) { return false; } if (existing.isValid === extension.isValid) { if (pickLatest && semver.gt(existing.manifest.version, extension.manifest.version)) { this.logService.debug( `Skipping extension ${extension.location.path} with lower version ${extension.manifest.version} in favour of ${existing.location.path} with version ${existing.manifest.version}`, ); return false; } if (semver.eq(existing.manifest.version, extension.manifest.version)) { if (existing.type === 0 /* ExtensionType.System */) { this.logService.debug( `Skipping extension ${extension.location.path} in favour of system extension ${existing.location.path} with same version`, ); return false; } if (existing.targetPlatform === targetPlatform) { this.logService.debug( `Skipping extension ${extension.location.path} from different target platform ${extension.targetPlatform}`, ); return false; } } } if (isDevelopment) { this.logService.warn(`Overwriting user extension ${existing.location.path} with ${extension.location.path}.`); } else { this.logService.debug(`Overwriting user extension ${existing.location.path} with ${extension.location.path}.`); } return true; }; const result = new Map(); system?.forEach((extension) => { const extensionKey = ExtensionIdentifier.toKey(extension.identifier.id); const existing = result.get(extensionKey); if (!existing || pick(existing, extension, false)) { result.set(extensionKey, extension); } }); user?.forEach((extension) => { const extensionKey = ExtensionIdentifier.toKey(extension.identifier.id); const existing = result.get(extensionKey); if (!existing && system && extension.type === 0 /* ExtensionType.System */) { this.logService.debug(`Skipping obsolete system extension ${extension.location.path}.`); return; } if (!existing || pick(existing, extension, false)) { result.set(extensionKey, extension); } }); development?.forEach((extension) => { const extensionKey = ExtensionIdentifier.toKey(extension.identifier.id); const existing = result.get(extensionKey); if (!existing || pick(existing, extension, true)) { result.set(extensionKey, extension); } result.set(extensionKey, extension); }); return [...result.values()]; } async scanDefaultSystemExtensions(useCache, language) { this.logService.trace('Started scanning system extensions'); const extensionsScannerInput = await this.createExtensionScannerInput( this.systemExtensionsLocation, false, 0 /* ExtensionType.System */, true, language, true, undefined, ); const extensionsScanner = useCache && !extensionsScannerInput.devMode ? this.systemExtensionsCachedScanner : this.extensionsScanner; const result = await extensionsScanner.scanExtensions(extensionsScannerInput); this.logService.trace('Scanned system extensions:', result.length); return result; } async scanDevSystemExtensions(language, checkControlFile) { const devSystemExtensionsList = this.environmentService.isBuilt ? [] : this.productService.builtInExtensions; if (!devSystemExtensionsList?.length) { return []; } this.logService.trace('Started scanning dev system extensions'); const builtinExtensionControl = checkControlFile ? await this.getBuiltInExtensionControl() : {}; const devSystemExtensionsLocations = []; const devSystemExtensionsLocation = URI.file( path.normalize(path.join(FileAccess.asFileUri('').fsPath, '..', '.build', 'builtInExtensions')), ); for (const extension of devSystemExtensionsList) { const controlState = builtinExtensionControl[extension.name] || 'marketplace'; switch (controlState) { case 'disabled': break; case 'marketplace': devSystemExtensionsLocations.push(joinPath(devSystemExtensionsLocation, extension.name)); break; default: devSystemExtensionsLocations.push(URI.file(controlState)); break; } } const result = await Promise.all( devSystemExtensionsLocations.map(async (location) => this.extensionsScanner.scanExtension( await this.createExtensionScannerInput( location, false, 0 /* ExtensionType.System */, true, language, true, undefined, ), ), ), ); this.logService.trace('Scanned dev system extensions:', result.length); return coalesce(result); } async getBuiltInExtensionControl() { try { const content = await this.fileService.readFile(this.extensionsControlLocation); return JSON.parse(content.value.toString()); } catch (error) { return {}; } } async createExtensionScannerInput(location, profile, type, excludeObsolete, language, validate, profileScanOptions) { const translations = await this.getTranslations(language ?? platform.language); const mtime = await this.getMtime(location); const applicationExtensionsLocation = profile && !this.uriIdentityService.extUri.isEqual(location, this.userDataProfilesService.defaultProfile.extensionsResource) ? this.userDataProfilesService.defaultProfile.extensionsResource : undefined; const applicationExtensionsLocationMtime = applicationExtensionsLocation ? await this.getMtime(applicationExtensionsLocation) : undefined; return new ExtensionScannerInput( location, mtime, applicationExtensionsLocation, applicationExtensionsLocationMtime, profile, profileScanOptions, type, excludeObsolete, validate, this.productService.version, this.productService.date, this.productService.commit, !this.environmentService.isBuilt, language, translations, ); } async getMtime(location) { try { const stat = await this.fileService.stat(location); if (typeof stat.mtime === 'number') { return stat.mtime; } } catch (err) { // That's ok... } return undefined; } }; AbstractExtensionsScannerService = __decorate( [ __param(4, IUserDataProfilesService), __param(5, IExtensionsProfileScannerService), __param(6, IFileService), __param(7, ILogService), __param(8, IEnvironmentService), __param(9, IProductService), __param(10, IUriIdentityService), __param(11, IInstantiationService), ], AbstractExtensionsScannerService, ); export { AbstractExtensionsScannerService }; class ExtensionScannerInput { location; mtime; applicationExtensionslocation; applicationExtensionslocationMtime; profile; profileScanOptions; type; excludeObsolete; validate; productVersion; productDate; productCommit; devMode; language; translations; constructor( location, mtime, applicationExtensionslocation, applicationExtensionslocationMtime, profile, profileScanOptions, type, excludeObsolete, validate, productVersion, productDate, productCommit, devMode, language, translations, ) { this.location = location; this.mtime = mtime; this.applicationExtensionslocation = applicationExtensionslocation; this.applicationExtensionslocationMtime = applicationExtensionslocationMtime; this.profile = profile; this.profileScanOptions = profileScanOptions; this.type = type; this.excludeObsolete = excludeObsolete; this.validate = validate; this.productVersion = productVersion; this.productDate = productDate; this.productCommit = productCommit; this.devMode = devMode; this.language = language; this.translations = translations; // Keep empty!! (JSON.parse) } static createNlsConfiguration(input) { return { language: input.language, pseudo: input.language === 'pseudo', devMode: input.devMode, translations: input.translations, }; } static equals(a, b) { return ( isEqual(a.location, b.location) && a.mtime === b.mtime && isEqual(a.applicationExtensionslocation, b.applicationExtensionslocation) && a.applicationExtensionslocationMtime === b.applicationExtensionslocationMtime && a.profile === b.profile && a.profileScanOptions === b.profileScanOptions && a.type === b.type && a.excludeObsolete === b.excludeObsolete && a.validate === b.validate && a.productVersion === b.productVersion && a.productDate === b.productDate && a.productCommit === b.productCommit && a.devMode === b.devMode && a.language === b.language && Translations.equals(a.translations, b.translations) ); } } let ExtensionsScanner = class ExtensionsScanner extends Disposable { obsoleteFile; extensionsProfileScannerService; uriIdentityService; fileService; logService; constructor(obsoleteFile, extensionsProfileScannerService, uriIdentityService, fileService, logService) { super(); this.obsoleteFile = obsoleteFile; this.extensionsProfileScannerService = extensionsProfileScannerService; this.uriIdentityService = uriIdentityService; this.fileService = fileService; this.logService = logService; } async scanExtensions(input) { const extensions = input.profile ? await this.scanExtensionsFromProfile(input) : await this.scanExtensionsFromLocation(input); let obsolete = {}; if (input.excludeObsolete && input.type === 1 /* ExtensionType.User */) { try { const raw = (await this.fileService.readFile(this.obsoleteFile)).value.toString(); obsolete = JSON.parse(raw); } catch (error) { /* ignore */ } } return isEmptyObject(obsolete) ? extensions : extensions.filter((e) => !obsolete[ExtensionKey.create(e).toString()]); } async scanExtensionsFromLocation(input) { const stat = await this.fileService.resolve(input.location); if (!stat.children?.length) { return []; } const extensions = await Promise.all( stat.children.map(async (c) => { if (!c.isDirectory) { return null; } // Do not consider user extension folder starting with `.` if (input.type === 1 /* ExtensionType.User */ && basename(c.resource).indexOf('.') === 0) { return null; } const extensionScannerInput = new ExtensionScannerInput( c.resource, input.mtime, input.applicationExtensionslocation, input.applicationExtensionslocationMtime, input.profile, input.profileScanOptions, input.type, input.excludeObsolete, input.validate, input.productVersion, input.productDate, input.productCommit, input.devMode, input.language, input.translations, ); return this.scanExtension(extensionScannerInput); }), ); return ( coalesce(extensions) // Sort: Make sure extensions are in the same order always. Helps cache invalidation even if the order changes. .sort((a, b) => (a.location.path < b.location.path ? -1 : 1)) ); } async scanExtensionsFromProfile(input) { let profileExtensions = await this.scanExtensionsFromProfileResource(input.location, () => true, input); if ( input.applicationExtensionslocation && !this.uriIdentityService.extUri.isEqual(input.location, input.applicationExtensionslocation) ) { profileExtensions = profileExtensions.filter((e) => !e.metadata?.isApplicationScoped); const applicationExtensions = await this.scanExtensionsFromProfileResource( input.applicationExtensionslocation, (e) => !!e.metadata?.isApplicationScoped, input, ); profileExtensions.push(...applicationExtensions); } return profileExtensions; } async scanExtensionsFromProfileResource(profileResource, filter, input) { const scannedProfileExtensions = await this.extensionsProfileScannerService.scanProfileExtensions( profileResource, input.profileScanOptions, ); if (!scannedProfileExtensions.length) { return []; } const extensions = await Promise.all( scannedProfileExtensions.map(async (extensionInfo) => { if (filter(extensionInfo)) { const extensionScannerInput = new ExtensionScannerInput( extensionInfo.location, input.mtime, input.applicationExtensionslocation, input.applicationExtensionslocationMtime, input.profile, input.profileScanOptions, input.type, input.excludeObsolete, input.validate, input.productVersion, input.productDate, input.productCommit, input.devMode, input.language, input.translations, ); return this.scanExtension(extensionScannerInput, extensionInfo.metadata); } return null; }), ); return coalesce(extensions); } async scanOneOrMultipleExtensions(input) { try { if (await this.fileService.exists(joinPath(input.location, 'package.json'))) { const extension = await this.scanExtension(input); return extension ? [extension] : []; } else { return await this.scanExtensions(input); } } catch (error) { this.logService.error(`Error scanning extensions at ${input.location.path}:`, getErrorMessage(error)); return []; } } async scanExtension(input, metadata) { try { let manifest = await this.scanExtensionManifest(input.location); if (manifest) { // allow publisher to be undefined to make the initial extension authoring experience smoother if (!manifest.publisher) { manifest.publisher = UNDEFINED_PUBLISHER; } metadata = metadata ?? manifest.__metadata; delete manifest.__metadata; const id = getGalleryExtensionId(manifest.publisher, manifest.name); const identifier = metadata?.id ? { id, uuid: metadata.id } : { id }; const type = metadata?.isSystem ? 0 /* ExtensionType.System */ : input.type; const isBuiltin = type === 0 /* ExtensionType.System */ || !!metadata?.isBuiltin; ImplicitActivationEvents.updateManifest(manifest); manifest = await this.translateManifest( input.location, manifest, ExtensionScannerInput.createNlsConfiguration(input), ); const extension = { type, identifier, manifest, location: input.location, isBuiltin, targetPlatform: metadata?.targetPlatform ?? 'undefined' /* TargetPlatform.UNDEFINED */, metadata, isValid: true, validations: [], }; return input.validate ? this.validate(extension, input) : extension; } } catch (e) { if (input.type !== 0 /* ExtensionType.System */) { this.logService.error(e); } } return null; } validate(extension, input) { let isValid = true; const validations = validateExtensionManifest( input.productVersion, input.productDate, input.location, extension.manifest, extension.isBuiltin, ); for (const [severity, message] of validations) { if (severity === Severity.Error) { isValid = false; this.logService.error(this.formatMessage(input.location, message)); } } extension.isValid = isValid; extension.validations = validations; return extension; } async scanExtensionManifest(extensionLocation) { const manifestLocation = joinPath(extensionLocation, 'package.json'); let content; try { content = (await this.fileService.readFile(manifestLocation)).value.toString(); } catch (error) { if (toFileOperationResult(error) !== 1 /* FileOperationResult.FILE_NOT_FOUND */) { this.logService.error( this.formatMessage( extensionLocation, localize('fileReadFail', 'Cannot read file {0}: {1}.', manifestLocation.path, error.message), ), ); } return null; } let manifest; try { manifest = JSON.parse(content); } catch (err) { // invalid JSON, let's get good errors const errors = []; parse(content, errors); for (const e of errors) { this.logService.error( this.formatMessage( extensionLocation, localize( 'jsonParseFail', 'Failed to parse {0}: [{1}, {2}] {3}.', manifestLocation.path, e.offset, e.length, getParseErrorMessage(e.error), ), ), ); } return null; } if (getNodeType(manifest) !== 'object') { this.logService.error( this.formatMessage( extensionLocation, localize('jsonParseInvalidType', 'Invalid manifest file {0}: Not an JSON object.', manifestLocation.path), ), ); return null; } return manifest; } async translateManifest(extensionLocation, extensionManifest, nlsConfiguration) { const localizedMessages = await this.getLocalizedMessages(extensionLocation, extensionManifest, nlsConfiguration); if (localizedMessages) { try { const errors = []; // resolveOriginalMessageBundle returns null if localizedMessages.default === undefined; const defaults = await this.resolveOriginalMessageBundle(localizedMessages.default, errors); if (errors.length > 0) { errors.forEach((error) => { this.logService.error( this.formatMessage( extensionLocation, localize( 'jsonsParseReportErrors', 'Failed to parse {0}: {1}.', localizedMessages.default?.path, getParseErrorMessage(error.error), ), ), ); }); return extensionManifest; } else if (getNodeType(localizedMessages) !== 'object') { this.logService.error( this.formatMessage( extensionLocation, localize( 'jsonInvalidFormat', 'Invalid format {0}: JSON object expected.', localizedMessages.default?.path, ), ), ); return extensionManifest; } const localized = localizedMessages.values || Object.create(null); return localizeManifest(extensionManifest, localized, defaults); } catch (error) { /*Ignore Error*/ } } return extensionManifest; } async getLocalizedMessages(extensionLocation, extensionManifest, nlsConfiguration) { const defaultPackageNLS = joinPath(extensionLocation, 'package.nls.json'); const reportErrors = (localized, errors) => { errors.forEach((error) => { this.logService.error( this.formatMessage( extensionLocation, localize( 'jsonsParseReportErrors', 'Failed to parse {0}: {1}.', localized?.path, getParseErrorMessage(error.error), ), ), ); }); }; const reportInvalidFormat = (localized) => { this.logService.error( this.formatMessage( extensionLocation, localize('jsonInvalidFormat', 'Invalid format {0}: JSON object expected.', localized?.path), ), ); }; const translationId = `${extensionManifest.publisher}.${extensionManifest.name}`; const translationPath = nlsConfiguration.translations[translationId]; if (translationPath) { try { const translationResource = URI.file(translationPath); const content = (await this.fileService.readFile(translationResource)).value.toString(); const errors = []; const translationBundle = parse(content, errors); if (errors.length > 0) { reportErrors(translationResource, errors); return { values: undefined, default: defaultPackageNLS }; } else if (getNodeType(translationBundle) !== 'object') { reportInvalidFormat(translationResource); return { values: undefined, default: defaultPackageNLS }; } else { const values = translationBundle.contents ? translationBundle.contents.package : undefined; return { values: values, default: defaultPackageNLS }; } } catch (error) { return { values: undefined, default: defaultPackageNLS }; } } else { const exists = await this.fileService.exists(defaultPackageNLS); if (!exists) { return undefined; } let messageBundle; try { messageBundle = await this.findMessageBundles(extensionLocation, nlsConfiguration); } catch (error) { return undefined; } if (!messageBundle.localized) { return { values: undefined, default: messageBundle.original }; } try { const messageBundleContent = (await this.fileService.readFile(messageBundle.localized)).value.toString(); const errors = []; const messages = parse(messageBundleContent, errors); if (errors.length > 0) { reportErrors(messageBundle.localized, errors); return { values: undefined, default: messageBundle.original }; } else if (getNodeType(messages) !== 'object') { reportInvalidFormat(messageBundle.localized); return { values: undefined, default: messageBundle.original }; } return { values: messages, default: messageBundle.original }; } catch (error) { return { values: undefined, default: messageBundle.original }; } } } /** * Parses original message bundle, returns null if the original message bundle is null. */ async resolveOriginalMessageBundle(originalMessageBundle, errors) { if (originalMessageBundle) { try { const originalBundleContent = (await this.fileService.readFile(originalMessageBundle)).value.toString(); return parse(originalBundleContent, errors); } catch (error) { /* Ignore Error */ } } return; } /** * Finds localized message bundle and the original (unlocalized) one. * If the localized file is not present, returns null for the original and marks original as localized. */ findMessageBundles(extensionLocation, nlsConfiguration) { return new Promise((c, e) => { const loop = (locale) => { const toCheck = joinPath(extensionLocation, `package.nls.${locale}.json`); this.fileService.exists(toCheck).then((exists) => { if (exists) { c({ localized: toCheck, original: joinPath(extensionLocation, 'package.nls.json') }); } const index = locale.lastIndexOf('-'); if (index === -1) { c({ localized: joinPath(extensionLocation, 'package.nls.json'), original: null }); } else { locale = locale.substring(0, index); loop(locale); } }); }; if (nlsConfiguration.devMode || nlsConfiguration.pseudo || !nlsConfiguration.language) { return c({ localized: joinPath(extensionLocation, 'package.nls.json'), original: null }); } loop(nlsConfiguration.language); }); } formatMessage(extensionLocation, message) { return `[${extensionLocation.path}]: ${message}`; } }; ExtensionsScanner = __decorate( [ __param(1, IExtensionsProfileScannerService), __param(2, IUriIdentityService), __param(3, IFileService), __param(4, ILogService), ], ExtensionsScanner, ); let CachedExtensionsScanner = class CachedExtensionsScanner extends ExtensionsScanner { cacheFile; input; cacheValidatorThrottler = this._register(new ThrottledDelayer(3000)); _onDidChangeCache = this._register(new Emitter()); onDidChangeCache = this._onDidChangeCache.event; constructor(cacheFile, obsoleteFile, extensionsProfileScannerService, uriIdentityService, fileService, logService) { super(obsoleteFile, extensionsProfileScannerService, uriIdentityService, fileService, logService); this.cacheFile = cacheFile; } async scanExtensions(input) { const cacheContents = await this.readExtensionCache(); this.input = input; if (cacheContents && cacheContents.input && ExtensionScannerInput.equals(cacheContents.input, this.input)) { this.cacheValidatorThrottler.trigger(() => this.validateCache()); return cacheContents.result.map((extension) => { // revive URI object extension.location = URI.revive(extension.location); return extension; }); } const result = await super.scanExtensions(input); await this.writeExtensionCache({ input, result }); return result; } async readExtensionCache() { try { const cacheRawContents = await this.fileService.readFile(this.cacheFile); const extensionCacheData = JSON.parse(cacheRawContents.value.toString()); return { result: extensionCacheData.result, input: revive(extensionCacheData.input) }; } catch (error) { this.logService.debug( 'Error while reading the extension cache file:', this.cacheFile.path, getErrorMessage(error), ); } return null; } async writeExtensionCache(cacheContents) { try { await this.fileService.writeFile(this.cacheFile, VSBuffer.fromString(JSON.stringify(cacheContents))); } catch (error) { this.logService.debug( 'Error while writing the extension cache file:', this.cacheFile.path, getErrorMessage(error), ); } } async validateCache() { if (!this.input) { // Input has been unset by the time we get here, so skip validation return; } const cacheContents = await this.readExtensionCache(); if (!cacheContents) { // Cache has been deleted by someone else, which is perfectly fine... return; } const actual = cacheContents.result; const expected = JSON.parse(JSON.stringify(await super.scanExtensions(this.input))); if (objects.equals(expected, actual)) { // Cache is valid and running with it is perfectly fine... return; } try { // Cache is invalid, delete it await this.fileService.del(this.cacheFile); this._onDidChangeCache.fire(); } catch (error) { this.logService.error(error); } } }; CachedExtensionsScanner = __decorate( [ __param(2, IExtensionsProfileScannerService), __param(3, IUriIdentityService), __param(4, IFileService), __param(5, ILogService), ], CachedExtensionsScanner, ); export function toExtensionDescription(extension, isUnderDevelopment) { const id = getExtensionId(extension.manifest.publisher, extension.manifest.name); return { id, identifier: new ExtensionIdentifier(id), isBuiltin: extension.type === 0 /* ExtensionType.System */, isUserBuiltin: extension.type === 1 /* ExtensionType.User */ && extension.isBuiltin, isUnderDevelopment, extensionLocation: extension.location, uuid: extension.identifier.uuid, targetPlatform: extension.targetPlatform, ...extension.manifest, }; } export class NativeExtensionsScannerService extends AbstractExtensionsScannerService { translationsPromise; constructor( systemExtensionsLocation, userExtensionsLocation, userHome, userDataPath, userDataProfilesService, extensionsProfileScannerService, fileService, logService, environmentService, productService, uriIdentityService, instantiationService, ) { super( systemExtensionsLocation, userExtensionsLocation, joinPath(userHome, '.vscode-oss-dev', 'extensions', 'control.json'), joinPath(userDataPath, MANIFEST_CACHE_FOLDER), userDataProfilesService, extensionsProfileScannerService, fileService, logService, environmentService, productService, uriIdentityService, instantiationService, ); this.translationsPromise = (async () => { if (platform.translationsConfigFile) { try { const content = await this.fileService.readFile(URI.file(platform.translationsConfigFile)); return JSON.parse(content.value.toString()); } catch (err) { /* Ignore Error */ } } return Object.create(null); })(); } getTranslations(language) { return this.translationsPromise; } }