diff --git a/CHANGELOG.md b/CHANGELOG.md
index fcf2f5a2f8c03..505c6060e9f59 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,10 @@
- [plugin] stub multiDocumentHighlightProvider proposed API [#13248](https://github.com/eclipse-theia/theia/pull/13248) - contributed on behalf of STMicroelectronics
- [terminal] update terminalQuickFixProvider proposed API according to vscode 1.85 version [#13240](https://github.com/eclipse-theia/theia/pull/13240) - contributed on behalf of STMicroelectronics
+[Breaking Changes:](#breaking_changes_not_yet_released)
+
+- [core] moved `FileUri` from `node` package to `common`
+
## v1.45.0 - 12/21/2023
- [application-manager] updated logic to allow rebinding messaging services in preload [#13199](https://github.com/eclipse-theia/theia/pull/13199)
diff --git a/dev-packages/application-manager/package.json b/dev-packages/application-manager/package.json
index 1d7cfbdf2478e..7ad70e5831baf 100644
--- a/dev-packages/application-manager/package.json
+++ b/dev-packages/application-manager/package.json
@@ -45,6 +45,7 @@
"css-loader": "^6.2.0",
"electron-rebuild": "^3.2.7",
"fs-extra": "^4.0.2",
+ "http-server": "^14.1.1",
"ignore-loader": "^0.1.2",
"less": "^3.0.3",
"mini-css-extract-plugin": "^2.6.1",
diff --git a/dev-packages/application-manager/src/application-package-manager.ts b/dev-packages/application-manager/src/application-package-manager.ts
index 86b5126c8eb6b..09d3d26956789 100644
--- a/dev-packages/application-manager/src/application-package-manager.ts
+++ b/dev-packages/application-manager/src/application-package-manager.ts
@@ -119,10 +119,32 @@ export class ApplicationPackageManager {
start(args: string[] = []): cp.ChildProcess {
if (this.pck.isElectron()) {
return this.startElectron(args);
+ } else if (this.pck.isBrowserOnly()) {
+ return this.startBrowserOnly(args);
}
return this.startBrowser(args);
}
+ startBrowserOnly(args: string[]): cp.ChildProcess {
+ const { command, mainArgs, options } = this.adjustBrowserOnlyArgs(args);
+ return this.__process.spawnBin(command, mainArgs, options);
+ }
+
+ adjustBrowserOnlyArgs(args: string[]): Readonly<{ command: string, mainArgs: string[]; options: cp.SpawnOptions }> {
+ let { mainArgs, options } = this.adjustArgs(args);
+
+ // first parameter: path to generated frontend
+ // second parameter: disable cache to support watching
+ mainArgs = ['lib/frontend', '-c-1', ...mainArgs];
+
+ const portIndex = mainArgs.findIndex(v => v.startsWith('--port'));
+ if (portIndex === -1) {
+ mainArgs.push('--port=3000');
+ }
+
+ return { command: 'http-server', mainArgs, options };
+ }
+
startElectron(args: string[]): cp.ChildProcess {
// If possible, pass the project root directory to electron rather than the script file so that Electron
// can determine the app name. This requires that the package.json has a main field.
diff --git a/dev-packages/application-manager/src/generator/abstract-generator.ts b/dev-packages/application-manager/src/generator/abstract-generator.ts
index 2ff52527ef3ec..da35c5d588849 100644
--- a/dev-packages/application-manager/src/generator/abstract-generator.ts
+++ b/dev-packages/application-manager/src/generator/abstract-generator.ts
@@ -37,6 +37,10 @@ export abstract class AbstractGenerator {
return this.pck.ifElectron(value, defaultValue);
}
+ protected ifBrowserOnly(value: string, defaultValue: string = ''): string {
+ return this.pck.ifBrowserOnly(value, defaultValue);
+ }
+
protected async write(path: string, content: string): Promise {
await fs.ensureFile(path);
await fs.writeFile(path, content);
diff --git a/dev-packages/application-manager/src/generator/backend-generator.ts b/dev-packages/application-manager/src/generator/backend-generator.ts
index 5f457d0d9dba5..a5df1bdb80b46 100644
--- a/dev-packages/application-manager/src/generator/backend-generator.ts
+++ b/dev-packages/application-manager/src/generator/backend-generator.ts
@@ -20,6 +20,10 @@ import { AbstractGenerator } from './abstract-generator';
export class BackendGenerator extends AbstractGenerator {
async generate(): Promise {
+ if (this.pck.isBrowserOnly()) {
+ // no backend generation in case of browser-only target
+ return;
+ }
const backendModules = this.pck.targetBackendModules;
await this.write(this.pck.backend('server.js'), this.compileServer(backendModules));
await this.write(this.pck.backend('main.js'), this.compileMain(backendModules));
diff --git a/dev-packages/application-manager/src/generator/frontend-generator.ts b/dev-packages/application-manager/src/generator/frontend-generator.ts
index d4218f3b174b6..672b11cbcb43e 100644
--- a/dev-packages/application-manager/src/generator/frontend-generator.ts
+++ b/dev-packages/application-manager/src/generator/frontend-generator.ts
@@ -24,7 +24,7 @@ export class FrontendGenerator extends AbstractGenerator {
async generate(options?: GeneratorOptions): Promise {
await this.write(this.pck.frontend('index.html'), this.compileIndexHtml(this.pck.targetFrontendModules));
- await this.write(this.pck.frontend('index.js'), this.compileIndexJs(this.pck.targetFrontendModules, this.pck.frontendPreloadModules));
+ await this.write(this.pck.frontend('index.js'), this.compileIndexJs(this.pck.targetFrontendModules, this.pck.targetFrontendPreloadModules));
await this.write(this.pck.frontend('secondary-window.html'), this.compileSecondaryWindowHtml());
await this.write(this.pck.frontend('secondary-index.js'), this.compileSecondaryIndexJs(this.pck.secondaryWindowModules));
if (this.pck.isElectron()) {
@@ -108,18 +108,26 @@ ${Array.from(frontendPreloadModules.values(), jsModulePath => `\
}
module.exports = (async () => {
- const { messagingFrontendModule } = require('@theia/core/lib/${this.pck.isBrowser()
- ? 'browser/messaging/messaging-frontend-module'
- : 'electron-browser/messaging/electron-messaging-frontend-module'}');
+ const { messagingFrontendModule } = require('@theia/core/lib/${!this.pck.isElectron()
+ ? 'browser/messaging/messaging-frontend-module'
+ : 'electron-browser/messaging/electron-messaging-frontend-module'}');
const container = new Container();
container.load(messagingFrontendModule);
+ ${this.ifBrowserOnly(`const { messagingFrontendOnlyModule } = require('@theia/core/lib/browser-only/messaging/messaging-frontend-only-module');
+ container.load(messagingFrontendOnlyModule);`)}
+
await preload(container);
const { FrontendApplication } = require('@theia/core/lib/browser');
const { frontendApplicationModule } = require('@theia/core/lib/browser/frontend-application-module');
const { loggerFrontendModule } = require('@theia/core/lib/browser/logger-frontend-module');
container.load(frontendApplicationModule);
+ ${this.pck.ifBrowserOnly(`const { frontendOnlyApplicationModule } = require('@theia/core/lib/browser-only/frontend-only-application-module');
+ container.load(frontendOnlyApplicationModule);`)}
+
container.load(loggerFrontendModule);
+ ${this.ifBrowserOnly(`const { loggerFrontendOnlyModule } = require('@theia/core/lib/browser-only/logger-frontend-only-module');
+ container.load(loggerFrontendOnlyModule);`)}
try {
${Array.from(frontendModules.values(), jsModulePath => `\
diff --git a/dev-packages/application-manager/src/generator/webpack-generator.ts b/dev-packages/application-manager/src/generator/webpack-generator.ts
index e650c7a7dbd09..fa942815843b9 100644
--- a/dev-packages/application-manager/src/generator/webpack-generator.ts
+++ b/dev-packages/application-manager/src/generator/webpack-generator.ts
@@ -22,7 +22,9 @@ export class WebpackGenerator extends AbstractGenerator {
async generate(): Promise {
await this.write(this.genConfigPath, this.compileWebpackConfig());
- await this.write(this.genNodeConfigPath, this.compileNodeWebpackConfig());
+ if (!this.pck.isBrowserOnly()) {
+ await this.write(this.genNodeConfigPath, this.compileNodeWebpackConfig());
+ }
if (await this.shouldGenerateUserWebpackConfig()) {
await this.write(this.configPath, this.compileUserWebpackConfig());
}
@@ -332,7 +334,7 @@ module.exports = [{
*/
// @ts-check
const configs = require('./${paths.basename(this.genConfigPath)}');
-const nodeConfig = require('./${paths.basename(this.genNodeConfigPath)}');
+${this.ifBrowserOnly('', `const nodeConfig = require('./${paths.basename(this.genNodeConfigPath)}');`)}
/**
* Expose bundled modules on window.theia.moduleName namespace, e.g.
@@ -343,10 +345,11 @@ configs[0].module.rules.push({
loader: require.resolve('@theia/application-manager/lib/expose-loader')
}); */
-module.exports = [
+${this.ifBrowserOnly('module.exports = configs;', `module.exports = [
...configs,
nodeConfig.config
-];`;
+];`)}
+`;
}
protected compileNodeWebpackConfig(): string {
diff --git a/dev-packages/application-manager/src/rebuild.ts b/dev-packages/application-manager/src/rebuild.ts
index 0784c315558ba..82e454be7c802 100644
--- a/dev-packages/application-manager/src/rebuild.ts
+++ b/dev-packages/application-manager/src/rebuild.ts
@@ -19,7 +19,7 @@ import fs = require('fs-extra');
import path = require('path');
import os = require('os');
-export type RebuildTarget = 'electron' | 'browser';
+export type RebuildTarget = 'electron' | 'browser' | 'browser-only';
const EXIT_SIGNALS: NodeJS.Signals[] = ['SIGINT', 'SIGTERM'];
diff --git a/dev-packages/application-package/src/application-package.ts b/dev-packages/application-package/src/application-package.ts
index 68b5cb896c6ec..700dec43bb2d8 100644
--- a/dev-packages/application-package/src/application-package.ts
+++ b/dev-packages/application-package/src/application-package.ts
@@ -73,7 +73,7 @@ export class ApplicationPackage {
theia.target = this.options.appTarget;
}
- if (theia.target && !(theia.target in ApplicationProps.ApplicationTarget)) {
+ if (theia.target && !(Object.values(ApplicationProps.ApplicationTarget).includes(theia.target))) {
const defaultTarget = ApplicationProps.ApplicationTarget.browser;
console.warn(`Unknown application target '${theia.target}', '${defaultTarget}' to be used instead`);
theia.target = defaultTarget;
@@ -140,10 +140,24 @@ export class ApplicationPackage {
return this._frontendPreloadModules ??= this.computeModules('frontendPreload');
}
+ get frontendOnlyPreloadModules(): Map {
+ if (!this._frontendPreloadModules) {
+ this._frontendPreloadModules = this.computeModules('frontendOnlyPreload', 'frontendPreload');
+ }
+ return this._frontendPreloadModules;
+ }
+
get frontendModules(): Map {
return this._frontendModules ??= this.computeModules('frontend');
}
+ get frontendOnlyModules(): Map {
+ if (!this._frontendModules) {
+ this._frontendModules = this.computeModules('frontendOnly', 'frontend');
+ }
+ return this._frontendModules;
+ }
+
get frontendElectronModules(): Map {
return this._frontendElectronModules ??= this.computeModules('frontendElectron', 'frontend');
}
@@ -227,6 +241,10 @@ export class ApplicationPackage {
return this.target === ApplicationProps.ApplicationTarget.electron;
}
+ isBrowserOnly(): boolean {
+ return this.target === ApplicationProps.ApplicationTarget.browserOnly;
+ }
+
ifBrowser(value: T): T | undefined;
ifBrowser(value: T, defaultValue: T): T;
ifBrowser(value: T, defaultValue?: T): T | undefined {
@@ -239,14 +257,33 @@ export class ApplicationPackage {
return this.isElectron() ? value : defaultValue;
}
+ ifBrowserOnly(value: T): T | undefined;
+ ifBrowserOnly(value: T, defaultValue: T): T;
+ ifBrowserOnly(value: T, defaultValue?: T): T | undefined {
+ return this.isBrowserOnly() ? value : defaultValue;
+ }
+
get targetBackendModules(): Map {
+ if (this.isBrowserOnly()) {
+ return new Map();
+ }
return this.ifBrowser(this.backendModules, this.backendElectronModules);
}
get targetFrontendModules(): Map {
+ if (this.isBrowserOnly()) {
+ return this.frontendOnlyModules;
+ }
return this.ifBrowser(this.frontendModules, this.frontendElectronModules);
}
+ get targetFrontendPreloadModules(): Map {
+ if (this.isBrowserOnly()) {
+ return this.frontendOnlyPreloadModules;
+ }
+ return this.frontendPreloadModules;
+ }
+
get targetElectronMainModules(): Map {
return this.ifElectron(this.electronMainModules, new Map());
}
diff --git a/dev-packages/application-package/src/application-props.ts b/dev-packages/application-package/src/application-props.ts
index 78bcece1c011e..a443a60df679d 100644
--- a/dev-packages/application-package/src/application-props.ts
+++ b/dev-packages/application-package/src/application-props.ts
@@ -241,10 +241,11 @@ export interface ApplicationProps extends NpmRegistryProps {
};
}
export namespace ApplicationProps {
- export type Target = keyof typeof ApplicationTarget;
+ export type Target = `${ApplicationTarget}`;
export enum ApplicationTarget {
browser = 'browser',
- electron = 'electron'
+ electron = 'electron',
+ browserOnly = 'browser-only'
};
export const DEFAULT: ApplicationProps = {
...NpmRegistryProps.DEFAULT,
diff --git a/dev-packages/application-package/src/extension-package.ts b/dev-packages/application-package/src/extension-package.ts
index 8ccd8d661513a..dced483c99674 100644
--- a/dev-packages/application-package/src/extension-package.ts
+++ b/dev-packages/application-package/src/extension-package.ts
@@ -21,7 +21,9 @@ import { NpmRegistry, PublishedNodePackage, NodePackage } from './npm-registry';
export interface Extension {
frontendPreload?: string;
+ frontendOnlyPreload?: string;
frontend?: string;
+ frontendOnly?: string;
frontendElectron?: string;
secondaryWindow?: string;
backend?: string;
diff --git a/dev-packages/cli/package.json b/dev-packages/cli/package.json
index ee50fc60bfde2..7b70f54e5d527 100644
--- a/dev-packages/cli/package.json
+++ b/dev-packages/cli/package.json
@@ -44,6 +44,7 @@
"decompress": "^4.2.1",
"escape-string-regexp": "4.0.0",
"glob": "^8.0.3",
+ "http-server": "^14.1.1",
"limiter": "^2.1.0",
"log-update": "^4.0.0",
"mocha": "^10.1.0",
diff --git a/dev-packages/cli/src/theia.ts b/dev-packages/cli/src/theia.ts
index 41acf9d6e09e4..d215c578de288 100644
--- a/dev-packages/cli/src/theia.ts
+++ b/dev-packages/cli/src/theia.ts
@@ -94,12 +94,12 @@ function rebuildCommand(command: string, target: ApplicationProps.Target): yargs
}
function defineCommonOptions(cli: yargs.Argv): yargs.Argv {
return cli
.option('app-target', {
description: 'The target application type. Overrides `theia.target` in the application\'s package.json',
- choices: ['browser', 'electron'] as const,
+ choices: ['browser', 'electron', 'browser-only'] as const,
});
}
diff --git a/examples/api-samples/package.json b/examples/api-samples/package.json
index d300bb90c332b..28f584a2356e9 100644
--- a/examples/api-samples/package.json
+++ b/examples/api-samples/package.json
@@ -29,6 +29,9 @@
{
"electronMain": "lib/electron-main/update/sample-updater-main-module",
"frontendElectron": "lib/electron-browser/updater/sample-updater-frontend-module"
+ },
+ {
+ "frontendOnly": "lib/browser-only/api-samples-frontend-only-module"
}
],
"keywords": [
diff --git a/examples/api-samples/src/browser-only/api-samples-frontend-only-module.ts b/examples/api-samples/src/browser-only/api-samples-frontend-only-module.ts
new file mode 100644
index 0000000000000..2fe1703ffa8b1
--- /dev/null
+++ b/examples/api-samples/src/browser-only/api-samples-frontend-only-module.ts
@@ -0,0 +1,27 @@
+// *****************************************************************************
+// Copyright (C) 2023 EclipseSource and others.
+//
+// This program and the accompanying materials are made available under the
+// terms of the Eclipse Public License v. 2.0 which is available at
+// http://www.eclipse.org/legal/epl-2.0.
+//
+// This Source Code may also be made available under the following Secondary
+// Licenses when the conditions for such availability set forth in the Eclipse
+// Public License v. 2.0 are satisfied: GNU General Public License, version 2
+// with the GNU Classpath Exception which is available at
+// https://www.gnu.org/software/classpath/license.html.
+//
+// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
+// *****************************************************************************
+
+import { ContainerModule, interfaces } from '@theia/core/shared/inversify';
+import { bindBrowserFSInitialization } from './filesystem/example-filesystem-initialization';
+
+export default new ContainerModule((
+ bind: interfaces.Bind,
+ _unbind: interfaces.Unbind,
+ _isBound: interfaces.IsBound,
+ rebind: interfaces.Rebind,
+) => {
+ bindBrowserFSInitialization(bind, rebind);
+});
diff --git a/examples/api-samples/src/browser-only/filesystem/example-filesystem-initialization.ts b/examples/api-samples/src/browser-only/filesystem/example-filesystem-initialization.ts
new file mode 100644
index 0000000000000..f048231f977a5
--- /dev/null
+++ b/examples/api-samples/src/browser-only/filesystem/example-filesystem-initialization.ts
@@ -0,0 +1,51 @@
+// *****************************************************************************
+// Copyright (C) 2023 EclipseSource and others.
+//
+// This program and the accompanying materials are made available under the
+// terms of the Eclipse Public License v. 2.0 which is available at
+// http://www.eclipse.org/legal/epl-2.0.
+//
+// This Source Code may also be made available under the following Secondary
+// Licenses when the conditions for such availability set forth in the Eclipse
+// Public License v. 2.0 are satisfied: GNU General Public License, version 2
+// with the GNU Classpath Exception which is available at
+// https://www.gnu.org/software/classpath/license.html.
+//
+// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
+// *****************************************************************************
+
+import { URI } from '@theia/core';
+import { inject, injectable, interfaces } from '@theia/core/shared/inversify';
+import { EncodingService } from '@theia/core/lib/common/encoding-service';
+import { BrowserFSInitialization, DefaultBrowserFSInitialization } from '@theia/filesystem/lib/browser-only/browserfs-filesystem-initialization';
+import { BrowserFSFileSystemProvider } from '@theia/filesystem/lib/browser-only/browserfs-filesystem-provider';
+import type { FSModule } from 'browserfs/dist/node/core/FS';
+
+@injectable()
+export class ExampleBrowserFSInitialization extends DefaultBrowserFSInitialization {
+
+ @inject(EncodingService)
+ protected encodingService: EncodingService;
+
+ override async initializeFS(fs: FSModule, provider: BrowserFSFileSystemProvider): Promise {
+ try {
+ if (!fs.existsSync('/home/workspace')) {
+ await provider.mkdir(new URI('/home/workspace'));
+ await provider.writeFile(new URI('/home/workspace/my-file.txt'), this.encodingService.encode('foo').buffer, { create: true, overwrite: false });
+ await provider.writeFile(new URI('/home/workspace/my-file2.txt'), this.encodingService.encode('bar').buffer, { create: true, overwrite: false });
+ }
+ if (!fs.existsSync('/home/workspace2')) {
+ await provider.mkdir(new URI('/home/workspace2'));
+ await provider.writeFile(new URI('/home/workspace2/my-file.json'), this.encodingService.encode('{ foo: true }').buffer, { create: true, overwrite: false });
+ await provider.writeFile(new URI('/home/workspace2/my-file2.json'), this.encodingService.encode('{ bar: false }').buffer, { create: true, overwrite: false });
+ }
+ } catch (e) {
+ console.error('An error occurred while initializing the demo workspaces', e);
+ }
+ }
+}
+
+export const bindBrowserFSInitialization = (bind: interfaces.Bind, rebind: interfaces.Rebind): void => {
+ bind(ExampleBrowserFSInitialization).toSelf();
+ rebind(BrowserFSInitialization).toService(ExampleBrowserFSInitialization);
+};
diff --git a/examples/browser-only/package.json b/examples/browser-only/package.json
new file mode 100644
index 0000000000000..453fb7dae24a4
--- /dev/null
+++ b/examples/browser-only/package.json
@@ -0,0 +1,78 @@
+{
+ "private": true,
+ "name": "@theia/example-browser-only",
+ "version": "1.45.0",
+ "license": "EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0",
+ "theia": {
+ "target": "browser-only",
+ "frontend": {
+ "config": {
+ "applicationName": "Theia Browser-Only Example",
+ "preferences": {
+ "files.enableTrash": false
+ }
+ }
+ }
+ },
+ "dependencies": {
+ "@theia/api-samples": "1.45.0",
+ "@theia/bulk-edit": "1.45.0",
+ "@theia/callhierarchy": "1.45.0",
+ "@theia/console": "1.45.0",
+ "@theia/core": "1.45.0",
+ "@theia/debug": "1.45.0",
+ "@theia/editor": "1.45.0",
+ "@theia/editor-preview": "1.45.0",
+ "@theia/file-search": "1.45.0",
+ "@theia/filesystem": "1.45.0",
+ "@theia/getting-started": "1.45.0",
+ "@theia/git": "1.45.0",
+ "@theia/keymaps": "1.45.0",
+ "@theia/markers": "1.45.0",
+ "@theia/memory-inspector": "1.45.0",
+ "@theia/messages": "1.45.0",
+ "@theia/metrics": "1.45.0",
+ "@theia/mini-browser": "1.45.0",
+ "@theia/monaco": "1.45.0",
+ "@theia/navigator": "1.45.0",
+ "@theia/outline-view": "1.45.0",
+ "@theia/output": "1.45.0",
+ "@theia/plugin-dev": "1.45.0",
+ "@theia/plugin-ext": "1.45.0",
+ "@theia/plugin-ext-vscode": "1.45.0",
+ "@theia/plugin-metrics": "1.45.0",
+ "@theia/preferences": "1.45.0",
+ "@theia/preview": "1.45.0",
+ "@theia/process": "1.45.0",
+ "@theia/property-view": "1.45.0",
+ "@theia/scm": "1.45.0",
+ "@theia/scm-extra": "1.45.0",
+ "@theia/search-in-workspace": "1.45.0",
+ "@theia/secondary-window": "1.45.0",
+ "@theia/task": "1.45.0",
+ "@theia/terminal": "1.45.0",
+ "@theia/timeline": "1.45.0",
+ "@theia/toolbar": "1.45.0",
+ "@theia/typehierarchy": "1.45.0",
+ "@theia/userstorage": "1.45.0",
+ "@theia/variable-resolver": "1.45.0",
+ "@theia/vsx-registry": "1.45.0",
+ "@theia/workspace": "1.45.0"
+ },
+ "scripts": {
+ "prepare:no-native": "lerna run prepare --scope=\"@theia/re-exports\" && lerna run generate-theia-re-exports --scope=\"@theia/core\"",
+ "clean": "theia clean",
+ "build": "yarn -s compile && yarn -s bundle",
+ "bundle": "theia build --mode development",
+ "compile": "tsc -b",
+ "start": "theia start",
+ "start:debug": "yarn -s start --log-level=debug",
+ "start:watch": "concurrently --kill-others -n tsc,bundle,run -c red,yellow,green \"tsc -b -w --preserveWatchOutput\" \"yarn -s watch:bundle\" \"yarn -s start\"",
+ "watch": "concurrently --kill-others -n tsc,bundle -c red,yellow \"tsc -b -w --preserveWatchOutput\" \"yarn -s watch:bundle\"",
+ "watch:bundle": "theia build --watch --mode development",
+ "watch:compile": "tsc -b -w"
+ },
+ "devDependencies": {
+ "@theia/cli": "1.45.0"
+ }
+}
diff --git a/examples/browser-only/tsconfig.json b/examples/browser-only/tsconfig.json
new file mode 100644
index 0000000000000..d4bcfc14426b9
--- /dev/null
+++ b/examples/browser-only/tsconfig.json
@@ -0,0 +1,141 @@
+{
+ "extends": "../../configs/base.tsconfig",
+ "include": [],
+ "compilerOptions": {
+ "composite": true
+ },
+ "references": [
+ {
+ "path": "../../dev-packages/cli"
+ },
+ {
+ "path": "../../packages/bulk-edit"
+ },
+ {
+ "path": "../../packages/callhierarchy"
+ },
+ {
+ "path": "../../packages/console"
+ },
+ {
+ "path": "../../packages/core"
+ },
+ {
+ "path": "../../packages/debug"
+ },
+ {
+ "path": "../../packages/editor"
+ },
+ {
+ "path": "../../packages/editor-preview"
+ },
+ {
+ "path": "../../packages/file-search"
+ },
+ {
+ "path": "../../packages/filesystem"
+ },
+ {
+ "path": "../../packages/getting-started"
+ },
+ {
+ "path": "../../packages/git"
+ },
+ {
+ "path": "../../packages/keymaps"
+ },
+ {
+ "path": "../../packages/markers"
+ },
+ {
+ "path": "../../packages/memory-inspector"
+ },
+ {
+ "path": "../../packages/messages"
+ },
+ {
+ "path": "../../packages/metrics"
+ },
+ {
+ "path": "../../packages/mini-browser"
+ },
+ {
+ "path": "../../packages/monaco"
+ },
+ {
+ "path": "../../packages/navigator"
+ },
+ {
+ "path": "../../packages/outline-view"
+ },
+ {
+ "path": "../../packages/output"
+ },
+ {
+ "path": "../../packages/plugin-dev"
+ },
+ {
+ "path": "../../packages/plugin-ext"
+ },
+ {
+ "path": "../../packages/plugin-ext-vscode"
+ },
+ {
+ "path": "../../packages/plugin-metrics"
+ },
+ {
+ "path": "../../packages/preferences"
+ },
+ {
+ "path": "../../packages/preview"
+ },
+ {
+ "path": "../../packages/process"
+ },
+ {
+ "path": "../../packages/property-view"
+ },
+ {
+ "path": "../../packages/scm"
+ },
+ {
+ "path": "../../packages/scm-extra"
+ },
+ {
+ "path": "../../packages/search-in-workspace"
+ },
+ {
+ "path": "../../packages/secondary-window"
+ },
+ {
+ "path": "../../packages/task"
+ },
+ {
+ "path": "../../packages/terminal"
+ },
+ {
+ "path": "../../packages/timeline"
+ },
+ {
+ "path": "../../packages/toolbar"
+ },
+ {
+ "path": "../../packages/typehierarchy"
+ },
+ {
+ "path": "../../packages/userstorage"
+ },
+ {
+ "path": "../../packages/variable-resolver"
+ },
+ {
+ "path": "../../packages/vsx-registry"
+ },
+ {
+ "path": "../../packages/workspace"
+ },
+ {
+ "path": "../api-samples"
+ }
+ ]
+}
diff --git a/examples/browser-only/webpack.config.js b/examples/browser-only/webpack.config.js
new file mode 100644
index 0000000000000..40e4ee963ba9f
--- /dev/null
+++ b/examples/browser-only/webpack.config.js
@@ -0,0 +1,18 @@
+/**
+ * This file can be edited to customize webpack configuration.
+ * To reset delete this file and rerun theia build again.
+ */
+// @ts-check
+const configs = require('./gen-webpack.config.js');
+
+
+/**
+ * Expose bundled modules on window.theia.moduleName namespace, e.g.
+ * window['theia']['@theia/core/lib/common/uri'].
+ * Such syntax can be used by external code, for instance, for testing.
+configs[0].module.rules.push({
+ test: /\.js$/,
+ loader: require.resolve('@theia/application-manager/lib/expose-loader')
+}); */
+
+module.exports = configs;
diff --git a/package.json b/package.json
index eedb0306c48a5..250eb85206fa3 100644
--- a/package.json
+++ b/package.json
@@ -62,8 +62,9 @@
"scripts": {
"all": "yarn -s install && yarn -s lint && yarn -s build",
"browser": "yarn -s --cwd examples/browser",
+ "browser-only": "yarn -s --cwd examples/browser-only",
"build": "yarn -s compile && yarn -s build:examples",
- "build:examples": "yarn browser build && yarn electron build",
+ "build:examples": "yarn browser build && yarn electron build && yarn browser-only build",
"clean": "yarn -s rebuild:clean && yarn -s lint:clean && node scripts/run-reverse-topo.js yarn -s clean",
"compile": "echo Compiling TypeScript sources... && yarn -s compile:clean && yarn -s compile:tsc",
"compile:clean": "ts-clean dev-packages/* packages/*",
diff --git a/packages/core/package.json b/packages/core/package.json
index 85c21e0f86dce..babe4599f4599 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -134,8 +134,12 @@
"frontendPreload": "lib/browser/preload/preload-module",
"preload": "lib/electron-browser/preload"
},
+ {
+ "frontendOnlyPreload": "lib/browser-only/preload/frontend-only-preload-module"
+ },
{
"frontend": "lib/browser/i18n/i18n-frontend-module",
+ "frontendOnly": "lib/browser-only/i18n/i18n-frontend-only-module",
"backend": "lib/node/i18n/i18n-backend-module"
},
{
diff --git a/packages/core/src/browser-only/frontend-only-application-module.ts b/packages/core/src/browser-only/frontend-only-application-module.ts
new file mode 100644
index 0000000000000..680e7d227c83a
--- /dev/null
+++ b/packages/core/src/browser-only/frontend-only-application-module.ts
@@ -0,0 +1,114 @@
+// *****************************************************************************
+// Copyright (C) 2023 EclipseSource and others.
+//
+// This program and the accompanying materials are made available under the
+// terms of the Eclipse Public License v. 2.0 which is available at
+// http://www.eclipse.org/legal/epl-2.0.
+//
+// This Source Code may also be made available under the following Secondary
+// Licenses when the conditions for such availability set forth in the Eclipse
+// Public License v. 2.0 are satisfied: GNU General Public License, version 2
+// with the GNU Classpath Exception which is available at
+// https://www.gnu.org/software/classpath/license.html.
+//
+// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
+// *****************************************************************************
+
+import { ContainerModule } from 'inversify';
+import { BackendStopwatch, CommandRegistry, Emitter, MeasurementOptions, OS } from '../common';
+import { ApplicationInfo, ApplicationServer, ExtensionInfo } from '../common/application-protocol';
+import { EnvVariable, EnvVariablesServer } from './../common/env-variables';
+import { bindMessageService } from '../browser/frontend-application-bindings';
+import { KeyStoreService } from '../common/key-store';
+import { QuickPickService } from '../common/quick-pick-service';
+import { QuickPickServiceImpl } from '../browser/quick-input';
+import { BackendRequestService, RequestService } from '@theia/request';
+import { ConnectionStatus, ConnectionStatusService } from '../browser/connection-status-service';
+
+export { bindMessageService };
+
+// is loaded directly after the regular frontend module
+export const frontendOnlyApplicationModule = new ContainerModule((bind, unbind, isBound, rebind) => {
+
+ if (isBound(CommandRegistry)) {
+ rebind(CommandRegistry).toSelf().inSingletonScope();
+ } else {
+ bind(CommandRegistry).toSelf().inSingletonScope();
+ }
+
+ const stopwatch: BackendStopwatch = {
+ start: async (_name: string, _options?: MeasurementOptions | undefined): Promise => -1,
+ stop: async (_measurement: number, _message: string, _messageArgs: unknown[]): Promise => { }
+ };
+ if (isBound(BackendStopwatch)) {
+ rebind(BackendStopwatch).toConstantValue(stopwatch);
+ } else {
+ bind(BackendStopwatch).toConstantValue(stopwatch);
+ }
+
+ if (isBound(CommandRegistry)) {
+ rebind(QuickPickService).to(QuickPickServiceImpl).inSingletonScope();
+ } else {
+ bind(QuickPickService).to(QuickPickServiceImpl).inSingletonScope();
+ }
+
+ const mockedApplicationServer: ApplicationServer = {
+ getExtensionsInfos: async (): Promise => [],
+ getApplicationInfo: async (): Promise => undefined,
+ getBackendOS: async (): Promise => OS.Type.Linux
+ };
+ if (isBound(ApplicationServer)) {
+ rebind(ApplicationServer).toConstantValue(mockedApplicationServer);
+ } else {
+ bind(ApplicationServer).toConstantValue(mockedApplicationServer);
+ }
+
+ const varServer: EnvVariablesServer = {
+ getExecPath: async (): Promise => '',
+ getVariables: async (): Promise => [],
+ getValue: async (_key: string): Promise => undefined,
+ getConfigDirUri: async (): Promise => '',
+ getHomeDirUri: async (): Promise => '',
+ getDrives: async (): Promise => []
+ };
+ if (isBound(EnvVariablesServer)) {
+ rebind(EnvVariablesServer).toConstantValue(varServer);
+ } else {
+ bind(EnvVariablesServer).toConstantValue(varServer);
+ }
+
+ const keyStoreService: KeyStoreService = {
+ deletePassword: () => Promise.resolve(false),
+ findCredentials: () => Promise.resolve([]),
+ findPassword: () => Promise.resolve(undefined),
+ setPassword: () => Promise.resolve(),
+ getPassword: () => Promise.resolve(undefined)
+ };
+ if (isBound(KeyStoreService)) {
+ rebind(KeyStoreService).toConstantValue(keyStoreService);
+ } else {
+ bind(KeyStoreService).toConstantValue(keyStoreService);
+ }
+
+ const requestService: RequestService = {
+ configure: () => Promise.resolve(),
+ request: () => Promise.reject(),
+ resolveProxy: () => Promise.resolve(undefined)
+ };
+ if (isBound(BackendRequestService)) {
+ rebind(BackendRequestService).toConstantValue(requestService);
+ } else {
+ bind(BackendRequestService).toConstantValue(requestService);
+ }
+
+ const connectionStatusService: ConnectionStatusService = {
+ currentStatus: ConnectionStatus.ONLINE,
+ onStatusChange: new Emitter().event
+ };
+ if (isBound(ConnectionStatusService)) {
+ rebind(ConnectionStatusService).toConstantValue(connectionStatusService);
+ } else {
+ bind(ConnectionStatusService).toConstantValue(connectionStatusService);
+ }
+
+});
diff --git a/packages/core/src/browser-only/i18n/i18n-frontend-only-module.ts b/packages/core/src/browser-only/i18n/i18n-frontend-only-module.ts
new file mode 100644
index 0000000000000..e88337fcb65b7
--- /dev/null
+++ b/packages/core/src/browser-only/i18n/i18n-frontend-only-module.ts
@@ -0,0 +1,37 @@
+// *****************************************************************************
+// Copyright (C) 2023 EclipseSource and others.
+//
+// This program and the accompanying materials are made available under the
+// terms of the Eclipse Public License v. 2.0 which is available at
+// http://www.eclipse.org/legal/epl-2.0.
+//
+// This Source Code may also be made available under the following Secondary
+// Licenses when the conditions for such availability set forth in the Eclipse
+// Public License v. 2.0 are satisfied: GNU General Public License, version 2
+// with the GNU Classpath Exception which is available at
+// https://www.gnu.org/software/classpath/license.html.
+//
+// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
+// *****************************************************************************
+
+import { ContainerModule } from 'inversify';
+import { AsyncLocalizationProvider, LanguageInfo, Localization } from '../../common/i18n/localization';
+import { LanguageQuickPickService } from '../../browser/i18n/language-quick-pick-service';
+
+export default new ContainerModule(bind => {
+ const i18nMock: AsyncLocalizationProvider = {
+ getCurrentLanguage: async (): Promise => 'en',
+ setCurrentLanguage: async (_languageId: string): Promise => {
+
+ },
+ getAvailableLanguages: async (): Promise =>
+ []
+ ,
+ loadLocalization: async (_languageId: string): Promise => ({
+ translations: {},
+ languageId: 'en'
+ })
+ };
+ bind(AsyncLocalizationProvider).toConstantValue(i18nMock);
+ bind(LanguageQuickPickService).toSelf().inSingletonScope();
+});
diff --git a/packages/core/src/browser-only/logger-frontend-only-module.ts b/packages/core/src/browser-only/logger-frontend-only-module.ts
new file mode 100644
index 0000000000000..e5fc84021476d
--- /dev/null
+++ b/packages/core/src/browser-only/logger-frontend-only-module.ts
@@ -0,0 +1,63 @@
+// *****************************************************************************
+// Copyright (C) 2023 EclipseSource and others.
+//
+// This program and the accompanying materials are made available under the
+// terms of the Eclipse Public License v. 2.0 which is available at
+// http://www.eclipse.org/legal/epl-2.0.
+//
+// This Source Code may also be made available under the following Secondary
+// Licenses when the conditions for such availability set forth in the Eclipse
+// Public License v. 2.0 are satisfied: GNU General Public License, version 2
+// with the GNU Classpath Exception which is available at
+// https://www.gnu.org/software/classpath/license.html.
+//
+// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
+// *****************************************************************************
+
+import { ContainerModule, Container } from 'inversify';
+import { ILoggerServer, ILoggerClient, LogLevel, ConsoleLogger } from '../common/logger-protocol';
+import { ILogger, Logger, LoggerFactory, LoggerName } from '../common/logger';
+
+// is loaded directly after the regular logger frontend module
+export const loggerFrontendOnlyModule = new ContainerModule((bind, unbind, isBound, rebind) => {
+ const logger: ILoggerServer = {
+ setLogLevel: async (_name: string, _logLevel: number): Promise => { },
+ getLogLevel: async (_name: string): Promise => LogLevel.INFO,
+ log: async (name: string, logLevel: number, message: string, params: unknown[]): Promise => {
+ ConsoleLogger.log(name, logLevel, message, params);
+
+ },
+ child: async (_name: string): Promise => { },
+ dispose: (): void => {
+ },
+ setClient: (_client: ILoggerClient | undefined): void => {
+ }
+ };
+ if (isBound(ILoggerServer)) {
+ rebind(ILoggerServer).toConstantValue(logger);
+ } else {
+ bind(ILoggerServer).toConstantValue(logger);
+ }
+
+ if (isBound(ILoggerServer)) {
+ rebind(LoggerFactory).toFactory(ctx =>
+ (name: string) => {
+ const child = new Container({ defaultScope: 'Singleton' });
+ child.parent = ctx.container;
+ child.bind(ILogger).to(Logger).inTransientScope();
+ child.bind(LoggerName).toConstantValue(name);
+ return child.get(ILogger);
+ }
+ );
+ } else {
+ bind(LoggerFactory).toFactory(ctx =>
+ (name: string) => {
+ const child = new Container({ defaultScope: 'Singleton' });
+ child.parent = ctx.container;
+ child.bind(ILogger).to(Logger).inTransientScope();
+ child.bind(LoggerName).toConstantValue(name);
+ return child.get(ILogger);
+ }
+ );
+ }
+});
diff --git a/packages/core/src/browser-only/messaging/frontend-only-service-connection-provider.ts b/packages/core/src/browser-only/messaging/frontend-only-service-connection-provider.ts
new file mode 100644
index 0000000000000..e2208565be4eb
--- /dev/null
+++ b/packages/core/src/browser-only/messaging/frontend-only-service-connection-provider.ts
@@ -0,0 +1,39 @@
+// *****************************************************************************
+// Copyright (C) 2023 EclipseSource and others.
+//
+// This program and the accompanying materials are made available under the
+// terms of the Eclipse Public License v. 2.0 which is available at
+// http://www.eclipse.org/legal/epl-2.0.
+//
+// This Source Code may also be made available under the following Secondary
+// Licenses when the conditions for such availability set forth in the Eclipse
+// Public License v. 2.0 are satisfied: GNU General Public License, version 2
+// with the GNU Classpath Exception which is available at
+// https://www.gnu.org/software/classpath/license.html.
+//
+// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
+// *****************************************************************************
+import { Event, RpcProxy, Channel, RpcProxyFactory, Emitter } from '../../common';
+import { injectable } from 'inversify';
+import { ServiceConnectionProvider } from '../../browser/messaging/service-connection-provider';
+import { ConnectionSource } from '../../browser/messaging/connection-source';
+
+@injectable()
+export class FrontendOnlyConnectionSource implements ConnectionSource {
+ onConnectionDidOpen = new Emitter().event;
+}
+
+@injectable()
+export class FrontendOnlyServiceConnectionProvider extends ServiceConnectionProvider {
+ onSocketDidOpen = Event.None;
+ onSocketDidClose = Event.None;
+ onIncomingMessageActivity = Event.None;
+ override createProxy(path: unknown, target?: unknown): RpcProxy {
+ console.debug(`[Frontend-Only Fallback] Created proxy connection for ${path}`);
+ const factory = target instanceof RpcProxyFactory ? target : new RpcProxyFactory(target);
+ return factory.createProxy();
+ }
+ override listen(path: string, handler: ServiceConnectionProvider.ConnectionHandler, reconnect: boolean): void {
+ console.debug('[Frontend-Only Fallback] Listen to websocket connection requested');
+ }
+}
diff --git a/packages/core/src/browser-only/messaging/messaging-frontend-only-module.ts b/packages/core/src/browser-only/messaging/messaging-frontend-only-module.ts
new file mode 100644
index 0000000000000..55029105c81d8
--- /dev/null
+++ b/packages/core/src/browser-only/messaging/messaging-frontend-only-module.ts
@@ -0,0 +1,42 @@
+// *****************************************************************************
+// Copyright (C) 2023 EclipseSource and others.
+//
+// This program and the accompanying materials are made available under the
+// terms of the Eclipse Public License v. 2.0 which is available at
+// http://www.eclipse.org/legal/epl-2.0.
+//
+// This Source Code may also be made available under the following Secondary
+// Licenses when the conditions for such availability set forth in the Eclipse
+// Public License v. 2.0 are satisfied: GNU General Public License, version 2
+// with the GNU Classpath Exception which is available at
+// https://www.gnu.org/software/classpath/license.html.
+//
+// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
+// *****************************************************************************
+import { ContainerModule } from 'inversify';
+import { WebSocketConnectionSource } from '../../browser/messaging/ws-connection-source';
+import { FrontendOnlyConnectionSource, FrontendOnlyServiceConnectionProvider } from './frontend-only-service-connection-provider';
+import { ConnectionSource } from '../../browser/messaging/connection-source';
+import { LocalConnectionProvider, RemoteConnectionProvider } from '../../browser/messaging/service-connection-provider';
+
+// is loaded directly after the regular message frontend module
+export const messagingFrontendOnlyModule = new ContainerModule((bind, unbind, isBound, rebind) => {
+ unbind(WebSocketConnectionSource);
+ bind(FrontendOnlyConnectionSource).toSelf().inSingletonScope();
+ if (isBound(ConnectionSource)) {
+ rebind(ConnectionSource).toService(FrontendOnlyConnectionSource);
+ } else {
+ bind(ConnectionSource).toService(FrontendOnlyConnectionSource);
+ }
+ bind(FrontendOnlyServiceConnectionProvider).toSelf().inSingletonScope();
+ if (isBound(LocalConnectionProvider)) {
+ rebind(LocalConnectionProvider).toService(FrontendOnlyServiceConnectionProvider);
+ } else {
+ bind(LocalConnectionProvider).toService(FrontendOnlyServiceConnectionProvider);
+ }
+ if (isBound(RemoteConnectionProvider)) {
+ rebind(RemoteConnectionProvider).toService(FrontendOnlyServiceConnectionProvider);
+ } else {
+ bind(RemoteConnectionProvider).toService(FrontendOnlyServiceConnectionProvider);
+ }
+});
diff --git a/packages/core/src/browser-only/preload/frontend-only-preload-module.ts b/packages/core/src/browser-only/preload/frontend-only-preload-module.ts
new file mode 100644
index 0000000000000..ea0ed3fcb7e2a
--- /dev/null
+++ b/packages/core/src/browser-only/preload/frontend-only-preload-module.ts
@@ -0,0 +1,49 @@
+// *****************************************************************************
+// Copyright (C) 2023 TypeFox and others.
+//
+// This program and the accompanying materials are made available under the
+// terms of the Eclipse Public License v. 2.0 which is available at
+// http://www.eclipse.org/legal/epl-2.0.
+//
+// This Source Code may also be made available under the following Secondary
+// Licenses when the conditions for such availability set forth in the Eclipse
+// Public License v. 2.0 are satisfied: GNU General Public License, version 2
+// with the GNU Classpath Exception which is available at
+// https://www.gnu.org/software/classpath/license.html.
+//
+// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
+// *****************************************************************************
+
+import { ContainerModule } from 'inversify';
+import { LocalizationServer } from '../../common/i18n/localization-server';
+import { OS, OSBackendProvider } from '../../common/os';
+import { Localization } from '../../common/i18n/localization';
+
+// loaded after regular preload module
+export default new ContainerModule((bind, unbind, isBound, rebind) => {
+ const frontendOnlyLocalizationServer: LocalizationServer = {
+ loadLocalization: async (languageId: string): Promise => ({ translations: {}, languageId })
+ };
+ if (isBound(LocalizationServer)) {
+ rebind(LocalizationServer).toConstantValue(frontendOnlyLocalizationServer);
+ } else {
+ bind(LocalizationServer).toConstantValue(frontendOnlyLocalizationServer);
+ }
+
+ const frontendOnlyOSBackendProvider: OSBackendProvider = {
+ getBackendOS: async (): Promise => {
+ if (window.navigator.platform.startsWith('Win')) {
+ return OS.Type.Windows;
+ } else if (window.navigator.platform.startsWith('Mac')) {
+ return OS.Type.OSX;
+ } else {
+ return OS.Type.Linux;
+ }
+ }
+ };
+ if (isBound(OSBackendProvider)) {
+ rebind(OSBackendProvider).toConstantValue(frontendOnlyOSBackendProvider);
+ } else {
+ bind(OSBackendProvider).toConstantValue(frontendOnlyOSBackendProvider);
+ }
+});
diff --git a/packages/core/src/browser/frontend-application-module.ts b/packages/core/src/browser/frontend-application-module.ts
index ca1bb7f3ab971..34e857decdf2b 100644
--- a/packages/core/src/browser/frontend-application-module.ts
+++ b/packages/core/src/browser/frontend-application-module.ts
@@ -346,7 +346,6 @@ export const frontendApplicationModule = new ContainerModule((bind, _unbind, _is
});
bind(FrontendConnectionStatusService).toSelf().inSingletonScope();
bind(ConnectionStatusService).toService(FrontendConnectionStatusService);
- bind(FrontendApplicationContribution).toService(FrontendConnectionStatusService);
bind(ApplicationConnectionStatusContribution).toSelf().inSingletonScope();
bind(FrontendApplicationContribution).toService(ApplicationConnectionStatusContribution);
diff --git a/packages/core/src/node/file-uri.ts b/packages/core/src/common/file-uri.ts
similarity index 97%
rename from packages/core/src/node/file-uri.ts
rename to packages/core/src/common/file-uri.ts
index 99a86fbb7b5ca..f92af9fc0fdaf 100644
--- a/packages/core/src/node/file-uri.ts
+++ b/packages/core/src/common/file-uri.ts
@@ -15,8 +15,8 @@
// *****************************************************************************
import { URI as Uri } from 'vscode-uri';
-import URI from '../common/uri';
-import { isWindows } from '../common/os';
+import URI from './uri';
+import { isWindows } from './os';
export namespace FileUri {
diff --git a/packages/core/src/electron-main/electron-main-application.ts b/packages/core/src/electron-main/electron-main-application.ts
index cc9b03039b15c..2efa9fce419e2 100644
--- a/packages/core/src/electron-main/electron-main-application.ts
+++ b/packages/core/src/electron-main/electron-main-application.ts
@@ -24,7 +24,7 @@ import { existsSync, mkdirSync } from 'fs-extra';
import { fork, ForkOptions } from 'child_process';
import { DefaultTheme, FrontendApplicationConfig } from '@theia/application-package/lib/application-props';
import URI from '../common/uri';
-import { FileUri } from '../node/file-uri';
+import { FileUri } from '../common/file-uri';
import { Deferred } from '../common/promise-util';
import { MaybePromise } from '../common/types';
import { ContributionProvider } from '../common/contribution-provider';
diff --git a/packages/core/src/electron-main/theia-electron-window.ts b/packages/core/src/electron-main/theia-electron-window.ts
index 855939e1ca9dc..17c82fa9a4511 100644
--- a/packages/core/src/electron-main/theia-electron-window.ts
+++ b/packages/core/src/electron-main/theia-electron-window.ts
@@ -22,7 +22,7 @@ import { ElectronMainApplicationGlobals } from './electron-main-constants';
import { DisposableCollection, Emitter, Event } from '../common';
import { createDisposableListener } from './event-utils';
import { URI } from '../common/uri';
-import { FileUri } from '../node/file-uri';
+import { FileUri } from '../common/file-uri';
import { TheiaRendererAPI } from './electron-api-main';
/**
diff --git a/packages/core/src/node/env-variables/env-variables-server.ts b/packages/core/src/node/env-variables/env-variables-server.ts
index 3d795a9063dde..7c337992ac90b 100644
--- a/packages/core/src/node/env-variables/env-variables-server.ts
+++ b/packages/core/src/node/env-variables/env-variables-server.ts
@@ -21,7 +21,7 @@ import * as drivelist from 'drivelist';
import { pathExists, mkdir } from 'fs-extra';
import { EnvVariable, EnvVariablesServer } from '../../common/env-variables';
import { isWindows } from '../../common/os';
-import { FileUri } from '../file-uri';
+import { FileUri } from '../../common/file-uri';
@injectable()
export class EnvVariablesServerImpl implements EnvVariablesServer {
diff --git a/packages/core/src/node/file-uri.spec.ts b/packages/core/src/node/file-uri.spec.ts
index 378fe71edfc99..f4689d6a58672 100644
--- a/packages/core/src/node/file-uri.spec.ts
+++ b/packages/core/src/node/file-uri.spec.ts
@@ -17,7 +17,7 @@
import * as os from 'os';
import * as path from 'path';
import * as chai from 'chai';
-import { FileUri } from './file-uri';
+import { FileUri } from '../common/file-uri';
import { isWindows } from '../common/os';
const expect = chai.expect;
diff --git a/packages/core/src/node/index.ts b/packages/core/src/node/index.ts
index 192f8089d99de..c7d735f4f03d6 100644
--- a/packages/core/src/node/index.ts
+++ b/packages/core/src/node/index.ts
@@ -16,7 +16,7 @@
export * from './backend-application';
export * from './debug';
-export * from './file-uri';
+export * from '../common/file-uri';
export * from './messaging';
export * from './cli';
export { FileSystemLocking } from './filesystem-locking';
diff --git a/packages/external-terminal/src/electron-node/linux-external-terminal-service.ts b/packages/external-terminal/src/electron-node/linux-external-terminal-service.ts
index 44180a1fa1743..2c0b34b4d1035 100644
--- a/packages/external-terminal/src/electron-node/linux-external-terminal-service.ts
+++ b/packages/external-terminal/src/electron-node/linux-external-terminal-service.ts
@@ -18,7 +18,7 @@ import * as cp from 'child_process';
import * as fs from '@theia/core/shared/fs-extra';
import { injectable } from '@theia/core/shared/inversify';
import { OS } from '@theia/core/lib/common/os';
-import { FileUri } from '@theia/core/lib/node/file-uri';
+import { FileUri } from '@theia/core/lib/common/file-uri';
import { ExternalTerminalService, ExternalTerminalConfiguration } from '../common/external-terminal';
/*---------------------------------------------------------------------------------------------
diff --git a/packages/external-terminal/src/electron-node/mac-external-terminal-service.ts b/packages/external-terminal/src/electron-node/mac-external-terminal-service.ts
index 9dd06d706fb89..7c5e8bd43acc4 100644
--- a/packages/external-terminal/src/electron-node/mac-external-terminal-service.ts
+++ b/packages/external-terminal/src/electron-node/mac-external-terminal-service.ts
@@ -16,7 +16,7 @@
import * as cp from 'child_process';
import { injectable } from '@theia/core/shared/inversify';
-import { FileUri } from '@theia/core/lib/node/file-uri';
+import { FileUri } from '@theia/core/lib/common/file-uri';
import { ExternalTerminalService, ExternalTerminalConfiguration } from '../common/external-terminal';
/*---------------------------------------------------------------------------------------------
diff --git a/packages/external-terminal/src/electron-node/windows-external-terminal-service.ts b/packages/external-terminal/src/electron-node/windows-external-terminal-service.ts
index d169339c38e95..7e8c54a1a900e 100644
--- a/packages/external-terminal/src/electron-node/windows-external-terminal-service.ts
+++ b/packages/external-terminal/src/electron-node/windows-external-terminal-service.ts
@@ -17,7 +17,7 @@
import * as cp from 'child_process';
import * as path from 'path';
import { injectable } from '@theia/core/shared/inversify';
-import { FileUri } from '@theia/core/lib/node/file-uri';
+import { FileUri } from '@theia/core/lib/common/file-uri';
import { ExternalTerminalService, ExternalTerminalConfiguration } from '../common/external-terminal';
/*---------------------------------------------------------------------------------------------
diff --git a/packages/file-search/src/node/file-search-service-impl.ts b/packages/file-search/src/node/file-search-service-impl.ts
index 8169a5077b788..609be17abef81 100644
--- a/packages/file-search/src/node/file-search-service-impl.ts
+++ b/packages/file-search/src/node/file-search-service-impl.ts
@@ -20,7 +20,7 @@ import * as readline from 'readline';
import { rgPath } from '@vscode/ripgrep';
import { injectable, inject } from '@theia/core/shared/inversify';
import URI from '@theia/core/lib/common/uri';
-import { FileUri } from '@theia/core/lib/node/file-uri';
+import { FileUri } from '@theia/core/lib/common/file-uri';
import { CancellationTokenSource, CancellationToken, ILogger, isWindows } from '@theia/core';
import { RawProcessFactory } from '@theia/process/lib/node';
import { FileSearchService, WHITESPACE_QUERY_SEPARATOR } from '../common/file-search-service';
diff --git a/packages/filesystem/package.json b/packages/filesystem/package.json
index a1517d8fe786f..997fca2738f1f 100644
--- a/packages/filesystem/package.json
+++ b/packages/filesystem/package.json
@@ -11,6 +11,7 @@
"@types/uuid": "^7.0.3",
"async-mutex": "^0.3.1",
"body-parser": "^1.18.3",
+ "browserfs": "^1.4.3",
"http-status-codes": "^1.3.0",
"minimatch": "^5.1.0",
"multer": "1.4.4-lts.1",
@@ -33,6 +34,9 @@
"frontend": "lib/browser/filesystem-frontend-module",
"backend": "lib/node/filesystem-backend-module"
},
+ {
+ "frontendOnly": "lib/browser-only/browser-only-filesystem-frontend-module"
+ },
{
"frontend": "lib/browser/download/file-download-frontend-module",
"backend": "lib/node/download/file-download-backend-module"
diff --git a/packages/filesystem/src/browser-only/browser-only-filesystem-frontend-module.ts b/packages/filesystem/src/browser-only/browser-only-filesystem-frontend-module.ts
new file mode 100644
index 0000000000000..eae27435b6d90
--- /dev/null
+++ b/packages/filesystem/src/browser-only/browser-only-filesystem-frontend-module.ts
@@ -0,0 +1,38 @@
+// *****************************************************************************
+// Copyright (C) 2023 EclipseSource and others.
+//
+// This program and the accompanying materials are made available under the
+// terms of the Eclipse Public License v. 2.0 which is available at
+// http://www.eclipse.org/legal/epl-2.0.
+//
+// This Source Code may also be made available under the following Secondary
+// Licenses when the conditions for such availability set forth in the Eclipse
+// Public License v. 2.0 are satisfied: GNU General Public License, version 2
+// with the GNU Classpath Exception which is available at
+// https://www.gnu.org/software/classpath/license.html.
+//
+// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
+// *****************************************************************************
+
+import { ContainerModule } from '@theia/core/shared/inversify';
+import { FileSystemProvider } from '../common/files';
+import { BrowserFSFileSystemProvider } from './browserfs-filesystem-provider';
+import { RemoteFileSystemProvider, RemoteFileSystemServer } from '../common/remote-file-system-provider';
+import { BrowserFSInitialization, DefaultBrowserFSInitialization } from './browserfs-filesystem-initialization';
+import { BrowserOnlyFileSystemProviderServer } from './browser-only-filesystem-provider-server';
+
+export default new ContainerModule((bind, _unbind, isBound, rebind) => {
+ bind(DefaultBrowserFSInitialization).toSelf();
+ bind(BrowserFSFileSystemProvider).toSelf();
+ bind(BrowserFSInitialization).toService(DefaultBrowserFSInitialization);
+ if (isBound(FileSystemProvider)) {
+ rebind(FileSystemProvider).to(BrowserFSFileSystemProvider).inSingletonScope();
+ } else {
+ bind(FileSystemProvider).to(BrowserFSFileSystemProvider).inSingletonScope();
+ }
+ if (isBound(RemoteFileSystemProvider)) {
+ rebind(RemoteFileSystemServer).to(BrowserOnlyFileSystemProviderServer).inSingletonScope();
+ } else {
+ bind(RemoteFileSystemServer).to(BrowserOnlyFileSystemProviderServer).inSingletonScope();
+ }
+});
diff --git a/packages/filesystem/src/browser-only/browser-only-filesystem-provider-server.ts b/packages/filesystem/src/browser-only/browser-only-filesystem-provider-server.ts
new file mode 100644
index 0000000000000..5b3c2b4a3a555
--- /dev/null
+++ b/packages/filesystem/src/browser-only/browser-only-filesystem-provider-server.ts
@@ -0,0 +1,32 @@
+// *****************************************************************************
+// Copyright (C) 2023 EclipseSource and others.
+//
+// This program and the accompanying materials are made available under the
+// terms of the Eclipse Public License v. 2.0 which is available at
+// http://www.eclipse.org/legal/epl-2.0.
+//
+// This Source Code may also be made available under the following Secondary
+// Licenses when the conditions for such availability set forth in the Eclipse
+// Public License v. 2.0 are satisfied: GNU General Public License, version 2
+// with the GNU Classpath Exception which is available at
+// https://www.gnu.org/software/classpath/license.html.
+//
+// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
+// *****************************************************************************
+
+import { injectable } from '@theia/core/shared/inversify';
+import { FileSystemProviderServer } from '../common/remote-file-system-provider';
+import { Event } from '@theia/core';
+
+/**
+ * Backend component.
+ *
+ * JSON-RPC server exposing a wrapped file system provider remotely.
+ */
+@injectable()
+export class BrowserOnlyFileSystemProviderServer extends FileSystemProviderServer {
+
+ // needed because users expect implicitly the RemoteFileSystemServer to be a RemoteFileSystemProxyFactory
+ onDidOpenConnection = Event.None;
+ onDidCloseConnection = Event.None;
+}
diff --git a/packages/filesystem/src/browser-only/browserfs-filesystem-initialization.ts b/packages/filesystem/src/browser-only/browserfs-filesystem-initialization.ts
new file mode 100644
index 0000000000000..0b17d3076a468
--- /dev/null
+++ b/packages/filesystem/src/browser-only/browserfs-filesystem-initialization.ts
@@ -0,0 +1,61 @@
+// *****************************************************************************
+// Copyright (C) 2023 EclipseSource and others.
+//
+// This program and the accompanying materials are made available under the
+// terms of the Eclipse Public License v. 2.0 which is available at
+// http://www.eclipse.org/legal/epl-2.0.
+//
+// This Source Code may also be made available under the following Secondary
+// Licenses when the conditions for such availability set forth in the Eclipse
+// Public License v. 2.0 are satisfied: GNU General Public License, version 2
+// with the GNU Classpath Exception which is available at
+// https://www.gnu.org/software/classpath/license.html.
+//
+// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
+// *****************************************************************************
+
+import type { FSModule } from 'browserfs/dist/node/core/FS';
+import type { BrowserFSFileSystemProvider } from './browserfs-filesystem-provider';
+import { injectable } from '@theia/core/shared/inversify';
+import { FileSystem, initialize } from 'browserfs';
+import MountableFileSystem from 'browserfs/dist/node/backend/MountableFileSystem';
+
+export const BrowserFSInitialization = Symbol('BrowserFSInitialization');
+export interface BrowserFSInitialization {
+ createMountableFileSystem(): Promise
+ initializeFS: (fs: FSModule, provider: BrowserFSFileSystemProvider) => Promise;
+}
+
+@injectable()
+export class DefaultBrowserFSInitialization implements BrowserFSInitialization {
+
+ createMountableFileSystem(): Promise {
+ return new Promise(resolve => {
+ FileSystem.IndexedDB.Create({}, (e, persistedFS) => {
+ if (e) {
+ throw e;
+ }
+ if (!persistedFS) {
+ throw Error('Could not create filesystem');
+ }
+ FileSystem.MountableFileSystem.Create({
+ '/home': persistedFS
+
+ }, (error, mountableFS) => {
+ if (error) {
+ throw error;
+ }
+ if (!mountableFS) {
+ throw Error('Could not create filesystem');
+ }
+ initialize(mountableFS);
+ resolve(mountableFS);
+ });
+ });
+ });
+ }
+
+ async initializeFS(fs: FSModule, provider: BrowserFSFileSystemProvider): Promise {
+
+ }
+}
diff --git a/packages/filesystem/src/browser-only/browserfs-filesystem-provider.ts b/packages/filesystem/src/browser-only/browserfs-filesystem-provider.ts
new file mode 100644
index 0000000000000..ca833d9536313
--- /dev/null
+++ b/packages/filesystem/src/browser-only/browserfs-filesystem-provider.ts
@@ -0,0 +1,462 @@
+// *****************************************************************************
+// Copyright (C) 2023 EclipseSource and others.
+//
+// This program and the accompanying materials are made available under the
+// terms of the Eclipse Public License v. 2.0 which is available at
+// http://www.eclipse.org/legal/epl-2.0.
+//
+// This Source Code may also be made available under the following Secondary
+// Licenses when the conditions for such availability set forth in the Eclipse
+// Public License v. 2.0 are satisfied: GNU General Public License, version 2
+// with the GNU Classpath Exception which is available at
+// https://www.gnu.org/software/classpath/license.html.
+//
+// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
+// *****************************************************************************
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+// based on https://github.com/microsoft/vscode/blob/04c36be045a94fee58e5f8992d3e3fd980294a84/src/vs/platform/files/node/diskFileSystemProvider.ts
+
+/* eslint-disable no-null/no-null */
+
+import { inject, injectable } from '@theia/core/shared/inversify';
+import {
+ FileChange, FileDeleteOptions, FileOpenOptions,
+ FileOverwriteOptions, FileReadStreamOptions, FileSystemProviderCapabilities,
+ FileSystemProviderError,
+ FileSystemProviderErrorCode,
+ FileSystemProviderWithFileReadWriteCapability,
+ FileType, FileUpdateOptions, FileUpdateResult, FileWriteOptions, Stat, WatchOptions, createFileSystemProviderError
+} from '../common/files';
+import { Event, URI, Disposable, CancellationToken } from '@theia/core';
+import { TextDocumentContentChangeEvent } from '@theia/core/shared/vscode-languageserver-protocol';
+import { ReadableStreamEvents } from '@theia/core/lib/common/stream';
+import { BFSRequire } from 'browserfs';
+import type { FSModule } from 'browserfs/dist/node/core/FS';
+import type { FileSystem } from 'browserfs/dist/node/core/file_system';
+import MountableFileSystem from 'browserfs/dist/node/backend/MountableFileSystem';
+import { basename, dirname, normalize } from 'path';
+import Stats from 'browserfs/dist/node/core/node_fs_stats';
+import { retry } from '@theia/core/lib/common/promise-util';
+import { BrowserFSInitialization } from './browserfs-filesystem-initialization';
+
+// adapted from DiskFileSystemProvider
+@injectable()
+export class BrowserFSFileSystemProvider implements FileSystemProviderWithFileReadWriteCapability {
+ capabilities: FileSystemProviderCapabilities = FileSystemProviderCapabilities.FileReadWrite;
+ onDidChangeCapabilities: Event = Event.None;
+ onDidChangeFile: Event = Event.None;
+ onFileWatchError: Event = Event.None;
+ private mapHandleToPos: Map = new Map();
+ private writeHandles: Set = new Set();
+ private canFlush: boolean = true;
+
+ private fs: FSModule;
+ private mountableFS: MountableFileSystem;
+ private initialized: Promise;
+
+ constructor(@inject(BrowserFSInitialization) readonly initialization: BrowserFSInitialization) {
+ const init = async (): Promise => {
+ this.mountableFS = await initialization.createMountableFileSystem();
+ this.fs = BFSRequire('fs');
+ await initialization.initializeFS(this.fs, new Proxy(this, {
+ get(target, prop, receiver): unknown {
+ if (prop === 'initialized') {
+ return Promise.resolve(true);
+ }
+ return Reflect.get(target, prop, receiver);
+ }
+ }));
+ return true;
+ };
+ this.initialized = init();
+ }
+
+ async mount(mountPoint: string, fs: FileSystem): Promise {
+ await this.initialized;
+ this.mountableFS.mount(mountPoint, fs);
+ };
+
+ watch(_resource: URI, _opts: WatchOptions): Disposable {
+ return Disposable.NULL;
+ }
+ async stat(resource: URI): Promise {
+ await this.initialized;
+ const path = this.toFilePath(resource);
+
+ let stats: Stats;
+ try {
+ stats = await this.promisify(this.fs.stat)(path) as Stats;
+ } catch (error) {
+ throw this.toFileSystemProviderError(error);
+ }
+ if (stats === undefined) {
+ throw new Error(`Could not read file stat for resource '${path}'`);
+ }
+ return {
+ type: this.toType(stats, /* symbolicLink */undefined), // FIXME: missing symbolicLink
+ ctime: stats.birthtime.getTime(), // intentionally not using ctime here, we want the creation time
+ mtime: stats.mtime.getTime(),
+ size: stats.size,
+ // FIXME: missing mode, permissions
+ };
+
+ }
+ async mkdir(resource: URI): Promise {
+ await this.initialized;
+ try {
+ await this.promisify(this.fs.mkdir)(this.toFilePath(resource));
+ } catch (error) {
+ throw this.toFileSystemProviderError(error);
+ }
+ }
+ async readdir(resource: URI): Promise<[string, FileType][]> {
+ await this.initialized;
+ try {
+
+ const children = await this.promisify(this.fs.readdir)(this.toFilePath(resource)) as string[];
+ const result: [string, FileType][] = [];
+ await Promise.all(children.map(async child => {
+ try {
+ const stat = await this.stat(resource.resolve(child));
+ result.push([child, stat.type]);
+ } catch (error) {
+ console.trace(error); // ignore errors for individual entries that can arise from permission denied
+ }
+ }));
+
+ return result;
+ } catch (error) {
+ throw this.toFileSystemProviderError(error);
+ }
+ }
+ async delete(resource: URI, _opts: FileDeleteOptions): Promise {
+ await this.initialized;
+ // FIXME use options
+ try {
+ await this.promisify(this.fs.unlink)(this.toFilePath(resource));
+ } catch (error) {
+ throw this.toFileSystemProviderError(error);
+ }
+ }
+ async rename(from: URI, to: URI, opts: FileOverwriteOptions): Promise {
+ await this.initialized;
+ const fromFilePath = this.toFilePath(from);
+ const toFilePath = this.toFilePath(to);
+ if (fromFilePath === toFilePath) {
+ return; // simulate node.js behaviour here and do a no-op if paths match
+ }
+ try {
+ // assume FS is path case sensitive - correct?
+ const targetExists = await this.promisify(this.fs.exists)(toFilePath);
+ if (targetExists) {
+ throw Error(`File '${toFilePath}' already exists.`);
+ }
+ if (fromFilePath === toFilePath) {
+ return Promise.resolve();
+ }
+
+ await this.promisify(this.fs.rename)(fromFilePath, toFilePath);
+
+ const stat = await this.promisify(this.fs.lstat)(toFilePath) as Stats;
+ if (stat.isDirectory() || stat.isSymbolicLink()) {
+ return Promise.resolve(); // only for files
+ }
+ const fd = await this.promisify(open)(toFilePath, 'a');
+ try {
+ await this.promisify(this.fs.futimes)(fd, stat.atime, new Date());
+ } catch (error) {
+ // ignore
+ }
+
+ this.promisify(this.fs.close)(fd);
+ } catch (error) {
+ // rewrite some typical errors that can happen especially around symlinks
+ // to something the user can better understand
+ if (error.code === 'EINVAL' || error.code === 'EBUSY' || error.code === 'ENAMETOOLONG') {
+ error = new Error(`Unable to move '${basename(fromFilePath)}' into '${basename(dirname(toFilePath))}' (${error.toString()}).`);
+ }
+
+ throw this.toFileSystemProviderError(error);
+ }
+ }
+ async copy?(from: URI, to: URI, opts: FileOverwriteOptions): Promise {
+ await this.initialized;
+ throw new Error('Method not implemented.');
+ }
+ async readFile(resource: URI): Promise {
+ await this.initialized;
+ try {
+ const filePath = this.toFilePath(resource);
+ return await this.promisify(this.fs.readFile)(filePath) as Uint8Array;
+ } catch (error) {
+ throw this.toFileSystemProviderError(error);
+ }
+ }
+ async writeFile(resource: URI, content: Uint8Array, opts: FileWriteOptions): Promise {
+ await this.initialized;
+ let handle: number | undefined = undefined;
+ try {
+ const filePath = this.toFilePath(resource);
+
+ // Validate target unless { create: true, overwrite: true }
+ if (!opts.create || !opts.overwrite) {
+ const fileExists = await this.promisify(this.fs.exists)(filePath);
+ if (fileExists) {
+ if (!opts.overwrite) {
+ throw createFileSystemProviderError('File already exists', FileSystemProviderErrorCode.FileExists);
+ }
+ } else {
+ if (!opts.create) {
+ throw createFileSystemProviderError('File does not exist', FileSystemProviderErrorCode.FileNotFound);
+ }
+ }
+ }
+
+ // Open
+ handle = await this.open(resource, { create: true });
+
+ // Write content at once
+ await this.write(handle, 0, content, 0, content.byteLength);
+ } catch (error) {
+ throw this.toFileSystemProviderError(error);
+ } finally {
+ if (typeof handle === 'number') {
+ await this.close(handle);
+ }
+ }
+ }
+ readFileStream?(resource: URI, opts: FileReadStreamOptions, token: CancellationToken): ReadableStreamEvents {
+ throw new Error('Method not implemented.');
+ }
+ async open(resource: URI, opts: FileOpenOptions): Promise {
+ await this.initialized;
+ try {
+ const filePath = this.toFilePath(resource);
+
+ let flags: string | undefined = undefined;
+ if (opts.create) {
+ // we take opts.create as a hint that the file is opened for writing
+ // as such we use 'w' to truncate an existing or create the
+ // file otherwise. we do not allow reading.
+ if (!flags) {
+ flags = 'w';
+ }
+ } else {
+ // otherwise we assume the file is opened for reading
+ // as such we use 'r' to neither truncate, nor create
+ // the file.
+ flags = 'r';
+ }
+
+ const handle = await this.promisify(this.fs.open)(filePath, flags) as number;
+
+ // remember this handle to track file position of the handle
+ // we init the position to 0 since the file descriptor was
+ // just created and the position was not moved so far (see
+ // also http://man7.org/linux/man-pages/man2/open.2.html -
+ // "The file offset is set to the beginning of the file.")
+ this.mapHandleToPos.set(handle, 0);
+
+ // remember that this handle was used for writing
+ if (opts.create) {
+ this.writeHandles.add(handle);
+ }
+
+ return handle;
+ } catch (error) {
+ throw this.toFileSystemProviderError(error);
+ }
+ }
+ async close(fd: number): Promise {
+ await this.initialized;
+ // remove this handle from map of positions
+ this.mapHandleToPos.delete(fd);
+
+ // if a handle is closed that was used for writing, ensure
+ // to flush the contents to disk if possible.
+ if (this.writeHandles.delete(fd) && this.canFlush) {
+ try {
+ await this.promisify(this.fs.fdatasync)(fd);
+ } catch (error) {
+ // In some exotic setups it is well possible that node fails to sync
+ // In that case we disable flushing and log the error to our logger
+ this.canFlush = false;
+ console.error(error);
+ }
+ }
+
+ await this.promisify(this.fs.close)(fd);
+ }
+ async read(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise {
+ await this.initialized;
+ const normalizedPos = this.normalizePos(fd, pos);
+
+ let bytesRead: number | null = null;
+ try {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const result: { bytesRead: number, buffer: Uint8Array } | number = (await this.promisify(this.fs.read)(fd, data, offset, length, normalizedPos)) as any;
+
+ if (typeof result === 'number') {
+ bytesRead = result; // node.d.ts fail
+ } else {
+ bytesRead = result.bytesRead;
+ }
+
+ return bytesRead;
+ } catch (error) {
+ throw this.toFileSystemProviderError(error);
+ } finally {
+ this.updatePos(fd, normalizedPos, bytesRead);
+ }
+ }
+ async write(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise {
+ await this.initialized;
+ // we know at this point that the file to write to is truncated and thus empty
+ // if the write now fails, the file remains empty. as such we really try hard
+ // to ensure the write succeeds by retrying up to three times.
+ return retry(() => this.doWrite(fd, pos, data, offset, length), 100 /* ms delay */, 3 /* retries */);
+
+ }
+ private async doWrite(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise {
+ await this.initialized;
+ const normalizedPos = this.normalizePos(fd, pos);
+
+ let bytesWritten: number | null = null;
+ try {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const result: { bytesWritten: number, buffer: Uint8Array } | number = (await this.promisify(this.fs.write)(fd, data, offset, length, normalizedPos)) as any;
+
+ if (typeof result === 'number') {
+ bytesWritten = result; // node.d.ts fail
+ } else {
+ bytesWritten = result.bytesWritten;
+ }
+
+ return bytesWritten;
+ } catch (error) {
+ throw this.toFileSystemProviderError(error);
+ } finally {
+ this.updatePos(fd, normalizedPos, bytesWritten);
+ }
+ }
+ private normalizePos(fd: number, pos: number): number | null {
+
+ // when calling fs.read/write we try to avoid passing in the "pos" argument and
+ // rather prefer to pass in "null" because this avoids an extra seek(pos)
+ // call that in some cases can even fail (e.g. when opening a file over FTP -
+ // see https://github.com/microsoft/vscode/issues/73884).
+ //
+ // as such, we compare the passed in position argument with our last known
+ // position for the file descriptor and use "null" if they match.
+ if (pos === this.mapHandleToPos.get(fd)) {
+ return null;
+ }
+
+ return pos;
+ }
+ private updatePos(fd: number, pos: number | null, bytesLength: number | null): void {
+ const lastKnownPos = this.mapHandleToPos.get(fd);
+ if (typeof lastKnownPos === 'number') {
+
+ // pos !== null signals that previously a position was used that is
+ // not null. node.js documentation explains, that in this case
+ // the internal file pointer is not moving and as such we do not move
+ // our position pointer.
+ //
+ // Docs: "If position is null, data will be read from the current file position,
+ // and the file position will be updated. If position is an integer, the file position
+ // will remain unchanged."
+ if (typeof pos === 'number') {
+ // do not modify the position
+ } else if (typeof bytesLength === 'number') {
+ this.mapHandleToPos.set(fd, lastKnownPos + bytesLength);
+ } else {
+ this.mapHandleToPos.delete(fd);
+ }
+ }
+ }
+ async access?(resource: URI, mode?: number | undefined): Promise {
+ await this.initialized;
+ throw new Error('Method not implemented.');
+ }
+ async fsPath?(resource: URI): Promise {
+ await this.initialized;
+ throw new Error('Method not implemented.');
+ }
+ async updateFile?(resource: URI, changes: TextDocumentContentChangeEvent[], opts: FileUpdateOptions): Promise {
+ await this.initialized;
+ throw new Error('Method not implemented.');
+ }
+
+ private toFilePath(resource: URI): string {
+ return normalize(resource.path.toString());
+ }
+
+ private toType(entry: Stats, symbolicLink?: { dangling: boolean }): FileType {
+ // Signal file type by checking for file / directory, except:
+ // - symbolic links pointing to non-existing files are FileType.Unknown
+ // - files that are neither file nor directory are FileType.Unknown
+ let type: FileType;
+ if (symbolicLink?.dangling) {
+ type = FileType.Unknown;
+ } else if (entry.isFile()) {
+ type = FileType.File;
+ } else if (entry.isDirectory()) {
+ type = FileType.Directory;
+ } else {
+ type = FileType.Unknown;
+ }
+
+ // Always signal symbolic link as file type additionally
+ if (symbolicLink) {
+ type |= FileType.SymbolicLink;
+ }
+
+ return type;
+ }
+
+ // FIXME typing
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ private promisify(f: Function): (...args: any[]) => Promise {
+ // eslint-disable-next-line @typescript-eslint/tslint/config, @typescript-eslint/no-explicit-any
+ return function (...args: any[]) {
+ return new Promise((resolve, reject) => {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ f(...args, (err: Error, result: T) => err ? reject(err) : resolve(result));
+ });
+ };
+ }
+
+ private toFileSystemProviderError(error: NodeJS.ErrnoException): FileSystemProviderError {
+ if (error instanceof FileSystemProviderError) {
+ return error; // avoid double conversion
+ }
+
+ let code: FileSystemProviderErrorCode;
+ switch (error.code) {
+ case 'ENOENT':
+ code = FileSystemProviderErrorCode.FileNotFound;
+ break;
+ case 'EISDIR':
+ code = FileSystemProviderErrorCode.FileIsADirectory;
+ break;
+ case 'ENOTDIR':
+ code = FileSystemProviderErrorCode.FileNotADirectory;
+ break;
+ case 'EEXIST':
+ code = FileSystemProviderErrorCode.FileExists;
+ break;
+ case 'EPERM':
+ case 'EACCES':
+ code = FileSystemProviderErrorCode.NoPermissions;
+ break;
+ default:
+ code = FileSystemProviderErrorCode.Unknown;
+ }
+
+ return createFileSystemProviderError(error, code);
+ }
+}
diff --git a/packages/filesystem/src/electron-browser/file-dialog/electron-file-dialog-service.ts b/packages/filesystem/src/electron-browser/file-dialog/electron-file-dialog-service.ts
index c3edec5afd967..a5cd9a7a4ad63 100644
--- a/packages/filesystem/src/electron-browser/file-dialog/electron-file-dialog-service.ts
+++ b/packages/filesystem/src/electron-browser/file-dialog/electron-file-dialog-service.ts
@@ -28,7 +28,7 @@ import { DefaultFileDialogService, OpenFileDialogProps, SaveFileDialogProps } fr
// solution.
//
// eslint-disable-next-line @theia/runtime-import-check
-import { FileUri } from '@theia/core/lib/node/file-uri';
+import { FileUri } from '@theia/core/lib/common/file-uri';
import { OpenDialogOptions, SaveDialogOptions } from '../../electron-common/electron-api';
import '@theia/core/lib/electron-common/electron-api';
diff --git a/packages/filesystem/src/node/disk-file-system-provider.spec.ts b/packages/filesystem/src/node/disk-file-system-provider.spec.ts
index ed0011db27e92..f5f434fb7b0fd 100644
--- a/packages/filesystem/src/node/disk-file-system-provider.spec.ts
+++ b/packages/filesystem/src/node/disk-file-system-provider.spec.ts
@@ -18,7 +18,7 @@ import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposa
import { EncodingService } from '@theia/core/lib/common/encoding-service';
import { ILogger } from '@theia/core/lib/common/logger';
import { MockLogger } from '@theia/core/lib/common/test/mock-logger';
-import { FileUri } from '@theia/core/lib/node/file-uri';
+import { FileUri } from '@theia/core/lib/common/file-uri';
import { IPCConnectionProvider } from '@theia/core/lib/node/messaging/ipc-connection-provider';
import { Container, ContainerModule } from '@theia/core/shared/inversify';
import { equal, fail } from 'assert';
diff --git a/packages/filesystem/src/node/disk-file-system-provider.ts b/packages/filesystem/src/node/disk-file-system-provider.ts
index b5bdf2234934c..e1e1e64e532a3 100644
--- a/packages/filesystem/src/node/disk-file-system-provider.ts
+++ b/packages/filesystem/src/node/disk-file-system-provider.ts
@@ -35,7 +35,7 @@ import {
import { promisify } from 'util';
import URI from '@theia/core/lib/common/uri';
import { Path } from '@theia/core/lib/common/path';
-import { FileUri } from '@theia/core/lib/node/file-uri';
+import { FileUri } from '@theia/core/lib/common/file-uri';
import { Event, Emitter } from '@theia/core/lib/common/event';
import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable';
import { OS, isWindows } from '@theia/core/lib/common/os';
diff --git a/packages/filesystem/src/node/download/directory-archiver.spec.ts b/packages/filesystem/src/node/download/directory-archiver.spec.ts
index a1c1e2c410c21..12a2752f25a8c 100644
--- a/packages/filesystem/src/node/download/directory-archiver.spec.ts
+++ b/packages/filesystem/src/node/download/directory-archiver.spec.ts
@@ -21,7 +21,7 @@ import { extract } from 'tar-fs';
import { expect } from 'chai';
import URI from '@theia/core/lib/common/uri';
import { MockDirectoryArchiver } from './test/mock-directory-archiver';
-import { FileUri } from '@theia/core/lib/node/file-uri';
+import { FileUri } from '@theia/core/lib/common/file-uri';
const track = temp.track();
diff --git a/packages/filesystem/src/node/download/directory-archiver.ts b/packages/filesystem/src/node/download/directory-archiver.ts
index c8c99526e8af8..40513d74f720e 100644
--- a/packages/filesystem/src/node/download/directory-archiver.ts
+++ b/packages/filesystem/src/node/download/directory-archiver.ts
@@ -18,7 +18,7 @@ import { injectable } from '@theia/core/shared/inversify';
import * as fs from '@theia/core/shared/fs-extra';
import { pack } from 'tar-fs';
import URI from '@theia/core/lib/common/uri';
-import { FileUri } from '@theia/core/lib/node/file-uri';
+import { FileUri } from '@theia/core/lib/common/file-uri';
@injectable()
export class DirectoryArchiver {
diff --git a/packages/filesystem/src/node/download/file-download-endpoint.ts b/packages/filesystem/src/node/download/file-download-endpoint.ts
index 6237c1e7ef050..a33523ef82bd0 100644
--- a/packages/filesystem/src/node/download/file-download-endpoint.ts
+++ b/packages/filesystem/src/node/download/file-download-endpoint.ts
@@ -21,7 +21,7 @@ import { injectable, inject, named } from '@theia/core/shared/inversify';
import { json } from 'body-parser';
import { Application, Router } from '@theia/core/shared/express';
import { BackendApplicationContribution } from '@theia/core/lib/node/backend-application';
-import { FileUri } from '@theia/core/lib/node/file-uri';
+import { FileUri } from '@theia/core/lib/common/file-uri';
import { FileDownloadHandler } from './file-download-handler';
@injectable()
diff --git a/packages/filesystem/src/node/download/file-download-handler.ts b/packages/filesystem/src/node/download/file-download-handler.ts
index f1ae5e478c366..7a54513e6bcae 100644
--- a/packages/filesystem/src/node/download/file-download-handler.ts
+++ b/packages/filesystem/src/node/download/file-download-handler.ts
@@ -24,7 +24,7 @@ import { OK, BAD_REQUEST, METHOD_NOT_ALLOWED, NOT_FOUND, INTERNAL_SERVER_ERROR,
import URI from '@theia/core/lib/common/uri';
import { isEmpty } from '@theia/core/lib/common/objects';
import { ILogger } from '@theia/core/lib/common/logger';
-import { FileUri } from '@theia/core/lib/node/file-uri';
+import { FileUri } from '@theia/core/lib/common/file-uri';
import { DirectoryArchiver } from './directory-archiver';
import { FileDownloadData } from '../../common/download/file-download-data';
import { FileDownloadCache, DownloadStorageItem } from './file-download-cache';
diff --git a/packages/filesystem/src/node/file-change-collection.spec.ts b/packages/filesystem/src/node/file-change-collection.spec.ts
index 30f218a5e0eeb..a745e20899084 100644
--- a/packages/filesystem/src/node/file-change-collection.spec.ts
+++ b/packages/filesystem/src/node/file-change-collection.spec.ts
@@ -15,7 +15,7 @@
// *****************************************************************************
import * as assert from 'assert';
-import { FileUri } from '@theia/core/lib/node/file-uri';
+import { FileUri } from '@theia/core/lib/common/file-uri';
import { FileChangeCollection } from './file-change-collection';
import { FileChangeType } from '../common/files';
diff --git a/packages/filesystem/src/node/nsfw-watcher/nsfw-filesystem-service.ts b/packages/filesystem/src/node/nsfw-watcher/nsfw-filesystem-service.ts
index 6e4723076fea3..ebc947846e804 100644
--- a/packages/filesystem/src/node/nsfw-watcher/nsfw-filesystem-service.ts
+++ b/packages/filesystem/src/node/nsfw-watcher/nsfw-filesystem-service.ts
@@ -18,7 +18,7 @@ import nsfw = require('@theia/core/shared/nsfw');
import path = require('path');
import { promises as fsp } from 'fs';
import { IMinimatch, Minimatch } from 'minimatch';
-import { FileUri } from '@theia/core/lib/node/file-uri';
+import { FileUri } from '@theia/core/lib/common/file-uri';
import {
FileChangeType, FileSystemWatcherService, FileSystemWatcherServiceClient, WatchOptions
} from '../../common/filesystem-watcher-protocol';
diff --git a/packages/git/src/node/dugite-git-watcher.slow-spec.ts b/packages/git/src/node/dugite-git-watcher.slow-spec.ts
index 5a4f0e95bdd4f..843e092029471 100644
--- a/packages/git/src/node/dugite-git-watcher.slow-spec.ts
+++ b/packages/git/src/node/dugite-git-watcher.slow-spec.ts
@@ -18,7 +18,7 @@ import * as fs from '@theia/core/shared/fs-extra';
import * as temp from 'temp';
import * as path from 'path';
import { expect } from 'chai';
-import { FileUri } from '@theia/core/lib/node/file-uri';
+import { FileUri } from '@theia/core/lib/common/file-uri';
import { Git } from '../common/git';
import { DugiteGit } from './dugite-git';
import { Repository } from '../common';
diff --git a/packages/git/src/node/dugite-git.slow-spec.ts b/packages/git/src/node/dugite-git.slow-spec.ts
index 888507b401d54..b4954dd321188 100644
--- a/packages/git/src/node/dugite-git.slow-spec.ts
+++ b/packages/git/src/node/dugite-git.slow-spec.ts
@@ -16,7 +16,7 @@
import * as temp from 'temp';
import { expect } from 'chai';
-import { FileUri } from '@theia/core/lib/node/file-uri';
+import { FileUri } from '@theia/core/lib/common/file-uri';
import { GitFileStatus } from '../common';
import { createGit } from './test/binding-helper';
diff --git a/packages/git/src/node/dugite-git.spec.ts b/packages/git/src/node/dugite-git.spec.ts
index db3592f321c7c..340fafaf6e1b2 100644
--- a/packages/git/src/node/dugite-git.spec.ts
+++ b/packages/git/src/node/dugite-git.spec.ts
@@ -22,7 +22,7 @@ import * as fs from '@theia/core/shared/fs-extra';
import { expect } from 'chai';
import { Git } from '../common/git';
import { git as gitExec } from 'dugite-extra/lib/core/git';
-import { FileUri } from '@theia/core/lib/node/file-uri';
+import { FileUri } from '@theia/core/lib/common/file-uri';
import { WorkingDirectoryStatus, Repository, GitUtils, GitFileStatus, GitFileChange } from '../common';
import { initRepository, createTestRepository } from 'dugite-extra/lib/command/test-helper';
import { createGit } from './test/binding-helper';
diff --git a/packages/git/src/node/dugite-git.ts b/packages/git/src/node/dugite-git.ts
index e72ad9dee1724..857c757b12ba6 100644
--- a/packages/git/src/node/dugite-git.ts
+++ b/packages/git/src/node/dugite-git.ts
@@ -24,7 +24,7 @@ import { clone } from 'dugite-extra/lib/command/clone';
import { fetch } from 'dugite-extra/lib/command/fetch';
import { stash } from 'dugite-extra/lib/command/stash';
import { merge } from 'dugite-extra/lib/command/merge';
-import { FileUri } from '@theia/core/lib/node/file-uri';
+import { FileUri } from '@theia/core/lib/common/file-uri';
import { getStatus } from 'dugite-extra/lib/command/status';
import { createCommit } from 'dugite-extra/lib/command/commit';
import { stage, unstage } from 'dugite-extra/lib/command/stage';
diff --git a/packages/mini-browser/src/node/mini-browser-endpoint.ts b/packages/mini-browser/src/node/mini-browser-endpoint.ts
index f8400b862bfff..1c3420bf5eba7 100644
--- a/packages/mini-browser/src/node/mini-browser-endpoint.ts
+++ b/packages/mini-browser/src/node/mini-browser-endpoint.ts
@@ -20,7 +20,7 @@ import * as fs from '@theia/core/shared/fs-extra';
import { lookup } from 'mime-types';
import { injectable, inject, named } from '@theia/core/shared/inversify';
import { Application, Request, Response } from '@theia/core/shared/express';
-import { FileUri } from '@theia/core/lib/node/file-uri';
+import { FileUri } from '@theia/core/lib/common/file-uri';
import { ILogger } from '@theia/core/lib/common/logger';
import { MaybePromise } from '@theia/core/lib/common/types';
import { ContributionProvider } from '@theia/core/lib/common/contribution-provider';
diff --git a/packages/navigator/src/browser/navigator-diff.spec.ts b/packages/navigator/src/browser/navigator-diff.spec.ts
index ced5d792c6678..2f70af9ace45f 100644
--- a/packages/navigator/src/browser/navigator-diff.spec.ts
+++ b/packages/navigator/src/browser/navigator-diff.spec.ts
@@ -31,7 +31,7 @@ import { OpenerService } from '@theia/core/lib/browser';
import { MockOpenerService } from '@theia/core/lib/browser/test/mock-opener-service';
import { MessageService } from '@theia/core/lib/common/message-service';
import { MessageClient } from '@theia/core/lib/common/message-service-protocol';
-import { FileUri } from '@theia/core/lib/node/file-uri';
+import { FileUri } from '@theia/core/lib/common/file-uri';
import { FileService } from '@theia/filesystem/lib/browser/file-service';
import { DiskFileSystemProvider } from '@theia/filesystem/lib/node/disk-file-system-provider';
diff --git a/packages/plugin-dev/src/node/hosted-instance-manager.ts b/packages/plugin-dev/src/node/hosted-instance-manager.ts
index dc11d18603478..e56ad1d1932f7 100644
--- a/packages/plugin-dev/src/node/hosted-instance-manager.ts
+++ b/packages/plugin-dev/src/node/hosted-instance-manager.ts
@@ -24,7 +24,7 @@ import URI from '@theia/core/lib/common/uri';
import { ContributionProvider } from '@theia/core/lib/common/contribution-provider';
import { HostedPluginUriPostProcessor, HostedPluginUriPostProcessorSymbolName } from './hosted-plugin-uri-postprocessor';
import { environment, isWindows } from '@theia/core';
-import { FileUri } from '@theia/core/lib/node/file-uri';
+import { FileUri } from '@theia/core/lib/common/file-uri';
import { LogType } from '@theia/plugin-ext/lib/common/types';
import { HostedPluginSupport } from '@theia/plugin-ext/lib/hosted/node/hosted-plugin';
import { MetadataScanner } from '@theia/plugin-ext/lib/hosted/node/metadata-scanner';
diff --git a/packages/plugin-ext-vscode/src/node/plugin-vscode-file-handler.ts b/packages/plugin-ext-vscode/src/node/plugin-vscode-file-handler.ts
index f3f6459aa95cc..599d61fae2158 100644
--- a/packages/plugin-ext-vscode/src/node/plugin-vscode-file-handler.ts
+++ b/packages/plugin-ext-vscode/src/node/plugin-vscode-file-handler.ts
@@ -18,8 +18,8 @@ import { PluginDeployerFileHandler, PluginDeployerEntry, PluginDeployerFileHandl
import * as filenamify from 'filenamify';
import { inject, injectable } from '@theia/core/shared/inversify';
import * as fs from '@theia/core/shared/fs-extra';
-import { FileUri } from '@theia/core/lib/node';
import { PluginVSCodeEnvironment } from '../common/plugin-vscode-environment';
+import { FileUri } from '@theia/core/lib/common/file-uri';
import { unpackToDeploymentDir } from './plugin-vscode-utils';
export const isVSCodePluginFile = (pluginPath?: string) => Boolean(pluginPath && (pluginPath.endsWith('.vsix') || pluginPath.endsWith('.tgz')));
diff --git a/packages/plugin-ext/src/hosted/node/scanners/file-plugin-uri-factory.ts b/packages/plugin-ext/src/hosted/node/scanners/file-plugin-uri-factory.ts
index 3ad460efd6c78..74518f5097d66 100644
--- a/packages/plugin-ext/src/hosted/node/scanners/file-plugin-uri-factory.ts
+++ b/packages/plugin-ext/src/hosted/node/scanners/file-plugin-uri-factory.ts
@@ -17,7 +17,7 @@
import { injectable } from '@theia/core/shared/inversify';
import * as path from 'path';
import URI from '@theia/core/lib/common/uri';
-import { FileUri } from '@theia/core/lib/node/file-uri';
+import { FileUri } from '@theia/core/lib/common/file-uri';
import { PluginPackage } from '../../../common';
import { PluginUriFactory } from './plugin-uri-factory';
/**
diff --git a/packages/plugin-ext/src/main/node/handlers/plugin-theia-file-handler.ts b/packages/plugin-ext/src/main/node/handlers/plugin-theia-file-handler.ts
index 28983e18b15af..367bdf5aa4d01 100644
--- a/packages/plugin-ext/src/main/node/handlers/plugin-theia-file-handler.ts
+++ b/packages/plugin-ext/src/main/node/handlers/plugin-theia-file-handler.ts
@@ -21,7 +21,7 @@ import { Deferred } from '@theia/core/lib/common/promise-util';
import { getTempDirPathAsync } from '../temp-dir-util';
import * as fs from '@theia/core/shared/fs-extra';
import * as filenamify from 'filenamify';
-import { FileUri } from '@theia/core/lib/node/file-uri';
+import { FileUri } from '@theia/core/lib/common/file-uri';
import { PluginTheiaEnvironment } from '../../common/plugin-theia-environment';
@injectable()
diff --git a/packages/plugin-ext/src/main/node/plugins-key-value-storage.ts b/packages/plugin-ext/src/main/node/plugins-key-value-storage.ts
index 833654c727e98..48aa75ebc3fa1 100644
--- a/packages/plugin-ext/src/main/node/plugins-key-value-storage.ts
+++ b/packages/plugin-ext/src/main/node/plugins-key-value-storage.ts
@@ -18,7 +18,7 @@ import { injectable, inject, postConstruct } from '@theia/core/shared/inversify'
import { FileSystemLocking } from '@theia/core/lib/node';
import * as fs from '@theia/core/shared/fs-extra';
import * as path from 'path';
-import { FileUri } from '@theia/core/lib/node/file-uri';
+import { FileUri } from '@theia/core/lib/common/file-uri';
import { Deferred } from '@theia/core/lib/common/promise-util';
import { EnvVariablesServer } from '@theia/core/lib/common/env-variables';
import { PluginPaths } from './paths/const';
diff --git a/packages/search-in-workspace/src/node/ripgrep-search-in-workspace-server.slow-spec.ts b/packages/search-in-workspace/src/node/ripgrep-search-in-workspace-server.slow-spec.ts
index 70f659b355bd0..d33d0e8c1a736 100644
--- a/packages/search-in-workspace/src/node/ripgrep-search-in-workspace-server.slow-spec.ts
+++ b/packages/search-in-workspace/src/node/ripgrep-search-in-workspace-server.slow-spec.ts
@@ -16,7 +16,7 @@
import { Container } from '@theia/core/shared/inversify';
import { ILogger, isWindows } from '@theia/core';
-import { FileUri } from '@theia/core/lib/node/file-uri';
+import { FileUri } from '@theia/core/lib/common/file-uri';
import { MockLogger } from '@theia/core/lib/common/test/mock-logger';
import { RawProcessFactory, RawProcessOptions, RawProcess, ProcessManager } from '@theia/process/lib/node';
import { RipgrepSearchInWorkspaceServer, RgPath } from './ripgrep-search-in-workspace-server';
diff --git a/packages/search-in-workspace/src/node/ripgrep-search-in-workspace-server.ts b/packages/search-in-workspace/src/node/ripgrep-search-in-workspace-server.ts
index a10fc6588a15c..dc55caa616149 100644
--- a/packages/search-in-workspace/src/node/ripgrep-search-in-workspace-server.ts
+++ b/packages/search-in-workspace/src/node/ripgrep-search-in-workspace-server.ts
@@ -18,7 +18,7 @@ import * as fs from '@theia/core/shared/fs-extra';
import * as path from 'path';
import { ILogger } from '@theia/core';
import { RawProcess, RawProcessFactory, RawProcessOptions } from '@theia/process/lib/node';
-import { FileUri } from '@theia/core/lib/node/file-uri';
+import { FileUri } from '@theia/core/lib/common/file-uri';
import URI from '@theia/core/lib/common/uri';
import { inject, injectable } from '@theia/core/shared/inversify';
import { SearchInWorkspaceServer, SearchInWorkspaceOptions, SearchInWorkspaceResult, SearchInWorkspaceClient, LinePreview } from '../common/search-in-workspace-interface';
diff --git a/packages/terminal/src/node/shell-process.ts b/packages/terminal/src/node/shell-process.ts
index 524a697082c7e..4bf64080756c5 100644
--- a/packages/terminal/src/node/shell-process.ts
+++ b/packages/terminal/src/node/shell-process.ts
@@ -20,7 +20,7 @@ import { ILogger } from '@theia/core/lib/common/logger';
import { TerminalProcess, TerminalProcessOptions, ProcessManager, MultiRingBuffer } from '@theia/process/lib/node';
import { isWindows, isOSX } from '@theia/core/lib/common';
import URI from '@theia/core/lib/common/uri';
-import { FileUri } from '@theia/core/lib/node/file-uri';
+import { FileUri } from '@theia/core/lib/common/file-uri';
import { EnvironmentUtils } from '@theia/core/lib/node/environment-utils';
import { parseArgs } from '@theia/process/lib/node/utils';
diff --git a/packages/workspace/package.json b/packages/workspace/package.json
index 797c43fedbdb2..542c9a1b56205 100644
--- a/packages/workspace/package.json
+++ b/packages/workspace/package.json
@@ -16,6 +16,9 @@
{
"frontend": "lib/browser/workspace-frontend-module",
"backend": "lib/node/workspace-backend-module"
+ },
+ {
+ "frontendOnly": "lib/browser-only/workspace-frontend-only-module"
}
],
"keywords": [
diff --git a/packages/workspace/src/browser-only/browser-only-workspace-server.ts b/packages/workspace/src/browser-only/browser-only-workspace-server.ts
new file mode 100644
index 0000000000000..2c94b3b997791
--- /dev/null
+++ b/packages/workspace/src/browser-only/browser-only-workspace-server.ts
@@ -0,0 +1,69 @@
+// *****************************************************************************
+// Copyright (C) 2023 EclipseSource and others.
+//
+// This program and the accompanying materials are made available under the
+// terms of the Eclipse Public License v. 2.0 which is available at
+// http://www.eclipse.org/legal/epl-2.0.
+//
+// This Source Code may also be made available under the following Secondary
+// Licenses when the conditions for such availability set forth in the Eclipse
+// Public License v. 2.0 are satisfied: GNU General Public License, version 2
+// with the GNU Classpath Exception which is available at
+// https://www.gnu.org/software/classpath/license.html.
+//
+// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
+// *****************************************************************************
+import { inject, injectable } from '@theia/core/shared/inversify';
+import { WorkspaceServer } from '../common/workspace-protocol';
+import { ILogger, isStringArray } from '@theia/core';
+import { FileService } from '@theia/filesystem/lib/browser/file-service';
+
+export const RECENT_WORKSPACES_LOCAL_STORAGE_KEY = 'workspaces';
+
+@injectable()
+export class BrowserOnlyWorkspaceServer implements WorkspaceServer {
+
+ @inject(ILogger)
+ protected logger: ILogger;
+
+ @inject(FileService)
+ protected readonly fileService: FileService;
+
+ async getRecentWorkspaces(): Promise {
+ const storedWorkspaces = localStorage.getItem(RECENT_WORKSPACES_LOCAL_STORAGE_KEY);
+ if (!storedWorkspaces) {
+ return [];
+ }
+ try {
+ const parsedWorkspaces = JSON.parse(storedWorkspaces);
+ if (isStringArray(parsedWorkspaces)) {
+ return parsedWorkspaces;
+ }
+ } catch (e) {
+ this.logger.error(e);
+ return [];
+ }
+ return [];
+ }
+
+ async getMostRecentlyUsedWorkspace(): Promise {
+ const workspaces = await this.getRecentWorkspaces();
+ return workspaces[0];
+ }
+
+ async setMostRecentlyUsedWorkspace(uri: string): Promise {
+ const workspaces = await this.getRecentWorkspaces();
+ if (workspaces.includes(uri)) {
+ workspaces.splice(workspaces.indexOf(uri), 1);
+ }
+ localStorage.setItem(RECENT_WORKSPACES_LOCAL_STORAGE_KEY, JSON.stringify([uri, ...workspaces]));
+ }
+
+ async removeRecentWorkspace(uri: string): Promise {
+ const workspaces = await this.getRecentWorkspaces();
+ if (workspaces.includes(uri)) {
+ workspaces.splice(workspaces.indexOf(uri), 1);
+ }
+ localStorage.setItem(RECENT_WORKSPACES_LOCAL_STORAGE_KEY, JSON.stringify(workspaces));
+ }
+}
diff --git a/packages/workspace/src/browser-only/workspace-frontend-only-module.ts b/packages/workspace/src/browser-only/workspace-frontend-only-module.ts
new file mode 100644
index 0000000000000..2953f66fdfd2d
--- /dev/null
+++ b/packages/workspace/src/browser-only/workspace-frontend-only-module.ts
@@ -0,0 +1,28 @@
+// *****************************************************************************
+// Copyright (C) 2023 EclipseSource and others.
+//
+// This program and the accompanying materials are made available under the
+// terms of the Eclipse Public License v. 2.0 which is available at
+// http://www.eclipse.org/legal/epl-2.0.
+//
+// This Source Code may also be made available under the following Secondary
+// Licenses when the conditions for such availability set forth in the Eclipse
+// Public License v. 2.0 are satisfied: GNU General Public License, version 2
+// with the GNU Classpath Exception which is available at
+// https://www.gnu.org/software/classpath/license.html.
+//
+// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
+// *****************************************************************************
+
+import { ContainerModule, interfaces } from '@theia/core/shared/inversify';
+import { BrowserOnlyWorkspaceServer } from './browser-only-workspace-server';
+import { WorkspaceServer } from '../common';
+
+export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Unbind, isBound: interfaces.IsBound, rebind: interfaces.Rebind) => {
+ bind(BrowserOnlyWorkspaceServer).toSelf().inSingletonScope();
+ if (isBound(WorkspaceServer)) {
+ rebind(WorkspaceServer).toService(BrowserOnlyWorkspaceServer);
+ } else {
+ bind(WorkspaceServer).toService(BrowserOnlyWorkspaceServer);
+ }
+});
diff --git a/tsconfig.json b/tsconfig.json
index 732d0029c6500..ac07be3ca619e 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -42,6 +42,9 @@
{
"path": "examples/browser"
},
+ {
+ "path": "examples/browser-only"
+ },
{
"path": "examples/electron"
},
diff --git a/yarn.lock b/yarn.lock
index 4a9da1bdbf274..011b396fd23e3 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3160,6 +3160,13 @@ async-mutex@^0.4.0:
dependencies:
tslib "^2.4.0"
+async@^2.1.4, async@^2.6.4:
+ version "2.6.4"
+ resolved "https://registry.yarnpkg.com/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221"
+ integrity sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==
+ dependencies:
+ lodash "^4.17.14"
+
async@^3.2.3, async@^3.2.4:
version "3.2.4"
resolved "https://registry.yarnpkg.com/async/-/async-3.2.4.tgz#2d22e00f8cddeb5fde5dd33522b56d1cf569a81c"
@@ -3295,6 +3302,13 @@ base64id@2.0.0, base64id@~2.0.0:
resolved "https://registry.yarnpkg.com/base64id/-/base64id-2.0.0.tgz#2770ac6bc47d312af97a8bf9a634342e0cd25cb6"
integrity sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==
+basic-auth@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-2.0.1.tgz#b998279bf47ce38344b4f3cf916d4679bbf51e3a"
+ integrity sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==
+ dependencies:
+ safe-buffer "5.1.2"
+
bcrypt-pbkdf@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e"
@@ -3446,6 +3460,14 @@ browser-stdout@1.3.1:
resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60"
integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==
+browserfs@^1.4.3:
+ version "1.4.3"
+ resolved "https://registry.yarnpkg.com/browserfs/-/browserfs-1.4.3.tgz#92ffc6063967612daccdb8566d3fc03f521205fb"
+ integrity sha512-tz8HClVrzTJshcyIu8frE15cjqjcBIu15Bezxsvl/i+6f59iNCN3kznlWjz0FEb3DlnDx3gW5szxeT6D1x0s0w==
+ dependencies:
+ async "^2.1.4"
+ pako "^1.0.4"
+
browserslist@^4.14.5, browserslist@^4.21.9, browserslist@^4.22.1:
version "4.22.1"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.22.1.tgz#ba91958d1a59b87dab6fed8dfbcb3da5e2e9c619"
@@ -3702,7 +3724,7 @@ chalk@^2.3.0, chalk@^2.4.1, chalk@^2.4.2:
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"
-chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.1:
+chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
@@ -4200,6 +4222,11 @@ cors@~2.8.5:
object-assign "^4"
vary "^1"
+corser@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/corser/-/corser-2.0.1.tgz#8eda252ecaab5840dcd975ceb90d9370c819ff87"
+ integrity sha512-utCYNzRSQIZNPIcGZdQc92UVJYAhtGAteCFg0yRaFm8f0P+CPtyGyHXJcGXnffjCybUCEx3FQ2G7U3/o9eIkVQ==
+
cosmiconfig@8.0.0:
version "8.0.0"
resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-8.0.0.tgz#e9feae014eab580f858f8a0288f38997a7bebe97"
@@ -5315,7 +5342,7 @@ event-stream@=3.3.4:
stream-combiner "~0.0.4"
through "~2.3.1"
-eventemitter3@^4.0.4:
+eventemitter3@^4.0.0, eventemitter3@^4.0.4:
version "4.0.7"
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f"
integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==
@@ -5670,7 +5697,7 @@ flatted@^3.2.9:
resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.9.tgz#7eb4c67ca1ba34232ca9d2d93e9886e611ad7daf"
integrity sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==
-follow-redirects@^1.14.0, follow-redirects@^1.15.0:
+follow-redirects@^1.0.0, follow-redirects@^1.14.0, follow-redirects@^1.15.0:
version "1.15.3"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.3.tgz#fe2f3ef2690afce7e82ed0b44db08165b207123a"
integrity sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==
@@ -6258,7 +6285,7 @@ hasha@^5.0.0:
is-stream "^2.0.0"
type-fest "^0.8.0"
-he@1.2.0:
+he@1.2.0, he@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
@@ -6355,6 +6382,34 @@ http-proxy-agent@^5.0.0:
agent-base "6"
debug "4"
+http-proxy@^1.18.1:
+ version "1.18.1"
+ resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549"
+ integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==
+ dependencies:
+ eventemitter3 "^4.0.0"
+ follow-redirects "^1.0.0"
+ requires-port "^1.0.0"
+
+http-server@^14.1.1:
+ version "14.1.1"
+ resolved "https://registry.yarnpkg.com/http-server/-/http-server-14.1.1.tgz#d60fbb37d7c2fdff0f0fbff0d0ee6670bd285e2e"
+ integrity sha512-+cbxadF40UXd9T01zUHgA+rlo2Bg1Srer4+B4NwIHdaGxAGGv59nYRnGGDJ9LBk7alpS0US+J+bLLdQOOkJq4A==
+ dependencies:
+ basic-auth "^2.0.1"
+ chalk "^4.1.2"
+ corser "^2.0.1"
+ he "^1.2.0"
+ html-encoding-sniffer "^3.0.0"
+ http-proxy "^1.18.1"
+ mime "^1.6.0"
+ minimist "^1.2.6"
+ opener "^1.5.1"
+ portfinder "^1.0.28"
+ secure-compare "3.0.1"
+ union "~0.5.0"
+ url-join "^4.0.1"
+
http-status-codes@^1.3.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/http-status-codes/-/http-status-codes-1.4.0.tgz#6e4c15d16ff3a9e2df03b89f3a55e1aae05fb477"
@@ -7558,7 +7613,7 @@ lodash.union@^4.6.0:
resolved "https://registry.yarnpkg.com/lodash.union/-/lodash.union-4.6.0.tgz#48bb5088409f16f1821666641c44dd1aaae3cd88"
integrity sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==
-lodash@^4.17.15, lodash@^4.17.21, lodash@^4.5.1:
+lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.21, lodash@^4.5.1:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@@ -7821,7 +7876,7 @@ mime-types@^2.1.12, mime-types@^2.1.18, mime-types@^2.1.27, mime-types@~2.1.24,
dependencies:
mime-db "1.52.0"
-mime@1.6.0, mime@^1.3.4, mime@^1.4.1:
+mime@1.6.0, mime@^1.3.4, mime@^1.4.1, mime@^1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
@@ -8018,7 +8073,7 @@ mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3:
resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113"
integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==
-"mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.4:
+"mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.4, mkdirp@^0.5.6:
version "0.5.6"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6"
integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==
@@ -8786,6 +8841,11 @@ open@^8.4.0:
is-docker "^2.1.1"
is-wsl "^2.2.0"
+opener@^1.5.1:
+ version "1.5.2"
+ resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598"
+ integrity sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==
+
optionator@^0.9.1:
version "0.9.3"
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.3.tgz#007397d44ed1872fdc6ed31360190f81814e2c64"
@@ -8992,6 +9052,11 @@ pacote@^15.2.0:
ssri "^10.0.0"
tar "^6.1.11"
+pako@^1.0.4:
+ version "1.0.11"
+ resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf"
+ integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==
+
parent-module@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"
@@ -9234,6 +9299,15 @@ playwright@1.38.1:
optionalDependencies:
fsevents "2.3.2"
+portfinder@^1.0.28:
+ version "1.0.32"
+ resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.32.tgz#2fe1b9e58389712429dc2bea5beb2146146c7f81"
+ integrity sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg==
+ dependencies:
+ async "^2.6.4"
+ debug "^3.2.7"
+ mkdirp "^0.5.6"
+
postcss-modules-extract-imports@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz#cda1f047c0ae80c97dbe28c3e76a43b88025741d"
@@ -9530,7 +9604,7 @@ qs@6.11.0:
dependencies:
side-channel "^1.0.4"
-qs@^6.9.1, qs@^6.9.4:
+qs@^6.4.0, qs@^6.9.1, qs@^6.9.4:
version "6.11.2"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.2.tgz#64bea51f12c1f5da1bc01496f48ffcff7c69d7d9"
integrity sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==
@@ -10058,16 +10132,16 @@ safe-array-concat@^1.0.1:
has-symbols "^1.0.3"
isarray "^2.0.5"
+safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
+ version "5.1.2"
+ resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
+ integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
+
safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@~5.2.0:
version "5.2.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
-safe-buffer@~5.1.0, safe-buffer@~5.1.1:
- version "5.1.2"
- resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
- integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
-
safe-regex-test@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.0.tgz#793b874d524eb3640d1873aad03596db2d4f2295"
@@ -10141,6 +10215,11 @@ schema-utils@^4.0.0:
ajv-formats "^2.1.1"
ajv-keywords "^5.1.0"
+secure-compare@3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/secure-compare/-/secure-compare-3.0.1.tgz#f1a0329b308b221fae37b9974f3d578d0ca999e3"
+ integrity sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw==
+
seek-bzip@^1.0.5:
version "1.0.6"
resolved "https://registry.yarnpkg.com/seek-bzip/-/seek-bzip-1.0.6.tgz#35c4171f55a680916b52a07859ecf3b5857f21c4"
@@ -11429,6 +11508,13 @@ unicode-property-aliases-ecmascript@^2.0.0:
resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz#43d41e3be698bd493ef911077c9b131f827e8ccd"
integrity sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==
+union@~0.5.0:
+ version "0.5.0"
+ resolved "https://registry.yarnpkg.com/union/-/union-0.5.0.tgz#b2c11be84f60538537b846edb9ba266ba0090075"
+ integrity sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==
+ dependencies:
+ qs "^6.4.0"
+
unique-filename@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-3.0.0.tgz#48ba7a5a16849f5080d26c760c86cf5cf05770ea"