diff --git a/package.json b/package.json index 422201c4..1d6bcbac 100644 --- a/package.json +++ b/package.json @@ -140,6 +140,7 @@ "license": "BSD-3-Clause", "devDependencies": { "@yarnpkg/lockfile": "~1.1.0", + "@types/ejs": "^3.1.0", "css-loader": "~5.1.1", "electron": "^12.0.0", "electron-builder": "^22.11.11", @@ -258,7 +259,7 @@ "@types/semver": "^7.3.4", "@types/yargs": "^16.0.0", "bottlejs": "^2.0.0", - "electron-debug": "^3.2.0", + "ejs": "^3.1.6", "electron-log": "^4.3.2", "fix-path": "^3.0.0", "node-fetch": "~2.6.1", diff --git a/scripts/copyassets.js b/scripts/copyassets.js index aa42d920..059067fc 100644 --- a/scripts/copyassets.js +++ b/scripts/copyassets.js @@ -48,6 +48,9 @@ function copyAssests() { const htmlPath = path.join('browser', 'index.html'); fs.copySync(path.join(srcDir, htmlPath), path.join(dest, htmlPath)); + const envInfoPath = path.join('main', 'env_info.py'); + fs.copySync(path.join(srcDir, envInfoPath), path.join(dest, envInfoPath)); + // Copy install scripts if (platform === 'darwin') { fs.copySync(path.join(path.resolve('./'), 'electron-builder-scripts', 'postinstall'), path.join(buildDir, 'pkg-scripts', 'postinstall')); diff --git a/src/browser/app.tsx b/src/browser/app.tsx index 71b1b423..d81cafe5 100644 --- a/src/browser/app.tsx +++ b/src/browser/app.tsx @@ -71,7 +71,7 @@ class Application extends React.Component { this._serverReady(data); @@ -191,7 +191,9 @@ class Application extends React.Component { ipcRenderer.send('lab-ready'); - (this.refs.splash as SplashScreen).fadeSplashScreen(); + if (this.refs.splash) { + (this.refs.splash as SplashScreen).fadeSplashScreen(); + } }); }); }) diff --git a/src/browser/extensions/desktop-extension/envStatus.tsx b/src/browser/extensions/desktop-extension/envStatus.tsx new file mode 100644 index 00000000..09bab4c5 --- /dev/null +++ b/src/browser/extensions/desktop-extension/envStatus.tsx @@ -0,0 +1,145 @@ +// Copyright (c) Jupyter Development Team. +// Distributed under the terms of the Modified BSD License. + +import { VDomModel, VDomRenderer } from '@jupyterlab/apputils'; +import React from 'react'; +import { GroupItem, interactiveItem, TextItem } from '@jupyterlab/statusbar'; +import { + pythonIcon +} from '@jupyterlab/ui-components'; + +/** + * A pure functional component for rendering environment status. + */ +function EnvironmentStatusComponent( + props: EnvironmentStatusComponent.IProps +): React.ReactElement { + return ( + + + + + ); +} + +/** + * A namespace for EnvironmentStatusComponent statics. + */ +namespace EnvironmentStatusComponent { + /** + * Props for the environment status component. + */ + export interface IProps { + /** + * A click handler for the environment status component. By default + * we have it bring up the environment change dialog. + */ + handleClick: () => void; + + /** + * The name the environment. + */ + name: string; + + /** + * The description of the environment. + */ + description: string; + } +} + +/** + * A VDomRenderer widget for displaying the environment. + */ +export class EnvironmentStatus extends VDomRenderer { + /** + * Construct the environment status widget. + */ + constructor(opts: EnvironmentStatus.IOptions) { + super(new EnvironmentStatus.Model()); + this.model.name = opts.name; + this.model.description = opts.description; + this._handleClick = opts.onClick; + this.addClass(interactiveItem); + } + + /** + * Render the environment status item. + */ + render() { + if (this.model === null) { + return null; + } else { + return ( + + ); + } + } + + private _handleClick: () => void; +} + +/** + * A namespace for EnvironmentStatus statics. + */ +export namespace EnvironmentStatus { + export class Model extends VDomModel { + constructor() { + super(); + + this._name = 'env'; + this._description = ''; + } + + get name() { + return this._name; + } + + set name(val: string) { + const oldVal = this._name; + if (oldVal === val) { + return; + } + this._name = val; + this.stateChanged.emit(void 0); + } + + get description(): string { + return this._description; + } + set description(val: string) { + const oldVal = this._description; + if (oldVal === val) { + return; + } + this._description = val; + this.stateChanged.emit(void 0); + } + + private _name: string; + private _description: string; + } + + /** + * Options for creating a EnvironmentStatus object. + */ + export interface IOptions { + /** + * Environment name + */ + name: string; + /** + * Environment description + */ + description: string; + /** + * A click handler for the item. By default + * we launch an environment selection dialog. + */ + onClick: () => void; + } +} diff --git a/src/browser/extensions/desktop-extension/index.ts b/src/browser/extensions/desktop-extension/index.ts index 66448637..54db90d8 100644 --- a/src/browser/extensions/desktop-extension/index.ts +++ b/src/browser/extensions/desktop-extension/index.ts @@ -11,6 +11,8 @@ import { IMainMenu } from '@jupyterlab/mainmenu'; +import { IStatusBar } from '@jupyterlab/statusbar'; + import { JupyterFrontEndPlugin } from '@jupyterlab/application'; @@ -24,12 +26,14 @@ import { } from '../../../asyncremote'; import { IAppRemoteInterface } from '../../../main/app'; +import { IPythonEnvironment } from 'src/main/tokens'; +import { EnvironmentStatus } from './envStatus'; const desktopExtension: JupyterFrontEndPlugin = { id: 'jupyterlab-desktop.extensions.desktop', - requires: [ICommandPalette, IMainMenu], - activate: (app: ElectronJupyterLab, palette: ICommandPalette, menu: IMainMenu) => { + requires: [ICommandPalette, IMainMenu, IStatusBar], + activate: (app: ElectronJupyterLab, palette: ICommandPalette, menu: IMainMenu, statusBar: IStatusBar) => { app.commands.addCommand('check-for-updates', { label: 'Check for Updates…', execute: () => { @@ -48,6 +52,30 @@ const desktopExtension: JupyterFrontEndPlugin = { { command: 'open-dev-tools' }, { command: 'check-for-updates' } ], 20); + + const changeEnvironment = async () => { + asyncRemoteRenderer.runRemoteMethod(IAppRemoteInterface.showPythonPathSelector, void(0)); + }; + + const statusItem = new EnvironmentStatus({ name: 'env', description: '', onClick: changeEnvironment }); + + statusBar.registerStatusItem('jupyterlab-desktop-py-env-status', { + item: statusItem, + align: 'left' + }); + + const updateStatusItem = (env: IPythonEnvironment) => { + statusItem.model.name = env.name; + let packages = []; + for (const name in env.versions) { + packages.push(`${name}: ${env.versions[name]}`); + } + statusItem.model.description = `${env.name}\n${env.path}\n${packages.join(', ')}`; + }; + + asyncRemoteRenderer.runRemoteMethod(IAppRemoteInterface.getCurrentPythonEnvironment, void(0)).then((env) => { + updateStatusItem(env); + }); }, autoStart: true }; diff --git a/src/main/app.ts b/src/main/app.ts index a2911999..e7b57fb7 100644 --- a/src/main/app.ts +++ b/src/main/app.ts @@ -2,7 +2,7 @@ // Distributed under the terms of the Modified BSD License. import { - app, BrowserWindow, ipcMain, shell + app, BrowserWindow, dialog, ipcMain, shell } from 'electron'; import { @@ -20,9 +20,13 @@ import { import log from 'electron-log'; import { AsyncRemote, asyncRemoteMain } from '../asyncremote'; +import { IPythonEnvironment } from './tokens'; +import { IRegistry } from './registry'; import fetch from 'node-fetch'; import * as yaml from 'js-yaml'; import * as semver from 'semver'; +import * as ejs from 'ejs'; +import * as path from 'path'; export interface IApplication { @@ -40,6 +44,8 @@ interface IApplication { * Force the application service to write data to the disk. */ saveState: (service: IStatefulService, data: JSONValue) => Promise; + + getPythonEnvironment(): Promise; } /** @@ -97,16 +103,26 @@ namespace IAppRemoteInterface { let openDevTools: AsyncRemote.IMethod = { id: 'JupyterLabDesktop-open-dev-tools' }; + export + let getCurrentPythonEnvironment: AsyncRemote.IMethod = { + id: 'JupyterLabDesktop-get-python-env' + }; + export + let showPythonPathSelector: AsyncRemote.IMethod = { + id: 'JupyterLabDesktop-select-python-path' + }; } export class JupyterApplication implements IApplication, IStatefulService { readonly id = 'JupyterLabDesktop'; + private _registry: IRegistry; /** * Construct the Jupyter application */ - constructor() { + constructor(registry: IRegistry) { + this._registry = registry; this._registerListeners(); // Get application state from state db file. @@ -122,13 +138,29 @@ class JupyterApplication implements IApplication, IStatefulService { }); this._applicationState = { - checkForUpdatesAutomatically: true + checkForUpdatesAutomatically: true, + pythonPath: '', }; this.registerStatefulService(this) .then((state: JupyterApplication.IState) => { if (state) { this._applicationState = state; + if (this._applicationState.pythonPath === undefined) { + this._applicationState.pythonPath = ''; + } + } + + let pythonPath = this._applicationState.pythonPath; + if (pythonPath === '') { + pythonPath = this._registry.getBundledPythonPath(); + } + + if (this._registry.validatePythonEnvironmentAtPath(pythonPath)) { + this._registry.setDefaultPythonPath(pythonPath); + this._applicationState.pythonPath = pythonPath; + } else { + this._showPythonSelectorDialog('invalid-setting'); } if (this._applicationState.checkForUpdatesAutomatically) { @@ -139,13 +171,21 @@ class JupyterApplication implements IApplication, IStatefulService { }); } + getPythonEnvironment(): Promise { + return new Promise((resolve, _reject) => { + this._appState.then((state: JSONObject) => { + resolve(this._registry.getCurrentPythonEnvironment()); + }); + }); + } + registerStatefulService(service: IStatefulService): Promise { this._services.push(service); return new Promise((res, rej) => { this._appState .then((state: JSONObject) => { - if (state[service.id] && service.verifyState(state[service.id])) { + if (state && state[service.id] && service.verifyState(state[service.id])) { res(state[service.id]); } res(null); @@ -266,6 +306,39 @@ class JupyterApplication implements IApplication, IStatefulService { shell.openExternal('https://github.com/jupyterlab/jupyterlab-desktop/releases'); }); + ipcMain.on('select-python-path', (event) => { + const currentEnv = this._registry.getCurrentPythonEnvironment(); + + dialog.showOpenDialog({ + properties: ['openFile', 'showHiddenFiles', 'noResolveAliases'], + buttonLabel: 'Use Path', + defaultPath: currentEnv ? path.dirname(currentEnv.path) : undefined + }).then(({filePaths}) => { + if (filePaths.length > 0) { + event.sender.send('custom-python-path-selected', filePaths[0]); + } + }); + }); + + ipcMain.handle('validate-python-path', (event, path) => { + return this._registry.validatePythonEnvironmentAtPath(path); + }); + + ipcMain.on('show-invalid-python-path-message', (event, path) => { + const requirements = this._registry.getRequirements(); + const reqVersions = requirements.map((req) => `${req.name} ${req.versionRange.format()}`); + const reqList = reqVersions.join(', '); + const message = `Failed to find a compatible Python environment at the configured path "${path}". Environment Python package requirements are: ${reqList}.` + dialog.showMessageBox({message, type: 'error' }); + }); + + ipcMain.on('set-python-path', (event, path) => { + this._applicationState.pythonPath = path; + app.relaunch(); + app.quit(); + }); + + asyncRemoteMain.registerRemoteMethod(IAppRemoteInterface.checkForUpdates, (): Promise => { this._checkForUpdates('always'); @@ -277,6 +350,17 @@ class JupyterApplication implements IApplication, IStatefulService { this._window.webContents.openDevTools(); return Promise.resolve(); }); + + asyncRemoteMain.registerRemoteMethod(IAppRemoteInterface.getCurrentPythonEnvironment, + (): Promise => { + return this.getPythonEnvironment(); + }); + + asyncRemoteMain.registerRemoteMethod(IAppRemoteInterface.showPythonPathSelector, + (): Promise => { + this._showPythonSelectorDialog('change'); + return Promise.resolve(); + }); } private _showUpdateDialog(type: 'updates-available' | 'error' | 'no-updates') { @@ -297,16 +381,16 @@ class JupyterApplication implements IApplication, IStatefulService { const message = type === 'error' ? 'Error occurred while checking for updates!' : type === 'no-updates' ? 'There are no updates available.' : - `There is a new version available. Download the latest version from the Releases page.` + `There is a new version available. Download the latest version from the Releases page.`; - const pageSource = ` + const template = `
- ${message} + <%= message %>
- +
@@ -323,6 +407,119 @@ class JupyterApplication implements IApplication, IStatefulService { `; + const pageSource = ejs.render(template, {message, checkForUpdatesAutomatically}); + dialog.loadURL(`data:text/html;charset=utf-8,${pageSource}`); + } + + private _showPythonSelectorDialog(reason: 'change' | 'invalid-setting' = 'change') { + const dialog = new BrowserWindow({ + title: 'Set Python Environment', + width: 600, + height: 280, + resizable: false, + parent: this._window, + modal: true, + webPreferences: { + nodeIntegration: true, + enableRemoteModule: true, + contextIsolation: false + } + }); + dialog.setMenuBarVisibility(false); + + const bundledPythonPath = this._registry.getBundledPythonPath(); + const pythonPath = this._applicationState.pythonPath; + let useBundledPythonPath = false; + if (pythonPath === '' || pythonPath === bundledPythonPath) { + useBundledPythonPath = true; + } + const configuredPath = pythonPath === '' ? bundledPythonPath : pythonPath; + const requirements = this._registry.getRequirements(); + const reqVersions = requirements.map((req) => `${req.name} ${req.versionRange.format()}`); + const reqList = reqVersions.join(', '); + + const message = reason === 'change' ? + `Select the Python executable in the conda or virtualenv environment you would like to use for JupyterLab Desktop. Python packages in the environment selected need to meet the following requirements: ${reqList}. Prebuilt extensions installed in the selected environment will also be available in JupyterLab Desktop.` : + ejs.render(`Failed to find a compatible Python environment at the configured path "<%= configuredPath %>". Environment Python package requirements are: ${reqList}.`, {configuredPath}); + + const template = ` + + +
+
+ Set Python Environment +
+
+ ${message} +
+
+
+ onchange="handleEnvTypeChange(this);"> + +
+
+ onchange="handleEnvTypeChange(this);"> + +
+ +
+
+ +
+
+ +
+
+
+ + +
+
+
+ + + + `; + const pageSource = ejs.render(template, {useBundledPythonPath, pythonPath}); dialog.loadURL(`data:text/html;charset=utf-8,${pageSource}`); } @@ -389,14 +586,15 @@ namespace JupyterApplication { export interface IState extends JSONObject { checkForUpdatesAutomatically?: boolean; + pythonPath?: string; } } let service: IService = { - requirements: [], + requirements: ['IRegistry'], provides: 'IApplication', - activate: (): IApplication => { - return new JupyterApplication(); + activate: (registry: IRegistry): IApplication => { + return new JupyterApplication(registry); } }; export default service; diff --git a/src/main/env_info.py b/src/main/env_info.py new file mode 100644 index 00000000..a7c0f66b --- /dev/null +++ b/src/main/env_info.py @@ -0,0 +1,15 @@ +import os, sys, json + +env_type = 'system' +env_name = 'python' + +if (getattr(sys, "base_prefix", None) or getattr(sys, "real_prefix", None) or sys.prefix) != sys.prefix: + env_type = 'venv' + +if env_type != 'venv' and os.path.exists(os.path.join(sys.prefix, "conda-meta")): + env_type = 'conda' + +if env_type != 'system': + env_name = os.path.basename(sys.prefix) + +print(json.dumps({"type" : env_type, "name": env_name})) diff --git a/src/main/main.ts b/src/main/main.ts index f9debaea..ff46d616 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -23,13 +23,6 @@ if (process.argv.length > 1) { const isDevMode = process.mainModule.filename.indexOf( 'app.asar' ) === -1; -/** - * Require debugging tools. Only - * runs when in development. - */ -// tslint:disable-next-line:no-var-requires -require('electron-debug')({showDevTools: false}); - /** * * On Mac OSX the PATH env variable a packaged app gets does not * contain all the information that is usually set in .bashrc, .bash_profile, etc. diff --git a/src/main/registry.ts b/src/main/registry.ts index 622bd078..7166a156 100644 --- a/src/main/registry.ts +++ b/src/main/registry.ts @@ -1,5 +1,5 @@ import { - execFile + execFile, ExecFileOptions, execFileSync } from 'child_process'; import { @@ -10,13 +10,13 @@ import { join, basename, normalize, dirname } from 'path'; +import * as path from 'path'; + import { app, dialog } from 'electron'; -import { - Range, satisfies -} from 'semver'; +import * as semver from 'semver'; import { ArrayExt @@ -24,85 +24,101 @@ import { import * as fs from 'fs'; import log from 'electron-log'; +import { IEnvironmentType, IPythonEnvironment, IVersionContainer } from './tokens'; + +const env_info_py = fs.readFileSync(path.join(__dirname, 'env_info.py')).toString(); let which = require('which'); let WinRegistry = require('winreg'); export interface IRegistry { - getDefaultEnvironment: () => Promise; + getDefaultEnvironment: () => Promise; - getEnvironmentByPath: (path: string) => Promise; + getEnvironmentByPath: (path: string) => Promise; setDefaultEnvironment: (path: string) => Promise; // refreshEnvironmentList: () => Promise; - getEnvironmentList: () => Promise; + getEnvironmentList: () => Promise; + + addEnvironment: (path: string) => Promise; + + getUserJupyterPath: () => Promise; + + getBundledPythonPath: () => string; + + validatePythonEnvironmentAtPath:(path: string) => boolean; + + setDefaultPythonPath: (path: string) => void; - addEnvironment: (path: string) => Promise; + getCurrentPythonEnvironment: () => IPythonEnvironment; - getUserJupyterPath: () => Promise; + getAdditionalPathIncludesForPythonPath: (pythonPath: string) => string; + + getRequirements: () => Registry.IRequirement[]; } export class Registry implements IRegistry { - constructor() { this._requirements = [ { - name: 'jupyter_core', - moduleName: 'jupyter', + name: 'jupyterlab', + moduleName: 'jupyterlab', commands: ['--version'], - versionRange: new Range('>=4.7.0') - }, - { - name: 'notebook', - moduleName: 'jupyter', - commands: ['notebook', '--version'], - versionRange: new Range('>=6.0.0') + versionRange: new semver.Range('>=3.1.0') } ]; - let pathEnvironments = this._loadPATHEnvironments(); - let condaEnvironments = this._loadCondaEnvironments(); - let allEnvironments = [pathEnvironments, condaEnvironments]; - if (process.platform === 'win32') { - let windowRegEnvironments = this._loadWindowsRegistryEnvironments(this._requirements); - allEnvironments.push(windowRegEnvironments); - } + // disable registry building since environment selection list is not + // exposed to users yet + const onlySingleUserEnvSupport = true; + + if (!onlySingleUserEnvSupport) { + let pathEnvironments = this._loadPATHEnvironments(); + let condaEnvironments = this._loadCondaEnvironments(); + let allEnvironments = [pathEnvironments, condaEnvironments]; + if (process.platform === 'win32') { + let windowRegEnvironments = this._loadWindowsRegistryEnvironments(this._requirements); + allEnvironments.push(windowRegEnvironments); + } - this._registryBuilt = Promise.all(allEnvironments).then(environments => { - let flattenedEnvs: Registry.IPythonEnvironment[] = Array.prototype.concat.apply([], environments); - let uniqueEnvs = this._getUniqueObjects(flattenedEnvs, env => { - return env.path; - }); + this._registryBuilt = Promise.all(allEnvironments).then(environments => { + let flattenedEnvs: IPythonEnvironment[] = Array.prototype.concat.apply([], environments); + let uniqueEnvs = this._getUniqueObjects(flattenedEnvs, env => { + return env.path; + }); - return this._filterPromises(uniqueEnvs, (value) => { - return this._pathExists(value.path); - }).then(existingEnvs => { - let updatedEnvs = this._updatePythonEnvironmentsWithRequirementVersions(uniqueEnvs, this._requirements); - return updatedEnvs.then(envs => { - let filteredEnvs = this._filterPythonEnvironmentsByRequirements(envs, this._requirements); - this._sortEnvironments(filteredEnvs, this._requirements); + return this._filterPromises(uniqueEnvs, (value) => { + return this._pathExists(value.path); + }).then(existingEnvs => { + let updatedEnvs = this._updatePythonEnvironmentsWithRequirementVersions(uniqueEnvs, this._requirements); + return updatedEnvs.then(envs => { + let filteredEnvs = this._filterPythonEnvironmentsByRequirements(envs, this._requirements); + this._sortEnvironments(filteredEnvs, this._requirements); - this._setDefaultEnvironment(filteredEnvs[0]); - this._environments = this._environments.concat(filteredEnvs); + this._setDefaultEnvironment(filteredEnvs[0]); + this._environments = this._environments.concat(filteredEnvs); - return; + return; + }); }); - }); - }).catch(reason => { - if (reason.fileName || reason.lineNumber) { - log.error(`Registry building failed! ${reason.name} at ${reason.fileName}:${reason.lineNumber}: ${reason.message}`); - } else if (reason.stack) { - log.error(`Registry building failed! ${reason.name}: ${reason.message}`); - log.error(reason.stack); - } else { - log.error(`Registry building failed! ${reason.name}: ${reason.message}`); - } - this._setDefaultEnvironment(undefined); - }); + }).catch(reason => { + if (reason.fileName || reason.lineNumber) { + log.error(`Registry building failed! ${reason.name} at ${reason.fileName}:${reason.lineNumber}: ${reason.message}`); + } else if (reason.stack) { + log.error(`Registry building failed! ${reason.name}: ${reason.message}`); + log.error(reason.stack); + } else { + log.error(`Registry building failed! ${reason.name}: ${reason.message}`); + } + this._setDefaultEnvironment(undefined); + }); + } else { + this._registryBuilt = Promise.resolve(); + } } /** @@ -110,38 +126,21 @@ export class Registry implements IRegistry { * * @returns a promise containin the default environment */ - getDefaultEnvironment(): Promise { - const platform = process.platform; - let envPath = join(dirname(app.getAppPath()), 'jlab_server'); - if (platform !== 'win32') { - envPath = join(envPath, 'bin'); - } - const pythonPath = join(envPath, `python${platform === 'win32' ? '.exe' : ''}`); - - return Promise.resolve({ - path: pythonPath, - name: 'App bundled', - type: Registry.IEnvironmentType.PATH, - versions: { - 'jupyter_core': '4.7.0', - 'notebook': '6.0.0' - }, - default: true, + getDefaultEnvironment(): Promise { + return new Promise((resolve, reject) => { + this._registryBuilt.then(() => { + if (this._default) { + resolve(this._default); + } else { + reject(new Error(`No default environment found!`)); + } + }).catch(reason => { + reject(new Error(`Registry failed to build!`)); + }); }); - // return new Promise((resolve, reject) => { - // this._registryBuilt.then(() => { - // if (this._default) { - // resolve(this._default); - // } else { - // reject(new Error(`No default environment found!`)); - // } - // }).catch(reason => { - // reject(new Error(`Registry failed to build!`)); - // }); - // }); - } - - getEnvironmentByPath(pathToMatch: string): Promise { + } + + getEnvironmentByPath(pathToMatch: string): Promise { return new Promise((resolve, reject) => { this._registryBuilt.then(() => { let matchingEnv = ArrayExt.findFirstValue(this._environments, env => pathToMatch === env.path); @@ -201,7 +200,7 @@ export class Registry implements IRegistry { * Retrieve the complete list of environments, once they have been resolved * @returns a promise that resolves to a complete list of environments */ - getEnvironmentList(): Promise { + getEnvironmentList(): Promise { return new Promise((resolve, reject) => { this._registryBuilt.then(() => { if (this._environments) { @@ -220,7 +219,7 @@ export class Registry implements IRegistry { * entire registry to be resolved first. * @param path The location of the python executable to create an environment from */ - addEnvironment(path: string): Promise { + addEnvironment(path: string): Promise { return new Promise((resolve, reject) => { this._buildEnvironmentFromPath(path, this._requirements).then(newEnv => { this._environments.push(newEnv); @@ -235,8 +234,8 @@ export class Registry implements IRegistry { * * @return a promise that is fulfilled with the user path. */ - getUserJupyterPath(): Promise { - return new Promise((resolve, reject) => { + getUserJupyterPath(): Promise { + return new Promise((resolve, reject) => { dialog.showOpenDialog({ properties: ['openFile', 'showHiddenFiles'], buttonLabel: 'Use Path' @@ -251,11 +250,87 @@ export class Registry implements IRegistry { }); } - private _buildEnvironmentFromPath(pythonPath: string, requirements: Registry.IRequirement[]): Promise { - let newEnvironment: Registry.IPythonEnvironment = { + validatePythonEnvironmentAtPath(path: string): boolean { + if (!fs.existsSync(path)) { + return false; + } + + for (const req of this._requirements) { + let pythonOutput = this._runPythonModuleCommandSync(path, req.moduleName, req.commands); + let versionFromOutput = this._extractVersionFromExecOutputSync(pythonOutput); + if (!semver.satisfies(versionFromOutput, req.versionRange)) { + return false; + } + } + + return true; + } + + getEnvironmentInfo(pythonPath: string): IPythonEnvironment { + const runOptions = { env: { 'PATH': this.getAdditionalPathIncludesForPythonPath(pythonPath)}}; + const pythonVersionOutput = this._runCommandSync(pythonPath, ['--version'], runOptions); + const pythonVersion = this._extractVersionFromExecOutputSync(pythonVersionOutput); + const jlabVersionOutput = this._runCommandSync(pythonPath, ['-m', 'jupyterlab', '--version'], runOptions); + const jlabVersion = this._extractVersionFromExecOutputSync(jlabVersionOutput); + const env_info_out = this._runCommandSync(pythonPath, ['-c', env_info_py]); + const envInfo = JSON.parse(env_info_out.trim()); + const envName = `${envInfo.type}: ${envInfo.name}`; + + return { + type: IEnvironmentType.PATH, + name: envName, + path: pythonPath, + versions: { 'python': pythonVersion, 'jupyterlab': jlabVersion }, + default: false + }; + } + + setDefaultPythonPath(path: string): void { + this._default = this.getEnvironmentInfo(path); + } + + getCurrentPythonEnvironment() : IPythonEnvironment { + return this._default; + } + + getBundledPythonPath(): string { + const platform = process.platform; + let envPath = join(dirname(app.getAppPath()), 'jlab_server'); + if (platform !== 'win32') { + envPath = join(envPath, 'bin'); + } + const bundledPythonPath = join(envPath, `python${platform === 'win32' ? '.exe' : ''}`); + + return bundledPythonPath; + } + + getAdditionalPathIncludesForPythonPath(pythonPath: string): string { + const platform = process.platform; + + let envPath = path.dirname(pythonPath); + if (platform !== 'win32') { + envPath = path.normalize(path.join(envPath, '../')); + } + + let path_env = ''; + if (platform === 'win32') { + path_env = `${envPath};${envPath}\\Library\\mingw-w64\\bin;${envPath}\\Library\\usr\\bin;${envPath}\\Library\\bin;${envPath}\\Scripts;${envPath}\\bin;${process.env['PATH']}`; + } else { + path_env = `${envPath}:${process.env['PATH']}`; + } + + return path_env; + } + + getRequirements(): Registry.IRequirement[] { + return this._requirements; + } + + private _buildEnvironmentFromPath(pythonPath: string, requirements: Registry.IRequirement[]): Promise { + let newEnvironment: IPythonEnvironment = { name: `SetDefault-${basename(pythonPath)}`, path: pythonPath, - type: Registry.IEnvironmentType.PATH, + type: IEnvironmentType.PATH, versions: {}, default: false }; @@ -274,7 +349,8 @@ export class Registry implements IRegistry { }); } - private _loadPATHEnvironments(): Promise { + + private _loadPATHEnvironments(): Promise { let pythonExecutableName: string; if (process.platform === 'win32') { pythonExecutableName = 'python.exe'; @@ -293,10 +369,10 @@ export class Registry implements IRegistry { return flattenedPythonPaths.then((pythons: string[]) => { return pythons.map((pythonPath, index) => { - let newPythonEnvironment: Registry.IPythonEnvironment = { + let newPythonEnvironment: IPythonEnvironment = { name: `${basename(pythonPath)}-${index}`, path: pythonPath, - type: Registry.IEnvironmentType.PATH, + type: IEnvironmentType.PATH, versions: {}, default: false }; @@ -306,7 +382,7 @@ export class Registry implements IRegistry { }); } - private _loadCondaEnvironments(): Promise { + private _loadCondaEnvironments(): Promise { let pathCondas = this._getPATHCondas(); let commonCondas = this._filterNonexistantPaths(Registry.COMMON_CONDA_LOCATIONS); @@ -316,7 +392,7 @@ export class Registry implements IRegistry { } return this._loadRootCondaEnvironments(allCondas).then(rootEnvs => { - let subEnvs = rootEnvs.reduce[]>((accum, currentRootEnv, index, self) => { + let subEnvs = rootEnvs.reduce[]>((accum, currentRootEnv, index, self) => { let rootSubEnvsFolderPath: string; if (process.platform === 'win32') { rootSubEnvsFolderPath = normalize(join(currentRootEnv.path, '..')); @@ -330,14 +406,14 @@ export class Registry implements IRegistry { }, []); return Promise.all(subEnvs).then(subEnvs => { - let flattenSubEnvs = Array.prototype.concat.apply([], subEnvs) as Registry.IPythonEnvironment[]; + let flattenSubEnvs = Array.prototype.concat.apply([], subEnvs) as IPythonEnvironment[]; return rootEnvs.concat(flattenSubEnvs); }); }); } - private _getSubEnvironmentsFromRoot(rootPath: string): Promise { + private _getSubEnvironmentsFromRoot(rootPath: string): Promise { let subEnvironmentsFolder = join(rootPath, 'envs'); let rootName = basename(rootPath); @@ -357,9 +433,9 @@ export class Registry implements IRegistry { return { name: `${rootName}-${basename(normalize(join(subEnvPath, '..', '..')))}`, path: subEnvPath, - type: Registry.IEnvironmentType.CondaEnv, + type: IEnvironmentType.CondaEnv, versions: {} - } as Registry.IPythonEnvironment; + } as IPythonEnvironment; })); }); } @@ -367,7 +443,7 @@ export class Registry implements IRegistry { }); } - private _loadRootCondaEnvironments(condaRoots: Promise[]): Promise { + private _loadRootCondaEnvironments(condaRoots: Promise[]): Promise { return Promise.all(condaRoots).then(allCondas => { let flattenedCondaRoots: string[] = Array.prototype.concat.apply([], allCondas); let uniqueCondaRoots = this._getUniqueObjects(flattenedCondaRoots); @@ -380,10 +456,10 @@ export class Registry implements IRegistry { path = join(condaRootPath, 'bin', 'python'); } - let newRootEnvironment: Registry.IPythonEnvironment = { + let newRootEnvironment: IPythonEnvironment = { name: basename(condaRootPath), path: path, - type: Registry.IEnvironmentType.CondaRoot, + type: IEnvironmentType.CondaRoot, versions: {}, default: false }; @@ -413,7 +489,7 @@ export class Registry implements IRegistry { return this._getAllMatchingValuesFromSubRegistry(WinRegistry.HKCU, '\\SOFTWARE\\Python\\ContinuumAnalytics', 'InstallPath', valuePredicate); } - private _loadWindowsRegistryEnvironments(requirements: Registry.IRequirement[]): Promise { + private _loadWindowsRegistryEnvironments(requirements: Registry.IRequirement[]): Promise { let valuePredicate = (value: any) => { return value.name === '(Default)'; }; @@ -427,9 +503,9 @@ export class Registry implements IRegistry { return { name: `WinReg-${basename(normalize(join(path, '..')))}`, path: finalPath, - type: Registry.IEnvironmentType.WindowsReg, + type: IEnvironmentType.WindowsReg, versions: {} - } as Registry.IPythonEnvironment; + } as IPythonEnvironment; })); }); } @@ -480,11 +556,11 @@ export class Registry implements IRegistry { }); } - private _filterPythonEnvironmentsByRequirements(environments: Registry.IPythonEnvironment[], requirements: Registry.IRequirement[]): Registry.IPythonEnvironment[] { + private _filterPythonEnvironmentsByRequirements(environments: IPythonEnvironment[], requirements: Registry.IRequirement[]): IPythonEnvironment[] { return environments.filter((env, index, envSelf) => { return requirements.every((req, index, reqSelf) => { try { - return satisfies(env.versions[req.name], req.versionRange); + return semver.satisfies(env.versions[req.name], req.versionRange); } catch (e) { return false; } @@ -492,7 +568,7 @@ export class Registry implements IRegistry { }); } - private _updatePythonEnvironmentsWithRequirementVersions(environments: Registry.IPythonEnvironment[], requirements: Registry.IRequirement[]): Promise { + private _updatePythonEnvironmentsWithRequirementVersions(environments: IPythonEnvironment[], requirements: Registry.IRequirement[]): Promise { let updatedEnvironments = environments.map(env => { // Get versions for each requirement let versions: Promise<[string, string]>[] = requirements.map(req => { @@ -515,7 +591,7 @@ export class Registry implements IRegistry { versions.push(pythonVersion); return Promise.all(versions).then(versions => { - env.versions = versions.reduce((accum: Registry.IVersionContainer, current: [string, string], index, self) => { + env.versions = versions.reduce((accum: IVersionContainer, current: [string, string], index, self) => { accum[current[0]] = current[1]; return accum; }, {}); @@ -530,19 +606,11 @@ export class Registry implements IRegistry { private _extractVersionFromExecOutput(output: Promise): Promise { return new Promise((resolve, reject) => { return output.then(output => { - let matches: string[] = []; - let currentMatch: RegExpExecArray; - do { - currentMatch = Registry.SEMVER_REGEX.exec(output); - if (currentMatch) { - matches.push(currentMatch[0]); - } - } while (currentMatch); - - if (matches.length === 0) { + const version = this._extractVersionFromExecOutputSync(output); + if (version === '') { reject(new Error(`Could not find SemVer match in output!`)); } else { - resolve(matches[0]); + resolve(version); } }).catch(reason => { reject(new Error(`Command output failed!`)); @@ -550,6 +618,19 @@ export class Registry implements IRegistry { }); } + private _extractVersionFromExecOutputSync(output: string): string { + let matches: string[] = []; + let currentMatch: RegExpExecArray; + do { + currentMatch = Registry.SEMVER_REGEX.exec(output); + if (currentMatch) { + matches.push(currentMatch[0]); + } + } while (currentMatch); + + return matches.length === 0 ? '' : matches[0]; + } + private _convertExecutableOutputFromJson(output: Promise): Promise { return new Promise((resolve, reject) => { output.then(output => { @@ -613,6 +694,13 @@ export class Registry implements IRegistry { }); } + private _runPythonModuleCommandSync(pythonPath: string, moduleName: string, commands: string[]): string { + const totalCommands = ['-m', moduleName].concat(commands); + const runOptions = { env: { 'PATH': this.getAdditionalPathIncludesForPythonPath(pythonPath)}}; + + return this._runCommandSync(pythonPath, totalCommands, runOptions); + } + private _runCommand(executablePath: string, commands: string[]): Promise { return new Promise((resolve, reject) => { let executableRun = execFile(executablePath, commands); @@ -662,7 +750,15 @@ export class Registry implements IRegistry { }); } - private _sortEnvironments(environments: Registry.IPythonEnvironment[], requirements: Registry.IRequirement[]) { + private _runCommandSync(executablePath: string, commands: string[], options?: ExecFileOptions): string { + try { + return execFileSync(executablePath, commands, options).toString(); + } catch(error) { + return 'EXEC:ERROR'; + } + } + + private _sortEnvironments(environments: IPythonEnvironment[], requirements: Registry.IRequirement[]) { environments.sort((a, b) => { let typeCompareResult = this._compareEnvType(a.type, b.type); if (typeCompareResult !== 0) { @@ -678,7 +774,7 @@ export class Registry implements IRegistry { }); } - private _compareVersions(a: Registry.IVersionContainer, b: Registry.IVersionContainer, requirements: Registry.IRequirement[]): number { + private _compareVersions(a: IVersionContainer, b: IVersionContainer, requirements: Registry.IRequirement[]): number { let versionPairs = requirements.map(req => { return [a[req.name], b[req.name]]; }); @@ -695,19 +791,19 @@ export class Registry implements IRegistry { return 0; } - private _compareEnvType(a: Registry.IEnvironmentType, b: Registry.IEnvironmentType): number { + private _compareEnvType(a: IEnvironmentType, b: IEnvironmentType): number { return this._getEnvTypeValue(a) - this._getEnvTypeValue(b); } - private _getEnvTypeValue(a: Registry.IEnvironmentType): number { + private _getEnvTypeValue(a: IEnvironmentType): number { switch (a) { - case Registry.IEnvironmentType.PATH: + case IEnvironmentType.PATH: return 0; - case Registry.IEnvironmentType.CondaRoot: + case IEnvironmentType.CondaRoot: return 1; - case Registry.IEnvironmentType.WindowsReg: + case IEnvironmentType.WindowsReg: return 2; - case Registry.IEnvironmentType.CondaEnv: + case IEnvironmentType.CondaEnv: return 3; default: return 100; @@ -747,7 +843,7 @@ export class Registry implements IRegistry { }); } - private _setDefaultEnvironment(newEnv: Registry.IPythonEnvironment) { + private _setDefaultEnvironment(newEnv: IPythonEnvironment) { if (this._default) { this._default.default = false; } @@ -757,9 +853,9 @@ export class Registry implements IRegistry { } } - private _environments: Registry.IPythonEnvironment[] = []; + private _environments: IPythonEnvironment[] = []; - private _default: Registry.IPythonEnvironment; + private _default: IPythonEnvironment; private _registryBuilt: Promise; @@ -769,64 +865,6 @@ export class Registry implements IRegistry { export namespace Registry { - /** - * The respresentation of the python environment - */ - export interface IPythonEnvironment { - /** - * The file path of the python executable - */ - path: string; - /** - * Arbitrary name used for display, not garuanteed to be unique - */ - name: string; - /** - * The type of the environment - */ - type: IEnvironmentType; - /** - * For each requirement specified by the registry, there will be one corresponding version - * There will also be a version that accompanies the python executable - */ - versions: IVersionContainer; - - /** - * True if this is the current default environment. - */ - default: boolean; - } - - /** - * Dictionary that contains all requirement names mapped to version number strings - */ - export interface IVersionContainer { - [name: string]: string; - } - - /** - * Different types of environments - */ - export enum IEnvironmentType { - /** - * This is the catch-all type value, any environments that are randomly found or - * entered will have this type - */ - PATH = 'PATH', - /** - * This environment type is reserved for the type level of conda installations - */ - CondaRoot = 'conda-root', - /** - * This environment type is reserved for sub environments of a conda installation - */ - CondaEnv = 'conda-env', - /** - * This environment type is for environments that were derived from the WindowsRegistry - */ - WindowsReg = 'windows-reg', - } - /** * This type represents module/executable package requirements for the python executables * in the registry. Each requirement should correspond to a python module that is also @@ -848,7 +886,7 @@ namespace Registry { /** * The Range of acceptable version produced by the previous commands field */ - versionRange: Range; + versionRange: semver.Range; } export const COMMON_CONDA_LOCATIONS = [ diff --git a/src/main/server.ts b/src/main/server.ts index 616b75b6..1ae5bca3 100644 --- a/src/main/server.ts +++ b/src/main/server.ts @@ -7,7 +7,7 @@ import { } from './main'; import { - IRegistry, Registry + IRegistry } from './registry'; import { @@ -28,14 +28,15 @@ import { import log from 'electron-log'; -import * as path from 'path'; import * as fs from 'fs'; +import { IPythonEnvironment } from './tokens'; export class JupyterServer { - constructor(options: JupyterServer.IOptions) { + constructor(options: JupyterServer.IOptions, registry: IRegistry) { this._info.environment = options.environment; + this._registry = registry; } get info(): JupyterServer.IInfo { @@ -57,30 +58,17 @@ class JupyterServer { let urlRegExp = /http:\/\/localhost:\d+\/\S*/g; let tokenRegExp = /token=\w+/g; let baseRegExp = /http:\/\/localhost:\d+\//g; - const platform = process.platform; const home = process.env.JLAB_DESKTOP_HOME || app.getPath('home'); - let envPath = path.join(path.dirname(app.getAppPath()), 'jlab_server'); - if (platform !== 'win32') { - envPath = path.join(envPath, 'bin'); - } - const pythonPath = path.join(envPath, `python${platform === 'win32' ? '.exe' : ''}`); + const pythonPath = this._info.environment.path; if (!fs.existsSync(pythonPath)) { dialog.showMessageBox({message: `Environment not found at: ${pythonPath}`, type: 'error' }); - } - - this._info.environment.path = pythonPath; - - let PATH_ENV = ''; - if (platform === 'win32') { - PATH_ENV = `${envPath};${envPath}\\Library\\mingw-w64\\bin;${envPath}\\Library\\usr\\bin;${envPath}\\Library\\bin;${envPath}\\Scripts;${envPath}\\bin;${process.env['PATH']}`; - } else { - PATH_ENV = `${envPath}:${process.env['PATH']}`; + reject(); } this._nbServer = execFile(this._info.environment.path, ['-m', 'jupyterlab', '--no-browser', '--JupyterApp.config_file_name', '', '--ServerApp.password', '', '--ServerApp.disable_check_xsrf', 'True', '--ServerApp.allow_origin', '*'], { cwd: home, env: { - PATH: PATH_ENV + PATH: this._registry.getAdditionalPathIncludesForPythonPath(this._info.environment.path) } }); @@ -169,6 +157,8 @@ class JupyterServer { private _startServer: Promise = null; private _info: JupyterServer.IInfo = { url: null, token: null, environment: null }; + + private _registry: IRegistry; } export @@ -176,14 +166,14 @@ namespace JupyterServer { export interface IOptions { - environment: Registry.IPythonEnvironment; + environment: IPythonEnvironment; } export interface IInfo { url: string; token: string; - environment: Registry.IPythonEnvironment; + environment: IPythonEnvironment; } } @@ -280,7 +270,7 @@ class JupyterServerFactory implements IServerFactory, IClosingService { asyncRemoteMain.registerRemoteMethod(IServerFactory.requestServerStartPath, (data: any, caller) => { return this._registry.getUserJupyterPath() - .then((environment: Registry.IPythonEnvironment) => { + .then((environment: IPythonEnvironment) => { asyncRemoteMain.emitRemoteEvent(IServerFactory.pathSelectedEvent, undefined, caller); return this.createServer({ environment }); }) @@ -312,7 +302,7 @@ class JupyterServerFactory implements IServerFactory, IClosingService { */ createFreeServer(opts: JupyterServer.IOptions): JupyterServerFactory.IFactoryItem { let item: JupyterServerFactory.IFactoryItem; - let env: Promise; + let env: Promise; if (!opts.environment) { env = this._registry.getDefaultEnvironment(); @@ -343,7 +333,7 @@ class JupyterServerFactory implements IServerFactory, IClosingService { */ createServer(opts: JupyterServer.IOptions, forceNewServer?: boolean): Promise { let server: JupyterServerFactory.IFactoryItem; - let env: Promise; + let env: Promise; if (!opts.environment) { env = this._registry.getDefaultEnvironment(); @@ -434,7 +424,7 @@ class JupyterServerFactory implements IServerFactory, IClosingService { private _createServer(opts: JupyterServer.IOptions): JupyterServerFactory.IFactoryItem { let item: JupyterServerFactory.IFactoryItem = { factoryId: this._nextId++, - server: new JupyterServer(opts), + server: new JupyterServer(opts, this._registry), closing: null, used: false }; diff --git a/src/main/sessions.ts b/src/main/sessions.ts index 298f5ccd..4c40300d 100644 --- a/src/main/sessions.ts +++ b/src/main/sessions.ts @@ -29,6 +29,8 @@ import { IService } from './main'; +import { IRegistry } from './registry'; + import { EventEmitter } from 'events'; @@ -85,9 +87,10 @@ class JupyterLabSessions extends EventEmitter implements ISessions, IStatefulSer readonly id = 'JupyterLabSessions'; - constructor(app: IApplication, serverFactory: IServerFactory) { + constructor(app: IApplication, serverFactory: IServerFactory, registry: IRegistry) { super(); this._serverFactory = serverFactory; + this._registry = registry; // check if UI state was set by user for (let arg of process.argv) { @@ -108,8 +111,10 @@ class JupyterLabSessions extends EventEmitter implements ISessions, IStatefulSer app.registerStatefulService(this) .then((state: JupyterLabSession.IState) => { this._lastWindowState = state; - this.createSession() - .then( () => {this._startingSession = null; }); + if (this._registry.getCurrentPythonEnvironment()) { + this.createSession() + .then( () => {this._startingSession = null; }); + } }) .catch(() => { this.createSession() @@ -324,14 +329,18 @@ class JupyterLabSessions extends EventEmitter implements ISessions, IStatefulSer private _isFile(path: string): Promise { return new Promise( (resolve, reject) => { fs.lstat(path, (err: any, stats: fs.Stats) => { - if (stats === null || stats === undefined) { + try { + if (stats === null || stats === undefined) { + reject(); + } else if (err) { + reject(); + } else if (stats.isFile()) { + resolve(); + } reject(); - } else if (err) { + } catch(error: any) { reject(); - } else if (stats.isFile()) { - resolve(); } - reject(); }); }); } @@ -346,6 +355,8 @@ class JupyterLabSessions extends EventEmitter implements ISessions, IStatefulSer private _serverFactory: IServerFactory; + private _registry: IRegistry; + private _uiState: JupyterLabSession.UIState; } @@ -567,10 +578,10 @@ if (process && process.type !== 'renderer') { } let service: IService = { - requirements: ['IApplication', 'IServerFactory', 'IAsyncRemoteMain'], + requirements: ['IApplication', 'IServerFactory', 'IRegistry'], provides: 'ISessions', - activate: (app: IApplication, serverFactory: IServerFactory): ISessions => { - sessions = new JupyterLabSessions(app, serverFactory); + activate: (app: IApplication, serverFactory: IServerFactory, registry: IRegistry): ISessions => { + sessions = new JupyterLabSessions(app, serverFactory, registry); return sessions; }, autostart: true diff --git a/src/main/tokens.ts b/src/main/tokens.ts new file mode 100644 index 00000000..800ddd00 --- /dev/null +++ b/src/main/tokens.ts @@ -0,0 +1,67 @@ +/** + * Dictionary that contains all requirement names mapped to version number strings + */ + export interface IVersionContainer { + [name: string]: string; +} + +/** + * Different types of environments + */ + export enum IEnvironmentType { + /** + * This is the catch-all type value, any environments that are randomly found or + * entered will have this type + */ + PATH = 'PATH', + /** + * This environment type is reserved for the type level of conda installations + */ + CondaRoot = 'conda-root', + /** + * This environment type is reserved for sub environments of a conda installation + */ + CondaEnv = 'conda-env', + /** + * This environment type is for environments that were derived from the WindowsRegistry + */ + WindowsReg = 'windows-reg', + + VirtualEnv = 'venv' +} + +export const EnvironmentTypeName = { + [IEnvironmentType.PATH]: 'system', + [IEnvironmentType.CondaRoot]: 'conda', + [IEnvironmentType.CondaEnv]: 'conda', + [IEnvironmentType.WindowsReg]: 'win', + [IEnvironmentType.VirtualEnv]: 'venv', +}; + +/** + * The respresentation of the python environment + */ + export interface IPythonEnvironment { + /** + * The file path of the python executable + */ + path: string; + /** + * Arbitrary name used for display, not garuanteed to be unique + */ + name: string; + /** + * The type of the environment + */ + type: IEnvironmentType; + /** + * For each requirement specified by the registry, there will be one corresponding version + * There will also be a version that accompanies the python executable + */ + versions: IVersionContainer; + + /** + * True if this is the current default environment. + */ + default: boolean; +} diff --git a/yarn.lock b/yarn.lock index 4af4bf7b..3015d96f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1,5 +1,7 @@ # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. # yarn lockfile v1 +# yarn v0.25.2 +# node v14.8.0 "7zip-bin@~5.1.1": @@ -7,36 +9,36 @@ resolved "https://registry.yarnpkg.com/7zip-bin/-/7zip-bin-5.1.1.tgz#9274ec7460652f9c632c59addf24efb1684ef876" "@babel/code-frame@^7.0.0": - version "7.15.8" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.15.8.tgz#45990c47adadb00c03677baa89221f7cc23d2503" + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.16.0.tgz#0dfc80309beec8411e65e706461c408b0bb9b431" dependencies: - "@babel/highlight" "^7.14.5" + "@babel/highlight" "^7.16.0" -"@babel/helper-validator-identifier@^7.14.5": +"@babel/helper-validator-identifier@^7.15.7": version "7.15.7" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz#220df993bfe904a4a6b02ab4f3385a5ebf6e2389" -"@babel/highlight@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.14.5.tgz#6861a52f03966405001f6aa534a01a24d99e8cd9" +"@babel/highlight@^7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.16.0.tgz#6ceb32b2ca4b8f5f361fb7fd821e3fddf4a1725a" dependencies: - "@babel/helper-validator-identifier" "^7.14.5" + "@babel/helper-validator-identifier" "^7.15.7" chalk "^2.0.0" js-tokens "^4.0.0" "@babel/runtime@^7.1.2": - version "7.15.4" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.15.4.tgz#fd17d16bfdf878e6dd02d19753a39fa8a8d9c84a" + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.16.0.tgz#e27b977f2e2088ba24748bf99b5e1dece64e4f0b" dependencies: regenerator-runtime "^0.13.4" "@blueprintjs/colors@^4.0.0-alpha.1": - version "4.0.0-beta.0" - resolved "https://registry.yarnpkg.com/@blueprintjs/colors/-/colors-4.0.0-beta.0.tgz#324a6e0cb0fa4223aaaca1746e0ce3b02ed0cbb4" + version "4.0.0-beta.2" + resolved "https://registry.yarnpkg.com/@blueprintjs/colors/-/colors-4.0.0-beta.2.tgz#581c8b77c8c7d98a0da3fcecc818105f51bf2e5b" -"@blueprintjs/core@^3.36.0", "@blueprintjs/core@^3.51.2": - version "3.51.2" - resolved "https://registry.yarnpkg.com/@blueprintjs/core/-/core-3.51.2.tgz#9ace39c831bbb57b827d2f6b0de80bf1b0e2ad8c" +"@blueprintjs/core@^3.36.0", "@blueprintjs/core@^3.51.3": + version "3.51.3" + resolved "https://registry.yarnpkg.com/@blueprintjs/core/-/core-3.51.3.tgz#d74dd9ac299c0d8f635f04a81c8bda7ef534f069" dependencies: "@blueprintjs/colors" "^4.0.0-alpha.1" "@blueprintjs/icons" "^3.31.0" @@ -59,10 +61,10 @@ tslib "~1.13.0" "@blueprintjs/select@^3.15.0": - version "3.18.9" - resolved "https://registry.yarnpkg.com/@blueprintjs/select/-/select-3.18.9.tgz#cfe6ee552c3ced06b0b55e49b7a90bd0a1f65ef5" + version "3.18.10" + resolved "https://registry.yarnpkg.com/@blueprintjs/select/-/select-3.18.10.tgz#6f71a070da17e478701a0417f138e4b18e051b1f" dependencies: - "@blueprintjs/core" "^3.51.2" + "@blueprintjs/core" "^3.51.3" classnames "^2.2" tslib "~1.13.0" @@ -78,8 +80,8 @@ resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.5.tgz#9283c9ce5b289a3c4f61c12757469e59377f81f3" "@electron/get@^1.0.1": - version "1.13.0" - resolved "https://registry.yarnpkg.com/@electron/get/-/get-1.13.0.tgz#95c6bcaff4f9a505ea46792424f451efea89228c" + version "1.13.1" + resolved "https://registry.yarnpkg.com/@electron/get/-/get-1.13.1.tgz#42a0aa62fd1189638bd966e23effaebb16108368" dependencies: debug "^4.1.1" env-paths "^2.2.0" @@ -89,7 +91,7 @@ semver "^6.2.0" sumchecker "^3.0.1" optionalDependencies: - global-agent "^2.0.2" + global-agent "^3.0.0" global-tunnel-ng "^2.7.1" "@electron/universal@1.0.5": @@ -1748,39 +1750,39 @@ vega-embed "^6.2.1" vega-lite "^5.1.0" -"@lumino/algorithm@^1.1.0", "@lumino/algorithm@^1.3.3", "@lumino/algorithm@^1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@lumino/algorithm/-/algorithm-1.9.0.tgz#bae4c9a90a0d6d6d04537601501370d4b496a044" +"@lumino/algorithm@^1.1.0", "@lumino/algorithm@^1.3.3", "@lumino/algorithm@^1.9.1": + version "1.9.1" + resolved "https://registry.yarnpkg.com/@lumino/algorithm/-/algorithm-1.9.1.tgz#a870598e031f5ee85e20e77ce7bfffbb0dffd7f5" "@lumino/application@^1.16.0": - version "1.26.0" - resolved "https://registry.yarnpkg.com/@lumino/application/-/application-1.26.0.tgz#a63bcc10c9a008a1f918595d0cc9ec10c31ea746" + version "1.27.0" + resolved "https://registry.yarnpkg.com/@lumino/application/-/application-1.27.0.tgz#4d17725ed209e04e840102ff67c892d87ffac2a2" dependencies: - "@lumino/commands" "^1.18.0" - "@lumino/coreutils" "^1.11.0" - "@lumino/widgets" "^1.29.0" + "@lumino/commands" "^1.19.0" + "@lumino/coreutils" "^1.11.1" + "@lumino/widgets" "^1.30.0" -"@lumino/collections@^1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@lumino/collections/-/collections-1.9.0.tgz#d837e17d78f2db2e56f3e931fc71fdda9ed75d05" +"@lumino/collections@^1.9.1": + version "1.9.1" + resolved "https://registry.yarnpkg.com/@lumino/collections/-/collections-1.9.1.tgz#268f1ec6850d5e131cfc8db232c7e1e106144aa0" dependencies: - "@lumino/algorithm" "^1.9.0" + "@lumino/algorithm" "^1.9.1" -"@lumino/commands@^1.12.0", "@lumino/commands@^1.18.0": - version "1.18.0" - resolved "https://registry.yarnpkg.com/@lumino/commands/-/commands-1.18.0.tgz#fc224210664fdbf6ce68f6f6e6b8c74b8de4a2bc" +"@lumino/commands@^1.12.0", "@lumino/commands@^1.19.0": + version "1.19.0" + resolved "https://registry.yarnpkg.com/@lumino/commands/-/commands-1.19.0.tgz#9349c34b900653ac9d654e47831e7204e0c3476f" dependencies: - "@lumino/algorithm" "^1.9.0" - "@lumino/coreutils" "^1.11.0" - "@lumino/disposable" "^1.10.0" - "@lumino/domutils" "^1.8.0" - "@lumino/keyboard" "^1.8.0" - "@lumino/signaling" "^1.10.0" - "@lumino/virtualdom" "^1.14.0" + "@lumino/algorithm" "^1.9.1" + "@lumino/coreutils" "^1.11.1" + "@lumino/disposable" "^1.10.1" + "@lumino/domutils" "^1.8.1" + "@lumino/keyboard" "^1.8.1" + "@lumino/signaling" "^1.10.1" + "@lumino/virtualdom" "^1.14.1" -"@lumino/coreutils@^1.11.0", "@lumino/coreutils@^1.2.0", "@lumino/coreutils@^1.3.0", "@lumino/coreutils@^1.5.3": - version "1.11.0" - resolved "https://registry.yarnpkg.com/@lumino/coreutils/-/coreutils-1.11.0.tgz#ad704b10ffbbddc4009cf4cb64481848bee19eff" +"@lumino/coreutils@^1.11.1", "@lumino/coreutils@^1.2.0", "@lumino/coreutils@^1.3.0", "@lumino/coreutils@^1.5.3": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@lumino/coreutils/-/coreutils-1.11.1.tgz#6d89c6325d7adb5f2179dfe3660f0aec8f3c4546" "@lumino/datagrid@^0.20.0": version "0.20.1" @@ -1796,74 +1798,74 @@ "@lumino/signaling" "^1.4.3" "@lumino/widgets" "^1.19.1" -"@lumino/disposable@^1.1.1", "@lumino/disposable@^1.10.0", "@lumino/disposable@^1.4.3": - version "1.10.0" - resolved "https://registry.yarnpkg.com/@lumino/disposable/-/disposable-1.10.0.tgz#ffd404233ba09ec73c84b098605bc06e0628148d" +"@lumino/disposable@^1.1.1", "@lumino/disposable@^1.10.1", "@lumino/disposable@^1.4.3": + version "1.10.1" + resolved "https://registry.yarnpkg.com/@lumino/disposable/-/disposable-1.10.1.tgz#58fddc619cf89335802d168564b76ff5315d5a84" dependencies: - "@lumino/algorithm" "^1.9.0" - "@lumino/signaling" "^1.10.0" + "@lumino/algorithm" "^1.9.1" + "@lumino/signaling" "^1.10.1" -"@lumino/domutils@^1.1.0", "@lumino/domutils@^1.2.3", "@lumino/domutils@^1.8.0": - version "1.8.0" - resolved "https://registry.yarnpkg.com/@lumino/domutils/-/domutils-1.8.0.tgz#3ebd2d581603bc917a69c4e3c60d31a6b5988da5" +"@lumino/domutils@^1.1.0", "@lumino/domutils@^1.2.3", "@lumino/domutils@^1.8.1": + version "1.8.1" + resolved "https://registry.yarnpkg.com/@lumino/domutils/-/domutils-1.8.1.tgz#cf118e4eba90c3bf1e3edf7f19cce8846ec7875c" -"@lumino/dragdrop@^1.13.0", "@lumino/dragdrop@^1.7.1": - version "1.13.0" - resolved "https://registry.yarnpkg.com/@lumino/dragdrop/-/dragdrop-1.13.0.tgz#44297039a0397c417f9240f6dc3c8391f47574f9" +"@lumino/dragdrop@^1.13.1", "@lumino/dragdrop@^1.7.1": + version "1.13.1" + resolved "https://registry.yarnpkg.com/@lumino/dragdrop/-/dragdrop-1.13.1.tgz#a8f8ae4262dcbba4ef85900f6081c90bd47df2b5" dependencies: - "@lumino/coreutils" "^1.11.0" - "@lumino/disposable" "^1.10.0" + "@lumino/coreutils" "^1.11.1" + "@lumino/disposable" "^1.10.1" -"@lumino/keyboard@^1.2.3", "@lumino/keyboard@^1.8.0": - version "1.8.0" - resolved "https://registry.yarnpkg.com/@lumino/keyboard/-/keyboard-1.8.0.tgz#fc14a020ec8f3b54f84cfb6be30fa141622ff456" +"@lumino/keyboard@^1.2.3", "@lumino/keyboard@^1.8.1": + version "1.8.1" + resolved "https://registry.yarnpkg.com/@lumino/keyboard/-/keyboard-1.8.1.tgz#e7850e2fb973fbb4c6e737ca8d9307f2dc3eb74b" -"@lumino/messaging@^1.10.0", "@lumino/messaging@^1.2.1", "@lumino/messaging@^1.4.3": - version "1.10.0" - resolved "https://registry.yarnpkg.com/@lumino/messaging/-/messaging-1.10.0.tgz#e08d81b0115e63d5bde5d6b367dcbe00bd36d9f3" +"@lumino/messaging@^1.10.1", "@lumino/messaging@^1.2.1", "@lumino/messaging@^1.4.3": + version "1.10.1" + resolved "https://registry.yarnpkg.com/@lumino/messaging/-/messaging-1.10.1.tgz#b29575cca46e2f23b84626b793ec8e2be46a53ba" dependencies: - "@lumino/algorithm" "^1.9.0" - "@lumino/collections" "^1.9.0" + "@lumino/algorithm" "^1.9.1" + "@lumino/collections" "^1.9.1" "@lumino/polling@^1.3.3": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@lumino/polling/-/polling-1.9.0.tgz#0928964eca474e3c8add1ef2bc4d339fdd0c2e05" + version "1.9.1" + resolved "https://registry.yarnpkg.com/@lumino/polling/-/polling-1.9.1.tgz#38d5f31b16ecdf95af0f48b9b67f0444b3de2df8" dependencies: - "@lumino/coreutils" "^1.11.0" - "@lumino/disposable" "^1.10.0" - "@lumino/signaling" "^1.10.0" + "@lumino/coreutils" "^1.11.1" + "@lumino/disposable" "^1.10.1" + "@lumino/signaling" "^1.10.1" -"@lumino/properties@^1.1.0", "@lumino/properties@^1.2.3", "@lumino/properties@^1.8.0": - version "1.8.0" - resolved "https://registry.yarnpkg.com/@lumino/properties/-/properties-1.8.0.tgz#ef0ffb85bc0958b1aceab6728dc2d22580df18d0" +"@lumino/properties@^1.1.0", "@lumino/properties@^1.2.3", "@lumino/properties@^1.8.1": + version "1.8.1" + resolved "https://registry.yarnpkg.com/@lumino/properties/-/properties-1.8.1.tgz#47eb8516e92c987dcb2c404db83a258159efec3d" -"@lumino/signaling@^1.10.0", "@lumino/signaling@^1.2.0", "@lumino/signaling@^1.4.3": - version "1.10.0" - resolved "https://registry.yarnpkg.com/@lumino/signaling/-/signaling-1.10.0.tgz#92a4ac37c315eca418b2c03f770a605590332eb3" +"@lumino/signaling@^1.10.1", "@lumino/signaling@^1.2.0", "@lumino/signaling@^1.4.3": + version "1.10.1" + resolved "https://registry.yarnpkg.com/@lumino/signaling/-/signaling-1.10.1.tgz#c8a1cb5b661b6744ea817c99c758fdc897847c26" dependencies: - "@lumino/algorithm" "^1.9.0" + "@lumino/algorithm" "^1.9.1" -"@lumino/virtualdom@^1.14.0", "@lumino/virtualdom@^1.8.0": - version "1.14.0" - resolved "https://registry.yarnpkg.com/@lumino/virtualdom/-/virtualdom-1.14.0.tgz#236112af59ebce24932cc4390b10a3b11c7aa930" +"@lumino/virtualdom@^1.14.1", "@lumino/virtualdom@^1.8.0": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@lumino/virtualdom/-/virtualdom-1.14.1.tgz#2551b146cbe87c48d23754f370c1331a60c9fe62" dependencies: - "@lumino/algorithm" "^1.9.0" + "@lumino/algorithm" "^1.9.1" -"@lumino/widgets@^1.19.0", "@lumino/widgets@^1.19.1", "@lumino/widgets@^1.29.0", "@lumino/widgets@^1.3.0": - version "1.29.0" - resolved "https://registry.yarnpkg.com/@lumino/widgets/-/widgets-1.29.0.tgz#efc4b6989b2454162f496fee40ec8b08a4cf2ea8" +"@lumino/widgets@^1.19.0", "@lumino/widgets@^1.19.1", "@lumino/widgets@^1.3.0", "@lumino/widgets@^1.30.0": + version "1.30.0" + resolved "https://registry.yarnpkg.com/@lumino/widgets/-/widgets-1.30.0.tgz#fdf96ffab9a018523b932afd5727317c3a360b4f" dependencies: - "@lumino/algorithm" "^1.9.0" - "@lumino/commands" "^1.18.0" - "@lumino/coreutils" "^1.11.0" - "@lumino/disposable" "^1.10.0" - "@lumino/domutils" "^1.8.0" - "@lumino/dragdrop" "^1.13.0" - "@lumino/keyboard" "^1.8.0" - "@lumino/messaging" "^1.10.0" - "@lumino/properties" "^1.8.0" - "@lumino/signaling" "^1.10.0" - "@lumino/virtualdom" "^1.14.0" + "@lumino/algorithm" "^1.9.1" + "@lumino/commands" "^1.19.0" + "@lumino/coreutils" "^1.11.1" + "@lumino/disposable" "^1.10.1" + "@lumino/domutils" "^1.8.1" + "@lumino/dragdrop" "^1.13.1" + "@lumino/keyboard" "^1.8.1" + "@lumino/messaging" "^1.10.1" + "@lumino/properties" "^1.8.1" + "@lumino/signaling" "^1.10.1" + "@lumino/virtualdom" "^1.14.1" "@malept/cross-spawn-promise@^1.1.0": version "1.1.1" @@ -1921,6 +1923,10 @@ version "2.0.2" resolved "https://registry.yarnpkg.com/@types/dom4/-/dom4-2.0.2.tgz#6495303f049689ce936ed328a3e5ede9c51408ee" +"@types/ejs@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@types/ejs/-/ejs-3.1.0.tgz#ab8109208106b5e764e5a6c92b2ba1c625b73020" + "@types/eslint-scope@^3.7.0": version "3.7.1" resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.1.tgz#8dc390a7b4f9dd9f1284629efce982e41612116e" @@ -1966,13 +1972,7 @@ version "7.0.9" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d" -"@types/lodash.curry@^4.1.6": - version "4.1.6" - resolved "https://registry.yarnpkg.com/@types/lodash.curry/-/lodash.curry-4.1.6.tgz#f26c490c80c92d7cbaa2300d542e89781d44b1ff" - dependencies: - "@types/lodash" "*" - -"@types/lodash@*", "@types/lodash@^4.14.134": +"@types/lodash@^4.14.134", "@types/lodash@^4.14.176": version "4.14.176" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.176.tgz#641150fc1cda36fbfa329de603bbb175d7ee20c0" @@ -1996,8 +1996,8 @@ form-data "^3.0.0" "@types/node@*", "@types/node@^14.14.31", "@types/node@^14.6.2": - version "14.17.29" - resolved "https://registry.yarnpkg.com/@types/node/-/node-14.17.29.tgz#44a774fa2858efa4d039dd9051b51160e8295c70" + version "14.17.32" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.17.32.tgz#2ca61c9ef8c77f6fa1733be9e623ceb0d372ad96" "@types/normalize-package-data@^2.4.0": version "2.4.1" @@ -2010,19 +2010,19 @@ "@types/node" "*" xmlbuilder ">=11.0.1" -"@types/prop-types@*", "@types/prop-types@^15.7.3": +"@types/prop-types@*", "@types/prop-types@^15.7.4": version "15.7.4" resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.4.tgz#fcf7205c25dff795ee79af1e30da2c9790808f11" "@types/react-dom@^17.0.1": - version "17.0.10" - resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.10.tgz#d6972ec018d23cf22b99597f1289343d99ea9d9d" + version "17.0.11" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.11.tgz#e1eadc3c5e86bdb5f7684e00274ae228e7bcc466" dependencies: "@types/react" "*" "@types/react@*", "@types/react@^17.0.0", "@types/react@~17.0.2": - version "17.0.32" - resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.32.tgz#89a161286bbe2325d4d516420a27364a324909f4" + version "17.0.34" + resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.34.tgz#797b66d359b692e3f19991b6b07e4b0c706c0102" dependencies: "@types/prop-types" "*" "@types/scheduler" "*" @@ -2059,8 +2059,8 @@ "@types/yargs-parser" "*" "@types/yargs@^17.0.1": - version "17.0.4" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.4.tgz#d7ad5c311aaca3d7daebba169e1ecf35be97ceee" + version "17.0.5" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.5.tgz#1e7e59a88420872875842352b73618f5e77e835f" dependencies: "@types/yargs-parser" "*" @@ -2407,11 +2407,11 @@ brace-expansion@^1.1.7: concat-map "0.0.1" browserslist@^4.14.5: - version "4.17.5" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.17.5.tgz#c827bbe172a4c22b123f5e337533ceebadfdd559" + version "4.17.6" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.17.6.tgz#c76be33e7786b497f66cad25a73756c8b938985d" dependencies: - caniuse-lite "^1.0.30001271" - electron-to-chromium "^1.3.878" + caniuse-lite "^1.0.30001274" + electron-to-chromium "^1.3.886" escalade "^3.1.1" node-releases "^2.0.1" picocolors "^1.0.0" @@ -2512,9 +2512,9 @@ camelcase@^6.2.0: version "6.2.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809" -caniuse-lite@^1.0.30001271: - version "1.0.30001271" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001271.tgz#0dda0c9bcae2cf5407cd34cac304186616cc83e8" +caniuse-lite@^1.0.30001274: + version "1.0.30001278" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001278.tgz#51cafc858df77d966b17f59b5839250b24417fff" chalk@^2.0.0, chalk@^2.4.2: version "2.4.2" @@ -2543,7 +2543,7 @@ ci-info@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" -ci-info@^3.1.1: +ci-info@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.2.0.tgz#2876cb948a498797b5236f0095bc057d0dca38b6" @@ -2619,7 +2619,7 @@ color-string@^1.6.0: color-name "^1.0.0" simple-swizzle "^0.2.2" -color@^3.1.2: +color@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/color/-/color-3.2.1.tgz#3544dc198caf4490c3ecc9a790b54fe9ff45e164" dependencies: @@ -2693,10 +2693,6 @@ configstore@^5.0.1: write-file-atomic "^3.0.0" xdg-basedir "^4.0.0" -core-js@^3.6.5: - version "3.19.0" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.19.0.tgz#9e40098a9bc326c7e81b486abbd5e12b9d275176" - core-util-is@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" @@ -2762,7 +2758,7 @@ csstype@2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.9.tgz#05141d0cd557a56b8891394c1911c40c8a98d098" -csstype@^3.0.2, csstype@~3.0.3: +csstype@^3.0.2, csstype@^3.0.9, csstype@~3.0.3: version "3.0.9" resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.9.tgz#6410af31b26bd0520933d02cbc64fce9ce3fbf0b" @@ -2881,7 +2877,7 @@ debug@^2.6.8, debug@^2.6.9: dependencies: ms "2.0.0" -debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2: +debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2: version "4.3.2" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" dependencies: @@ -3080,30 +3076,6 @@ electron-builder@^22.11.11: update-notifier "^5.1.0" yargs "^17.0.1" -electron-debug@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/electron-debug/-/electron-debug-3.2.0.tgz#46a15b555c3b11872218c65ea01d058aa0814920" - dependencies: - electron-is-dev "^1.1.0" - electron-localshortcut "^3.1.0" - -electron-is-accelerator@^0.1.0: - version "0.1.2" - resolved "https://registry.yarnpkg.com/electron-is-accelerator/-/electron-is-accelerator-0.1.2.tgz#509e510c26a56b55e17f863a4b04e111846ab27b" - -electron-is-dev@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/electron-is-dev/-/electron-is-dev-1.2.0.tgz#2e5cea0a1b3ccf1c86f577cee77363ef55deb05e" - -electron-localshortcut@^3.1.0: - version "3.2.1" - resolved "https://registry.yarnpkg.com/electron-localshortcut/-/electron-localshortcut-3.2.1.tgz#cfc83a3eff5e28faf98ddcc87f80a2ce4f623cd3" - dependencies: - debug "^4.0.1" - electron-is-accelerator "^0.1.0" - keyboardevent-from-electron-accelerator "^2.0.0" - keyboardevents-areequal "^0.2.1" - electron-log@^4.3.2: version "4.4.1" resolved "https://registry.yarnpkg.com/electron-log/-/electron-log-4.4.1.tgz#28ebeb474eccba2ebf194a96c40d6328e5353e4d" @@ -3131,9 +3103,9 @@ electron-publish@22.14.5: lazy-val "^1.0.5" mime "^2.5.2" -electron-to-chromium@^1.3.878: - version "1.3.878" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.878.tgz#baa9fb5c24b9b580f08fb245cbb52a22f8fc8fa8" +electron-to-chromium@^1.3.886: + version "1.3.890" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.890.tgz#e7143b659f73dc4d0512d1ae4baeb0fb9e7bc835" electron@^12.0.0: version "12.2.2" @@ -3323,8 +3295,8 @@ extract-zip@^1.0.3: yauzl "^2.10.0" extsprintf@^1.2.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" + version "1.4.1" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.1.tgz#8d172c064867f235c0c84a596806d279bf4bcc07" fast-deep-equal@^3.1.1, fast-deep-equal@~3.1.3: version "3.1.3" @@ -3484,12 +3456,11 @@ glob@^7.1.1, glob@^7.1.3, glob@^7.1.6: once "^1.3.0" path-is-absolute "^1.0.0" -global-agent@^2.0.2: - version "2.2.0" - resolved "https://registry.yarnpkg.com/global-agent/-/global-agent-2.2.0.tgz#566331b0646e6bf79429a16877685c4a1fbf76dc" +global-agent@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/global-agent/-/global-agent-3.0.0.tgz#ae7cd31bd3583b93c5a16437a1afe27cc33a1ab6" dependencies: boolean "^3.0.1" - core-js "^3.6.5" es6-error "^4.1.1" matcher "^3.0.0" roarr "^2.15.3" @@ -3732,10 +3703,10 @@ is-ci@^2.0.0: ci-info "^2.0.0" is-ci@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-3.0.0.tgz#c7e7be3c9d8eef7d0fa144390bd1e4b88dc4c994" + version "3.0.1" + resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-3.0.1.tgz#db6ecbed1bd659c43dac0f45661e7674103d1867" dependencies: - ci-info "^3.1.1" + ci-info "^3.2.0" is-core-module@^2.2.0: version "2.8.0" @@ -3952,14 +3923,6 @@ jsonfile@^6.0.1: optionalDependencies: graceful-fs "^4.1.6" -keyboardevent-from-electron-accelerator@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/keyboardevent-from-electron-accelerator/-/keyboardevent-from-electron-accelerator-2.0.0.tgz#ace21b1aa4e47148815d160057f9edb66567c50c" - -keyboardevents-areequal@^0.2.1: - version "0.2.2" - resolved "https://registry.yarnpkg.com/keyboardevents-areequal/-/keyboardevents-areequal-0.2.2.tgz#88191ec738ce9f7591c25e9056de928b40277194" - keyv@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9" @@ -3971,8 +3934,8 @@ kind-of@^6.0.2, kind-of@^6.0.3: resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" klona@^2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.4.tgz#7bb1e3affb0cb8624547ef7e8f6708ea2e39dfc0" + version "2.0.5" + resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.5.tgz#d166574d90076395d9963aa7a928fabb8d76afbc" latest-version@^5.1.0: version "5.1.0" @@ -4071,8 +4034,8 @@ loader-runner@^4.2.0: resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.2.0.tgz#d7022380d66d14c5fb1d496b89864ebcfd478384" loader-utils@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.0.tgz#e4cace5b816d425a166b5f097e10cd12b36064b0" + version "2.0.2" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.2.tgz#d6e3b4fb81870721ae4e0868ab11dd638368c129" dependencies: big.js "^5.2.2" emojis-list "^3.0.0" @@ -4190,8 +4153,8 @@ mime-types@^2.1.12, mime-types@^2.1.27: mime-db "1.50.0" mime@^2.5.2: - version "2.5.2" - resolved "https://registry.yarnpkg.com/mime/-/mime-2.5.2.tgz#6e3dc6cc2b9510643830e5f19d5cb753da5eeabe" + version "2.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367" mimic-fn@^2.1.0: version "2.1.0" @@ -4266,8 +4229,8 @@ node-addon-api@^1.6.3: resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-1.7.2.tgz#3df30b95720b53c24e59948b49532b662444f54d" node-fetch@^2.6.0, node-fetch@^2.6.1, node-fetch@~2.6.1: - version "2.6.5" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.5.tgz#42735537d7f080a7e5f78b6c549b7146be1742fd" + version "2.6.6" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.6.tgz#1751a7c01834e8e1697758732e9efb6eeadfaf89" dependencies: whatwg-url "^5.0.0" @@ -4616,15 +4579,15 @@ rc@^1.2.8: minimist "^1.2.0" strip-json-comments "~2.0.1" -react-base16-styling@^0.8.0: - version "0.8.0" - resolved "https://registry.yarnpkg.com/react-base16-styling/-/react-base16-styling-0.8.0.tgz#6251b814b4e09efab3284ae1ecdd490f2c4111eb" +react-base16-styling@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/react-base16-styling/-/react-base16-styling-0.8.1.tgz#fa397b831a1c1050cb13cf29e3729e4780483b14" dependencies: "@types/base16" "^1.0.2" - "@types/lodash.curry" "^4.1.6" + "@types/lodash" "^4.14.176" base16 "^1.0.0" - color "^3.1.2" - csstype "^3.0.2" + color "^3.2.1" + csstype "^3.0.9" lodash.curry "^4.1.1" react-dom@^17.0.1: @@ -4649,12 +4612,12 @@ react-is@^16.8.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" react-json-tree@^0.15.0: - version "0.15.0" - resolved "https://registry.yarnpkg.com/react-json-tree/-/react-json-tree-0.15.0.tgz#16a5bbed761f711d1656de6c62818d40ddb09442" + version "0.15.1" + resolved "https://registry.yarnpkg.com/react-json-tree/-/react-json-tree-0.15.1.tgz#1682c3b59feaf192d4ff26cbb99d38f36d904a12" dependencies: - "@types/prop-types" "^15.7.3" + "@types/prop-types" "^15.7.4" prop-types "^15.7.2" - react-base16-styling "^0.8.0" + react-base16-styling "^0.8.1" react-lifecycles-compat@^3.0.4: version "3.0.4" @@ -5184,8 +5147,8 @@ terser@^5.7.2: source-map-support "~0.5.20" tmp-promise@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/tmp-promise/-/tmp-promise-3.0.2.tgz#6e933782abff8b00c3119d63589ca1fb9caaa62a" + version "3.0.3" + resolved "https://registry.yarnpkg.com/tmp-promise/-/tmp-promise-3.0.3.tgz#60a1a1cc98c988674fcbfd23b6e3367bdeac4ce7" dependencies: tmp "^0.2.0" @@ -5394,8 +5357,8 @@ vega-dataflow@^5.7.3, vega-dataflow@^5.7.4, vega-dataflow@~5.7.4: vega-util "^1.16.1" vega-embed@^6.2.1: - version "6.19.1" - resolved "https://registry.yarnpkg.com/vega-embed/-/vega-embed-6.19.1.tgz#05e2f5aa9d013344e8ed77245b3009a52e309e63" + version "6.20.0" + resolved "https://registry.yarnpkg.com/vega-embed/-/vega-embed-6.20.0.tgz#6a35e58695709b2c2d75eb767509498f44b3dc9f" dependencies: fast-json-patch "^3.1.0" json-stringify-pretty-compact "^3.0.0" @@ -5718,16 +5681,16 @@ vega@^5.20.0: vega-wordcloud "~4.1.3" verror@^1.10.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" + version "1.10.1" + resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.1.tgz#4bf09eeccf4563b109ed4b3d458380c972b0cdeb" dependencies: assert-plus "^1.0.0" core-util-is "1.0.2" extsprintf "^1.2.0" vscode-debugprotocol@^1.37.0: - version "1.49.0" - resolved "https://registry.yarnpkg.com/vscode-debugprotocol/-/vscode-debugprotocol-1.49.0.tgz#1ed0b7d9f2806df24ca9f18bb3485de060f85166" + version "1.50.1" + resolved "https://registry.yarnpkg.com/vscode-debugprotocol/-/vscode-debugprotocol-1.50.1.tgz#8aa7114ede9ded5ee49ba056593ad936116cfb42" warning@^4.0.2, warning@^4.0.3: version "4.0.3" @@ -5782,8 +5745,8 @@ webpack-sources@^3.2.0: resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.1.tgz#251a7d9720d75ada1469ca07dbb62f3641a05b6d" webpack@^5.24.3: - version "5.59.1" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.59.1.tgz#60c77e9aad796252153d4d7ab6b2d4c11f0e548c" + version "5.62.1" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.62.1.tgz#06f09b56a7b1bb13ed5137ad4b118358a90c9505" dependencies: "@types/eslint-scope" "^3.7.0" "@types/estree" "^0.0.50"