support gallery extensions as static extensions
This commit is contained in:
parent
99a2f34ed9
commit
522faa099d
|
@ -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 };
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in a new issue