From b796ca4529a770a8a52562210eebc259c80dc22b Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Fri, 10 Nov 2023 03:10:37 +0100 Subject: [PATCH 01/17] feat: extension gallery & installation support remove field change semver to vscode version --- demo/package-lock.json | 14 ++ demo/package.json | 1 + demo/src/setup.ts | 12 ++ package-lock.json | 18 ++ package.json | 2 + rollup/rollup.config.ts | 1 + scripts/vscode.patch | 26 +++ src/missing-services.ts | 176 ++++++++++++++++++- src/override/vs/base/common/semver/semver.ts | 6 +- src/server/server-main.ts | 127 +++++++++++++ src/server/server.ts | 139 +++------------ src/service-override/extensionGallery.ts | 52 ++++++ 12 files changed, 446 insertions(+), 128 deletions(-) create mode 100644 src/server/server-main.ts create mode 100644 src/service-override/extensionGallery.ts diff --git a/demo/package-lock.json b/demo/package-lock.json index bc6045c3..9b5247fd 100644 --- a/demo/package-lock.json +++ b/demo/package-lock.json @@ -32,6 +32,7 @@ "@codingame/monaco-vscode-emmet-default-extension": "file:../dist/default-extension-emmet", "@codingame/monaco-vscode-environment-service-override": "file:../dist/service-override-environment", "@codingame/monaco-vscode-extension-editing-default-extension": "file:../dist/default-extension-extension-editing", + "@codingame/monaco-vscode-extension-gallery-service-override": "file:../dist/service-override-extension-gallery", "@codingame/monaco-vscode-extensions-service-override": "file:../dist/service-override-extensions", "@codingame/monaco-vscode-files-service-override": "file:../dist/service-override-files", "@codingame/monaco-vscode-fsharp-default-extension": "file:../dist/default-extension-fsharp", @@ -1002,6 +1003,15 @@ "vscode": "npm:@codingame/monaco-vscode-api@^0.0.0-semantic-release" } }, + "../dist/service-override-extension-gallery": { + "name": "@codingame/monaco-vscode-extension-gallery-service-override", + "version": "0.0.0-semantic-release", + "license": "MIT", + "dependencies": { + "monaco-editor": "0.44.0", + "vscode": "npm:@codingame/monaco-vscode-api@^0.0.0-semantic-release" + } + }, "../dist/service-override-extensions": { "name": "@codingame/monaco-vscode-extensions-service-override", "version": "0.0.0-semantic-release", @@ -1484,6 +1494,10 @@ "resolved": "../dist/default-extension-extension-editing", "link": true }, + "node_modules/@codingame/monaco-vscode-extension-gallery-service-override": { + "resolved": "../dist/service-override-extension-gallery", + "link": true + }, "node_modules/@codingame/monaco-vscode-extensions-service-override": { "resolved": "../dist/service-override-extensions", "link": true diff --git a/demo/package.json b/demo/package.json index 3db80911..be912f19 100644 --- a/demo/package.json +++ b/demo/package.json @@ -49,6 +49,7 @@ "@codingame/monaco-vscode-editor-service-override": "file:../dist/service-override-editor", "@codingame/monaco-vscode-emmet-default-extension": "file:../dist/default-extension-emmet", "@codingame/monaco-vscode-environment-service-override": "file:../dist/service-override-environment", + "@codingame/monaco-vscode-extension-gallery-service-override": "file:../dist/service-override-extension-gallery", "@codingame/monaco-vscode-extension-editing-default-extension": "file:../dist/default-extension-extension-editing", "@codingame/monaco-vscode-extensions-service-override": "file:../dist/service-override-extensions", "@codingame/monaco-vscode-files-service-override": "file:../dist/service-override-files", diff --git a/demo/src/setup.ts b/demo/src/setup.ts index e6b153b9..81ba1c07 100644 --- a/demo/src/setup.ts +++ b/demo/src/setup.ts @@ -9,6 +9,7 @@ import getTextmateServiceOverride from '@codingame/monaco-vscode-textmate-servic import getThemeServiceOverride from '@codingame/monaco-vscode-theme-service-override' import getLanguagesServiceOverride from '@codingame/monaco-vscode-languages-service-override' import getAudioCueServiceOverride from '@codingame/monaco-vscode-audio-cue-service-override' +import getExtensionGalleryServiceOverride from '@codingame/monaco-vscode-extension-gallery-service-override' import getViewsServiceOverride, { isEditorPartVisible, Parts, @@ -91,6 +92,7 @@ await Promise.all([ await initializeMonacoService({ ...getLogServiceOverride(), ...getExtensionServiceOverride(toWorkerConfig(ExtensionHostWorker)), + ...getExtensionGalleryServiceOverride(), ...getModelServiceOverride(), ...getNotificationServiceOverride(), ...getDialogsServiceOverride(), @@ -140,6 +142,16 @@ await initializeMonacoService({ }, developmentOptions: { logLevel: LogLevel.Info // Default value + }, + productConfiguration: { + extensionsGallery: { + serviceUrl: 'https://open-vsx.org/vscode/gallery', + itemUrl: 'https://open-vsx.org/vscode/item', + resourceUrlTemplate: 'https://open-vsx.org/vscode/unpkg/{publisher}/{name}/{version}/{path}', + controlUrl: '', + nlsBaseUrl: '', + publisherUrl: '' + } } }) diff --git a/package-lock.json b/package-lock.json index 26799bce..341274be 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,7 @@ "node-pty": "1.1.0-beta1", "vscode-oniguruma": "1.7.0", "vscode-regexpp": "^3.1.0", + "vscode-semver": "npm:semver@=5.5.0", "vscode-textmate": "9.0.0", "xterm": "5.4.0-beta.27", "xterm-addon-canvas": "0.6.0-beta.27", @@ -58,6 +59,7 @@ "@types/node": "^18.17.15", "@types/semver": "^7.5.5", "@types/vscode": "~1.83.0", + "@types/vscode-semver": "npm:@types/semver@=5.5.0", "@types/yauzl": "^2.10.3", "@typescript-eslint/eslint-plugin": "^6.11.0", "@typescript-eslint/parser": "^6.11.0", @@ -3012,6 +3014,13 @@ "integrity": "sha512-3mUtHqLAVz9hegut9au4xehuBrzRE3UJiQMpoEHkNl6XHliihO7eATx2BMHs0odsmmrwjJrlixx/Pte6M3ygDQ==", "dev": true }, + "node_modules/@types/vscode-semver": { + "name": "@types/semver", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-5.5.0.tgz", + "integrity": "sha512-41qEJgBH/TWgo5NFSvBCJ1qkoi3Q6ONSF2avrHq1LVEZfYpdHmj0y9SuTK+u9ZhG1sYQKBL1AWXKyLWP4RaUoQ==", + "dev": true + }, "node_modules/@types/which": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/which/-/which-3.0.0.tgz", @@ -9519,6 +9528,15 @@ "url": "https://github.com/sponsors/mysticatea" } }, + "node_modules/vscode-semver": { + "name": "semver", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", + "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", + "bin": { + "semver": "bin/semver" + } + }, "node_modules/vscode-textmate": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-9.0.0.tgz", diff --git a/package.json b/package.json index 9e68f4f3..1786c866 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,7 @@ "@types/mime-types": "^2.1.4", "@types/node": "^18.17.15", "@types/semver": "^7.5.5", + "@types/vscode-semver": "npm:@types/semver@=5.5.0", "@types/vscode": "~1.83.0", "@types/yauzl": "^2.10.3", "@typescript-eslint/eslint-plugin": "^6.11.0", @@ -112,6 +113,7 @@ "node-pty": "1.1.0-beta1", "vscode-oniguruma": "1.7.0", "vscode-regexpp": "^3.1.0", + "vscode-semver": "npm:semver@=5.5.0", "vscode-textmate": "9.0.0", "xterm": "5.4.0-beta.27", "xterm-addon-canvas": "0.6.0-beta.27", diff --git a/rollup/rollup.config.ts b/rollup/rollup.config.ts index 1a35b41a..30d222dd 100644 --- a/rollup/rollup.config.ts +++ b/rollup/rollup.config.ts @@ -496,6 +496,7 @@ const workerGroups: Record = { const externals = Object.keys({ ...pkg.dependencies }) const external: rollup.ExternalOption = (source) => { + if (source === 'semver' || source.startsWith('semver')) return true if (source.includes('tas-client-umd')) return true if (source.startsWith(MONACO_EDITOR_DIR) || source.startsWith('monaco-editor/')) { return true diff --git a/scripts/vscode.patch b/scripts/vscode.patch index e93fba06..84e84def 100644 --- a/scripts/vscode.patch +++ b/scripts/vscode.patch @@ -1675,6 +1675,19 @@ index f4707432371..7c5cdde39cc 100644 declare _serviceBrand: undefined; +diff --git a/src/vs/workbench/services/remote/common/remoteExtensionsScanner.ts b/src/vs/workbench/services/remote/common/remoteExtensionsScanner.ts +index a466cc1f3a1..09f6a40eb26 100644 +--- a/src/vs/workbench/services/remote/common/remoteExtensionsScanner.ts ++++ b/src/vs/workbench/services/remote/common/remoteExtensionsScanner.ts +@@ -16,7 +16,7 @@ import { ILogService } from 'vs/platform/log/common/log'; + import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; + import { IActiveLanguagePackService } from 'vs/workbench/services/localization/common/locale'; + +-class RemoteExtensionsScannerService implements IRemoteExtensionsScannerService { ++export class RemoteExtensionsScannerService implements IRemoteExtensionsScannerService { + + declare readonly _serviceBrand: undefined; + diff --git a/src/vs/workbench/services/textMate/browser/backgroundTokenization/textMateWorkerTokenizerController.ts b/src/vs/workbench/services/textMate/browser/backgroundTokenization/textMateWorkerTokenizerController.ts index 850b58e1e6c..2eb835fa2b6 100644 --- a/src/vs/workbench/services/textMate/browser/backgroundTokenization/textMateWorkerTokenizerController.ts @@ -1843,6 +1856,19 @@ index ad67fb4f422..fadf86cece5 100644 // ensure to limit buffer for guessing due to https://github.com/aadsm/jschardet/issues/53 const limitedBuffer = buffer.slice(0, AUTO_ENCODING_GUESS_MAX_BYTES); +diff --git a/src/vs/workbench/services/userDataProfile/common/remoteUserDataProfiles.ts b/src/vs/workbench/services/userDataProfile/common/remoteUserDataProfiles.ts +index d3ef612836b..0d0fb8d2862 100644 +--- a/src/vs/workbench/services/userDataProfile/common/remoteUserDataProfiles.ts ++++ b/src/vs/workbench/services/userDataProfile/common/remoteUserDataProfiles.ts +@@ -25,7 +25,7 @@ export interface IRemoteUserDataProfilesService { + getRemoteProfile(localProfile: IUserDataProfile): Promise; + } + +-class RemoteUserDataProfilesService extends Disposable implements IRemoteUserDataProfilesService { ++export class RemoteUserDataProfilesService extends Disposable implements IRemoteUserDataProfilesService { + + readonly _serviceBrand: undefined; + diff --git a/src/vs/workbench/services/workspaces/browser/abstractWorkspaceEditingService.ts b/src/vs/workbench/services/workspaces/browser/abstractWorkspaceEditingService.ts index 4653e395be9..1eccfce4bf4 100644 --- a/src/vs/workbench/services/workspaces/browser/abstractWorkspaceEditingService.ts diff --git a/src/missing-services.ts b/src/missing-services.ts index c9c20826..32df6752 100644 --- a/src/missing-services.ts +++ b/src/missing-services.ts @@ -55,7 +55,7 @@ import { IEditSessionIdentityService } from 'vs/platform/workspace/common/editSe import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing' import { ITimerService } from 'vs/workbench/services/timer/browser/timerService' import { IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions' -import { EnablementState, IWorkbenchExtensionEnablementService, IWorkbenchExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement' +import { EnablementState, IExtensionManagementServerService, IWebExtensionsScannerService, IWorkbenchExtensionEnablementService, IWorkbenchExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement' import { ITunnelService } from 'vs/platform/tunnel/common/tunnel' import { IWorkingCopyBackupService } from 'vs/workbench/services/workingCopy/common/workingCopyBackup' import { IWorkingCopyService, WorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService' @@ -93,7 +93,7 @@ import { IBreadcrumbsService } from 'vs/workbench/browser/parts/editor/breadcrum import { IOutlineService } from 'vs/workbench/services/outline/browser/outline' import { IUpdateService, State } from 'vs/platform/update/common/update' import { IStatusbarService } from 'vs/workbench/services/statusbar/browser/statusbar' -import { IExtensionGalleryService, IExtensionManagementService, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement' +import { IExtensionGalleryService, IExtensionManagementService, IExtensionTipsService, IGlobalExtensionEnablementService, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement' import { IModelService } from 'vs/editor/common/services/model' import { IDetachedTerminalInstance, ITerminalEditorService, ITerminalGroupService, ITerminalInstance, ITerminalInstanceService, ITerminalService, TerminalConnectionState } from 'vs/workbench/contrib/terminal/browser/terminal' import { ITerminalProfileResolverService, ITerminalProfileService } from 'vs/workbench/contrib/terminal/common/terminal' @@ -104,7 +104,7 @@ import { IEnvironmentVariableService } from 'vs/workbench/contrib/terminal/commo import { ITerminalQuickFixService } from 'vs/workbench/contrib/terminalContrib/quickFix/browser/quickFix' import { IPreferencesSearchService } from 'vs/workbench/contrib/preferences/common/preferences' import { AccountStatus, IUserDataSyncWorkbenchService } from 'vs/workbench/services/userDataSync/common/userDataSync' -import { IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync' +import { IUserDataAutoSyncService, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync' import { IKeybindingEditingService } from 'vs/workbench/services/keybinding/common/keybindingEditing' import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService' import { ISearchHistoryService } from 'vs/workbench/contrib/search/common/searchHistoryService' @@ -128,7 +128,7 @@ import { IEditorDropService } from 'vs/workbench/services/editor/browser/editorD import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver' import { ExternalUriOpenerService, IExternalUriOpenerService } from 'vs/workbench/contrib/externalUriOpener/common/externalUriOpenerService' import { IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView' -import { IExtension, IRelaxedExtensionDescription } from 'vs/platform/extensions/common/extensions' +import { IBuiltinExtensionsScannerService, IExtension, IRelaxedExtensionDescription } from 'vs/platform/extensions/common/extensions' import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService' import { IRemoteExtensionsScannerService } from 'vs/platform/remote/common/remoteExtensionsScanner' import { BrowserURLService } from 'vs/workbench/services/url/browser/urlService' @@ -171,10 +171,17 @@ import { BrowserHostService } from 'vs/workbench/services/host/browser/browserHo import { IBannerService } from 'vs/workbench/services/banner/browser/bannerService' import { ITitleService } from 'vs/workbench/services/title/common/titleService' import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents' +import { IActiveLanguagePackService, ILocaleService } from 'vs/workbench/services/localization/common/locale' import { joinPath } from 'vs/base/common/resources' +import { IExtensionIgnoredRecommendationsService, IExtensionRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations' +import { IIgnoredExtensionsManagementService } from 'vs/platform/userDataSync/common/ignoredExtensions' +import { IExtensionRecommendationNotificationService } from 'vs/platform/extensionRecommendations/common/extensionRecommendations' +import { IWorkspaceExtensionsConfigService } from 'vs/workbench/services/extensionRecommendations/common/workspaceExtensionsConfig' +import { IRemoteUserDataProfilesService } from 'vs/workbench/services/userDataProfile/common/remoteUserDataProfiles' +import { IExtensionBisectService } from 'vs/workbench/services/extensionManagement/browser/extensionBisect' +import { IUserDataSyncAccountService } from 'vs/platform/userDataSync/common/userDataSyncAccount' import { unsupported } from './tools' import { getBuiltInExtensionTranslationsUris } from './l10n' - class NullLoggerService extends AbstractLoggerService { constructor () { super(LogLevel.Info, URI.file('logs.log')) @@ -435,6 +442,13 @@ registerSingleton(IProductService, class ProductService implements IProductServi serverApplicationName = 'code-server-oss' }, InstantiationType.Eager) +registerSingleton(IExtensionTipsService, class ExtensionTipsService implements IExtensionTipsService { + readonly _serviceBrand = undefined + getConfigBasedTips = async () => [] + getImportantExecutableBasedTips = async () => [] + getOtherExecutableBasedTips = async () => [] +}, InstantiationType.Eager) + registerSingleton(ILanguageStatusService, class LanguageStatusServiceImpl implements ILanguageStatusService { declare _serviceBrand: undefined @@ -939,6 +953,93 @@ registerSingleton(IExtensionsWorkbenchService, class ExtensionsWorkbenchService toggleExtensionIgnoredToSync = unsupported }, InstantiationType.Eager) +registerSingleton(IExtensionManagementServerService, class ExtensionManagementServerService implements IExtensionManagementServerService { + _serviceBrand = undefined + localExtensionManagementServer = null + remoteExtensionManagementServer = null + webExtensionManagementServer = null + + getExtensionManagementServer (_extension: IExtension) { + return null + } + + getExtensionInstallLocation (_extension: IExtension) { + return null + } +}, InstantiationType.Eager) + +registerSingleton(IExtensionRecommendationsService, class ExtensionRecommendationsService implements IExtensionRecommendationsService { + _serviceBrand: undefined + onDidChangeRecommendations = Event.None + getAllRecommendationsWithReason = () => ({}) + getImportantRecommendations = async () => [] + getOtherRecommendations = async () => [] + getFileBasedRecommendations = () => [] + getExeBasedRecommendations = async () => ({ important: [], others: [] }) + getConfigBasedRecommendations = async () => ({ important: [], others: [] }) + getWorkspaceRecommendations = async () => [] + getKeymapRecommendations = () => [] + getLanguageRecommendations = () => [] + getRemoteRecommendations = () => [] +}, InstantiationType.Eager) +registerSingleton(IUserDataAutoSyncService, class UserDataAutoSyncService implements IUserDataAutoSyncService { + _serviceBrand: undefined + readonly onError = Event.None + turnOn = unsupported + turnOff = unsupported + triggerSync = unsupported +}, InstantiationType.Eager) + +registerSingleton(IIgnoredExtensionsManagementService, class IgnoredExtensionsManagementService implements IIgnoredExtensionsManagementService { + _serviceBrand: undefined + getIgnoredExtensions = () => [] + hasToNeverSyncExtension = () => false + hasToAlwaysSyncExtension = () => false + updateIgnoredExtensions = unsupported + updateSynchronizedExtensions = unsupported +}, InstantiationType.Eager) + +registerSingleton(IExtensionRecommendationNotificationService, class ExtensionRecommendationNotificationService implements IExtensionRecommendationNotificationService { + _serviceBrand: undefined + readonly ignoredRecommendations: string[] = [] + hasToIgnoreRecommendationNotifications = () => false + promptImportantExtensionsInstallNotification = unsupported + promptWorkspaceRecommendations = unsupported +}, InstantiationType.Eager) + +registerSingleton(IWebExtensionsScannerService, class WebExtensionsScannerService implements IWebExtensionsScannerService { + _serviceBrand: undefined + scanSystemExtensions = async () => [] + scanUserExtensions = async () => [] + scanExtensionsUnderDevelopment = async () => [] + scanExistingExtension = async () => null + addExtension = unsupported + addExtensionFromGallery = unsupported + removeExtension = async () => {} + copyExtensions = async () => {} + updateMetadata = unsupported + scanExtensionManifest = async () => null +}, InstantiationType.Eager) + +registerSingleton(IExtensionIgnoredRecommendationsService, class ExtensionIgnoredRecommendationsService implements IExtensionIgnoredRecommendationsService { + _serviceBrand: undefined + onDidChangeIgnoredRecommendations = Event.None + ignoredRecommendations = [] + onDidChangeGlobalIgnoredRecommendation = Event.None + globalIgnoredRecommendations = [] + toggleGlobalIgnoredRecommendation = unsupported +}, InstantiationType.Eager) + +registerSingleton(IWorkspaceExtensionsConfigService, class WorkspaceExtensionsConfigService implements IWorkspaceExtensionsConfigService { + _serviceBrand: undefined + onDidChangeExtensionsConfigs = Event.None + getExtensionsConfigs = unsupported + getRecommendations = unsupported + getUnwantedRecommendations = unsupported + toggleRecommendation = unsupported + toggleUnwantedRecommendation = unsupported +}, InstantiationType.Eager) + registerSingleton(IWorkbenchExtensionEnablementService, class WorkbenchExtensionEnablementService implements IWorkbenchExtensionEnablementService { _serviceBrand: undefined onEnablementChanged = Event.None @@ -1094,6 +1195,13 @@ class SimpleExtensionResourceLoaderService extends AbstractExtensionResourceLoad } registerSingleton(IExtensionResourceLoaderService, SimpleExtensionResourceLoaderService, InstantiationType.Eager) +registerSingleton(IBuiltinExtensionsScannerService, class BuiltinExtensionsScannerService implements IBuiltinExtensionsScannerService { + _serviceBrand: undefined + scanBuiltinExtensions () { + return Promise.resolve([]) + } +}, InstantiationType.Eager) + registerSingleton(IHoverService, class HoverService implements IHoverService { showAndFocusLastHover = unsupported _serviceBrand: undefined @@ -1123,6 +1231,22 @@ registerSingleton(IExplorerService, class ExplorerService implements IExplorerSe registerSingleton(IExtensionStorageService, ExtensionStorageService, InstantiationType.Delayed) +registerSingleton(IGlobalExtensionEnablementService, class GlobalExtensionEnablementService implements IGlobalExtensionEnablementService { + _serviceBrand: undefined + onDidChangeEnablement = Event.None + getDisabledExtensions () { + return [] + } + + enableExtension () { + return Promise.resolve(true) + } + + disableExtension () { + return Promise.resolve(true) + } +}, InstantiationType.Delayed) + registerSingleton(ILanguagePackService, class LanguagePackService implements ILanguagePackService { _serviceBrand: undefined async getAvailableLanguages (): Promise { @@ -1689,6 +1813,15 @@ registerSingleton(IWebviewViewService, class WebviewService implements IWebviewV resolve = unsupported }, InstantiationType.Delayed) +registerSingleton(ILocaleService, class LocaleService implements ILocaleService { + _serviceBrand: undefined + setLocale = unsupported + + clearLocalePreference () { + return Promise.resolve() + } +}, InstantiationType.Delayed) + registerSingleton(IWebviewWorkbenchService, class WebviewWorkbenchService implements IWebviewWorkbenchService { _serviceBrand: undefined get iconManager () { @@ -1927,6 +2060,39 @@ registerSingleton(IInteractiveDocumentService, class InteractiveDocumentService willRemoveInteractiveDocument = unsupported }, InstantiationType.Delayed) +registerSingleton(IActiveLanguagePackService, class ActiveLanguagePackService implements IActiveLanguagePackService { + readonly _serviceBrand: undefined + getExtensionIdProvidingCurrentLocale () { + return Promise.resolve(undefined) + } +}, InstantiationType.Eager) + +registerSingleton(IRemoteUserDataProfilesService, class RemoteUserDataProfilesService implements IRemoteUserDataProfilesService { + _serviceBrand: undefined + getRemoteProfiles = async () => [] + getRemoteProfile = unsupported +}, InstantiationType.Eager) + +registerSingleton(IExtensionBisectService, class ExtensionBisectService implements IExtensionBisectService { + _serviceBrand: undefined + isDisabledByBisect = () => false + isActive = false + disabledCount = 0 + start = unsupported + next = unsupported + reset = unsupported +}, InstantiationType.Eager) +registerSingleton(IUserDataSyncAccountService, class UserDataSyncAccountService implements IUserDataSyncAccountService { + _serviceBrand: undefined + + readonly onTokenFailed = Event.None + readonly account = undefined + readonly onDidChangeAccount = Event.None + updateAccount (): Promise { + return Promise.resolve() + } +}, InstantiationType.Eager) + registerSingleton(IInlineChatService, class InlineChatService implements IInlineChatService { onDidChangeProviders = Event.None _serviceBrand: undefined diff --git a/src/override/vs/base/common/semver/semver.ts b/src/override/vs/base/common/semver/semver.ts index 44de371f..cb7ee45b 100644 --- a/src/override/vs/base/common/semver/semver.ts +++ b/src/override/vs/base/common/semver/semver.ts @@ -1,5 +1 @@ -export const gte = (): boolean => false -export const gt = (): boolean => false -export const lt = (): boolean => false -export const lte = (): boolean => false -export const valid = (): boolean => false +export { gte, gt, lt, lte, valid, eq, rcompare } from 'vscode-semver' diff --git a/src/server/server-main.ts b/src/server/server-main.ts new file mode 100644 index 00000000..7952ad43 --- /dev/null +++ b/src/server/server-main.ts @@ -0,0 +1,127 @@ +import './server-assets' +import type { IServerAPI } from 'vs/server/node/remoteExtensionHostAgentServer' +import { createServer } from 'vs/server/node/server.main.js' +import { buildHelpMessage, buildVersionMessage, parseArgs } from 'vs/platform/environment/node/argv' +import { ServerParsedArgs, serverOptions } from 'vs/server/node/serverEnvironmentService' +import product from 'vs/platform/product/common/product' +import { AddressInfo, ListenOptions, Socket } from 'net' +import http from 'http' + +export async function start (options: ListenOptions): Promise { + let _remoteExtensionHostAgentServer: IServerAPI | null = null + let _remoteExtensionHostAgentServerPromise: Promise | null = null + const getRemoteExtensionHostAgentServer = (): Promise => { + if (_remoteExtensionHostAgentServerPromise == null) { + _remoteExtensionHostAgentServerPromise = createServer(address).then(server => { + _remoteExtensionHostAgentServer = server + return server + }) + } + return _remoteExtensionHostAgentServerPromise + } + + let address: AddressInfo | string | null = null + const server = http.createServer(async (req, res) => { + const remoteExtensionHostAgentServer = await getRemoteExtensionHostAgentServer() + return remoteExtensionHostAgentServer.handleRequest(req, res) + }) + server.on('upgrade', async (req, socket) => { + const remoteExtensionHostAgentServer = await getRemoteExtensionHostAgentServer() + return remoteExtensionHostAgentServer.handleUpgrade(req, socket) + }) + server.on('error', async (err) => { + const remoteExtensionHostAgentServer = await getRemoteExtensionHostAgentServer() + return remoteExtensionHostAgentServer.handleServerError(err) + }) + + server.listen(options, async () => { + address = server.address() + if (address === null) { + throw new Error('Unexpected server address') + } + + // eslint-disable-next-line no-console + console.log(`Server bound to ${typeof address === 'string' ? address : `${address.address}:${address.port} (${address.family})`}`) + + await getRemoteExtensionHostAgentServer() + }) + + process.on('exit', () => { + server.close() + if (_remoteExtensionHostAgentServer != null) { + _remoteExtensionHostAgentServer.dispose() + } + }) +} + +function parseRange (strRange: string) { + const match = strRange.match(/^(\d+)-(\d+)$/) + if (match != null) { + const start = parseInt(match[1]!, 10); const end = parseInt(match[2]!, 10) + if (start > 0 && start <= end && end <= 65535) { + return { start, end } + } + } + return undefined +} + +async function findFreePort (host: string, start: number, end: number) { + const testPort = (port: number) => { + return new Promise((resolve) => { + const server = http.createServer() + server.listen(port, host, () => { + server.close() + resolve(true) + }).on('error', () => { + resolve(false) + }) + }) + } + for (let port = start; port <= end; port++) { + if (await testPort(port)) { + return port + } + } + return undefined +} + +async function parsePort (host: string, strPort: string | undefined) { + if (strPort != null) { + let range + if (strPort.match(/^\d+$/) != null) { + return parseInt(strPort, 10) + } else if ((range = parseRange(strPort)) != null) { + const port = await findFreePort(host, range.start, range.end) + if (port !== undefined) { + return port + } + // Remote-SSH extension relies on this exact port error message, treat as an API + console.warn(`--port: Could not find free port in range: ${range.start} - ${range.end} (inclusive).`) + process.exit(1) + } else { + console.warn(`--port "${strPort}" is not a valid number or range. Ranges must be in the form 'from-to' with 'from' an integer larger than 0 and not larger than 'end'.`) + process.exit(1) + } + } + return 8000 +} + +const parsedArgs = parseArgs(process.argv.slice(2), serverOptions) +if (parsedArgs.help) { + const serverOptionsWithoutExtensionManagement = Object.fromEntries(Object.entries(serverOptions).filter(([, def]) => def.cat !== 'e')) + // eslint-disable-next-line no-console + console.log(buildHelpMessage(product.nameLong, 'vscode-ext-host-server', product.version, serverOptionsWithoutExtensionManagement, { noInputFiles: true, noPipe: true })) +} else if (parsedArgs.version) { + // eslint-disable-next-line no-console + console.log(buildVersionMessage(product.version, product.commit)) +} else { + const host = parsedArgs.host ?? 'localhost' + + const nodeListenOptions = ( + parsedArgs['socket-path'] != null + ? { path: parsedArgs['socket-path'] } + : { host, port: await parsePort(host, parsedArgs.port) } + ) + + void start(nodeListenOptions) +} diff --git a/src/server/server.ts b/src/server/server.ts index 7952ad43..dfbec4fc 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -1,127 +1,30 @@ -import './server-assets' -import type { IServerAPI } from 'vs/server/node/remoteExtensionHostAgentServer' -import { createServer } from 'vs/server/node/server.main.js' -import { buildHelpMessage, buildVersionMessage, parseArgs } from 'vs/platform/environment/node/argv' -import { ServerParsedArgs, serverOptions } from 'vs/server/node/serverEnvironmentService' -import product from 'vs/platform/product/common/product' -import { AddressInfo, ListenOptions, Socket } from 'net' -import http from 'http' +import { IProductService } from 'vs/platform/product/common/productService' +import { readFile } from 'fs/promises' -export async function start (options: ListenOptions): Promise { - let _remoteExtensionHostAgentServer: IServerAPI | null = null - let _remoteExtensionHostAgentServerPromise: Promise | null = null - const getRemoteExtensionHostAgentServer = (): Promise => { - if (_remoteExtensionHostAgentServerPromise == null) { - _remoteExtensionHostAgentServerPromise = createServer(address).then(server => { - _remoteExtensionHostAgentServer = server - return server - }) - } - return _remoteExtensionHostAgentServerPromise - } - - let address: AddressInfo | string | null = null - const server = http.createServer(async (req, res) => { - const remoteExtensionHostAgentServer = await getRemoteExtensionHostAgentServer() - return remoteExtensionHostAgentServer.handleRequest(req, res) - }) - server.on('upgrade', async (req, socket) => { - const remoteExtensionHostAgentServer = await getRemoteExtensionHostAgentServer() - return remoteExtensionHostAgentServer.handleUpgrade(req, socket) - }) - server.on('error', async (err) => { - const remoteExtensionHostAgentServer = await getRemoteExtensionHostAgentServer() - return remoteExtensionHostAgentServer.handleServerError(err) - }) - - server.listen(options, async () => { - address = server.address() - if (address === null) { - throw new Error('Unexpected server address') - } - - // eslint-disable-next-line no-console - console.log(`Server bound to ${typeof address === 'string' ? address : `${address.address}:${address.port} (${address.family})`}`) - - await getRemoteExtensionHostAgentServer() - }) - - process.on('exit', () => { - server.close() - if (_remoteExtensionHostAgentServer != null) { - _remoteExtensionHostAgentServer.dispose() - } - }) +const thisWithVSCodeParams = globalThis as typeof globalThis & { + _VSCODE_PRODUCT_JSON: Partial + _VSCODE_PACKAGE_JSON: { version: string } } -function parseRange (strRange: string) { - const match = strRange.match(/^(\d+)-(\d+)$/) - if (match != null) { - const start = parseInt(match[1]!, 10); const end = parseInt(match[2]!, 10) - if (start > 0 && start <= end && end <= 65535) { - return { start, end } - } +// Initialize the product information for the server, including the extension gallery URL. +thisWithVSCodeParams._VSCODE_PRODUCT_JSON = { + extensionsGallery: { + serviceUrl: 'https://open-vsx.org/vscode/gallery', + itemUrl: 'https://open-vsx.org/vscode/item', + resourceUrlTemplate: 'https://open-vsx.org/vscode/unpkg/{publisher}/{name}/{version}/{path}', + controlUrl: '', + nlsBaseUrl: '', + publisherUrl: '' } - return undefined } - -async function findFreePort (host: string, start: number, end: number) { - const testPort = (port: number) => { - return new Promise((resolve) => { - const server = http.createServer() - server.listen(port, host, () => { - server.close() - resolve(true) - }).on('error', () => { - resolve(false) - }) - }) - } - for (let port = start; port <= end; port++) { - if (await testPort(port)) { - return port - } - } - return undefined +thisWithVSCodeParams._VSCODE_PACKAGE_JSON = { + version: VSCODE_VERSION } -async function parsePort (host: string, strPort: string | undefined) { - if (strPort != null) { - let range - if (strPort.match(/^\d+$/) != null) { - return parseInt(strPort, 10) - } else if ((range = parseRange(strPort)) != null) { - const port = await findFreePort(host, range.start, range.end) - if (port !== undefined) { - return port - } - // Remote-SSH extension relies on this exact port error message, treat as an API - console.warn(`--port: Could not find free port in range: ${range.start} - ${range.end} (inclusive).`) - process.exit(1) - } else { - console.warn(`--port "${strPort}" is not a valid number or range. Ranges must be in the form 'from-to' with 'from' an integer larger than 0 and not larger than 'end'.`) - process.exit(1) - } - } - return 8000 +const PRODUCT_JSON_PATH = process.env.PRODUCT_JSON_PATH +if (PRODUCT_JSON_PATH != null) { + const productJson = await readFile(PRODUCT_JSON_PATH, { encoding: 'utf8' }) + Object.assign(thisWithVSCodeParams._VSCODE_PRODUCT_JSON, JSON.parse(productJson)) } -const parsedArgs = parseArgs(process.argv.slice(2), serverOptions) -if (parsedArgs.help) { - const serverOptionsWithoutExtensionManagement = Object.fromEntries(Object.entries(serverOptions).filter(([, def]) => def.cat !== 'e')) - // eslint-disable-next-line no-console - console.log(buildHelpMessage(product.nameLong, 'vscode-ext-host-server', product.version, serverOptionsWithoutExtensionManagement, { noInputFiles: true, noPipe: true })) -} else if (parsedArgs.version) { - // eslint-disable-next-line no-console - console.log(buildVersionMessage(product.version, product.commit)) -} else { - const host = parsedArgs.host ?? 'localhost' - - const nodeListenOptions = ( - parsedArgs['socket-path'] != null - ? { path: parsedArgs['socket-path'] } - : { host, port: await parsePort(host, parsedArgs.port) } - ) - - void start(nodeListenOptions) -} +import('./server-main') diff --git a/src/service-override/extensionGallery.ts b/src/service-override/extensionGallery.ts new file mode 100644 index 00000000..e953eb35 --- /dev/null +++ b/src/service-override/extensionGallery.ts @@ -0,0 +1,52 @@ +import { IEditorOverrideServices } from 'vs/editor/standalone/browser/standaloneServices' +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors' +import { IExtensionGalleryService, IExtensionManagementService, IExtensionTipsService, IGlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement' +import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionGalleryService' +import { GlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionEnablementService' +import { IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions' +import { ExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/browser/extensionsWorkbenchService' +import { IExtensionManagementServerService, IWebExtensionsScannerService, IWorkbenchExtensionEnablementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement' +import { ExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/common/extensionManagementServerService' +import { IExtensionIgnoredRecommendationsService, IExtensionRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations' +import { ExtensionRecommendationsService } from 'vs/workbench/contrib/extensions/browser/extensionRecommendationsService' +import { WebExtensionsScannerService } from 'vs/workbench/services/extensionManagement/browser/webExtensionsScannerService' +import { IExtensionRecommendationNotificationService } from 'vs/platform/extensionRecommendations/common/extensionRecommendations' +import { IIgnoredExtensionsManagementService, IgnoredExtensionsManagementService } from 'vs/platform/userDataSync/common/ignoredExtensions' +import { ExtensionManifestPropertiesService, IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService' +import { IBuiltinExtensionsScannerService } from 'vs/platform/extensions/common/extensions' +import { BuiltinExtensionsScannerService } from 'vs/workbench/services/extensionManagement/browser/builtinExtensionsScannerService' +import { ExtensionIgnoredRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionIgnoredRecommendationsService' +import { IWorkspaceExtensionsConfigService, WorkspaceExtensionsConfigService } from 'vs/workbench/services/extensionRecommendations/common/workspaceExtensionsConfig' +import { IRemoteExtensionsScannerService } from 'vs/platform/remote/common/remoteExtensionsScanner' +import { RemoteExtensionsScannerService } from 'vs/workbench/services/remote/common/remoteExtensionsScanner' +import { ExtensionRecommendationNotificationService } from 'vs/workbench/contrib/extensions/browser/extensionRecommendationNotificationService' +import { ExtensionTipsService } from 'vs/platform/extensionManagement/common/extensionTipsService' +import { ExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagementService' +import 'vs/workbench/contrib/extensions/browser/extensions.contribution' +import 'vs/workbench/contrib/extensions/browser/extensions.web.contribution' +import 'vs/workbench/contrib/logs/common/logs.contribution' +import { IRemoteUserDataProfilesService, RemoteUserDataProfilesService } from 'vs/workbench/services/userDataProfile/common/remoteUserDataProfiles' +import { ExtensionEnablementService } from 'vs/workbench/services/extensionManagement/browser/extensionEnablementService' +import 'vs/workbench/services/extensionManagement/browser/extensionBisect' + +export default function getServiceOverride (): IEditorOverrideServices { + return { + [IExtensionGalleryService.toString()]: new SyncDescriptor(ExtensionGalleryService, [], true), + [IGlobalExtensionEnablementService.toString()]: new SyncDescriptor(GlobalExtensionEnablementService, [], true), + [IExtensionsWorkbenchService.toString()]: new SyncDescriptor(ExtensionsWorkbenchService, [], true), + [IExtensionManagementServerService.toString()]: new SyncDescriptor(ExtensionManagementServerService, [], true), + [IExtensionRecommendationsService.toString()]: new SyncDescriptor(ExtensionRecommendationsService, [], true), + [IExtensionRecommendationNotificationService.toString()]: new SyncDescriptor(ExtensionRecommendationNotificationService, [], true), + [IWebExtensionsScannerService.toString()]: new SyncDescriptor(WebExtensionsScannerService, [], true), + [IExtensionIgnoredRecommendationsService.toString()]: new SyncDescriptor(ExtensionIgnoredRecommendationsService, [], true), + [IIgnoredExtensionsManagementService.toString()]: new SyncDescriptor(IgnoredExtensionsManagementService, [], true), + [IExtensionManifestPropertiesService.toString()]: new SyncDescriptor(ExtensionManifestPropertiesService, [], true), + [IExtensionManagementService.toString()]: new SyncDescriptor(ExtensionManagementService, [], true), + [IBuiltinExtensionsScannerService.toString()]: new SyncDescriptor(BuiltinExtensionsScannerService, [], true), + [IWorkspaceExtensionsConfigService.toString()]: new SyncDescriptor(WorkspaceExtensionsConfigService, [], true), + [IRemoteExtensionsScannerService.toString()]: new SyncDescriptor(RemoteExtensionsScannerService, [], true), + [IExtensionTipsService.toString()]: new SyncDescriptor(ExtensionTipsService, [], true), + [IRemoteUserDataProfilesService.toString()]: new SyncDescriptor(RemoteUserDataProfilesService, [], true), + [IWorkbenchExtensionEnablementService.toString()]: new SyncDescriptor(ExtensionEnablementService, [], true) + } +} From d3b5b12857394c1ec6ecef351af0d57df9e91112 Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Thu, 16 Nov 2023 23:26:26 +0100 Subject: [PATCH 02/17] chore: add docs --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index cecb3f6b..ac953ce3 100644 --- a/README.md +++ b/README.md @@ -189,6 +189,8 @@ Additionally, 25 packages that include the vscode version of some services (with - Register accessibility helpers - **Workspace trust**: `@codingame/monaco-vscode-workspace-trust-service-override` - Ask user it they trust the current workspace, disable some features if not +- **Extension Gallery**: `@codingame/monaco-vscode-extension-gallery-service-override` + - Support for the VSCode marketplace, it allows to install extensions from the marketplace. Usage: ```typescript From 444a80c8bbeabdea6b28d71870e34ccdab22a460 Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Fri, 17 Nov 2023 15:18:50 +0100 Subject: [PATCH 03/17] feat: support for discovering extensions on boot --- src/server/server.ts | 12 ++++- src/service-override/extensions.ts | 86 ++++++++++++++++++++++++++++-- 2 files changed, 92 insertions(+), 6 deletions(-) diff --git a/src/server/server.ts b/src/server/server.ts index dfbec4fc..d8737cc2 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -1,5 +1,7 @@ import { IProductService } from 'vs/platform/product/common/productService' -import { readFile } from 'fs/promises' +import * as fs from 'fs/promises' +import * as path from 'path' +import { fileURLToPath } from 'url' const thisWithVSCodeParams = globalThis as typeof globalThis & { _VSCODE_PRODUCT_JSON: Partial @@ -23,8 +25,14 @@ thisWithVSCodeParams._VSCODE_PACKAGE_JSON = { const PRODUCT_JSON_PATH = process.env.PRODUCT_JSON_PATH if (PRODUCT_JSON_PATH != null) { - const productJson = await readFile(PRODUCT_JSON_PATH, { encoding: 'utf8' }) + const productJson = await fs.readFile(PRODUCT_JSON_PATH, { encoding: 'utf8' }) Object.assign(thisWithVSCodeParams._VSCODE_PRODUCT_JSON, JSON.parse(productJson)) } +// Create a directory for system extensions to be installed in, VSCode +// will try to read this folder to find system extensions, and will +// error if it does not exist. +const currentDirPath = fileURLToPath(import.meta.url) +await fs.mkdir(path.join(currentDirPath, 'extensions'), { recursive: true }) + import('./server-main') diff --git a/src/service-override/extensions.ts b/src/service-override/extensions.ts index 44f6087a..917df534 100644 --- a/src/service-override/extensions.ts +++ b/src/service-override/extensions.ts @@ -10,10 +10,10 @@ import { AbstractExtensionService, DeltaExtensionsQueueItem, ResolvedExtensions import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry' import { Event } from 'vs/base/common/event' import { IDialogService } from 'vs/platform/dialogs/common/dialogs' -import { IRemoteAuthorityResolverService, ResolverResult } from 'vs/platform/remote/common/remoteAuthorityResolver' +import { IRemoteAuthorityResolverService, RemoteAuthorityResolverError, ResolverResult } from 'vs/platform/remote/common/remoteAuthorityResolver' import { IRemoteExtensionsScannerService } from 'vs/platform/remote/common/remoteExtensionsScanner' import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService' -import { IWorkbenchExtensionEnablementService, IWorkbenchExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement' +import { IWebExtensionsScannerService, IWorkbenchExtensionEnablementService, IWorkbenchExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement' import { ExtensionManifestPropertiesService, IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService' import { IConfigurationService } from 'vs/platform/configuration/common/configuration' import { IProductService } from 'vs/platform/product/common/productService' @@ -28,6 +28,7 @@ import { Schemas } from 'vs/base/common/network' import { ExtensionHostKind, ExtensionRunningPreference } from 'vs/workbench/services/extensions/common/extensionHostKind' import { ExtensionRunningLocation, LocalProcessRunningLocation, LocalWebWorkerRunningLocation } from 'vs/workbench/services/extensions/common/extensionRunningLocation' import { ExtensionRunningLocationTracker } from 'vs/workbench/services/extensions/common/extensionRunningLocationTracker' +import { dedupExtensions } from 'vs/workbench/services/extensions/common/extensionsUtil' import { IExtHostExtensionService, IHostUtils } from 'vs/workbench/api/common/extHostExtensionService' import { ExtHostExtensionService } from 'vs/workbench/api/worker/extHostExtensionService' import type * as vscode from 'vscode' @@ -49,7 +50,11 @@ import { ILayoutService } from 'vs/platform/layout/browser/layoutService' import { IStorageService } from 'vs/platform/storage/common/storage' import { ILabelService } from 'vs/platform/label/common/label' import { ExtensionKind } from 'vs/platform/environment/common/environment' +import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile' +import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust' +import { IRemoteExplorerService } from 'vs/workbench/services/remote/common/remoteExplorerService' import { ExtensionDescriptionRegistrySnapshot } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry' +import { PersistentConnectionEventType } from 'vs/platform/remote/common/remoteAgentConnection' import { changeUrlDomain } from './tools/url' import { registerAssets } from '../assets' import { unsupported } from '../tools' @@ -380,7 +385,12 @@ export class SimpleExtensionService extends AbstractExtensionService implements @ILifecycleService lifecycleService: ILifecycleService, @IRemoteAuthorityResolverService remoteAuthorityResolverService: IRemoteAuthorityResolverService, @IUserDataInitializationService userDataInitializationService: IUserDataInitializationService, - @IDialogService dialogService: IDialogService + @IDialogService dialogService: IDialogService, + @IWebExtensionsScannerService private readonly _webExtensionsScannerService: IWebExtensionsScannerService, + @IUserDataProfileService private readonly _userDataProfileService: IUserDataProfileService, + @IBrowserWorkbenchEnvironmentService private readonly _browserEnvironmentService: IBrowserWorkbenchEnvironmentService, + @IWorkspaceTrustManagementService private readonly _workspaceTrustManagementService: IWorkspaceTrustManagementService, + @IRemoteExplorerService private readonly _remoteExplorerService: IRemoteExplorerService ) { const extensionsProposedApi = instantiationService.createInstance(ExtensionsProposedApi) const extensionHostFactory = new LocalBrowserExtensionHostFactory( @@ -446,8 +456,76 @@ export class SimpleExtensionService extends AbstractExtensionService implements await this._handleDeltaExtensions(new DeltaExtensionsQueueItem(toAdd, toRemove)) } + private async _scanWebExtensions (): Promise { + const system: IExtensionDescription[] = [] + const user: IExtensionDescription[] = [] + const development: IExtensionDescription[] = [] + try { + await Promise.all([ + this._webExtensionsScannerService.scanSystemExtensions().then(extensions => system.push(...extensions.map(e => toExtensionDescription(e)))), + this._webExtensionsScannerService.scanUserExtensions(this._userDataProfileService.currentProfile.extensionsResource, { skipInvalidExtensions: true }).then(extensions => user.push(...extensions.map(e => toExtensionDescription(e)))), + this._webExtensionsScannerService.scanExtensionsUnderDevelopment().then(extensions => development.push(...extensions.map(e => toExtensionDescription(e, true)))) + ]) + } catch (error) { + if (error instanceof Error) { + this._logService.error(error) + } + } + return dedupExtensions(system, user, development, this._logService) + } + + protected async _resolveExtensionsDefault (): Promise { + const [localExtensions, remoteExtensions] = await Promise.all([ + this._scanWebExtensions(), + this._remoteExtensionsScannerService.scanExtensions() + ]) + + return new ResolvedExtensions(localExtensions, remoteExtensions, true, true) + } + protected override async _resolveExtensions (): Promise { - return new ResolvedExtensions([], [], false, false) + // Taken from vs/workbench/services/extensions/browser/extensionService + if (!this._browserEnvironmentService.expectsResolverExtension) { + return this._resolveExtensionsDefault() + } + + const remoteAuthority = this._environmentService.remoteAuthority! + + // Now that the canonical URI provider has been registered, we need to wait for the trust state to be + // calculated. The trust state will be used while resolving the authority, however the resolver can + // override the trust state through the resolver result. + await this._workspaceTrustManagementService.workspaceResolved + + let resolverResult: ResolverResult + try { + resolverResult = await this._resolveAuthorityInitial(remoteAuthority) + } catch (err) { + if (RemoteAuthorityResolverError.isHandled(err)) { + // eslint-disable-next-line no-console + console.log('Error handled: Not showing a notification for the error') + } + this._remoteAuthorityResolverService._setResolvedAuthorityError(remoteAuthority, err) + + // Proceed with the local extension host + return this._resolveExtensionsDefault() + } + + // set the resolved authority + this._remoteAuthorityResolverService._setResolvedAuthority(resolverResult.authority, resolverResult.options) + this._remoteExplorerService.setTunnelInformation(resolverResult.tunnelInformation) + + // monitor for breakage + const connection = this._remoteAgentService.getConnection() + if (connection != null) { + connection.onDidStateChange(async (e) => { + if (e.type === PersistentConnectionEventType.ConnectionLost) { + this._remoteAuthorityResolverService._clearResolvedAuthority(remoteAuthority) + } + }) + connection.onReconnecting(() => this._resolveAuthorityAgain()) + } + + return this._resolveExtensionsDefault() } protected override async _scanSingleExtension (extension: IExtension): Promise | null> { From b5d29cc9a9cd1dfcd583c403ff5cbb532bb9cf5e Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Fri, 17 Nov 2023 15:19:02 +0100 Subject: [PATCH 04/17] fix: change corp settings to support loading images --- demo/vite.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/vite.config.ts b/demo/vite.config.ts index 0e1d88b1..30d0d89e 100644 --- a/demo/vite.config.ts +++ b/demo/vite.config.ts @@ -19,7 +19,7 @@ export default defineConfig({ apply: 'serve', configureServer: server => { server.middlewares.use((_req, res, next) => { - res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp') + res.setHeader('Cross-Origin-Embedder-Policy', 'credentialless') res.setHeader('Cross-Origin-Opener-Policy', 'same-origin') res.setHeader('Cross-Origin-Resource-Policy', 'cross-origin') next() From d44a58c1bf526492057e2b1b9e7256b7e098d9ff Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Fri, 17 Nov 2023 15:59:13 +0100 Subject: [PATCH 05/17] feat: allow for web-only extensions with an override --- demo/src/setup.ts | 2 +- src/service-override/extensionGallery.ts | 35 ++++++++++++++++++++++-- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/demo/src/setup.ts b/demo/src/setup.ts index 81ba1c07..203ca9d9 100644 --- a/demo/src/setup.ts +++ b/demo/src/setup.ts @@ -92,7 +92,7 @@ await Promise.all([ await initializeMonacoService({ ...getLogServiceOverride(), ...getExtensionServiceOverride(toWorkerConfig(ExtensionHostWorker)), - ...getExtensionGalleryServiceOverride(), + ...getExtensionGalleryServiceOverride({ webOnly: false }), ...getModelServiceOverride(), ...getNotificationServiceOverride(), ...getDialogsServiceOverride(), diff --git a/src/service-override/extensionGallery.ts b/src/service-override/extensionGallery.ts index e953eb35..0d8b37c2 100644 --- a/src/service-override/extensionGallery.ts +++ b/src/service-override/extensionGallery.ts @@ -28,13 +28,44 @@ import 'vs/workbench/contrib/logs/common/logs.contribution' import { IRemoteUserDataProfilesService, RemoteUserDataProfilesService } from 'vs/workbench/services/userDataProfile/common/remoteUserDataProfiles' import { ExtensionEnablementService } from 'vs/workbench/services/extensionManagement/browser/extensionEnablementService' import 'vs/workbench/services/extensionManagement/browser/extensionBisect' +import { IInstantiationService, ILabelService, IRemoteAgentService } from '../services' -export default function getServiceOverride (): IEditorOverrideServices { +class ExtensionManagementServerServiceOverride extends ExtensionManagementServerService { + constructor ( + private isWebOnly: boolean, + @IRemoteAgentService readonly remoteAgentService: IRemoteAgentService, + @ILabelService readonly labelService: ILabelService, + @IInstantiationService readonly instantiationService: IInstantiationService + ) { + super(remoteAgentService, labelService, instantiationService) + + if (this.isWebOnly) { + /** + * If `isWebOnly` is set to true, we explicitly set the remote extension management server to `null`, even if + * we're connected to a remote server. + */ + // Cannot override read-only property, but this is the only way we can override it to be null, + // overriding the field doesn't work and setting a getter is not allowed. + // @ts-ignore + this.remoteExtensionManagementServer = null + } + } +} + +export interface ExtensionGalleryOptions { + /** + * Whether we should only allow for web extensions to be installed, this is generally + * true if there is no server part. + */ + webOnly: boolean +} + +export default function getServiceOverride (options: ExtensionGalleryOptions = { webOnly: false }): IEditorOverrideServices { return { [IExtensionGalleryService.toString()]: new SyncDescriptor(ExtensionGalleryService, [], true), [IGlobalExtensionEnablementService.toString()]: new SyncDescriptor(GlobalExtensionEnablementService, [], true), [IExtensionsWorkbenchService.toString()]: new SyncDescriptor(ExtensionsWorkbenchService, [], true), - [IExtensionManagementServerService.toString()]: new SyncDescriptor(ExtensionManagementServerService, [], true), + [IExtensionManagementServerService.toString()]: new SyncDescriptor(ExtensionManagementServerServiceOverride, [options.webOnly], true), [IExtensionRecommendationsService.toString()]: new SyncDescriptor(ExtensionRecommendationsService, [], true), [IExtensionRecommendationNotificationService.toString()]: new SyncDescriptor(ExtensionRecommendationNotificationService, [], true), [IWebExtensionsScannerService.toString()]: new SyncDescriptor(WebExtensionsScannerService, [], true), From 3f67588fa8c301550c6dbccf69c9c688c5fe50b3 Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Fri, 17 Nov 2023 16:04:44 +0100 Subject: [PATCH 06/17] feat: use ExtensionResourceLoaderService from vscode --- scripts/vscode.patch | 13 +++++++++++++ src/missing-services.ts | 25 +++---------------------- 2 files changed, 16 insertions(+), 22 deletions(-) diff --git a/scripts/vscode.patch b/scripts/vscode.patch index 84e84def..e1e2fc4c 100644 --- a/scripts/vscode.patch +++ b/scripts/vscode.patch @@ -542,6 +542,19 @@ index d8cefb6df67..a94fca911ea 100644 import { IProcessEnvironment } from 'vs/base/common/platform'; import { localize } from 'vs/nls'; import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; +diff --git a/src/vs/platform/extensionResourceLoader/browser/extensionResourceLoaderService.ts b/src/vs/platform/extensionResourceLoader/browser/extensionResourceLoaderService.ts +index c766318f30f..58c3000cbd3 100644 +--- a/src/vs/platform/extensionResourceLoader/browser/extensionResourceLoaderService.ts ++++ b/src/vs/platform/extensionResourceLoader/browser/extensionResourceLoaderService.ts +@@ -14,7 +14,7 @@ import { ILogService } from 'vs/platform/log/common/log'; + import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; + import { AbstractExtensionResourceLoaderService, IExtensionResourceLoaderService } from 'vs/platform/extensionResourceLoader/common/extensionResourceLoader'; + +-class ExtensionResourceLoaderService extends AbstractExtensionResourceLoaderService { ++export class ExtensionResourceLoaderService extends AbstractExtensionResourceLoaderService { + + declare readonly _serviceBrand: undefined; + diff --git a/src/vs/platform/files/node/diskFileSystemProvider.ts b/src/vs/platform/files/node/diskFileSystemProvider.ts index e22cfe1b45e..b48c1babf86 100644 --- a/src/vs/platform/files/node/diskFileSystemProvider.ts diff --git a/src/missing-services.ts b/src/missing-services.ts index 32df6752..d8b6e233 100644 --- a/src/missing-services.ts +++ b/src/missing-services.ts @@ -78,9 +78,8 @@ import { IEditorResolverService } from 'vs/workbench/services/editor/common/edit import { AbstractLifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycleService' import { IOutputChannel, IOutputChannelDescriptor, IOutputService } from 'vs/workbench/services/output/common/output' import { IOutputChannelModelService, OutputChannelModelService } from 'vs/workbench/contrib/output/common/outputChannelModelService' -import { AbstractExtensionResourceLoaderService, IExtensionResourceLoaderService } from 'vs/platform/extensionResourceLoader/common/extensionResourceLoader' -import { IStorageService } from 'vs/platform/storage/common/storage' -import { IConfigurationService } from 'vs/platform/configuration/common/configuration' +import { IExtensionResourceLoaderService } from 'vs/platform/extensionResourceLoader/common/extensionResourceLoader' +import { ExtensionResourceLoaderService } from 'vs/platform/extensionResourceLoader/browser/extensionResourceLoaderService' import { IHoverService } from 'vs/workbench/services/hover/browser/hover' import { IExplorerService } from 'vs/workbench/contrib/files/browser/files' import { ExtensionStorageService, IExtensionStorageService } from 'vs/platform/extensionManagement/common/extensionStorage' @@ -1175,25 +1174,7 @@ registerSingleton(IOutputService, class OutputService implements IOutputService }, InstantiationType.Delayed) registerSingleton(IOutputChannelModelService, OutputChannelModelService, InstantiationType.Delayed) -class SimpleExtensionResourceLoaderService extends AbstractExtensionResourceLoaderService { - // required for injection - // eslint-disable-next-line @typescript-eslint/no-useless-constructor - constructor ( - @IFileService fileService: IFileService, - @IStorageService storageService: IStorageService, - @IProductService productService: IProductService, - @IEnvironmentService environmentService: IEnvironmentService, - @IConfigurationService configurationService: IConfigurationService - ) { - super(fileService, storageService, productService, environmentService, configurationService) - } - - async readExtensionResource (uri: URI): Promise { - const result = await this._fileService.readFile(uri) - return result.value.toString() - } -} -registerSingleton(IExtensionResourceLoaderService, SimpleExtensionResourceLoaderService, InstantiationType.Eager) +registerSingleton(IExtensionResourceLoaderService, ExtensionResourceLoaderService, InstantiationType.Eager) registerSingleton(IBuiltinExtensionsScannerService, class BuiltinExtensionsScannerService implements IBuiltinExtensionsScannerService { _serviceBrand: undefined From f7ed4a4222aa104ac06710e0d9ba8dbab981d6b5 Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Mon, 20 Nov 2023 16:00:35 +0100 Subject: [PATCH 07/17] fix: rename 'extension' fs to 'extension-fs' The gallery uses 'extension' scheme for its extension editor inputs --- scripts/vscode.patch | 2 +- src/extensions.ts | 2 +- src/service-override/files.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/vscode.patch b/scripts/vscode.patch index e1e2fc4c..e07cd46b 100644 --- a/scripts/vscode.patch +++ b/scripts/vscode.patch @@ -1065,7 +1065,7 @@ index 6e6f8845da5..48462781c2a 100644 + private notFound = false; + override open(method: string, url: string | URL, async?: boolean, username?: string | null, password?: string | null): void { + const transformedUrl = asWorkerBrowserUrl(url); -+ this.notFound = transformedUrl.startsWith('extension:'); ++ this.notFound = transformedUrl.startsWith('extension-fs:'); + return super.open(method, transformedUrl, async ?? true, username, password); + } + override send(body?: Document | XMLHttpRequestBodyInit | null | undefined): void { diff --git a/src/extensions.ts b/src/extensions.ts index eb8e3310..bd1594da 100644 --- a/src/extensions.ts +++ b/src/extensions.ts @@ -99,7 +99,7 @@ export function registerExtension (manifest: IExtensionManifest, extHostKind?: E export function registerExtension (manifest: IExtensionManifest, extHostKind?: ExtensionHostKind, { builtin = manifest.publisher === 'vscode', path = '/' }: RegisterExtensionParams = {}): RegisterExtensionResult { const disposableStore = new DisposableStore() const id = getExtensionId(manifest.publisher, manifest.name) - const location = URI.from({ scheme: 'extension', authority: id, path }) + const location = URI.from({ scheme: 'extension-fs', authority: id, path }) const addExtensionPromise = (async () => { const remoteAuthority = (await getService(IWorkbenchEnvironmentService)).remoteAuthority diff --git a/src/service-override/files.ts b/src/service-override/files.ts index 7c341f9b..80665336 100644 --- a/src/service-override/files.ts +++ b/src/service-override/files.ts @@ -517,7 +517,7 @@ const userDataFileSystemProvider = new InMemoryFileSystemProvider() void userDataFileSystemProvider.mkdir(URI.from({ scheme: Schemas.vscodeUserData, path: '/User/' })) const providers: Record = { - extension: extensionFileSystemProvider, + 'extension-fs': extensionFileSystemProvider, [logsPath.scheme]: new InMemoryFileSystemProvider(), [Schemas.vscodeUserData]: userDataFileSystemProvider, [Schemas.tmp]: new InMemoryFileSystemProvider(), From bfa7e675a930f7caf33997f3fcbbebc847ecb5d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Mangeonjean?= Date: Tue, 21 Nov 2023 11:45:20 +0100 Subject: [PATCH 08/17] refactor: do not duplicate VSCode code --- scripts/vscode.patch | 125 ++++++++++++++++++++++++++- src/extensions.ts | 4 +- src/service-override/extensions.ts | 134 +++++------------------------ 3 files changed, 144 insertions(+), 119 deletions(-) diff --git a/scripts/vscode.patch b/scripts/vscode.patch index e07cd46b..246728d9 100644 --- a/scripts/vscode.patch +++ b/scripts/vscode.patch @@ -968,7 +968,7 @@ index b83a462131d..5555f431ba0 100644 performance.mark('code/extHost/didInitAPI'); diff --git a/src/vs/workbench/api/worker/extensionHostWorker.ts b/src/vs/workbench/api/worker/extensionHostWorker.ts -index 6e6f8845da5..48462781c2a 100644 +index 6e6f8845da5..80a3a9f0095 100644 --- a/src/vs/workbench/api/worker/extensionHostWorker.ts +++ b/src/vs/workbench/api/worker/extensionHostWorker.ts @@ -15,7 +15,6 @@ import * as performance from 'vs/base/common/performance'; @@ -1455,10 +1455,54 @@ index a860270eca9..0ef927948db 100644 if (this.element && this._messagePort) { this._messagePort.postMessage({ channel, args: data }, transferable); diff --git a/src/vs/workbench/services/extensions/browser/extensionService.ts b/src/vs/workbench/services/extensions/browser/extensionService.ts -index 25885c7c4f9..ec1beb2c951 100644 +index 25885c7c4f9..2d220cbe1ad 100644 --- a/src/vs/workbench/services/extensions/browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/browser/extensionService.ts -@@ -212,16 +212,16 @@ export class ExtensionService extends AbstractExtensionService implements IExten +@@ -42,8 +42,10 @@ import { IUserDataInitializationService } from 'vs/workbench/services/userData/b + import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; + + export class ExtensionService extends AbstractExtensionService implements IExtensionService { +- + constructor( ++ extensionsProposedApi: ExtensionsProposedApi, ++ extensionHostFactory: IExtensionHostFactory, ++ extensionHostKindPicker: IExtensionHostKindPicker, + @IInstantiationService instantiationService: IInstantiationService, + @INotificationService notificationService: INotificationService, + @IBrowserWorkbenchEnvironmentService private readonly _browserEnvironmentService: IBrowserWorkbenchEnvironmentService, +@@ -67,21 +69,10 @@ export class ExtensionService extends AbstractExtensionService implements IExten + @IRemoteExplorerService private readonly _remoteExplorerService: IRemoteExplorerService, + @IDialogService dialogService: IDialogService, + ) { +- const extensionsProposedApi = instantiationService.createInstance(ExtensionsProposedApi); +- const extensionHostFactory = new BrowserExtensionHostFactory( +- extensionsProposedApi, +- () => this._scanWebExtensions(), +- () => this._getExtensionRegistrySnapshotWhenReady(), +- instantiationService, +- remoteAgentService, +- remoteAuthorityResolverService, +- extensionEnablementService, +- logService +- ); + super( + extensionsProposedApi, + extensionHostFactory, +- new BrowserExtensionHostKindPicker(logService), ++ extensionHostKindPicker, + instantiationService, + notificationService, + _browserEnvironmentService, +@@ -129,7 +120,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten + this._register(this._fileService.registerProvider(Schemas.https, provider)); + } + +- private async _scanWebExtensions(): Promise { ++ protected async _scanWebExtensions(): Promise { + const system: IExtensionDescription[] = [], user: IExtensionDescription[] = [], development: IExtensionDescription[] = []; + try { + await Promise.all([ +@@ -212,16 +203,16 @@ export class ExtensionService extends AbstractExtensionService implements IExten } } @@ -1480,7 +1524,7 @@ index 25885c7c4f9..ec1beb2c951 100644 @ILogService private readonly _logService: ILogService, ) { } -@@ -248,7 +248,7 @@ class BrowserExtensionHostFactory implements IExtensionHostFactory { +@@ -248,7 +239,7 @@ class BrowserExtensionHostFactory implements IExtensionHostFactory { } } @@ -1489,6 +1533,79 @@ index 25885c7c4f9..ec1beb2c951 100644 return { getInitData: async (): Promise => { if (isInitialStart) { +@@ -345,4 +336,71 @@ export class BrowserExtensionHostKindPicker implements IExtensionHostKindPicker + } + } + +-registerSingleton(IExtensionService, ExtensionService, InstantiationType.Eager); ++export class BrowserExtensionService extends ExtensionService { ++ constructor( ++ @IInstantiationService instantiationService: IInstantiationService, ++ @INotificationService notificationService: INotificationService, ++ @IBrowserWorkbenchEnvironmentService browserEnvironmentService: IBrowserWorkbenchEnvironmentService, ++ @ITelemetryService telemetryService: ITelemetryService, ++ @IWorkbenchExtensionEnablementService extensionEnablementService: IWorkbenchExtensionEnablementService, ++ @IFileService fileService: IFileService, ++ @IProductService productService: IProductService, ++ @IWorkbenchExtensionManagementService extensionManagementService: IWorkbenchExtensionManagementService, ++ @IWorkspaceContextService contextService: IWorkspaceContextService, ++ @IConfigurationService configurationService: IConfigurationService, ++ @IExtensionManifestPropertiesService extensionManifestPropertiesService: IExtensionManifestPropertiesService, ++ @IWebExtensionsScannerService webExtensionsScannerService: IWebExtensionsScannerService, ++ @ILogService logService: ILogService, ++ @IRemoteAgentService remoteAgentService: IRemoteAgentService, ++ @IRemoteExtensionsScannerService remoteExtensionsScannerService: IRemoteExtensionsScannerService, ++ @ILifecycleService lifecycleService: ILifecycleService, ++ @IRemoteAuthorityResolverService remoteAuthorityResolverService: IRemoteAuthorityResolverService, ++ @IUserDataInitializationService userDataInitializationService: IUserDataInitializationService, ++ @IUserDataProfileService userDataProfileService: IUserDataProfileService, ++ @IWorkspaceTrustManagementService workspaceTrustManagementService: IWorkspaceTrustManagementService, ++ @IRemoteExplorerService remoteExplorerService: IRemoteExplorerService, ++ @IDialogService dialogService: IDialogService, ++ ) { ++ const extensionsProposedApi = instantiationService.createInstance(ExtensionsProposedApi); ++ const extensionHostFactory = new BrowserExtensionHostFactory( ++ extensionsProposedApi, ++ () => this._scanWebExtensions(), ++ () => this._getExtensionRegistrySnapshotWhenReady(), ++ instantiationService, ++ remoteAgentService, ++ remoteAuthorityResolverService, ++ extensionEnablementService, ++ logService ++ ); ++ super( ++ extensionsProposedApi, ++ extensionHostFactory, ++ new BrowserExtensionHostKindPicker(logService), ++ instantiationService, ++ notificationService, ++ browserEnvironmentService, ++ telemetryService, ++ extensionEnablementService, ++ fileService, ++ productService, ++ extensionManagementService, ++ contextService, ++ configurationService, ++ extensionManifestPropertiesService, ++ webExtensionsScannerService, ++ logService, ++ remoteAgentService, ++ remoteExtensionsScannerService, ++ lifecycleService, ++ remoteAuthorityResolverService, ++ userDataInitializationService, ++ userDataProfileService, ++ workspaceTrustManagementService, ++ remoteExplorerService, ++ dialogService ++ ); ++ } ++} ++ ++ ++registerSingleton(IExtensionService, BrowserExtensionService, InstantiationType.Eager); diff --git a/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts b/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts index a7ce017ab9f..fd873470224 100644 --- a/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts diff --git a/src/extensions.ts b/src/extensions.ts index bd1594da..38c1e295 100644 --- a/src/extensions.ts +++ b/src/extensions.ts @@ -13,7 +13,7 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ import { parse } from 'vs/base/common/json' import { IFileService } from 'vs/platform/files/common/files' import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation' -import { IExtensionWithExtHostKind, SimpleExtensionService, getLocalExtHostExtensionService } from './service-override/extensions' +import { IExtensionWithExtHostKind, ExtensionServiceOverride, getLocalExtHostExtensionService } from './service-override/extensions' import { registerExtensionFile } from './service-override/files' import { setDefaultApi } from './api' import { getService } from './services' @@ -79,7 +79,7 @@ interface ExtensionDelta { toRemove: IExtension[] } const deltaExtensions = throttle(async ({ toAdd, toRemove }: ExtensionDelta) => { - const extensionService = await getService(IExtensionService) as SimpleExtensionService + const extensionService = await getService(IExtensionService) as ExtensionServiceOverride await extensionService.deltaExtensions(toAdd, toRemove) }, (a, b) => ({ toAdd: [...a.toAdd, ...b.toAdd], toRemove: [...a.toRemove, ...b.toRemove] }), 0) diff --git a/src/service-override/extensions.ts b/src/service-override/extensions.ts index 917df534..43c151b3 100644 --- a/src/service-override/extensions.ts +++ b/src/service-override/extensions.ts @@ -1,16 +1,16 @@ import { IFileService } from 'vs/platform/files/common/files' -import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle' +import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle' import { ExtensionHostExtensions, ExtensionHostStartup, IExtensionHost, IExtensionService, nullExtensionDescription, toExtensionDescription } from 'vs/workbench/services/extensions/common/extensions' import { ILogService, ILoggerService } from 'vs/platform/log/common/log' import { ExtensionIdentifier, ExtensionIdentifierMap, IExtension, IExtensionDescription, IRelaxedExtensionDescription } from 'vs/platform/extensions/common/extensions' import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace' import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation' import { INotificationService } from 'vs/platform/notification/common/notification' -import { AbstractExtensionService, DeltaExtensionsQueueItem, ResolvedExtensions } from 'vs/workbench/services/extensions/common/abstractExtensionService' +import { DeltaExtensionsQueueItem } from 'vs/workbench/services/extensions/common/abstractExtensionService' import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry' import { Event } from 'vs/base/common/event' import { IDialogService } from 'vs/platform/dialogs/common/dialogs' -import { IRemoteAuthorityResolverService, RemoteAuthorityResolverError, ResolverResult } from 'vs/platform/remote/common/remoteAuthorityResolver' +import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver' import { IRemoteExtensionsScannerService } from 'vs/platform/remote/common/remoteExtensionsScanner' import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService' import { IWebExtensionsScannerService, IWorkbenchExtensionEnablementService, IWorkbenchExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement' @@ -22,13 +22,10 @@ import { IEditorOverrideServices } from 'vs/editor/standalone/browser/standalone import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors' import { IUserDataInitializationService } from 'vs/workbench/services/userData/browser/userDataInit' import { ExtensionsProposedApi } from 'vs/workbench/services/extensions/common/extensionsProposedApi' -import { BrowserExtensionHostFactory, BrowserExtensionHostKindPicker } from 'vs/workbench/services/extensions/browser/extensionService' -import { FetchFileSystemProvider } from 'vs/workbench/services/extensions/browser/webWorkerFileSystemProvider' -import { Schemas } from 'vs/base/common/network' +import { BrowserExtensionHostFactory, BrowserExtensionHostKindPicker, ExtensionService } from 'vs/workbench/services/extensions/browser/extensionService' import { ExtensionHostKind, ExtensionRunningPreference } from 'vs/workbench/services/extensions/common/extensionHostKind' import { ExtensionRunningLocation, LocalProcessRunningLocation, LocalWebWorkerRunningLocation } from 'vs/workbench/services/extensions/common/extensionRunningLocation' import { ExtensionRunningLocationTracker } from 'vs/workbench/services/extensions/common/extensionRunningLocationTracker' -import { dedupExtensions } from 'vs/workbench/services/extensions/common/extensionsUtil' import { IExtHostExtensionService, IHostUtils } from 'vs/workbench/api/common/extHostExtensionService' import { ExtHostExtensionService } from 'vs/workbench/api/worker/extHostExtensionService' import type * as vscode from 'vscode' @@ -54,7 +51,6 @@ import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/c import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust' import { IRemoteExplorerService } from 'vs/workbench/services/remote/common/remoteExplorerService' import { ExtensionDescriptionRegistrySnapshot } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry' -import { PersistentConnectionEventType } from 'vs/platform/remote/common/remoteAgentConnection' import { changeUrlDomain } from './tools/url' import { registerAssets } from '../assets' import { unsupported } from '../tools' @@ -365,7 +361,7 @@ export interface IExtensionWithExtHostKind extends IExtension { extHostKind?: ExtensionHostKind } -export class SimpleExtensionService extends AbstractExtensionService implements IExtensionService { +export class ExtensionServiceOverride extends ExtensionService implements IExtensionService { constructor ( workerConfig: WorkerConfig | undefined, @IInstantiationService instantiationService: IInstantiationService, @@ -379,24 +375,23 @@ export class SimpleExtensionService extends AbstractExtensionService implements @IWorkspaceContextService contextService: IWorkspaceContextService, @IConfigurationService configurationService: IConfigurationService, @IExtensionManifestPropertiesService extensionManifestPropertiesService: IExtensionManifestPropertiesService, + @IWebExtensionsScannerService webExtensionsScannerService: IWebExtensionsScannerService, @ILogService logService: ILogService, @IRemoteAgentService remoteAgentService: IRemoteAgentService, @IRemoteExtensionsScannerService remoteExtensionsScannerService: IRemoteExtensionsScannerService, @ILifecycleService lifecycleService: ILifecycleService, @IRemoteAuthorityResolverService remoteAuthorityResolverService: IRemoteAuthorityResolverService, @IUserDataInitializationService userDataInitializationService: IUserDataInitializationService, - @IDialogService dialogService: IDialogService, - @IWebExtensionsScannerService private readonly _webExtensionsScannerService: IWebExtensionsScannerService, - @IUserDataProfileService private readonly _userDataProfileService: IUserDataProfileService, - @IBrowserWorkbenchEnvironmentService private readonly _browserEnvironmentService: IBrowserWorkbenchEnvironmentService, - @IWorkspaceTrustManagementService private readonly _workspaceTrustManagementService: IWorkspaceTrustManagementService, - @IRemoteExplorerService private readonly _remoteExplorerService: IRemoteExplorerService + @IUserDataProfileService userDataProfileService: IUserDataProfileService, + @IWorkspaceTrustManagementService workspaceTrustManagementService: IWorkspaceTrustManagementService, + @IRemoteExplorerService remoteExplorerService: IRemoteExplorerService, + @IDialogService dialogService: IDialogService ) { const extensionsProposedApi = instantiationService.createInstance(ExtensionsProposedApi) const extensionHostFactory = new LocalBrowserExtensionHostFactory( workerConfig, extensionsProposedApi, - async () => [], + async () => this._scanWebExtensions(), () => this._getExtensionRegistrySnapshotWhenReady(), instantiationService, remoteAgentService, @@ -419,27 +414,18 @@ export class SimpleExtensionService extends AbstractExtensionService implements contextService, configurationService, extensionManifestPropertiesService, + webExtensionsScannerService, logService, remoteAgentService, remoteExtensionsScannerService, lifecycleService, remoteAuthorityResolverService, + userDataInitializationService, + userDataProfileService, + workspaceTrustManagementService, + remoteExplorerService, dialogService ) - - // Initialize installed extensions first and do it only after workbench is ready - void lifecycleService.when(LifecyclePhase.Ready).then(async () => { - await userDataInitializationService.initializeInstalledExtensions(instantiationService) - return this._initialize() - }) - - this._initFetchFileSystem() - } - - private _initFetchFileSystem (): void { - const provider = new FetchFileSystemProvider() - this._register(this._fileService.registerProvider(Schemas.http, provider)) - this._register(this._fileService.registerProvider(Schemas.https, provider)) } public async deltaExtensions (toAdd: IExtensionWithExtHostKind[], toRemove: IExtension[]): Promise { @@ -456,89 +442,11 @@ export class SimpleExtensionService extends AbstractExtensionService implements await this._handleDeltaExtensions(new DeltaExtensionsQueueItem(toAdd, toRemove)) } - private async _scanWebExtensions (): Promise { - const system: IExtensionDescription[] = [] - const user: IExtensionDescription[] = [] - const development: IExtensionDescription[] = [] - try { - await Promise.all([ - this._webExtensionsScannerService.scanSystemExtensions().then(extensions => system.push(...extensions.map(e => toExtensionDescription(e)))), - this._webExtensionsScannerService.scanUserExtensions(this._userDataProfileService.currentProfile.extensionsResource, { skipInvalidExtensions: true }).then(extensions => user.push(...extensions.map(e => toExtensionDescription(e)))), - this._webExtensionsScannerService.scanExtensionsUnderDevelopment().then(extensions => development.push(...extensions.map(e => toExtensionDescription(e, true)))) - ]) - } catch (error) { - if (error instanceof Error) { - this._logService.error(error) - } - } - return dedupExtensions(system, user, development, this._logService) - } - - protected async _resolveExtensionsDefault (): Promise { - const [localExtensions, remoteExtensions] = await Promise.all([ - this._scanWebExtensions(), - this._remoteExtensionsScannerService.scanExtensions() - ]) - - return new ResolvedExtensions(localExtensions, remoteExtensions, true, true) - } - - protected override async _resolveExtensions (): Promise { - // Taken from vs/workbench/services/extensions/browser/extensionService - if (!this._browserEnvironmentService.expectsResolverExtension) { - return this._resolveExtensionsDefault() - } - - const remoteAuthority = this._environmentService.remoteAuthority! - - // Now that the canonical URI provider has been registered, we need to wait for the trust state to be - // calculated. The trust state will be used while resolving the authority, however the resolver can - // override the trust state through the resolver result. - await this._workspaceTrustManagementService.workspaceResolved - - let resolverResult: ResolverResult - try { - resolverResult = await this._resolveAuthorityInitial(remoteAuthority) - } catch (err) { - if (RemoteAuthorityResolverError.isHandled(err)) { - // eslint-disable-next-line no-console - console.log('Error handled: Not showing a notification for the error') - } - this._remoteAuthorityResolverService._setResolvedAuthorityError(remoteAuthority, err) - - // Proceed with the local extension host - return this._resolveExtensionsDefault() - } - - // set the resolved authority - this._remoteAuthorityResolverService._setResolvedAuthority(resolverResult.authority, resolverResult.options) - this._remoteExplorerService.setTunnelInformation(resolverResult.tunnelInformation) - - // monitor for breakage - const connection = this._remoteAgentService.getConnection() - if (connection != null) { - connection.onDidStateChange(async (e) => { - if (e.type === PersistentConnectionEventType.ConnectionLost) { - this._remoteAuthorityResolverService._clearResolvedAuthority(remoteAuthority) - } - }) - connection.onReconnecting(() => this._resolveAuthorityAgain()) - } - - return this._resolveExtensionsDefault() - } - protected override async _scanSingleExtension (extension: IExtension): Promise | null> { - return toExtensionDescription(extension) - } - - protected _onExtensionHostExit (): void { - // Dispose everything associated with the extension host - this._doStopExtensionHosts() - } - - protected _resolveAuthority (remoteAuthority: string): Promise { - return this._resolveAuthorityOnExtensionHosts(ExtensionHostKind.LocalWebWorker, remoteAuthority) + if (extension.location.scheme === 'extension-fs') { + return toExtensionDescription(extension) + } + return super._scanSingleExtension(extension) } } @@ -559,7 +467,7 @@ export default function getServiceOverride (workerConfig?: WorkerConfig, _iframe : undefined return { - [IExtensionService.toString()]: new SyncDescriptor(SimpleExtensionService, [_workerConfig], false), + [IExtensionService.toString()]: new SyncDescriptor(ExtensionServiceOverride, [_workerConfig], false), [IExtensionManifestPropertiesService.toString()]: new SyncDescriptor(ExtensionManifestPropertiesService, [], true) } } From d9f3c63cfd45bc64c083661c48ed3e89a077f018 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Mangeonjean?= Date: Tue, 21 Nov 2023 11:46:14 +0100 Subject: [PATCH 09/17] fix(demo): force optimize vscode-semver --- demo/vite.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/vite.config.ts b/demo/vite.config.ts index 30d0d89e..37151795 100644 --- a/demo/vite.config.ts +++ b/demo/vite.config.ts @@ -59,7 +59,7 @@ export default defineConfig({ // These 2 lines prevent vite from reloading the whole page when starting a worker (so 2 times in a row after cleaning the vite cache - for the editor then the textmate workers) // it's mainly empirical and probably not the best way, fix me if you find a better way - 'monaco-editor/esm/vs/nls.js', 'monaco-editor/esm/vs/editor/editor.worker.js', 'vscode-textmate', 'vscode-oniguruma', '@vscode/vscode-languagedetection', + 'monaco-editor/esm/vs/nls.js', 'monaco-editor/esm/vs/editor/editor.worker.js', 'vscode-textmate', 'vscode-oniguruma', '@vscode/vscode-languagedetection', 'vscode-semver', ...(await glob('monaco-editor/esm/vs/**/common/**/*.js', { cwd: path.resolve(__dirname, '../node_modules') })) ], exclude: [], From a88fb8944287ff369ef6e95006ea0559d59b2d52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Mangeonjean?= Date: Tue, 21 Nov 2023 12:18:03 +0100 Subject: [PATCH 10/17] fix(demo): fix remote extension demo --- demo/src/features/remoteExtension.ts | 2 +- demo/src/features/remoteExtensionExample/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/demo/src/features/remoteExtension.ts b/demo/src/features/remoteExtension.ts index 3b039370..293bfbca 100644 --- a/demo/src/features/remoteExtension.ts +++ b/demo/src/features/remoteExtension.ts @@ -7,5 +7,5 @@ declare global { } if (window.rootDirectory != null) { - registerRemoteExtension(`${window.rootDirectory}/src/features/remoteExtensionExample/`) + void registerRemoteExtension(`${window.rootDirectory}/src/features/remoteExtensionExample/`) } diff --git a/demo/src/features/remoteExtensionExample/package.json b/demo/src/features/remoteExtensionExample/package.json index 2055817f..d97208ba 100644 --- a/demo/src/features/remoteExtensionExample/package.json +++ b/demo/src/features/remoteExtensionExample/package.json @@ -4,7 +4,7 @@ "version": "1.0.0", "activationEvents": ["*"], "engines": { - "vscode": "*" + "vscode": "^1.77.0" }, "l10n": "./l10n", "main": "./main.js", From d4121ff810bc0e33de3f65eb5cf58c13c8046151 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Mangeonjean?= Date: Tue, 21 Nov 2023 12:22:28 +0100 Subject: [PATCH 11/17] fix: wrong path --- src/server/server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/server.ts b/src/server/server.ts index d8737cc2..29790e8c 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -32,7 +32,7 @@ if (PRODUCT_JSON_PATH != null) { // Create a directory for system extensions to be installed in, VSCode // will try to read this folder to find system extensions, and will // error if it does not exist. -const currentDirPath = fileURLToPath(import.meta.url) +const currentDirPath = path.dirname(fileURLToPath(import.meta.url)) await fs.mkdir(path.join(currentDirPath, 'extensions'), { recursive: true }) import('./server-main') From 6e9fa45e2dab58deed5ccc3949620f9b92c24820 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Mangeonjean?= Date: Tue, 21 Nov 2023 15:34:45 +0100 Subject: [PATCH 12/17] fix: properly inject quality and commit into product --- package.json | 2 +- rollup/rollup.server.config.ts | 143 ++++++++++++++++++--------------- src/missing-services.ts | 1 + src/server/server.ts | 7 +- src/services.ts | 4 +- 5 files changed, 87 insertions(+), 70 deletions(-) diff --git a/package.json b/package.json index 1786c866..e093ed7a 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "compile-default-extensions": "NODE_OPTIONS=--max_old_space_size=8192 rollup --config rollup/rollup.default-extensions.ts --configPlugin 'typescript={tsconfig: `tsconfig.rollup-config-default-extensions.json`}'", "compile-language-packs": "NODE_OPTIONS=--max_old_space_size=8192 rollup --config rollup/rollup.language-packs.ts --configPlugin 'typescript={tsconfig: `tsconfig.rollup-config-language-packs.json`}'", "clean": "rm -rf dist/", - "compile-server": "rollup --config rollup/rollup.server.config.ts --configPlugin 'typescript={tsconfig: `tsconfig.rollup-config-server.json`, include: [`./rollup/rollup.server.config.ts`, `./rollup/rollup-metadata-plugin.ts`]}'", + "compile-server": "rollup --config rollup/rollup.server.config.ts --configPlugin 'typescript={tsconfig: `tsconfig.rollup-config-server.json`, include: [`./rollup/rollup.server.config.ts`, `./rollup/rollup-metadata-plugin.ts`]}' --vscode-version ${npm_package_config_vscode_version} --vscode-ref ${npm_package_config_vscode_ref}", "compile-treemending-script": "rollup --config rollup/rollup.treemending-script.config.ts --configPlugin 'typescript={tsconfig: `tsconfig.rollup-config-treemending-script.json`, include: [`./rollup/rollup.treemending-script.config.ts`, `./rollup/rollup-metadata-plugin.ts`]}'", "compile-rollup-plugins": "rollup --config rollup/rollup.rollup-plugins.config.ts --configPlugin 'typescript={tsconfig: `tsconfig.rollup-config-plugins.json`, include: [`./rollup/rollup.rollup-plugins.config.ts`, `./rollup/rollup-metadata-plugin.ts`]}'", "copy-monaco-editor": "cp -R node_modules/monaco-editor dist/ && (cd dist/monaco-editor; change-package-name \"@codingame/monaco-editor-treemended\")", diff --git a/rollup/rollup.server.config.ts b/rollup/rollup.server.config.ts index 8cfacd67..772e023c 100644 --- a/rollup/rollup.server.config.ts +++ b/rollup/rollup.server.config.ts @@ -4,6 +4,7 @@ import typescript from '@rollup/plugin-typescript' import commonjs from '@rollup/plugin-commonjs' import json from '@rollup/plugin-json' import { PackageJson } from 'type-fest' +import replace from '@rollup/plugin-replace' import * as path from 'path' import { fileURLToPath } from 'url' import metadataPlugin from './rollup-metadata-plugin.js' @@ -15,72 +16,84 @@ const BASE_DIR = path.resolve(__dirname, '..') const TSCONFIG = path.resolve(BASE_DIR, 'tsconfig.rollup.json') const externals = Object.keys(pkg.dependencies) -const config: rollup.RollupOptions = { - cache: false, - external: (source) => { - if (source === 'graceful-fs' || source === 'xterm-headless') { - // commonjs module - return false - } - return externals.some(external => source === external || source.startsWith(`${external}/`)) - }, - output: [{ - format: 'esm', - dir: 'dist/server', - entryFileNames: '[name].js', - chunkFileNames: '[name].js', - banner: (module) => module.isEntry ? '#!/usr/bin/env node' : '' - }], - input: { - server: 'src/server/server.ts', - 'bootstrap-fork': 'src/server/bootstrap-fork.ts' - }, - plugins: [ - json({ - compact: true, - namedExports: false, - preferConst: false - }), - commonjs({ - ignoreDynamicRequires: true - }), - nodeResolve({ - extensions: EXTENSIONS, - modulePaths: ['vscode/src'], - browser: false, - preferBuiltins: true - }), - typescript({ - noEmitOnError: true, - tsconfig: TSCONFIG, - compilerOptions: { - outDir: 'dist/server' +export default (args: Record): rollup.RollupOptions => { + const vscodeVersion = args['vscode-version'] + delete args['vscode-version'] + const vscodeRef = args['vscode-ref'] + delete args['vscode-ref'] + if (vscodeVersion == null) { + throw new Error('Vscode version is mandatory') + } + return rollup.defineConfig({ + cache: false, + external: (source) => { + if (source === 'graceful-fs' || source === 'xterm-headless') { + // commonjs module + return false } - }), - metadataPlugin({ - handle (_, dependencies) { - const packageJson: PackageJson = { - name: '@codingame/monaco-vscode-server', - ...Object.fromEntries(Object.entries(pkg).filter(([key]) => ['version', 'keywords', 'author', 'license', 'repository', 'type'].includes(key))), - private: false, - description: `VSCode server designed to be used with ${pkg.name}`, - bin: { - 'vscode-ext-host-server': './server.js' - }, - dependencies: { - vscode: `npm:${pkg.name}@^${pkg.version}`, - ...Object.fromEntries(Object.entries(pkg.dependencies).filter(([key]) => dependencies.has(key))) + return externals.some(external => source === external || source.startsWith(`${external}/`)) + }, + output: [{ + format: 'esm', + dir: 'dist/server', + entryFileNames: '[name].js', + chunkFileNames: '[name].js', + banner: (module) => module.isEntry ? '#!/usr/bin/env node' : '' + }], + input: { + server: 'src/server/server.ts', + 'bootstrap-fork': 'src/server/bootstrap-fork.ts' + }, + plugins: [ + replace({ + VSCODE_VERSION: JSON.stringify(vscodeVersion), + VSCODE_REF: JSON.stringify(vscodeRef), + preventAssignment: true + }), + json({ + compact: true, + namedExports: false, + preferConst: false + }), + commonjs({ + ignoreDynamicRequires: true + }), + nodeResolve({ + extensions: EXTENSIONS, + modulePaths: ['vscode/src'], + browser: false, + preferBuiltins: true + }), + typescript({ + noEmitOnError: true, + tsconfig: TSCONFIG, + compilerOptions: { + outDir: 'dist/server' + } + }), + metadataPlugin({ + handle (_, dependencies) { + const packageJson: PackageJson = { + name: '@codingame/monaco-vscode-server', + ...Object.fromEntries(Object.entries(pkg).filter(([key]) => ['version', 'keywords', 'author', 'license', 'repository', 'type'].includes(key))), + private: false, + description: `VSCode server designed to be used with ${pkg.name}`, + bin: { + 'vscode-ext-host-server': './server.js' + }, + dependencies: { + vscode: `npm:${pkg.name}@^${pkg.version}`, + ...Object.fromEntries(Object.entries(pkg.dependencies).filter(([key]) => dependencies.has(key))) + } } + this.emitFile({ + fileName: 'package.json', + needsCodeReference: false, + source: JSON.stringify(packageJson, null, 2), + type: 'asset' + }) } - this.emitFile({ - fileName: 'package.json', - needsCodeReference: false, - source: JSON.stringify(packageJson, null, 2), - type: 'asset' - }) - } - }) - ] + }) + ] + }) } - -export default config diff --git a/src/missing-services.ts b/src/missing-services.ts index d8b6e233..6d6636d8 100644 --- a/src/missing-services.ts +++ b/src/missing-services.ts @@ -430,6 +430,7 @@ registerSingleton(IProductService, class ProductService implements IProductServi version = VSCODE_VERSION commit = VSCODE_REF + quality = 'oss' nameShort = 'Code - OSS Dev' nameLong = 'Code - OSS Dev' applicationName = 'code-oss' diff --git a/src/server/server.ts b/src/server/server.ts index 29790e8c..7abc83bc 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -2,6 +2,7 @@ import { IProductService } from 'vs/platform/product/common/productService' import * as fs from 'fs/promises' import * as path from 'path' import { fileURLToPath } from 'url' +import VSCODE_PACKAGE_JSON from '../../vscode/package.json' assert { type: 'json' } const thisWithVSCodeParams = globalThis as typeof globalThis & { _VSCODE_PRODUCT_JSON: Partial @@ -10,6 +11,8 @@ const thisWithVSCodeParams = globalThis as typeof globalThis & { // Initialize the product information for the server, including the extension gallery URL. thisWithVSCodeParams._VSCODE_PRODUCT_JSON = { + quality: 'oss', + commit: VSCODE_REF, extensionsGallery: { serviceUrl: 'https://open-vsx.org/vscode/gallery', itemUrl: 'https://open-vsx.org/vscode/item', @@ -19,9 +22,7 @@ thisWithVSCodeParams._VSCODE_PRODUCT_JSON = { publisherUrl: '' } } -thisWithVSCodeParams._VSCODE_PACKAGE_JSON = { - version: VSCODE_VERSION -} +thisWithVSCodeParams._VSCODE_PACKAGE_JSON = VSCODE_PACKAGE_JSON const PRODUCT_JSON_PATH = process.env.PRODUCT_JSON_PATH if (PRODUCT_JSON_PATH != null) { diff --git a/src/services.ts b/src/services.ts index 7fc9a067..f17c55d4 100644 --- a/src/services.ts +++ b/src/services.ts @@ -11,6 +11,7 @@ import { IAction } from 'vs/base/common/actions' import { IProductService } from 'vs/platform/product/common/productService' import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle' import { IWorkbenchConstructionOptions } from 'vs/workbench/browser/web.api' +import { IProductConfiguration } from 'vs/base/common/product' import getLayoutServiceOverride from './service-override/layout' import getEnvironmentServiceOverride from './service-override/environment' import getExtensionsServiceOverride from './service-override/extensions' @@ -25,8 +26,9 @@ export async function initialize (overrides: IEditorOverrideServices, container: initializeWorkbench(container, configuration) const instantiationService = StandaloneServices.initialize({ - [IProductService.toString()]: mixin({ + [IProductService.toString()]: mixin(>{ version: VSCODE_VERSION, + quality: 'oss', commit: VSCODE_REF, nameShort: 'Code - OSS Dev', nameLong: 'Code - OSS Dev', From f7cb6b3f904467923e03acf1ffabfeb7ba66c928 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Mangeonjean?= Date: Tue, 21 Nov 2023 15:50:16 +0100 Subject: [PATCH 13/17] fix: add a way to easily set webEndpointUrlTemplate --- demo/package.json | 2 +- src/server/server.ts | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/demo/package.json b/demo/package.json index be912f19..ab60ed24 100644 --- a/demo/package.json +++ b/demo/package.json @@ -12,7 +12,7 @@ "build": "tsc --noEmit && vite --config vite.config.ts build", "build:github": "tsc --noEmit && vite --config vite.github-page.config.ts build && touch dist/.nojekyll", "start:debugServer": "node --loader ts-node/esm src/debugServer.ts", - "start:extHostServer": "vscode-ext-host-server --without-connection-token" + "start:extHostServer": "WEB_ENDPOINT_URL_TEMPLATE=http://localhost:5173/ vscode-ext-host-server --without-connection-token" }, "devDependencies": { "@codingame/monaco-vscode-rollup-vsix-plugin": "file:../dist/rollup-vsix-plugin", diff --git a/src/server/server.ts b/src/server/server.ts index 7abc83bc..212a6f97 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -9,10 +9,16 @@ const thisWithVSCodeParams = globalThis as typeof globalThis & { _VSCODE_PACKAGE_JSON: { version: string } } +const WEB_ENDPOINT_URL_TEMPLATE = process.env.WEB_ENDPOINT_URL_TEMPLATE +if (WEB_ENDPOINT_URL_TEMPLATE == null) { + console.warn('No WEB_ENDPOINT_URL_TEMPLATE env variable set, the client won\'t be able to load server extension files') +} + // Initialize the product information for the server, including the extension gallery URL. thisWithVSCodeParams._VSCODE_PRODUCT_JSON = { quality: 'oss', commit: VSCODE_REF, + webEndpointUrlTemplate: WEB_ENDPOINT_URL_TEMPLATE, extensionsGallery: { serviceUrl: 'https://open-vsx.org/vscode/gallery', itemUrl: 'https://open-vsx.org/vscode/item', From 91e758ed9fa9364d3b858c0862178ceb4d8670d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Mangeonjean?= Date: Tue, 21 Nov 2023 15:50:29 +0100 Subject: [PATCH 14/17] fix(doc): update README --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ac953ce3..3a6500a9 100644 --- a/README.md +++ b/README.md @@ -184,7 +184,8 @@ Additionally, 25 packages that include the vscode version of some services (with - Running terminals - Running vscode extensions (not web-compatible) - and probably more? - This library exports a `vscode-ext-host-server` bin to start the remote agent + + Another package `@codingame/monaco-vscode-server` is published, which expose a `vscode-ext-host-server` bin to start the remote agent - **Accessibility**: `@codingame/monaco-vscode-accessibility-service-override` - Register accessibility helpers - **Workspace trust**: `@codingame/monaco-vscode-workspace-trust-service-override` From e08647a97495962480b2c4962e213603c3ef1fc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Mangeonjean?= Date: Tue, 21 Nov 2023 16:48:03 +0100 Subject: [PATCH 15/17] fix: rename extension-fs to extension-file and factorize constant --- scripts/vscode.patch | 2 +- src/extensions.ts | 4 ++-- src/service-override/extensions.ts | 3 ++- src/service-override/files.ts | 12 ++++++++++-- 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/scripts/vscode.patch b/scripts/vscode.patch index 246728d9..15f78f23 100644 --- a/scripts/vscode.patch +++ b/scripts/vscode.patch @@ -1065,7 +1065,7 @@ index 6e6f8845da5..80a3a9f0095 100644 + private notFound = false; + override open(method: string, url: string | URL, async?: boolean, username?: string | null, password?: string | null): void { + const transformedUrl = asWorkerBrowserUrl(url); -+ this.notFound = transformedUrl.startsWith('extension-fs:'); ++ this.notFound = transformedUrl.startsWith('extension-file:'); + return super.open(method, transformedUrl, async ?? true, username, password); + } + override send(body?: Document | XMLHttpRequestBodyInit | null | undefined): void { diff --git a/src/extensions.ts b/src/extensions.ts index 38c1e295..0e25d570 100644 --- a/src/extensions.ts +++ b/src/extensions.ts @@ -14,7 +14,7 @@ import { parse } from 'vs/base/common/json' import { IFileService } from 'vs/platform/files/common/files' import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation' import { IExtensionWithExtHostKind, ExtensionServiceOverride, getLocalExtHostExtensionService } from './service-override/extensions' -import { registerExtensionFile } from './service-override/files' +import { CustomSchemas, registerExtensionFile } from './service-override/files' import { setDefaultApi } from './api' import { getService } from './services' import { ExtensionManifestTranslator } from './tools/l10n' @@ -99,7 +99,7 @@ export function registerExtension (manifest: IExtensionManifest, extHostKind?: E export function registerExtension (manifest: IExtensionManifest, extHostKind?: ExtensionHostKind, { builtin = manifest.publisher === 'vscode', path = '/' }: RegisterExtensionParams = {}): RegisterExtensionResult { const disposableStore = new DisposableStore() const id = getExtensionId(manifest.publisher, manifest.name) - const location = URI.from({ scheme: 'extension-fs', authority: id, path }) + const location = URI.from({ scheme: CustomSchemas.extensionFile, authority: id, path }) const addExtensionPromise = (async () => { const remoteAuthority = (await getService(IWorkbenchEnvironmentService)).remoteAuthority diff --git a/src/service-override/extensions.ts b/src/service-override/extensions.ts index 43c151b3..011e40dd 100644 --- a/src/service-override/extensions.ts +++ b/src/service-override/extensions.ts @@ -52,6 +52,7 @@ import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/w import { IRemoteExplorerService } from 'vs/workbench/services/remote/common/remoteExplorerService' import { ExtensionDescriptionRegistrySnapshot } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry' import { changeUrlDomain } from './tools/url' +import { CustomSchemas } from './files' import { registerAssets } from '../assets' import { unsupported } from '../tools' import 'vs/workbench/api/browser/extensionHost.contribution' @@ -443,7 +444,7 @@ export class ExtensionServiceOverride extends ExtensionService implements IExten } protected override async _scanSingleExtension (extension: IExtension): Promise | null> { - if (extension.location.scheme === 'extension-fs') { + if (extension.location.scheme === CustomSchemas.extensionFile) { return toExtensionDescription(extension) } return super._scanSingleExtension(extension) diff --git a/src/service-override/files.ts b/src/service-override/files.ts index 80665336..02580dae 100644 --- a/src/service-override/files.ts +++ b/src/service-override/files.ts @@ -516,12 +516,20 @@ const userDataFileSystemProvider = new InMemoryFileSystemProvider() // The `mkdirp` logic is inside the service, and the provider will just fail if asked to write a file in a non-existent directory void userDataFileSystemProvider.mkdir(URI.from({ scheme: Schemas.vscodeUserData, path: '/User/' })) +export namespace CustomSchemas { + /** + * A schema that is used for models that exist in memory + * only and that have no correspondence on a server or such. + */ + export const extensionFile = 'extension-file' +} + const providers: Record = { - 'extension-fs': extensionFileSystemProvider, + [CustomSchemas.extensionFile]: extensionFileSystemProvider, [logsPath.scheme]: new InMemoryFileSystemProvider(), [Schemas.vscodeUserData]: userDataFileSystemProvider, [Schemas.tmp]: new InMemoryFileSystemProvider(), - file: fileSystemProvider + [Schemas.file]: fileSystemProvider } class MemoryFileService extends FileService { From 7d049d6b706dcef1f44915dfaddded340f1578f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Mangeonjean?= Date: Tue, 21 Nov 2023 16:48:38 +0100 Subject: [PATCH 16/17] fix: cleanup imports --- src/service-override/extensionGallery.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/service-override/extensionGallery.ts b/src/service-override/extensionGallery.ts index 0d8b37c2..06e6ef8c 100644 --- a/src/service-override/extensionGallery.ts +++ b/src/service-override/extensionGallery.ts @@ -22,13 +22,11 @@ import { RemoteExtensionsScannerService } from 'vs/workbench/services/remote/com import { ExtensionRecommendationNotificationService } from 'vs/workbench/contrib/extensions/browser/extensionRecommendationNotificationService' import { ExtensionTipsService } from 'vs/platform/extensionManagement/common/extensionTipsService' import { ExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagementService' -import 'vs/workbench/contrib/extensions/browser/extensions.contribution' -import 'vs/workbench/contrib/extensions/browser/extensions.web.contribution' -import 'vs/workbench/contrib/logs/common/logs.contribution' import { IRemoteUserDataProfilesService, RemoteUserDataProfilesService } from 'vs/workbench/services/userDataProfile/common/remoteUserDataProfiles' import { ExtensionEnablementService } from 'vs/workbench/services/extensionManagement/browser/extensionEnablementService' -import 'vs/workbench/services/extensionManagement/browser/extensionBisect' import { IInstantiationService, ILabelService, IRemoteAgentService } from '../services' +import 'vs/workbench/contrib/extensions/browser/extensions.contribution' +import 'vs/workbench/contrib/extensions/browser/extensions.web.contribution' class ExtensionManagementServerServiceOverride extends ExtensionManagementServerService { constructor ( From 50bb8c08a01e421775b0d50c22742193bf80ea53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Mangeonjean?= Date: Tue, 21 Nov 2023 16:49:08 +0100 Subject: [PATCH 17/17] feat(demo): demo extension recommendations --- demo/src/setup.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/demo/src/setup.ts b/demo/src/setup.ts index 203ca9d9..fe08b9d7 100644 --- a/demo/src/setup.ts +++ b/demo/src/setup.ts @@ -85,7 +85,12 @@ await Promise.all([ folders: [{ path: '/tmp' }] - })) + })), + initFile(monaco.Uri.file('/tmp/.vscode/extensions.json'), `{ + "recommendations": [ + "vscodevim.vim" + ] +}`) ]) // Override services