diff --git a/CHANGELOG.md b/CHANGELOG.md index 6dc96a8f62061..a6f51949b2534 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ - [plugin] fixed auto detection of new languages [#5753](https://github.com/theia-ide/theia/issues/5753) - [vscode] unzip node_modules for built-in extensions [#5756](https://github.com/theia-ide/theia/pull/5756) - [core] prevent the IDE from scrolling along with the text on mobile (e.g. on iPad) [#5742](https://github.com/theia-ide/theia/pull/5742) +- [core] added command to manually choose a keyboard layout +- [electron] use inversify to allow for contributions to the electron main process Breaking changes: diff --git a/dev-packages/application-manager/src/generator/frontend-generator.ts b/dev-packages/application-manager/src/generator/frontend-generator.ts index d379b8be49db0..9692d96ade39a 100644 --- a/dev-packages/application-manager/src/generator/frontend-generator.ts +++ b/dev-packages/application-manager/src/generator/frontend-generator.ts @@ -28,6 +28,10 @@ export class FrontendGenerator extends AbstractGenerator { } } + protected compileElectronMainModuleImports(): string { + return this.compileModuleImports(this.pck.electronMainModules, 'require'); + } + protected compileIndexPreload(frontendModules: Map): string { const template = this.pck.props.generator.config.preloadTemplate; if (!template) { @@ -92,7 +96,7 @@ function start() { themeService.loadUserTheme(); const application = container.get(FrontendApplication); - application.start(); + return application.start(); } module.exports = Promise.resolve()${this.compileFrontendModuleImports(frontendModules)} @@ -122,215 +126,55 @@ if (process.env.LC_ALL) { } process.env.LC_NUMERIC = 'C'; +require('reflect-metadata'); const electron = require('electron'); -const { join, resolve } = require('path'); -const { fork } = require('child_process'); -const { app, shell, BrowserWindow, ipcMain, Menu } = electron; +const { resolve } = require('path'); +const { Container } = require('inversify'); +const { ElectronMainApplication, TheiaApplicationName, TheiaBackendMainPath, TheiaIndexHtmlPath } = require('@theia/core/lib/electron-main'); +const { electronMainApplicationModule } = require('@theia/core/lib/electron-main/electron-main-application-module'); + +// We cannot use the \`process.cwd()\` as the application project path (the location of the \`package.json\` in other words) +// in a bundled electron application because it depends on the way we start it. For instance, on OS X, these are a differences: +// https://github.com/theia-ide/theia/issues/3297#issuecomment-439172274 +process.env.THEIA_APP_PROJECT_PATH = resolve(__dirname, '..', '..'); + +// Set the electron version for both the dev and the production mode. (https://github.com/theia-ide/theia/issues/3254) +// Otherwise, the forked backend processes will not know that they're serving the electron frontend. +const { versions } = process; +// @ts-ignore +if (versions && typeof versions.electron !== 'undefined') { + // @ts-ignore + process.env.THEIA_ELECTRON_VERSION = versions.electron; +} +const mainPath = resolve(__dirname, '..', 'backend', 'main.js'); +const indexHtmlPath = resolve(__dirname, '../../lib/index.html'); const applicationName = \`${this.pck.props.frontend.config.applicationName}\`; -const nativeKeymap = require('native-keymap'); -const Storage = require('electron-store'); -const electronStore = new Storage(); - -let canPreventStop = true; -const windows = []; - -app.on('before-quit', async event => { - if (canPreventStop) { - // Pause the stop. - event.preventDefault(); - let preventStop = false; - // Ask all opened windows whether they want to prevent the \`close\` event or not. - for (const window of windows) { - if (!preventStop) { - window.webContents.send('prevent-stop-request'); - const preventStopPerWindow = await new Promise((resolve) => { - ipcMain.once('prevent-stop-response', (_, arg) => { - if (!!arg && 'preventStop' in arg && typeof arg.preventStop === 'boolean') { - resolve(arg.preventStop); - } - }) - }); - if (preventStopPerWindow) { - preventStop = true; - } - } - } - if (!preventStop) { - canPreventStop = false; - app.quit(); - } - } -}); -app.on('ready', () => { - const { screen } = electron; - - // Remove the default electron menus, waiting for the application to set its own. - Menu.setApplicationMenu(Menu.buildFromTemplate([{ - role: 'help', submenu: [{ role: 'toggledevtools'}] - }])); - - function createNewWindow(theUrl) { - - // We must center by hand because \`browserWindow.center()\` fails on multi-screen setups - // See: https://github.com/electron/electron/issues/3490 - const { bounds } = screen.getDisplayNearestPoint(screen.getCursorScreenPoint()); - const height = Math.floor(bounds.height * (2/3)); - const width = Math.floor(bounds.width * (2/3)); - - const y = Math.floor(bounds.y + (bounds.height - height) / 2); - const x = Math.floor(bounds.x + (bounds.width - width) / 2); - - const WINDOW_STATE = 'windowstate'; - const windowState = electronStore.get(WINDOW_STATE, { - width, height, x, y - }); - - let windowOptions = { - show: false, - title: applicationName, - width: windowState.width, - height: windowState.height, - minWidth: 200, - minHeight: 120, - x: windowState.x, - y: windowState.y, - isMaximized: windowState.isMaximized - }; - - // Always hide the window, we will show the window when it is ready to be shown in any case. - const newWindow = new BrowserWindow(windowOptions); - if (windowOptions.isMaximized) { - newWindow.maximize(); - } - newWindow.on('ready-to-show', () => newWindow.show()); - - // Prevent calls to "window.open" from opening an ElectronBrowser window, - // and rather open in the OS default web browser. - newWindow.webContents.on('new-window', (event, url) => { - event.preventDefault(); - shell.openExternal(url); - }); +const container = new Container(); +container.bind(TheiaApplicationName).toConstantValue(applicationName); +container.bind(TheiaBackendMainPath).toConstantValue(mainPath); +container.bind(TheiaIndexHtmlPath).toConstantValue(indexHtmlPath); +container.load(electronMainApplicationModule); - // Save the window geometry state on every change - const saveWindowState = () => { - try { - let bounds; - if (newWindow.isMaximized()) { - bounds = electronStore.get(WINDOW_STATE, {}); - } else { - bounds = newWindow.getBounds(); - } - electronStore.set(WINDOW_STATE, { - isMaximized: newWindow.isMaximized(), - width: bounds.width, - height: bounds.height, - x: bounds.x, - y: bounds.y - }); - } catch (e) { - console.error("Error while saving window state.", e); - } - }; - let delayedSaveTimeout; - const saveWindowStateDelayed = () => { - if (delayedSaveTimeout) { - clearTimeout(delayedSaveTimeout); - } - delayedSaveTimeout = setTimeout(saveWindowState, 1000); - }; - newWindow.on('close', saveWindowState); - newWindow.on('resize', saveWindowStateDelayed); - newWindow.on('move', saveWindowStateDelayed); - newWindow.on('closed', () => { - const index = windows.indexOf(newWindow); - if (index !== -1) { - windows.splice(index, 1); - } - if (windows.length === 0) { - app.quit(); - } - }); +function load(raw) { + return Promise.resolve(raw.default).then(module => + container.load(module) + ) +} - // Notify the renderer process on keyboard layout change - nativeKeymap.onDidChangeKeyboardLayout(() => { - if (!newWindow.isDestroyed()) { - const newLayout = { - info: nativeKeymap.getCurrentKeyboardLayout(), - mapping: nativeKeymap.getKeyMap() - }; - newWindow.webContents.send('keyboardLayoutChanged', newLayout); - } - }); +function start() { + const electronMainApplication = container.get(ElectronMainApplication); + return electronMainApplication.start(electron.app); +} - if (!!theUrl) { - newWindow.loadURL(theUrl); +Promise.resolve()${this.compileElectronMainModuleImports()} + .then(start).catch(reason => { + console.error('Failed to start the electron application.'); + if (reason) { + console.error(reason); } - windows.push(newWindow); - return newWindow; - } - - app.on('window-all-closed', () => { - app.quit(); }); - ipcMain.on('create-new-window', (event, url) => { - createNewWindow(url); - }); - ipcMain.on('open-external', (event, url) => { - shell.openExternal(url); - }); - - // Check whether we are in bundled application or development mode. - // @ts-ignore - const devMode = process.defaultApp || /node_modules[\/]electron[\/]/.test(process.execPath); - const mainWindow = createNewWindow(); - const loadMainWindow = (port) => { - if (!mainWindow.isDestroyed()) { - mainWindow.loadURL('file://' + join(__dirname, '../../lib/index.html') + '?port=' + port); - } - }; - - // We cannot use the \`process.cwd()\` as the application project path (the location of the \`package.json\` in other words) - // in a bundled electron application because it depends on the way we start it. For instance, on OS X, these are a differences: - // https://github.com/theia-ide/theia/issues/3297#issuecomment-439172274 - process.env.THEIA_APP_PROJECT_PATH = resolve(__dirname, '..', '..'); - - // Set the electron version for both the dev and the production mode. (https://github.com/theia-ide/theia/issues/3254) - // Otherwise, the forked backend processes will not know that they're serving the electron frontend. - const { versions } = process; - // @ts-ignore - if (versions && typeof versions.electron !== 'undefined') { - // @ts-ignore - process.env.THEIA_ELECTRON_VERSION = versions.electron; - } - - 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); - }).catch((error) => { - console.error(error); - app.exit(1); - }); - } else { - const cp = fork(mainPath, [], { env: Object.assign({}, process.env) }); - cp.on('message', (message) => { - loadMainWindow(message); - }); - cp.on('error', (error) => { - console.error(error); - app.exit(1); - }); - app.on('quit', () => { - // If we forked the process for the clusters, we need to manually terminate it. - // See: https://github.com/theia-ide/theia/issues/835 - process.kill(cp.pid); - }); - } -}); `; } diff --git a/dev-packages/application-package/src/application-package.ts b/dev-packages/application-package/src/application-package.ts index fff0fb8ced667..27bd31241d0ad 100644 --- a/dev-packages/application-package/src/application-package.ts +++ b/dev-packages/application-package/src/application-package.ts @@ -96,6 +96,7 @@ export class ApplicationPackage { protected _frontendElectronModules: Map | undefined; protected _backendModules: Map | undefined; protected _backendElectronModules: Map | undefined; + protected _electronMainModules: Map | undefined; protected _extensionPackages: ReadonlyArray | undefined; /** @@ -157,6 +158,13 @@ export class ApplicationPackage { return this._backendElectronModules; } + get electronMainModules(): Map { + if (!this._electronMainModules) { + this._electronMainModules = this.computeModules('electronMain'); + } + return this._electronMainModules; + } + protected computeModules

(primary: P, secondary?: S): Map { const result = new Map(); let moduleIndex = 1; diff --git a/dev-packages/application-package/src/extension-package.ts b/dev-packages/application-package/src/extension-package.ts index 442e1d600ecf5..d48985382550c 100644 --- a/dev-packages/application-package/src/extension-package.ts +++ b/dev-packages/application-package/src/extension-package.ts @@ -24,6 +24,7 @@ export interface Extension { frontendElectron?: string; backend?: string; backendElectron?: string; + electronMain?: string; } export class ExtensionPackage { diff --git a/dev-packages/electron/package.json b/dev-packages/electron/package.json index 65afa64ff8a2f..b1b07c3fa9da5 100644 --- a/dev-packages/electron/package.json +++ b/dev-packages/electron/package.json @@ -21,6 +21,7 @@ "electron-replace-ffmpeg": "electron-replace-ffmpeg.js" }, "dependencies": { + "@types/electron-store": "^1.3.1", "electron": "^3.1.7", "electron-download": "^4.1.1", "electron-store": "^2.0.0", diff --git a/packages/core/src/electron-main/electron-main-application-module.ts b/packages/core/src/electron-main/electron-main-application-module.ts new file mode 100644 index 0000000000000..1f7b9db22420f --- /dev/null +++ b/packages/core/src/electron-main/electron-main-application-module.ts @@ -0,0 +1,24 @@ +/******************************************************************************** + * Copyright (C) 2019 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 { bindContributionProvider } from '../common'; +import { ElectronMainApplication, ElectronMainApplicationContribution } from './electron-main-application'; + +export const electronMainApplicationModule = new ContainerModule(bind => { + bind(ElectronMainApplication).toSelf().inSingletonScope(); + bindContributionProvider(bind, ElectronMainApplicationContribution); +}); diff --git a/packages/core/src/electron-main/electron-main-application.ts b/packages/core/src/electron-main/electron-main-application.ts new file mode 100644 index 0000000000000..74a9af183fdb8 --- /dev/null +++ b/packages/core/src/electron-main/electron-main-application.ts @@ -0,0 +1,357 @@ +/******************************************************************************** + * Copyright (C) 2019 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 electron from 'electron'; +import * as nativeKeymap from 'native-keymap'; +import ElectronStorage = require('electron-store'); +import { injectable, inject, named } from 'inversify'; +import { ContributionProvider, MaybePromise } from '../common'; +import { Deferred } from '../common/promise-util'; +import { fork } from 'child_process'; + +export const ElectronMainApplicationContribution = Symbol('ElectronMainApplicationContribution'); +export interface ElectronMainApplicationContribution { + + onStart?(app: Electron.App): MaybePromise; + + /** + * Will wait for both the electron `ready` event, and all contributions to + * finish executing `onStart`. + * + * @param infos https://electronjs.org/docs/api/app#event-ready. + */ + // tslint:disable-next-line:no-any + onReady?(infos?: any): MaybePromise; + + onNewWindow?(newWindow: electron.BrowserWindow): MaybePromise; + + onBeforeQuit?(event: electron.Event): void; + + onQuit?(event: electron.Event, exitCode: number): void; +} + +export interface ElectronBrowserWindowOptions extends electron.BrowserWindowConstructorOptions { + isMaximized?: boolean, +} + +const WindowState = 'windowstate'; +interface WindowState { + isMaximized?: boolean + height: number + width: number + x: number + y: number +} +export interface ElectronMainApplicationStorage { + [WindowState]: WindowState; +} + +export const TheiaApplicationName = Symbol('TheiaApplicationName'); +export type TheiaApplicationName = string; + +export const TheiaBackendMainPath = Symbol('TheiaBackendMainPath'); +export type TheiaBackendMainPath = string; + +export const TheiaIndexHtmlPath = Symbol('TheiaIndexHtmlPath'); +export type TheiaIndexHtmlPath = string; + +@injectable() +export class ElectronMainApplication { + + @inject(TheiaApplicationName) + protected readonly applicationName: TheiaApplicationName; + + @inject(TheiaBackendMainPath) + protected readonly mainPath: TheiaBackendMainPath; + + @inject(TheiaIndexHtmlPath) + protected readonly indexHtml: TheiaIndexHtmlPath; + + @inject(ContributionProvider) @named(ElectronMainApplicationContribution) + protected readonly contributions: ContributionProvider; + + // tslint:disable-next-line:no-any + protected readonly storage = new ElectronStorage(); + + protected readonly backendPortDeferred = new Deferred(); + /** + * Set when the NodeJS backend has started and dispatched the port it is running on. + */ + protected readonly backendPort = this.backendPortDeferred.promise; + + async start(app: electron.App): Promise { + await this.bindApplicationEvents(app); + this.startBackend(app); // no await here, let it be concurrent. + try { + await Promise.all(this.contributions.getContributions() + .map(contribution => contribution.onStart && contribution.onStart(app))); + } catch (error) { + console.error(error); + app.exit(1); + return; + } + this.ready(app); + } + + protected async ready(app: electron.App): Promise { + const infos = await app.whenReady(); + this.bindIpcEvents(); + await this.setTempMenu(); + await Promise.all(this.contributions.getContributions() + .map(contribution => contribution.onReady && contribution.onReady(infos))); + await this.openWindow(); + } + + protected canPreventStop = true; + protected async beforeQuit(event: electron.Event): Promise { + if (this.canPreventStop) { + // Pause the stop. + event.preventDefault(); + let preventStop = false; + // Ask all opened windows whether they want to prevent the \`close\` event or not. + for (const window of electron.BrowserWindow.getAllWindows()) { + if (!preventStop) { + window.webContents.send('prevent-stop-request'); + const preventStopPerWindow = await new Promise(resolve => { + // tslint:disable-next-line:no-any + electron.ipcMain.once('prevent-stop-response', (_: electron.Event, arg: any) => { + if (!!arg && 'preventStop' in arg && typeof arg.preventStop === 'boolean') { + resolve(arg.preventStop); + } + }); + }); + if (preventStopPerWindow) { + preventStop = true; + } + } + } + if (!preventStop) { + this.canPreventStop = false; + electron.app.quit(); + } + } + this.contributions.getContributions() + .map(contribution => contribution.onBeforeQuit && contribution.onBeforeQuit(event)); + } + + protected quit(event: electron.Event, exitCode: number): void { + this.contributions.getContributions() + .map(contribution => contribution.onQuit && contribution.onQuit(event, exitCode)); + } + + protected bindApplicationEvents(app: electron.App): MaybePromise { + app.on('activate', (event, hasVisibleWindows) => this.activate(event, hasVisibleWindows)); + app.on('window-all-closed', () => this.windowAllClosed(app)); + app.on('before-quit', event => this.beforeQuit(event)); + app.on('quit', (event, exitCode) => this.quit(event, exitCode)); + } + + protected bindIpcEvents(): void { + electron.ipcMain.on('create-new-window', + (event: electron.IpcMessageEvent, url?: string) => this.createNewWindow(url)); + electron.ipcMain.on('open-external', + (event: electron.IpcMessageEvent, url?: string) => url && this.openExternally(url)); + } + + protected activate(event: electron.Event, hasVisibleWindows: boolean): MaybePromise { + if (electron.BrowserWindow.getAllWindows().length === 0) { + this.openWindow(); + } + } + + protected async startBackend(app: electron.App): Promise { + const devMode = process.defaultApp || /node_modules[\/]electron[\/]/.test(process.execPath); + try { + await (devMode + ? this.requireBackend(app) + : this.forkBackend(app)); + } catch (error) { + console.error(error); + app.exit(1); + } + } + + /** + * In development mode, it is easier to run the backend as part of the main process. + * + * @param app + */ + protected async requireBackend(app: electron.App): Promise { + // tslint:disable-next-line:no-any + require(this.mainPath).then((address: any) => { + this.backendPortDeferred.resolve(address.port); + }).catch((error: Error) => { + this.backendPortDeferred.reject(error); + }); + return this.backendPort; + } + + /** + * In production mode, the backend gets its own process. + * + * @param app + */ + protected async forkBackend(app: electron.App): Promise { + const backendProcess = fork(this.mainPath); + backendProcess.on('error', error => { + this.backendPortDeferred.reject(error); + }); + backendProcess.on('message', message => { + const port = Number.parseInt(message, 10); + if (!isNaN(port)) { + this.backendPortDeferred.resolve(port); + } + }); + app.on('quit', () => { + process.kill(backendProcess.pid); + }); + return this.backendPort; + } + + /** + * @todo Allow user to open windows when none is left. + * + * Usually, Mac application don't stop when there isn't more windows. + * But we will skip that platform check for now, until we support opening + * new windows when none were left. + */ + protected windowAllClosed(app: electron.App): MaybePromise { + app.quit(); + } + + protected async openWindow(): Promise { + const backendPort = await this.backendPort; + return this.createNewWindow(`file://${this.indexHtml}?port=${backendPort}`); + } + + protected async createNewWindow(url?: string): Promise { + const windowOptions: ElectronBrowserWindowOptions = { + show: false, + ...await this.getWindowOptions(url), + }; + + // Always hide the window, we will show the window when it is ready to be shown in any case. + const newWindow = new electron.BrowserWindow(windowOptions); + if (windowOptions.isMaximized) { + newWindow.maximize(); + } + newWindow.on('ready-to-show', () => newWindow.show()); + + await this.bindWindowEvents(newWindow); + + if (url) { + newWindow.loadURL(url); + } + + await Promise.all(this.contributions.getContributions() + .map(contribution => contribution.onNewWindow && contribution.onNewWindow(newWindow))); + + return newWindow; + } + + protected bindWindowEvents(electronWindow: electron.BrowserWindow): MaybePromise { + + // Prevent calls to "window.open" from opening an ElectronBrowser window, + // and rather open in the OS default web browser. + electronWindow.webContents.on('new-window', (event, url) => { + event.preventDefault(); + electron.shell.openExternal(url); + }); + + // Notify the renderer process on keyboard layout change + nativeKeymap.onDidChangeKeyboardLayout(() => { + if (!electronWindow.isDestroyed()) { + const newLayout = { + info: nativeKeymap.getCurrentKeyboardLayout(), + mapping: nativeKeymap.getKeyMap() + }; + electronWindow.webContents.send('keyboardLayoutChanged', newLayout); + } + }); + + const saveWindowState = () => { + try { + let bounds: electron.Rectangle; + if (electronWindow.isMaximized()) { + bounds = this.storage.get(WindowState, {} as WindowState); + } else { + bounds = electronWindow.getBounds(); + } + this.storage.set(WindowState, { + isMaximized: electronWindow.isMaximized(), + width: bounds.width, + height: bounds.height, + x: bounds.x, + y: bounds.y + }); + } catch (e) { + console.error('Error while saving window state.', e); + } + }; + // tslint:disable-next-line:no-any + let delayedSaveTimeout: any; + const saveWindowStateDelayed = () => { + if (delayedSaveTimeout) { + clearTimeout(delayedSaveTimeout); + } + delayedSaveTimeout = setTimeout(saveWindowState, 1000); + }; + electronWindow.on('close', saveWindowState); + electronWindow.on('resize', saveWindowStateDelayed); + electronWindow.on('move', saveWindowStateDelayed); + } + + protected getWindowOptions(url?: string): MaybePromise { + // We must center by hand because \`browserWindow.center()\` fails on multi-screen setups + // See: https://github.com/electron/electron/issues/3490 + const { bounds } = electron.screen.getDisplayNearestPoint(electron.screen.getCursorScreenPoint()); + const height = Math.floor(bounds.height * (2 / 3)); + const width = Math.floor(bounds.width * (2 / 3)); + + const y = Math.floor(bounds.y + (bounds.height - height) / 2); + const x = Math.floor(bounds.x + (bounds.width - width) / 2); + + const windowState = this.storage.get(WindowState, { + width, height, x, y, + }); + + return { + show: false, + title: this.applicationName, + width: windowState.width, + height: windowState.height, + minWidth: 200, + minHeight: 120, + x: windowState.x, + y: windowState.y, + isMaximized: windowState.isMaximized + }; + } + + protected openExternally(url: string): MaybePromise { + electron.shell.openExternal(url); + } + + /** + * Remove the default electron menus, waiting for the application to set its own. + */ + protected setTempMenu(): MaybePromise { + electron.Menu.setApplicationMenu(electron.Menu.buildFromTemplate([{ + role: 'help', submenu: [{ role: 'toggledevtools' }] + }])); + } + +} diff --git a/packages/core/src/electron-main/index.ts b/packages/core/src/electron-main/index.ts new file mode 100644 index 0000000000000..2e6d741b6708c --- /dev/null +++ b/packages/core/src/electron-main/index.ts @@ -0,0 +1,18 @@ +/******************************************************************************** + * Copyright (C) 2019 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 * from './electron-main-application'; +export * from './electron-main-application-module'; diff --git a/yarn.lock b/yarn.lock index 22fc627496b65..48b9444dc1053 100644 --- a/yarn.lock +++ b/yarn.lock @@ -190,6 +190,13 @@ version "3.5.1" resolved "https://registry.yarnpkg.com/@types/diff/-/diff-3.5.1.tgz#30253f6e177564ad7da707b1ebe46d3eade71706" +"@types/electron-store@^1.3.1": + version "1.3.1" + resolved "https://registry.yarnpkg.com/@types/electron-store/-/electron-store-1.3.1.tgz#6fa239b0582374308b72a8db9492645e51f64990" + integrity sha512-RvEAlIWcy7ATEMeyw481SdnuceN6Pd2Qh5KSW5NohwtY1t1uP0MmC3Cvoszd+ueGLqTKCpRwhCJY4qdER5QQVA== + dependencies: + "@types/node" "*" + "@types/events@*": version "1.2.0" resolved "https://registry.yarnpkg.com/@types/events/-/events-1.2.0.tgz#81a6731ce4df43619e5c8c945383b3e62a89ea86"