Improvements & clean up
- Deprecate static extensions - Introduce additional builtin extensions - Install web extension from location
This commit is contained in:
parent
bf06adcc48
commit
9ce9551a8b
|
@ -183,10 +183,7 @@ async function getCommandlineProvidedExtensionInfos() {
|
|||
const packageJSON = await getExtensionPackageJSON(extensionPath);
|
||||
if (packageJSON) {
|
||||
const extensionId = `${packageJSON.publisher}.${packageJSON.name}`;
|
||||
extensions.push({
|
||||
packageJSON,
|
||||
extensionLocation: { scheme: SCHEME, authority: AUTHORITY, path: `/extension/${extensionId}` }
|
||||
});
|
||||
extensions.push({ scheme: SCHEME, authority: AUTHORITY, path: `/extension/${extensionId}` });
|
||||
locations[extensionId] = extensionPath;
|
||||
}
|
||||
}));
|
||||
|
@ -208,13 +205,6 @@ async function getExtensionPackageJSON(extensionPath) {
|
|||
if (packageJSON.main && !packageJSON.browser) {
|
||||
return; // unsupported
|
||||
}
|
||||
|
||||
const packageNLSPath = path.join(extensionPath, 'package.nls.json');
|
||||
const packageNLSExists = await exists(packageNLSPath);
|
||||
if (packageNLSExists) {
|
||||
packageJSON = extensions.translatePackageJSON(packageJSON, packageNLSPath); // temporary, until fixed in core
|
||||
}
|
||||
|
||||
return packageJSON;
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
|
@ -407,7 +397,7 @@ async function handleRoot(req, res) {
|
|||
}
|
||||
|
||||
const { extensions: builtInExtensions } = await builtInExtensionsPromise;
|
||||
const { extensions: staticExtensions, locations: staticLocations } = await commandlineProvidedExtensionsPromise;
|
||||
const { extensions: additionalBuiltinExtensions, locations: staticLocations } = await commandlineProvidedExtensionsPromise;
|
||||
|
||||
const dedupedBuiltInExtensions = [];
|
||||
for (const builtInExtension of builtInExtensions) {
|
||||
|
@ -422,7 +412,7 @@ async function handleRoot(req, res) {
|
|||
|
||||
if (args.verbose) {
|
||||
fancyLog(`${ansiColors.magenta('BuiltIn extensions')}: ${dedupedBuiltInExtensions.map(e => path.basename(e.extensionPath)).join(', ')}`);
|
||||
fancyLog(`${ansiColors.magenta('Additional extensions')}: ${staticExtensions.map(e => path.basename(e.extensionLocation.path)).join(', ') || 'None'}`);
|
||||
fancyLog(`${ansiColors.magenta('Additional extensions')}: ${additionalBuiltinExtensions.map(e => path.basename(e.extensionLocation.path)).join(', ') || 'None'}`);
|
||||
}
|
||||
|
||||
const secondaryHost = (
|
||||
|
@ -432,7 +422,7 @@ async function handleRoot(req, res) {
|
|||
);
|
||||
const webConfigJSON = {
|
||||
folderUri: folderUri,
|
||||
staticExtensions,
|
||||
additionalBuiltinExtensions,
|
||||
webWorkerExtensionHostIframeSrc: `${SCHEME}://${secondaryHost}/static/out/vs/workbench/services/extensions/worker/httpWebWorkerExtensionHostIframe.html`
|
||||
};
|
||||
if (args['wrap-iframe']) {
|
||||
|
|
|
@ -9,7 +9,7 @@ import { Registry } from 'vs/platform/registry/common/platform';
|
|||
import { MenuRegistry, MenuId, registerAction2, Action2, ISubmenuItem, IMenuItem, IAction2Options } from 'vs/platform/actions/common/actions';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { ExtensionsLabel, ExtensionsLocalizedLabel, ExtensionsChannelId, IExtensionManagementService, IExtensionGalleryService, PreferencesLocalizedLabel, InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { EnablementState, IExtensionManagementServerService, IWorkbenchExtensionEnablementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
||||
import { EnablementState, IExtensionManagementServerService, IWorkbenchExtensionEnablementService, IWorkbenchExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
||||
import { IExtensionIgnoredRecommendationsService, IExtensionRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations';
|
||||
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||
import { IOutputChannelRegistry, Extensions as OutputExtensions } from 'vs/workbench/services/output/common/output';
|
||||
|
@ -74,6 +74,7 @@ import { EditorExtensions } from 'vs/workbench/common/editor';
|
|||
import { WORKSPACE_TRUST_EXTENSION_SUPPORT } from 'vs/workbench/services/workspaces/common/workspaceTrust';
|
||||
import { ExtensionsCompletionItemsProvider } from 'vs/workbench/contrib/extensions/browser/extensionsCompletionItemsProvider';
|
||||
import { ITASExperimentService } from 'vs/workbench/services/experiment/common/experimentService';
|
||||
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
|
||||
|
||||
// Singletons
|
||||
registerSingleton(IExtensionsWorkbenchService, ExtensionsWorkbenchService);
|
||||
|
@ -772,6 +773,36 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi
|
|||
}
|
||||
});
|
||||
|
||||
this.registerExtensionAction({
|
||||
id: 'workbench.extensions.action.installWebExtensionFromLocation',
|
||||
title: { value: localize('installWebExtensionFromLocation', "Install Web Extension..."), original: 'Install Web Extension...' },
|
||||
category: CATEGORIES.Developer,
|
||||
menu: [{
|
||||
id: MenuId.CommandPalette,
|
||||
when: ContextKeyOrExpr.create([CONTEXT_HAS_WEB_SERVER])
|
||||
}],
|
||||
run: async (accessor: ServicesAccessor) => {
|
||||
const quickInputService = accessor.get(IQuickInputService);
|
||||
const extensionManagementService = accessor.get(IWorkbenchExtensionManagementService);
|
||||
|
||||
const disposables = new DisposableStore();
|
||||
const quickPick = disposables.add(quickInputService.createQuickPick());
|
||||
quickPick.title = localize('installFromLocation', "Install Web Extension from Location");
|
||||
quickPick.customButton = true;
|
||||
quickPick.customLabel = localize('install button', "Install");
|
||||
quickPick.placeholder = localize('installFromLocationPlaceHolder', "Location of the web extension");
|
||||
quickPick.ignoreFocusOut = true;
|
||||
disposables.add(quickPick.onDidAccept(() => {
|
||||
quickPick.hide();
|
||||
if (quickPick.value) {
|
||||
extensionManagementService.installWebExtension(URI.parse(quickPick.value));
|
||||
}
|
||||
}));
|
||||
disposables.add(quickPick.onDidHide(() => disposables.dispose()));
|
||||
quickPick.show();
|
||||
}
|
||||
});
|
||||
|
||||
const extensionsFilterSubMenu = new MenuId('extensionsFilterSubMenu');
|
||||
MenuRegistry.appendMenuItem(MenuId.ViewContainerTitle, <ISubmenuItem>{
|
||||
submenu: extensionsFilterSubMenu,
|
||||
|
|
|
@ -279,7 +279,7 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment
|
|||
const developmentOptions = this.options.developmentOptions;
|
||||
if (developmentOptions) {
|
||||
if (developmentOptions.extensions?.length) {
|
||||
extensionHostDebugEnvironment.extensionDevelopmentLocationURI = developmentOptions.extensions.map(e => URI.revive(e.extensionLocation));
|
||||
extensionHostDebugEnvironment.extensionDevelopmentLocationURI = developmentOptions.extensions.map(e => URI.revive(e));
|
||||
extensionHostDebugEnvironment.isExtensionDevelopment = true;
|
||||
}
|
||||
if (developmentOptions) {
|
||||
|
|
|
@ -27,6 +27,7 @@ export interface IExtensionManagementServerService {
|
|||
export const IWorkbenchExtensionManagementService = refineServiceDecorator<IExtensionManagementService, IWorkbenchExtensionManagementService>(IExtensionManagementService);
|
||||
export interface IWorkbenchExtensionManagementService extends IExtensionManagementService {
|
||||
readonly _serviceBrand: undefined;
|
||||
installWebExtension(location: URI): Promise<ILocalExtension>;
|
||||
installExtensions(extensions: IGalleryExtension[], installOptions?: InstallOptions): Promise<ILocalExtension[]>;
|
||||
updateFromGallery(gallery: IGalleryExtension, extension: ILocalExtension): Promise<ILocalExtension>;
|
||||
getExtensionManagementServerToInstall(manifest: IExtensionManifest): IExtensionManagementServer | null
|
||||
|
@ -112,9 +113,12 @@ export interface IWebExtensionsScannerService {
|
|||
scanSystemExtensions(): Promise<IExtension[]>;
|
||||
scanUserExtensions(): Promise<IExtension[]>;
|
||||
scanExtensionsUnderDevelopment(): Promise<IExtension[]>;
|
||||
scanSingleExtension(extensionLocation: URI, extensionType: ExtensionType): Promise<IExtension | null>;
|
||||
scanExistingExtension(extensionLocation: URI, extensionType: ExtensionType): Promise<IExtension | null>;
|
||||
|
||||
canAddExtension(galleryExtension: IGalleryExtension): boolean;
|
||||
addExtension(galleryExtension: IGalleryExtension): Promise<IExtension>;
|
||||
addExtension(location: URI): Promise<IExtension>;
|
||||
addExtensionFromGallery(galleryExtension: IGalleryExtension): Promise<IExtension>;
|
||||
removeExtension(identifier: IExtensionIdentifier, version?: string): Promise<void>;
|
||||
|
||||
scanExtensionManifest(extensionLocation: URI): Promise<IExtensionManifest | null>;
|
||||
}
|
||||
|
|
|
@ -195,6 +195,13 @@ export class ExtensionManagementService extends Disposable implements IWorkbench
|
|||
return Promise.reject('No Servers to Install');
|
||||
}
|
||||
|
||||
async installWebExtension(location: URI): Promise<ILocalExtension> {
|
||||
if (!this.extensionManagementServerService.webExtensionManagementServer) {
|
||||
throw new Error('Web extension management server is not found');
|
||||
}
|
||||
return this.extensionManagementServerService.webExtensionManagementServer.extensionManagementService.install(location);
|
||||
}
|
||||
|
||||
protected async installVSIX(vsix: URI, server: IExtensionManagementServer, options: InstallVSIXOptions | undefined): Promise<ILocalExtension> {
|
||||
const manifest = await this.getManifest(vsix);
|
||||
if (manifest) {
|
||||
|
|
|
@ -7,7 +7,7 @@ import { ExtensionType, IExtension, IExtensionIdentifier, IExtensionManifest } f
|
|||
import { IExtensionManagementService, ILocalExtension, InstallExtensionEvent, DidInstallExtensionEvent, DidUninstallExtensionEvent, IGalleryExtension, IReportedExtension, IGalleryMetadata, InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { areSameExtensions, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { IWebExtensionsScannerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
|
@ -53,6 +53,30 @@ export class WebExtensionManagementService extends Disposable implements IExtens
|
|||
return this.webExtensionsScannerService.canAddExtension(gallery);
|
||||
}
|
||||
|
||||
async install(location: URI): Promise<ILocalExtension> {
|
||||
const manifest = await this.webExtensionsScannerService.scanExtensionManifest(location);
|
||||
if (!manifest) {
|
||||
throw new Error(`Cannot find packageJSON from the location ${location.toString()}`);
|
||||
}
|
||||
|
||||
const identifier = { id: getGalleryExtensionId(manifest.publisher, manifest.name) };
|
||||
this.logService.info('Installing extension:', identifier.id);
|
||||
this._onInstallExtension.fire({ identifier: identifier });
|
||||
try {
|
||||
const existingExtension = await this.getUserExtension(identifier);
|
||||
const extension = await this.webExtensionsScannerService.addExtension(location);
|
||||
const local = this.toLocalExtension(extension);
|
||||
if (existingExtension && existingExtension.manifest.version !== manifest.version) {
|
||||
await this.webExtensionsScannerService.removeExtension(existingExtension.identifier, existingExtension.manifest.version);
|
||||
}
|
||||
this._onDidInstallExtension.fire({ local, identifier: extension.identifier, operation: InstallOperation.Install });
|
||||
return local;
|
||||
} catch (error) {
|
||||
this._onDidInstallExtension.fire({ error, identifier, operation: InstallOperation.Install });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async installFromGallery(gallery: IGalleryExtension): Promise<ILocalExtension> {
|
||||
if (!(await this.canInstall(gallery))) {
|
||||
throw new Error(localize('cannot be installed', "Cannot install '{0}' because this extension is not a web extension.", gallery.displayName || gallery.name));
|
||||
|
@ -61,7 +85,7 @@ export class WebExtensionManagementService extends Disposable implements IExtens
|
|||
this._onInstallExtension.fire({ identifier: gallery.identifier, gallery });
|
||||
try {
|
||||
const existingExtension = await this.getUserExtension(gallery.identifier);
|
||||
const scannedExtension = await this.webExtensionsScannerService.addExtension(gallery);
|
||||
const scannedExtension = await this.webExtensionsScannerService.addExtensionFromGallery(gallery);
|
||||
const local = await this.toLocalExtension(scannedExtension);
|
||||
if (existingExtension && existingExtension.manifest.version !== gallery.version) {
|
||||
await this.webExtensionsScannerService.removeExtension(existingExtension.identifier, existingExtension.manifest.version);
|
||||
|
@ -107,7 +131,6 @@ export class WebExtensionManagementService extends Disposable implements IExtens
|
|||
zip(extension: ILocalExtension): Promise<URI> { throw new Error('unsupported'); }
|
||||
unzip(zipLocation: URI): Promise<IExtensionIdentifier> { throw new Error('unsupported'); }
|
||||
getManifest(vsix: URI): Promise<IExtensionManifest> { throw new Error('unsupported'); }
|
||||
install(vsix: URI): Promise<ILocalExtension> { throw new Error('unsupported'); }
|
||||
reinstallFromGallery(extension: ILocalExtension): Promise<void> { throw new Error('unsupported'); }
|
||||
getExtensionsReport(): Promise<IReportedExtension[]> { throw new Error('unsupported'); }
|
||||
updateExtensionScope(): Promise<ILocalExtension> { throw new Error('unsupported'); }
|
||||
|
|
|
@ -11,20 +11,19 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
|||
import { joinPath } from 'vs/base/common/resources';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { Queue } from 'vs/base/common/async';
|
||||
import { Promises, Queue } from 'vs/base/common/async';
|
||||
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 { 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';
|
||||
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, isString, isUndefined } from 'vs/base/common/types';
|
||||
import { isFunction, isString, isUndefined } from 'vs/base/common/types';
|
||||
import { getErrorMessage } from 'vs/base/common/errors';
|
||||
import { ResourceMap } from 'vs/base/common/map';
|
||||
|
||||
|
@ -52,9 +51,9 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
|
|||
|
||||
private readonly builtinExtensionsPromise: Promise<IExtension[]> = Promise.resolve([]);
|
||||
private readonly staticExtensionsPromise: Promise<IExtension[]> = Promise.resolve([]);
|
||||
private readonly userConfiguredExtensionsPromise: Promise<IExtension[]> = Promise.resolve([]);
|
||||
private readonly customBuiltinExtensionsPromise: Promise<IExtension[]> = Promise.resolve([]);
|
||||
|
||||
private readonly staticExtensionsResource: URI | undefined = undefined;
|
||||
private readonly customBuiltinExtensionsCacheResource: URI | undefined = undefined;
|
||||
private readonly installedExtensionsResource: URI | undefined = undefined;
|
||||
private readonly resourcesAccessQueueMap = new ResourceMap<Queue<IWebExtension[]>>();
|
||||
|
||||
|
@ -64,23 +63,22 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
|
|||
@IFileService private readonly fileService: IFileService,
|
||||
@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.customBuiltinExtensionsCacheResource = joinPath(environmentService.userRoamingDataHome, 'customBuiltinExtensionsCache.json');
|
||||
this.builtinExtensionsPromise = this.readSystemExtensions();
|
||||
this.staticExtensionsPromise = this.readStaticExtensions();
|
||||
this.userConfiguredExtensionsPromise = this.readUserConfiguredExtensions();
|
||||
this.customBuiltinExtensionsPromise = this.readCustomBuiltinExtensions();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* All builtin extensions bundled with the product
|
||||
* All system extensions bundled with the product
|
||||
*/
|
||||
private async readBuiltinExtensions(): Promise<IExtension[]> {
|
||||
private async readSystemExtensions(): Promise<IExtension[]> {
|
||||
let builtinExtensions = await this.builtinExtensionsScannerService.scanBuiltinExtensions();
|
||||
if (isFunction(this.environmentService.options?.builtinExtensionsFilter)) {
|
||||
builtinExtensions = builtinExtensions.filter(e => this.environmentService.options!.builtinExtensionsFilter!(e.identifier.id));
|
||||
|
@ -92,25 +90,55 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
|
|||
* All extensions defined via `staticExtensions` API
|
||||
*/
|
||||
private async readStaticExtensions(): Promise<IExtension[]> {
|
||||
const staticExtensions: (string | IStaticExtension)[] = this.environmentService.options && Array.isArray(this.environmentService.options.staticExtensions) ? this.environmentService.options.staticExtensions : [];
|
||||
const staticExtensionIds = [], result: IExtension[] = [];
|
||||
const staticExtensions = this.environmentService.options && Array.isArray(this.environmentService.options.staticExtensions) ? this.environmentService.options.staticExtensions : [];
|
||||
const result: IExtension[] = [];
|
||||
for (const e of staticExtensions) {
|
||||
const extension = this.parseStaticExtension(e, isUndefined(e.isBuiltin) ? true : e.isBuiltin);
|
||||
if (extension) {
|
||||
result.push(extension);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* All extensions defined via `additionalBuiltinExtensions` API
|
||||
*/
|
||||
private async readCustomBuiltinExtensions(): Promise<IExtension[]> {
|
||||
const customBuiltinExtensions = this.environmentService.options && Array.isArray(this.environmentService.options.additionalBuiltinExtensions) ? this.environmentService.options.additionalBuiltinExtensions : [];
|
||||
const extensionIds: string[] = [], extensionLocations: URI[] = [], result: IExtension[] = [];
|
||||
for (const e of customBuiltinExtensions) {
|
||||
if (isString(e)) {
|
||||
staticExtensionIds.push(e);
|
||||
extensionIds.push(e);
|
||||
} else {
|
||||
const extension = this.parseStaticExtension(e, isUndefined(e.isBuiltin) ? true : e.isBuiltin);
|
||||
if (extension) {
|
||||
result.push(extension);
|
||||
extensionLocations.push(URI.revive(e));
|
||||
}
|
||||
}
|
||||
|
||||
await Promises.allSettled([
|
||||
(async () => {
|
||||
if (extensionLocations.length) {
|
||||
await Promises.allSettled(extensionLocations.map(async location => {
|
||||
try {
|
||||
const webExtension = await this.toWebExtensionFromLocation(location);
|
||||
result.push(await this.toExtension(webExtension, true));
|
||||
} catch (error) {
|
||||
this.logService.info(`Error while fetching the the static extension ${location.toString()}.`, getErrorMessage(error));
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
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));
|
||||
}
|
||||
}
|
||||
})(),
|
||||
(async () => {
|
||||
if (extensionIds.length) {
|
||||
try {
|
||||
result.push(...await this.getStaticExtensionsFromGallery(extensionIds));
|
||||
} catch (error) {
|
||||
this.logService.info('Ignoring following static extensions as there is an error while fetching them from gallery', extensionIds, getErrorMessage(error));
|
||||
}
|
||||
}
|
||||
})(),
|
||||
]);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -120,7 +148,7 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
|
|||
return [];
|
||||
}
|
||||
|
||||
const cachedStaticWebExtensions = await this.readWebExtensions(this.staticExtensionsResource);
|
||||
const cachedStaticWebExtensions = await this.readWebExtensions(this.customBuiltinExtensionsCacheResource);
|
||||
const webExtensions: IWebExtension[] = [];
|
||||
extensionIds = extensionIds.map(id => id.toLowerCase());
|
||||
|
||||
|
@ -142,7 +170,7 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
|
|||
await Promise.all(galleryExtensions.map(async gallery => {
|
||||
try {
|
||||
if (this.canAddExtension(gallery)) {
|
||||
webExtensions.push(await this.toWebExtension(gallery));
|
||||
webExtensions.push(await this.toWebExtensionFromGallery(gallery));
|
||||
} else {
|
||||
this.logService.info(`Ignoring static gallery extension ${gallery.identifier.id} because it is not a web extension`);
|
||||
}
|
||||
|
@ -165,7 +193,7 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
|
|||
}
|
||||
|
||||
try {
|
||||
await this.writeWebExtensions(this.staticExtensionsResource, webExtensions);
|
||||
await this.writeWebExtensions(this.customBuiltinExtensionsCacheResource, webExtensions);
|
||||
} catch (error) {
|
||||
this.logService.info(`Ignoring the error while adding static gallery extensions`, getErrorMessage(error));
|
||||
}
|
||||
|
@ -190,44 +218,6 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
|
|||
return null;
|
||||
}
|
||||
|
||||
private async readUserConfiguredExtensions(): Promise<IExtension[]> {
|
||||
const result: IStaticExtension[] = [];
|
||||
const userConfiguredExtensions = this.configurationService.getValue<{ location: string }[]>('_extensions.defaultUserWebExtensions');
|
||||
if (isArray(userConfiguredExtensions)) {
|
||||
for (const userConfiguredExtension of userConfiguredExtensions) {
|
||||
try {
|
||||
const extensionLocation = URI.parse(userConfiguredExtension.location);
|
||||
const manifestLocation = joinPath(extensionLocation, 'package.json');
|
||||
const context = await this.requestService.request({ type: 'GET', url: manifestLocation.toString(true) }, CancellationToken.None);
|
||||
if (!isSuccess(context)) {
|
||||
this.logService.warn('Skipped user static extension as there is an error while fetching manifest', manifestLocation);
|
||||
continue;
|
||||
}
|
||||
const content = await asText(context);
|
||||
if (!content) {
|
||||
this.logService.warn('Skipped user static extension as there is manifest is not found', manifestLocation);
|
||||
continue;
|
||||
}
|
||||
const packageJSON = JSON.parse(content);
|
||||
result.push({
|
||||
packageJSON,
|
||||
extensionLocation,
|
||||
});
|
||||
} catch (error) {
|
||||
this.logService.warn('Skipped user static extension as there is an error while fetching manifest', userConfiguredExtension);
|
||||
}
|
||||
}
|
||||
}
|
||||
const extensions: IExtension[] = [];
|
||||
for (const e of result) {
|
||||
const extension = this.parseStaticExtension(e, false);
|
||||
if (extension) {
|
||||
extensions.push(extension);
|
||||
}
|
||||
}
|
||||
return extensions;
|
||||
}
|
||||
|
||||
async scanSystemExtensions(): Promise<IExtension[]> {
|
||||
return this.builtinExtensionsPromise;
|
||||
}
|
||||
|
@ -239,9 +229,9 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
|
|||
const staticExtensions = await this.staticExtensionsPromise;
|
||||
extensions.push(...staticExtensions);
|
||||
|
||||
// User configured extensions
|
||||
const userConfiguredExtensions = await this.userConfiguredExtensionsPromise;
|
||||
extensions.push(...userConfiguredExtensions);
|
||||
// Custom builtin extensions defined through `additionalBuiltinExtensions` API
|
||||
const customBuiltinExtensions = await this.customBuiltinExtensionsPromise;
|
||||
extensions.push(...customBuiltinExtensions);
|
||||
|
||||
// User Installed extensions
|
||||
const installedExtensions = await this.scanInstalledExtensions();
|
||||
|
@ -254,17 +244,24 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
|
|||
const devExtensions = this.environmentService.options?.developmentOptions?.extensions;
|
||||
const result: IExtension[] = [];
|
||||
if (Array.isArray(devExtensions)) {
|
||||
for (const e of devExtensions) {
|
||||
const extension = this.parseStaticExtension(e, false);
|
||||
if (extension) {
|
||||
result.push(extension);
|
||||
await Promises.allSettled(devExtensions.map(async devExtension => {
|
||||
try {
|
||||
const location = URI.revive(devExtension);
|
||||
if (URI.isUri(location)) {
|
||||
const webExtension = await this.toWebExtensionFromLocation(location);
|
||||
result.push(await this.toExtension(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 scanSingleExtension(extensionLocation: URI, extensionType: ExtensionType): Promise<IExtension | null> {
|
||||
async scanExistingExtension(extensionLocation: URI, extensionType: ExtensionType): Promise<IExtension | null> {
|
||||
if (extensionType === ExtensionType.System) {
|
||||
const systemExtensions = await this.scanSystemExtensions();
|
||||
return systemExtensions.find(e => e.location.toString() === extensionLocation.toString()) || null;
|
||||
|
@ -273,6 +270,18 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
|
|||
return userExtensions.find(e => e.location.toString() === extensionLocation.toString()) || null;
|
||||
}
|
||||
|
||||
async scanExtensionManifest(extensionLocation: URI): Promise<IExtensionManifest | null> {
|
||||
const packageJSONUri = joinPath(extensionLocation, 'package.json');
|
||||
const context = await this.requestService.request({ type: 'GET', url: packageJSONUri.toString() }, CancellationToken.None);
|
||||
if (isSuccess(context)) {
|
||||
const content = await asText(context);
|
||||
if (content) {
|
||||
return JSON.parse(content);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
canAddExtension(galleryExtension: IGalleryExtension): boolean {
|
||||
if (this.environmentService.options?.assumeGalleryExtensionsAreAddressable) {
|
||||
return true;
|
||||
|
@ -281,17 +290,18 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
|
|||
return !!galleryExtension.properties.webExtension && !!galleryExtension.webResource;
|
||||
}
|
||||
|
||||
async addExtension(galleryExtension: IGalleryExtension): Promise<IExtension> {
|
||||
async addExtensionFromGallery(galleryExtension: IGalleryExtension): Promise<IExtension> {
|
||||
if (!this.canAddExtension(galleryExtension)) {
|
||||
throw new Error(localize('cannot be installed', "Cannot install '{0}' because this extension is not a web extension.", galleryExtension.displayName || galleryExtension.name));
|
||||
}
|
||||
|
||||
const webExtension = await this.toWebExtension(galleryExtension);
|
||||
const extension = await this.toExtension(webExtension, false);
|
||||
const installedExtensions = await this.readInstalledExtensions();
|
||||
installedExtensions.push(webExtension);
|
||||
await this.writeInstalledExtensions(installedExtensions);
|
||||
return extension;
|
||||
const webExtension = await this.toWebExtensionFromGallery(galleryExtension);
|
||||
return this.addWebExtension(webExtension);
|
||||
}
|
||||
|
||||
async addExtension(location: URI): Promise<IExtension> {
|
||||
const webExtension = await this.toWebExtensionFromLocation(location);
|
||||
return this.addWebExtension(webExtension);
|
||||
}
|
||||
|
||||
async removeExtension(identifier: IExtensionIdentifier, version?: string): Promise<void> {
|
||||
|
@ -300,6 +310,14 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
|
|||
await this.writeInstalledExtensions(installedExtensions);
|
||||
}
|
||||
|
||||
private async addWebExtension(webExtension: IWebExtension) {
|
||||
const extension = await this.toExtension(webExtension, false);
|
||||
const installedExtensions = await this.readInstalledExtensions();
|
||||
installedExtensions.push(webExtension);
|
||||
await this.writeInstalledExtensions(installedExtensions);
|
||||
return extension;
|
||||
}
|
||||
|
||||
private async scanInstalledExtensions(): Promise<IExtension[]> {
|
||||
let installedExtensions = await this.readInstalledExtensions();
|
||||
const byExtension: IWebExtension[][] = groupByExtension(installedExtensions, e => e.identifier);
|
||||
|
@ -315,7 +333,7 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
|
|||
return extensions;
|
||||
}
|
||||
|
||||
private async toWebExtension(galleryExtension: IGalleryExtension): Promise<IWebExtension> {
|
||||
private async toWebExtensionFromGallery(galleryExtension: IGalleryExtension): Promise<IWebExtension> {
|
||||
const extensionLocation = joinPath(galleryExtension.assetUri, 'Microsoft.VisualStudio.Code.WebResources', 'extension');
|
||||
const packageNLSUri = joinPath(extensionLocation, 'package.nls.json');
|
||||
const context = await this.requestService.request({ type: 'GET', url: packageNLSUri.toString() }, CancellationToken.None);
|
||||
|
@ -330,6 +348,35 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
|
|||
};
|
||||
}
|
||||
|
||||
private async toWebExtensionFromLocation(extensionLocation: URI): Promise<IWebExtension> {
|
||||
const packageJSONUri = joinPath(extensionLocation, 'package.json');
|
||||
const packageNLSUri: URI = joinPath(extensionLocation, 'package.nls.json');
|
||||
|
||||
const [result1, result2] = await Promises.allSettled([
|
||||
this.requestService.request({ type: 'GET', url: packageJSONUri.toString() }, CancellationToken.None),
|
||||
this.requestService.request({ type: 'GET', url: packageNLSUri.toString() }, CancellationToken.None)
|
||||
]);
|
||||
|
||||
if (result1.status === 'rejected' || !isSuccess(result1.value)) {
|
||||
throw new Error(`Cannot find the package.json from the location '${extensionLocation.toString()}'`);
|
||||
}
|
||||
|
||||
const content = await asText(result1.value);
|
||||
if (!content) {
|
||||
throw new Error(`Error while fetching package.json for extension '${extensionLocation.toString()}'. Server returned no content`);
|
||||
}
|
||||
|
||||
const manifest = JSON.parse(content);
|
||||
const packageNLSExists = result2.status === 'fulfilled' && isSuccess(result2.value);
|
||||
|
||||
return {
|
||||
identifier: { id: getGalleryExtensionId(manifest.publisher, manifest.name) },
|
||||
version: manifest.version,
|
||||
location: extensionLocation,
|
||||
packageNLSUri: packageNLSExists ? packageNLSUri : undefined
|
||||
};
|
||||
}
|
||||
|
||||
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)) {
|
||||
|
|
|
@ -93,7 +93,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten
|
|||
return this._remoteAgentService.scanSingleExtension(extension.location, extension.type === ExtensionType.System);
|
||||
}
|
||||
|
||||
const scannedExtension = await this._webExtensionsScannerService.scanSingleExtension(extension.location, extension.type);
|
||||
const scannedExtension = await this._webExtensionsScannerService.scanExistingExtension(extension.location, extension.type);
|
||||
if (scannedExtension) {
|
||||
return toExtensionDescription(scannedExtension);
|
||||
}
|
||||
|
|
|
@ -318,8 +318,15 @@ interface IWorkbenchConstructionOptions {
|
|||
|
||||
/**
|
||||
* Add static extensions that cannot be uninstalled but only be disabled.
|
||||
* @deprecated
|
||||
*/
|
||||
readonly staticExtensions?: readonly (string | IStaticExtension)[];
|
||||
readonly staticExtensions?: readonly IStaticExtension[];
|
||||
|
||||
/**
|
||||
* Additional builtin extensions that cannot be uninstalled but only be disabled.
|
||||
* It can be an Id of an extension published in the Marketplace or location of the extension where it is hosted.
|
||||
*/
|
||||
readonly additionalBuiltinExtensions?: readonly (string | UriComponents)[];
|
||||
|
||||
/**
|
||||
* Filter for built-in extensions.
|
||||
|
@ -438,7 +445,7 @@ interface IDevelopmentOptions {
|
|||
/**
|
||||
* Add extensions under development.
|
||||
*/
|
||||
readonly extensions?: readonly IStaticExtension[];
|
||||
readonly extensions?: readonly UriComponents[];
|
||||
|
||||
/**
|
||||
* Whether to enable the smoke test driver.
|
||||
|
|
Loading…
Reference in a new issue