support gallery extensions as static extensions

This commit is contained in:
Sandeep Somavarapu 2021-06-16 15:10:03 +02:00
parent 99a2f34ed9
commit 522faa099d
No known key found for this signature in database
GPG key ID: 1FED25EC4646638B
3 changed files with 110 additions and 26 deletions

View file

@ -53,6 +53,7 @@ const args = minimist(process.argv, {
'port',
'local_port',
'extension',
'extensionId',
'github-auth'
],
});
@ -69,6 +70,7 @@ if (args.help) {
' --local_port Local port override\n' +
' --secondary-port Secondary port\n' +
' --extension Path of an extension to include\n' +
' --extensionId Id of an extension to include\n' +
' --github-auth Github authentication token\n' +
' --verbose Print out more information\n' +
' --help\n' +
@ -169,23 +171,31 @@ async function getCommandlineProvidedExtensionInfos() {
const locations = {};
let extensionArg = args['extension'];
if (!extensionArg) {
let extensionIdArg = args['extensionId'];
if (!extensionArg && !extensionIdArg) {
return { extensions, locations };
}
const extensionPaths = Array.isArray(extensionArg) ? extensionArg : [extensionArg];
await Promise.all(extensionPaths.map(async extensionPath => {
extensionPath = path.resolve(process.cwd(), extensionPath);
const packageJSON = await getExtensionPackageJSON(extensionPath);
if (packageJSON) {
const extensionId = `${packageJSON.publisher}.${packageJSON.name}`;
extensions.push({
packageJSON,
extensionLocation: { scheme: SCHEME, authority: AUTHORITY, path: `/extension/${extensionId}` }
});
locations[extensionId] = extensionPath;
}
}));
if (extensionArg) {
const extensionPaths = Array.isArray(extensionArg) ? extensionArg : [extensionArg];
await Promise.all(extensionPaths.map(async extensionPath => {
extensionPath = path.resolve(process.cwd(), extensionPath);
const packageJSON = await getExtensionPackageJSON(extensionPath);
if (packageJSON) {
const extensionId = `${packageJSON.publisher}.${packageJSON.name}`;
extensions.push({
packageJSON,
extensionLocation: { scheme: SCHEME, authority: AUTHORITY, path: `/extension/${extensionId}` }
});
locations[extensionId] = extensionPath;
}
}));
}
if (extensionIdArg) {
extensions.push(...(Array.isArray(extensionIdArg) ? extensionIdArg : [extensionIdArg]));
}
return { extensions, locations };
}

View file

@ -16,7 +16,7 @@ import { VSBuffer } from 'vs/base/common/buffer';
import { asText, isSuccess, IRequestService } from 'vs/platform/request/common/request';
import { ILogService } from 'vs/platform/log/common/log';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IGalleryExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IExtensionGalleryService, IGalleryExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
import { groupByExtension, areSameExtensions, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import type { IStaticExtension } from 'vs/workbench/workbench.web.api';
@ -24,7 +24,8 @@ import { Disposable } from 'vs/base/common/lifecycle';
import { localizeManifest } from 'vs/platform/extensionManagement/common/extensionNls';
import { localize } from 'vs/nls';
import * as semver from 'vs/base/common/semver/semver';
import { isArray, isFunction, isUndefined } from 'vs/base/common/types';
import { isArray, isFunction, isString, isUndefined } from 'vs/base/common/types';
import { getErrorMessage } from 'vs/base/common/errors';
import { ResourceMap } from 'vs/base/common/map';
interface IStoredWebExtension {
@ -53,6 +54,7 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
private readonly staticExtensionsPromise: Promise<IExtension[]> = Promise.resolve([]);
private readonly userConfiguredExtensionsPromise: Promise<IExtension[]> = Promise.resolve([]);
private readonly staticExtensionsResource: URI | undefined = undefined;
private readonly installedExtensionsResource: URI | undefined = undefined;
private readonly resourcesAccessQueueMap = new ResourceMap<Queue<IWebExtension[]>>();
@ -63,10 +65,12 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
@IRequestService private readonly requestService: IRequestService,
@ILogService private readonly logService: ILogService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IExtensionGalleryService private readonly galleryService: IExtensionGalleryService,
) {
super();
if (isWeb) {
this.installedExtensionsResource = joinPath(environmentService.userRoamingDataHome, 'extensions.json');
this.staticExtensionsResource = joinPath(environmentService.userRoamingDataHome, 'staticExtensions.json');
this.builtinExtensionsPromise = this.readBuiltinExtensions();
this.staticExtensionsPromise = this.readStaticExtensions();
this.userConfiguredExtensionsPromise = this.readUserConfiguredExtensions();
@ -88,14 +92,84 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
* All extensions defined via `staticExtensions` API
*/
private async readStaticExtensions(): Promise<IExtension[]> {
const staticExtensions = this.environmentService.options && Array.isArray(this.environmentService.options.staticExtensions) ? this.environmentService.options.staticExtensions : [];
const result: IExtension[] = [];
const staticExtensions: (string | IStaticExtension)[] = this.environmentService.options && Array.isArray(this.environmentService.options.staticExtensions) ? this.environmentService.options.staticExtensions : [];
const staticExtensionIds = [], result: IExtension[] = [];
for (const e of staticExtensions) {
const extension = this.parseStaticExtension(e, isUndefined(e.isBuiltin) ? true : e.isBuiltin);
if (extension) {
result.push(extension);
if (isString(e)) {
staticExtensionIds.push(e);
} else {
const extension = this.parseStaticExtension(e, isUndefined(e.isBuiltin) ? true : e.isBuiltin);
if (extension) {
result.push(extension);
}
}
}
if (staticExtensionIds.length) {
try {
result.push(...await this.getStaticExtensionsFromGallery(staticExtensionIds));
} catch (error) {
this.logService.info('Ignoring following static extensions as there is an error while fetching them from gallery', staticExtensionIds, getErrorMessage(error));
}
}
return result;
}
private async getStaticExtensionsFromGallery(extensionIds: string[]): Promise<IExtension[]> {
if (!this.galleryService.isEnabled()) {
this.logService.info('Ignoring fetching static extensions from gallery as it is disabled.');
return [];
}
const cachedStaticWebExtensions = await this.readWebExtensions(this.staticExtensionsResource);
const webExtensions: IWebExtension[] = [];
extensionIds = extensionIds.map(id => id.toLowerCase());
for (const webExtension of cachedStaticWebExtensions) {
const index = extensionIds.indexOf(webExtension.identifier.id.toLowerCase());
if (index !== -1) {
webExtensions.push(webExtension);
extensionIds.splice(index, 1);
}
}
if (extensionIds.length) {
const galleryExtensions = await this.galleryService.getExtensions(extensionIds, CancellationToken.None);
const missingExtensions = extensionIds.filter(id => !galleryExtensions.find(({ identifier }) => areSameExtensions(identifier, { id })));
if (missingExtensions.length) {
this.logService.info('Cannot find static extensions from gallery', missingExtensions);
}
await Promise.all(galleryExtensions.map(async gallery => {
try {
if (this.canAddExtension(gallery)) {
webExtensions.push(await this.toWebExtension(gallery));
} else {
this.logService.info(`Ignoring static gallery extension ${gallery.identifier.id} because it is not a web extension`);
}
} catch (error) {
this.logService.info(`Ignoring static gallery extension ${gallery.identifier.id} because there is an error while converting it into web extension`, getErrorMessage(error));
}
}));
}
const result: IExtension[] = [];
if (webExtensions.length) {
await Promise.all(webExtensions.map(async webExtension => {
try {
result.push(await this.toExtension(webExtension, true));
} catch (error) {
this.logService.info(`Ignoring static gallery extension ${webExtension.identifier.id} because there is an error while converting it into scanned extension`, getErrorMessage(error));
}
}));
}
try {
await this.writeWebExtensions(this.staticExtensionsResource, webExtensions);
} catch (error) {
this.logService.info(`Ignoring the error while adding static gallery extensions`, getErrorMessage(error));
}
return result;
}
@ -213,7 +287,7 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
}
const webExtension = await this.toWebExtension(galleryExtension);
const extension = await this.toExtension(webExtension);
const extension = await this.toExtension(webExtension, false);
const installedExtensions = await this.readInstalledExtensions();
installedExtensions.push(webExtension);
await this.writeInstalledExtensions(installedExtensions);
@ -233,7 +307,7 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
const extensions: IExtension[] = [];
await Promise.all(installedExtensions.map(async installedExtension => {
try {
extensions.push(await this.toExtension(installedExtension));
extensions.push(await this.toExtension(installedExtension, false));
} catch (error) {
this.logService.error(error, 'Error while scanning user extension', installedExtension.identifier.id);
}
@ -256,7 +330,7 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
};
}
private async toExtension(webExtension: IWebExtension): Promise<IExtension> {
private async toExtension(webExtension: IWebExtension, isBuiltin: boolean): Promise<IExtension> {
const context = await this.requestService.request({ type: 'GET', url: joinPath(webExtension.location, 'package.json').toString() }, CancellationToken.None);
if (!isSuccess(context)) {
throw new Error(`Error while fetching package.json for extension '${webExtension.identifier.id}'. Server returned ${context.res.statusCode}`);
@ -276,7 +350,7 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
location: webExtension.location,
manifest,
type: ExtensionType.User,
isBuiltin: false,
isBuiltin,
readmeUrl: webExtension.readmeUri,
changelogUrl: webExtension.changelogUri,
};

View file

@ -319,7 +319,7 @@ interface IWorkbenchConstructionOptions {
/**
* Add static extensions that cannot be uninstalled but only be disabled.
*/
readonly staticExtensions?: readonly IStaticExtension[];
readonly staticExtensions?: readonly (string | IStaticExtension)[];
/**
* Filter for built-in extensions.