diff --git a/dev-packages/application-manager/src/application-package-manager.ts b/dev-packages/application-manager/src/application-package-manager.ts index deb08c5fe9b95..5c80e467c8661 100644 --- a/dev-packages/application-manager/src/application-package-manager.ts +++ b/dev-packages/application-manager/src/application-package-manager.ts @@ -61,7 +61,12 @@ export class ApplicationPackageManager { async copy(): Promise { await fs.ensureDir(this.pck.lib()); - await fs.copy(this.pck.frontend('index.html'), this.pck.lib('index.html')); + if (this.pck.isHybrid()) { + await fs.copy(this.pck.frontend('electron', 'index.html'), this.pck.lib('electron', 'index.html')); + await fs.copy(this.pck.frontend('browser', 'index.html'), this.pck.lib('browser', 'index.html')); + } else { + await fs.copy(this.pck.frontend('index.html'), this.pck.lib('index.html')); + } } async build(args: string[] = []): Promise { @@ -73,8 +78,14 @@ export class ApplicationPackageManager { async start(args: string[] = []): Promise { if (this.pck.isElectron()) { return this.startElectron(args); + + } else if (this.pck.isBrowser()) { + return this.startBrowser(args); + + } else if (this.pck.isHybrid()) { + return this.startHybrid(args); } - return this.startBrowser(args); + throw new Error(`Unknown target: '${this.pck.target}'`); } async startElectron(args: string[]): Promise { @@ -99,4 +110,8 @@ export class ApplicationPackageManager { this.__process.fork(this.pck.backend('main.js'), mainArgs, options); } + async startHybrid(args: string[]): Promise { + return this.startBrowser(args); + } + } diff --git a/dev-packages/application-manager/src/generator/abstract-generator.ts b/dev-packages/application-manager/src/generator/abstract-generator.ts index 2a3e465a10669..7def9d3abd4c3 100644 --- a/dev-packages/application-manager/src/generator/abstract-generator.ts +++ b/dev-packages/application-manager/src/generator/abstract-generator.ts @@ -59,6 +59,14 @@ export abstract class AbstractGenerator { return os.EOL + lines.join(os.EOL); } + get package(): ApplicationPackage { + return this.pck; + } + + normalized(string: string): string { + return string.replace(/\W/, ''); + } + protected ifBrowser(value: string, defaultValue: string = '') { return this.pck.ifBrowser(value, defaultValue); } @@ -67,6 +75,10 @@ export abstract class AbstractGenerator { return this.pck.ifElectron(value, defaultValue); } + protected ifHybrid(value: string, defaultValue: string = '') { + return this.pck.ifHybrid(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 58ce8194c4ff6..3a10260ba47f8 100644 --- a/dev-packages/application-manager/src/generator/backend-generator.ts +++ b/dev-packages/application-manager/src/generator/backend-generator.ts @@ -24,8 +24,32 @@ export class BackendGenerator extends AbstractGenerator { await this.write(this.pck.backend('main.js'), this.compileMain(backendModules)); } + protected compileExpressStatic(segment: string = ''): string { + return `express.static(path.join(__dirname, '../../lib${segment}'), { + index: 'index.html' + })`; + } + + protected compileMiddleware(backendModules: Map): string { + return this.pck.isHybrid() ? ` + const electron = ${this.compileExpressStatic('/electron')}; + const browser = ${this.compileExpressStatic('/browser')}; + + application.use('*', (request, ...args) => { + const userAgent = request.headers['user-agent'] || 'unknown'; + const isElectron = /electron/ig.test(userAgent); + request.url = request.baseUrl || request.url; + return (isElectron ? + electron : browser)(request, ...args); + }); +` : ` + application.use(${this.compileExpressStatic()}); +`; + } + protected compileServer(backendModules: Map): string { - return `// @ts-check + return `\ +// @ts-check require('reflect-metadata'); const path = require('path'); const express = require('express'); @@ -54,9 +78,7 @@ function start(port, host) { const cliManager = container.get(CliManager); return cliManager.initializeCli().then(function () { const application = container.get(BackendApplication); - application.use(express.static(path.join(__dirname, '../../lib'), { - index: 'index.html' - })); +${this.compileMiddleware(backendModules)} return application.start(port, host); }); } @@ -72,7 +94,8 @@ module.exports = (port, host) => Promise.resolve()${this.compileBackendModuleImp } protected compileMain(backendModules: Map): string { - return `// @ts-check + return `\ +// @ts-check const serverPath = require('path').resolve(__dirname, 'server'); const address = require('@theia/core/lib/node/cluster/main').default(serverPath); address.then(function (address) { diff --git a/dev-packages/application-manager/src/generator/frontend-generator.ts b/dev-packages/application-manager/src/generator/frontend-generator.ts index 76f3c19aa1876..10ff5a64d711c 100644 --- a/dev-packages/application-manager/src/generator/frontend-generator.ts +++ b/dev-packages/application-manager/src/generator/frontend-generator.ts @@ -20,15 +20,25 @@ export class FrontendGenerator extends AbstractGenerator { async generate(): Promise { const frontendModules = this.pck.targetFrontendModules; - await this.write(this.pck.frontend('index.html'), this.compileIndexHtml(frontendModules)); - await this.write(this.pck.frontend('index.js'), this.compileIndexJs(frontendModules)); + + if (this.package.isHybrid()) { + await this.write(this.pck.frontend('browser', 'index.html'), this.compileIndexHtml(this.package.frontendModules)); + await this.write(this.pck.frontend('electron', 'index.html'), this.compileIndexHtml(this.package.frontendElectronModules)); + await this.write(this.pck.frontend('browser', 'index.js'), this.compileIndexJs(this.package.frontendModules)); + await this.write(this.pck.frontend('electron', 'index.js'), this.compileIndexJs(this.package.frontendElectronModules)); + } else { + await this.write(this.pck.frontend('index.html'), this.compileIndexHtml(frontendModules)); + await this.write(this.pck.frontend('index.js'), this.compileIndexJs(frontendModules)); + } + if (this.pck.isElectron()) { await this.write(this.pck.frontend('electron-main.js'), this.compileElectronMain()); } } protected compileIndexHtml(frontendModules: Map): string { - return ` + return `\ + ${this.compileIndexHead(frontendModules)} @@ -48,8 +58,10 @@ export class FrontendGenerator extends AbstractGenerator { } protected compileIndexJs(frontendModules: Map): string { - return `// @ts-check -${this.ifBrowser("require('es6-promise/auto');")} + return `\ +// @ts-check +${this.ifBrowser(`require('es6-promise/auto'); +`)}\ require('reflect-metadata'); const { Container } = require('inversify'); const { FrontendApplication } = require('@theia/core/lib/browser'); @@ -90,8 +102,8 @@ module.exports = Promise.resolve()${this.compileFrontendModuleImports(frontendMo } protected compileElectronMain(): string { - return `// @ts-check - + return `\ +// @ts-check // Workaround for https://github.com/electron/electron/issues/9225. Chrome has an issue where // in certain locales (e.g. PL), image metrics are wrongly computed. We explicitly set the // LC_NUMERIC to prevent this from happening (selects the numeric formatting category of the @@ -105,18 +117,45 @@ const { join } = require('path'); const { isMaster } = require('cluster'); const { fork } = require('child_process'); const { app, BrowserWindow, ipcMain } = require('electron'); +const EventEmitter = require('events'); +const fileSchemeTester = /^file:/; +const localUriEvent = new EventEmitter(); +let localUri = undefined; const windows = []; +function setLocalUri(uri) { + localUriEvent.emit('update', localUri = uri); +} +function resolveLocalUriFromPort(port) { + setLocalUri('file://' + join(__dirname, '../../lib/index.html') + '?port=' + port); +} + function createNewWindow(theUrl) { - const newWindow = new BrowserWindow({ width: 1024, height: 728, show: !!theUrl }); + const config = { + width: 1024, + height: 728, + show: !!theUrl + }; + + // Converts 'localhost' to the running local backend endpoint + if (localUri && theUrl === 'localhost') { + theUrl = localUri; + } + + if (!!theUrl && !fileSchemeTester.test(theUrl)) { + config.webPreferences = { + // nodeIntegration: false, + // contextIsolation: true, + }; + }; + + const newWindow = new BrowserWindow(config); if (windows.length === 0) { newWindow.webContents.on('new-window', (event, url, frameName, disposition, options) => { // If the first electron window isn't visible, then all other new windows will remain invisible. // https://github.com/electron/electron/issues/3751 - options.show = true; - options.width = 1024; - options.height = 728; + Object.assign(options, config); }); } windows.push(newWindow); @@ -148,17 +187,20 @@ if (isMaster) { }); app.on('ready', () => { // Check whether we are in bundled application or development mode. - const devMode = process.defaultApp || /node_modules[\/]electron[\/]/.test(process.execPath); + const devMode = process.defaultApp || /node_modules[\\/]electron[\\/]/.test(process.execPath); + const mainWindow = createNewWindow(); - const loadMainWindow = (port) => { - mainWindow.loadURL('file://' + join(__dirname, '../../lib/index.html') + '?port=' + port); + const loadMainWindow = (uri) => { + mainWindow.loadURL(uri); }; + localUriEvent.once('update', loadMainWindow); + const mainPath = join(__dirname, '..', 'backend', 'main'); // We need to distinguish between bundled application and development mode when starting the clusters. // See: https://github.com/electron/electron/issues/6337#issuecomment-230183287 if (devMode) { require(mainPath).then(address => { - loadMainWindow(address.port); + resolveLocalUriFromPort(address.port) }).catch((error) => { console.error(error); app.exit(1); @@ -166,7 +208,7 @@ if (isMaster) { } else { const cp = fork(mainPath); cp.on('message', (message) => { - loadMainWindow(message); + resolveLocalUriFromPort(message); }); cp.on('error', (error) => { console.error(error); diff --git a/dev-packages/application-manager/src/generator/webpack-generator.ts b/dev-packages/application-manager/src/generator/webpack-generator.ts index 6ef7c564e2ec8..769b094f85467 100644 --- a/dev-packages/application-manager/src/generator/webpack-generator.ts +++ b/dev-packages/application-manager/src/generator/webpack-generator.ts @@ -16,11 +16,12 @@ import * as paths from 'path'; import { AbstractGenerator } from './abstract-generator'; +import { ApplicationProps } from '@theia/application-package'; export class WebpackGenerator extends AbstractGenerator { async generate(): Promise { - await this.write(this.configPath, this.compileWebpackConfig()); + await this.write(this.configPath, this.compileWebpackConfigFile()); } get configPath(): string { @@ -31,7 +32,18 @@ export class WebpackGenerator extends AbstractGenerator { return this.pck.resolveModulePath(moduleName, path).split(paths.sep).join('/'); } - protected compileWebpackConfig(): string { + protected compileExports(): string { + const exports = []; + if (this.package.isHybrid() || this.package.isBrowser()) { + exports.push(this.normalized('browser')); + } + if (this.package.isHybrid() || this.package.isElectron()) { + exports.push(this.normalized('electron')); + } + return `[${exports.join(', ')}]`; + } + + protected compileWebpackConfigFile(): string { return `// @ts-check const path = require('path'); const webpack = require('webpack'); @@ -45,27 +57,38 @@ const { mode } = yargs.option('mode', { choices: ["development", "production"], default: "production" }).argv; -const development = mode === 'development';${this.ifMonaco(() => ` - +const development = mode === 'development'; +${this.ifMonaco(() => ` const monacoEditorCorePath = development ? '${this.resolve('monaco-editor-core', 'dev/vs')}' : '${this.resolve('monaco-editor-core', 'min/vs')}'; const monacoCssLanguagePath = '${this.resolve('monaco-css', 'release/min')}'; -const monacoHtmlLanguagePath = '${this.resolve('monaco-html', 'release/min')}';`)} +const monacoHtmlLanguagePath = '${this.resolve('monaco-html', 'release/min')}'; +`)} +${this.package.isHybrid() || this.package.isBrowser() ? this.compileWebpackConfigObjectFor('browser') : ''}\ +${this.package.isHybrid() || this.package.isElectron() ? this.compileWebpackConfigObjectFor('electron') : ''}\ -module.exports = { - entry: path.resolve(__dirname, 'src-gen/frontend/index.js'), +module.exports = ${this.compileExports()}; +`; + } + + protected compileWebpackConfigObjectFor(target: ApplicationProps.Target): string { + const normalizedTarget = this.normalized(target); + return `\ +const ${normalizedTarget} = { + entry: path.resolve(__dirname, 'src-gen/frontend/${this.ifHybrid(normalizedTarget + '/')}index.js'), output: { filename: 'bundle.js', - path: outputPath + path: path.resolve(outputPath, '${this.ifHybrid(normalizedTarget)}') }, - target: '${this.ifBrowser('web', 'electron-renderer')}', + target: '${target === 'browser' ? 'web' : 'electron-renderer'}', mode, - node: {${this.ifElectron(` + node: {${target === 'electron' ? ` __dirname: false, - __filename: false`, ` + __filename: false` + : /* else */ ` fs: 'empty', child_process: 'empty', net: 'empty', - crypto: 'empty'`)} + crypto: 'empty'` } }, module: { rules: [ @@ -153,7 +176,7 @@ module.exports = { stats: { warnings: true } -};`; +}; +`; } - } diff --git a/dev-packages/application-manager/src/rebuild.ts b/dev-packages/application-manager/src/rebuild.ts index 36662e84c67d4..187360af1743c 100644 --- a/dev-packages/application-manager/src/rebuild.ts +++ b/dev-packages/application-manager/src/rebuild.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (C) 2017 TypeFox and others. + * Copyright (C) 2018 TypeFox, Ericsson 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 @@ -14,11 +14,12 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { ApplicationProps } from '@theia/application-package/lib/'; import fs = require('fs-extra'); import path = require('path'); import cp = require('child_process'); -export function rebuild(target: 'electron' | 'browser', modules: string[]) { +export function rebuild(target: ApplicationProps.Target, modules: string[]) { const nodeModulesPath = path.join(process.cwd(), 'node_modules'); const browserModulesPath = path.join(process.cwd(), '.browser_modules'); const modulesToProcess = modules || ['node-pty', 'vscode-nsfw', 'find-git-repositories']; @@ -54,7 +55,8 @@ export function rebuild(target: 'electron' | 'browser', modules: string[]) { fs.writeFile(packFile, packageText); }, 100); } - } else if (target === 'browser' && fs.existsSync(browserModulesPath)) { + + } else if (/^(browser|hybrid)$/.test(target) && fs.existsSync(browserModulesPath)) { for (const moduleName of fs.readdirSync(browserModulesPath)) { console.log('Reverting ' + moduleName); const src = path.join(browserModulesPath, moduleName); @@ -63,6 +65,7 @@ export function rebuild(target: 'electron' | 'browser', modules: string[]) { fs.copySync(src, dest); } fs.removeSync(browserModulesPath); + } else { console.log('native node modules are already rebuilt for ' + target); } diff --git a/dev-packages/application-package/src/application-package.ts b/dev-packages/application-package/src/application-package.ts index 4284f3649d16e..2c8d9a607b446 100644 --- a/dev-packages/application-package/src/application-package.ts +++ b/dev-packages/application-package/src/application-package.ts @@ -17,11 +17,14 @@ import * as fs from 'fs-extra'; import * as paths from 'path'; import { readJsonFile, writeJsonFile } from './json-file'; -import { NpmRegistry, NodePackage, PublishedNodePackage, sortByKey } from './npm-registry'; +import { NpmRegistry, NpmRegistryOptions, NodePackage, PublishedNodePackage, sortByKey } from './npm-registry'; import { Extension, ExtensionPackage, RawExtensionPackage } from './extension-package'; import { ExtensionPackageCollector } from './extension-package-collector'; import { ApplicationProps } from './application-props'; +export class ApplicationPackageConfig extends NpmRegistryOptions { + readonly target: ApplicationProps.Target; +} // tslint:disable-next-line:no-any export type ApplicationLog = (message?: any, ...optionalParams: any[]) => void; export class ApplicationPackageOptions { @@ -198,6 +201,10 @@ export class ApplicationPackage { return this.target === 'electron'; } + isHybrid(): boolean { + return this.target === 'hybrid'; + } + ifBrowser(value: T): T | undefined; ifBrowser(value: T, defaultValue: T): T; ifBrowser(value: T, defaultValue?: T): T | undefined { @@ -210,6 +217,12 @@ export class ApplicationPackage { return this.isElectron() ? value : defaultValue; } + ifHybrid(value: T): T | undefined; + ifHybrid(value: T, defaultValue: T): T; + ifHybrid(value: T, defaultValue?: T): T | undefined { + return this.isHybrid() ? value : defaultValue; + } + get targetBackendModules(): Map { return this.ifBrowser(this.backendModules, this.backendElectronModules); } diff --git a/dev-packages/application-package/src/application-props.ts b/dev-packages/application-package/src/application-props.ts index 2d7b6f7e1ac95..82262c09cae42 100644 --- a/dev-packages/application-package/src/application-props.ts +++ b/dev-packages/application-package/src/application-props.ts @@ -60,7 +60,7 @@ export interface ApplicationProps extends NpmRegistryProps { } export namespace ApplicationProps { - export type Target = 'browser' | 'electron'; + export type Target = 'browser' | 'electron' | 'hybrid'; export const DEFAULT: ApplicationProps = { ...NpmRegistryProps.DEFAULT, diff --git a/examples/hybrid/compile.tsconfig.json b/examples/hybrid/compile.tsconfig.json new file mode 100644 index 0000000000000..0c45f79cfd764 --- /dev/null +++ b/examples/hybrid/compile.tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../../configs/base.tsconfig" +} \ No newline at end of file diff --git a/examples/hybrid/package.json b/examples/hybrid/package.json new file mode 100644 index 0000000000000..0856f969149e0 --- /dev/null +++ b/examples/hybrid/package.json @@ -0,0 +1,59 @@ +{ + "private": true, + "name": "@theia/example-hybrid", + "version": "0.3.13", + "theia": { + "target": "hybrid" + }, + "dependencies": { + "@theia/callhierarchy": "^0.3.13", + "@theia/core": "^0.3.13", + "@theia/cpp": "^0.3.13", + "@theia/editor": "^0.3.13", + "@theia/editorconfig": "^0.3.13", + "@theia/extension-manager": "^0.3.13", + "@theia/file-search": "^0.3.13", + "@theia/filesystem": "^0.3.13", + "@theia/git": "^0.3.13", + "@theia/java": "^0.3.13", + "@theia/keymaps": "^0.3.13", + "@theia/languages": "^0.3.13", + "@theia/markers": "^0.3.13", + "@theia/merge-conflicts": "^0.3.13", + "@theia/messages": "^0.3.13", + "@theia/metrics": "^0.3.13", + "@theia/mini-browser": "^0.3.13", + "@theia/monaco": "^0.3.13", + "@theia/navigator": "^0.3.13", + "@theia/outline-view": "^0.3.13", + "@theia/output": "^0.3.13", + "@theia/plugin-ext": "^0.3.13", + "@theia/plugin-ext-vscode": "^0.3.13", + "@theia/preferences": "^0.3.13", + "@theia/preview": "^0.3.13", + "@theia/process": "^0.3.13", + "@theia/python": "^0.3.13", + "@theia/search-in-workspace": "^0.3.13", + "@theia/task": "^0.3.13", + "@theia/terminal": "^0.3.13", + "@theia/typescript": "^0.3.13", + "@theia/userstorage": "^0.3.13", + "@theia/variable-resolver": "^0.3.13", + "@theia/workspace": "^0.3.13" + }, + "scripts": { + "prepare": "yarn run clean && yarn build", + "clean": "theia clean", + "build": "theia build --mode development", + "watch": "yarn build --watch --mode development", + "start": "theia start", + "start:debug": "yarn start --log-level=debug", + "test": "electron-mocha --timeout 60000 --require ts-node/register \"./test/**/*.espec.ts\"", + "test:ui": "wdio wdio.conf.js" + }, + "devDependencies": { + "@theia/cli": "^0.3.13", + "webpack": "^4.13.0", + "webpack-cli": "^3.0.8" + } +} diff --git a/packages/core/package.json b/packages/core/package.json index e93dfa37b10fd..8073594db267b 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -19,6 +19,7 @@ "@types/ws": "^3.0.2", "@types/yargs": "^8.0.2", "ajv": "^5.2.2", + "axios": "^0.18.0", "body-parser": "^1.17.2", "electron": "1.8.2-beta.5", "es6-promise": "^4.2.4", @@ -45,14 +46,17 @@ "publishConfig": { "access": "public" }, - "theiaExtensions": [ - { + "theiaExtensions": [{ "frontend": "lib/browser/menu/browser-menu-module", "frontendElectron": "lib/electron-browser/menu/electron-menu-module" }, { "frontend": "lib/browser/window/browser-window-module", "frontendElectron": "lib/electron-browser/window/electron-window-module" + }, + { + "frontendElectron": "lib/electron-browser/remote/electron-remote-module", + "backendElectron": "lib/electron-node/remote/electron-remote-module" } ], "keywords": [ diff --git a/packages/core/src/browser/browser.ts b/packages/core/src/browser/browser.ts index bed9e22319a4b..f9d2c817c252a 100644 --- a/packages/core/src/browser/browser.ts +++ b/packages/core/src/browser/browser.ts @@ -35,6 +35,11 @@ export const isNative = typeof (window as any).process !== 'undefined'; // tslint:disable-next-line:no-any export const isBasicWasmSupported = typeof (window as any).WebAssembly !== 'undefined'; +// Current protocol in use to display the application +export const isHttps = /^https:/.test(self.location.href); +export const isHttp = /^http:/.test(self.location.href); +export const isFile = /^file:/.test(self.location.href); + /** * Parse a magnitude value (e.g. width, height, left, top) from a CSS attribute value. * Returns the given default value (or undefined) if the value cannot be determined, diff --git a/packages/core/src/browser/quick-open/quick-open-model.ts b/packages/core/src/browser/quick-open/quick-open-model.ts index 970fb80b981b5..bd98e3e8edbbb 100644 --- a/packages/core/src/browser/quick-open/quick-open-model.ts +++ b/packages/core/src/browser/quick-open/quick-open-model.ts @@ -75,7 +75,7 @@ export class QuickOpenItem { this.reject = reject; }); } + +/** + * Simple timeout-as-promise implementation. + * + * @param time delay to wait for + * @param value value to resolve to after the timeout + */ +export function timeout(time: Number, value?: T): Promise { + return new Promise(resolve => setTimeout(resolve, time, value)); +} diff --git a/packages/core/src/common/remote/electron-remote-protocol.ts b/packages/core/src/common/remote/electron-remote-protocol.ts new file mode 100644 index 0000000000000..1d2e60605153b --- /dev/null +++ b/packages/core/src/common/remote/electron-remote-protocol.ts @@ -0,0 +1,23 @@ +/******************************************************************************** + * Copyright (C) 2018 Ericsson 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 WITH Classpath-exception-2.0 + ********************************************************************************/ + +export const ElectronRemoteQuestionPath = Symbol('ElectronRemoteQuestionPath'); +export const ElectronRemoteAnswer = Symbol('ElectronRemoteAnswer'); + +export const defaultElectronRemoteQuestionPath = '/are-you-theia'; +export const defaultElectronRemoteAnswer = { + answer: 'yes-I-am-Theia', +}; diff --git a/packages/core/src/electron-browser/menu/electron-menu-contribution.ts b/packages/core/src/electron-browser/menu/electron-menu-contribution.ts index 40e71d4036cac..1770966a27ac8 100644 --- a/packages/core/src/electron-browser/menu/electron-menu-contribution.ts +++ b/packages/core/src/electron-browser/menu/electron-menu-contribution.ts @@ -20,9 +20,10 @@ import { Command, CommandContribution, CommandRegistry, isOSX, MenuModelRegistry, MenuContribution } from '../../common'; -import { KeybindingContribution, KeybindingRegistry } from '../../browser'; +import { KeybindingContribution, KeybindingRegistry, QuickOpenService } from '../../browser'; import { FrontendApplication, FrontendApplicationContribution, CommonMenus } from '../../browser'; import { ElectronMainMenuFactory } from './electron-main-menu-factory'; +import { WindowService } from '../../browser/window/window-service'; export namespace ElectronCommands { export const TOGGLE_DEVELOPER_TOOLS: Command = { @@ -59,6 +60,12 @@ export namespace ElectronMenus { @injectable() export class ElectronMenuContribution implements FrontendApplicationContribution, CommandContribution, MenuContribution, KeybindingContribution { + @inject(QuickOpenService) + protected readonly quickOpenService: QuickOpenService; + + @inject(WindowService) + protected readonly windowService: WindowService; + constructor( @inject(ElectronMainMenuFactory) protected readonly factory: ElectronMainMenuFactory ) { } diff --git a/packages/core/src/electron-browser/remote/electron-backend-location.ts b/packages/core/src/electron-browser/remote/electron-backend-location.ts new file mode 100644 index 0000000000000..d9994ca033dbd --- /dev/null +++ b/packages/core/src/electron-browser/remote/electron-backend-location.ts @@ -0,0 +1,35 @@ +/******************************************************************************** + * Copyright (C) 2018 Ericsson 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 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { isFile } from '../../browser'; + +export type ElectronBackendLocation = 'local' | 'remote'; +export namespace ElectronBackendLocation { + + export const location: ElectronBackendLocation = isFile ? 'local' : 'remote'; + + export function getLocation(): ElectronBackendLocation { + return location; + } + + export function isLocal(): boolean { + return location === 'local'; + } + + export function isRemote(): boolean { + return location === 'remote'; + } +} diff --git a/packages/core/src/electron-browser/remote/electron-remote-contribution.ts b/packages/core/src/electron-browser/remote/electron-remote-contribution.ts new file mode 100644 index 0000000000000..6b3a4404eca6a --- /dev/null +++ b/packages/core/src/electron-browser/remote/electron-remote-contribution.ts @@ -0,0 +1,357 @@ +/******************************************************************************** + * Copyright (C) 2017 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 WITH Classpath-exception-2.0 + ********************************************************************************/ +// tslint:disable:no-any + +import { join } from 'path'; +import { inject, injectable } from 'inversify'; +import Axios, { AxiosResponse } from 'axios'; +import URI from '../../common/uri'; +import { + Command, CommandContribution, CommandRegistry, + MenuModelRegistry, MenuContribution, ILogger +} from '../../common'; +import { + StorageService, KeybindingContribution, KeybindingRegistry, + QuickOpenService, QuickOpenModel, QuickOpenItem, QuickOpenMode, QuickOpenGroupItem, QuickOpenGroupItemOptions +} from '../../browser'; +import { CommonMenus } from '../../browser'; +import { WindowService } from '../../browser/window/window-service'; +import { timeout as delay } from '../../common/promise-util'; +import { ElectronBackendLocation } from './electron-backend-location'; +import { ElectronRemoteQuestionPath, ElectronRemoteAnswer } from '../../common/remote/electron-remote-protocol'; + +export type RemoteEntryGroup = 'Input' | 'Autocomplete' | 'History'; +export type Response = AxiosResponse; +export interface RemoteEntryOptions { + errorMessage?: string; + questionPath?: string; + expectedAnswer?: any; +} +export class RemoteEntry { + + protected _ok?: boolean; + protected _response?: Response; + protected _error?: Error; + + constructor( + public url: string, + protected options: RemoteEntryOptions = {}, + public group?: string, + ) { } + + protected get errorMessage(): string | undefined { + return this.options.errorMessage; + } + protected get questionPath(): string | undefined { + return this.options.questionPath; + } + protected get expectedAnswer(): object | undefined { + return this.options.expectedAnswer; + } + + protected createError(response: Response): Error { + return new Error(this.errorMessage || response.statusText); + } + + protected compareAnswer(answer: any, expected: any): boolean { + return Object.keys(answer) + .filter(key => answer.hasOwnProperty(key)) + .every(key => answer[key] === expected[key]) + ; + } + + protected coerce(response: Response): Response { + if (/^2/.test(response.status.toString())) { + if (this.expectedAnswer) { + if (!(this._ok = this.compareAnswer(response.data, this.expectedAnswer))) { + throw this.createError(response); + } + } + return response; + } + throw this.createError(response); + } + + async poll(timeout?: number): Promise { + if (this._error) { + throw this._error; + } + if (this._response) { + return this._response; + } + const url = this.questionPath ? join(this.url, this.questionPath) : this.url; + try { + return this.coerce(this._response = await Axios.get(url, { timeout, responseType: 'json' })); + } catch (error) { + if (error.response && /^4/.test(error.response.status.toString())) { + error = this.createError(error.response); + } + throw this._error = error; + } + } + + get response(): RemoteEntry['_response'] { + if (this._error) { + throw this._error; + } + return this._response; + } + + hasError(): boolean { + return typeof this._error !== 'undefined'; + } + + hasResponse(): boolean { + return typeof this.response !== 'undefined'; + } + + isOk(): boolean { + return !!this._ok; + } + + getStatusText(): string { + try { + const response = this.response; + if (response) { + return 'Online'; + } + } catch (error) { + return error.message; + } + return 'Unresolved'; + } + + clear(): void { + this._response = undefined; + this._error = undefined; + this._ok = undefined; + } +} + +@injectable() +export class ElectronRemoteContribution implements QuickOpenModel, CommandContribution, MenuContribution, KeybindingContribution { + + @inject(StorageService) protected readonly localStorageService: StorageService; + @inject(QuickOpenService) protected readonly quickOpenService: QuickOpenService; + @inject(WindowService) protected readonly windowService: WindowService; + @inject(ILogger) protected readonly logger: ILogger; + + protected historyEntries: Promise; + protected timeout: number = 500; // ms + + constructor( + @inject(ElectronRemoteQuestionPath) protected readonly questionPath?: string, + @inject(ElectronRemoteAnswer) protected readonly expectedAnswer?: object, + ) { } + + protected remoteEntryOptions: RemoteEntryOptions = { + errorMessage: 'Not a Theia Application', + expectedAnswer: this.expectedAnswer, + questionPath: this.questionPath, + }; + + protected get history(): Promise { + return this.localStorageService.getData(ElectronRemoteHistory.KEY, []) + .then(history => history.map(entry => decodeURI(entry))); + } + + protected async remember(url: string): Promise { + const history = await this.localStorageService.getData(ElectronRemoteHistory.KEY, []); + const encoded = encodeURI(url); + if (encoded) { + const index = history.indexOf(encoded); + if (index >= 0) { + history.splice(index); + } + history.unshift(encoded); + this.localStorageService.setData(ElectronRemoteHistory.KEY, history); + } + } + + protected async clearHistory(): Promise { + return this.localStorageService.setData(ElectronRemoteHistory.KEY, undefined); + } + + protected async computeHistoryCache(): Promise { + return this.accumulateResponses( + (await this.history).map( + url => new RemoteEntry(url, this.remoteEntryOptions, 'History') + ), this.timeout); + } + + protected async accumulateResponses(input: RemoteEntry[], timeout: number): Promise { + const output: RemoteEntry[] = []; + input.forEach(async entry => { + await entry.poll(timeout).catch(e => void 0); + output.push(entry); + }); + await delay(timeout); + return output.slice(0); + } + + protected urlOpener = (url: string) => (mode: QuickOpenMode): boolean => { + if (mode === QuickOpenMode.OPEN) { + this.windowService.openNewWindow(url); + this.remember(url); + } + return true; + } + + protected convertEntryToQuickOpenItem(entry: RemoteEntry, override: QuickOpenGroupItemOptions = {}): QuickOpenItem { + const opener = this.urlOpener(entry.url); + return new QuickOpenGroupItem({ + label: entry.url, + groupLabel: entry.group, + description: entry.getStatusText(), + run: mode => entry.isOk() ? opener(mode) : false, + ...override, + }); + } + + async onType(lookFor: string, acceptor: (items: QuickOpenItem[]) => void): Promise { + const defaultSchemes = ['http', 'https']; + const items: QuickOpenItem[] = []; + const inputResponses = []; + const inputEntries = []; + + // Add a way to open a local electron window + if (ElectronBackendLocation.isRemote()) { + items.push(new QuickOpenGroupItem({ + label: 'Localhost Application', + groupLabel: 'Electron', + description: 'Electron', + run: this.urlOpener('localhost'), + })); + } + + if (lookFor) { + let url = new URI(lookFor); + + // Autocompletion (http/https) if not using http(s) filescheme + if (!/^https?$/.test(url.scheme)) { + const reformated = new URI(`//${lookFor}`); + for (const scheme of defaultSchemes) { + url = reformated.withScheme(scheme); + inputEntries.push( + new RemoteEntry(url.toString(), this.remoteEntryOptions, 'Autocomplete') + ); + } + } else { + inputEntries.push( + new RemoteEntry(url.toString(), this.remoteEntryOptions, 'Input') + ); + } + + // Host polling + inputResponses.push(...await this.accumulateResponses(inputEntries, this.timeout)); + } + + // Sorting the autocompletion and history based on the status of the responses + const sortedEntries = [...inputResponses, ...await this.historyEntries] + // make unique + .filter((entry, index, array) => array.findIndex(e => e.url === entry.url) === index) + // place OK responses first + .sort((a, b) => { + if (a.isOk() === b.isOk()) { + return 0; + } else if (a.isOk()) { + return -1; + } else { + return 1; + } + }) + // place a separator between OK and Error responses + .map((entry, index, array) => { + const previous = array[index - 1]; + const options: QuickOpenGroupItemOptions = {}; + if (previous && previous.isOk() && !entry.isOk()) { + options.showBorder = true; + } + return this.convertEntryToQuickOpenItem(entry, options); + }); + + items.push(...sortedEntries); + acceptor(items); + } + + registerCommands(registry: CommandRegistry): void { + registry.registerCommand(ElectronRemoteCommands.CONNECT_TO_REMOTE, { + execute: () => { + this.historyEntries = this.computeHistoryCache(); + this.quickOpenService.open(this, { + placeholder: 'Type the URL to connect to...', + fuzzyMatchLabel: true, + }); + } + }); + + registry.registerCommand(ElectronRemoteCommands.DISCONNECT_FROM_REMOTE, { + isEnabled: () => ElectronBackendLocation.isRemote(), + isVisible: () => ElectronBackendLocation.isRemote(), + execute: () => { + this.windowService.openNewWindow('localhost'); + close(); + }, + }); + registry.registerCommand(ElectronRemoteCommands.CLEAR_REMOTE_HISTORY, { + execute: () => this.clearHistory() + }); + } + + registerKeybindings(registry: KeybindingRegistry): void { + registry.registerKeybindings({ + command: ElectronRemoteCommands.CONNECT_TO_REMOTE.id, + keybinding: 'ctrl+alt+r' + }); + } + + registerMenus(registry: MenuModelRegistry) { + registry.registerMenuAction(ElectronMenus.ELECTRON_REMOTE, { + commandId: ElectronRemoteCommands.CONNECT_TO_REMOTE.id, + order: 'z4', + }); + // Do not load the disconnect button if we are not on a remote server + if (ElectronBackendLocation.isRemote()) { + registry.registerMenuAction(ElectronMenus.ELECTRON_REMOTE, { + commandId: ElectronRemoteCommands.DISCONNECT_FROM_REMOTE.id, + order: 'z5', + }); + } + } +} + +export namespace ElectronRemoteCommands { + export const CONNECT_TO_REMOTE: Command = { + id: 'electron.remote.connect', + label: 'Remote: Connect to a Server' + }; + export const CLEAR_REMOTE_HISTORY: Command = { + id: 'electron.remote.history.clear', + label: 'Remote: Clear host history' + }; + export const DISCONNECT_FROM_REMOTE: Command = { + id: 'electron.remote.disconnect', + label: 'Remote: Disconnect', + }; +} + +export namespace ElectronMenus { + export const ELECTRON_REMOTE = [...CommonMenus.FILE_OPEN, 'z_connect']; +} + +export namespace ElectronRemoteHistory { + export const KEY = 'theia.remote.history'; +} diff --git a/packages/core/src/electron-browser/remote/electron-remote-module.ts b/packages/core/src/electron-browser/remote/electron-remote-module.ts new file mode 100644 index 0000000000000..c08fe04b10bb6 --- /dev/null +++ b/packages/core/src/electron-browser/remote/electron-remote-module.ts @@ -0,0 +1,33 @@ +/******************************************************************************** + * Copyright (C) 2018 Ericsson 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 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { ContainerModule } from 'inversify'; +import { CommandContribution, MenuContribution } from '../../common'; +import { + ElectronRemoteQuestionPath, ElectronRemoteAnswer, + defaultElectronRemoteQuestionPath, defaultElectronRemoteAnswer +} from '../../common/remote/electron-remote-protocol'; +import { KeybindingContribution } from '../../browser'; +import { ElectronRemoteContribution } from './electron-remote-contribution'; + +export default new ContainerModule(bind => { + bind(ElectronRemoteContribution).toSelf().inSingletonScope(); + for (const serviceIdentifier of [KeybindingContribution, CommandContribution, MenuContribution]) { + bind(serviceIdentifier).toService(ElectronRemoteContribution); + } + bind(ElectronRemoteQuestionPath).toConstantValue(defaultElectronRemoteQuestionPath); + bind(ElectronRemoteAnswer).toConstantValue(defaultElectronRemoteAnswer); +}); diff --git a/packages/core/src/electron-node/remote/electron-remote-backend-contribution.ts b/packages/core/src/electron-node/remote/electron-remote-backend-contribution.ts new file mode 100644 index 0000000000000..72c6a2c4fd488 --- /dev/null +++ b/packages/core/src/electron-node/remote/electron-remote-backend-contribution.ts @@ -0,0 +1,33 @@ +/******************************************************************************** + * Copyright (C) 2018 Ericsson 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 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import * as express from 'express'; +import { injectable, inject } from 'inversify'; +import { BackendApplicationContribution } from '../../node'; +import { ElectronRemoteQuestionPath, ElectronRemoteAnswer } from '../../common/remote/electron-remote-protocol'; + +@injectable() +export class ElectronRemoteBackendContribution implements BackendApplicationContribution { + + @inject(ElectronRemoteQuestionPath) protected readonly path: string; + @inject(ElectronRemoteAnswer) protected readonly answer: object; + + configure(app: express.Application): void { + app.get(this.path, (request, response) => { + response.send(this.answer); + }); + } +} diff --git a/packages/core/src/electron-node/remote/electron-remote-module.ts b/packages/core/src/electron-node/remote/electron-remote-module.ts new file mode 100644 index 0000000000000..0e576271d3c56 --- /dev/null +++ b/packages/core/src/electron-node/remote/electron-remote-module.ts @@ -0,0 +1,29 @@ +/******************************************************************************** + * Copyright (C) 2018 Ericsson 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 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { ContainerModule } from 'inversify'; +import { BackendApplicationContribution } from '../../node'; +import { ElectronRemoteBackendContribution } from './electron-remote-backend-contribution'; +import { + ElectronRemoteQuestionPath, ElectronRemoteAnswer, + defaultElectronRemoteQuestionPath, defaultElectronRemoteAnswer +} from '../../common/remote/electron-remote-protocol'; + +export default new ContainerModule(bind => { + bind(BackendApplicationContribution).to(ElectronRemoteBackendContribution).inSingletonScope(); + bind(ElectronRemoteQuestionPath).toConstantValue(defaultElectronRemoteQuestionPath); + bind(ElectronRemoteAnswer).toConstantValue(defaultElectronRemoteAnswer); +}); diff --git a/packages/monaco/src/browser/monaco-loader.ts b/packages/monaco/src/browser/monaco-loader.ts index 4261a0650cf8d..a7a5af1819a33 100644 --- a/packages/monaco/src/browser/monaco-loader.ts +++ b/packages/monaco/src/browser/monaco-loader.ts @@ -34,7 +34,7 @@ export function loadVsRequire(context: any): Promise { context.require = originalRequire; } resolve(amdRequire); - }); + }, { once: true }); document.body.appendChild(vsLoader); }, { once: true }) ); diff --git a/packages/monaco/src/electron-browser/monaco-electron-module.ts b/packages/monaco/src/electron-browser/monaco-electron-module.ts index a29b5f6cebc46..3d9db2c4d2bb4 100644 --- a/packages/monaco/src/electron-browser/monaco-electron-module.ts +++ b/packages/monaco/src/electron-browser/monaco-electron-module.ts @@ -20,7 +20,8 @@ import { loadVsRequire, loadMonaco } from '../browser/monaco-loader'; export { ContainerModule }; -const s = self; +// tslint:disable-next-line:no-any +const globals = self; /** * We cannot use `FileUri#create` because URIs with file scheme cannot be properly decoded via the AMD loader. @@ -37,13 +38,22 @@ const uriFromPath = (filePath: string) => { export default loadVsRequire(global) .then(vsRequire => { - const baseUrl = uriFromPath(__dirname); + const isRemote = !/^file:/.test(self.location.href); + const baseUrl = isRemote ? self.location.href : uriFromPath(__dirname); vsRequire.config({ baseUrl }); // workaround monaco-css not understanding the environment - s.module = undefined; + globals.module = undefined; // workaround monaco-typescript not understanding the environment - s.process.browser = true; + globals.process.browser = true; + + // vscode-loader patching: https://github.com/Microsoft/vscode-loader/issues/12 + if (isRemote) { + Object.defineProperty(globals.AMDLoader.Environment.prototype, 'isNode', { + get: () => false + }); + } + return loadMonaco(vsRequire); }) .then(() => import('../browser/monaco-frontend-module')) diff --git a/yarn.lock b/yarn.lock index f25e97ff4837c..32d7747ad92bf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -948,6 +948,13 @@ aws4@^1.2.1, aws4@^1.6.0, aws4@^1.8.0: version "1.8.0" resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f" +axios@^0.18.0: + version "0.18.0" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.18.0.tgz#32d53e4851efdc0a11993b6cd000789d70c05102" + dependencies: + follow-redirects "^1.3.0" + is-buffer "^1.1.5" + babel-code-frame@^6.11.0, babel-code-frame@^6.22.0, babel-code-frame@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" @@ -2041,6 +2048,10 @@ chardet@^0.4.0: version "0.4.2" resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.4.2.tgz#b5473b33dc97c424e5d98dc87d55d4d8a29c8bf2" +chardet@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.5.0.tgz#fe3ac73c00c3d865ffcc02a0682e2c20b6a06029" + "charenc@>= 0.0.1": version "0.0.2" resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" @@ -3482,6 +3493,13 @@ escodegen@^1.9.1: optionalDependencies: source-map "~0.6.1" +eslint-scope@^3.7.1: + version "3.7.3" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-3.7.3.tgz#bb507200d3d17f60247636160b4826284b108535" + dependencies: + esrecurse "^4.1.0" + estraverse "^4.1.1" + eslint-scope@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.0.tgz#50bf3071e9338bcdc43331794a0cb533f0136172" @@ -3686,6 +3704,14 @@ external-editor@^2.0.4, external-editor@^2.1.0: iconv-lite "^0.4.17" tmp "^0.0.33" +external-editor@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.0.0.tgz#dc35c48c6f98a30ca27a20e9687d7f3c77704bb6" + dependencies: + chardet "^0.5.0" + iconv-lite "^0.4.22" + tmp "^0.0.33" + extglob@^0.3.1: version "0.3.2" resolved "https://registry.yarnpkg.com/extglob/-/extglob-0.3.2.tgz#2e18ff3d2f49ab2765cec9023f011daa8d8349a1" @@ -3938,6 +3964,12 @@ flush-write-stream@^1.0.0: inherits "^2.0.1" readable-stream "^2.0.4" +follow-redirects@^1.3.0: + version "1.5.1" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.1.tgz#67a8f14f5a1f67f962c2c46469c79eaec0a90291" + dependencies: + debug "^3.1.0" + font-awesome-webpack@0.0.5-beta.2: version "0.0.5-beta.2" resolved "https://registry.yarnpkg.com/font-awesome-webpack/-/font-awesome-webpack-0.0.5-beta.2.tgz#9ea5f22f0615d08e76d8db341563649a726286d6" @@ -4291,6 +4323,10 @@ glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.0.6, glob@^7.1.1, glob@^7.1.2, gl once "^1.3.0" path-is-absolute "^1.0.0" +global-modules-path@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/global-modules-path/-/global-modules-path-2.1.0.tgz#923ec524e8726bb0c1a4ed4b8e21e1ff80c88bbb" + global-modules@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea" @@ -4752,7 +4788,7 @@ iconv-lite@0.4.19: version "0.4.19" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b" -iconv-lite@0.4.23, iconv-lite@^0.4.17, iconv-lite@^0.4.4, iconv-lite@~0.4.13: +iconv-lite@0.4.23, iconv-lite@^0.4.17, iconv-lite@^0.4.22, iconv-lite@^0.4.4, iconv-lite@~0.4.13: version "0.4.23" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63" dependencies: @@ -4798,6 +4834,13 @@ image-size@~0.5.0: version "0.5.5" resolved "https://registry.yarnpkg.com/image-size/-/image-size-0.5.5.tgz#09dfd4ab9d20e29eb1c3e80b8990378df9e3cb9c" +import-local@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-1.0.0.tgz#5e4ffdc03f4fe6c009c6729beb29631c2f8227bc" + dependencies: + pkg-dir "^2.0.0" + resolve-cwd "^2.0.0" + imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" @@ -4876,7 +4919,25 @@ inquirer@^5.1.0, inquirer@^5.2.0: strip-ansi "^4.0.0" through "^2.3.6" -interpret@^1.0.0, interpret@^1.0.4: +inquirer@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.0.0.tgz#e8c20303ddc15bbfc2c12a6213710ccd9e1413d8" + dependencies: + ansi-escapes "^3.0.0" + chalk "^2.0.0" + cli-cursor "^2.1.0" + cli-width "^2.0.0" + external-editor "^3.0.0" + figures "^2.0.0" + lodash "^4.3.0" + mute-stream "0.0.7" + run-async "^2.2.0" + rxjs "^6.1.0" + string-width "^2.1.0" + strip-ansi "^4.0.0" + through "^2.3.6" + +interpret@^1.0.0, interpret@^1.0.4, interpret@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614" @@ -8273,6 +8334,12 @@ rxjs@^5.1.1, rxjs@^5.4.2, rxjs@^5.5.2: dependencies: symbol-observable "1.0.1" +rxjs@^6.1.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.2.1.tgz#246cebec189a6cbc143a3ef9f62d6f4c91813ca1" + dependencies: + tslib "^1.9.0" + safe-buffer@5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" @@ -9621,6 +9688,10 @@ v8-compile-cache@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-1.1.2.tgz#8d32e4f16974654657e676e0e467a348e89b0dc4" +v8-compile-cache@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.0.0.tgz#526492e35fc616864284700b7043e01baee09f0a" + valid-filename@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/valid-filename/-/valid-filename-2.0.1.tgz#0768d6f364b1ed3bdf68f0d15abffb0d9d6cecaf" @@ -9905,6 +9976,22 @@ webpack-cli@2.0.12: yeoman-environment "^2.0.0" yeoman-generator "^2.0.3" +webpack-cli@^3.0.8: + version "3.0.8" + resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-3.0.8.tgz#90eddcf04a4bfc31aa8c0edc4c76785bc4f1ccd9" + dependencies: + chalk "^2.4.1" + cross-spawn "^6.0.5" + enhanced-resolve "^4.0.0" + global-modules-path "^2.1.0" + import-local "^1.0.0" + inquirer "^6.0.0" + interpret "^1.1.0" + loader-utils "^1.1.0" + supports-color "^5.4.0" + v8-compile-cache "^2.0.0" + yargs "^11.1.0" + webpack-sources@^1.0.1, webpack-sources@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.1.0.tgz#a101ebae59d6507354d71d8013950a3a8b7a5a54" @@ -9942,6 +10029,36 @@ webpack@^4.0.0: watchpack "^1.5.0" webpack-sources "^1.0.1" +webpack@^4.13.0: + version "4.15.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.15.0.tgz#c9704e98f045499b84bdb194d23ade18dfaf4441" + dependencies: + "@webassemblyjs/ast" "1.5.13" + "@webassemblyjs/helper-module-context" "1.5.13" + "@webassemblyjs/wasm-edit" "1.5.13" + "@webassemblyjs/wasm-opt" "1.5.13" + "@webassemblyjs/wasm-parser" "1.5.13" + acorn "^5.6.2" + acorn-dynamic-import "^3.0.0" + ajv "^6.1.0" + ajv-keywords "^3.1.0" + chrome-trace-event "^1.0.0" + enhanced-resolve "^4.1.0" + eslint-scope "^3.7.1" + json-parse-better-errors "^1.0.2" + loader-runner "^2.3.0" + loader-utils "^1.1.0" + memory-fs "~0.4.1" + micromatch "^3.1.8" + mkdirp "~0.5.0" + neo-async "^2.5.0" + node-libs-browser "^2.0.0" + schema-utils "^0.4.4" + tapable "^1.0.0" + uglifyjs-webpack-plugin "^1.2.4" + watchpack "^1.5.0" + webpack-sources "^1.0.1" + wgxpath@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/wgxpath/-/wgxpath-1.0.0.tgz#eef8a4b9d558cc495ad3a9a2b751597ecd9af690" @@ -10187,7 +10304,7 @@ yargs-parser@^9.0.2: dependencies: camelcase "^4.1.0" -yargs@11.1.0, yargs@^11.0.0: +yargs@11.1.0, yargs@^11.0.0, yargs@^11.1.0: version "11.1.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-11.1.0.tgz#90b869934ed6e871115ea2ff58b03f4724ed2d77" dependencies: