diff --git a/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts b/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts index abf7fbbcd96be..a3b06c8f6ade5 100644 --- a/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts +++ b/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts @@ -58,6 +58,7 @@ interface IStoredWebExtension { readonly identifier: IExtensionIdentifier; readonly version: string; readonly location: UriComponents; + readonly manifest?: string; readonly readmeUri?: UriComponents; readonly changelogUri?: UriComponents; // deprecated in favor of packageNLSUris & fallbackPackageNLSUri @@ -71,6 +72,7 @@ interface IWebExtension { identifier: IExtensionIdentifier; version: string; location: URI; + manifest?: IExtensionManifest; readmeUri?: URI; changelogUri?: URI; // deprecated in favor of packageNLSUris & fallbackPackageNLSUri @@ -425,16 +427,12 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten } async scanExtensionManifest(extensionLocation: URI): Promise { - const packageJSONUri = joinPath(extensionLocation, 'package.json'); try { - const content = await this.extensionResourceLoaderService.readExtensionResource(packageJSONUri); - if (content) { - return JSON.parse(content); - } + return await this.getExtensionManifest(extensionLocation); } catch (error) { - this.logService.warn(`Error while fetching package.json from ${packageJSONUri.toString()}`, getErrorMessage(error)); + this.logService.warn(`Error while fetching manifest from ${extensionLocation.toString()}`, getErrorMessage(error)); + return null; } - return null; } async addExtensionFromGallery(galleryExtension: IGalleryExtension, metadata: Metadata, profileLocation: URI): Promise { @@ -593,18 +591,13 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten } private async toWebExtension(extensionLocation: URI, identifier?: IExtensionIdentifier, packageNLSUris?: Map, bundleNLSUris?: Map, fallbackPackageNLSUri?: URI | null, readmeUri?: URI, changelogUri?: URI, metadata?: Metadata): Promise { - let packageJSONContent; + let manifest: IExtensionManifest; try { - packageJSONContent = await this.extensionResourceLoaderService.readExtensionResource(joinPath(extensionLocation, 'package.json')); + manifest = await this.getExtensionManifest(extensionLocation); } catch (error) { - throw new Error(`Cannot find the package.json from the location '${extensionLocation.toString()}'. ${getErrorMessage(error)}`); + throw new Error(`Error while fetching manifest from the location '${extensionLocation.toString()}'. ${getErrorMessage(error)}`); } - if (!packageJSONContent) { - throw new Error(`Error while fetching package.json for extension '${extensionLocation.toString()}'. Server returned no content`); - } - - const manifest = JSON.parse(packageJSONContent); if (!this.extensionManifestPropertiesService.canExecuteOnWeb(manifest)) { throw new Error(localize('not a web extension', "Cannot add '{0}' because this extension is not a web extension.", manifest.displayName || manifest.name)); } @@ -618,7 +611,7 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten } } - if (bundleNLSUris === undefined) { + if (bundleNLSUris === undefined && manifest.browser) { const englishStringsUri = joinPath( this.uriIdentityService.extUri.dirname(joinPath(extensionLocation, manifest.browser)), 'nls.metadata.json' @@ -637,6 +630,7 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten identifier: { id: getGalleryExtensionId(manifest.publisher, manifest.name), uuid: identifier?.uuid }, version: manifest.version, location: extensionLocation, + manifest, readmeUri, changelogUri, packageNLSUris, @@ -647,25 +641,14 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten } private async toScannedExtension(webExtension: IWebExtension, isBuiltin: boolean, type: ExtensionType = ExtensionType.User): Promise { - const url = joinPath(webExtension.location, 'package.json'); - const validations: [Severity, string][] = []; - let content: string | undefined; - try { - content = await this.extensionResourceLoaderService.readExtensionResource(url); - if (!content) { - validations.push([Severity.Error, `Error while fetching package.json from the location '${url}'. Server returned no content`]); - } - } catch (error) { - validations.push([Severity.Error, `Error while fetching package.json from the location '${url}'. ${getErrorMessage(error)}`]); - } + let manifest: IExtensionManifest | undefined = webExtension.manifest; - let manifest: IExtensionManifest | null = null; - if (content) { + if (!manifest) { try { - manifest = JSON.parse(content); + manifest = await this.getExtensionManifest(webExtension.location); } catch (error) { - validations.push([Severity.Error, `Error while parsing package.json. ${getErrorMessage(error)}`]); + validations.push([Severity.Error, `Error while fetching manifest from the location '${webExtension.location}'. ${getErrorMessage(error)}`]); } } @@ -764,6 +747,12 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten return this._migratePackageNLSUrisPromise; } + private async getExtensionManifest(location: URI): Promise { + const url = joinPath(location, 'package.json'); + const content = await this.extensionResourceLoaderService.readExtensionResource(url); + return JSON.parse(content); + } + private async readInstalledExtensions(profileLocation: URI): Promise { if (this.uriIdentityService.extUri.isEqual(profileLocation, this.userDataProfilesService.defaultProfile.extensionsResource)) { await this.migratePackageNLSUris(); @@ -817,6 +806,7 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten identifier: e.identifier, version: e.version, location: URI.revive(e.location), + manifest: e.manifest ? JSON.parse(e.manifest) : undefined, readmeUri: URI.revive(e.readmeUri), changelogUri: URI.revive(e.changelogUri), packageNLSUris, @@ -825,6 +815,13 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten 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) { /* Ignore */ if ((error).fileOperationResult !== FileOperationResult.FILE_NOT_FOUND) { @@ -834,37 +831,59 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten // Update if (updateFn) { - webExtensions = updateFn(webExtensions); - function toStringDictionary(dictionary: Map | undefined): IStringDictionary | undefined { - if (!dictionary) { - return undefined; - } - const result: IStringDictionary = Object.create(null); - dictionary.forEach((value, key) => result[key] = value.toJSON()); - return result; - } - const storedWebExtensions: IStoredWebExtension[] = webExtensions.map(e => ({ - identifier: e.identifier, - version: e.version, - location: e.location.toJSON(), - readmeUri: e.readmeUri?.toJSON(), - changelogUri: e.changelogUri?.toJSON(), - packageNLSUris: toStringDictionary(e.packageNLSUris), - fallbackPackageNLSUri: e.fallbackPackageNLSUri?.toJSON(), - metadata: e.metadata - })); - await this.fileService.writeFile(file, VSBuffer.fromString(JSON.stringify(storedWebExtensions))); + await this.storeWebExtensions(webExtensions = updateFn(webExtensions), file); } return webExtensions; }); } + private async migrateWebExtensions(webExtensions: IWebExtension[], file: URI): Promise { + let update = false; + webExtensions = await Promise.all(webExtensions.map(async webExtension => { + if (!webExtension.manifest) { + try { + webExtension.manifest = await this.getExtensionManifest(webExtension.location); + update = true; + } catch (error) { + this.logService.error(`Error while updating manifest of an extension in ${file.toString()}`, webExtension.identifier.id, getErrorMessage(error)); + } + } + return webExtension; + })); + if (update) { + await this.storeWebExtensions(webExtensions, file); + } + return webExtensions; + } + + private async storeWebExtensions(webExtensions: IWebExtension[], file: URI): Promise { + function toStringDictionary(dictionary: Map | undefined): IStringDictionary | undefined { + if (!dictionary) { + return undefined; + } + const result: IStringDictionary = Object.create(null); + dictionary.forEach((value, key) => result[key] = value.toJSON()); + return result; + } + const storedWebExtensions: IStoredWebExtension[] = webExtensions.map(e => ({ + identifier: e.identifier, + version: e.version, + manifest: e.manifest ? JSON.stringify(e.manifest) : undefined, + location: e.location.toJSON(), + readmeUri: e.readmeUri?.toJSON(), + changelogUri: e.changelogUri?.toJSON(), + packageNLSUris: toStringDictionary(e.packageNLSUris), + fallbackPackageNLSUri: e.fallbackPackageNLSUri?.toJSON(), + metadata: e.metadata + })); + await this.fileService.writeFile(file, VSBuffer.fromString(JSON.stringify(storedWebExtensions))); + } + private getResourceAccessQueue(file: URI): Queue { let resourceQueue = this.resourcesAccessQueueMap.get(file); if (!resourceQueue) { - resourceQueue = new Queue(); - this.resourcesAccessQueueMap.set(file, resourceQueue); + this.resourcesAccessQueueMap.set(file, resourceQueue = new Queue()); } return resourceQueue; }